Initial commit: apps.supersam24.ru
This commit is contained in:
commit
e955e1da36
6566 files changed
+1271095
No files matched your search
Vendored
+25
@@ -0,0 +1,25 @@
|
||||
# Basic docker based environment
|
||||
# Necessary to trick dokku into building the documentation
|
||||
# using dockerfile instead of herokuish
|
||||
FROM ubuntu:17.04
|
||||
|
||||
# Add basic tools
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential \
|
||||
software-properties-common \
|
||||
curl \
|
||||
git \
|
||||
libxml2 \
|
||||
libffi-dev \
|
||||
libssl-dev && \
|
||||
LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php && \
|
||||
apt-get update && \
|
||||
apt-get install -y php7.2-cli php7.2-mbstring php7.2-xml php7.2-zip php7.2-intl php7.2-opcache php7.2-sqlite &&\
|
||||
apt-get clean &&\
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /code
|
||||
|
||||
VOLUME ["/code"]
|
||||
|
||||
CMD [ '/bin/bash' ]
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
The MIT License
|
||||
|
||||
CakePHP(tm) : The Rapid Development PHP Framework (http://cakephp.org)
|
||||
Copyright (c) 2005-present, Cake Software Foundation, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
Cake Software Foundation, Inc.
|
||||
1785 E. Sahara Avenue,
|
||||
Suite 490-204
|
||||
Las Vegas, Nevada 89104,
|
||||
United States of America.
|
||||
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
# Migrations plugin for CakePHP
|
||||
|
||||
[](LICENSE.txt)
|
||||
[](https://travis-ci.org/cakephp/migrations)
|
||||
[](https://codecov.io/github/cakephp/migrations?branch=master)
|
||||
[](https://packagist.org/packages/cakephp/migrations)
|
||||
|
||||
This is a Database Migrations system for CakePHP 3.
|
||||
|
||||
The plugin consists of a CakePHP shell wrapper for the [Phinx](http://phinx.org) migrations library.
|
||||
|
||||
## Installation
|
||||
|
||||
You can install this plugin into your CakePHP application using [Composer](http://getcomposer.org).
|
||||
|
||||
Run the following command
|
||||
```sh
|
||||
composer require cakephp/migrations
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
You can load the plugin using the shell command:
|
||||
|
||||
```
|
||||
bin/cake plugin load Migrations
|
||||
```
|
||||
|
||||
Or you can manually add the loading statement in the **src/Application.php** file of your application:
|
||||
```php
|
||||
public function bootstrap()
|
||||
{
|
||||
parent::bootstrap();
|
||||
$this->addPlugin('Migrations');
|
||||
}
|
||||
```
|
||||
Prior to 3.6.0
|
||||
```php
|
||||
Plugin::load('Migrations');
|
||||
```
|
||||
|
||||
Additionally, you will need to configure the ``default`` database configuration in your **config/app.php** file.
|
||||
|
||||
## Documentation
|
||||
|
||||
Full documentation of the plugin can be found on the [CakePHP Cookbook](https://book.cakephp.org/migrations/2/).
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"name": "cakephp/migrations",
|
||||
"description": "Database Migration plugin for CakePHP based on Phinx",
|
||||
"type": "cakephp-plugin",
|
||||
"keywords": ["cakephp", "migrations"],
|
||||
"homepage": "https://github.com/cakephp/migrations",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "CakePHP Community",
|
||||
"homepage": "https://github.com/cakephp/migrations/graphs/contributors"
|
||||
}
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/cakephp/migrations/issues",
|
||||
"forum": "https://stackoverflow.com/tags/cakephp",
|
||||
"irc": "irc://irc.freenode.org/cakephp",
|
||||
"source": "https://github.com/cakephp/migrations"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.6.0",
|
||||
"robmorgan/phinx": "^0.10.3|^0.11.1",
|
||||
"cakephp/orm": "^3.6.0",
|
||||
"cakephp/cache": "^3.6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^5.7.14|^6.0",
|
||||
"cakephp/cakephp": "^3.6.0",
|
||||
"cakephp/bake": "^1.7.0",
|
||||
"cakephp/cakephp-codesniffer": "^3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Migrations\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Cake\\Test\\": "vendor/cakephp/cakephp/tests/",
|
||||
"Migrations\\PHPStan\\": "tests/PHPStan/",
|
||||
"Migrations\\Test\\": "tests/",
|
||||
"TestApp\\": "tests/test_app/App/",
|
||||
"TestBlog\\": "tests/test_app/Plugin/TestBlog/src/"
|
||||
}
|
||||
},
|
||||
"suggest": {
|
||||
"cakephp/bake": "If you want to generate migrations."
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"scripts": {
|
||||
"check": [
|
||||
"@cs-check",
|
||||
"@test"
|
||||
],
|
||||
"cs-check": "phpcs --colors -p src/ tests/",
|
||||
"cs-fix": "phpcbf --colors -p src/ tests/",
|
||||
"phpstan": "phpstan analyse src/",
|
||||
"phpstan-setup": "cp composer.json composer.backup && composer require --dev phpstan/phpstan-shim:^0.11 && mv composer.backup composer.json",
|
||||
"test": "phpunit",
|
||||
"test-coverage": "phpunit --coverage-clover=clover.xml"
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
# Generate the HTML output.
|
||||
FROM markstory/cakephp-docs-builder as builder
|
||||
|
||||
COPY docs /data/docs
|
||||
|
||||
RUN cd /data/docs-builder && \
|
||||
# In the future repeat website for each version
|
||||
make website LANGS="en fr ja pt ru" SOURCE=/data/docs DEST=/data/website/
|
||||
|
||||
# Build a small nginx container with just the static site in it.
|
||||
FROM nginx:1.15-alpine
|
||||
|
||||
COPY --from=builder /data/website /data/website
|
||||
COPY --from=builder /data/docs-builder/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Move each version into place
|
||||
RUN cp -R /data/website/html/* /usr/share/nginx/html \
|
||||
&& rm -rf /data/website/
|
||||
|
||||
RUN ln -s /usr/share/nginx/html /usr/share/nginx/html/2.x
|
||||
Whitespace-only changes.
+48
@@ -0,0 +1,48 @@
|
||||
# Global configuration information used across all the
|
||||
# translations of documentation.
|
||||
#
|
||||
# Import the base theme configuration
|
||||
from cakephpsphinx.config.all import *
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '2.x'
|
||||
|
||||
# The search index version.
|
||||
search_version = 'migrations-2'
|
||||
|
||||
# The marketing display name for the book.
|
||||
version_name = ''
|
||||
|
||||
# Project name shown in the black header bar
|
||||
project = 'CakePHP Migrations'
|
||||
|
||||
# Other versions that display in the version picker menu.
|
||||
version_list = [
|
||||
{'name': '2.x', 'number': '/migrations/2.x', 'title': '2.x', 'current': True},
|
||||
]
|
||||
|
||||
# Languages available.
|
||||
languages = ['en', 'fr', 'ja', 'pt', 'ru']
|
||||
|
||||
# The GitHub branch name for this version of the docs
|
||||
# for edit links to point at.
|
||||
branch = 'master'
|
||||
|
||||
# Current version being built
|
||||
version = '2.x'
|
||||
|
||||
# Language in use for this directory.
|
||||
language = 'en'
|
||||
|
||||
show_root_link = True
|
||||
|
||||
repository = 'cakephp/migrations'
|
||||
|
||||
source_path = 'docs/'
|
||||
|
||||
hide_page_contents = ('search', '404', 'contents')
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import sys, os
|
||||
|
||||
# Append the top level directory of the docs, so we can import from the config dir.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# Pull in all the configuration options defined in the global config file..
|
||||
from config.all import *
|
||||
|
||||
language = 'en'
|
||||
@@ -0,0 +1,5 @@
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: CakePHP Migrations
|
||||
|
||||
/index
|
||||
+1094
File diff suppressed because it is too large.
Load diff
+9
@@ -0,0 +1,9 @@
|
||||
import sys, os
|
||||
|
||||
# Append the top level directory of the docs, so we can import from the config dir.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# Pull in all the configuration options defined in the global config file..
|
||||
from config.all import *
|
||||
|
||||
language = 'fr'
|
||||
@@ -0,0 +1,5 @@
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: CakePHP Migrations
|
||||
|
||||
/index
|
||||
+1131
File diff suppressed because it is too large.
Load diff
+9
@@ -0,0 +1,9 @@
|
||||
import sys, os
|
||||
|
||||
# Append the top level directory of the docs, so we can import from the config dir.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# Pull in all the configuration options defined in the global config file..
|
||||
from config.all import *
|
||||
|
||||
language = 'ja'
|
||||
@@ -0,0 +1,5 @@
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: CakePHP Migrations
|
||||
|
||||
/index
|
||||
+1050
File diff suppressed because it is too large.
Load diff
+9
@@ -0,0 +1,9 @@
|
||||
import sys, os
|
||||
|
||||
# Append the top level directory of the docs, so we can import from the config dir.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# Pull in all the configuration options defined in the global config file..
|
||||
from config.all import *
|
||||
|
||||
language = 'pt'
|
||||
@@ -0,0 +1,5 @@
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: CakePHP Migrations
|
||||
|
||||
/index
|
||||
+940
@@ -0,0 +1,940 @@
|
||||
Migrations
|
||||
##########
|
||||
|
||||
Migrations é um plugin suportado pela equipe oficial do CakePHP que ajuda você a
|
||||
fazer mudanças no **schema** do banco de dados utilizando arquivos PHP,
|
||||
que podem ser versionados utilizando um sistema de controle de versão.
|
||||
|
||||
Ele permite que você atualize suas tabelas ao longo do tempo. Ao invés de
|
||||
escrever modificações de **schema** via SQL, este plugin permite que você
|
||||
utilize um conjunto intuitivo de métodos para fazer mudanças no seu banco de
|
||||
dados.
|
||||
|
||||
Esse plugin é um **wrapper** para a biblioteca `Phinx <https://phinx.org/>`_.
|
||||
|
||||
Instalação
|
||||
==========
|
||||
|
||||
Por padrão o plugin é instalado junto com o esqueleto da aplicação.
|
||||
Se você o removeu e quer reinstalá-lo, execute o comando a seguir a partir do
|
||||
diretório **ROOT** da sua aplicação
|
||||
(onde o arquivo composer.json está localizado):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
$ php composer.phar require cakephp/migrations "@stable"
|
||||
|
||||
# Or if composer is installed globally
|
||||
|
||||
$ composer require cakephp/migrations "@stable"
|
||||
|
||||
Para usar o plugin você precisa carregá-lo no arquivo **config/bootstrap.php**
|
||||
da sua aplicação. Você pode usar o
|
||||
:ref:`shell de plugins do CakePHP <https://book.cakephp.org/3.0/pt/console-and-shells/plugin-shell.html>` para carregar e descarregar
|
||||
plugins do seu arquivo **config/bootstrap.php**::
|
||||
|
||||
$ bin/cake plugin load Migrations
|
||||
|
||||
Ou você pode carregar o plugin editando seu arquivo **config/bootstrap.php** e
|
||||
adicionando a linha::
|
||||
|
||||
Plugin::load('Migrations');
|
||||
|
||||
Adicionalmente, você precisará configurar o banco de dados padrão da sua
|
||||
aplicação, no arquivo **config/app.php** como explicado na seção
|
||||
:ref:`Configuração de banco de dados <https://book.cakephp.org/3.0/pt/orm/database-basics.html#database-configuration>`.
|
||||
|
||||
Visão Geral
|
||||
===========
|
||||
|
||||
Uma migração é basicamente um arquivo PHP que descreve as mudanças a
|
||||
serem feitas no banco de dados. Um arquivo de migração pode criar ou excluir
|
||||
tabelas, adicionar ou remover colunas, criar índices e até mesmo inserir
|
||||
dados em seu banco de dados.
|
||||
|
||||
Aqui segue um exemplo de migração::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class CreateProducts extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* More information on this method is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
* @return void
|
||||
*/
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table->addColumn('name', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('description', 'text', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('created', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('modified', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->create();
|
||||
}
|
||||
}
|
||||
|
||||
Essa migração irá adicionar uma tabela chamada ``products`` ao banco de dados
|
||||
com as seguintes colunas:
|
||||
|
||||
- ``id`` coluna do tipo ``integer`` como chave primária
|
||||
- ``name`` coluna do tipo ``string``
|
||||
- ``description`` coluna do tipo ``text``
|
||||
- ``created`` coluna do tipo ``datetime``
|
||||
- ``modified`` coluna do tipo ``datetime``
|
||||
|
||||
.. tip::
|
||||
|
||||
A coluna de chave primária ``id`` será adicionada **implicitamente**.
|
||||
|
||||
.. note::
|
||||
|
||||
Note que este arquivo descreve como o banco de dados deve ser **após** a
|
||||
aplicação da migração. Neste ponto, a tabela ``products``ainda não existe
|
||||
no banco de dados, nós apenas criamos um arquivo que é capaz de criar a
|
||||
tabela ``products`` com seus devidos campos ou excluir a tabela caso uma
|
||||
operação rollback seja executada.
|
||||
|
||||
Com o arquivo criado na pasta **config/MIgrations**, você será capaz de executar
|
||||
o comando abaixo para executar as migrações no seu banco de dados::
|
||||
|
||||
bin/cake migrations migrate
|
||||
|
||||
O comando seguinte irá executar um **rollback** na migração e irá excluir a
|
||||
tabela recém criada::
|
||||
|
||||
bin/cake migrations rollback
|
||||
|
||||
Criando migrations
|
||||
==================
|
||||
|
||||
Arquivos de migração são armazeados no diretório **config/Migrations** da
|
||||
sua aplicação. O nome dos arquivos de migração têm como prefixo a data
|
||||
em que foram criados, no formato **YYYYMMDDHHMMSS_MigrationName.php**. Aqui
|
||||
estão exemplos de arquivos de migração:
|
||||
|
||||
* 20160121163850_CreateProducts.php
|
||||
* 20160210133047_AddRatingToProducts.php
|
||||
|
||||
A maneira mais fácil de criar um arquivo de migrações é usando o
|
||||
:doc:`/bake/usage` a linha de comando.
|
||||
|
||||
Por favor, leia a `documentação do Phinx <https://book.cakephp.org/phinx/0/en/migrations.html>`
|
||||
a fim de conhecer a lista completa dos métodos que você pode usar para escrever
|
||||
os arquivos de migração.
|
||||
|
||||
.. note::
|
||||
|
||||
Ao gerar as migrações através do ``bake`` você ainda pode alterá-las antes
|
||||
da sua execução, caso seja necessário.
|
||||
|
||||
Sintaxe
|
||||
-------
|
||||
|
||||
A sintaxe do ``bake`` para a geração de migrações segue o formato abaixo::
|
||||
|
||||
$ bin/cake bake migration CreateProducts name:string description:text created modified
|
||||
|
||||
Quando utilizar o ``bake`` para criar as migrações, você normalmente precisará
|
||||
informar os seguintes dados::
|
||||
|
||||
* o nome da migração que você irá gerar (``CreateProducts`` por exemplo)
|
||||
* as colunas da tabela que serão adicionadas ou removidas na migração
|
||||
(``name:string description:text created modified`` no nosso caso)
|
||||
|
||||
Devido às convenções, nem todas as alterações de schema podem ser realizadas
|
||||
através destes comandos.
|
||||
|
||||
Além disso, você pode criar um arquivo de migração vazio caso deseje ter um
|
||||
controle total do que precisa ser executado. Para isto, apenas omita a definição
|
||||
das colunas::
|
||||
|
||||
$ bin/cake migrations create MyCustomMigration
|
||||
|
||||
Nomenclatura de migrations
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
A nomenclatura das migrações pode seguir qualquer um dos padrões apresentados a
|
||||
seguir:
|
||||
|
||||
* (``/^(Create)(.*)/``) Cria a tabela especificada.
|
||||
* (``/^(Drop)(.*)/``) Exclui a tabela especificada.
|
||||
Ignora campos especificados nos argumentos
|
||||
* (``/^(Add).*(?:To)(.*)/``) Adiciona campos a
|
||||
tabela especificada
|
||||
* (``/^(Remove).*(?:From)(.*)/``) Remove campos de uma
|
||||
tabela específica
|
||||
* (``/^(Alter)(.*)/``) Altera a tabela especificada. Um apelido para
|
||||
um CreateTable seguido de um AlterTable
|
||||
* (``/^(Alter).*(?:On)(.*)/``) Alterar os campos da tabela especificada
|
||||
|
||||
Você também pode usar ``underscore_form`` como nome das suas **migrations**.
|
||||
Ex.: ``create_products``.
|
||||
|
||||
.. versionadded:: cakephp/migrations 1.5.2
|
||||
|
||||
A partir da versão 1.5.2 do `plugin migrations <https://github.com/cakephp/migrations/>`_,
|
||||
o nome dos arquivos de migrações são colocados automaticamente no padrão
|
||||
**camel case**.
|
||||
Esta versão do plugin está disponível apenas a partir da versão 3.1 do
|
||||
CakePHP.
|
||||
Antes disto, o padrão de nomes do plugin migrations utilizava a nomenclatura
|
||||
baseada em **underlines**, ex.: ``20160121164955_create_products.php``.
|
||||
|
||||
.. warning::
|
||||
|
||||
O nome das migrações são usados como nomes de classe, e podem colidir com
|
||||
outras migrações se o nome das classes não forem únicos. Neste caso, pode
|
||||
ser necessário sobreescrever manualmente os nomes mais tarde ou simplesmente
|
||||
mudar os nomes que você está especificando.
|
||||
|
||||
Definição de colunas
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Quando utilizar colunas na linha de comando, pode ser útil lembrar que eles seguem o
|
||||
seguinte padrão::
|
||||
|
||||
fieldName:fieldType[length]:indexType:indexName
|
||||
|
||||
Por exemplo, veja formas válidas de especificar um campo de e-mail:
|
||||
|
||||
* ``email:string:unique``
|
||||
* ``email:string:unique:EMAIL_INDEX``
|
||||
* ``email:string[120]:unique:EMAIL_INDEX``
|
||||
|
||||
O parâmetro ``length`` para o ``fieldType`` é opcional e deve sempre ser
|
||||
escrito entre colchetes
|
||||
|
||||
Os campos ``created`` e ``modified`` serão automaticamente definidos
|
||||
como ``datetime``.
|
||||
|
||||
Os tipos de campos são genericamente disponibilizados pela biblioteca ``Phinx``.
|
||||
Eles podem ser:
|
||||
|
||||
* string
|
||||
* text
|
||||
* integer
|
||||
* biginteger
|
||||
* float
|
||||
* decimal
|
||||
* datetime
|
||||
* timestamp
|
||||
* time
|
||||
* date
|
||||
* binary
|
||||
* boolean
|
||||
* uuid
|
||||
|
||||
Há algumas heurísticas para a escolha de tipos de campos que não são especificados
|
||||
ou são definidos com valor inválido. O tipo de campo padrão é ``string``;
|
||||
|
||||
* id: integer
|
||||
* created, modified, updated: datetime
|
||||
|
||||
Criando uma tabela
|
||||
------------------
|
||||
|
||||
Você pode utilizar o ``bake`` para criar uma tabela::
|
||||
|
||||
$ bin/cake bake migration CreateProducts name:string description:text created modified
|
||||
|
||||
A linha de comando acima irá gerar um arquivo de migração parecido com este::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class CreateProducts extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* More information on this method is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
* @return void
|
||||
*/
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table->addColumn('name', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('description', 'text', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('created', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->addColumn('modified', 'datetime', [
|
||||
'default' => null,
|
||||
'null' => false,
|
||||
]);
|
||||
$table->create();
|
||||
}
|
||||
}
|
||||
|
||||
Adicionando colunas a uma tabela existente
|
||||
------------------------------------------
|
||||
|
||||
Se o nome da migração na linha de comando estiver na forma "AddXXXToYYY" e
|
||||
for seguido por uma lista de nomes de colunas e tipos, então o arquivo de
|
||||
migração com o código para criar as colunas será gerado::
|
||||
|
||||
$ bin/cake bake migration AddPriceToProducts price:decimal
|
||||
|
||||
A linha de comando acima irá gerar um arquivo com o seguinte conteúdo::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class AddPriceToProducts extends AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table->addColumn('price', 'decimal')
|
||||
->update();
|
||||
}
|
||||
}
|
||||
|
||||
Adicionando uma coluna como indice a uma tabela
|
||||
-----------------------------------------------
|
||||
|
||||
Também é possível adicionar índices a colunas::
|
||||
|
||||
$ bin/cake bake migration AddNameIndexToProducts name:string:index
|
||||
|
||||
irá gerar::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class AddNameIndexToProducts extends AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table->addColumn('name', 'string')
|
||||
->addIndex(['name'])
|
||||
->update();
|
||||
}
|
||||
}
|
||||
|
||||
Especificando o tamanho do campo
|
||||
--------------------------------
|
||||
|
||||
.. versionadded:: cakephp/migrations 1.4
|
||||
|
||||
Se você precisar especificar o tamanho do campo, você pode fazer isto entre
|
||||
colchetes logo após o tipo do campo, ex.::
|
||||
|
||||
$ bin/cake bake migration AddFullDescriptionToProducts full_description:string[60]
|
||||
|
||||
Executar o comando acima irá gerar::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class AddFullDescriptionToProducts extends AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table->addColumn('full_description', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 60,
|
||||
'null' => false,
|
||||
])
|
||||
->update();
|
||||
}
|
||||
}
|
||||
|
||||
Se o tamanho não for especificado, os seguintes padrões serão utilizados:
|
||||
|
||||
* string: 255
|
||||
* integer: 11
|
||||
* biginteger: 20
|
||||
|
||||
Alterar uma coluna de uma tabela
|
||||
-----------------------------------
|
||||
|
||||
Da mesma maneira, você pode gerar uma migração para alterar uma coluna usando a
|
||||
linha de comando, se o nome da migração estiver no formato "X""AlterXXXOnYYY":
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
bin/cake bake migration AlterPriceOnProducts name:float
|
||||
|
||||
Cria o arquivo::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class AlterPriceFromProducts extends AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table->changeColumn('name', 'float');
|
||||
$table->update();
|
||||
}
|
||||
}
|
||||
|
||||
Removendo uma coluna de uma tabela
|
||||
----------------------------------
|
||||
|
||||
Da mesma forma, você pode gerar uma migração para remover uma coluna
|
||||
utilizando a linha de comando, se o nome da migração estiver na forma
|
||||
"RemoveXXXFromYYY"::
|
||||
|
||||
$ bin/cake bake migration RemovePriceFromProducts price
|
||||
|
||||
Cria o arquivo::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class RemovePriceFromProducts extends AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table->removeColumn('price');
|
||||
}
|
||||
}
|
||||
|
||||
Gerando migrações a partir de uma base de dados existente
|
||||
=========================================================
|
||||
|
||||
Se você está trabalhando com um banco de dados pré-existente e quer começar
|
||||
a usar migrações, ou para versionar o schema inicial da base de dados da sua
|
||||
aplicação, você pode executar o comando ``migration_snapshot``::
|
||||
|
||||
$ bin/cake bake migration_snapshot Initial
|
||||
|
||||
Isto irá gerar um arquivo de migração chamado **YYYYMMDDHHMMSS_Initial.php**
|
||||
contendo todas as instruções CREATE para todas as tabelas no seu banco de dados.
|
||||
|
||||
Por padrão, o snapshot será criado a partir da conexão ``default`` definida na
|
||||
configuração.
|
||||
Se você precisar fazer o bake de um snapshot de uma fonte de dados diferente,
|
||||
você pode utilizar a opção ``--connection``::
|
||||
|
||||
$ bin/cake bake migration_snapshot Initial --connection my_other_connection
|
||||
|
||||
Você também pode definir que o snapshot inclua apenas as tabelas para as quais
|
||||
você tenha definido models correspendentes, utilizando a flag
|
||||
``require-table``::
|
||||
|
||||
$ bin/cake bake migration_snapshot Initial --require-table
|
||||
|
||||
Quando utilizar a flag ``--require-table``, o shell irá olhar através das
|
||||
classes do diretório ``Table`` da sua aplicação e apenas irá adicionar no
|
||||
snapshot as tabelas lá definidas.
|
||||
|
||||
A mesma lógica será aplicada implicitamente se você quiser fazer o bake de um
|
||||
snapshot para um plugin. Para fazer isso, você precisa usar a opção
|
||||
``--plugin``, veja a seguir::
|
||||
|
||||
$ bin/cake bake migration_snapshot Initial --plugin MyPlugin
|
||||
|
||||
Apenas as tabelas que tiverem um objeto ``Table`` definido serão adicionadas
|
||||
ao snapshot do seu plugin.
|
||||
|
||||
.. note::
|
||||
|
||||
Quando fizer o bake de um snapshot para um plugin, os arquivos de migrações
|
||||
serão criados no diretório **config/Migrations** do seu plugin.
|
||||
|
||||
Fique atento que quando você faz o bake de um snapshot, ele é automaticamente
|
||||
adicionado ao log do phinx como migrado.
|
||||
|
||||
Gerando um *diff* entre dois estados da base de dados
|
||||
=====================================================
|
||||
|
||||
.. versionadded:: cakephp/migrations 1.6.0
|
||||
|
||||
Você pode gerar um arquivo de migração que agrupará todas as diferenças entre
|
||||
dois estados de uma base de dados usando ``migration_diff``. Para fazê-lo,
|
||||
você pode usar o seguinte comando::
|
||||
|
||||
$ bin/cake bake migration_diff NomeDasMigrações
|
||||
|
||||
De forma a ter um ponto de comparação do estado atual da sua base de dados, a
|
||||
*shell* de ``migrations`` gerará um arquivo de *dump* após cada chamada de
|
||||
``migrate`` ou ``rollback``. O arquivo de *dump* é um arquivo contendo o
|
||||
estado completo do esquema da sua base de dados em um determinado instante no
|
||||
tempo.
|
||||
|
||||
Uma vez gerado o arquivo de *dump*, cada modificação que você fizer
|
||||
diretamente no seu sistema de gerenciamento da base de dados será adicionada
|
||||
quando você chamar o comando ``bake migration_diff``.
|
||||
|
||||
Por padrão, o *diff* será criado através de uma conexão com a base de dados
|
||||
definida na configuração de conexão ``default``.
|
||||
Se você precisar criar um *diff* de uma fonte de dados diferente, você pode
|
||||
usar a opção ``--connection``::
|
||||
|
||||
$ bin/cake bake migration_diff NomeDasMigrações --connection minha_outra_conexão
|
||||
|
||||
Se você quiser usar a funcionalidade de *diff* em uma aplicação que já possui
|
||||
um histórico de migrações, você precisará criar manualmente o arquivo de
|
||||
*dump* a ser usado como base da comparação::
|
||||
|
||||
$ bin/cake migrations dump
|
||||
|
||||
O estado da base de dados deve ser o mesmo que você teria caso você tivesse
|
||||
migrado todas as suas migrações antes de criar o arquivo de *dump*.
|
||||
Uma vez que o arquivo de *dump* for gerado, você pode começar a fazer
|
||||
modificações na sua base de dados e usar o comando ``bake migration_diff``
|
||||
sempre que desejar.
|
||||
|
||||
.. note::
|
||||
|
||||
A *shell* de migrações não é capaz de detectar colunas renomeadas.
|
||||
|
||||
Os Comandos
|
||||
===========
|
||||
|
||||
``migrate`` : Aplicando migrações
|
||||
---------------------------------
|
||||
|
||||
Depois de ter gerado ou escrito seu arquivo de migração, você precisa executar
|
||||
o seguinte comando para aplicar as mudanças a sua base de dados::
|
||||
|
||||
# Executa todas as migrações
|
||||
$ bin/cake migrations migrate
|
||||
|
||||
# Execute uma migração específica utilizando a opção ``--target`` ou ``-t``
|
||||
# O valor é um timestamp que serve como prefixo para cada migração::
|
||||
$ bin/cake migrations migrate -t 20150103081132
|
||||
|
||||
# Por padrão, as migrações ficam no diretório **config/Migrations**. Você
|
||||
# pode especificar um diretório utilizando a opção ``--source`` ou ``-s``.
|
||||
# O comando abaixo executa as migrações no diretório **config/Alternate**
|
||||
$ bin/cake migrations migrate -s Alternate
|
||||
|
||||
# Você pode executar as migrações de uma conexão diferente da ``default``
|
||||
# utilizando a opção ``--connection`` ou ``-c``.
|
||||
$ bin/cake migrations migrate -c my_custom_connection
|
||||
|
||||
# Migrações também podem ser executadas para plugins. Simplesmente utilize
|
||||
# a opção ``--plugin`` ou ``-p``
|
||||
$ bin/cake migrations migrate -p MyAwesomePlugin
|
||||
|
||||
``rollback`` : Revertendo migrações
|
||||
-----------------------------------
|
||||
|
||||
O comando rollback é utilizado para desfazer migrações realizadas anteriormente
|
||||
pelo plugin Migrations. É o inverso do comando ``migrate``::
|
||||
|
||||
# Você pode desfazer uma migração anterior utilizando o
|
||||
# comando ``rollback``::
|
||||
$ bin/cake migrations rollback
|
||||
|
||||
# Você também pode passar a versão da migração para voltar
|
||||
# para uma versão específica::
|
||||
$ bin/cake migrations rollback -t 20150103081132
|
||||
|
||||
Você também pode utilizar as opções ``--source``, ``--connection`` e
|
||||
``--plugin`` exatamente como no comando ``migrate``.
|
||||
|
||||
``status`` : Status da migração
|
||||
-------------------------------
|
||||
|
||||
O comando status exibe uma lista de todas as migrações juntamente com seu
|
||||
status. Você pode utilizar este comando para ver quais migrações foram
|
||||
executadas::
|
||||
|
||||
$ bin/cake migrations status
|
||||
|
||||
Você também pode ver os resultados como JSON utilizando a opção
|
||||
``--format`` (ou ``-f``)::
|
||||
|
||||
$ bin/cake migrations status --format json
|
||||
|
||||
Você também pode utilizar as opções ``--source``, ``--connection`` e
|
||||
``--plugin`` exatamente como no comando ``migrate``.
|
||||
|
||||
``mark_migrated`` : Marcando uma migração como migrada
|
||||
------------------------------------------------------
|
||||
|
||||
.. versionadded:: 1.4.0
|
||||
|
||||
Algumas vezes pode ser útil marcar uma lista de migrações como migrada sem
|
||||
efetivamente executá-las.
|
||||
Para fazer isto, você pode usar o comando ``mark_migrated``. O comando é
|
||||
bastante semelhante aos outros comandos.
|
||||
|
||||
Você pode marcar todas as migrações como migradas utilizando este comando::
|
||||
|
||||
$ bin/cake migrations mark_migrated
|
||||
|
||||
Você também pode marcar todas as migrações de uma versão específica
|
||||
utilizando a opção ``--target``::
|
||||
|
||||
$ bin/cake migrations mark_migrated --target=20151016204000
|
||||
|
||||
Se você não quer marcar a migração alvo como migrada durante o processo, você
|
||||
pode utilizar a opção ``--exclude``::
|
||||
|
||||
$ bin/cake migrations mark_migrated --target=20151016204000 --exclude
|
||||
|
||||
Finalmente, se você deseja marcar somente a migração alvo como migrada,
|
||||
você pode utilizar a opção ``--only``::
|
||||
|
||||
$ bin/cake migrations mark_migrated --target=20151016204000 --only
|
||||
|
||||
Você também pode utilizar as opções ``--source``, ``--connection`` e
|
||||
``--plugin`` exatamente como no comando ``migrate``.
|
||||
|
||||
.. note::
|
||||
|
||||
Quando você criar um snapshot utilizando o bake com o comando
|
||||
``cake bake migration_snapshot``, a migração criada será automaticamente
|
||||
marcada como migrada.
|
||||
|
||||
.. deprecated:: 1.4.0
|
||||
|
||||
A seguinte maneira de utilizar o comando foi depreciada. Use somente se
|
||||
você estiver utilizando uma versão do plugin inferior a 1.4.0.
|
||||
|
||||
Este comando espera um número de versão de migração como argumento::
|
||||
|
||||
$ bin/cake migrations mark_migrated
|
||||
|
||||
Se você deseja marcar todas as migrações como migradas, você pode utilizar
|
||||
o valor especial ``all``. Se você o utilizar, ele irá marcar todas as migrações
|
||||
como migradas::
|
||||
|
||||
$ bin/cake migrations mark_migrated all
|
||||
|
||||
``seed`` : Populando seu banco de dados
|
||||
---------------------------------------
|
||||
|
||||
A partir da versão 1.5.5, você pode usar a **shell** de ``migrations`` para
|
||||
popular seu banco de dados. Essa função é oferecida graças ao
|
||||
`recurso de seed da biblioteca Phinx <https://book.cakephp.org/phinx/0/en/seeding.html>`_.
|
||||
Por padrão, arquivos **seed** ficarão no diretório ``config/Seeds`` de sua
|
||||
aplicação. Por favor, tenha certeza de seguir as
|
||||
`instruções do Phinx para construir seus arquivos de seed <https://book.cakephp.org/phinx/0/en/seeding.html#creating-a-new-seed-class>`_.
|
||||
|
||||
Assim como nos **migrations**, uma interface do ```bake`` é oferecida para gerar
|
||||
arquivos de **seed**::
|
||||
|
||||
# This will create a ArticlesSeed.php file in the directory config/Seeds of your application
|
||||
# By default, the table the seed will try to alter is the "tableized" version of the seed filename
|
||||
$ bin/cake bake seed Articles
|
||||
|
||||
# You specify the name of the table the seed files will alter by using the ``--table`` option
|
||||
$ bin/cake bake seed Articles --table my_articles_table
|
||||
|
||||
# You can specify a plugin to bake into
|
||||
$ bin/cake bake seed Articles --plugin PluginName
|
||||
|
||||
# You can specify an alternative connection when generating a seeder.
|
||||
$ bin/cake bake seed Articles --connection connection
|
||||
|
||||
.. versionadded:: cakephp/migrations 1.6.4
|
||||
|
||||
As opções ``--data``, ``--limit`` e ``--fields`` foram adicionadas para
|
||||
exportar dados da sua base de dados.
|
||||
|
||||
A partir da versão 16.4, o comando ``bake seed`` permite que você crie um
|
||||
arquivo de *seed* com dados exportados da sua base de dados com o uso da
|
||||
*flag* ``--data``::
|
||||
|
||||
$ bin/cake bake seed --data Articles
|
||||
|
||||
Por padrão, esse comando exportará todas as linhas encontradas na sua
|
||||
tabela. Você pode limitar o número de linhas a exportar usando a opção
|
||||
``--limit``::
|
||||
|
||||
# Exportará apenas as 10 primeiras linhas encontradas
|
||||
$ bin/cake bake seed --data --limit 10 Articles
|
||||
|
||||
Se você deseja incluir apenas uma seleção dos campos da tabela no seu
|
||||
arquivo de *seed*, você pode usar a opção ``--fields``. Ela recebe a
|
||||
lista de campos a incluir na forma de uma *string* separada por
|
||||
vírgulas::
|
||||
|
||||
# Exportará apenas os campos `id`, `title` e `excerpt`
|
||||
$ bin/cake bake seed --data --fields id,title,excerpt Articles
|
||||
|
||||
.. tip::
|
||||
|
||||
Você pode utilizar ambas as opções ``--limit`` e ``--fields``
|
||||
simultaneamente em uma mesma chamada.
|
||||
|
||||
Para popular seu banco de dados, você pode usar o subcomando ``seed``::
|
||||
|
||||
# Without parameters, the seed subcommand will run all available seeders
|
||||
# in the target directory, in alphabetical order.
|
||||
$ bin/cake migrations seed
|
||||
|
||||
# You can specify only one seeder to be run using the `--seed` option
|
||||
$ bin/cake migrations seed --seed ArticlesSeed
|
||||
|
||||
# You can run seeders from an alternative directory
|
||||
$ bin/cake migrations seed --source AlternativeSeeds
|
||||
|
||||
# You can run seeders from a plugin
|
||||
$ bin/cake migrations seed --plugin PluginName
|
||||
|
||||
# You can run seeders from a specific connection
|
||||
$ bin/cake migrations seed --connection connection
|
||||
|
||||
Esteja ciente que, ao oposto das **migrations**, **seeders** não são
|
||||
versionados, o que significa que o mesmo **seeder** pode ser aplicado diversas
|
||||
vezes.
|
||||
|
||||
Usando migrations em plugins
|
||||
============================
|
||||
|
||||
**Plugins** também podem oferecer **migrations**. Isso faz com que **plugins**
|
||||
que são planejados para serem distribuídos tornem-se muito mais práticos e
|
||||
fáceis de instalar. Todos os comandos do plugin **Migrations** suportam a opção
|
||||
``--plugin`` ou ``-p``, que por sua vez vai delegar a execução da tarefa ao
|
||||
escopo relativo a um determinado **plugin**::
|
||||
|
||||
$ bin/cake migrations status -p PluginName
|
||||
|
||||
$ bin/cake migrations migrate -p PluginName
|
||||
|
||||
Executando migrations em ambientes fora da linha de comando
|
||||
===========================================================
|
||||
|
||||
.. versionadded:: cakephp/migrations 1.2.0
|
||||
|
||||
Desde o lançamento da versão 1.2 do plugin, você pode executar **migrations**
|
||||
fora da linha de comando, diretamente de uma aplicação, ao usar a nova classe
|
||||
``Migrations``. Isso pode ser muito útil caso você esteja desenvolvendo um
|
||||
instalador de **plugins** para um CMS, para exemplificar.
|
||||
|
||||
A classe ``Migrations`` permite que você execute os seguintes comandos
|
||||
disponíveis na **shell**:
|
||||
|
||||
* migrate
|
||||
* rollback
|
||||
* markMigrated
|
||||
* status
|
||||
* seed
|
||||
|
||||
Cada um desses comandos tem um método definido na classe ``Migrations``.
|
||||
|
||||
Veja como usá-la::
|
||||
|
||||
use Migrations\Migrations;
|
||||
|
||||
$migrations = new Migrations();
|
||||
|
||||
// Retornará um array de todos migrations e seus status
|
||||
$status = $migrations->status();
|
||||
|
||||
// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
|
||||
$migrate = $migrations->migrate();
|
||||
|
||||
// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
|
||||
$rollback = $migrations->rollback();
|
||||
|
||||
// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
|
||||
$markMigrated = $migrations->markMigrated(20150804222900);
|
||||
|
||||
// Retornará true se bem sucedido. Se um erro ocorrer, uma exceção será lançada
|
||||
$seeded = $migrations->seed();
|
||||
|
||||
Os métodos aceitam um **array** de parâmetros que devem combinar com as opções
|
||||
dos comandos::
|
||||
|
||||
use Migrations\Migrations;
|
||||
|
||||
$migrations = new Migrations();
|
||||
|
||||
// Retornará um array de todos migrations e seus status
|
||||
$status = $migrations->status(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);
|
||||
|
||||
Você pode passar qualquer opção que esteja disponível pelos comandos **shell**.
|
||||
A única exceção é o comando ``markMigrated`` que espera um número de versão a
|
||||
ser marcado como migrado, como primeiro argumento. Passe o **array** de
|
||||
parâmetros como segundo argumento nesse caso.
|
||||
|
||||
Opcionalmente, você pode passar esses parâmetros pelo construtor da classe.
|
||||
Eles serão usados como padrão evitando que você tenha que passá-los em cada
|
||||
chamada do método::
|
||||
|
||||
use Migrations\Migrations;
|
||||
|
||||
$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);
|
||||
|
||||
// Todas as chamadas de métodos serão executadas usando os parâmetros passados pelo construtor da classe
|
||||
$status = $migrations->status();
|
||||
$migrate = $migrations->migrate();
|
||||
|
||||
Se você precisar sobrescrever um ou mais parâmetros definidos previamente, você
|
||||
pode passá-los para um método::
|
||||
|
||||
use Migrations\Migrations;
|
||||
|
||||
$migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']);
|
||||
|
||||
// Essa chamada será feita com a conexão "custom"
|
||||
$status = $migrations->status();
|
||||
// Essa chamada será feita com a conexão "default"
|
||||
$migrate = $migrations->migrate(['connection' => 'default']);
|
||||
|
||||
Dicas e truques
|
||||
===============
|
||||
|
||||
Criando chaves primárias customizadas
|
||||
-------------------------------------
|
||||
|
||||
Se você precisa evitar a criação automática da chave primária ``id`` ao
|
||||
adicioanr novas tabelas ao banco de dados, é possível usar o segundo argumento
|
||||
do método ``table()``::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class CreateProductsTable extends AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$table = $this->table('products', ['id' => false, 'primary_key' => ['id']]);
|
||||
$table
|
||||
->addColumn('id', 'uuid')
|
||||
->addColumn('name', 'string')
|
||||
->addColumn('description', 'text')
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
||||
O código acima vai criar uma coluna ``CHAR(36)`` ``id`` que também é a chave
|
||||
primária.
|
||||
|
||||
.. note::
|
||||
|
||||
Ao especificar chaves primárias customizadas pela linha de comando, você
|
||||
deve apontá-las como chave primária no campo id, caso contrário você pode
|
||||
receber um erro apontando campos diplicados, i.e.::
|
||||
|
||||
$ bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified
|
||||
|
||||
Adicionalmente, desde a versão 1.3, uma novo meio de lidar com chaves primárias
|
||||
foi introduzido. Para tal, sua classe de migração deve estender a nova classe
|
||||
``Migrations\AbstractMigration``.
|
||||
|
||||
Você pode especificar uma propriedade ``autoId`` na sua classe e defini-la como
|
||||
``false``, o quê desabilitará a geração automática da coluna ``id``. Você
|
||||
vai precisar criar manualmente a coluna que será usada como chave primária e
|
||||
adicioná-la à declaração da tabela::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class CreateProductsTable extends AbstractMigration
|
||||
{
|
||||
|
||||
public $autoId = false;
|
||||
|
||||
public function up()
|
||||
{
|
||||
$table = $this->table('products');
|
||||
$table
|
||||
->addColumn('id', 'integer', [
|
||||
'autoIncrement' => true,
|
||||
'limit' => 11
|
||||
])
|
||||
->addPrimaryKey('id')
|
||||
->addColumn('name', 'string')
|
||||
->addColumn('description', 'text')
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
||||
Comparado ao método apresentado anteriormente de lidar com chaves primárias,
|
||||
nesse método, temos a possibilidade de ter maior controle sobre as definições
|
||||
da coluna da chave primária:
|
||||
unsigned, limit, comentários, etc.
|
||||
|
||||
Todas as migrations e snapshots criadas pelo bake vão usar essa nova forma
|
||||
quando necessário.
|
||||
|
||||
.. warning::
|
||||
|
||||
Lidar com chaves primárias só é possível no momento de criação de tabelas.
|
||||
Isso é devido a algumas limitações de alguns servidores de banco de dados
|
||||
que o plugin suporta.
|
||||
|
||||
Colações
|
||||
--------
|
||||
|
||||
Se você precisar criar uma tabela com colação diferente do padrão do banco de
|
||||
dados, você pode defini-la pelo método ``table()``, como uma opção::
|
||||
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class CreateCategoriesTable extends AbstractMigration
|
||||
{
|
||||
public function change()
|
||||
{
|
||||
$table = $this
|
||||
->table('categories', [
|
||||
'collation' => 'latin1_german1_ci'
|
||||
])
|
||||
->addColumn('title', 'string', [
|
||||
'default' => null,
|
||||
'limit' => 255,
|
||||
'null' => false,
|
||||
])
|
||||
->create();
|
||||
}
|
||||
}
|
||||
|
||||
Note que isso só pode ser feito na criação da tabela : não há atualmente uma
|
||||
forma de adicionar uma coluna a uma tabela existente com uma colação diferente
|
||||
do padrão da tabela, ou mesmo do banco de dados.
|
||||
Apenas ``MySQL`` e ``SqlServer`` suportam essa chave de configuração.
|
||||
|
||||
Atualizando nome de colunas e usando objetos de tabela
|
||||
------------------------------------------------------
|
||||
|
||||
Se você usa um objeto ORM Table do CakePHP para manipular valores do seu banco
|
||||
de dados, renomeando ou removendo uma coluna, certifique-se de criar uma nova
|
||||
instância do seu objeto depois da chamada do ``update()``. O registro do objeto
|
||||
é limpo depois da chamada do ``update()`` para atualizar o **schema** que é
|
||||
refletido e armazenado no objeto ``Table`` paralelo à instanciação.
|
||||
|
||||
Migrations e Deployment
|
||||
-----------------------
|
||||
|
||||
Se você usa o plugin ao fazer o **deploy** de sua aplicação, garanta que o cache
|
||||
ORM seja limpo para renovar os metadados das colunas de suas tabelas.
|
||||
Caso contrário, você pode acabar recebendo erros relativos a colunas
|
||||
inexistentes ao criar operações nessas mesmas colunas.
|
||||
O **core** do CakePHP possui uma :doc:`Schema Cache Shell <https://book.cakephp.org/3.0/en/console-and-shells/schema-cache.html>`
|
||||
que você pode usar para realizar essas operação::
|
||||
|
||||
$ bin/cake schema_cache clear
|
||||
|
||||
Leia a seção :doc:`Schema Cache Shell <https://book.cakephp.org/3.0/en/console-and-shells/schema-cache.html>` do cookbook
|
||||
se você quiser conhecer mais sobre essa **shell**.
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import sys, os
|
||||
|
||||
# Append the top level directory of the docs, so we can import from the config dir.
|
||||
sys.path.insert(0, os.path.abspath('..'))
|
||||
|
||||
# Pull in all the configuration options defined in the global config file..
|
||||
from config.all import *
|
||||
|
||||
language = 'ru'
|
||||
@@ -0,0 +1,5 @@
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: CakePHP Migrations
|
||||
|
||||
/index
|
||||
+1016
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Phinx\Migration\AbstractMigration as BaseAbstractMigration;
|
||||
|
||||
class AbstractMigration extends BaseAbstractMigration
|
||||
{
|
||||
|
||||
/**
|
||||
* Whether the tables created in this migration
|
||||
* should auto-create an `id` field or not
|
||||
*
|
||||
* This option is global for all tables created in the migration file.
|
||||
* If you set it to false, you have to manually add the primary keys for your
|
||||
* tables using the Migrations\Table::addPrimaryKey() method
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $autoId = true;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @return \Migrations\Table
|
||||
*/
|
||||
public function table($tableName, $options = [])
|
||||
{
|
||||
if ($this->autoId === false) {
|
||||
$options['id'] = false;
|
||||
}
|
||||
|
||||
return new Table($tableName, $options, $this->getAdapter());
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Migrations\Command\Seed;
|
||||
use Phinx\Migration\Manager;
|
||||
use Phinx\Seed\AbstractSeed as BaseAbstractSeed;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractSeed
|
||||
* Extends Phinx base AbstractSeed class in order to extend the features the seed class
|
||||
* offers.
|
||||
*/
|
||||
abstract class AbstractSeed extends BaseAbstractSeed
|
||||
{
|
||||
|
||||
/**
|
||||
* InputInterface this Seed class is being used with.
|
||||
*
|
||||
* @var \Symfony\Component\Console\Input\InputInterface
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Gives the ability to a seeder to call another seeder.
|
||||
* This is particularly useful if you need to run the seeders of your applications in a specific sequences,
|
||||
* for instance to respect foreign key constraints.
|
||||
*
|
||||
* @param string $seeder Name of the seeder to call from the current seed
|
||||
* @return void
|
||||
*/
|
||||
public function call($seeder)
|
||||
{
|
||||
$this->getOutput()->writeln('');
|
||||
$this->getOutput()->writeln(
|
||||
' ====' .
|
||||
' <info>' . $seeder . ':</info>' .
|
||||
' <comment>seeding</comment>'
|
||||
);
|
||||
|
||||
$start = microtime(true);
|
||||
$this->runCall($seeder);
|
||||
$end = microtime(true);
|
||||
|
||||
$this->getOutput()->writeln(
|
||||
' ====' .
|
||||
' <info>' . $seeder . ':</info>' .
|
||||
' <comment>seeded' .
|
||||
' ' . sprintf('%.4fs', $end - $start) . '</comment>'
|
||||
);
|
||||
$this->getOutput()->writeln('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls another seeder from this seeder.
|
||||
* It will load the Seed class you are calling and run it.
|
||||
*
|
||||
* @param string $seeder Name of the seeder to call from the current seed
|
||||
* @return void
|
||||
*/
|
||||
protected function runCall($seeder)
|
||||
{
|
||||
list($pluginName, $seeder) = pluginSplit($seeder);
|
||||
|
||||
$argv = [
|
||||
'seed',
|
||||
'--seed',
|
||||
$seeder,
|
||||
];
|
||||
|
||||
$plugin = $pluginName ?: $this->input->getOption('plugin');
|
||||
if ($plugin !== null) {
|
||||
$argv[] = '--plugin';
|
||||
$argv[] = $plugin;
|
||||
}
|
||||
|
||||
$connection = $this->input->getOption('connection');
|
||||
if ($connection !== null) {
|
||||
$argv[] = '--connection';
|
||||
$argv[] = $connection;
|
||||
}
|
||||
|
||||
$source = $this->input->getOption('source');
|
||||
if ($source !== null) {
|
||||
$argv[] = '--source';
|
||||
$argv[] = $source;
|
||||
}
|
||||
|
||||
$seedCommand = new Seed();
|
||||
$input = new ArgvInput($argv, $seedCommand->getDefinition());
|
||||
$seedCommand->setInput($input);
|
||||
$config = $seedCommand->getConfig();
|
||||
|
||||
$seedPaths = $config->getSeedPaths();
|
||||
require_once(array_pop($seedPaths) . DS . $seeder . '.php');
|
||||
$seeder = new $seeder();
|
||||
$seeder->setOutput($this->getOutput());
|
||||
$seeder->setAdapter($this->getAdapter());
|
||||
$seeder->setInput($this->input);
|
||||
$seeder->run();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the InputInterface this Seed class is being used with.
|
||||
*
|
||||
* @param InputInterface $input Input object.
|
||||
* @return void
|
||||
*/
|
||||
public function setInput(InputInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Cake\Database\Connection;
|
||||
use Cake\Database\Driver\Postgres;
|
||||
use PDO;
|
||||
use Phinx\Db\Adapter\AdapterInterface;
|
||||
use Phinx\Db\Adapter\AdapterWrapper;
|
||||
use Phinx\Db\Table as PhinxTable;
|
||||
use Phinx\Db\Table\Column;
|
||||
use Phinx\Db\Table\ForeignKey;
|
||||
use Phinx\Db\Table\Index;
|
||||
use Phinx\Migration\MigrationInterface;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Decorates an AdapterInterface in order to proxy some method to the actual
|
||||
* connection object.
|
||||
*/
|
||||
class CakeAdapter extends AdapterWrapper
|
||||
{
|
||||
|
||||
/**
|
||||
* Database connection
|
||||
*
|
||||
* @var \Cake\Database\Connection
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \Phinx\Db\Adapter\AdapterInterface $adapter The original adapter to decorate.
|
||||
* @param \Cake\Database\Connection|null $connection The connection to actually use.
|
||||
*/
|
||||
public function __construct(AdapterInterface $adapter, Connection $connection = null)
|
||||
{
|
||||
if ($connection === null) {
|
||||
throw new \InvalidArgumentException("The cake connection cannot be null");
|
||||
}
|
||||
|
||||
parent::__construct($adapter);
|
||||
|
||||
$this->connection = $connection;
|
||||
$pdo = $adapter->getConnection();
|
||||
|
||||
if ($pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION) {
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
}
|
||||
$connection->cacheMetadata(false);
|
||||
|
||||
if ($connection->getDriver() instanceof Postgres) {
|
||||
$config = $connection->config();
|
||||
$schema = empty($config['schema']) ? 'public' : $config['schema'];
|
||||
$pdo->exec('SET search_path TO ' . $schema);
|
||||
}
|
||||
$connection->getDriver()->setConnection($pdo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the CakePHP Connection object.
|
||||
*
|
||||
* @return \Cake\Database\Connection
|
||||
*/
|
||||
public function getCakeConnection()
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new Query object
|
||||
*
|
||||
* @return \Cake\Database\Query
|
||||
*/
|
||||
public function getQueryBuilder()
|
||||
{
|
||||
return $this->getCakeConnection()->newQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the adapter type name, for example mysql
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAdapterType()
|
||||
{
|
||||
return $this->getAdapter()->getAdapterType();
|
||||
}
|
||||
}
|
||||
+370
@@ -0,0 +1,370 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Phinx\Migration\Manager;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* Overrides Phinx Manager class in order to provide an interface
|
||||
* for running migrations within an app
|
||||
*/
|
||||
class CakeManager extends Manager
|
||||
{
|
||||
|
||||
public $maxNameLength = 0;
|
||||
|
||||
/**
|
||||
* Instance of InputInterface the Manager is dealing with for the current shell call
|
||||
*
|
||||
* @var \Symfony\Component\Console\Input\InputInterface
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Reset the migrations stored in the object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetMigrations()
|
||||
{
|
||||
$this->migrations = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the seeds stored in the object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function resetSeeds()
|
||||
{
|
||||
$this->seeds = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the specified environment's migration status.
|
||||
*
|
||||
* @param string $environment Environment name.
|
||||
* @param null|string $format Format (`json` or `array`).
|
||||
* @return array|string Array of migrations or json string.
|
||||
*/
|
||||
public function printStatus($environment, $format = null)
|
||||
{
|
||||
$migrations = [];
|
||||
$isJson = $format === 'json';
|
||||
if (count($this->getMigrations('default'))) {
|
||||
$env = $this->getEnvironment($environment);
|
||||
$versions = $env->getVersionLog();
|
||||
$this->maxNameLength = $versions ? max(array_map(function ($version) {
|
||||
return strlen($version['migration_name']);
|
||||
}, $versions)) : 0;
|
||||
|
||||
foreach ($this->getMigrations('default') as $migration) {
|
||||
if (array_key_exists($migration->getVersion(), $versions)) {
|
||||
$status = 'up';
|
||||
unset($versions[$migration->getVersion()]);
|
||||
} else {
|
||||
$status = 'down';
|
||||
}
|
||||
|
||||
$version = $migration->getVersion();
|
||||
$migrationParams = [
|
||||
'status' => $status,
|
||||
'id' => $migration->getVersion(),
|
||||
'name' => $migration->getName(),
|
||||
];
|
||||
|
||||
$migrations[$version] = $migrationParams;
|
||||
}
|
||||
|
||||
foreach ($versions as $missing) {
|
||||
$version = $missing['version'];
|
||||
$migrationParams = [
|
||||
'status' => 'up',
|
||||
'id' => $version,
|
||||
'name' => $missing['migration_name'],
|
||||
];
|
||||
|
||||
if (!$isJson) {
|
||||
$migrationParams = array_merge($migrationParams, [
|
||||
'missing' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
$migrations[$version] = $migrationParams;
|
||||
}
|
||||
}
|
||||
|
||||
ksort($migrations);
|
||||
$migrations = array_values($migrations);
|
||||
|
||||
if ($isJson) {
|
||||
$migrations = json_encode($migrations);
|
||||
}
|
||||
|
||||
return $migrations;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function migrateToDateTime($environment, \DateTime $dateTime, $fake = false)
|
||||
{
|
||||
$versions = array_keys($this->getMigrations('default'));
|
||||
$dateString = $dateTime->format('Ymdhis');
|
||||
$versionToMigrate = null;
|
||||
foreach ($versions as $version) {
|
||||
if ($dateString > $version) {
|
||||
$versionToMigrate = $version;
|
||||
}
|
||||
}
|
||||
|
||||
if ($versionToMigrate === null) {
|
||||
$this->getOutput()->writeln(
|
||||
'No migrations to run'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->getOutput()->writeln(
|
||||
'Migrating to version ' . $versionToMigrate
|
||||
);
|
||||
$this->migrate($environment, $versionToMigrate, $fake);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollbackToDateTime($environment, \DateTime $dateTime, $force = false)
|
||||
{
|
||||
$env = $this->getEnvironment($environment);
|
||||
$versions = $env->getVersions();
|
||||
$dateString = $dateTime->format('Ymdhis');
|
||||
sort($versions);
|
||||
$versions = array_reverse($versions);
|
||||
|
||||
if (empty($versions) || $dateString > $versions[0]) {
|
||||
$this->getOutput()->writeln('No migrations to rollback');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($dateString < end($versions)) {
|
||||
$this->getOutput()->writeln('Rolling back all migrations');
|
||||
$this->rollback($environment, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
foreach ($versions as $index => $version) {
|
||||
if ($dateString > $version) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$versionToRollback = $versions[$index];
|
||||
|
||||
$this->getOutput()->writeln('Rolling back to version ' . $versionToRollback);
|
||||
$this->rollback($environment, $versionToRollback, $force);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the migration with version number $version as already been mark migrated
|
||||
*
|
||||
* @param int|string $version Version number of the migration to check
|
||||
* @return bool
|
||||
*/
|
||||
public function isMigrated($version)
|
||||
{
|
||||
$adapter = $this->getEnvironment('default')->getAdapter();
|
||||
$versions = array_flip($adapter->getVersions());
|
||||
|
||||
return isset($versions[$version]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks migration with version number $version migrated
|
||||
*
|
||||
* @param int|string $version Version number of the migration to check
|
||||
* @param string $path Path where the migration file is located
|
||||
* @return bool True if success
|
||||
*/
|
||||
public function markMigrated($version, $path)
|
||||
{
|
||||
$adapter = $this->getEnvironment('default')->getAdapter();
|
||||
|
||||
$migrationFile = glob($path . DS . $version . '*');
|
||||
|
||||
if (empty($migrationFile)) {
|
||||
throw new \RuntimeException(
|
||||
sprintf('A migration file matching version number `%s` could not be found', $version)
|
||||
);
|
||||
}
|
||||
|
||||
$migrationFile = $migrationFile[0];
|
||||
$className = $this->getMigrationClassName($migrationFile);
|
||||
require_once $migrationFile;
|
||||
$Migration = new $className('default', $version);
|
||||
|
||||
$time = date('Y-m-d H:i:s', time());
|
||||
|
||||
$adapter->migrated($Migration, 'up', $time, $time);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides which versions it should mark as migrated
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input Input interface from which argument and options
|
||||
* will be extracted to determine which versions to be marked as migrated
|
||||
* @return array Array of versions that should be marked as migrated
|
||||
* @throws \InvalidArgumentException If the `--exclude` or `--only` options are used without `--target`
|
||||
* or version not found
|
||||
*/
|
||||
public function getVersionsToMark($input)
|
||||
{
|
||||
$migrations = $this->getMigrations('default');
|
||||
$versions = array_keys($migrations);
|
||||
|
||||
$versionArg = $input->getArgument('version');
|
||||
$targetArg = $input->getOption('target');
|
||||
$hasAllVersion = in_array($versionArg, ['all', '*']);
|
||||
if ((empty($versionArg) && empty($targetArg)) || $hasAllVersion) {
|
||||
return $versions;
|
||||
}
|
||||
|
||||
$version = $targetArg ?: $versionArg;
|
||||
|
||||
if ($input->getOption('only') || !empty($versionArg)) {
|
||||
if (!in_array($version, $versions)) {
|
||||
throw new \InvalidArgumentException("Migration `$version` was not found !");
|
||||
}
|
||||
|
||||
return [$version];
|
||||
}
|
||||
|
||||
$lengthIncrease = $input->getOption('exclude') ? 0 : 1;
|
||||
$index = array_search($version, $versions);
|
||||
|
||||
if ($index === false) {
|
||||
throw new \InvalidArgumentException("Migration `$version` was not found !");
|
||||
}
|
||||
|
||||
return array_slice($versions, 0, $index + $lengthIncrease);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all migrations in $versions array found in $path as migrated
|
||||
*
|
||||
* It will start a transaction and rollback in case one of the operation raises an exception
|
||||
*
|
||||
* @param string $path Path where to look for migrations
|
||||
* @param array $versions Versions which should be marked
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output OutputInterface used to store
|
||||
* the command output
|
||||
* @return void
|
||||
*/
|
||||
public function markVersionsAsMigrated($path, $versions, $output)
|
||||
{
|
||||
$adapter = $this->getEnvironment('default')->getAdapter();
|
||||
|
||||
if (empty($versions)) {
|
||||
$output->writeln('<info>No migrations were found. Nothing to mark as migrated.</info>');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$adapter->beginTransaction();
|
||||
foreach ($versions as $version) {
|
||||
if ($this->isMigrated($version)) {
|
||||
$output->writeln(sprintf('<info>Skipping migration `%s` (already migrated).</info>', $version));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->markMigrated($version, $path);
|
||||
$output->writeln(
|
||||
sprintf('<info>Migration `%s` successfully marked migrated !</info>', $version)
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
$adapter->rollbackTransaction();
|
||||
$output->writeln(
|
||||
sprintf(
|
||||
'<error>An error occurred while marking migration `%s` as migrated : %s</error>',
|
||||
$version,
|
||||
$e->getMessage()
|
||||
)
|
||||
);
|
||||
$output->writeln('<error>All marked migrations during this process were unmarked.</error>');
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
$adapter->commitTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves a migration class name based on $path
|
||||
*
|
||||
* @param string $path Path to the migration file of which we want the class name
|
||||
* @return string Migration class name
|
||||
*/
|
||||
protected function getMigrationClassName($path)
|
||||
{
|
||||
$class = preg_replace('/^[0-9]+_/', '', basename($path));
|
||||
$class = str_replace('_', ' ', $class);
|
||||
$class = ucwords($class);
|
||||
$class = str_replace(' ', '', $class);
|
||||
if (strpos($class, '.') !== false) {
|
||||
$class = substr($class, 0, strpos($class, '.'));
|
||||
}
|
||||
|
||||
return $class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the InputInterface the Manager is dealing with for the current shell call
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input Instance of InputInterface
|
||||
* @return void
|
||||
*/
|
||||
public function setInput(InputInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overload the basic behavior to add an instance of the InputInterface the shell call is
|
||||
* using in order to gives the ability to the AbstractSeed::call() method to propagate options
|
||||
* to the other MigrationsDispatcher it is generating.
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getSeeds()
|
||||
{
|
||||
parent::getSeeds();
|
||||
if (empty($this->seeds)) {
|
||||
return $this->seeds;
|
||||
}
|
||||
|
||||
foreach ($this->seeds as $class => $instance) {
|
||||
if ($instance instanceof AbstractSeed) {
|
||||
$instance->setInput($this->input);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->seeds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
|
||||
abstract class BaseCommand extends Command
|
||||
{
|
||||
const CODE_SUCCESS = 0;
|
||||
const CODE_ERROR = 1;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Migrations\Util\SchemaTrait;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CacheBuild extends BaseCommand
|
||||
{
|
||||
use SchemaTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('orm-cache-build')
|
||||
->setDescription('Build all metadata caches for the connection. If a table name is provided, only that table will be cached.')
|
||||
->addOption(
|
||||
'connection',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The connection to build/clear metadata cache data for.',
|
||||
'default'
|
||||
)
|
||||
->addArgument(
|
||||
'name',
|
||||
InputArgument::OPTIONAL,
|
||||
'A specific table you want to clear/refresh cached data for.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$name = $input->getArgument('name');
|
||||
$schema = $this->_getSchema($input, $output);
|
||||
if (!$schema) {
|
||||
return static::CODE_ERROR;
|
||||
}
|
||||
$tables = [$name];
|
||||
if (empty($name)) {
|
||||
$tables = $schema->listTables();
|
||||
}
|
||||
foreach ($tables as $table) {
|
||||
$output->writeln('Building metadata cache for ' . $table);
|
||||
$schema->describe($table, ['forceRefresh' => true]);
|
||||
}
|
||||
$output->writeln('<info>Cache build complete</info>');
|
||||
|
||||
return static::CODE_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Cake\Cache\Cache;
|
||||
use Migrations\Util\SchemaTrait;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CacheClear extends BaseCommand
|
||||
{
|
||||
use SchemaTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('orm-cache-clear')
|
||||
->setDescription('Clear all metadata caches for the connection. If a table name is provided, only that table will be removed.')
|
||||
->addOption(
|
||||
'connection',
|
||||
null,
|
||||
InputOption::VALUE_OPTIONAL,
|
||||
'The connection to build/clear metadata cache data for.',
|
||||
'default'
|
||||
)
|
||||
->addArgument(
|
||||
'name',
|
||||
InputArgument::OPTIONAL,
|
||||
'A specific table you want to clear/refresh cached data for.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$schema = $this->_getSchema($input, $output);
|
||||
$name = $input->getArgument('name');
|
||||
if (!$schema) {
|
||||
return static::CODE_ERROR;
|
||||
}
|
||||
$tables = [$name];
|
||||
if (empty($name)) {
|
||||
$tables = $schema->listTables();
|
||||
}
|
||||
$configName = $schema->getCacheMetadata();
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$output->writeln(sprintf(
|
||||
'Clearing metadata cache from "%s" for %s',
|
||||
$configName,
|
||||
$table
|
||||
));
|
||||
$key = $schema->cacheKey($table);
|
||||
Cache::delete($key, $configName);
|
||||
}
|
||||
$output->writeln('<info>Cache clear complete<info>');
|
||||
|
||||
return static::CODE_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Migrations\CakeAdapter;
|
||||
use Migrations\CakeManager;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
trait CommandTrait
|
||||
{
|
||||
/**
|
||||
* Overrides the action execute method in order to vanish the idea of environments
|
||||
* from phinx. CakePHP does not believe in the idea of having in-app environments
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int|null
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->beforeExecute($input, $output);
|
||||
|
||||
return parent::execute($input, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the action execute method in order to vanish the idea of environments
|
||||
* from phinx. CakePHP does not believe in the idea of having in-app environments
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return void
|
||||
*/
|
||||
protected function beforeExecute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setInput($input);
|
||||
$this->addOption('--environment', '-e', InputArgument::OPTIONAL);
|
||||
$input->setOption('environment', 'default');
|
||||
}
|
||||
|
||||
/**
|
||||
* A callback method that is used to inject the PDO object created from phinx into
|
||||
* the CakePHP connection. This is needed in case the user decides to use tables
|
||||
* from the ORM and executes queries.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return void
|
||||
*/
|
||||
public function bootstrap(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
parent::bootstrap($input, $output);
|
||||
$name = $this->getConnectionName($input);
|
||||
$this->connection = $name;
|
||||
ConnectionManager::alias($name, 'default');
|
||||
$connection = ConnectionManager::get($name);
|
||||
|
||||
$manager = $this->getManager();
|
||||
|
||||
if (!$manager instanceof CakeManager) {
|
||||
$this->setManager(new CakeManager($this->getConfig(), $input, $output));
|
||||
}
|
||||
$env = $this->getManager()->getEnvironment('default');
|
||||
$adapter = $env->getAdapter();
|
||||
if (!$adapter instanceof CakeAdapter) {
|
||||
$env->setAdapter(new CakeAdapter($adapter, $connection));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input object that should be used for the command class. This object
|
||||
* is used to inspect the extra options that are needed for CakePHP apps.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @return void
|
||||
*/
|
||||
public function setInput(InputInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Cake\Utility\Inflector;
|
||||
use Migrations\ConfigurationTrait;
|
||||
use Phinx\Console\Command\Create as CreateCommand;
|
||||
use Phinx\Util\Util;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Create extends CreateCommand
|
||||
{
|
||||
use CommandTrait {
|
||||
execute as parentExecute;
|
||||
}
|
||||
use ConfigurationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('create')
|
||||
->setDescription('Create a new migration')
|
||||
->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration?')
|
||||
->setHelp(sprintf(
|
||||
'%sCreates a new database migration file%s',
|
||||
PHP_EOL,
|
||||
PHP_EOL
|
||||
))
|
||||
->addOption('plugin', 'p', InputOption::VALUE_REQUIRED, 'The plugin the file should be created for')
|
||||
->addOption('connection', 'c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
|
||||
->addOption('source', 's', InputOption::VALUE_REQUIRED, 'The folder where migrations are in')
|
||||
->addOption('template', 't', InputOption::VALUE_REQUIRED, 'Use an alternative template')
|
||||
->addOption(
|
||||
'class',
|
||||
'l',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Use a class implementing "' . parent::CREATION_INTERFACE . '" to generate the template'
|
||||
)
|
||||
->addOption(
|
||||
'path',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'Specify the path in which to create this migration'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the action execute method in order to vanish the idea of environments
|
||||
* from phinx. CakePHP does not believe in the idea of having in-app environments
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$result = $this->parentExecute($input, $output);
|
||||
|
||||
$output->writeln('<info>renaming file in CamelCase to follow CakePHP convention...</info>');
|
||||
|
||||
$migrationPaths = $this->getConfig()->getMigrationPaths();
|
||||
$migrationPath = array_pop($migrationPaths) . DS;
|
||||
$name = $input->getArgument('name');
|
||||
list($phinxTimestamp, $phinxName) = explode('_', Util::mapClassNameToFileName($name), 2);
|
||||
$migrationFilename = glob($migrationPath . '*' . $phinxName);
|
||||
|
||||
if (empty($migrationFilename)) {
|
||||
$output->writeln(sprintf('<info>An error occurred while renaming file</info>'));
|
||||
} else {
|
||||
$migrationFilename = $migrationFilename[0];
|
||||
$path = dirname($migrationFilename) . DS;
|
||||
$name = Inflector::camelize($name);
|
||||
$newPath = $path . Util::getCurrentTimestamp() . '_' . $name . '.php';
|
||||
|
||||
$output->writeln('<info>renaming file in CamelCase to follow CakePHP convention...</info>');
|
||||
if (rename($migrationFilename, $newPath)) {
|
||||
$output->writeln(sprintf('<info>File successfully renamed to %s</info>', $newPath));
|
||||
} else {
|
||||
$output->writeln(sprintf('<info>An error occurred while renaming file to %s</info>', $newPath));
|
||||
}
|
||||
}
|
||||
|
||||
return $result !== null ? $result : BaseCommand::CODE_SUCCESS;
|
||||
}
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Migrations\ConfigurationTrait;
|
||||
use Migrations\TableFinderTrait;
|
||||
use Phinx\Console\Command\AbstractCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Dump command class.
|
||||
* A "dump" is a snapshot of a database at a given point in time. It is stored in a
|
||||
* .lock file in the same folder as migrations files.
|
||||
*/
|
||||
class Dump extends AbstractCommand
|
||||
{
|
||||
use CommandTrait;
|
||||
use ConfigurationTrait;
|
||||
use TableFinderTrait;
|
||||
|
||||
/**
|
||||
* Output object.
|
||||
*
|
||||
* @var \Symfony\Component\Console\Output\OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('dump')
|
||||
->setDescription('Dumps the current schema of the database to be used while baking a diff')
|
||||
->setHelp(sprintf(
|
||||
'%sDumps the current schema of the database to be used while baking a diff%s',
|
||||
PHP_EOL,
|
||||
PHP_EOL
|
||||
))
|
||||
->addOption('plugin', 'p', InputOption::VALUE_REQUIRED, 'The plugin the file should be created for')
|
||||
->addOption('connection', 'c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
|
||||
->addOption('source', 's', InputOption::VALUE_REQUIRED, 'The folder where migrations are in');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output The output object.
|
||||
* @return mixed
|
||||
*/
|
||||
public function output(OutputInterface $output = null)
|
||||
{
|
||||
if ($output !== null) {
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dumps the current schema to be used when baking a diff
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setInput($input);
|
||||
$this->bootstrap($input, $output);
|
||||
$this->output($output);
|
||||
|
||||
$path = $this->getOperationsPath($input);
|
||||
$connectionName = $input->getOption('connection') ?: 'default';
|
||||
$connection = ConnectionManager::get($connectionName);
|
||||
$collection = $connection->getSchemaCollection();
|
||||
|
||||
$options = [
|
||||
'require-table' => false,
|
||||
'plugin' => $this->getPlugin($input),
|
||||
];
|
||||
$tables = $this->getTablesToBake($collection, $options);
|
||||
|
||||
$dump = [];
|
||||
if ($tables) {
|
||||
foreach ($tables as $table) {
|
||||
$schema = $collection->describe($table);
|
||||
$dump[$table] = $schema;
|
||||
}
|
||||
}
|
||||
|
||||
$filePath = $path . DS . 'schema-dump-' . $connectionName . '.lock';
|
||||
$output->writeln(sprintf('<info>Writing dump file `%s`...</info>', $filePath));
|
||||
if (file_put_contents($filePath, serialize($dump))) {
|
||||
$output->writeln(sprintf('<info>Dump file `%s` was successfully written</info>', $filePath));
|
||||
|
||||
return BaseCommand::CODE_SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf(
|
||||
'<error>An error occurred while writing dump file `%s`</error>',
|
||||
$filePath
|
||||
));
|
||||
|
||||
return BaseCommand::CODE_ERROR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Migrations\ConfigurationTrait;
|
||||
use Phinx\Console\Command\AbstractCommand;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class MarkMigrated extends AbstractCommand
|
||||
{
|
||||
use CommandTrait;
|
||||
use ConfigurationTrait;
|
||||
|
||||
/**
|
||||
* The console output instance
|
||||
*
|
||||
* @var \Symfony\Component\Console\Output\OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output The output object.
|
||||
* @return mixed
|
||||
*/
|
||||
public function output(OutputInterface $output = null)
|
||||
{
|
||||
if ($output !== null) {
|
||||
$this->output = $output;
|
||||
}
|
||||
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('mark_migrated')
|
||||
->setDescription('Mark a migration as migrated')
|
||||
->addArgument(
|
||||
'version',
|
||||
InputArgument::OPTIONAL,
|
||||
'DEPRECATED: use `bin/cake migrations mark_migrated --target=VERSION --only` instead'
|
||||
)
|
||||
->setHelp(sprintf(
|
||||
'%sMark migrations as migrated%s',
|
||||
PHP_EOL,
|
||||
PHP_EOL
|
||||
))
|
||||
->addOption('plugin', 'p', InputOption::VALUE_REQUIRED, 'The plugin the file should be created for')
|
||||
->addOption('connection', 'c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
|
||||
->addOption('source', 's', InputOption::VALUE_REQUIRED, 'The folder where migrations are in')
|
||||
->addOption(
|
||||
'target',
|
||||
't',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'It will mark migrations from beginning to the given version'
|
||||
)
|
||||
->addOption(
|
||||
'exclude',
|
||||
'x',
|
||||
InputOption::VALUE_NONE,
|
||||
'If present it will mark migrations from beginning until the given version, excluding it'
|
||||
)
|
||||
->addOption(
|
||||
'only',
|
||||
'o',
|
||||
InputOption::VALUE_NONE,
|
||||
'If present it will only mark the given migration version'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark migrations as migrated
|
||||
*
|
||||
* `bin/cake migrations mark_migrated` mark every migrations as migrated
|
||||
* `bin/cake migrations mark_migrated all` DEPRECATED: the same effect as above
|
||||
* `bin/cake migrations mark_migrated --target=VERSION` mark migrations as migrated up to the VERSION param
|
||||
* `bin/cake migrations mark_migrated --target=20150417223600 --exclude` mark migrations as migrated up to
|
||||
* and except the VERSION param
|
||||
* `bin/cake migrations mark_migrated --target=20150417223600 --only` mark only the VERSION migration as migrated
|
||||
* `bin/cake migrations mark_migrated 20150417223600` DEPRECATED: the same effect as above
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->setInput($input);
|
||||
$this->bootstrap($input, $output);
|
||||
$this->output($output);
|
||||
|
||||
$migrationPaths = $this->getConfig()->getMigrationPaths();
|
||||
$path = array_pop($migrationPaths);
|
||||
|
||||
if ($this->invalidOnlyOrExclude()) {
|
||||
$output->writeln(
|
||||
"<error>You should use `--exclude` OR `--only` (not both) along with a `--target` !</error>"
|
||||
);
|
||||
|
||||
return BaseCommand::CODE_ERROR;
|
||||
}
|
||||
|
||||
if ($this->isUsingDeprecatedAll()) {
|
||||
$this->outputDeprecatedAllMessage();
|
||||
}
|
||||
|
||||
if ($this->isUsingDeprecatedVersion()) {
|
||||
$this->outputDeprecatedVersionMessage();
|
||||
}
|
||||
|
||||
try {
|
||||
$versions = $this->getManager()->getVersionsToMark($input);
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$output->writeln(sprintf("<error>%s</error>", $e->getMessage()));
|
||||
|
||||
return BaseCommand::CODE_ERROR;
|
||||
}
|
||||
|
||||
$this->getManager()->markVersionsAsMigrated($path, $versions, $output);
|
||||
|
||||
return BaseCommand::CODE_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the version is using the deprecated `all`
|
||||
*
|
||||
* @return bool Returns true if it is using the deprecated `all` otherwise false
|
||||
*/
|
||||
protected function isUsingDeprecatedAll()
|
||||
{
|
||||
$version = $this->input->getArgument('version');
|
||||
|
||||
return $version === 'all' || $version === '*';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input has the `--exclude` option
|
||||
*
|
||||
* @return bool Returns true if `--exclude` option gets passed in otherwise false
|
||||
*/
|
||||
protected function hasExclude()
|
||||
{
|
||||
return $this->input->getOption('exclude');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the input has the `--only` option
|
||||
*
|
||||
* @return bool Returns true if `--only` option gets passed in otherwise false
|
||||
*/
|
||||
protected function hasOnly()
|
||||
{
|
||||
return $this->input->getOption('only');
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for the usage of deprecated VERSION as argument when not `all`
|
||||
*
|
||||
* @return bool True if it is using VERSION argument otherwise false
|
||||
*/
|
||||
protected function isUsingDeprecatedVersion()
|
||||
{
|
||||
$version = $this->input->getArgument('version');
|
||||
|
||||
return $version && $version !== 'all' && $version !== '*';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for an invalid use of `--exclude` or `--only`
|
||||
*
|
||||
* @return bool Returns true when it is an invalid use of `--exclude` or `--only` otherwise false
|
||||
*/
|
||||
protected function invalidOnlyOrExclude()
|
||||
{
|
||||
return ($this->hasExclude() && $this->hasOnly()) ||
|
||||
($this->hasExclude() || $this->hasOnly()) &&
|
||||
$this->input->getOption('target') === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the deprecated message for the `all` or `*` usage
|
||||
*
|
||||
* @return void Just outputs the message
|
||||
*/
|
||||
protected function outputDeprecatedAllMessage()
|
||||
{
|
||||
$msg = "DEPRECATED: `all` or `*` as version is deprecated. Use `bin/cake migrations mark_migrated` instead";
|
||||
$output = $this->output();
|
||||
$output->writeln(sprintf("<comment>%s</comment>", $msg));
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs the deprecated message for the usage of VERSION as argument
|
||||
*
|
||||
* @return void Just outputs the message
|
||||
*/
|
||||
protected function outputDeprecatedVersionMessage()
|
||||
{
|
||||
$msg = 'DEPRECATED: VERSION as argument is deprecated. Use: ' .
|
||||
'`bin/cake migrations mark_migrated --target=VERSION --only`';
|
||||
$output = $this->output();
|
||||
$output->writeln(sprintf("<comment>%s</comment>", $msg));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Cake\Event\EventDispatcherTrait;
|
||||
use Migrations\ConfigurationTrait;
|
||||
use Phinx\Console\Command\Migrate as MigrateCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Migrate extends MigrateCommand
|
||||
{
|
||||
use CommandTrait {
|
||||
execute as parentExecute;
|
||||
}
|
||||
use ConfigurationTrait;
|
||||
use EventDispatcherTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('migrate')
|
||||
->setDescription('Migrate the database')
|
||||
->setHelp('runs all available migrations, optionally up to a specific version')
|
||||
->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to')
|
||||
->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to')
|
||||
->addOption('--dry-run', '-x', InputOption::VALUE_NONE, 'Dump queries to standard output instead of executing it')
|
||||
->addOption('--plugin', '-p', InputOption::VALUE_REQUIRED, 'The plugin containing the migrations')
|
||||
->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
|
||||
->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in')
|
||||
->addOption('--fake', null, InputOption::VALUE_NONE, "Mark any migrations selected as run, but don't actually execute them")
|
||||
->addOption(
|
||||
'--no-lock',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'If present, no lock file will be generated after migrating'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the action execute method in order to vanish the idea of environments
|
||||
* from phinx. CakePHP does not believe in the idea of having in-app environments
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$event = $this->dispatchEvent('Migration.beforeMigrate');
|
||||
if ($event->isStopped()) {
|
||||
return $event->result ? BaseCommand::CODE_SUCCESS : BaseCommand::CODE_ERROR;
|
||||
}
|
||||
$result = $this->parentExecute($input, $output);
|
||||
$this->dispatchEvent('Migration.afterMigrate');
|
||||
|
||||
return $result !== null ? $result : BaseCommand::CODE_SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Cake\Event\EventDispatcherTrait;
|
||||
use Migrations\ConfigurationTrait;
|
||||
use Phinx\Console\Command\Rollback as RollbackCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Rollback extends RollbackCommand
|
||||
{
|
||||
use CommandTrait {
|
||||
execute as parentExecute;
|
||||
}
|
||||
use ConfigurationTrait;
|
||||
use EventDispatcherTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('rollback')
|
||||
->setDescription('Rollback the last or to a specific migration')
|
||||
->setHelp('reverts the last migration, or optionally up to a specific version')
|
||||
->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to rollback to')
|
||||
->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to')
|
||||
->addOption('--plugin', '-p', InputOption::VALUE_REQUIRED, 'The plugin containing the migrations')
|
||||
->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
|
||||
->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in')
|
||||
->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force rollback to ignore breakpoints')
|
||||
->addOption('--dry-run', '-x', InputOption::VALUE_NONE, 'Dump queries to standard output instead of executing it')
|
||||
->addOption('--fake', null, InputOption::VALUE_NONE, "Mark any rollbacks selected as run, but don't actually execute them")
|
||||
->addOption(
|
||||
'--no-lock',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Whether a lock file should be generated after rolling back'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the action execute method in order to vanish the idea of environments
|
||||
* from phinx. CakePHP does not believe in the idea of having in-app environments
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$event = $this->dispatchEvent('Migration.beforeRollback');
|
||||
if ($event->isStopped()) {
|
||||
return $event->result ? BaseCommand::CODE_SUCCESS : BaseCommand::CODE_ERROR;
|
||||
}
|
||||
$result = $this->parentExecute($input, $output);
|
||||
$this->dispatchEvent('Migration.afterRollback');
|
||||
|
||||
return $result !== null ? $result : BaseCommand::CODE_SUCCESS;
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Cake\Event\EventDispatcherTrait;
|
||||
use Migrations\ConfigurationTrait;
|
||||
use Phinx\Console\Command\SeedRun;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Seed extends SeedRun
|
||||
{
|
||||
use CommandTrait {
|
||||
execute as parentExecute;
|
||||
}
|
||||
use ConfigurationTrait;
|
||||
use EventDispatcherTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('seed')
|
||||
->setDescription('Seed the database with data')
|
||||
->setHelp('runs all available migrations, optionally up to a specific version')
|
||||
->addOption(
|
||||
'--seed',
|
||||
null,
|
||||
InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
|
||||
'What is the name of the seeder?'
|
||||
)
|
||||
->addOption('--plugin', '-p', InputOption::VALUE_REQUIRED, 'The plugin containing the migrations')
|
||||
->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
|
||||
->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in');
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the action execute method in order to vanish the idea of environments
|
||||
* from phinx. CakePHP does not believe in the idea of having in-app environments
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$event = $this->dispatchEvent('Migration.beforeSeed');
|
||||
if ($event->isStopped()) {
|
||||
return $event->result ? BaseCommand::CODE_SUCCESS : BaseCommand::CODE_ERROR;
|
||||
}
|
||||
|
||||
$seed = $input->getOption('seed');
|
||||
if (!empty($seed) && !is_array($seed)) {
|
||||
$input->setOption('seed', [$seed]);
|
||||
}
|
||||
|
||||
$this->setInput($input);
|
||||
$this->bootstrap($input, $output);
|
||||
$this->getManager()->setInput($input);
|
||||
$result = $this->parentExecute($input, $output);
|
||||
$this->dispatchEvent('Migration.afterSeed');
|
||||
|
||||
return $result !== null ? $result : BaseCommand::CODE_SUCCESS;
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Command;
|
||||
|
||||
use Migrations\ConfigurationTrait;
|
||||
use Phinx\Console\Command\Status as StatusCommand;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Status extends StatusCommand
|
||||
{
|
||||
use CommandTrait;
|
||||
use ConfigurationTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('status')
|
||||
->setDescription('Show migration status')
|
||||
->addOption(
|
||||
'--format',
|
||||
'-f',
|
||||
InputOption::VALUE_REQUIRED,
|
||||
'The output format: text or json. Defaults to text.'
|
||||
)
|
||||
->setHelp('prints a list of all migrations, along with their current status')
|
||||
->addOption('--plugin', '-p', InputOption::VALUE_REQUIRED, 'The plugin containing the migrations')
|
||||
->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
|
||||
->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the migration status.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @param \Symfony\Component\Console\Output\OutputInterface $output the output object
|
||||
* @return int
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->beforeExecute($input, $output);
|
||||
$this->bootstrap($input, $output);
|
||||
|
||||
$environment = $input->getOption('environment');
|
||||
$format = $input->getOption('format');
|
||||
|
||||
if (null === $environment) {
|
||||
$environment = $this->getManager()->getConfig()->getDefaultEnvironment();
|
||||
$output->writeln('<comment>warning</comment> no environment specified, defaulting to: ' . $environment);
|
||||
} else {
|
||||
$output->writeln('<info>using environment</info> ' . $environment);
|
||||
}
|
||||
if (null !== $format) {
|
||||
$output->writeln('<info>using format</info> ' . $format);
|
||||
}
|
||||
|
||||
// print the status
|
||||
$migrations = $this->getManager()->printStatus($environment, $format);
|
||||
|
||||
switch ($format) {
|
||||
case 'json':
|
||||
$this->getManager()->getOutput()->writeln($migrations);
|
||||
break;
|
||||
default:
|
||||
$this->display($migrations);
|
||||
break;
|
||||
}
|
||||
|
||||
return BaseCommand::CODE_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will output the status of the migrations
|
||||
*
|
||||
* @param array $migrations Migrations array.
|
||||
* @return void
|
||||
*/
|
||||
protected function display(array $migrations)
|
||||
{
|
||||
$output = $this->getManager()->getOutput();
|
||||
|
||||
if (!empty($migrations)) {
|
||||
$output->writeln('');
|
||||
$output->writeln(' Status Migration ID Migration Name ');
|
||||
$output->writeln('-----------------------------------------');
|
||||
|
||||
foreach ($migrations as $migration) {
|
||||
$status = $migration['status'] === 'up' ? ' <info>up</info> ' : ' <error>down</error> ';
|
||||
$maxNameLength = $this->getManager()->maxNameLength;
|
||||
$name = $migration['name'] !== false ?
|
||||
' <comment>' . str_pad($migration['name'], $maxNameLength, ' ') . ' </comment>' :
|
||||
' <error>** MISSING **</error>';
|
||||
|
||||
$missingComment = '';
|
||||
if (!empty($migration['missing'])) {
|
||||
$missingComment = ' <error>** MISSING **</error>';
|
||||
}
|
||||
|
||||
$output->writeln(
|
||||
$status .
|
||||
sprintf(' %14.0f ', $migration['id']) .
|
||||
$name .
|
||||
$missingComment
|
||||
);
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
} else {
|
||||
$msg = 'There are no available migrations. Try creating one using the <info>create</info> command.';
|
||||
$output->writeln('');
|
||||
$output->writeln($msg);
|
||||
$output->writeln('');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Cake\Core\Plugin as CorePlugin;
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Migrations\Util\UtilTrait;
|
||||
use Phinx\Config\Config;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* Contains a set of methods designed as overrides for
|
||||
* the methods in phinx that are responsible for reading the project configuration.
|
||||
* This is needed so that we can use the application configuration instead of having
|
||||
* a configuration yaml file.
|
||||
*/
|
||||
trait ConfigurationTrait
|
||||
{
|
||||
use UtilTrait;
|
||||
|
||||
/**
|
||||
* The configuration object that phinx uses for connecting to the database
|
||||
*
|
||||
* @var \Phinx\Config\Config
|
||||
*/
|
||||
protected $configuration;
|
||||
|
||||
/**
|
||||
* Connection name to be used for this request
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The console input instance
|
||||
*
|
||||
* @var \Symfony\Component\Console\Input\Input
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Overrides the original method from phinx in order to return a tailored
|
||||
* Config object containing the connection details for the database.
|
||||
*
|
||||
* @param bool $forceRefresh Refresh config.
|
||||
* @return \Phinx\Config\Config
|
||||
*/
|
||||
public function getConfig($forceRefresh = false)
|
||||
{
|
||||
if ($this->configuration && $forceRefresh === false) {
|
||||
return $this->configuration;
|
||||
}
|
||||
|
||||
$migrationsPath = $this->getOperationsPath($this->input);
|
||||
$seedsPath = $this->getOperationsPath($this->input, 'Seeds');
|
||||
$plugin = $this->getPlugin($this->input);
|
||||
|
||||
if (!is_dir($migrationsPath)) {
|
||||
mkdir($migrationsPath, 0777, true);
|
||||
}
|
||||
|
||||
if (!is_dir($seedsPath)) {
|
||||
mkdir($seedsPath, 0777, true);
|
||||
}
|
||||
|
||||
$phinxTable = $this->getPhinxTable($plugin);
|
||||
|
||||
$connection = $this->getConnectionName($this->input);
|
||||
|
||||
$connectionConfig = ConnectionManager::getConfig($connection);
|
||||
$adapterName = $this->getAdapterName($connectionConfig['driver']);
|
||||
|
||||
$templatePath = __DIR__ . DS . 'Template' . DS;
|
||||
$config = [
|
||||
'paths' => [
|
||||
'migrations' => $migrationsPath,
|
||||
'seeds' => $seedsPath,
|
||||
],
|
||||
'templates' => [
|
||||
'file' => $templatePath . 'Phinx' . DS . 'create.php.template',
|
||||
],
|
||||
'migration_base_class' => 'Migrations\AbstractMigration',
|
||||
'environments' => [
|
||||
'default_migration_table' => $phinxTable,
|
||||
'default_database' => 'default',
|
||||
'default' => [
|
||||
'adapter' => $adapterName,
|
||||
'host' => isset($connectionConfig['host']) ? $connectionConfig['host'] : null,
|
||||
'user' => isset($connectionConfig['username']) ? $connectionConfig['username'] : null,
|
||||
'pass' => isset($connectionConfig['password']) ? $connectionConfig['password'] : null,
|
||||
'port' => isset($connectionConfig['port']) ? $connectionConfig['port'] : null,
|
||||
'name' => $connectionConfig['database'],
|
||||
'charset' => isset($connectionConfig['encoding']) ? $connectionConfig['encoding'] : null,
|
||||
'unix_socket' => isset($connectionConfig['unix_socket']) ? $connectionConfig['unix_socket'] : null,
|
||||
'suffix' => '',
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
if ($adapterName === 'pgsql') {
|
||||
if (!empty($connectionConfig['schema'])) {
|
||||
$config['environments']['default']['schema'] = $connectionConfig['schema'];
|
||||
}
|
||||
}
|
||||
|
||||
if ($adapterName === 'mysql') {
|
||||
if (!empty($connectionConfig['ssl_key']) && !empty($connectionConfig['ssl_cert'])) {
|
||||
$config['environments']['default']['mysql_attr_ssl_key'] = $connectionConfig['ssl_key'];
|
||||
$config['environments']['default']['mysql_attr_ssl_cert'] = $connectionConfig['ssl_cert'];
|
||||
}
|
||||
|
||||
if (!empty($connectionConfig['ssl_ca'])) {
|
||||
$config['environments']['default']['mysql_attr_ssl_ca'] = $connectionConfig['ssl_ca'];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->configuration = new Config($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the correct driver name to use in phinx based on the driver class
|
||||
* that was configured for the configuration.
|
||||
*
|
||||
* @param string $driver The driver name as configured for the CakePHP app.
|
||||
* @return string Name of the adapter.
|
||||
* @throws \InvalidArgumentException when it was not possible to infer the information
|
||||
* out of the provided database configuration
|
||||
*/
|
||||
public function getAdapterName($driver)
|
||||
{
|
||||
switch ($driver) {
|
||||
case 'Cake\Database\Driver\Mysql':
|
||||
case is_subclass_of($driver, 'Cake\Database\Driver\Mysql'):
|
||||
return 'mysql';
|
||||
case 'Cake\Database\Driver\Postgres':
|
||||
case is_subclass_of($driver, 'Cake\Database\Driver\Postgres'):
|
||||
return 'pgsql';
|
||||
case 'Cake\Database\Driver\Sqlite':
|
||||
case is_subclass_of($driver, 'Cake\Database\Driver\Sqlite'):
|
||||
return 'sqlite';
|
||||
case 'Cake\Database\Driver\Sqlserver':
|
||||
case is_subclass_of($driver, 'Cake\Database\Driver\Sqlserver'):
|
||||
return 'sqlsrv';
|
||||
}
|
||||
|
||||
throw new \InvalidArgumentException('Could not infer database type from driver');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the connection name that should be used for the migrations.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @return string
|
||||
*/
|
||||
protected function getConnectionName(InputInterface $input)
|
||||
{
|
||||
$connection = 'default';
|
||||
if ($input->getOption('connection')) {
|
||||
$connection = $input->getOption('connection');
|
||||
}
|
||||
|
||||
return $connection;
|
||||
}
|
||||
}
|
||||
+433
@@ -0,0 +1,433 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Phinx\Config\Config;
|
||||
use Phinx\Config\ConfigInterface;
|
||||
use Phinx\Db\Adapter\WrapperInterface;
|
||||
use Phinx\Migration\Manager;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\NullOutput;
|
||||
|
||||
/**
|
||||
* The Migrations class is responsible for handling migrations command
|
||||
* within an none-shell application.
|
||||
*/
|
||||
class Migrations
|
||||
{
|
||||
use ConfigurationTrait;
|
||||
|
||||
/**
|
||||
* The OutputInterface.
|
||||
* Should be a \Symfony\Component\Console\Output\NullOutput instance
|
||||
*
|
||||
* @var \Symfony\Component\Console\Output\OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* CakeManager instance
|
||||
*
|
||||
* @var \Migrations\CakeManager
|
||||
*/
|
||||
protected $manager;
|
||||
|
||||
/**
|
||||
* Default options to use
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $default = [];
|
||||
|
||||
/**
|
||||
* Current command being run.
|
||||
* Useful if some logic needs to be applied in the ConfigurationTrait depending
|
||||
* on the command
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $command;
|
||||
|
||||
/**
|
||||
* Stub input to feed the manager class since we might not have an input ready when we get the Manager using
|
||||
* the `getManager()` method
|
||||
*
|
||||
* @var \Symfony\Component\Console\Input\ArrayInput
|
||||
*/
|
||||
protected $stubInput;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param array $default Default option to be used when calling a method.
|
||||
* Available options are :
|
||||
* - `connection` The datasource connection to use
|
||||
* - `source` The folder where migrations are in
|
||||
* - `plugin` The plugin containing the migrations
|
||||
*/
|
||||
public function __construct(array $default = [])
|
||||
{
|
||||
$this->output = new NullOutput();
|
||||
$this->stubInput = new ArrayInput([]);
|
||||
|
||||
if ($default) {
|
||||
$this->default = $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the command
|
||||
*
|
||||
* @param string $command Command name to store.
|
||||
* @return self
|
||||
*/
|
||||
public function setCommand($command)
|
||||
{
|
||||
$this->command = $command;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the input object that should be used for the command class. This object
|
||||
* is used to inspect the extra options that are needed for CakePHP apps.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input the input object
|
||||
* @return void
|
||||
*/
|
||||
public function setInput(InputInterface $input)
|
||||
{
|
||||
$this->input = $input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the command
|
||||
*
|
||||
* @return string Command name
|
||||
*/
|
||||
public function getCommand()
|
||||
{
|
||||
return $this->command;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status of each migrations based on the options passed
|
||||
*
|
||||
* @param array $options Options to pass to the command
|
||||
* Available options are :
|
||||
*
|
||||
* - `format` Format to output the response. Can be 'json'
|
||||
* - `connection` The datasource connection to use
|
||||
* - `source` The folder where migrations are in
|
||||
* - `plugin` The plugin containing the migrations
|
||||
*
|
||||
* @return array The migrations list and their statuses
|
||||
*/
|
||||
public function status($options = [])
|
||||
{
|
||||
$this->setCommand('status');
|
||||
$input = $this->getInput('Status', [], $options);
|
||||
$params = ['default', $input->getOption('format')];
|
||||
|
||||
return $this->run('printStatus', $params, $input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates available migrations
|
||||
*
|
||||
* @param array $options Options to pass to the command
|
||||
* Available options are :
|
||||
*
|
||||
* - `target` The version number to migrate to. If not provided, will migrate
|
||||
* everything it can
|
||||
* - `connection` The datasource connection to use
|
||||
* - `source` The folder where migrations are in
|
||||
* - `plugin` The plugin containing the migrations
|
||||
* - `date` The date to migrate to
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function migrate($options = [])
|
||||
{
|
||||
$this->setCommand('migrate');
|
||||
$input = $this->getInput('Migrate', [], $options);
|
||||
$method = 'migrate';
|
||||
$params = ['default', $input->getOption('target')];
|
||||
|
||||
if ($input->getOption('date')) {
|
||||
$method = 'migrateToDateTime';
|
||||
$params[1] = new \DateTime($input->getOption('date'));
|
||||
}
|
||||
|
||||
$this->run($method, $params, $input);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollbacks migrations
|
||||
*
|
||||
* @param array $options Options to pass to the command
|
||||
* Available options are :
|
||||
*
|
||||
* - `target` The version number to migrate to. If not provided, will only migrate
|
||||
* the last migrations registered in the phinx log
|
||||
* - `connection` The datasource connection to use
|
||||
* - `source` The folder where migrations are in
|
||||
* - `plugin` The plugin containing the migrations
|
||||
* - `date` The date to rollback to
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function rollback($options = [])
|
||||
{
|
||||
$this->setCommand('rollback');
|
||||
$input = $this->getInput('Rollback', [], $options);
|
||||
$method = 'rollback';
|
||||
$params = ['default', $input->getOption('target')];
|
||||
|
||||
if ($input->getOption('date')) {
|
||||
$method = 'rollbackToDateTime';
|
||||
$params[1] = new \DateTime($input->getOption('date'));
|
||||
}
|
||||
|
||||
$this->run($method, $params, $input);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a migration as migrated
|
||||
*
|
||||
* @param int $version The version number of the migration to mark as migrated
|
||||
* @param array $options Options to pass to the command
|
||||
* Available options are :
|
||||
*
|
||||
* - `connection` The datasource connection to use
|
||||
* - `source` The folder where migrations are in
|
||||
* - `plugin` The plugin containing the migrations
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function markMigrated($version = null, $options = [])
|
||||
{
|
||||
$this->setCommand('mark_migrated');
|
||||
|
||||
if (
|
||||
isset($options['target']) &&
|
||||
isset($options['exclude']) &&
|
||||
isset($options['only'])
|
||||
) {
|
||||
$exceptionMessage = 'You should use `exclude` OR `only` (not both) along with a `target` argument';
|
||||
throw new \InvalidArgumentException($exceptionMessage);
|
||||
}
|
||||
|
||||
$input = $this->getInput('MarkMigrated', ['version' => $version], $options);
|
||||
$this->setInput($input);
|
||||
|
||||
$migrationPaths = $this->getConfig()->getMigrationPaths();
|
||||
$params = [
|
||||
array_pop($migrationPaths),
|
||||
$this->getManager()->getVersionsToMark($input),
|
||||
$this->output,
|
||||
];
|
||||
|
||||
$this->run('markVersionsAsMigrated', $params, $input);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seed the database using a seed file
|
||||
*
|
||||
* @param array $options Options to pass to the command
|
||||
* Available options are :
|
||||
*
|
||||
* - `connection` The datasource connection to use
|
||||
* - `source` The folder where migrations are in
|
||||
* - `plugin` The plugin containing the migrations
|
||||
* - `seed` The seed file to use
|
||||
*
|
||||
* @return bool Success
|
||||
*/
|
||||
public function seed($options = [])
|
||||
{
|
||||
$this->setCommand('seed');
|
||||
$input = $this->getInput('Seed', [], $options);
|
||||
|
||||
$seed = $input->getOption('seed');
|
||||
if (!$seed) {
|
||||
$seed = null;
|
||||
}
|
||||
|
||||
$params = ['default', $seed];
|
||||
$this->run('seed', $params, $input);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the method needed to execute and return
|
||||
*
|
||||
* @param string $method Manager method to call
|
||||
* @param array $params Manager params to pass
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input InputInterface needed for the
|
||||
* Manager to properly run
|
||||
*
|
||||
* @return mixed The result of the CakeManager::$method() call
|
||||
*/
|
||||
protected function run($method, $params, $input)
|
||||
{
|
||||
if ($this->configuration instanceof Config) {
|
||||
$migrationPaths = $this->getConfig()->getMigrationPaths();
|
||||
$migrationPath = array_pop($migrationPaths);
|
||||
$seedPaths = $this->getConfig()->getSeedPaths();
|
||||
$seedPath = array_pop($seedPaths);
|
||||
}
|
||||
|
||||
$pdo = null;
|
||||
if ($this->manager instanceof Manager) {
|
||||
$pdo = $this->manager->getEnvironment('default')
|
||||
->getAdapter()
|
||||
->getConnection();
|
||||
}
|
||||
|
||||
$this->setInput($input);
|
||||
$newConfig = $this->getConfig(true);
|
||||
$manager = $this->getManager($newConfig);
|
||||
$manager->setInput($input);
|
||||
|
||||
if ($pdo !== null) {
|
||||
$adapter = $this->manager->getEnvironment('default')->getAdapter();
|
||||
while ($adapter instanceof WrapperInterface) {
|
||||
$adapter = $adapter->getAdapter();
|
||||
}
|
||||
$adapter->setConnection($pdo);
|
||||
}
|
||||
|
||||
$newMigrationPaths = $newConfig->getMigrationPaths();
|
||||
if (isset($migrationPath) && array_pop($newMigrationPaths) !== $migrationPath) {
|
||||
$manager->resetMigrations();
|
||||
}
|
||||
$newSeedPaths = $newConfig->getSeedPaths();
|
||||
if (isset($seedPath) && array_pop($newSeedPaths) !== $seedPath) {
|
||||
$manager->resetSeeds();
|
||||
}
|
||||
|
||||
return call_user_func_array([$manager, $method], $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an instance of CakeManager
|
||||
*
|
||||
* @param \Phinx\Config\ConfigInterface|null $config ConfigInterface the Manager needs to run
|
||||
* @return \Migrations\CakeManager Instance of CakeManager
|
||||
*/
|
||||
public function getManager($config = null)
|
||||
{
|
||||
if (!($this->manager instanceof CakeManager)) {
|
||||
if (!($config instanceof ConfigInterface)) {
|
||||
throw new \RuntimeException(
|
||||
'You need to pass a ConfigInterface object for your first getManager() call'
|
||||
);
|
||||
}
|
||||
|
||||
$input = $this->input ?: $this->stubInput;
|
||||
$this->manager = new CakeManager($config, $input, $this->output);
|
||||
} elseif ($config !== null) {
|
||||
$defaultEnvironment = $config->getEnvironment('default');
|
||||
try {
|
||||
$environment = $this->manager->getEnvironment('default');
|
||||
$oldConfig = $environment->getOptions();
|
||||
unset($oldConfig['connection']);
|
||||
if ($oldConfig == $defaultEnvironment) {
|
||||
$defaultEnvironment['connection'] = $environment
|
||||
->getAdapter()
|
||||
->getConnection();
|
||||
}
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
}
|
||||
$config['environments'] = ['default' => $defaultEnvironment];
|
||||
$this->manager->setEnvironments([]);
|
||||
$this->manager->setConfig($config);
|
||||
}
|
||||
|
||||
$this->setAdapter();
|
||||
|
||||
return $this->manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the adapter the manager is going to need to operate on the DB
|
||||
* This will make sure the adapter instance is a \Migrations\CakeAdapter instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setAdapter()
|
||||
{
|
||||
if ($this->input !== null) {
|
||||
$connectionName = 'default';
|
||||
if ($this->input->getOption('connection')) {
|
||||
$connectionName = $this->input->getOption('connection');
|
||||
}
|
||||
$connection = ConnectionManager::get($connectionName);
|
||||
|
||||
$env = $this->manager->getEnvironment('default');
|
||||
$adapter = $env->getAdapter();
|
||||
if (!$adapter instanceof CakeAdapter) {
|
||||
$env->setAdapter(new CakeAdapter($adapter, $connection));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the input needed for each commands to be run
|
||||
*
|
||||
* @param string $command Command name for which we need the InputInterface
|
||||
* @param array $arguments Simple key/values array representing the command arguments
|
||||
* to pass to the InputInterface
|
||||
* @param array $options Simple key/values array representing the command options
|
||||
* to pass to the InputInterface
|
||||
* @return \Symfony\Component\Console\Input\InputInterface InputInterface needed for the
|
||||
* Manager to properly run
|
||||
*/
|
||||
public function getInput($command, $arguments, $options)
|
||||
{
|
||||
$className = '\Migrations\Command\\' . $command;
|
||||
$options = $arguments + $this->prepareOptions($options);
|
||||
$definition = (new $className())->getDefinition();
|
||||
|
||||
return new ArrayInput($options, $definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the option to pass on to the InputInterface
|
||||
*
|
||||
* @param array $options Simple key-values array to pass to the InputInterface
|
||||
* @return array Prepared $options
|
||||
*/
|
||||
protected function prepareOptions($options = [])
|
||||
{
|
||||
$options = array_merge($this->default, $options);
|
||||
if (!$options) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
foreach ($options as $name => $value) {
|
||||
$options['--' . $name] = $value;
|
||||
unset($options[$name]);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Migrations\Command;
|
||||
use Symfony\Component\Console\Application;
|
||||
|
||||
/**
|
||||
* Used to register all supported subcommand in order to make
|
||||
* them executable by the Symfony Console component
|
||||
*/
|
||||
class MigrationsDispatcher extends Application
|
||||
{
|
||||
/**
|
||||
* Class Constructor.
|
||||
*
|
||||
* Initialize the Phinx console application.
|
||||
*
|
||||
* @param string $version The Application Version
|
||||
*/
|
||||
public function __construct($version)
|
||||
{
|
||||
parent::__construct('Migrations plugin, based on Phinx by Rob Morgan.', $version);
|
||||
$this->add(new Command\Create());
|
||||
$this->add(new Command\Dump());
|
||||
$this->add(new Command\MarkMigrated());
|
||||
$this->add(new Command\Migrate());
|
||||
$this->add(new Command\Rollback());
|
||||
$this->add(new Command\Seed());
|
||||
$this->add(new Command\Status());
|
||||
$this->add(new Command\CacheBuild());
|
||||
$this->add(new Command\CacheClear());
|
||||
$this->setCatchExceptions(false);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Cake\Core\BasePlugin;
|
||||
|
||||
/**
|
||||
* Plugin class for migrations
|
||||
*/
|
||||
class Plugin extends BasePlugin
|
||||
{
|
||||
/**
|
||||
* Plugin name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = 'Migrations';
|
||||
|
||||
/**
|
||||
* Don't try to load routes.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $routesEnabled = false;
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell;
|
||||
|
||||
use Cake\Console\Shell;
|
||||
use Migrations\MigrationsDispatcher;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
|
||||
/**
|
||||
* A wrapper shell for phinx migrations, used to inject our own
|
||||
* console actions so that database configuration already defined
|
||||
* for the application can be reused.
|
||||
*
|
||||
* @property \Migrations\Shell\Task\CreateTask $Create
|
||||
* @property \Migrations\Shell\Task\DumpTask $Dump
|
||||
* @property \Migrations\Shell\Task\MarkMigratedTask $MarkMigrated
|
||||
* @property \Migrations\Shell\Task\MigrateTask $Migrate
|
||||
* @property \Migrations\Shell\Task\RollbackTask $Rollback
|
||||
* @property \Migrations\Shell\Task\StatusTask $Status
|
||||
*/
|
||||
class MigrationsShell extends Shell
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public $tasks = [
|
||||
'Migrations.Create',
|
||||
'Migrations.Dump',
|
||||
'Migrations.MarkMigrated',
|
||||
'Migrations.Migrate',
|
||||
'Migrations.Rollback',
|
||||
'Migrations.Status',
|
||||
];
|
||||
|
||||
/**
|
||||
* Array of arguments to run the shell with.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $argv = [];
|
||||
|
||||
/**
|
||||
* Defines what options can be passed to the shell.
|
||||
* This is required because CakePHP validates the passed options
|
||||
* and would complain if something not configured here is present
|
||||
*
|
||||
* @return \Cake\Console\ConsoleOptionParser
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
return parent::getOptionParser()
|
||||
->addOption('plugin', ['short' => 'p'])
|
||||
->addOption('target', ['short' => 't'])
|
||||
->addOption('connection', ['short' => 'c'])
|
||||
->addOption('source', ['short' => 's'])
|
||||
->addOption('seed')
|
||||
->addOption('ansi')
|
||||
->addOption('no-ansi')
|
||||
->addOption('no-lock', ['boolean' => true])
|
||||
->addOption('force', ['boolean' => true])
|
||||
->addOption('version', ['short' => 'V'])
|
||||
->addOption('no-interaction', ['short' => 'n'])
|
||||
->addOption('template', ['short' => 't'])
|
||||
->addOption('format', ['short' => 'f'])
|
||||
->addOption('only', ['short' => 'o'])
|
||||
->addOption('dry-run', ['short' => 'x'])
|
||||
->addOption('exclude', ['short' => 'e']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines constants that are required by phinx to get running
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function initialize()
|
||||
{
|
||||
if (!defined('PHINX_VERSION')) {
|
||||
define('PHINX_VERSION', 'UNKNOWN');
|
||||
}
|
||||
parent::initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* This acts as a front-controller for phinx. It just instantiates the classes
|
||||
* responsible for parsing the command line from phinx and gives full control of
|
||||
* the rest of the flow to it.
|
||||
*
|
||||
* The input parameter of the ``MigrationDispatcher::run()`` method is manually built
|
||||
* in case a MigrationsShell is dispatched using ``Shell::dispatch()``.
|
||||
*
|
||||
* @return bool Success of the call.
|
||||
*/
|
||||
public function main()
|
||||
{
|
||||
$app = $this->getApp();
|
||||
$input = new ArgvInput($this->argv);
|
||||
$app->setAutoExit(false);
|
||||
$exitCode = $app->run($input, $this->getOutput());
|
||||
|
||||
if (in_array('-h', $this->argv) || in_array('--help', $this->argv)) {
|
||||
return $exitCode === 0;
|
||||
}
|
||||
|
||||
if (
|
||||
isset($this->argv[1]) && in_array($this->argv[1], ['migrate', 'rollback']) &&
|
||||
!$this->params['no-lock'] &&
|
||||
$exitCode === 0
|
||||
) {
|
||||
$dispatchCommand = 'migrations dump';
|
||||
if (!empty($this->params['connection'])) {
|
||||
$dispatchCommand .= ' -c ' . $this->params['connection'];
|
||||
}
|
||||
|
||||
if (!empty($this->params['plugin'])) {
|
||||
$dispatchCommand .= ' -p ' . $this->params['plugin'];
|
||||
}
|
||||
|
||||
$dumpExitCode = $this->dispatchShell($dispatchCommand);
|
||||
}
|
||||
|
||||
if (isset($dumpExitCode) && $exitCode === 0 && $dumpExitCode !== 0) {
|
||||
$exitCode = 1;
|
||||
}
|
||||
|
||||
return $exitCode === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the MigrationsDispatcher the Shell will have to use
|
||||
*
|
||||
* @return \Migrations\MigrationsDispatcher
|
||||
*/
|
||||
protected function getApp()
|
||||
{
|
||||
return new MigrationsDispatcher(PHINX_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the instance of OutputInterface the MigrationsDispatcher will have to use.
|
||||
*
|
||||
* @return \Symfony\Component\Console\Output\ConsoleOutput
|
||||
*/
|
||||
protected function getOutput()
|
||||
{
|
||||
return new ConsoleOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the default behavior to save the command called
|
||||
* in order to pass it to the command dispatcher
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function runCommand($argv, $autoMethod = false, $extra = [])
|
||||
{
|
||||
array_unshift($argv, 'migrations');
|
||||
$this->argv = $argv;
|
||||
|
||||
return parent::runCommand($argv, $autoMethod, $extra);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the help in the correct format
|
||||
*
|
||||
* @param string $command The command to get help for.
|
||||
* @return int|bool|null Exit code or number of bytes written to stdout
|
||||
*/
|
||||
protected function displayHelp($command)
|
||||
{
|
||||
return $this->main();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
// @codingStandardsIgnoreStart
|
||||
protected function _displayHelp($command)
|
||||
{
|
||||
// @codingStandardsIgnoreEnd
|
||||
return $this->displayHelp($command);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Cake\Console\Shell;
|
||||
|
||||
/**
|
||||
* Base Task that Migrations subcommands Task class should extend.
|
||||
* It implements the getOptionParser method that defines the common options
|
||||
* for all subcommands.
|
||||
*/
|
||||
class CommandTask extends Shell
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
$parser
|
||||
->addOption('plugin', [
|
||||
'short' => 'p',
|
||||
'help' => 'The plugin the command should be applied to',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('connection', [
|
||||
'short' => 'c',
|
||||
'help' => 'The datasource connection to use',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('source', [
|
||||
'short' => 's',
|
||||
'help' => 'The name of the folder where migrations are stored',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('ansi')
|
||||
->addOption('no-ansi')
|
||||
->addOption('no-interaction', ['short' => 'n']);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Phinx\Console\Command\Create;
|
||||
|
||||
/**
|
||||
* This task class is needed in order to provide a correct autocompletion feature
|
||||
* when using the CakePHP migrations shell plugin. It has no effect on the
|
||||
* migrations process.
|
||||
*/
|
||||
class CreateTask extends CommandTask
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
$parser
|
||||
->addArgument('name', [
|
||||
'help' => 'What is the name of the migration?',
|
||||
])
|
||||
->addOption('template', [
|
||||
'short' => 't',
|
||||
'help' => 'Use an alternative template',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('class', [
|
||||
'short' => 'l',
|
||||
'help' => 'Use a class implementing "' . Create::CREATION_INTERFACE . '" to generate the template',
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
/**
|
||||
* This task class is needed in order to provide a correct autocompletion feature
|
||||
* when using the CakePHP migrations shell plugin. It has no effect on the
|
||||
* migrations process.
|
||||
*/
|
||||
class DumpTask extends CommandTask
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
$parser
|
||||
->setDescription('Dumps the current schema of the database to be used while baking a diff');
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
/**
|
||||
* This task class is needed in order to provide a correct autocompletion feature
|
||||
* when using the CakePHP migrations shell plugin. It has no effect on the
|
||||
* migrations process.
|
||||
*/
|
||||
class MarkMigratedTask extends CommandTask
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
$parser
|
||||
->addArgument('version', [
|
||||
'help' => 'What is the version of the migration?',
|
||||
])
|
||||
->addOption('exclude', [
|
||||
'short' => 'x',
|
||||
'help' => 'If present it will mark migrations from beginning until the given version, excluding it',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('only', [
|
||||
'short' => 'o',
|
||||
'help' => 'If present it will only mark the given migration version',
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
/**
|
||||
* This task class is needed in order to provide a correct autocompletion feature
|
||||
* when using the CakePHP migrations shell plugin. It has no effect on the
|
||||
* migrations process.
|
||||
*/
|
||||
class MigrateTask extends CommandTask
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
$parser
|
||||
->addOption('target', [
|
||||
'short' => 't',
|
||||
'help' => 'The version number to migrate to',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('date', [
|
||||
'short' => 'd',
|
||||
'help' => 'The date to migrate to',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('dry-run', ['short' => 'x'])
|
||||
->addOption('fake', ['boolean' => true])
|
||||
->addOption('no-lock', [
|
||||
'help' => 'If present, no lock file will be generated after migrating',
|
||||
'boolean' => true,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,561 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Database\Schema\Table;
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Cake\Event\Event;
|
||||
use Cake\Event\EventManager;
|
||||
use Migrations\Util\UtilTrait;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
|
||||
/**
|
||||
* Task class for generating migration diff files.
|
||||
*
|
||||
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
|
||||
* @property \Bake\Shell\Task\TestTask $Test
|
||||
*/
|
||||
class MigrationDiffTask extends SimpleMigrationTask
|
||||
{
|
||||
use SnapshotTrait;
|
||||
use UtilTrait;
|
||||
|
||||
/**
|
||||
* Array of migrations that have already been migrated
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migratedItems = [];
|
||||
|
||||
/**
|
||||
* Path to the migration files
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $migrationsPath;
|
||||
|
||||
/**
|
||||
* Migration files that are stored in the self::migrationsPath
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $migrationsFiles = [];
|
||||
|
||||
/**
|
||||
* Name of the phinx log table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $phinxTable;
|
||||
|
||||
/**
|
||||
* List the tables the connection currently holds
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tables = [];
|
||||
|
||||
/**
|
||||
* Array of \Cake\Database\Schema\Table objects from the dump file which
|
||||
* represents the state of the database after the last migrate / rollback command
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $dumpSchema;
|
||||
|
||||
/**
|
||||
* Array of \Cake\Database\Schema\Table objects from the current state of the database
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $currentSchema;
|
||||
|
||||
/**
|
||||
* List of the tables that are commonly found in the dump schema and the current schema
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $commonTables;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected $templateData = [];
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bake($name)
|
||||
{
|
||||
$this->setup();
|
||||
|
||||
if (!$this->checkSync()) {
|
||||
$this->abort('Your migrations history is not in sync with your migrations files. ' .
|
||||
'Make sure all your migrations have been migrated before baking a diff.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (empty($this->migrationsFiles) && empty($this->migratedItems)) {
|
||||
return $this->bakeSnapshot($name);
|
||||
}
|
||||
|
||||
$collection = $this->getCollection($this->connection);
|
||||
EventManager::instance()->on('Bake.initialize', function (Event $event) use ($collection) {
|
||||
$event->getSubject()->loadHelper('Migrations.Migration', [
|
||||
'collection' => $collection,
|
||||
]);
|
||||
});
|
||||
|
||||
return parent::bake($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up everything the baking process needs
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup()
|
||||
{
|
||||
$this->migrationsPath = $this->getPath();
|
||||
$this->migrationsFiles = glob($this->migrationsPath . '*.php');
|
||||
$this->phinxTable = $this->getPhinxTable($this->plugin);
|
||||
|
||||
$connection = ConnectionManager::get($this->connection);
|
||||
$this->tables = $connection->getSchemaCollection()->listTables();
|
||||
$tableExists = in_array($this->phinxTable, $this->tables);
|
||||
|
||||
$migratedItems = [];
|
||||
if ($tableExists) {
|
||||
$query = $connection->newQuery();
|
||||
$migratedItems = $query
|
||||
->select(['version'])
|
||||
->from($this->phinxTable)
|
||||
->order(['version DESC'])
|
||||
->execute()->fetchAll('assoc');
|
||||
}
|
||||
|
||||
$this->migratedItems = $migratedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection from a database.
|
||||
*
|
||||
* @param string $connection Database connection name.
|
||||
* @return \Cake\Database\Schema\Collection
|
||||
*/
|
||||
public function getCollection($connection)
|
||||
{
|
||||
$connection = ConnectionManager::get($connection);
|
||||
|
||||
return $connection->getSchemaCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and prepare the data needed for the bake template to be generated.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function templateData()
|
||||
{
|
||||
$this->dumpSchema = $this->getDumpSchema();
|
||||
$this->currentSchema = $this->getCurrentSchema();
|
||||
$this->commonTables = array_intersect_key($this->currentSchema, $this->dumpSchema);
|
||||
|
||||
$this->calculateDiff();
|
||||
|
||||
return [
|
||||
'data' => $this->templateData,
|
||||
'dumpSchema' => $this->dumpSchema,
|
||||
'currentSchema' => $this->currentSchema,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods runs the various methods needed to calculate a diff between the current
|
||||
* state of the database and the schema dump file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function calculateDiff()
|
||||
{
|
||||
$this->getConstraints();
|
||||
$this->getIndexes();
|
||||
$this->getColumns();
|
||||
$this->getTables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the diff between the current state of the database and the schema dump
|
||||
* by returning an array containing the full \Cake\Database\Schema\Table definitions
|
||||
* of tables to be created and removed in the diff file.
|
||||
*
|
||||
* The method directly sets the diff in a property of the class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function getTables()
|
||||
{
|
||||
$this->templateData['fullTables'] = [
|
||||
'add' => array_diff_key($this->currentSchema, $this->dumpSchema),
|
||||
'remove' => array_diff_key($this->dumpSchema, $this->currentSchema),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the diff between columns in existing tables.
|
||||
* This will look for columns addition, columns removal and changes in columns metadata
|
||||
* such as change of types or property such as length.
|
||||
*
|
||||
* Note that the method is not able to detect columns name change.
|
||||
* The method directly sets the diff in a property of the class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function getColumns()
|
||||
{
|
||||
foreach ($this->commonTables as $table => $currentSchema) {
|
||||
$currentColumns = $currentSchema->columns();
|
||||
$oldColumns = $this->dumpSchema[$table]->columns();
|
||||
|
||||
// brand new columns
|
||||
$addedColumns = array_diff($currentColumns, $oldColumns);
|
||||
foreach ($addedColumns as $columnName) {
|
||||
$column = $currentSchema->getColumn($columnName);
|
||||
$key = array_search($columnName, $currentColumns);
|
||||
if ($key > 0) {
|
||||
$column['after'] = $currentColumns[$key - 1];
|
||||
}
|
||||
if (isset($column['unsigned'])) {
|
||||
$column['signed'] = !$column['unsigned'];
|
||||
unset($column['unsigned']);
|
||||
}
|
||||
$this->templateData[$table]['columns']['add'][$columnName] = $column;
|
||||
}
|
||||
|
||||
// changes in columns meta-data
|
||||
foreach ($currentColumns as $columnName) {
|
||||
$column = $currentSchema->getColumn($columnName);
|
||||
$oldColumn = $this->dumpSchema[$table]->getColumn($columnName);
|
||||
unset($column['collate']);
|
||||
unset($oldColumn['collate']);
|
||||
|
||||
if (
|
||||
in_array($columnName, $oldColumns) &&
|
||||
$column !== $oldColumn
|
||||
) {
|
||||
$changedAttributes = array_diff_assoc($column, $oldColumn);
|
||||
|
||||
foreach (['type', 'length', 'null', 'default'] as $attribute) {
|
||||
$phinxAttributeName = $attribute;
|
||||
if ($attribute == 'length') {
|
||||
$phinxAttributeName = 'limit';
|
||||
}
|
||||
if (!isset($changedAttributes[$phinxAttributeName])) {
|
||||
$changedAttributes[$phinxAttributeName] = $column[$attribute];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($changedAttributes['unsigned'])) {
|
||||
$changedAttributes['signed'] = !$changedAttributes['unsigned'];
|
||||
unset($changedAttributes['unsigned']);
|
||||
} else {
|
||||
// badish hack
|
||||
if (isset($column['unsigned']) && $column['unsigned'] === true) {
|
||||
$changedAttributes['signed'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($changedAttributes['length'])) {
|
||||
if (!isset($changedAttributes['limit'])) {
|
||||
$changedAttributes['limit'] = $changedAttributes['length'];
|
||||
}
|
||||
|
||||
unset($changedAttributes['length']);
|
||||
}
|
||||
|
||||
$this->templateData[$table]['columns']['changed'][$columnName] = $changedAttributes;
|
||||
}
|
||||
}
|
||||
|
||||
// columns deletion
|
||||
if (!isset($this->templateData[$table]['columns']['remove'])) {
|
||||
$this->templateData[$table]['columns']['remove'] = [];
|
||||
}
|
||||
$removedColumns = array_diff($oldColumns, $currentColumns);
|
||||
if (!empty($removedColumns)) {
|
||||
foreach ($removedColumns as $columnName) {
|
||||
$column = $this->dumpSchema[$table]->getColumn($columnName);
|
||||
$key = array_search($columnName, $oldColumns);
|
||||
if ($key > 0) {
|
||||
$column['after'] = $oldColumns[$key - 1];
|
||||
}
|
||||
$this->templateData[$table]['columns']['remove'][$columnName] = $column;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the diff between contraints in existing tables.
|
||||
* This will look for contraints addition, contraints removal and changes in contraints metadata
|
||||
* such as change of referenced columns if the old constraints and the new one have the same name.
|
||||
*
|
||||
* The method directly sets the diff in a property of the class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function getConstraints()
|
||||
{
|
||||
foreach ($this->commonTables as $table => $currentSchema) {
|
||||
$currentConstraints = $currentSchema->constraints();
|
||||
$oldConstraints = $this->dumpSchema[$table]->constraints();
|
||||
|
||||
// brand new constraints
|
||||
$addedConstraints = array_diff($currentConstraints, $oldConstraints);
|
||||
foreach ($addedConstraints as $constraintName) {
|
||||
$this->templateData[$table]['constraints']['add'][$constraintName] =
|
||||
$currentSchema->getConstraint($constraintName);
|
||||
$constraint = $currentSchema->getConstraint($constraintName);
|
||||
if ($constraint['type'] === Table::CONSTRAINT_FOREIGN) {
|
||||
$this->templateData[$table]['constraints']['add'][$constraintName] = $constraint;
|
||||
} else {
|
||||
$this->templateData[$table]['indexes']['add'][$constraintName] = $constraint;
|
||||
}
|
||||
}
|
||||
|
||||
// constraints having the same name between new and old schema
|
||||
// if present in both, check if they are the same : if not, remove the old one and add the new one
|
||||
foreach ($currentConstraints as $constraintName) {
|
||||
$constraint = $currentSchema->getConstraint($constraintName);
|
||||
|
||||
if (
|
||||
in_array($constraintName, $oldConstraints) &&
|
||||
$constraint !== $this->dumpSchema[$table]->getConstraint($constraintName)
|
||||
) {
|
||||
$this->templateData[$table]['constraints']['remove'][$constraintName] =
|
||||
$this->dumpSchema[$table]->getConstraint($constraintName);
|
||||
$this->templateData[$table]['constraints']['add'][$constraintName] =
|
||||
$constraint;
|
||||
}
|
||||
}
|
||||
|
||||
// removed constraints
|
||||
$removedConstraints = array_diff($oldConstraints, $currentConstraints);
|
||||
foreach ($removedConstraints as $constraintName) {
|
||||
$constraint = $this->dumpSchema[$table]->getConstraint($constraintName);
|
||||
if ($constraint['type'] === Table::CONSTRAINT_FOREIGN) {
|
||||
$this->templateData[$table]['constraints']['remove'][$constraintName] = $constraint;
|
||||
} else {
|
||||
$this->templateData[$table]['indexes']['remove'][$constraintName] = $constraint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the diff between indexes in existing tables.
|
||||
* This will look for indexes addition, indexes removal and changes in indexes metadata
|
||||
* such as change of referenced columns if the old indexes and the new one have the same name.
|
||||
*
|
||||
* The method directly sets the diff in a property of the class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function getIndexes()
|
||||
{
|
||||
foreach ($this->commonTables as $table => $currentSchema) {
|
||||
$currentIndexes = $currentSchema->indexes();
|
||||
$oldIndexes = $this->dumpSchema[$table]->indexes();
|
||||
sort($currentIndexes);
|
||||
sort($oldIndexes);
|
||||
|
||||
// brand new indexes
|
||||
$addedIndexes = array_diff($currentIndexes, $oldIndexes);
|
||||
foreach ($addedIndexes as $indexName) {
|
||||
$this->templateData[$table]['indexes']['add'][$indexName] = $currentSchema->getIndex($indexName);
|
||||
}
|
||||
|
||||
// indexes having the same name between new and old schema
|
||||
// if present in both, check if they are the same : if not, remove the old one and add the new one
|
||||
foreach ($currentIndexes as $indexName) {
|
||||
$index = $currentSchema->getIndex($indexName);
|
||||
|
||||
if (
|
||||
in_array($indexName, $oldIndexes) &&
|
||||
$index !== $this->dumpSchema[$table]->getIndex($indexName)
|
||||
) {
|
||||
$this->templateData[$table]['indexes']['remove'][$indexName] =
|
||||
$this->dumpSchema[$table]->getIndex($indexName);
|
||||
$this->templateData[$table]['indexes']['add'][$indexName] = $index;
|
||||
}
|
||||
}
|
||||
|
||||
// indexes deletion
|
||||
if (!isset($this->templateData[$table]['indexes']['remove'])) {
|
||||
$this->templateData[$table]['indexes']['remove'] = [];
|
||||
}
|
||||
|
||||
$removedIndexes = array_diff($oldIndexes, $currentIndexes);
|
||||
$parts = [];
|
||||
if (!empty($removedIndexes)) {
|
||||
foreach ($removedIndexes as $index) {
|
||||
$parts[$index] = $this->dumpSchema[$table]->getIndex($index);
|
||||
}
|
||||
}
|
||||
$this->templateData[$table]['indexes']['remove'] = array_merge(
|
||||
$this->templateData[$table]['indexes']['remove'],
|
||||
$parts
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the migrations history is in sync with the migrations files
|
||||
*
|
||||
* @return bool Whether migrations history is sync or not
|
||||
*/
|
||||
protected function checkSync()
|
||||
{
|
||||
if (empty($this->migrationsFiles) && empty($this->migratedItems)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($this->migratedItems)) {
|
||||
$lastVersion = $this->migratedItems[0]['version'];
|
||||
$lastFile = end($this->migrationsFiles);
|
||||
|
||||
return (bool)strpos($lastFile, (string)$lastVersion);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback method called to bake a snapshot when the phinx log history is empty and
|
||||
* there are no migration files.
|
||||
*
|
||||
* @param string $name Name.
|
||||
* @return int Value of the snapshot baking dispatch process
|
||||
*/
|
||||
protected function bakeSnapshot($name)
|
||||
{
|
||||
$this->out('Your migrations history is empty and you do not have any migrations files.');
|
||||
$this->out('Falling back to baking a snapshot...');
|
||||
$dispatchCommand = 'bake migration_snapshot ' . $name;
|
||||
|
||||
if (!empty($this->params['connection'])) {
|
||||
$dispatchCommand .= ' -c ' . $this->params['connection'];
|
||||
}
|
||||
if (!empty($this->params['plugin'])) {
|
||||
$dispatchCommand .= ' -p ' . $this->params['plugin'];
|
||||
}
|
||||
|
||||
$dispatch = $this->dispatchShell([
|
||||
'command' => $dispatchCommand,
|
||||
]);
|
||||
|
||||
if ($dispatch === 1) {
|
||||
$this->abort('Something went wrong during the snapshot baking. Please try again.');
|
||||
}
|
||||
|
||||
return $dispatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the correct schema dump based on the arguments and options passed to the shell call
|
||||
* and returns it as an array
|
||||
*
|
||||
* @return array Full database schema : the key is the name of the table and the value is
|
||||
* an instance of \Cake\Database\Schema\Table.
|
||||
*/
|
||||
protected function getDumpSchema()
|
||||
{
|
||||
$inputArgs = [];
|
||||
|
||||
$connectionName = 'default';
|
||||
if (!empty($this->params['connection'])) {
|
||||
$connectionName = $inputArgs['--connection'] = $this->params['connection'];
|
||||
}
|
||||
if (!empty($this->params['plugin'])) {
|
||||
$inputArgs['--plugin'] = $this->params['plugin'];
|
||||
}
|
||||
|
||||
$className = '\Migrations\Command\Dump';
|
||||
$definition = (new $className())->getDefinition();
|
||||
|
||||
$input = new ArrayInput($inputArgs, $definition);
|
||||
$path = $this->getOperationsPath($input) . DS . 'schema-dump-' . $connectionName . '.lock';
|
||||
|
||||
if (!file_exists($path)) {
|
||||
$msg = 'Unable to retrieve the schema dump file. You can create a dump file using ' .
|
||||
'the `cake migrations dump` command';
|
||||
$this->abort($msg);
|
||||
}
|
||||
|
||||
return unserialize(file_get_contents($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reflects the current database schema.
|
||||
*
|
||||
* @return array Full database schema : the key is the name of the table and the value is
|
||||
* an instance of \Cake\Database\Schema\Table.
|
||||
*/
|
||||
protected function getCurrentSchema()
|
||||
{
|
||||
$schema = [];
|
||||
|
||||
if (empty($this->tables)) {
|
||||
return $schema;
|
||||
}
|
||||
|
||||
$collection = ConnectionManager::get($this->connection)->getSchemaCollection();
|
||||
foreach ($this->tables as $table) {
|
||||
if (preg_match("/^.*phinxlog$/", $table) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schema[$table] = $collection->describe($table);
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function template()
|
||||
{
|
||||
return 'Migrations.config/diff';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the option parser instance and configures it.
|
||||
*
|
||||
* @return \Cake\Console\ConsoleOptionParser
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
|
||||
$parser->addArgument('name', [
|
||||
'help' => 'Name of the migration to bake. Can use Plugin.name to bake migration files into plugins.',
|
||||
'required' => true,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Cake\Event\Event;
|
||||
use Cake\Event\EventManager;
|
||||
use Migrations\TableFinderTrait;
|
||||
use Migrations\Util\UtilTrait;
|
||||
|
||||
/**
|
||||
* Task class for generating migration snapshot files.
|
||||
*
|
||||
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
|
||||
* @property \Bake\Shell\Task\TestTask $Test
|
||||
*/
|
||||
class MigrationSnapshotTask extends SimpleMigrationTask
|
||||
{
|
||||
use SnapshotTrait;
|
||||
use TableFinderTrait;
|
||||
use UtilTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bake($name)
|
||||
{
|
||||
$collection = $this->getCollection($this->connection);
|
||||
EventManager::instance()->on('Bake.initialize', function (Event $event) use ($collection) {
|
||||
$event->getSubject()->loadHelper('Migrations.Migration', [
|
||||
'collection' => $collection,
|
||||
]);
|
||||
});
|
||||
|
||||
return parent::bake($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function template()
|
||||
{
|
||||
return 'Migrations.config/snapshot';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function templateData()
|
||||
{
|
||||
$namespace = Configure::read('App.namespace');
|
||||
$pluginPath = '';
|
||||
if ($this->plugin) {
|
||||
$namespace = $this->_pluginNamespace($this->plugin);
|
||||
$pluginPath = $this->plugin . '.';
|
||||
}
|
||||
|
||||
$collection = $this->getCollection($this->connection);
|
||||
$options = [
|
||||
'require-table' => $this->params['require-table'],
|
||||
'plugin' => $this->plugin,
|
||||
];
|
||||
$tables = $this->getTablesToBake($collection, $options);
|
||||
|
||||
sort($tables, SORT_NATURAL);
|
||||
|
||||
$tables = array_combine($tables, $tables);
|
||||
|
||||
$autoId = true;
|
||||
if (isset($this->params['disable-autoid'])) {
|
||||
$autoId = !$this->params['disable-autoid'];
|
||||
}
|
||||
|
||||
return [
|
||||
'plugin' => $this->plugin,
|
||||
'pluginPath' => $pluginPath,
|
||||
'namespace' => $namespace,
|
||||
'collection' => $collection,
|
||||
'tables' => $tables,
|
||||
'action' => 'create_table',
|
||||
'name' => $this->BakeTemplate->viewVars['name'],
|
||||
'autoId' => $autoId,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a collection from a database
|
||||
*
|
||||
* @param string $connection Database connection name.
|
||||
* @return \Cake\Database\Schema\Collection
|
||||
*/
|
||||
public function getCollection($connection)
|
||||
{
|
||||
$connection = ConnectionManager::get($connection);
|
||||
|
||||
return $connection->getSchemaCollection();
|
||||
}
|
||||
|
||||
/**
|
||||
* To check if a Table Model is to be added in the migration file
|
||||
*
|
||||
* @param string $tableName Table name in underscore case.
|
||||
* @param string|null $pluginName Plugin name if exists.
|
||||
* @deprecated Will be removed in the next version
|
||||
* @return bool True if the model is to be added.
|
||||
*/
|
||||
public function tableToAdd($tableName, $pluginName = null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the option parser instance and configures it.
|
||||
*
|
||||
* @return \Cake\Console\ConsoleOptionParser
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
|
||||
$parser->setDescription(
|
||||
'Bake migration snapshot class.'
|
||||
)->addArgument('name', [
|
||||
'help' => 'Name of the migration to bake. Can use Plugin.name to bake migration files into plugins.',
|
||||
'required' => true,
|
||||
])
|
||||
->addOption('require-table', [
|
||||
'boolean' => true,
|
||||
'default' => false,
|
||||
'help' => 'If require-table is set to true, check also that the table class exists.',
|
||||
])->addOption('disable-autoid', [
|
||||
'boolean' => true,
|
||||
'default' => false,
|
||||
'help' => 'Disable phinx behavior of automatically adding an id field.',
|
||||
])
|
||||
->addOption('no-lock', [
|
||||
'help' => 'If present, no lock file will be generated after baking',
|
||||
'boolean' => true,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Event\Event;
|
||||
use Cake\Event\EventManager;
|
||||
use Cake\Utility\Inflector;
|
||||
use Migrations\Util\ColumnParser;
|
||||
|
||||
/**
|
||||
* Task class for generating migration snapshot files.
|
||||
*
|
||||
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
|
||||
* @property \Bake\Shell\Task\TestTask $Test
|
||||
*/
|
||||
class MigrationTask extends SimpleMigrationTask
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bake($name)
|
||||
{
|
||||
EventManager::instance()->on('Bake.initialize', function (Event $event) {
|
||||
$event->getSubject()->loadHelper('Migrations.Migration');
|
||||
});
|
||||
|
||||
return parent::bake($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function template()
|
||||
{
|
||||
return 'Migrations.config/skeleton';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function templateData()
|
||||
{
|
||||
$className = $this->BakeTemplate->viewVars['name'];
|
||||
$namespace = Configure::read('App.namespace');
|
||||
$pluginPath = '';
|
||||
if ($this->plugin) {
|
||||
$namespace = $this->_pluginNamespace($this->plugin);
|
||||
$pluginPath = $this->plugin . '.';
|
||||
}
|
||||
|
||||
$action = $this->detectAction($className);
|
||||
|
||||
if (empty($action)) {
|
||||
return [
|
||||
'plugin' => $this->plugin,
|
||||
'pluginPath' => $pluginPath,
|
||||
'namespace' => $namespace,
|
||||
'tables' => [],
|
||||
'action' => null,
|
||||
'name' => $className,
|
||||
];
|
||||
}
|
||||
|
||||
$arguments = $this->args;
|
||||
unset($arguments[0]);
|
||||
$columnParser = new ColumnParser();
|
||||
$fields = $columnParser->parseFields($arguments);
|
||||
$indexes = $columnParser->parseIndexes($arguments);
|
||||
$primaryKey = $columnParser->parsePrimaryKey($arguments);
|
||||
|
||||
if (in_array($action[0], ['alter_table', 'add_field']) && !empty($primaryKey)) {
|
||||
$this->abort('Adding a primary key to an already existing table is not supported.');
|
||||
}
|
||||
|
||||
list($action, $table) = $action;
|
||||
|
||||
return [
|
||||
'plugin' => $this->plugin,
|
||||
'pluginPath' => $pluginPath,
|
||||
'namespace' => $namespace,
|
||||
'tables' => [$table],
|
||||
'action' => $action,
|
||||
'columns' => [
|
||||
'fields' => $fields,
|
||||
'indexes' => $indexes,
|
||||
'primaryKey' => $primaryKey,
|
||||
],
|
||||
'name' => $className,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the action and table from the name of a migration
|
||||
*
|
||||
* @param string $name Name of migration
|
||||
* @return array
|
||||
**/
|
||||
public function detectAction($name)
|
||||
{
|
||||
if (preg_match('/^(Create|Drop)(.*)/', $name, $matches)) {
|
||||
$action = strtolower($matches[1]) . '_table';
|
||||
$table = Inflector::underscore($matches[2]);
|
||||
} elseif (preg_match('/^(Add).+?(?:To)(.*)/', $name, $matches)) {
|
||||
$action = 'add_field';
|
||||
$table = Inflector::underscore($matches[2]);
|
||||
} elseif (preg_match('/^(Remove).+?(?:From)(.*)/', $name, $matches)) {
|
||||
$action = 'drop_field';
|
||||
$table = Inflector::underscore($matches[2]);
|
||||
} elseif (preg_match('/^(Alter).+?(?:On)(.*)/', $name, $matches)) {
|
||||
$action = 'alter_field';
|
||||
$table = Inflector::underscore($matches[2]);
|
||||
} elseif (preg_match('/^(Alter)(.*)/', $name, $matches)) {
|
||||
$action = 'alter_table';
|
||||
$table = Inflector::underscore($matches[2]);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [$action, $table];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
/**
|
||||
* This task class is needed in order to provide a correct autocompletion feature
|
||||
* when using the CakePHP migrations shell plugin. It has no effect on the
|
||||
* migrations process.
|
||||
*/
|
||||
class RollbackTask extends CommandTask
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
$parser
|
||||
->addOption('target', [
|
||||
'short' => 't',
|
||||
'help' => 'The version number to migrate to',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('date', [
|
||||
'short' => 'd',
|
||||
'help' => 'The date to migrate to',
|
||||
'required' => false,
|
||||
])
|
||||
->addOption('dry-run', ['short' => 'x'])
|
||||
->addOption('fake', ['boolean' => true])
|
||||
->addOption('no-lock', [
|
||||
'help' => 'If present, no lock file will be generated after rolling back',
|
||||
'boolean' => true,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Bake\Shell\Task\SimpleBakeTask;
|
||||
use Cake\Console\ConsoleOptionParser;
|
||||
use Cake\Core\Configure;
|
||||
use Cake\Core\Plugin as CorePlugin;
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Cake\Utility\Inflector;
|
||||
|
||||
/**
|
||||
* Task class for generating seed files.
|
||||
*
|
||||
* @property \Bake\Shell\Task\BakeTemplateTask $BakeTemplate
|
||||
* @property \Bake\Shell\Task\TestTask $Test
|
||||
*/
|
||||
class SeedTask extends SimpleBakeTask
|
||||
{
|
||||
/**
|
||||
* path to Migration directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $pathFragment = 'config/Seeds/';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
return 'seed';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fileName($name)
|
||||
{
|
||||
return Inflector::camelize($name) . 'Seed.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
$path = ROOT . DS . $this->pathFragment;
|
||||
if (isset($this->plugin)) {
|
||||
$path = $this->_pluginPath($this->plugin) . $this->pathFragment;
|
||||
}
|
||||
|
||||
return str_replace('/', DS, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function template()
|
||||
{
|
||||
return 'Migrations.Seed/seed';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function templateData()
|
||||
{
|
||||
$namespace = Configure::read('App.namespace');
|
||||
if ($this->plugin) {
|
||||
$namespace = $this->_pluginNamespace($this->plugin);
|
||||
}
|
||||
|
||||
$table = Inflector::tableize($this->args[0]);
|
||||
if (!empty($this->params['table'])) {
|
||||
$table = $this->params['table'];
|
||||
}
|
||||
|
||||
$records = false;
|
||||
if ($this->param('data')) {
|
||||
$limit = (int)$this->param('limit');
|
||||
|
||||
$fields = $this->param('fields') ?: '*';
|
||||
if ($fields !== '*') {
|
||||
$fields = explode(',', $fields);
|
||||
}
|
||||
|
||||
$connection = ConnectionManager::get($this->connection);
|
||||
|
||||
$query = $connection->newQuery()
|
||||
->from($table)
|
||||
->select($fields);
|
||||
|
||||
if ($limit) {
|
||||
$query->limit($limit);
|
||||
}
|
||||
|
||||
$records = $connection->execute($query)->fetchAll('assoc');
|
||||
$records = $this->prettifyArray($records);
|
||||
}
|
||||
|
||||
return [
|
||||
'className' => $this->BakeTemplate->viewVars['name'],
|
||||
'namespace' => $namespace,
|
||||
'records' => $records,
|
||||
'table' => $table,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bake($name)
|
||||
{
|
||||
$this->params['no-test'] = true;
|
||||
|
||||
return parent::bake($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the option parser instance and configures it.
|
||||
*
|
||||
* @return \Cake\Console\ConsoleOptionParser
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$name = ($this->plugin ? $this->plugin . '.' : '') . $this->name;
|
||||
$parser = new ConsoleOptionParser($name);
|
||||
|
||||
$bakeThemes = [];
|
||||
foreach (CorePlugin::loaded() as $plugin) {
|
||||
$path = CorePlugin::classPath($plugin);
|
||||
if (is_dir($path . 'Template' . DS . 'Bake')) {
|
||||
$bakeThemes[] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
$parser->setDescription(
|
||||
'Bake seed class.'
|
||||
)->addOption('plugin', [
|
||||
'short' => 'p',
|
||||
'help' => 'Plugin to bake into.',
|
||||
])->addOption('force', [
|
||||
'short' => 'f',
|
||||
'boolean' => true,
|
||||
'help' => 'Force overwriting existing files without prompting.',
|
||||
])->addOption('connection', [
|
||||
'short' => 'c',
|
||||
'default' => 'default',
|
||||
'help' => 'The datasource connection to get data from.',
|
||||
])->addOption('table', [
|
||||
'help' => 'The database table to use.',
|
||||
])->addOption('theme', [
|
||||
'short' => 't',
|
||||
'help' => 'The theme to use when baking code.',
|
||||
'choices' => $bakeThemes,
|
||||
])->addArgument('name', [
|
||||
'help' => 'Name of the seed to bake. Can use Plugin.name to bake plugin models.',
|
||||
])->addOption('data', [
|
||||
'boolean' => true,
|
||||
'help' => 'Include data from the table to the seed',
|
||||
])->addOption('fields', [
|
||||
'default' => '*',
|
||||
'help' => 'If including data, comma separated list of fields to select (all fields by default)',
|
||||
])->addOption('limit', [
|
||||
'short' => 'l',
|
||||
'help' => 'If including data, max number of rows to select',
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prettify var_export of an array output
|
||||
*
|
||||
* @param array $array Array to prettify
|
||||
* @param int $tabCount Initial tab count
|
||||
* @param string $indentCharacter Desired indent for the code.
|
||||
* @return string
|
||||
*/
|
||||
protected function prettifyArray($array, $tabCount = 3, $indentCharacter = " ")
|
||||
{
|
||||
$content = var_export($array, true);
|
||||
|
||||
$lines = explode("\n", $content);
|
||||
|
||||
$inString = false;
|
||||
|
||||
foreach ($lines as $k => &$line) {
|
||||
if ($k === 0) {
|
||||
// First row
|
||||
$line = '[';
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($k === count($lines) - 1) {
|
||||
// Last row
|
||||
$line = str_repeat($indentCharacter, --$tabCount) . ']';
|
||||
continue;
|
||||
}
|
||||
|
||||
$line = ltrim($line);
|
||||
|
||||
if (!$inString) {
|
||||
if ($line === '),') {
|
||||
//Check for closing bracket
|
||||
$line = '],';
|
||||
$tabCount--;
|
||||
} elseif (preg_match("/^\d+\s\=\>\s$/", $line)) {
|
||||
// Mark '0 =>' kind of lines to remove
|
||||
$line = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
//Insert tab count
|
||||
$line = str_repeat($indentCharacter, $tabCount) . $line;
|
||||
}
|
||||
|
||||
$length = strlen($line);
|
||||
for ($j = 0; $j < $length; $j++) {
|
||||
if ($line[$j] === '\\') {
|
||||
//skip character right after an escape \
|
||||
$j++;
|
||||
} elseif ($line[$j] === '\'') {
|
||||
//check string open/end
|
||||
$inString = !$inString;
|
||||
}
|
||||
}
|
||||
|
||||
//check for opening bracket
|
||||
if (!$inString && trim($line) === 'array (') {
|
||||
$line = str_replace('array (', '[', $line);
|
||||
$tabCount++;
|
||||
}
|
||||
}
|
||||
unset($line);
|
||||
|
||||
// Remove marked lines
|
||||
$lines = array_filter($lines, function ($line) {
|
||||
return $line !== false;
|
||||
});
|
||||
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Bake\Shell\Task\SimpleBakeTask;
|
||||
use Cake\Console\ConsoleOptionParser;
|
||||
use Cake\Core\Plugin as CorePlugin;
|
||||
use Cake\Utility\Inflector;
|
||||
use Phinx\Util\Util;
|
||||
|
||||
/**
|
||||
* Task class for generating migration snapshot files.
|
||||
*/
|
||||
abstract class SimpleMigrationTask extends SimpleBakeTask
|
||||
{
|
||||
/**
|
||||
* path to Migration directory
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $pathFragment = 'config/Migrations/';
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function name()
|
||||
{
|
||||
return 'migration';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function fileName($name)
|
||||
{
|
||||
$name = $this->getMigrationName($name);
|
||||
|
||||
return Util::getCurrentTimestamp() . '_' . Inflector::camelize($name) . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getPath()
|
||||
{
|
||||
$path = ROOT . DS . $this->pathFragment;
|
||||
if (isset($this->plugin)) {
|
||||
$path = $this->_pluginPath($this->plugin) . $this->pathFragment;
|
||||
}
|
||||
|
||||
return str_replace('/', DS, $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function bake($name)
|
||||
{
|
||||
$migrationWithSameName = glob($this->getPath() . '*_' . $name . '.php');
|
||||
if (!empty($migrationWithSameName)) {
|
||||
$force = $this->param('force');
|
||||
if (!$force) {
|
||||
$this->abort(
|
||||
sprintf(
|
||||
'A migration with the name `%s` already exists. Please use a different name.',
|
||||
$name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->info(sprintf('A migration with the name `%s` already exists, it will be deleted.', $name));
|
||||
foreach ($migrationWithSameName as $migration) {
|
||||
$this->info(sprintf('Deleting migration file `%s`...', $migration));
|
||||
if (unlink($migration)) {
|
||||
$this->success(sprintf('Deleted `%s`', $migration));
|
||||
} else {
|
||||
$this->err(sprintf('An error occurred while deleting `%s`', $migration));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->params['no-test'] = true;
|
||||
|
||||
return parent::bake($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a class name for the migration class
|
||||
*
|
||||
* If the name is invalid, the task will exit
|
||||
*
|
||||
* @param string|null $name Name for the generated migration
|
||||
* @return string|null Name of the migration file or null if empty
|
||||
*/
|
||||
protected function getMigrationName($name = null)
|
||||
{
|
||||
if (empty($name)) {
|
||||
$this->abort('Choose a migration name to bake in CamelCase format');
|
||||
}
|
||||
|
||||
$name = $this->_getName($name);
|
||||
$name = Inflector::camelize($name);
|
||||
|
||||
if (!preg_match('/^[A-Z]{1}[a-zA-Z0-9]+$/', $name)) {
|
||||
$this->abort('The className is not correct. The className can only contain "A-Z" and "0-9".');
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the option parser instance and configures it.
|
||||
*
|
||||
* @return \Cake\Console\ConsoleOptionParser
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$name = ($this->plugin ? $this->plugin . '.' : '') . $this->name;
|
||||
$parser = new ConsoleOptionParser($name);
|
||||
|
||||
$bakeThemes = [];
|
||||
foreach (CorePlugin::loaded() as $plugin) {
|
||||
$path = CorePlugin::classPath($plugin);
|
||||
if (is_dir($path . 'Template' . DS . 'Bake')) {
|
||||
$bakeThemes[] = $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
$parser->setDescription(
|
||||
'Bake migration class.'
|
||||
)
|
||||
->addOption('plugin', [
|
||||
'short' => 'p',
|
||||
'help' => 'Plugin to bake into.',
|
||||
])
|
||||
->addOption('force', [
|
||||
'short' => 'f',
|
||||
'boolean' => true,
|
||||
'help' => 'Force overwriting existing file if a migration already exists with the same name.',
|
||||
])
|
||||
->addOption('connection', [
|
||||
'short' => 'c',
|
||||
'default' => 'default',
|
||||
'help' => 'The datasource connection to get data from.',
|
||||
])
|
||||
->addOption('theme', [
|
||||
'short' => 't',
|
||||
'help' => 'The theme to use when baking code.',
|
||||
'choices' => $bakeThemes,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
use Cake\Core\App;
|
||||
use Cake\Core\Plugin;
|
||||
use Cake\Database\Schema\Collection;
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Cake\Filesystem\Folder;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Trait needed for all "snapshot" type of bake operations.
|
||||
* Snapshot type operations are : baking a snapshot and baking a diff.
|
||||
*/
|
||||
trait SnapshotTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* After a file has been successfully created, we mark the newly
|
||||
* created migration as applied
|
||||
*
|
||||
* @param string $path Where to put the file.
|
||||
* @param string $contents Content to put in the file.
|
||||
* @return bool Success
|
||||
*/
|
||||
public function createFile($path, $contents)
|
||||
{
|
||||
$createFile = parent::createFile($path, $contents);
|
||||
|
||||
if ($createFile) {
|
||||
$this->markSnapshotApplied($path);
|
||||
|
||||
if (!isset($this->params['no-lock']) || !$this->params['no-lock']) {
|
||||
$this->refreshDump();
|
||||
}
|
||||
}
|
||||
|
||||
return $createFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will mark a snapshot created, the snapshot being identified by its
|
||||
* full file path.
|
||||
*
|
||||
* @param string $path Path to the newly created snapshot
|
||||
* @return void
|
||||
*/
|
||||
protected function markSnapshotApplied($path)
|
||||
{
|
||||
$fileName = pathinfo($path, PATHINFO_FILENAME);
|
||||
list($version, ) = explode('_', $fileName, 2);
|
||||
|
||||
$dispatchCommand = 'migrations mark_migrated -t ' . $version . ' -o';
|
||||
if (!empty($this->params['connection'])) {
|
||||
$dispatchCommand .= ' -c ' . $this->params['connection'];
|
||||
}
|
||||
|
||||
if (!empty($this->params['plugin'])) {
|
||||
$dispatchCommand .= ' -p ' . $this->params['plugin'];
|
||||
}
|
||||
|
||||
$this->_io->out('Marking the migration ' . $fileName . ' as migrated...');
|
||||
$this->dispatchShell($dispatchCommand);
|
||||
}
|
||||
|
||||
/**
|
||||
* After a file has been successfully created, we refresh the dump of the database
|
||||
* to be able to generate a new diff afterward.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function refreshDump()
|
||||
{
|
||||
$dispatchCommand = 'migrations dump';
|
||||
if (!empty($this->params['connection'])) {
|
||||
$dispatchCommand .= ' -c ' . $this->params['connection'];
|
||||
}
|
||||
|
||||
if (!empty($this->params['plugin'])) {
|
||||
$dispatchCommand .= ' -p ' . $this->params['plugin'];
|
||||
}
|
||||
|
||||
$this->_io->out('Creating a dump of the new database state...');
|
||||
$this->dispatchShell($dispatchCommand);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Shell\Task;
|
||||
|
||||
/**
|
||||
* This task class is needed in order to provide a correct autocompletion feature
|
||||
* when using the CakePHP migrations shell plugin. It has no effect on the
|
||||
* migrations process.
|
||||
*/
|
||||
class StatusTask extends CommandTask
|
||||
{
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getOptionParser()
|
||||
{
|
||||
$parser = parent::getOptionParser();
|
||||
$parser->addOption('format', [
|
||||
'short' => 'f',
|
||||
'help' => 'The output format: text or json. Defaults to text.',
|
||||
'required' => false,
|
||||
]);
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
+203
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Cake\Collection\Collection;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use Phinx\Db\Table as BaseTable;
|
||||
|
||||
class Table extends BaseTable
|
||||
{
|
||||
|
||||
/**
|
||||
* Primary key for this table.
|
||||
* Can either be a string or an array in case of composite
|
||||
* primary key.
|
||||
*
|
||||
* @var string|array
|
||||
*/
|
||||
protected $primaryKey;
|
||||
|
||||
/**
|
||||
* Add a primary key to a database table.
|
||||
*
|
||||
* @param string|array $columns Table Column(s)
|
||||
* @return Table
|
||||
*/
|
||||
public function addPrimaryKey($columns)
|
||||
{
|
||||
$this->primaryKey = $columns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* You can pass `autoIncrement` as an option and it will be converted
|
||||
* to the correct option for phinx to create the column with an
|
||||
* auto increment attribute
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function addColumn($columnName, $type = null, $options = [])
|
||||
{
|
||||
$options = $this->convertedAutoIncrement($options);
|
||||
|
||||
return parent::addColumn($columnName, $type, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* You can pass `autoIncrement` as an option and it will be converted
|
||||
* to the correct option for phinx to create the column with an
|
||||
* auto increment attribute
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function changeColumn($columnName, $type, array $options = [])
|
||||
{
|
||||
$options = $this->convertedAutoIncrement($options);
|
||||
|
||||
return parent::changeColumn($columnName, $type, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the `autoIncrement` option to the correct options for phinx.
|
||||
*
|
||||
* @param array $options Options
|
||||
* @return array Converted options
|
||||
*/
|
||||
protected function convertedAutoIncrement($options)
|
||||
{
|
||||
if (isset($options['autoIncrement']) && $options['autoIncrement'] === true) {
|
||||
$options['identity'] = true;
|
||||
unset($options['autoIncrement']);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* If using MySQL and no collation information has been given to the table options, a request to the information
|
||||
* schema will be made to get the default database collation and apply it to the database. This is to prevent
|
||||
* phinx default mechanism to put the collation to a default of "utf8_general_ci".
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$options = $this->getTable()->getOptions();
|
||||
if ((!isset($options['id']) || $options['id'] === false) && !empty($this->primaryKey)) {
|
||||
$options['primary_key'] = $this->primaryKey;
|
||||
$this->filterPrimaryKey();
|
||||
}
|
||||
|
||||
if ($this->getAdapter()->getAdapterType() === 'mysql' && empty($options['collation'])) {
|
||||
$encodingRequest = 'SELECT DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME
|
||||
FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = "%s"';
|
||||
|
||||
$cakeConnection = $this->getAdapter()->getCakeConnection();
|
||||
$connectionConfig = $cakeConnection->config();
|
||||
$encodingRequest = sprintf($encodingRequest, $connectionConfig['database']);
|
||||
|
||||
$defaultEncoding = $cakeConnection->execute($encodingRequest)->fetch('assoc');
|
||||
if (!empty($defaultEncoding['DEFAULT_COLLATION_NAME'])) {
|
||||
$options['collation'] = $defaultEncoding['DEFAULT_COLLATION_NAME'];
|
||||
}
|
||||
}
|
||||
|
||||
$this->getTable()->setOptions($options);
|
||||
|
||||
parent::create();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* After a table update, the TableRegistry should be cleared in order to prevent issues with
|
||||
* table schema stored in Table objects having columns that might have been renamed or removed during
|
||||
* the update process.
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
parent::update();
|
||||
TableRegistry::clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* We disable foreign key deletion for the SQLite adapter as SQLite does not support the feature natively and the
|
||||
* process implemented by Phinx has serious side-effects (for instance it rename FK references in existing tables
|
||||
* which breaks the database schema cohesion).
|
||||
*/
|
||||
public function dropForeignKey($columns, $constraint = null)
|
||||
{
|
||||
if ($this->getAdapter()->getAdapterType() == 'sqlite') {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return parent::dropForeignKey($columns, $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called in case a primary key was defined using the addPrimaryKey() method.
|
||||
* It currently does something only if using SQLite.
|
||||
* If a column is an auto-increment key in SQLite, it has to be a primary key and it has to defined
|
||||
* when defining the column. Phinx takes care of that so we have to make sure columns defined as autoincrement were
|
||||
* not added with the addPrimaryKey method, otherwise, SQL queries will be wrong.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function filterPrimaryKey()
|
||||
{
|
||||
$options = $this->getTable()->getOptions();
|
||||
if ($this->getAdapter()->getAdapterType() !== 'sqlite' || empty($options['primary_key'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$primaryKey = $options['primary_key'];
|
||||
if (!is_array($primaryKey)) {
|
||||
$primaryKey = [$primaryKey];
|
||||
}
|
||||
$primaryKey = array_flip($primaryKey);
|
||||
|
||||
$columnsCollection = (new Collection($this->actions->getActions()))
|
||||
->filter(function ($action) {
|
||||
return $action instanceof \Phinx\Db\Action\AddColumn;
|
||||
})
|
||||
->map(function ($action) {
|
||||
return $action->getColumn();
|
||||
});
|
||||
$primaryKeyColumns = $columnsCollection->filter(function ($columnDef, $key) use ($primaryKey) {
|
||||
return isset($primaryKey[$columnDef->getName()]);
|
||||
})->toArray();
|
||||
|
||||
if (empty($primaryKeyColumns)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($primaryKeyColumns as $primaryKeyColumn) {
|
||||
if ($primaryKeyColumn->isIdentity()) {
|
||||
unset($primaryKey[$primaryKeyColumn->getName()]);
|
||||
}
|
||||
}
|
||||
|
||||
$primaryKey = array_flip($primaryKey);
|
||||
|
||||
if (!empty($primaryKey)) {
|
||||
$options['primary_key'] = $primaryKey;
|
||||
} else {
|
||||
unset($options['primary_key']);
|
||||
}
|
||||
|
||||
$this->getTable()->setOptions($options);
|
||||
}
|
||||
}
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations;
|
||||
|
||||
use Cake\Core\App;
|
||||
use Cake\Core\Plugin as CorePlugin;
|
||||
use Cake\Database\Schema\Collection;
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Cake\Filesystem\Folder;
|
||||
use Cake\ORM\TableRegistry;
|
||||
use ReflectionClass;
|
||||
|
||||
trait TableFinderTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* Tables to skip
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $skipTables = ['phinxlog'];
|
||||
|
||||
/**
|
||||
* Regex of Table name to skip
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $skipTablesRegex = '_phinxlog';
|
||||
|
||||
/**
|
||||
* Gets a list of table to baked based on the Collection instance passed and the options passed to
|
||||
* the shell call.
|
||||
*
|
||||
* @param \Cake\Database\Schema\Collection $collection Instance of the collection of a specific database
|
||||
* connection.
|
||||
* @param array $options Array of options passed to a shell call.
|
||||
* @return array
|
||||
*/
|
||||
protected function getTablesToBake(Collection $collection, $options = [])
|
||||
{
|
||||
$options = array_merge(['require-table' => false, 'plugin' => null], $options);
|
||||
$tables = $collection->listTables();
|
||||
|
||||
if (empty($tables)) {
|
||||
return $tables;
|
||||
}
|
||||
|
||||
if ($options['require-table'] === true || $options['plugin']) {
|
||||
$tableNamesInModel = $this->getTableNames($options['plugin']);
|
||||
|
||||
if (empty($tableNamesInModel)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($tableNamesInModel as $num => $table) {
|
||||
if (strpos($table, '.') !== false) {
|
||||
$splitted = array_reverse(explode('.', $table, 2));
|
||||
|
||||
$config = ConnectionManager::getConfig($this->connection);
|
||||
$key = isset($config['schema']) ? 'schema' : 'database';
|
||||
if ($config[$key] === $splitted[1]) {
|
||||
$table = $splitted[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($table, $tables)) {
|
||||
unset($tableNamesInModel[$num]);
|
||||
}
|
||||
}
|
||||
$tables = $tableNamesInModel;
|
||||
} else {
|
||||
foreach ($tables as $num => $table) {
|
||||
if ((in_array($table, $this->skipTables)) || (strpos($table, $this->skipTablesRegex) !== false)) {
|
||||
unset($tables[$num]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list Tables Names
|
||||
*
|
||||
* @param string|null $pluginName Plugin name if exists.
|
||||
* @return array
|
||||
*/
|
||||
protected function getTableNames($pluginName = null)
|
||||
{
|
||||
if ($pluginName !== null && !CorePlugin::getCollection()->has($pluginName)) {
|
||||
return [];
|
||||
}
|
||||
$list = [];
|
||||
$tables = $this->findTables($pluginName);
|
||||
|
||||
if (empty($tables)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($tables as $num => $table) {
|
||||
$list = array_merge($list, $this->fetchTableName($table, $pluginName));
|
||||
}
|
||||
|
||||
return array_unique($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find Table Class
|
||||
*
|
||||
* @param string $pluginName Plugin name if exists.
|
||||
* @return array
|
||||
*/
|
||||
protected function findTables($pluginName = null)
|
||||
{
|
||||
$path = 'Model' . DS . 'Table' . DS;
|
||||
if ($pluginName) {
|
||||
$path = CorePlugin::path($pluginName) . 'src' . DS . $path;
|
||||
} else {
|
||||
$path = APP . $path;
|
||||
}
|
||||
|
||||
if (!is_dir($path)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$tableDir = new Folder($path);
|
||||
$tableDir = $tableDir->find('.*\.php');
|
||||
|
||||
return $tableDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* fetch TableName From Table Object
|
||||
*
|
||||
* @param string $className Name of Table Class.
|
||||
* @param string|null $pluginName Plugin name if exists.
|
||||
* @return array
|
||||
*/
|
||||
protected function fetchTableName($className, $pluginName = null)
|
||||
{
|
||||
$tables = [];
|
||||
$className = str_replace('Table.php', '', $className);
|
||||
if ($pluginName !== null) {
|
||||
$className = $pluginName . '.' . $className;
|
||||
}
|
||||
|
||||
$namespacedClassName = App::className($className, 'Model/Table', 'Table');
|
||||
|
||||
if (!class_exists($namespacedClassName)) {
|
||||
return $tables;
|
||||
}
|
||||
|
||||
$reflection = new ReflectionClass($namespacedClassName);
|
||||
if (!$reflection->isInstantiable()) {
|
||||
return $tables;
|
||||
}
|
||||
|
||||
$table = TableRegistry::get($className);
|
||||
foreach ($table->associations()->keys() as $key) {
|
||||
if ($table->associations()->get($key)->type() === 'belongsToMany') {
|
||||
$tables[] = $table->associations()->get($key)->junction()->getTable();
|
||||
}
|
||||
}
|
||||
$tableName = $table->getTable();
|
||||
$splitted = array_reverse(explode('.', $tableName, 2));
|
||||
if (isset($splitted[1])) {
|
||||
$config = ConnectionManager::getConfig($this->connection);
|
||||
if ($config) {
|
||||
$key = isset($config['schema']) ? 'schema' : 'database';
|
||||
if ($config[$key] === $splitted[1]) {
|
||||
$tableName = $splitted[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
$tables[] = $tableName;
|
||||
|
||||
return $tables;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<% foreach ($columns as $columnName => $columnAttributes):
|
||||
$type = $columnAttributes['type'];
|
||||
unset($columnAttributes['type']);
|
||||
|
||||
$columnAttributes = $this->Migration->getColumnOption($columnAttributes);
|
||||
$columnAttributes = $this->Migration->stringifyList($columnAttributes, ['indent' => 4]);
|
||||
if (!empty($columnAttributes)): %>
|
||||
->addColumn('<%= $columnName %>', '<%= $type %>', [<%= $columnAttributes %>])
|
||||
<% else: %>
|
||||
->addColumn('<%= $columnName %>', '<%= $type %>')
|
||||
<% endif; %>
|
||||
<% endforeach; %>
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
<% foreach ($constraints as $table => $tableConstraints):
|
||||
echo $this->element('Migrations.add-foreign-keys', ['constraints' => $tableConstraints, 'table' => $table]);
|
||||
endforeach; %>
|
||||
@@ -0,0 +1,44 @@
|
||||
<%
|
||||
$statement = $this->Migration->tableStatement($table, true);
|
||||
$hasProcessedConstraint = false;
|
||||
%>
|
||||
<% foreach ($constraints as $constraint):
|
||||
$constraintColumns = $constraint['columns'];
|
||||
sort($constraintColumns);
|
||||
if ($constraint['type'] !== 'unique'):
|
||||
$hasProcessedConstraint = true;
|
||||
$columnsList = '\'' . $constraint['columns'][0] . '\'';
|
||||
if (count($constraint['columns']) > 1):
|
||||
$columnsList = '[' . $this->Migration->stringifyList($constraint['columns'], ['indent' => 5]) . ']';
|
||||
endif;
|
||||
$this->Migration->returnedData['dropForeignKeys'][$table][] = $columnsList;
|
||||
|
||||
if (is_array($constraint['references'][1])):
|
||||
$columnsReference = '[' . $this->Migration->stringifyList($constraint['references'][1], ['indent' => 5]) . ']';
|
||||
else:
|
||||
$columnsReference = '\'' . $constraint['references'][1] . '\'';
|
||||
endif;
|
||||
|
||||
if (!isset($statement)):
|
||||
$statement = $this->Migration->tableStatement($table);
|
||||
endif;
|
||||
|
||||
if (!empty($statement)): %>
|
||||
|
||||
<%= $statement %>
|
||||
<% unset($statement);
|
||||
endif; %>
|
||||
->addForeignKey(
|
||||
<%= $columnsList %>,
|
||||
'<%= $constraint['references'][0] %>',
|
||||
<%= $columnsReference %>,
|
||||
[
|
||||
'update' => '<%= $this->Migration->formatConstraintAction($constraint['update']) %>',
|
||||
'delete' => '<%= $this->Migration->formatConstraintAction($constraint['delete']) %>'
|
||||
]
|
||||
)
|
||||
<% endif; %>
|
||||
<% endforeach; %>
|
||||
<% if (isset($this->Migration->tableStatements[$table]) && $hasProcessedConstraint): %>
|
||||
->update();
|
||||
<% endif; %>
|
||||
@@ -0,0 +1,12 @@
|
||||
<% foreach ($indexes as $indexName => $index): %>
|
||||
->addIndex(
|
||||
[<% echo $this->Migration->stringifyList($index['columns'], ['indent' => 5]); %>],
|
||||
[<%
|
||||
$params = ['name' => $indexName];
|
||||
if ($index['type'] === 'unique'):
|
||||
$params['unique'] = true;
|
||||
endif;
|
||||
echo $this->Migration->stringifyList($params, ['indent' => 5]);
|
||||
%>]
|
||||
)
|
||||
<% endforeach; %>
|
||||
@@ -0,0 +1,83 @@
|
||||
<% foreach ($tables as $table => $schema):
|
||||
$tableArgForMethods = $useSchema === true ? $schema : $table;
|
||||
$tableArgForArray = $useSchema === true ? $table : $schema;
|
||||
|
||||
$foreignKeys = [];
|
||||
$primaryKeysColumns = $this->Migration->primaryKeysColumnsList($tableArgForMethods);
|
||||
$primaryKeys = $this->Migration->primaryKeys($tableArgForMethods);
|
||||
$specialPk = $primaryKeys && (count($primaryKeys) > 1 || $primaryKeys[0]['name'] !== 'id' || $primaryKeys[0]['info']['columnType'] !== 'integer') && $autoId;
|
||||
%>
|
||||
<% if ($specialPk): %>
|
||||
|
||||
$this->table('<%= $tableArgForArray %>', ['id' => false, 'primary_key' => ['<%= implode("', '", \Cake\Utility\Hash::extract($primaryKeys, '{n}.name')) %>']])
|
||||
<% elseif (!$primaryKeys && $autoId): %>
|
||||
|
||||
$this->table('<%= $tableArgForArray %>', ['id' => false])
|
||||
<% else: %>
|
||||
|
||||
$this->table('<%= $tableArgForArray %>')
|
||||
<% endif; %>
|
||||
<% if ($specialPk || !$autoId):
|
||||
foreach ($primaryKeys as $primaryKey) :
|
||||
%>
|
||||
->addColumn('<%= $primaryKey['name'] %>', '<%= $primaryKey['info']['columnType'] %>', [<%
|
||||
$columnOptions = $this->Migration->getColumnOption($primaryKey['info']['options']);
|
||||
echo $this->Migration->stringifyList($columnOptions, ['indent' => 4]);
|
||||
%>])
|
||||
<% endforeach; %>
|
||||
<% if (!$autoId && $primaryKeys): %>
|
||||
->addPrimaryKey(['<%= implode("', '", \Cake\Utility\Hash::extract($primaryKeys, '{n}.name')) %>'])
|
||||
<% endif; %>
|
||||
<% endif;
|
||||
foreach ($this->Migration->columns($tableArgForMethods) as $column => $config):
|
||||
%>
|
||||
->addColumn('<%= $column %>', '<%= $config['columnType'] %>', [<%
|
||||
$columnOptions = $this->Migration->getColumnOption($config['options']);
|
||||
if ($config['columnType'] === 'boolean' && isset($columnOptions['default']) && $this->Migration->value($columnOptions['default']) !== 'null'):
|
||||
$columnOptions['default'] = (bool)$columnOptions['default'];
|
||||
endif;
|
||||
echo $this->Migration->stringifyList($columnOptions, ['indent' => 4]);
|
||||
%>])
|
||||
<% endforeach;
|
||||
$tableConstraints = $this->Migration->constraints($tableArgForMethods);
|
||||
if (!empty($tableConstraints)):
|
||||
sort($tableConstraints);
|
||||
$constraints[$tableArgForArray] = $tableConstraints;
|
||||
|
||||
foreach ($constraints[$tableArgForArray] as $name => $constraint):
|
||||
if ($constraint['type'] === 'foreign'):
|
||||
$foreignKeys[] = $constraint['columns'];
|
||||
endif;
|
||||
if ($constraint['columns'] !== $primaryKeysColumns): %>
|
||||
->addIndex(
|
||||
[<% echo $this->Migration->stringifyList($constraint['columns'], ['indent' => 5]); %>]<% echo ($constraint['type'] === 'unique') ? ',' : ''; %>
|
||||
|
||||
<% if ($constraint['type'] === 'unique'): %>
|
||||
['unique' => true]
|
||||
<% endif; %>
|
||||
)
|
||||
<% endif;
|
||||
endforeach;
|
||||
endif;
|
||||
foreach($this->Migration->indexes($tableArgForMethods) as $index):
|
||||
sort($foreignKeys);
|
||||
$indexColumns = $index['columns'];
|
||||
sort($indexColumns);
|
||||
if (!in_array($indexColumns, $foreignKeys)):
|
||||
%>
|
||||
->addIndex(
|
||||
[<%
|
||||
echo $this->Migration->stringifyList($index['columns'], ['indent' => 5]);
|
||||
%>]<% echo ($index['type'] === 'fulltext') ? ',' : ''; %>
|
||||
|
||||
<%- if ($index['type'] === 'fulltext'): %>
|
||||
['type' => 'fulltext']
|
||||
<%- endif; %>
|
||||
)
|
||||
<% endif;
|
||||
endforeach; %>
|
||||
->create();
|
||||
<% endforeach; %>
|
||||
<% if (!empty($constraints)): %>
|
||||
<% echo $this->element('Migrations.add-foreign-keys-from-create', ['constraints' => $constraints]); %>
|
||||
<% endif; %>
|
||||
@@ -0,0 +1,45 @@
|
||||
<%
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @since 0.1.0
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
%>
|
||||
<?php
|
||||
use Migrations\AbstractSeed;
|
||||
|
||||
/**
|
||||
* <%= $name %> seed.
|
||||
*/
|
||||
class <%= $name %>Seed extends AbstractSeed
|
||||
{
|
||||
/**
|
||||
* Run Method.
|
||||
*
|
||||
* Write your database seeder using this method.
|
||||
*
|
||||
* More information on writing seeds is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/seeding.html
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run()
|
||||
{
|
||||
<% if ($records): %>
|
||||
$data = <%= $records %>;
|
||||
<% else: %>
|
||||
$data = [];
|
||||
<% endif; %>
|
||||
|
||||
$table = $this->table('<%= $table %>');
|
||||
$table->insert($data)->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
<%
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
$tables = $data['fullTables'];
|
||||
unset($data['fullTables']);
|
||||
$constraints = [];
|
||||
|
||||
$hasUnsignedPk = $this->Migration->hasUnsignedPrimaryKey($tables['add']);
|
||||
|
||||
$autoId = true;
|
||||
if ($hasUnsignedPk) {
|
||||
$autoId = false;
|
||||
}
|
||||
%>
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class <%= $name %> extends AbstractMigration
|
||||
{
|
||||
<%- if (!$autoId): %>
|
||||
|
||||
public $autoId = false;
|
||||
<%- endif; %>
|
||||
|
||||
public function up()
|
||||
{
|
||||
<%- foreach ($data as $tableName => $tableDiff):
|
||||
$hasRemoveFK = !empty($tableDiff['constraints']['remove']) || !empty($tableDiff['indexes']['remove']);
|
||||
%>
|
||||
<%- if ($hasRemoveFK): %>
|
||||
$this->table('<%= $tableName %>')
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['constraints']['remove'])): %>
|
||||
<%- foreach ($tableDiff['constraints']['remove'] as $constraintName => $constraintDefinition): %>
|
||||
->dropForeignKey([], '<%= $constraintName %>')
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['indexes']['remove'])): %>
|
||||
<%- foreach ($tableDiff['indexes']['remove'] as $indexName => $indexDefinition): %>
|
||||
->removeIndexByName('<%= $indexName %>')
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
<%- if ($hasRemoveFK): %>
|
||||
->update();
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['columns']['remove']) || !empty($tableDiff['columns']['changed'])): %>
|
||||
|
||||
<%= $this->Migration->tableStatement($tableName, true) %>
|
||||
<%- if (!empty($tableDiff['columns']['remove'])): %>
|
||||
<%- foreach ($tableDiff['columns']['remove'] as $columnName => $columnDefinition): %>
|
||||
->removeColumn('<%= $columnName %>')
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['columns']['changed'])): %>
|
||||
<%- foreach ($tableDiff['columns']['changed'] as $columnName => $columnAttributes):
|
||||
$type = $columnAttributes['type'];
|
||||
unset($columnAttributes['type']);
|
||||
$columnAttributes = $this->Migration->getColumnOption($columnAttributes);
|
||||
$columnAttributes = $this->Migration->stringifyList($columnAttributes, ['indent' => 4]);
|
||||
if (!empty($columnAttributes)): %>
|
||||
->changeColumn('<%= $columnName %>', '<%= $type %>', [<%= $columnAttributes %>])
|
||||
<%- else: %>
|
||||
->changeColumn('<%= $columnName %>', '<%= $type %>')
|
||||
<%- endif; %>
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
<%- if (isset($this->Migration->tableStatements[$tableName])): %>
|
||||
->update();
|
||||
<%- endif; %>
|
||||
<%- endif; %>
|
||||
<%- endforeach; %>
|
||||
<%- if (!empty($tables['add'])): %>
|
||||
<%- echo $this->element('Migrations.create-tables', ['tables' => $tables['add'], 'autoId' => $autoId, 'useSchema' => true]) %>
|
||||
<%- endif; %>
|
||||
<%- foreach ($data as $tableName => $tableDiff): %>
|
||||
<%- if (!empty($tableDiff['columns']['add']) || !empty($tableDiff['indexes']['add'])): %>
|
||||
|
||||
<%= $this->Migration->tableStatement($tableName, true) %>
|
||||
<%- if (!empty($tableDiff['columns']['add'])): %>
|
||||
<%- echo $this->element('Migrations.add-columns', ['columns' => $tableDiff['columns']['add']]) %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['indexes']['add'])): %>
|
||||
<%- echo $this->element('Migrations.add-indexes', ['indexes' => $tableDiff['indexes']['add']]) %>
|
||||
<%- endif;
|
||||
if (isset($this->Migration->tableStatements[$tableName])): %>
|
||||
->update();
|
||||
<%- endif; %>
|
||||
<%- endif; %>
|
||||
<%- endforeach; %>
|
||||
<%- foreach ($data as $tableName => $tableDiff): %>
|
||||
<%- if (!empty($tableDiff['constraints']['add'])): %>
|
||||
<%- echo $this->element(
|
||||
'Migrations.add-foreign-keys',
|
||||
['constraints' => $tableDiff['constraints']['add'], 'table' => $tableName]
|
||||
); %>
|
||||
<%- endif; %>
|
||||
<%- endforeach; %>
|
||||
<%- if (!empty($tables['remove'])): %>
|
||||
<%- foreach ($tables['remove'] as $tableName => $table): %>
|
||||
|
||||
$this->table('<%= $tableName %>')->drop()->save();
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
<%- $constraints = [];
|
||||
$emptyLine = false;
|
||||
if (!empty($this->Migration->returnedData['dropForeignKeys'])):
|
||||
foreach ($this->Migration->returnedData['dropForeignKeys'] as $table => $columnsList):
|
||||
$maxKey = count($columnsList) - 1;
|
||||
if ($emptyLine === true): %>
|
||||
|
||||
<%- else:
|
||||
$emptyLine = true;
|
||||
endif; %>
|
||||
$this->table('<%= $table %>')
|
||||
<%- foreach ($columnsList as $key => $columns): %>
|
||||
->dropForeignKey(
|
||||
<%= $columns %>
|
||||
)<%= ($key === $maxKey) ? '->save();' : '' %>
|
||||
<%- endforeach; %>
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tables['remove'])): %>
|
||||
<%- echo $this->element('Migrations.create-tables', ['tables' => $tables['remove'], 'autoId' => $autoId, 'useSchema' => true]) %>
|
||||
<%- endif; %>
|
||||
<%- foreach ($data as $tableName => $tableDiff):
|
||||
unset($this->Migration->tableStatements[$tableName]);
|
||||
if (!empty($tableDiff['indexes']['add'])): %>
|
||||
|
||||
$this->table('<%= $tableName %>')
|
||||
<%- foreach ($tableDiff['indexes']['add'] as $indexName => $index): %>
|
||||
->removeIndexByName('<%= $indexName %>')
|
||||
<%- endforeach %>
|
||||
->update();
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['columns']['remove']) ||
|
||||
!empty($tableDiff['columns']['changed']) ||
|
||||
!empty($tableDiff['columns']['add']) ||
|
||||
!empty($tableDiff['indexes']['remove'])
|
||||
): %>
|
||||
|
||||
<%= $this->Migration->tableStatement($tableName, true) %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['columns']['remove'])): %>
|
||||
<%- echo $this->element('Migrations.add-columns', ['columns' => $tableDiff['columns']['remove']]) %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['columns']['changed'])):
|
||||
$oldTableDef = $dumpSchema[$tableName];
|
||||
foreach ($tableDiff['columns']['changed'] as $columnName => $columnAttributes):
|
||||
$columnAttributes = $oldTableDef->getColumn($columnName);
|
||||
$type = $columnAttributes['type'];
|
||||
unset($columnAttributes['type']);
|
||||
$columnAttributes = $this->Migration->getColumnOption($columnAttributes);
|
||||
$columnAttributes = $this->Migration->stringifyList($columnAttributes, ['indent' => 4]);
|
||||
if (!empty($columnAttributes)): %>
|
||||
->changeColumn('<%= $columnName %>', '<%= $type %>', [<%= $columnAttributes %>])
|
||||
<%- else: %>
|
||||
->changeColumn('<%= $columnName %>', '<%= $type %>')
|
||||
<%- endif; %>
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['columns']['add'])): %>
|
||||
<%- foreach ($tableDiff['columns']['add'] as $columnName => $columnAttributes): %>
|
||||
->removeColumn('<%= $columnName %>')
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
<%- if (!empty($tableDiff['indexes']['remove'])): %>
|
||||
<%- echo $this->element('Migrations.add-indexes', ['indexes' => $tableDiff['indexes']['remove']]) %>
|
||||
<%- endif;
|
||||
if (isset($this->Migration->tableStatements[$tableName])): %>
|
||||
->update();
|
||||
<%- endif; %>
|
||||
<%- endforeach; %>
|
||||
<%- foreach ($data as $tableName => $tableDiff): %>
|
||||
<%- if (!empty($tableDiff['constraints']['remove'])): %>
|
||||
<%- echo $this->element(
|
||||
'Migrations.add-foreign-keys',
|
||||
['constraints' => $tableDiff['constraints']['remove'], 'table' => $tableName]
|
||||
); %>
|
||||
<%- endif; %>
|
||||
<%- endforeach; %>
|
||||
<%- if (!empty($tables['add'])): %>
|
||||
<%- foreach ($tables['add'] as $tableName => $table): %>
|
||||
|
||||
$this->table('<%= $tableName %>')->drop()->save();
|
||||
<%- endforeach; %>
|
||||
<%- endif; %>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
<%
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
$wantedOptions = array_flip(['length', 'limit', 'default', 'unsigned', 'null', 'comment', 'autoIncrement', 'precision', 'scale']);
|
||||
$tableMethod = $this->Migration->tableMethod($action);
|
||||
$columnMethod = $this->Migration->columnMethod($action);
|
||||
$indexMethod = $this->Migration->indexMethod($action);
|
||||
%>
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class <%= $name %> extends AbstractMigration
|
||||
{
|
||||
<%- if ($tableMethod === 'create' && !empty($columns['primaryKey'])): %>
|
||||
|
||||
public $autoId = false;
|
||||
|
||||
<%- endif; %>
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* More information on this method is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
* @return void
|
||||
*/
|
||||
public function change()
|
||||
{
|
||||
<% foreach ($tables as $table): %>
|
||||
$table = $this->table('<%= $table%>');
|
||||
<% if ($tableMethod !== 'drop') : %>
|
||||
<% if ($columnMethod === 'removeColumn'): %>
|
||||
<% foreach ($columns['fields'] as $column => $config): %>
|
||||
<%= "\$table->$columnMethod('" . $column . "');"; %>
|
||||
<% endforeach; %>
|
||||
<% foreach ($columns['indexes'] as $column => $config): %>
|
||||
<%= "\$table->$indexMethod([" . $this->Migration->stringifyList($config['columns']) . ");"; %>
|
||||
<% endforeach; %>
|
||||
<% else : %>
|
||||
<% foreach ($columns['fields'] as $column => $config): %>
|
||||
$table-><%= $columnMethod %>('<%= $column %>', '<%= $config['columnType'] %>', [<%
|
||||
$columnOptions = $config['options'];
|
||||
$columnOptions = array_intersect_key($columnOptions, $wantedOptions);
|
||||
if (empty($columnOptions['comment'])) {
|
||||
unset($columnOptions['comment']);
|
||||
}
|
||||
echo $this->Migration->stringifyList($columnOptions, ['indent' => 3]);
|
||||
%>]);
|
||||
<% endforeach; %>
|
||||
<% foreach ($columns['indexes'] as $column => $config): %>
|
||||
$table-><%= $indexMethod %>([<%=
|
||||
$this->Migration->stringifyList($config['columns'], ['indent' => 3])
|
||||
%>], [<%
|
||||
$options = [];
|
||||
echo $this->Migration->stringifyList($config['options'], ['indent' => 3]);
|
||||
%>]);
|
||||
<% endforeach; %>
|
||||
<% if ($tableMethod === 'create' && !empty($columns['primaryKey'])): %>
|
||||
$table->addPrimaryKey([<%=
|
||||
$this->Migration->stringifyList($columns['primaryKey'], ['indent' => 3])
|
||||
%>]);
|
||||
<% endif; %>
|
||||
<% endif; %>
|
||||
<% endif; %>
|
||||
$table-><%= $tableMethod %>();
|
||||
<% endforeach; %>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<%
|
||||
/**
|
||||
* CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* For full copyright and license information, please see the LICENSE.txt
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @since 3.0.0
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
|
||||
use Cake\Database\Schema\Table;
|
||||
|
||||
$constraints = $foreignKeys = $dropForeignKeys = [];
|
||||
$hasUnsignedPk = $this->Migration->hasUnsignedPrimaryKey($tables);
|
||||
|
||||
if ($autoId && $hasUnsignedPk) {
|
||||
$autoId = false;
|
||||
}
|
||||
%>
|
||||
<?php
|
||||
use Migrations\AbstractMigration;
|
||||
|
||||
class <%= $name %> extends AbstractMigration
|
||||
{
|
||||
<%- if (!$autoId): %>
|
||||
|
||||
public $autoId = false;
|
||||
|
||||
<%- endif; %>
|
||||
public function up()
|
||||
{
|
||||
<%- echo $this->element('Migrations.create-tables', ['tables' => $tables, 'autoId' => $autoId, 'useSchema' => false]) %>
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
<%- if (!empty($this->Migration->returnedData['dropForeignKeys'])):
|
||||
foreach ($this->Migration->returnedData['dropForeignKeys'] as $table => $columnsList):
|
||||
$maxKey = count($columnsList) - 1;
|
||||
%>
|
||||
$this->table('<%= $table %>')
|
||||
<%- foreach ($columnsList as $key => $columns): %>
|
||||
->dropForeignKey(
|
||||
<%= $columns %>
|
||||
)<%= ($key === $maxKey) ? '->save();' : '' %>
|
||||
<%- endforeach; %>
|
||||
|
||||
<%- endforeach;
|
||||
endif;
|
||||
%>
|
||||
<%- foreach ($tables as $table): %>
|
||||
$this->table('<%= $table%>')->drop()->save();
|
||||
<%- endforeach; %>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
use $useClassName;
|
||||
|
||||
class $className extends $baseClassName
|
||||
{
|
||||
/**
|
||||
* Change Method.
|
||||
*
|
||||
* More information on this method is available here:
|
||||
* https://book.cakephp.org/phinx/0/en/migrations.html#the-change-method
|
||||
* @return void
|
||||
*/
|
||||
public function change()
|
||||
{
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
<?php
|
||||
namespace Migrations\Util;
|
||||
|
||||
use Cake\Collection\Collection;
|
||||
use Cake\Utility\Hash;
|
||||
use ReflectionClass;
|
||||
|
||||
/**
|
||||
* Utility class used to parse arguments passed to a ``bake migration`` class
|
||||
*/
|
||||
class ColumnParser
|
||||
{
|
||||
|
||||
/**
|
||||
* Regex used to parse the column definition passed through the shell
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regexpParseColumn = '/
|
||||
^
|
||||
(\w+)
|
||||
(?::(\w+\??
|
||||
(?:\[
|
||||
(?:[0-9]|[1-9][0-9]+)
|
||||
(?:,(?:[0-9]|[1-9][0-9]+))?
|
||||
\])?
|
||||
))?
|
||||
(?::(\w+))?
|
||||
(?::(\w+))?
|
||||
$
|
||||
/x';
|
||||
|
||||
/**
|
||||
* Regex used to parse the field type and length
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $regexpParseField = '/(\w+\??)\[([0-9,]+)\]/';
|
||||
|
||||
/**
|
||||
* Parses a list of arguments into an array of fields
|
||||
*
|
||||
* @param array $arguments A list of arguments being parsed
|
||||
* @return array
|
||||
*/
|
||||
public function parseFields($arguments)
|
||||
{
|
||||
$fields = [];
|
||||
$arguments = $this->validArguments($arguments);
|
||||
foreach ($arguments as $field) {
|
||||
preg_match($this->regexpParseColumn, $field, $matches);
|
||||
$field = $matches[1];
|
||||
$type = Hash::get($matches, 2);
|
||||
$indexType = Hash::get($matches, 3);
|
||||
|
||||
$typeIsPk = in_array($type, ['primary', 'primary_key']);
|
||||
$isPrimaryKey = false;
|
||||
if ($typeIsPk || in_array($indexType, ['primary', 'primary_key'])) {
|
||||
$isPrimaryKey = true;
|
||||
|
||||
if ($typeIsPk) {
|
||||
$type = 'primary';
|
||||
}
|
||||
}
|
||||
$nullable = (bool)strpos($type, '?');
|
||||
$type = $nullable ? str_replace('?', '', $type) : $type;
|
||||
|
||||
list($type, $length) = $this->getTypeAndLength($field, $type);
|
||||
$fields[$field] = [
|
||||
'columnType' => $type,
|
||||
'options' => [
|
||||
'null' => $nullable,
|
||||
'default' => null,
|
||||
],
|
||||
];
|
||||
|
||||
if ($length !== null) {
|
||||
if (is_array($length)) {
|
||||
list($fields[$field]['options']['precision'], $fields[$field]['options']['scale']) = $length;
|
||||
} else {
|
||||
$fields[$field]['options']['limit'] = $length;
|
||||
}
|
||||
}
|
||||
|
||||
if ($isPrimaryKey === true && $type === 'integer') {
|
||||
$fields[$field]['options']['autoIncrement'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of arguments into an array of indexes
|
||||
*
|
||||
* @param array $arguments A list of arguments being parsed
|
||||
* @return array
|
||||
*/
|
||||
public function parseIndexes($arguments)
|
||||
{
|
||||
$indexes = [];
|
||||
$arguments = $this->validArguments($arguments);
|
||||
foreach ($arguments as $field) {
|
||||
preg_match($this->regexpParseColumn, $field, $matches);
|
||||
$field = $matches[1];
|
||||
$type = Hash::get($matches, 2);
|
||||
$indexType = Hash::get($matches, 3);
|
||||
$indexName = Hash::get($matches, 4);
|
||||
|
||||
if (
|
||||
in_array($type, ['primary', 'primary_key']) ||
|
||||
in_array($indexType, ['primary', 'primary_key']) ||
|
||||
$indexType === null
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$indexUnique = false;
|
||||
if ($indexType === 'unique') {
|
||||
$indexUnique = true;
|
||||
}
|
||||
|
||||
$indexName = $this->getIndexName($field, $indexType, $indexName, $indexUnique);
|
||||
|
||||
if (empty($indexes[$indexName])) {
|
||||
$indexes[$indexName] = [
|
||||
'columns' => [],
|
||||
'options' => [
|
||||
'unique' => $indexUnique,
|
||||
'name' => $indexName,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
$indexes[$indexName]['columns'][] = $field;
|
||||
}
|
||||
|
||||
return $indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of arguments into an array of fields composing the primary key
|
||||
* of the table
|
||||
*
|
||||
* @param array $arguments A list of arguments being parsed
|
||||
* @return array
|
||||
*/
|
||||
public function parsePrimaryKey($arguments)
|
||||
{
|
||||
$primaryKey = [];
|
||||
$arguments = $this->validArguments($arguments);
|
||||
foreach ($arguments as $field) {
|
||||
preg_match($this->regexpParseColumn, $field, $matches);
|
||||
$field = $matches[1];
|
||||
$type = Hash::get($matches, 2);
|
||||
$indexType = Hash::get($matches, 3);
|
||||
|
||||
if (in_array($type, ['primary', 'primary_key']) || in_array($indexType, ['primary', 'primary_key'])) {
|
||||
$primaryKey[] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
return $primaryKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of only valid arguments
|
||||
*
|
||||
* @param array $arguments A list of arguments
|
||||
* @return array
|
||||
*/
|
||||
public function validArguments($arguments)
|
||||
{
|
||||
$collection = new Collection($arguments);
|
||||
|
||||
return $collection->filter(function ($value, $field) {
|
||||
return preg_match($this->regexpParseColumn, $field);
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type and length of a field based on the field and the type passed
|
||||
*
|
||||
* @param string $field Name of field
|
||||
* @param string $type User-specified type
|
||||
* @return array First value is the field type, second value is the field length. If no length
|
||||
* can be extracted, null is returned for the second value
|
||||
*/
|
||||
public function getTypeAndLength($field, $type)
|
||||
{
|
||||
if (preg_match($this->regexpParseField, $type, $matches)) {
|
||||
if (strpos($matches[2], ',') !== false) {
|
||||
$matches[2] = explode(',', $matches[2]);
|
||||
}
|
||||
|
||||
return [$matches[1], $matches[2]];
|
||||
}
|
||||
|
||||
$fieldType = $this->getType($field, $type);
|
||||
$length = $this->getLength($fieldType);
|
||||
|
||||
return [$fieldType, $length];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a type that should be used for a specific field
|
||||
*
|
||||
* @param string $field Name of field
|
||||
* @param string $type User-specified type
|
||||
* @return string
|
||||
*/
|
||||
public function getType($field, $type)
|
||||
{
|
||||
$reflector = new ReflectionClass('Phinx\Db\Adapter\AdapterInterface');
|
||||
$collection = new Collection($reflector->getConstants());
|
||||
|
||||
$validTypes = $collection->filter(function ($value, $constant) {
|
||||
return substr($constant, 0, strlen('PHINX_TYPE_')) === 'PHINX_TYPE_';
|
||||
})->toArray();
|
||||
$fieldType = $type;
|
||||
if ($type === null || !in_array($type, $validTypes)) {
|
||||
if ($type === 'primary') {
|
||||
$fieldType = 'integer';
|
||||
} elseif ($field === 'id') {
|
||||
$fieldType = 'integer';
|
||||
} elseif (in_array($field, ['created', 'modified', 'updated']) || substr($field, -3) === '_at') {
|
||||
$fieldType = 'datetime';
|
||||
} elseif (in_array($field, ['latitude', 'longitude'])) {
|
||||
$fieldType = 'decimal';
|
||||
} else {
|
||||
$fieldType = 'string';
|
||||
}
|
||||
}
|
||||
|
||||
return $fieldType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default length to be used for a given fie
|
||||
*
|
||||
* @param string $type User-specified type
|
||||
* @return int
|
||||
*/
|
||||
public function getLength($type)
|
||||
{
|
||||
$length = null;
|
||||
if ($type === 'string') {
|
||||
$length = 255;
|
||||
} elseif ($type === 'integer') {
|
||||
$length = 11;
|
||||
} elseif ($type === 'biginteger') {
|
||||
$length = 20;
|
||||
} elseif ($type === 'decimal') {
|
||||
$length = [10, 6];
|
||||
}
|
||||
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default length to be used for a given fie
|
||||
*
|
||||
* @param string $field Name of field
|
||||
* @param string $indexType Type of index
|
||||
* @param string $indexName Name of index
|
||||
* @param bool $indexUnique Whether this is a unique index or not
|
||||
* @return string
|
||||
*/
|
||||
public function getIndexName($field, $indexType, $indexName, $indexUnique)
|
||||
{
|
||||
if (empty($indexName)) {
|
||||
$indexName = strtoupper('BY_' . $field);
|
||||
if ($indexType === 'primary') {
|
||||
$indexName = 'PRIMARY';
|
||||
} elseif ($indexUnique) {
|
||||
$indexName = strtoupper('UNIQUE_' . $field);
|
||||
}
|
||||
}
|
||||
|
||||
return $indexName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Util;
|
||||
|
||||
use Cake\Datasource\ConnectionManager;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Trait gathering schema collection used for caching
|
||||
*/
|
||||
trait SchemaTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* Helper method to get the schema collection.
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input Input object.
|
||||
* @param \Symfony\Component\Console\Input\OutputInterface $output Output object.
|
||||
* @return null|\Cake\Database\Schema\Collection
|
||||
*/
|
||||
protected function _getSchema(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$connectionName = $input->getOption('connection');
|
||||
$connection = ConnectionManager::get($connectionName);
|
||||
|
||||
if (!method_exists($connection, 'getSchemaCollection')) {
|
||||
$msg = sprintf(
|
||||
'The "%s" connection is not compatible with orm caching, ' .
|
||||
'as it does not implement a "getSchemaCollection()" method.',
|
||||
$connectionName
|
||||
);
|
||||
$output->writeln('<error>' . $msg . '</error>');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = $connection->config();
|
||||
|
||||
if (empty($config['cacheMetadata'])) {
|
||||
$output->writeln('Metadata cache was disabled in config. Enable to cache or clear.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$connection->cacheMetadata(true);
|
||||
|
||||
return $connection->getSchemaCollection();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\Util;
|
||||
|
||||
use Cake\Core\Plugin as CorePlugin;
|
||||
use Cake\Utility\Inflector;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
|
||||
/**
|
||||
* Trait gathering useful methods needed in various places of the plugin
|
||||
*/
|
||||
trait UtilTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the plugin name based on the current InputInterface
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input Input of the current command.
|
||||
* @return string|null
|
||||
*/
|
||||
protected function getPlugin(InputInterface $input)
|
||||
{
|
||||
$plugin = $input->getOption('plugin') ?: null;
|
||||
|
||||
return $plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the phinx table name used to store migrations data
|
||||
*
|
||||
* @param string $plugin Plugin name
|
||||
* @return string
|
||||
*/
|
||||
protected function getPhinxTable($plugin = null)
|
||||
{
|
||||
$table = 'phinxlog';
|
||||
|
||||
if (empty($plugin)) {
|
||||
return $table;
|
||||
}
|
||||
|
||||
$plugin = Inflector::underscore($plugin) . '_';
|
||||
$plugin = str_replace(['\\', '/', '.'], '_', $plugin);
|
||||
|
||||
return $plugin . $table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the migrations or seeds files path based on the current InputInterface
|
||||
*
|
||||
* @param \Symfony\Component\Console\Input\InputInterface $input Input of the current command.
|
||||
* @param string $default Default folder to set if no source option is found in the $input param
|
||||
* @return string
|
||||
*/
|
||||
protected function getOperationsPath(InputInterface $input, $default = 'Migrations')
|
||||
{
|
||||
$folder = $input->getOption('source') ?: $default;
|
||||
|
||||
$dir = ROOT . DS . 'config' . DS . $folder;
|
||||
|
||||
if (defined('CONFIG')) {
|
||||
$dir = CONFIG . $folder;
|
||||
}
|
||||
|
||||
$plugin = $this->getPlugin($input);
|
||||
|
||||
if ($plugin !== null) {
|
||||
$dir = CorePlugin::path($plugin) . 'config' . DS . $folder;
|
||||
}
|
||||
|
||||
return $dir;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
*
|
||||
* Licensed under The MIT License
|
||||
* Redistributions of files must retain the above copyright notice.
|
||||
*
|
||||
* @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||||
* @link http://cakephp.org CakePHP(tm) Project
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||
*/
|
||||
namespace Migrations\View\Helper;
|
||||
|
||||
use Cake\Database\Schema\Table;
|
||||
use Cake\Utility\Hash;
|
||||
use Cake\Utility\Inflector;
|
||||
use Cake\View\Helper;
|
||||
use Cake\View\View;
|
||||
|
||||
/**
|
||||
* Migration Helper class for output of field data in migration files.
|
||||
*
|
||||
* MigrationHelper encloses all methods needed while working with HTML pages.
|
||||
*/
|
||||
class MigrationHelper extends Helper
|
||||
{
|
||||
|
||||
/**
|
||||
* Schemas list for tables analyzed during migration baking
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $schemas = [];
|
||||
|
||||
/**
|
||||
* Stores the ``$this->table()`` statements issued while baking.
|
||||
* It helps prevent duplicate calls in case of complex conditions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $tableStatements = [];
|
||||
|
||||
public $returnedData = [];
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* ### Settings
|
||||
*
|
||||
* - `collection` \Cake\Database\Schema\Collection
|
||||
*
|
||||
* @param \Cake\View\View $View The View this helper is being attached to.
|
||||
* @param array $config Configuration settings for the helper.
|
||||
*/
|
||||
public function __construct(View $View, array $config = [])
|
||||
{
|
||||
parent::__construct($View, $config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method to be used for the Table::save()
|
||||
*
|
||||
* @param string $action Name of action to take against the table
|
||||
* @return string
|
||||
*/
|
||||
public function tableMethod($action)
|
||||
{
|
||||
if ($action === 'drop_table') {
|
||||
return 'drop';
|
||||
}
|
||||
|
||||
if ($action === 'create_table') {
|
||||
return 'create';
|
||||
}
|
||||
|
||||
return 'update';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method to be used for the index manipulation
|
||||
*
|
||||
* @param string $action Name of action to take against the table
|
||||
* @return string
|
||||
*/
|
||||
public function indexMethod($action)
|
||||
{
|
||||
if ($action === 'drop_field') {
|
||||
return 'removeIndex';
|
||||
}
|
||||
|
||||
return 'addIndex';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the method to be used for the column manipulation
|
||||
*
|
||||
* @param string $action Name of action to take against the table
|
||||
* @return string
|
||||
*/
|
||||
public function columnMethod($action)
|
||||
{
|
||||
if ($action === 'drop_field') {
|
||||
return 'removeColumn';
|
||||
}
|
||||
|
||||
if ($action === 'alter_field') {
|
||||
return 'changeColumn';
|
||||
}
|
||||
|
||||
return 'addColumn';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Cake\Database\Schema\Table for $table
|
||||
*
|
||||
* @param string $table Name of the table to get the Schema for
|
||||
* @return \Cake\Database\Schema\Table
|
||||
*/
|
||||
protected function schema($table)
|
||||
{
|
||||
if (isset($this->schemas[$table])) {
|
||||
return $this->schemas[$table];
|
||||
}
|
||||
|
||||
if ($table instanceof Table) {
|
||||
return $this->schemas[$table->name()] = $table;
|
||||
}
|
||||
|
||||
$collection = $this->getConfig('collection');
|
||||
$schema = $collection->describe($table);
|
||||
$this->schemas[$table] = $schema;
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of column data for a given table
|
||||
*
|
||||
* @param string $table Name of the table to retrieve columns for
|
||||
* @return array
|
||||
*/
|
||||
public function columns($table)
|
||||
{
|
||||
$tableSchema = $table;
|
||||
if (!($table instanceof Table)) {
|
||||
$tableSchema = $this->schema($table);
|
||||
}
|
||||
$columns = [];
|
||||
$tablePrimaryKeys = $tableSchema->primaryKey();
|
||||
foreach ($tableSchema->columns() as $column) {
|
||||
if (in_array($column, $tablePrimaryKeys)) {
|
||||
continue;
|
||||
}
|
||||
$columns[$column] = $this->column($tableSchema, $column);
|
||||
}
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of indexes for a given table
|
||||
*
|
||||
* @param string $table Name of the table to retrieve indexes for
|
||||
* @return array
|
||||
*/
|
||||
public function indexes($table)
|
||||
{
|
||||
$tableSchema = $table;
|
||||
if (!($table instanceof Table)) {
|
||||
$tableSchema = $this->schema($table);
|
||||
}
|
||||
|
||||
$tableIndexes = $tableSchema->indexes();
|
||||
$indexes = [];
|
||||
if (!empty($tableIndexes)) {
|
||||
foreach ($tableIndexes as $name) {
|
||||
$indexes[$name] = $tableSchema->getIndex($name);
|
||||
}
|
||||
}
|
||||
|
||||
return $indexes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of constraints for a given table
|
||||
*
|
||||
* @param string $table Name of the table to retrieve constraints for
|
||||
* @return array
|
||||
*/
|
||||
public function constraints($table)
|
||||
{
|
||||
$tableSchema = $table;
|
||||
if (!($table instanceof Table)) {
|
||||
$tableSchema = $this->schema($table);
|
||||
}
|
||||
|
||||
$constraints = [];
|
||||
$tableConstraints = $tableSchema->constraints();
|
||||
if (empty($tableConstraints)) {
|
||||
return $constraints;
|
||||
}
|
||||
|
||||
if ($tableConstraints[0] === 'primary') {
|
||||
unset($tableConstraints[0]);
|
||||
}
|
||||
if (!empty($tableConstraints)) {
|
||||
foreach ($tableConstraints as $name) {
|
||||
$constraint = $tableSchema->getConstraint($name);
|
||||
if (isset($constraint['update'])) {
|
||||
$constraint['update'] = $this->formatConstraintAction($constraint['update']);
|
||||
$constraint['delete'] = $this->formatConstraintAction($constraint['delete']);
|
||||
}
|
||||
$constraints[$name] = $constraint;
|
||||
}
|
||||
}
|
||||
|
||||
return $constraints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a constraint action if it is not already in the format expected by Phinx
|
||||
*
|
||||
* @param string $constraint Constraint action name
|
||||
* @return string Constraint action name altered if needed.
|
||||
*/
|
||||
public function formatConstraintAction($constraint)
|
||||
{
|
||||
if (defined('\Phinx\Db\Table\ForeignKey::' . $constraint)) {
|
||||
return $constraint;
|
||||
}
|
||||
|
||||
return strtoupper(Inflector::underscore($constraint));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary key data for a given table
|
||||
*
|
||||
* @param string $table Name of the table ot retrieve primary key for
|
||||
* @return array
|
||||
*/
|
||||
public function primaryKeys($table)
|
||||
{
|
||||
$tableSchema = $table;
|
||||
if (!($table instanceof Table)) {
|
||||
$tableSchema = $this->schema($table);
|
||||
}
|
||||
$primaryKeys = [];
|
||||
$tablePrimaryKeys = $tableSchema->primaryKey();
|
||||
foreach ($tableSchema->columns() as $column) {
|
||||
if (in_array($column, $tablePrimaryKeys)) {
|
||||
$primaryKeys[] = ['name' => $column, 'info' => $this->column($tableSchema, $column)];
|
||||
}
|
||||
}
|
||||
|
||||
return $primaryKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the $tables list given as arguments contains primary keys
|
||||
* unsigned.
|
||||
*
|
||||
* @param array $tables List of tables to check
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUnsignedPrimaryKey($tables)
|
||||
{
|
||||
foreach ($tables as $table) {
|
||||
$tableSchema = $table;
|
||||
if (!($table instanceof Table)) {
|
||||
$tableSchema = $this->schema($table);
|
||||
}
|
||||
$tablePrimaryKeys = $tableSchema->primaryKey();
|
||||
|
||||
foreach ($tablePrimaryKeys as $primaryKey) {
|
||||
$column = $tableSchema->getColumn($primaryKey);
|
||||
if (isset($column['unsigned']) && $column['unsigned'] === true) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary key columns name for a given table
|
||||
*
|
||||
* @param string $table Name of the table ot retrieve primary key for
|
||||
* @return array
|
||||
*/
|
||||
public function primaryKeysColumnsList($table)
|
||||
{
|
||||
$primaryKeys = $this->primaryKeys($table);
|
||||
$primaryKeysColumns = Hash::extract($primaryKeys, '{n}.name');
|
||||
sort($primaryKeysColumns);
|
||||
|
||||
return $primaryKeysColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of column data for a single column
|
||||
*
|
||||
* @param \Cake\Database\Schema\Table $tableSchema Name of the table to retrieve columns for
|
||||
* @param string $column A column to retrieve data for
|
||||
* @return array
|
||||
*/
|
||||
public function column($tableSchema, $column)
|
||||
{
|
||||
return [
|
||||
'columnType' => $tableSchema->getColumnType($column),
|
||||
'options' => $this->attributes($tableSchema, $column),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the final array of options to display in a `addColumn` or `changeColumn` instruction.
|
||||
* The method also takes care of translating properties names between CakePHP database layer and phinx database
|
||||
* layer.
|
||||
*
|
||||
* @param array $options Array of options to compute the final list from.
|
||||
* @return array
|
||||
*/
|
||||
public function getColumnOption($options)
|
||||
{
|
||||
$wantedOptions = array_flip([
|
||||
'length',
|
||||
'limit',
|
||||
'default',
|
||||
'signed',
|
||||
'null',
|
||||
'comment',
|
||||
'autoIncrement',
|
||||
'precision',
|
||||
'after',
|
||||
]);
|
||||
$columnOptions = array_intersect_key($options, $wantedOptions);
|
||||
if (empty($columnOptions['comment'])) {
|
||||
unset($columnOptions['comment']);
|
||||
}
|
||||
if (empty($columnOptions['autoIncrement'])) {
|
||||
unset($columnOptions['autoIncrement']);
|
||||
}
|
||||
if (isset($columnOptions['signed']) && $columnOptions['signed'] === true) {
|
||||
unset($columnOptions['signed']);
|
||||
}
|
||||
if (empty($columnOptions['precision'])) {
|
||||
unset($columnOptions['precision']);
|
||||
} else {
|
||||
// due to Phinx using different naming for the precision and scale to CakePHP
|
||||
$columnOptions['scale'] = $columnOptions['precision'];
|
||||
|
||||
if (isset($columnOptions['limit'])) {
|
||||
$columnOptions['precision'] = $columnOptions['limit'];
|
||||
unset($columnOptions['limit']);
|
||||
}
|
||||
if (isset($columnOptions['length'])) {
|
||||
$columnOptions['precision'] = $columnOptions['length'];
|
||||
unset($columnOptions['length']);
|
||||
}
|
||||
}
|
||||
|
||||
return $columnOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string-like representation of a value
|
||||
*
|
||||
* @param string $value A value to represent as a string
|
||||
* @param bool $numbersAsString Set tu true to return as string.
|
||||
* @return mixed
|
||||
*/
|
||||
public function value($value, $numbersAsString = false)
|
||||
{
|
||||
if ($value === null || $value === 'null' || $value === 'NULL') {
|
||||
return 'null';
|
||||
}
|
||||
|
||||
if ($value === 'true' || $value === 'false') {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (!$numbersAsString && (is_numeric($value) || ctype_digit($value))) {
|
||||
return (float)$value;
|
||||
}
|
||||
|
||||
return sprintf("'%s'", addslashes($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of attributes for a given table column
|
||||
*
|
||||
* @param string $table Name of the table to retrieve columns for
|
||||
* @param string $column A column to retrieve attributes for
|
||||
* @return array
|
||||
*/
|
||||
public function attributes($table, $column)
|
||||
{
|
||||
$tableSchema = $table;
|
||||
if (!($table instanceof Table)) {
|
||||
$tableSchema = $this->schema($table);
|
||||
}
|
||||
$validOptions = [
|
||||
'length', 'limit',
|
||||
'default', 'null',
|
||||
'precision', 'scale',
|
||||
'after', 'update',
|
||||
'comment', 'unsigned',
|
||||
'signed', 'properties',
|
||||
'autoIncrement', 'unique',
|
||||
];
|
||||
|
||||
$attributes = [];
|
||||
$options = $tableSchema->getColumn($column);
|
||||
foreach ($options as $_option => $value) {
|
||||
$option = $_option;
|
||||
switch ($_option) {
|
||||
case 'length':
|
||||
$option = 'limit';
|
||||
break;
|
||||
case 'unsigned':
|
||||
$option = 'signed';
|
||||
$value = (bool)!$value;
|
||||
break;
|
||||
case 'unique':
|
||||
$value = (bool)$value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!in_array($option, $validOptions)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$attributes[$option] = $value;
|
||||
}
|
||||
|
||||
ksort($attributes);
|
||||
|
||||
return $attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array converted into a formatted multiline string
|
||||
*
|
||||
* @param array $list array of items to be stringified
|
||||
* @param array $options options to use
|
||||
* @return string
|
||||
*/
|
||||
public function stringifyList(array $list, array $options = [])
|
||||
{
|
||||
$options += [
|
||||
'indent' => 2,
|
||||
];
|
||||
|
||||
if (!$list) {
|
||||
return '';
|
||||
}
|
||||
|
||||
ksort($list);
|
||||
foreach ($list as $k => &$v) {
|
||||
if (is_array($v)) {
|
||||
$v = $this->stringifyList($v, [
|
||||
'indent' => $options['indent'] + 1,
|
||||
]);
|
||||
$v = sprintf('[%s]', $v);
|
||||
} else {
|
||||
$v = $this->value($v, $k === 'default');
|
||||
}
|
||||
if (!is_numeric($k)) {
|
||||
$v = "'$k' => $v";
|
||||
}
|
||||
}
|
||||
|
||||
$start = $end = '';
|
||||
$join = ', ';
|
||||
if ($options['indent']) {
|
||||
$join = ',';
|
||||
$start = "\n" . str_repeat(" ", $options['indent']);
|
||||
$join .= $start;
|
||||
$end = "\n" . str_repeat(" ", $options['indent'] - 1);
|
||||
}
|
||||
|
||||
return $start . implode($join, $list) . ',' . $end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a $this->table() statement only if it was not issued already
|
||||
*
|
||||
* @param string $table Table for which the statement is needed
|
||||
* @param bool $reset Reset previously set statement.
|
||||
* @return string
|
||||
*/
|
||||
public function tableStatement($table, $reset = false)
|
||||
{
|
||||
if ($reset === true) {
|
||||
unset($this->tableStatements[$table]);
|
||||
}
|
||||
|
||||
if (!isset($this->tableStatements[$table])) {
|
||||
$this->tableStatements[$table] = true;
|
||||
|
||||
return '$this->table(\'' . $table . '\')';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user