├── .phive
└── phars.xml
├── Dockerfile
├── LICENSE.txt
├── README.md
├── composer.json
├── config
└── app.example.php
├── docs.Dockerfile
├── docs
├── config
│ ├── __init__.py
│ └── all.py
├── en
│ ├── conf.py
│ ├── contents.rst
│ ├── index.rst
│ ├── seeding.rst
│ ├── upgrading-to-builtin-backend.rst
│ └── writing-migrations.rst
├── fr
│ ├── conf.py
│ ├── contents.rst
│ └── index.rst
├── ja
│ ├── conf.py
│ ├── contents.rst
│ └── index.rst
├── pt
│ ├── conf.py
│ ├── contents.rst
│ └── index.rst
└── ru
│ ├── conf.py
│ ├── contents.rst
│ └── index.rst
├── phpcs.xml
├── phpstan-baseline.neon
├── psalm-baseline.xml
├── psalm.xml
├── src
├── AbstractMigration.php
├── AbstractSeed.php
├── BaseMigration.php
├── BaseSeed.php
├── CakeAdapter.php
├── CakeManager.php
├── Command
│ ├── BakeMigrationCommand.php
│ ├── BakeMigrationDiffCommand.php
│ ├── BakeMigrationSnapshotCommand.php
│ ├── BakeSeedCommand.php
│ ├── BakeSimpleMigrationCommand.php
│ ├── DumpCommand.php
│ ├── EntryCommand.php
│ ├── MarkMigratedCommand.php
│ ├── MigrateCommand.php
│ ├── MigrationsCacheBuildCommand.php
│ ├── MigrationsCacheClearCommand.php
│ ├── MigrationsCommand.php
│ ├── MigrationsCreateCommand.php
│ ├── MigrationsDumpCommand.php
│ ├── MigrationsMarkMigratedCommand.php
│ ├── MigrationsMigrateCommand.php
│ ├── MigrationsRollbackCommand.php
│ ├── MigrationsSeedCommand.php
│ ├── MigrationsStatusCommand.php
│ ├── Phinx
│ │ ├── BaseCommand.php
│ │ ├── CacheBuild.php
│ │ ├── CacheClear.php
│ │ ├── CommandTrait.php
│ │ ├── Create.php
│ │ ├── Dump.php
│ │ ├── MarkMigrated.php
│ │ ├── Migrate.php
│ │ ├── Rollback.php
│ │ ├── Seed.php
│ │ └── Status.php
│ ├── RollbackCommand.php
│ ├── SeedCommand.php
│ ├── SnapshotTrait.php
│ └── StatusCommand.php
├── Config
│ ├── Config.php
│ └── ConfigInterface.php
├── ConfigurationTrait.php
├── Db
│ ├── Action
│ │ ├── Action.php
│ │ ├── AddColumn.php
│ │ ├── AddForeignKey.php
│ │ ├── AddIndex.php
│ │ ├── ChangeColumn.php
│ │ ├── ChangeComment.php
│ │ ├── ChangePrimaryKey.php
│ │ ├── CreateTable.php
│ │ ├── DropForeignKey.php
│ │ ├── DropIndex.php
│ │ ├── DropTable.php
│ │ ├── RemoveColumn.php
│ │ ├── RenameColumn.php
│ │ └── RenameTable.php
│ ├── Adapter
│ │ ├── AbstractAdapter.php
│ │ ├── AdapterFactory.php
│ │ ├── AdapterInterface.php
│ │ ├── AdapterWrapper.php
│ │ ├── DirectActionInterface.php
│ │ ├── MysqlAdapter.php
│ │ ├── PhinxAdapter.php
│ │ ├── PostgresAdapter.php
│ │ ├── RecordingAdapter.php
│ │ ├── SqliteAdapter.php
│ │ ├── SqlserverAdapter.php
│ │ ├── TimedOutputAdapter.php
│ │ ├── UnsupportedColumnTypeException.php
│ │ └── WrapperInterface.php
│ ├── AlterInstructions.php
│ ├── Expression.php
│ ├── Literal.php
│ ├── Plan
│ │ ├── AlterTable.php
│ │ ├── Intent.php
│ │ ├── NewTable.php
│ │ ├── Plan.php
│ │ └── Solver
│ │ │ └── ActionSplitter.php
│ ├── Table.php
│ └── Table
│ │ ├── Column.php
│ │ ├── ForeignKey.php
│ │ ├── Index.php
│ │ └── Table.php
├── Middleware
│ └── PendingMigrationsMiddleware.php
├── Migration
│ ├── BackendInterface.php
│ ├── BuiltinBackend.php
│ ├── Environment.php
│ ├── IrreversibleMigrationException.php
│ ├── Manager.php
│ ├── ManagerFactory.php
│ └── PhinxBackend.php
├── MigrationInterface.php
├── Migrations.php
├── MigrationsDispatcher.php
├── MigrationsPlugin.php
├── SeedInterface.php
├── Shim
│ ├── MigrationAdapter.php
│ ├── OutputAdapter.php
│ └── SeedAdapter.php
├── Table.php
├── TestSuite
│ └── Migrator.php
├── Util
│ ├── ColumnParser.php
│ ├── SchemaTrait.php
│ ├── TableFinder.php
│ ├── Util.php
│ └── UtilTrait.php
└── View
│ └── Helper
│ └── MigrationHelper.php
└── templates
├── Phinx
└── create.php.template
└── bake
├── Seed
└── seed.twig
├── config
├── diff.twig
├── skeleton.twig
└── snapshot.twig
└── element
├── add-columns.twig
├── add-foreign-keys.twig
├── add-indexes.twig
└── create-tables.twig
/.phive/phars.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # Basic docker based environment
2 | # Necessary to trick dokku into building the documentation
3 | # using dockerfile instead of herokuish
4 | FROM ubuntu:22.04
5 |
6 | # Add basic tools
7 | RUN apt-get update && \
8 | apt-get install -y build-essential \
9 | software-properties-common \
10 | curl \
11 | git \
12 | libxml2 \
13 | libffi-dev \
14 | libssl-dev && \
15 | LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php && \
16 | apt-get update && \
17 | apt-get install -y php8.1-cli php8.1-mbstring php8.1-xml php8.1-zip php8.1-intl php8.1-opcache php8.1-sqlite &&\
18 | apt-get clean &&\
19 | rm -rf /var/lib/apt/lists/*
20 |
21 | WORKDIR /code
22 |
23 | VOLUME ["/code"]
24 |
25 | CMD [ '/bin/bash' ]
26 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | CakePHP(tm) : The Rapid Development PHP Framework (https://cakephp.org)
4 | Copyright (c) 2005-present, Cake Software Foundation, Inc.
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a
7 | copy of this software and associated documentation files (the "Software"),
8 | to deal in the Software without restriction, including without limitation
9 | the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 | and/or sell copies of the Software, and to permit persons to whom the
11 | Software is furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 | DEALINGS IN THE SOFTWARE.
23 |
24 | Cake Software Foundation, Inc.
25 | 1785 E. Sahara Avenue,
26 | Suite 490-204
27 | Las Vegas, Nevada 89104,
28 | United States of America.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Migrations plugin for CakePHP
2 |
3 | [](https://github.com/cakephp/migrations/actions/workflows/ci.yml)
4 | [](https://app.codecov.io/github/cakephp/migrations/tree/3.x)
5 | [](LICENSE.txt)
6 | [](https://packagist.org/packages/cakephp/migrations)
7 |
8 | This is a Database Migrations system for CakePHP.
9 |
10 | The plugin consists of a CakePHP CLI wrapper for the [Phinx](https://book.cakephp.org/phinx/0/en/index.html) migrations library.
11 |
12 | This branch is for use with CakePHP **5.x**. See [version map](https://github.com/cakephp/migrations/wiki#version-map) for details.
13 |
14 | ## Installation
15 |
16 | You can install this plugin into your CakePHP application using [Composer](https://getcomposer.org).
17 |
18 | Run the following command
19 | ```sh
20 | composer require cakephp/migrations
21 | ```
22 |
23 | ## Configuration
24 |
25 | You can load the plugin using the shell command:
26 |
27 | ```
28 | bin/cake plugin load Migrations --only-cli
29 | ```
30 |
31 | If you are using the PendingMigrations middleware, use:
32 | ```
33 | bin/cake plugin load Migrations
34 | ```
35 |
36 | ### Enabling the builtin backend
37 |
38 | In a future release, migrations will be switching to a new backend based on the CakePHP ORM. We're aiming
39 | to be compatible with as many existing migrations as possible, and could use your feedback. Enable the
40 | new backend with:
41 |
42 | ```php
43 | // in app/config/app_local.php
44 | $config = [
45 | // Other configuration
46 | 'Migrations' => ['backend' => 'builtin'],
47 | ];
48 |
49 | ```
50 |
51 | ## Documentation
52 |
53 | Full documentation of the plugin can be found on the [CakePHP Cookbook](https://book.cakephp.org/migrations/4/).
54 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cakephp/migrations",
3 | "description": "Database Migration plugin for CakePHP based on Phinx",
4 | "license": "MIT",
5 | "type": "cakephp-plugin",
6 | "keywords": [
7 | "cakephp",
8 | "migrations",
9 | "cli"
10 | ],
11 | "authors": [
12 | {
13 | "name": "CakePHP Community",
14 | "homepage": "https://github.com/cakephp/migrations/graphs/contributors"
15 | }
16 | ],
17 | "homepage": "https://github.com/cakephp/migrations",
18 | "support": {
19 | "issues": "https://github.com/cakephp/migrations/issues",
20 | "forum": "https://stackoverflow.com/tags/cakephp",
21 | "irc": "irc://irc.freenode.org/cakephp",
22 | "source": "https://github.com/cakephp/migrations"
23 | },
24 | "require": {
25 | "php": ">=8.1",
26 | "cakephp/cache": "^5.0",
27 | "cakephp/orm": "^5.0",
28 | "robmorgan/phinx": "^0.16.0"
29 | },
30 | "require-dev": {
31 | "cakephp/bake": "^3.0",
32 | "cakephp/cakephp": "^5.0.11",
33 | "cakephp/cakephp-codesniffer": "^5.0",
34 | "phpunit/phpunit": "^10.5.5 || ^11.1.3"
35 | },
36 | "suggest": {
37 | "cakephp/bake": "If you want to generate migrations.",
38 | "dereuromark/cakephp-ide-helper": "If you want to have IDE suggest/autocomplete when creating migrations."
39 | },
40 | "minimum-stability": "dev",
41 | "prefer-stable": true,
42 | "autoload": {
43 | "psr-4": {
44 | "Migrations\\": "src/"
45 | }
46 | },
47 | "autoload-dev": {
48 | "psr-4": {
49 | "Cake\\Test\\": "./vendor/cakephp/cakephp/tests/",
50 | "Migrations\\Test\\": "tests/",
51 | "SimpleSnapshot\\": "tests/test_app/Plugin/SimpleSnapshot/src/",
52 | "TestApp\\": "tests/test_app/App/",
53 | "TestBlog\\": "tests/test_app/Plugin/TestBlog/src/"
54 | }
55 | },
56 | "config": {
57 | "allow-plugins": {
58 | "dealerdirect/phpcodesniffer-composer-installer": true
59 | }
60 | },
61 | "scripts": {
62 | "check": [
63 | "@cs-check",
64 | "@stan",
65 | "@test"
66 | ],
67 | "cs-check": "phpcs -p",
68 | "cs-fix": "phpcbf -p",
69 | "phpstan": "tools/phpstan analyse",
70 | "psalm": "tools/psalm --show-info=false",
71 | "psalm-baseline": "tools/psalm --set-baseline=psalm-baseline.xml",
72 | "stan": [
73 | "@phpstan",
74 | "@psalm"
75 | ],
76 | "stan-baseline": "tools/phpstan --generate-baseline",
77 | "stan-setup": "phive install",
78 | "lowest": "validate-prefer-lowest",
79 | "lowest-setup": "composer update --prefer-lowest --prefer-stable --prefer-dist --no-interaction && cp composer.json composer.backup && composer require --dev dereuromark/composer-prefer-lowest && mv composer.backup composer.json",
80 | "test": "phpunit",
81 | "test-coverage": "phpunit --coverage-clover=clover.xml"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/config/app.example.php:
--------------------------------------------------------------------------------
1 | [
9 | 'backend' => 'builtin',
10 | 'unsigned_primary_keys' => null,
11 | 'column_null_default' => null,
12 | ],
13 | ];
14 |
--------------------------------------------------------------------------------
/docs.Dockerfile:
--------------------------------------------------------------------------------
1 | # Generate the HTML output.
2 | FROM ghcr.io/cakephp/docs-builder as builder
3 |
4 | COPY docs /data/docs
5 |
6 | RUN cd /data/docs-builder && \
7 | # In the future repeat website for each version
8 | make website LANGS="en fr ja pt ru" SOURCE=/data/docs DEST=/data/website/
9 |
10 | # Build a small nginx container with just the static site in it.
11 | FROM ghcr.io/cakephp/docs-builder:runtime as runtime
12 |
13 | ENV LANGS="en fr ja pt ru"
14 | ENV SEARCH_SOURCE="/usr/share/nginx/html"
15 | ENV SEARCH_URL_PREFIX="/migrations/4"
16 |
17 | COPY --from=builder /data/docs /data/docs
18 | COPY --from=builder /data/website /data/website
19 | COPY --from=builder /data/docs-builder/nginx.conf /etc/nginx/conf.d/default.conf
20 |
21 |
22 | # Move each version into place
23 | RUN cp -R /data/website/html/* /usr/share/nginx/html \
24 | && rm -rf /data/website/
25 |
26 | RUN ln -s /usr/share/nginx/html /usr/share/nginx/html/2.x
27 |
--------------------------------------------------------------------------------
/docs/config/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cakephp/migrations/a735a6f39e8452727b6281834529d40bc6efe741/docs/config/__init__.py
--------------------------------------------------------------------------------
/docs/config/all.py:
--------------------------------------------------------------------------------
1 | # Global configuration information used across all the
2 | # translations of documentation.
3 | #
4 | # Import the base theme configuration
5 | from cakephpsphinx.config.all import *
6 |
7 | # The version info for the project you're documenting, acts as replacement for
8 | # |version| and |release|, also used in various other places throughout the
9 | # built documents.
10 | #
11 |
12 | # The full version, including alpha/beta/rc tags.
13 | release = '4.x'
14 |
15 | # The search index version.
16 | search_version = 'migrations-4'
17 |
18 | # The marketing display name for the book.
19 | version_name = ''
20 |
21 | # Project name shown in the black header bar
22 | project = 'CakePHP Migrations'
23 |
24 | # Other versions that display in the version picker menu.
25 | version_list = [
26 | {'name': '2.x', 'number': 'migrations/2', 'title': '2.x'},
27 | {'name': '3.x', 'number': 'migrations/3', 'title': '3.x'},
28 | {'name': '4.x', 'number': 'migrations/4', 'title': '4.x', 'current': True},
29 | ]
30 |
31 | # Languages available.
32 | languages = ['en', 'fr', 'ja', 'pt', 'ru']
33 |
34 | # The GitHub branch name for this version of the docs
35 | # for edit links to point at.
36 | branch = '4.x'
37 |
38 | # Current version being built
39 | version = '4.x'
40 |
41 | # Language in use for this directory.
42 | language = 'en'
43 |
44 | show_root_link = True
45 |
46 | repository = 'cakephp/migrations'
47 |
48 | source_path = 'docs/'
49 |
50 | hide_page_contents = ('search', '404', 'contents')
51 |
52 | is_prerelease = False
53 |
--------------------------------------------------------------------------------
/docs/en/conf.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 |
3 | # Append the top level directory of the docs, so we can import from the config dir.
4 | sys.path.insert(0, os.path.abspath('..'))
5 |
6 | # Pull in all the configuration options defined in the global config file..
7 | from config.all import *
8 |
9 | language = 'en'
10 |
--------------------------------------------------------------------------------
/docs/en/contents.rst:
--------------------------------------------------------------------------------
1 | .. toctree::
2 | :maxdepth: 2
3 | :caption: CakePHP Migrations
4 |
5 | /index
6 | /writing-migrations
7 | /seeding
8 | /upgrading-to-builtin-backend
9 |
--------------------------------------------------------------------------------
/docs/en/upgrading-to-builtin-backend.rst:
--------------------------------------------------------------------------------
1 | Upgrading to the builtin backend
2 | ################################
3 |
4 | As of migrations 4.3 there is a new migrations backend that uses CakePHP's
5 | database abstractions and ORM. In 4.4, the ``builtin`` backend became the
6 | default backend. Longer term this will allow for phinx to be
7 | removed as a dependency. This greatly reduces the dependency footprint of
8 | migrations.
9 |
10 | What is the same?
11 | =================
12 |
13 | Your migrations shouldn't have to change much to adapt to the new backend.
14 | The migrations backend implements all of the phinx interfaces and can run
15 | migrations based on phinx classes. If your migrations don't work in a way that
16 | could be addressed by the changes outlined below, please open an issue, as we'd
17 | like to maintain as much compatibility as we can.
18 |
19 | What is different?
20 | ==================
21 |
22 | If your migrations are using the ``AdapterInterface`` to fetch rows or update
23 | rows you will need to update your code. If you use ``Adapter::query()`` to
24 | execute queries, the return of this method is now
25 | ``Cake\Database\StatementInterface`` instead. This impacts ``fetchAll()``,
26 | and ``fetch()``::
27 |
28 | // This
29 | $stmt = $this->getAdapter()->query('SELECT * FROM articles');
30 | $rows = $stmt->fetchAll();
31 |
32 | // Now needs to be
33 | $stmt = $this->getAdapter()->query('SELECT * FROM articles');
34 | $rows = $stmt->fetchAll('assoc');
35 |
36 | Similar changes are for fetching a single row::
37 |
38 | // This
39 | $stmt = $this->getAdapter()->query('SELECT * FROM articles');
40 | $rows = $stmt->fetch();
41 |
42 | // Now needs to be
43 | $stmt = $this->getAdapter()->query('SELECT * FROM articles');
44 | $rows = $stmt->fetch('assoc');
45 |
46 | Problems with the new backend?
47 | ==============================
48 |
49 | The new backend is enabled by default. If your migrations contain errors when
50 | run with the builtin backend, please open `an issue
51 | `_. You can also switch back
52 | to the ``phinx`` backend through application configuration. Add the
53 | following to your ``config/app.php``::
54 |
55 | return [
56 | // Other configuration.
57 | 'Migrations' => ['backend' => 'phinx'],
58 | ];
59 |
--------------------------------------------------------------------------------
/docs/fr/conf.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 |
3 | # Append the top level directory of the docs, so we can import from the config dir.
4 | sys.path.insert(0, os.path.abspath('..'))
5 |
6 | # Pull in all the configuration options defined in the global config file..
7 | from config.all import *
8 |
9 | language = 'fr'
10 |
--------------------------------------------------------------------------------
/docs/fr/contents.rst:
--------------------------------------------------------------------------------
1 | .. toctree::
2 | :maxdepth: 2
3 | :caption: CakePHP Migrations
4 |
5 | /index
6 |
--------------------------------------------------------------------------------
/docs/ja/conf.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 |
3 | # Append the top level directory of the docs, so we can import from the config dir.
4 | sys.path.insert(0, os.path.abspath('..'))
5 |
6 | # Pull in all the configuration options defined in the global config file..
7 | from config.all import *
8 |
9 | language = 'ja'
10 |
--------------------------------------------------------------------------------
/docs/ja/contents.rst:
--------------------------------------------------------------------------------
1 | .. toctree::
2 | :maxdepth: 2
3 | :caption: CakePHP Migrations
4 |
5 | /index
6 |
--------------------------------------------------------------------------------
/docs/pt/conf.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 |
3 | # Append the top level directory of the docs, so we can import from the config dir.
4 | sys.path.insert(0, os.path.abspath('..'))
5 |
6 | # Pull in all the configuration options defined in the global config file..
7 | from config.all import *
8 |
9 | language = 'pt'
10 |
--------------------------------------------------------------------------------
/docs/pt/contents.rst:
--------------------------------------------------------------------------------
1 | .. toctree::
2 | :maxdepth: 2
3 | :caption: CakePHP Migrations
4 |
5 | /index
6 |
--------------------------------------------------------------------------------
/docs/ru/conf.py:
--------------------------------------------------------------------------------
1 | import sys, os
2 |
3 | # Append the top level directory of the docs, so we can import from the config dir.
4 | sys.path.insert(0, os.path.abspath('..'))
5 |
6 | # Pull in all the configuration options defined in the global config file..
7 | from config.all import *
8 |
9 | language = 'ru'
10 |
--------------------------------------------------------------------------------
/docs/ru/contents.rst:
--------------------------------------------------------------------------------
1 | .. toctree::
2 | :maxdepth: 2
3 | :caption: CakePHP Migrations
4 |
5 | /index
6 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | src/
6 | tests/
7 |
8 | */tests/comparisons/*
9 | */tests/TestCase/Util/_files/*
10 | */test_app/config/*
11 | */TestBlog/config/*
12 | */BarPlugin/config/*
13 | */FooPlugin/config/*
14 | */Migrator/config/*
15 |
16 |
17 |
18 |
19 |
20 | 0
21 |
22 |
23 | tests/comparisons/*
24 | test_app/config/*
25 | test_app/**/config/*
26 |
27 |
28 | tests/comparisons/*
29 |
30 |
31 |
--------------------------------------------------------------------------------
/psalm.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/AbstractMigration.php:
--------------------------------------------------------------------------------
1 | getAdapter()->hasTransactions();
46 | }
47 |
48 | /**
49 | * Returns an instance of the Table class.
50 | *
51 | * You can use this class to create and manipulate tables.
52 | *
53 | * @param string $tableName Table Name
54 | * @param array $options Options
55 | * @return \Migrations\Table
56 | */
57 | public function table(string $tableName, array $options = []): Table
58 | {
59 | if ($this->autoId === false) {
60 | $options['id'] = false;
61 | }
62 |
63 | $table = new Table($tableName, $options, $this->getAdapter());
64 | $this->tables[] = $table;
65 |
66 | return $table;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/AbstractSeed.php:
--------------------------------------------------------------------------------
1 | getOutput()->writeln('');
49 | $this->getOutput()->writeln(
50 | ' ====' .
51 | ' ' . $seeder . ':' .
52 | ' seeding',
53 | );
54 |
55 | $start = microtime(true);
56 | $this->runCall($seeder);
57 | $end = microtime(true);
58 |
59 | $this->getOutput()->writeln(
60 | ' ====' .
61 | ' ' . $seeder . ':' .
62 | ' seeded' .
63 | ' ' . sprintf('%.4fs', $end - $start) . '',
64 | );
65 | $this->getOutput()->writeln('');
66 | }
67 |
68 | /**
69 | * Calls another seeder from this seeder.
70 | * It will load the Seed class you are calling and run it.
71 | *
72 | * @param string $seeder Name of the seeder to call from the current seed
73 | * @return void
74 | */
75 | protected function runCall(string $seeder): void
76 | {
77 | [$pluginName, $seeder] = pluginSplit($seeder);
78 |
79 | $argv = [
80 | 'seed',
81 | '--seed',
82 | $seeder,
83 | ];
84 |
85 | $plugin = $pluginName ?: $this->input->getOption('plugin');
86 | if ($plugin !== null) {
87 | $argv[] = '--plugin';
88 | $argv[] = $plugin;
89 | }
90 |
91 | $connection = $this->input->getOption('connection');
92 | if ($connection !== null) {
93 | $argv[] = '--connection';
94 | $argv[] = $connection;
95 | }
96 |
97 | $source = $this->input->getOption('source');
98 | if ($source !== null) {
99 | $argv[] = '--source';
100 | $argv[] = $source;
101 | }
102 |
103 | $seedCommand = new Seed();
104 | $input = new ArgvInput($argv, $seedCommand->getDefinition());
105 | $seedCommand->setInput($input);
106 | $config = $seedCommand->getConfig();
107 |
108 | $seedPaths = $config->getSeedPaths();
109 | require_once array_pop($seedPaths) . DS . $seeder . '.php';
110 | /** @var \Phinx\Seed\SeedInterface $seeder */
111 | $seeder = new $seeder();
112 | $seeder->setOutput($this->getOutput());
113 | $seeder->setAdapter($this->getAdapter());
114 | $seeder->setInput($this->input);
115 | $seeder->run();
116 | }
117 |
118 | /**
119 | * Sets the InputInterface this Seed class is being used with.
120 | *
121 | * @param \Symfony\Component\Console\Input\InputInterface $input Input object.
122 | * @return $this
123 | */
124 | public function setInput(InputInterface $input)
125 | {
126 | $this->input = $input;
127 |
128 | return $this;
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/src/CakeAdapter.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
53 | $pdo = $adapter->getConnection();
54 |
55 | if ($pdo->getAttribute(PDO::ATTR_ERRMODE) !== PDO::ERRMODE_EXCEPTION) {
56 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
57 | }
58 | $connection->cacheMetadata(false);
59 |
60 | if ($connection->getDriver() instanceof Postgres) {
61 | $config = $connection->config();
62 | $schema = empty($config['schema']) ? 'public' : $config['schema'];
63 | $pdo->exec('SET search_path TO ' . $pdo->quote($schema));
64 | }
65 |
66 | $driver = $connection->getDriver();
67 | $prop = new ReflectionProperty($driver, 'pdo');
68 | $prop->setValue($driver, $pdo);
69 | }
70 |
71 | /**
72 | * Gets the CakePHP Connection object.
73 | *
74 | * @return \Cake\Database\Connection
75 | */
76 | public function getCakeConnection(): Connection
77 | {
78 | return $this->connection;
79 | }
80 |
81 | /**
82 | * Returns a new Query object
83 | *
84 | * @param string $type The type of query to generate
85 | * (one of the `\Cake\Database\Query::TYPE_*` constants).
86 | * @return \Cake\Database\Query
87 | */
88 | public function getQueryBuilder(string $type): Query
89 | {
90 | return match ($type) {
91 | Query::TYPE_SELECT => $this->getCakeConnection()->selectQuery(),
92 | Query::TYPE_INSERT => $this->getCakeConnection()->insertQuery(),
93 | Query::TYPE_UPDATE => $this->getCakeConnection()->updateQuery(),
94 | Query::TYPE_DELETE => $this->getCakeConnection()->deleteQuery(),
95 | default => throw new InvalidArgumentException(
96 | 'Query type must be one of: `select`, `insert`, `update`, `delete`.',
97 | )
98 | };
99 | }
100 |
101 | /**
102 | * Returns the adapter type name, for example mysql
103 | *
104 | * @return string
105 | */
106 | public function getAdapterType(): string
107 | {
108 | return $this->getAdapter()->getAdapterType();
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Command/DumpCommand.php:
--------------------------------------------------------------------------------
1 |
50 | */
51 | public static function extractArgs(Arguments $args): array
52 | {
53 | /** @var array $newArgs */
54 | $newArgs = [];
55 | if ($args->getOption('connection')) {
56 | $newArgs[] = '-c';
57 | $newArgs[] = $args->getOption('connection');
58 | }
59 | if ($args->getOption('plugin')) {
60 | $newArgs[] = '-p';
61 | $newArgs[] = $args->getOption('plugin');
62 | }
63 | if ($args->getOption('source')) {
64 | $newArgs[] = '-s';
65 | $newArgs[] = $args->getOption('source');
66 | }
67 |
68 | return $newArgs;
69 | }
70 |
71 | /**
72 | * Configure the option parser
73 | *
74 | * @param \Cake\Console\ConsoleOptionParser $parser The option parser to configure
75 | * @return \Cake\Console\ConsoleOptionParser
76 | */
77 | public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
78 | {
79 | $parser->setDescription([
80 | 'Dumps the current schema of the database to be used while baking a diff',
81 | '',
82 | 'migrations dump -c secondary',
83 | ])->addOption('plugin', [
84 | 'short' => 'p',
85 | 'help' => 'The plugin to dump migrations for',
86 | ])->addOption('connection', [
87 | 'short' => 'c',
88 | 'help' => 'The datasource connection to use',
89 | 'default' => 'default',
90 | ])->addOption('source', [
91 | 'short' => 's',
92 | 'help' => 'The folder under src/Config that migrations are in',
93 | 'default' => ConfigInterface::DEFAULT_MIGRATION_FOLDER,
94 | ]);
95 |
96 | return $parser;
97 | }
98 |
99 | /**
100 | * Execute the command.
101 | *
102 | * @param \Cake\Console\Arguments $args The command arguments.
103 | * @param \Cake\Console\ConsoleIo $io The console io
104 | * @return int|null The exit code or null for success
105 | */
106 | public function execute(Arguments $args, ConsoleIo $io): ?int
107 | {
108 | $factory = new ManagerFactory([
109 | 'plugin' => $args->getOption('plugin'),
110 | 'source' => $args->getOption('source'),
111 | 'connection' => $args->getOption('connection'),
112 | ]);
113 | $config = $factory->createConfig();
114 | $path = $config->getMigrationPath();
115 | $connectionName = (string)$config->getConnection();
116 | $connection = ConnectionManager::get($connectionName);
117 | assert($connection instanceof Connection);
118 |
119 | $collection = $connection->getSchemaCollection();
120 | $options = [
121 | 'require-table' => false,
122 | 'plugin' => $args->getOption('plugin'),
123 | ];
124 | // The connection property is used by the trait methods.
125 | $this->connection = $connectionName;
126 | $finder = new TableFinder($connectionName);
127 | $tables = $finder->getTablesToBake($collection, $options);
128 |
129 | $dump = [];
130 | if ($tables) {
131 | foreach ($tables as $table) {
132 | $schema = $collection->describe($table);
133 | $dump[$table] = $schema;
134 | }
135 | }
136 |
137 | $filePath = $path . DS . 'schema-dump-' . $connectionName . '.lock';
138 | $io->out("Writing dump file `{$filePath}`...");
139 | if (file_put_contents($filePath, serialize($dump))) {
140 | $io->out("Dump file `{$filePath}` was successfully written");
141 |
142 | return self::CODE_SUCCESS;
143 | }
144 | $io->out("An error occurred while writing dump file `{$filePath}`");
145 |
146 | return self::CODE_ERROR;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/Command/EntryCommand.php:
--------------------------------------------------------------------------------
1 | commands = $commands;
54 | }
55 |
56 | /**
57 | * Run the command.
58 | *
59 | * Override the run() method for special handling of the `--help` option.
60 | *
61 | * @param array $argv Arguments from the CLI environment.
62 | * @param \Cake\Console\ConsoleIo $io The console io
63 | * @return int|null Exit code or null for success.
64 | */
65 | public function run(array $argv, ConsoleIo $io): ?int
66 | {
67 | $this->initialize();
68 |
69 | $parser = $this->getOptionParser();
70 | try {
71 | [$options, $arguments] = $parser->parse($argv);
72 | $args = new Arguments(
73 | $arguments,
74 | $options,
75 | $parser->argumentNames(),
76 | );
77 | } catch (ConsoleException $e) {
78 | $io->err('Error: ' . $e->getMessage());
79 |
80 | return static::CODE_ERROR;
81 | }
82 | $this->setOutputLevel($args, $io);
83 |
84 | // This is the variance from Command::run()
85 | if (!$args->getArgumentAt(0) && $args->getOption('help')) {
86 | $backend = Configure::read('Migrations.backend', 'builtin');
87 | $io->out([
88 | 'Migrations',
89 | '',
90 | "Migrations provides commands for managing your application's database schema and initial data.",
91 | '',
92 | "Using {$backend} backend.",
93 | '',
94 | ]);
95 | $help = $this->getHelp();
96 | $this->executeCommand($help, [], $io);
97 |
98 | return static::CODE_SUCCESS;
99 | }
100 |
101 | return $this->execute($args, $io);
102 | }
103 |
104 | /**
105 | * Execute the command.
106 | *
107 | * @param \Cake\Console\Arguments $args The command arguments.
108 | * @param \Cake\Console\ConsoleIo $io The console io
109 | * @return int|null The exit code or null for success
110 | */
111 | public function execute(Arguments $args, ConsoleIo $io): ?int
112 | {
113 | if ($args->hasArgumentAt(0)) {
114 | $name = $args->getArgumentAt(0);
115 | $io->err(
116 | "Could not find migrations command named `$name`."
117 | . ' Run `migrations --help` to get a list of commands.',
118 | );
119 |
120 | return static::CODE_ERROR;
121 | }
122 | $io->err('No command provided. Run `migrations --help` to get a list of commands.');
123 |
124 | return static::CODE_ERROR;
125 | }
126 |
127 | /**
128 | * Gets the generated help command
129 | *
130 | * @return \Cake\Console\Command\HelpCommand
131 | */
132 | public function getHelp(): HelpCommand
133 | {
134 | $help = new HelpCommand();
135 | $commands = [];
136 | foreach ($this->commands as $command => $class) {
137 | if (str_starts_with($command, 'migrations')) {
138 | $parts = explode(' ', $command);
139 |
140 | // Remove `migrations`
141 | array_shift($parts);
142 | if (count($parts) === 0) {
143 | continue;
144 | }
145 | $commands[$command] = $class;
146 | }
147 | }
148 |
149 | $CommandCollection = new CommandCollection($commands);
150 | $help->setCommandCollection($CommandCollection);
151 |
152 | return $help;
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/src/Command/MarkMigratedCommand.php:
--------------------------------------------------------------------------------
1 | setDescription([
48 | 'Mark a migration as applied',
49 | '',
50 | 'Can mark one or more migrations as applied without applying the changes in the migration.',
51 | '',
52 | 'migrations mark_migrated --connection secondary',
53 | 'Mark all migrations as applied',
54 | '',
55 | 'migrations mark_migrated --connection secondary --target 003',
56 | 'mark migrations as applied up to the 003',
57 | '',
58 | 'migrations mark_migrated --target 003 --only',
59 | 'mark only 003 as applied.',
60 | '',
61 | 'migrations mark_migrated --target 003 --exclude',
62 | 'mark up to 003, but not 003 as applied.',
63 | ])->addOption('plugin', [
64 | 'short' => 'p',
65 | 'help' => 'The plugin to mark migrations for',
66 | ])->addOption('connection', [
67 | 'short' => 'c',
68 | 'help' => 'The datasource connection to use',
69 | 'default' => 'default',
70 | ])->addOption('source', [
71 | 'short' => 's',
72 | 'default' => ConfigInterface::DEFAULT_MIGRATION_FOLDER,
73 | 'help' => 'The folder where your migrations are',
74 | ])->addOption('target', [
75 | 'short' => 't',
76 | 'help' => 'Migrations from the beginning to the provided version will be marked as applied.',
77 | ])->addOption('only', [
78 | 'short' => 'o',
79 | 'help' => 'If present, only the target migration will be marked as applied.',
80 | 'boolean' => true,
81 | ])->addOption('exclude', [
82 | 'short' => 'x',
83 | 'help' => 'If present, migrations from the beginning until the target version but not including the target will be marked as applied.',
84 | 'boolean' => true,
85 | ]);
86 |
87 | return $parser;
88 | }
89 |
90 | /**
91 | * Checks for an invalid use of `--exclude` or `--only`
92 | *
93 | * @param \Cake\Console\Arguments $args The console arguments
94 | * @return bool Returns true when it is an invalid use of `--exclude` or `--only` otherwise false
95 | */
96 | protected function invalidOnlyOrExclude(Arguments $args): bool
97 | {
98 | return ($args->getOption('exclude') && $args->getOption('only')) ||
99 | ($args->getOption('exclude') || $args->getOption('only')) &&
100 | $args->getOption('target') === null;
101 | }
102 |
103 | /**
104 | * Execute the command.
105 | *
106 | * @param \Cake\Console\Arguments $args The command arguments.
107 | * @param \Cake\Console\ConsoleIo $io The console io
108 | * @return int|null The exit code or null for success
109 | */
110 | public function execute(Arguments $args, ConsoleIo $io): ?int
111 | {
112 | $factory = new ManagerFactory([
113 | 'plugin' => $args->getOption('plugin'),
114 | 'source' => $args->getOption('source'),
115 | 'connection' => $args->getOption('connection'),
116 | ]);
117 | $manager = $factory->createManager($io);
118 | $config = $manager->getConfig();
119 | $path = $config->getMigrationPath();
120 |
121 | if ($this->invalidOnlyOrExclude($args)) {
122 | $io->err(
123 | 'You should use `--exclude` OR `--only` (not both) along with a `--target` !',
124 | );
125 |
126 | return self::CODE_ERROR;
127 | }
128 |
129 | try {
130 | $versions = $manager->getVersionsToMark($args);
131 | } catch (InvalidArgumentException $e) {
132 | $io->err(sprintf('%s', $e->getMessage()));
133 |
134 | return self::CODE_ERROR;
135 | }
136 |
137 | $output = $manager->markVersionsAsMigrated($path, $versions);
138 | array_map(fn($line) => $io->out($line), $output);
139 |
140 | return self::CODE_SUCCESS;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Command/MigrationsCacheBuildCommand.php:
--------------------------------------------------------------------------------
1 | setName('orm-cache-build')
25 | ->setDescription(
26 | 'Build all metadata caches for the connection. ' .
27 | 'If a table name is provided, only that table will be cached.',
28 | )
29 | ->addOption(
30 | 'connection',
31 | null,
32 | InputOption::VALUE_OPTIONAL,
33 | 'The connection to build/clear metadata cache data for.',
34 | 'default',
35 | )
36 | ->addArgument(
37 | 'name',
38 | InputArgument::OPTIONAL,
39 | 'A specific table you want to clear/refresh cached data for.',
40 | );
41 | }
42 |
43 | /**
44 | * @inheritDoc
45 | */
46 | protected function execute(InputInterface $input, OutputInterface $output): int
47 | {
48 | /** @var string $name */
49 | $name = $input->getArgument('name');
50 | $schema = $this->_getSchema($input, $output);
51 | if (!$schema) {
52 | return static::CODE_ERROR;
53 | }
54 | $tables = [$name];
55 | if (!$name) {
56 | $tables = $schema->listTables();
57 | }
58 | foreach ($tables as $table) {
59 | $output->writeln('Building metadata cache for ' . $table);
60 | $schema->describe($table, ['forceRefresh' => true]);
61 | }
62 | $output->writeln('Cache build complete');
63 |
64 | return static::CODE_SUCCESS;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Command/Phinx/CacheClear.php:
--------------------------------------------------------------------------------
1 | setName('orm-cache-clear')
25 | ->setDescription(
26 | 'Clear all metadata caches for the connection. ' .
27 | 'If a table name is provided, only that table will be removed.',
28 | )
29 | ->addOption(
30 | 'connection',
31 | null,
32 | InputOption::VALUE_OPTIONAL,
33 | 'The connection to build/clear metadata cache data for.',
34 | 'default',
35 | )
36 | ->addArgument(
37 | 'name',
38 | InputArgument::OPTIONAL,
39 | 'A specific table you want to clear/refresh cached data for.',
40 | );
41 | }
42 |
43 | /**
44 | * @inheritDoc
45 | */
46 | protected function execute(InputInterface $input, OutputInterface $output): int
47 | {
48 | $schema = $this->_getSchema($input, $output);
49 | /** @var string $name */
50 | $name = $input->getArgument('name');
51 | if (!$schema) {
52 | return static::CODE_ERROR;
53 | }
54 | $tables = [$name];
55 | if (!$name) {
56 | $tables = $schema->listTables();
57 | }
58 | $cacher = $schema->getCacher();
59 | foreach ($tables as $table) {
60 | $output->writeln(sprintf(
61 | 'Clearing metadata cache for %s',
62 | $table,
63 | ));
64 | $cacher->delete($table);
65 | }
66 | $output->writeln('Cache clear complete');
67 |
68 | return static::CODE_SUCCESS;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/Command/Phinx/CommandTrait.php:
--------------------------------------------------------------------------------
1 | beforeExecute($input, $output);
36 |
37 | return parent::execute($input, $output);
38 | }
39 |
40 | /**
41 | * Overrides the action execute method in order to vanish the idea of environments
42 | * from phinx. CakePHP does not believe in the idea of having in-app environments
43 | *
44 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
45 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
46 | * @return void
47 | */
48 | protected function beforeExecute(InputInterface $input, OutputInterface $output): void
49 | {
50 | $this->setInput($input);
51 | $this->addOption('--environment', '-e', InputArgument::OPTIONAL);
52 | $input->setOption('environment', 'default');
53 | }
54 |
55 | /**
56 | * A callback method that is used to inject the PDO object created from phinx into
57 | * the CakePHP connection. This is needed in case the user decides to use tables
58 | * from the ORM and executes queries.
59 | *
60 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
61 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
62 | * @return void
63 | */
64 | public function bootstrap(InputInterface $input, OutputInterface $output): void
65 | {
66 | parent::bootstrap($input, $output);
67 | $name = $this->getConnectionName($input);
68 | $this->connection = $name;
69 | ConnectionManager::alias($name, 'default');
70 | /** @var \Cake\Database\Connection $connection */
71 | $connection = ConnectionManager::get($name);
72 |
73 | $manager = $this->getManager();
74 |
75 | if (!$manager instanceof CakeManager) {
76 | $this->setManager(new CakeManager($this->getConfig(), $input, $output));
77 | }
78 | /** @var \Phinx\Migration\Manager\Environment $env */
79 | /** @psalm-suppress PossiblyNullReference */
80 | $env = $this->getManager()->getEnvironment('default');
81 | $adapter = $env->getAdapter();
82 | if (!$adapter instanceof CakeAdapter) {
83 | $env->setAdapter(new CakeAdapter($adapter, $connection));
84 | }
85 | }
86 |
87 | /**
88 | * Sets the input object that should be used for the command class. This object
89 | * is used to inspect the extra options that are needed for CakePHP apps.
90 | *
91 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
92 | * @return void
93 | */
94 | public function setInput(InputInterface $input): void
95 | {
96 | $this->input = $input;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/src/Command/Phinx/Create.php:
--------------------------------------------------------------------------------
1 | setName('create')
44 | ->setDescription('Create a new migration')
45 | ->addArgument('name', InputArgument::REQUIRED, 'What is the name of the migration?')
46 | ->setHelp(sprintf(
47 | '%sCreates a new database migration file%s',
48 | PHP_EOL,
49 | PHP_EOL,
50 | ))
51 | ->addOption('plugin', 'p', InputOption::VALUE_REQUIRED, 'The plugin the file should be created for')
52 | ->addOption('connection', 'c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
53 | ->addOption('source', 's', InputOption::VALUE_REQUIRED, 'The folder where migrations are in')
54 | ->addOption('template', 't', InputOption::VALUE_REQUIRED, 'Use an alternative template')
55 | ->addOption(
56 | 'class',
57 | 'l',
58 | InputOption::VALUE_REQUIRED,
59 | 'Use a class implementing "' . parent::CREATION_INTERFACE . '" to generate the template',
60 | )
61 | ->addOption(
62 | 'path',
63 | null,
64 | InputOption::VALUE_REQUIRED,
65 | 'Specify the path in which to create this migration',
66 | );
67 | }
68 |
69 | /**
70 | * Configures Phinx Create command CLI options that are unused by this extended
71 | * command.
72 | *
73 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
74 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
75 | * @return void
76 | */
77 | protected function beforeExecute(InputInterface $input, OutputInterface $output): void
78 | {
79 | // Set up as a dummy, its value is not going to be used, as a custom
80 | // template will always be set.
81 | $this->addOption('style', null, InputOption::VALUE_OPTIONAL);
82 |
83 | $this->parentBeforeExecute($input, $output);
84 | }
85 |
86 | /**
87 | * Overrides the action execute method in order to vanish the idea of environments
88 | * from phinx. CakePHP does not believe in the idea of having in-app environments
89 | *
90 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
91 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
92 | * @return int
93 | */
94 | protected function execute(InputInterface $input, OutputInterface $output): int
95 | {
96 | $result = $this->parentExecute($input, $output);
97 |
98 | $output->writeln('renaming file in CamelCase to follow CakePHP convention...');
99 |
100 | $migrationPaths = $this->getConfig()->getMigrationPaths();
101 | $migrationPath = array_pop($migrationPaths) . DS;
102 | /** @var string $name */
103 | $name = $input->getArgument('name');
104 | // phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable
105 | [$phinxTimestamp, $phinxName] = explode('_', Util::mapClassNameToFileName($name), 2);
106 | $migrationFilename = glob($migrationPath . '*' . $phinxName);
107 |
108 | if (!$migrationFilename) {
109 | $output->writeln('An error occurred while renaming file');
110 | } else {
111 | $migrationFilename = $migrationFilename[0];
112 | $path = dirname($migrationFilename) . DS;
113 | $name = Inflector::camelize($name);
114 | $newPath = $path . Util::getCurrentTimestamp() . '_' . $name . '.php';
115 |
116 | $output->writeln('renaming file in CamelCase to follow CakePHP convention...');
117 | if (rename($migrationFilename, $newPath)) {
118 | $output->writeln(sprintf('File successfully renamed to %s', $newPath));
119 | } else {
120 | $output->writeln(sprintf('An error occurred while renaming file to %s', $newPath));
121 | }
122 | }
123 |
124 | return $result;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Command/Phinx/Dump.php:
--------------------------------------------------------------------------------
1 | setName('dump')
52 | ->setDescription('Dumps the current schema of the database to be used while baking a diff')
53 | ->setHelp(sprintf(
54 | '%sDumps the current schema of the database to be used while baking a diff%s',
55 | PHP_EOL,
56 | PHP_EOL,
57 | ))
58 | ->addOption('plugin', 'p', InputOption::VALUE_REQUIRED, 'The plugin the file should be created for')
59 | ->addOption('connection', 'c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
60 | ->addOption('source', 's', InputOption::VALUE_REQUIRED, 'The folder where migrations are in');
61 | }
62 |
63 | /**
64 | * @param \Symfony\Component\Console\Output\OutputInterface $output The output object.
65 | * @return \Symfony\Component\Console\Output\OutputInterface|null
66 | */
67 | public function output(?OutputInterface $output = null): ?OutputInterface
68 | {
69 | if ($output !== null) {
70 | $this->output = $output;
71 | }
72 |
73 | return $this->output;
74 | }
75 |
76 | /**
77 | * Dumps the current schema to be used when baking a diff
78 | *
79 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
80 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
81 | * @return int
82 | */
83 | protected function execute(InputInterface $input, OutputInterface $output): int
84 | {
85 | $this->setInput($input);
86 | $this->bootstrap($input, $output);
87 | $this->output($output);
88 |
89 | $path = $this->getOperationsPath($input);
90 | $connectionName = $input->getOption('connection') ?: 'default';
91 | assert(is_string($connectionName), 'Connection name must be a string');
92 | $connection = ConnectionManager::get($connectionName);
93 | assert($connection instanceof Connection);
94 | $collection = $connection->getSchemaCollection();
95 |
96 | $options = [
97 | 'require-table' => false,
98 | 'plugin' => $this->getPlugin($input),
99 | ];
100 | $finder = new TableFinder($connectionName);
101 | $tables = $finder->getTablesToBake($collection, $options);
102 |
103 | $dump = [];
104 | if ($tables) {
105 | foreach ($tables as $table) {
106 | $schema = $collection->describe($table);
107 | $dump[$table] = $schema;
108 | }
109 | }
110 |
111 | $filePath = $path . DS . 'schema-dump-' . $connectionName . '.lock';
112 | $output->writeln(sprintf('Writing dump file `%s`...', $filePath));
113 | if (file_put_contents($filePath, serialize($dump))) {
114 | $output->writeln(sprintf('Dump file `%s` was successfully written', $filePath));
115 |
116 | return BaseCommand::CODE_SUCCESS;
117 | }
118 |
119 | $output->writeln(sprintf(
120 | 'An error occurred while writing dump file `%s`',
121 | $filePath,
122 | ));
123 |
124 | return BaseCommand::CODE_ERROR;
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Command/Phinx/Migrate.php:
--------------------------------------------------------------------------------
1 |
34 | */
35 | use EventDispatcherTrait;
36 |
37 | /**
38 | * Configures the current command.
39 | *
40 | * @return void
41 | */
42 | protected function configure(): void
43 | {
44 | $this->setName('migrate')
45 | ->setDescription('Migrate the database')
46 | ->setHelp('runs all available migrations, optionally up to a specific version')
47 | ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to migrate to')
48 | ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to')
49 | ->addOption(
50 | '--dry-run',
51 | '-x',
52 | InputOption::VALUE_NONE,
53 | 'Dump queries to standard output instead of executing it',
54 | )
55 | ->addOption(
56 | '--plugin',
57 | '-p',
58 | InputOption::VALUE_REQUIRED,
59 | 'The plugin containing the migrations',
60 | )
61 | ->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
62 | ->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in')
63 | ->addOption(
64 | '--fake',
65 | null,
66 | InputOption::VALUE_NONE,
67 | "Mark any migrations selected as run, but don't actually execute them",
68 | )
69 | ->addOption(
70 | '--no-lock',
71 | null,
72 | InputOption::VALUE_NONE,
73 | 'If present, no lock file will be generated after migrating',
74 | );
75 | }
76 |
77 | /**
78 | * Overrides the action execute method in order to vanish the idea of environments
79 | * from phinx. CakePHP does not believe in the idea of having in-app environments
80 | *
81 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
82 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
83 | * @return int
84 | */
85 | protected function execute(InputInterface $input, OutputInterface $output): int
86 | {
87 | $event = $this->dispatchEvent('Migration.beforeMigrate');
88 | if ($event->isStopped()) {
89 | return $event->getResult() ? BaseCommand::CODE_SUCCESS : BaseCommand::CODE_ERROR;
90 | }
91 | $result = $this->parentExecute($input, $output);
92 | $this->dispatchEvent('Migration.afterMigrate');
93 |
94 | return $result;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Command/Phinx/Rollback.php:
--------------------------------------------------------------------------------
1 |
34 | */
35 | use EventDispatcherTrait;
36 |
37 | /**
38 | * Configures the current command.
39 | *
40 | * @return void
41 | */
42 | protected function configure(): void
43 | {
44 | $this->setName('rollback')
45 | ->setDescription('Rollback the last or to a specific migration')
46 | ->setHelp('reverts the last migration, or optionally up to a specific version')
47 | ->addOption('--target', '-t', InputOption::VALUE_REQUIRED, 'The version number to rollback to')
48 | ->addOption('--date', '-d', InputOption::VALUE_REQUIRED, 'The date to migrate to')
49 | ->addOption('--plugin', '-p', InputOption::VALUE_REQUIRED, 'The plugin containing the migrations')
50 | ->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
51 | ->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in')
52 | ->addOption('--force', '-f', InputOption::VALUE_NONE, 'Force rollback to ignore breakpoints')
53 | ->addOption(
54 | '--dry-run',
55 | '-x',
56 | InputOption::VALUE_NONE,
57 | 'Dump queries to standard output instead of executing it',
58 | )
59 | ->addOption(
60 | '--fake',
61 | null,
62 | InputOption::VALUE_NONE,
63 | "Mark any rollbacks selected as run, but don't actually execute them",
64 | )
65 | ->addOption(
66 | '--no-lock',
67 | null,
68 | InputOption::VALUE_NONE,
69 | 'Whether a lock file should be generated after rolling back',
70 | );
71 | }
72 |
73 | /**
74 | * Overrides the action execute method in order to vanish the idea of environments
75 | * from phinx. CakePHP does not believe in the idea of having in-app environments
76 | *
77 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
78 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
79 | * @return int
80 | */
81 | protected function execute(InputInterface $input, OutputInterface $output): int
82 | {
83 | $event = $this->dispatchEvent('Migration.beforeRollback');
84 | if ($event->isStopped()) {
85 | return $event->getResult() ? BaseCommand::CODE_SUCCESS : BaseCommand::CODE_ERROR;
86 | }
87 | $result = $this->parentExecute($input, $output);
88 | $this->dispatchEvent('Migration.afterRollback');
89 |
90 | return $result;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Command/Phinx/Seed.php:
--------------------------------------------------------------------------------
1 |
34 | */
35 | use EventDispatcherTrait;
36 |
37 | /**
38 | * Configures the current command.
39 | *
40 | * @return void
41 | */
42 | protected function configure(): void
43 | {
44 | $this->setName('seed')
45 | ->setDescription('Seed the database with data')
46 | ->setHelp('runs all available migrations, optionally up to a specific version')
47 | ->addOption(
48 | '--seed',
49 | null,
50 | InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY,
51 | 'What is the name of the seeder?',
52 | )
53 | ->addOption('--plugin', '-p', InputOption::VALUE_REQUIRED, 'The plugin containing the migrations')
54 | ->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
55 | ->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in');
56 | }
57 |
58 | /**
59 | * Overrides the action execute method in order to vanish the idea of environments
60 | * from phinx. CakePHP does not believe in the idea of having in-app environments
61 | *
62 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
63 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
64 | * @return int
65 | */
66 | protected function execute(InputInterface $input, OutputInterface $output): int
67 | {
68 | $event = $this->dispatchEvent('Migration.beforeSeed');
69 | if ($event->isStopped()) {
70 | return $event->getResult() ? BaseCommand::CODE_SUCCESS : BaseCommand::CODE_ERROR;
71 | }
72 |
73 | $seed = $input->getOption('seed');
74 | if ($seed && !is_array($seed)) {
75 | $input->setOption('seed', [$seed]);
76 | }
77 |
78 | $this->setInput($input);
79 | $this->bootstrap($input, $output);
80 | $this->getManager()->setInput($input);
81 | $result = $this->parentExecute($input, $output);
82 | $this->dispatchEvent('Migration.afterSeed');
83 |
84 | return $result;
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/src/Command/Phinx/Status.php:
--------------------------------------------------------------------------------
1 | setName('status')
39 | ->setDescription('Show migration status')
40 | ->addOption(
41 | '--format',
42 | '-f',
43 | InputOption::VALUE_REQUIRED,
44 | 'The output format: text or json. Defaults to text.',
45 | )
46 | ->setHelp('prints a list of all migrations, along with their current status')
47 | ->addOption('--plugin', '-p', InputOption::VALUE_REQUIRED, 'The plugin containing the migrations')
48 | ->addOption('--connection', '-c', InputOption::VALUE_REQUIRED, 'The datasource connection to use')
49 | ->addOption('--source', '-s', InputOption::VALUE_REQUIRED, 'The folder where migrations are in');
50 | }
51 |
52 | /**
53 | * Show the migration status.
54 | *
55 | * @param \Symfony\Component\Console\Input\InputInterface $input the input object
56 | * @param \Symfony\Component\Console\Output\OutputInterface $output the output object
57 | * @return int
58 | */
59 | protected function execute(InputInterface $input, OutputInterface $output): int
60 | {
61 | $this->beforeExecute($input, $output);
62 | $this->bootstrap($input, $output);
63 |
64 | /** @var string|null $environment */
65 | $environment = $input->getOption('environment');
66 | /** @var string|null $format */
67 | $format = $input->getOption('format');
68 |
69 | if ($environment === null) {
70 | $environment = $this->getManager()->getConfig()->getDefaultEnvironment();
71 | $output->writeln('warning no environment specified, defaulting to: ' . $environment);
72 | } else {
73 | $output->writeln('using environment ' . $environment);
74 | }
75 | if ($format !== null) {
76 | $output->writeln('using format ' . $format);
77 | }
78 |
79 | $migrations = $this->getManager()->printStatus($environment, $format);
80 |
81 | switch ($format) {
82 | case 'json':
83 | $flags = 0;
84 | if ($input->getOption('verbose')) {
85 | $flags = JSON_PRETTY_PRINT;
86 | }
87 | $migrationString = (string)json_encode($migrations, $flags);
88 | $this->getManager()->getOutput()->writeln($migrationString);
89 | break;
90 | default:
91 | $this->display($migrations);
92 | break;
93 | }
94 |
95 | return BaseCommand::CODE_SUCCESS;
96 | }
97 |
98 | /**
99 | * Will output the status of the migrations
100 | *
101 | * @param array> $migrations Migrations array.
102 | * @return void
103 | */
104 | protected function display(array $migrations): void
105 | {
106 | $output = $this->getManager()->getOutput();
107 |
108 | if ($migrations) {
109 | $output->writeln('');
110 | $output->writeln(' Status Migration ID Migration Name ');
111 | $output->writeln('-----------------------------------------');
112 |
113 | foreach ($migrations as $migration) {
114 | $status = $migration['status'] === 'up' ? ' up ' : ' down ';
115 | $maxNameLength = $this->getManager()->maxNameLength;
116 | $name = $migration['name'] ?
117 | ' ' . str_pad($migration['name'], $maxNameLength, ' ') . ' ' :
118 | ' ** MISSING **';
119 |
120 | $missingComment = '';
121 | if (!empty($migration['missing'])) {
122 | $missingComment = ' ** MISSING **';
123 | }
124 |
125 | $output->writeln(
126 | $status .
127 | sprintf(' %14.0f ', $migration['id']) .
128 | $name .
129 | $missingComment,
130 | );
131 | }
132 |
133 | $output->writeln('');
134 | } else {
135 | $msg = 'There are no available migrations. Try creating one using the create command.';
136 | $output->writeln('');
137 | $output->writeln($msg);
138 | $output->writeln('');
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Command/SeedCommand.php:
--------------------------------------------------------------------------------
1 |
32 | */
33 | use EventDispatcherTrait;
34 |
35 | /**
36 | * The default name added to the application command list
37 | *
38 | * @return string
39 | */
40 | public static function defaultName(): string
41 | {
42 | return 'migrations seed';
43 | }
44 |
45 | /**
46 | * Configure the option parser
47 | *
48 | * @param \Cake\Console\ConsoleOptionParser $parser The option parser to configure
49 | * @return \Cake\Console\ConsoleOptionParser
50 | */
51 | public function buildOptionParser(ConsoleOptionParser $parser): ConsoleOptionParser
52 | {
53 | $parser->setDescription([
54 | 'Seed the database with data',
55 | '',
56 | 'Runs a seeder script that can populate the database with data, or run mutations',
57 | '',
58 | 'migrations seed --connection secondary --seed UserSeeder',
59 | '',
60 | 'The `--seed` option can be supplied multiple times to run more than one seeder',
61 | ])->addOption('plugin', [
62 | 'short' => 'p',
63 | 'help' => 'The plugin to run seeders in',
64 | ])->addOption('connection', [
65 | 'short' => 'c',
66 | 'help' => 'The datasource connection to use',
67 | 'default' => 'default',
68 | ])->addOption('source', [
69 | 'short' => 's',
70 | 'default' => ConfigInterface::DEFAULT_SEED_FOLDER,
71 | 'help' => 'The folder where your seeders are.',
72 | ])->addOption('seed', [
73 | 'help' => 'The name of the seeder that you want to run.',
74 | 'multiple' => true,
75 | ]);
76 |
77 | return $parser;
78 | }
79 |
80 | /**
81 | * Execute the command.
82 | *
83 | * @param \Cake\Console\Arguments $args The command arguments.
84 | * @param \Cake\Console\ConsoleIo $io The console io
85 | * @return int|null The exit code or null for success
86 | */
87 | public function execute(Arguments $args, ConsoleIo $io): ?int
88 | {
89 | $event = $this->dispatchEvent('Migration.beforeSeed');
90 | if ($event->isStopped()) {
91 | return $event->getResult() ? self::CODE_SUCCESS : self::CODE_ERROR;
92 | }
93 | $result = $this->executeSeeds($args, $io);
94 | $this->dispatchEvent('Migration.afterSeed');
95 |
96 | return $result;
97 | }
98 |
99 | /**
100 | * Execute seeders based on console inputs.
101 | *
102 | * @param \Cake\Console\Arguments $args The command arguments.
103 | * @param \Cake\Console\ConsoleIo $io The console io
104 | * @return int|null The exit code or null for success
105 | */
106 | protected function executeSeeds(Arguments $args, ConsoleIo $io): ?int
107 | {
108 | $factory = new ManagerFactory([
109 | 'plugin' => $args->getOption('plugin'),
110 | 'source' => $args->getOption('source'),
111 | 'connection' => $args->getOption('connection'),
112 | ]);
113 | $manager = $factory->createManager($io);
114 | $config = $manager->getConfig();
115 | if (version_compare(Configure::version(), '5.2.0', '>=')) {
116 | $seeds = (array)$args->getArrayOption('seed');
117 | } else {
118 | $seeds = (array)$args->getMultipleOption('seed');
119 | }
120 |
121 | $versionOrder = $config->getVersionOrder();
122 | $io->out('using connection ' . (string)$args->getOption('connection'));
123 | $io->out('using paths ' . $config->getMigrationPath());
124 | $io->out('ordering by ' . $versionOrder . ' time');
125 |
126 | $start = microtime(true);
127 | if (!$seeds) {
128 | // run all the seed(ers)
129 | $manager->seed();
130 | } else {
131 | // run seed(ers) specified in a comma-separated list of classes
132 | foreach ($seeds as $seed) {
133 | $manager->seed(trim($seed));
134 | }
135 | }
136 | $end = microtime(true);
137 |
138 | $io->out('All Done. Took ' . sprintf('%.4fs', $end - $start) . '');
139 |
140 | return self::CODE_SUCCESS;
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/Command/SnapshotTrait.php:
--------------------------------------------------------------------------------
1 | markSnapshotApplied($path, $args, $io);
35 |
36 | if (!$args->getOption('no-lock')) {
37 | $this->refreshDump($args, $io);
38 | }
39 | }
40 |
41 | return $createFile;
42 | }
43 |
44 | /**
45 | * @internal
46 | * @return bool Whether the builtin backend is active.
47 | */
48 | protected function useBuiltinBackend(): bool
49 | {
50 | return Configure::read('Migrations.backend', 'builtin') === 'builtin';
51 | }
52 |
53 | /**
54 | * Will mark a snapshot created, the snapshot being identified by its
55 | * full file path.
56 | *
57 | * @param string $path Path to the newly created snapshot
58 | * @param \Cake\Console\Arguments $args The command arguments.
59 | * @param \Cake\Console\ConsoleIo $io The console io
60 | * @return void
61 | */
62 | protected function markSnapshotApplied(string $path, Arguments $args, ConsoleIo $io): void
63 | {
64 | $fileName = pathinfo($path, PATHINFO_FILENAME);
65 | [$version, ] = explode('_', $fileName, 2);
66 |
67 | $newArgs = [];
68 | $newArgs[] = '-t';
69 | $newArgs[] = $version;
70 | $newArgs[] = '-o';
71 |
72 | $newArgs = array_merge($newArgs, $this->parseOptions($args));
73 |
74 | $io->out('Marking the migration ' . $fileName . ' as migrated...');
75 | if ($this->useBuiltinBackend()) {
76 | $this->executeCommand(MarkMigratedCommand::class, $newArgs, $io);
77 | } else {
78 | $this->executeCommand(MigrationsMarkMigratedCommand::class, $newArgs, $io);
79 | }
80 | }
81 |
82 | /**
83 | * After a file has been successfully created, we refresh the dump of the database
84 | * to be able to generate a new diff afterward.
85 | *
86 | * @param \Cake\Console\Arguments $args The command arguments.
87 | * @param \Cake\Console\ConsoleIo $io The console io
88 | * @return void
89 | */
90 | protected function refreshDump(Arguments $args, ConsoleIo $io): void
91 | {
92 | $newArgs = $this->parseOptions($args);
93 |
94 | $io->out('Creating a dump of the new database state...');
95 | if ($this->useBuiltinBackend()) {
96 | $this->executeCommand(DumpCommand::class, $newArgs, $io);
97 | } else {
98 | $this->executeCommand(MigrationsDumpCommand::class, $newArgs, $io);
99 | }
100 | }
101 |
102 | /**
103 | * Will parse 'connection', 'plugin' and 'source' options into a new Array
104 | *
105 | * @param \Cake\Console\Arguments $args The command arguments.
106 | * @return array Array containing the short for the option followed by its value
107 | */
108 | protected function parseOptions(Arguments $args): array
109 | {
110 | $newArgs = [];
111 | if ($args->getOption('connection')) {
112 | $newArgs[] = '-c';
113 | $newArgs[] = $args->getOption('connection');
114 | }
115 |
116 | if ($args->getOption('plugin')) {
117 | $newArgs[] = '-p';
118 | $newArgs[] = $args->getOption('plugin');
119 | }
120 |
121 | if ($args->getOption('source')) {
122 | $newArgs[] = '-s';
123 | $newArgs[] = $args->getOption('source');
124 | }
125 |
126 | return $newArgs;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Command/StatusCommand.php:
--------------------------------------------------------------------------------
1 | setDescription([
62 | 'The status command prints a list of all migrations, along with their current status',
63 | '',
64 | 'migrations status -c secondary',
65 | 'migrations status -c secondary -f json',
66 | ])->addOption('plugin', [
67 | 'short' => 'p',
68 | 'help' => 'The plugin to run migrations for',
69 | ])->addOption('connection', [
70 | 'short' => 'c',
71 | 'help' => 'The datasource connection to use',
72 | 'default' => 'default',
73 | ])->addOption('source', [
74 | 'short' => 's',
75 | 'help' => 'The folder under src/Config that migrations are in',
76 | 'default' => ConfigInterface::DEFAULT_MIGRATION_FOLDER,
77 | ])->addOption('format', [
78 | 'short' => 'f',
79 | 'help' => 'The output format: text or json. Defaults to text.',
80 | 'choices' => ['text', 'json'],
81 | 'default' => 'text',
82 | ]);
83 |
84 | return $parser;
85 | }
86 |
87 | /**
88 | * Execute the command.
89 | *
90 | * @param \Cake\Console\Arguments $args The command arguments.
91 | * @param \Cake\Console\ConsoleIo $io The console io
92 | * @return int|null The exit code or null for success
93 | */
94 | public function execute(Arguments $args, ConsoleIo $io): ?int
95 | {
96 | /** @var string|null $format */
97 | $format = $args->getOption('format');
98 |
99 | $factory = new ManagerFactory([
100 | 'plugin' => $args->getOption('plugin'),
101 | 'source' => $args->getOption('source'),
102 | 'connection' => $args->getOption('connection'),
103 | 'dry-run' => $args->getOption('dry-run'),
104 | ]);
105 | $manager = $factory->createManager($io);
106 | $migrations = $manager->printStatus($format);
107 |
108 | switch ($format) {
109 | case 'json':
110 | $flags = 0;
111 | if ($args->getOption('verbose')) {
112 | $flags = JSON_PRETTY_PRINT;
113 | }
114 | $migrationString = (string)json_encode($migrations, $flags);
115 | $io->out($migrationString);
116 | break;
117 | default:
118 | $this->display($migrations, $io);
119 | break;
120 | }
121 |
122 | return Command::CODE_SUCCESS;
123 | }
124 |
125 | /**
126 | * Print migration status to stdout.
127 | *
128 | * @param array $migrations
129 | * @param \Cake\Console\ConsoleIo $io The console io
130 | * @return void
131 | */
132 | protected function display(array $migrations, ConsoleIo $io): void
133 | {
134 | if ($migrations) {
135 | $rows = [];
136 | $rows[] = ['Status', 'Migration ID', 'Migration Name'];
137 |
138 | foreach ($migrations as $migration) {
139 | $status = $migration['status'] === 'up' ? 'up' : 'down';
140 | $name = $migration['name'] ?
141 | '' . $migration['name'] . '' :
142 | '** MISSING **';
143 |
144 | $missingComment = '';
145 | if (!empty($migration['missing'])) {
146 | $missingComment = '** MISSING **';
147 | }
148 | $rows[] = [$status, sprintf('%14.0f ', $migration['id']), $name . $missingComment];
149 | }
150 | $io->helper('table')->output($rows);
151 | } else {
152 | $msg = 'There are no available migrations. Try creating one using the create command.';
153 | $io->err('');
154 | $io->err($msg);
155 | $io->err('');
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/src/Config/ConfigInterface.php:
--------------------------------------------------------------------------------
1 |
17 | */
18 | interface ConfigInterface extends ArrayAccess
19 | {
20 | public const DEFAULT_MIGRATION_FOLDER = 'Migrations';
21 | public const DEFAULT_SEED_FOLDER = 'Seeds';
22 |
23 | /**
24 | * Returns the configuration for the current environment.
25 | *
26 | * This method returns null
if the specified environment
27 | * doesn't exist.
28 | *
29 | * @return array|null
30 | */
31 | public function getEnvironment(): ?array;
32 |
33 | /**
34 | * Gets the path to search for migration files.
35 | *
36 | * @return string
37 | */
38 | public function getMigrationPath(): string;
39 |
40 | /**
41 | * Gets the path to search for seed files.
42 | *
43 | * @return string
44 | */
45 | public function getSeedPath(): string;
46 |
47 | /**
48 | * Get the connection name
49 | *
50 | * @return string|false
51 | */
52 | public function getConnection(): string|false;
53 |
54 | /**
55 | * Get the template file name.
56 | *
57 | * @return string|false
58 | */
59 | public function getTemplateFile(): string|false;
60 |
61 | /**
62 | * Get the template class name.
63 | *
64 | * @return string|false
65 | */
66 | public function getTemplateClass(): string|false;
67 |
68 | /**
69 | * Get the template style to use, either change or up_down.
70 | *
71 | * @return string
72 | */
73 | public function getTemplateStyle(): string;
74 |
75 | /**
76 | * Get the version order.
77 | *
78 | * @return string
79 | */
80 | public function getVersionOrder(): string;
81 |
82 | /**
83 | * Is version order creation time?
84 | *
85 | * @return bool
86 | */
87 | public function isVersionOrderCreationTime(): bool;
88 |
89 | /**
90 | * Gets the base class name for migrations.
91 | *
92 | * @param bool $dropNamespace Return the base migration class name without the namespace.
93 | * @return string
94 | */
95 | public function getMigrationBaseClassName(bool $dropNamespace = true): string;
96 |
97 | /**
98 | * Gets the base class name for seeders.
99 | *
100 | * @param bool $dropNamespace Return the base seeder class name without the namespace.
101 | * @return string
102 | */
103 | public function getSeedBaseClassName(bool $dropNamespace = true): string;
104 |
105 | /**
106 | * Get the seeder template file name or null if not set.
107 | *
108 | * @return string|null
109 | */
110 | public function getSeedTemplateFile(): ?string;
111 | }
112 |
--------------------------------------------------------------------------------
/src/Db/Action/Action.php:
--------------------------------------------------------------------------------
1 | table = $table;
28 | }
29 |
30 | /**
31 | * The table this action will be applied to
32 | *
33 | * @return \Migrations\Db\Table\Table
34 | */
35 | public function getTable(): Table
36 | {
37 | return $this->table;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Db/Action/AddColumn.php:
--------------------------------------------------------------------------------
1 | column = $column;
34 | }
35 |
36 | /**
37 | * Returns a new AddColumn object after assembling the given commands
38 | *
39 | * @param \Migrations\Db\Table\Table $table The table to add the column to
40 | * @param string $columnName The column name
41 | * @param string|\Migrations\Db\Literal $type The column type
42 | * @param array $options The column options
43 | * @return self
44 | */
45 | public static function build(Table $table, string $columnName, string|Literal $type, array $options = []): self
46 | {
47 | $column = new Column();
48 | $column->setName($columnName);
49 | $column->setType($type);
50 | $column->setOptions($options); // map options to column methods
51 |
52 | return new AddColumn($table, $column);
53 | }
54 |
55 | /**
56 | * Returns the column to be added
57 | *
58 | * @return \Migrations\Db\Table\Column
59 | */
60 | public function getColumn(): Column
61 | {
62 | return $this->column;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/Db/Action/AddForeignKey.php:
--------------------------------------------------------------------------------
1 | foreignKey = $fk;
33 | }
34 |
35 | /**
36 | * Creates a new AddForeignKey object after building the foreign key with
37 | * the passed attributes
38 | *
39 | * @param \Migrations\Db\Table\Table $table The table object to add the foreign key to
40 | * @param string|string[] $columns The columns for the foreign key
41 | * @param \Migrations\Db\Table\Table|string $referencedTable The table the foreign key references
42 | * @param string|string[] $referencedColumns The columns in the referenced table
43 | * @param array $options Extra options for the foreign key
44 | * @param string|null $name The name of the foreign key
45 | * @return self
46 | */
47 | public static function build(Table $table, string|array $columns, Table|string $referencedTable, string|array $referencedColumns = ['id'], array $options = [], ?string $name = null): self
48 | {
49 | if (is_string($referencedColumns)) {
50 | $referencedColumns = [$referencedColumns]; // str to array
51 | }
52 |
53 | if (is_string($referencedTable)) {
54 | $referencedTable = new Table($referencedTable);
55 | }
56 |
57 | $fk = new ForeignKey();
58 | $fk->setReferencedTable($referencedTable)
59 | ->setColumns($columns)
60 | ->setReferencedColumns($referencedColumns)
61 | ->setOptions($options);
62 |
63 | if ($name !== null) {
64 | $fk->setName($name);
65 | }
66 |
67 | return new AddForeignKey($table, $fk);
68 | }
69 |
70 | /**
71 | * Returns the foreign key to be added
72 | *
73 | * @return \Migrations\Db\Table\ForeignKey
74 | */
75 | public function getForeignKey(): ForeignKey
76 | {
77 | return $this->foreignKey;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Db/Action/AddIndex.php:
--------------------------------------------------------------------------------
1 | index = $index;
33 | }
34 |
35 | /**
36 | * Creates a new AddIndex object after building the index object with the
37 | * provided arguments
38 | *
39 | * @param \Migrations\Db\Table\Table $table The table to add the index to
40 | * @param string|string[]|\Migrations\Db\Table\Index $columns The columns to index
41 | * @param array $options Additional options for the index creation
42 | * @return self
43 | */
44 | public static function build(Table $table, string|array|Index $columns, array $options = []): self
45 | {
46 | // create a new index object if strings or an array of strings were supplied
47 | if (!($columns instanceof Index)) {
48 | $index = new Index();
49 |
50 | $index->setColumns($columns);
51 | $index->setOptions($options);
52 | } else {
53 | $index = $columns;
54 | }
55 |
56 | return new AddIndex($table, $index);
57 | }
58 |
59 | /**
60 | * Returns the index to be added
61 | *
62 | * @return \Migrations\Db\Table\Index
63 | */
64 | public function getIndex(): Index
65 | {
66 | return $this->index;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Db/Action/ChangeColumn.php:
--------------------------------------------------------------------------------
1 | columnName = $columnName;
42 | $this->column = $column;
43 |
44 | // if the name was omitted use the existing column name
45 | if ($column->getName() === null || strlen((string)$column->getName()) === 0) {
46 | $column->setName($columnName);
47 | }
48 | }
49 |
50 | /**
51 | * Creates a new ChangeColumn object after building the column definition
52 | * out of the provided arguments
53 | *
54 | * @param \Migrations\Db\Table\Table $table The table to alter
55 | * @param string $columnName The name of the column to change
56 | * @param string|\Migrations\Db\Literal $type The type of the column
57 | * @param array $options Additional options for the column
58 | * @return self
59 | */
60 | public static function build(Table $table, string $columnName, string|Literal $type, array $options = []): self
61 | {
62 | $column = new Column();
63 | $column->setName($columnName);
64 | $column->setType($type);
65 | $column->setOptions($options); // map options to column methods
66 |
67 | return new ChangeColumn($table, $columnName, $column);
68 | }
69 |
70 | /**
71 | * Returns the name of the column to change
72 | *
73 | * @return string
74 | */
75 | public function getColumnName(): string
76 | {
77 | return $this->columnName;
78 | }
79 |
80 | /**
81 | * Returns the column definition
82 | *
83 | * @return \Migrations\Db\Table\Column
84 | */
85 | public function getColumn(): Column
86 | {
87 | return $this->column;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Db/Action/ChangeComment.php:
--------------------------------------------------------------------------------
1 | newComment = $newComment;
32 | }
33 |
34 | /**
35 | * Return the new comment for the table
36 | *
37 | * @return string|null
38 | */
39 | public function getNewComment(): ?string
40 | {
41 | return $this->newComment;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Db/Action/ChangePrimaryKey.php:
--------------------------------------------------------------------------------
1 | newColumns = $newColumns;
32 | }
33 |
34 | /**
35 | * Return the new columns for the primary key
36 | *
37 | * @return string|string[]|null
38 | */
39 | public function getNewColumns(): string|array|null
40 | {
41 | return $this->newColumns;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Db/Action/CreateTable.php:
--------------------------------------------------------------------------------
1 | foreignKey = $foreignKey;
33 | }
34 |
35 | /**
36 | * Creates a new DropForeignKey object after building the ForeignKey
37 | * definition out of the passed arguments.
38 | *
39 | * @param \Migrations\Db\Table\Table $table The table to delete the foreign key from
40 | * @param string|string[] $columns The columns participating in the foreign key
41 | * @param string|null $constraint The constraint name
42 | * @return self
43 | */
44 | public static function build(Table $table, string|array $columns, ?string $constraint = null): self
45 | {
46 | if (is_string($columns)) {
47 | $columns = [$columns];
48 | }
49 |
50 | $foreignKey = new ForeignKey();
51 | $foreignKey->setColumns($columns);
52 |
53 | if ($constraint) {
54 | $foreignKey->setName($constraint);
55 | }
56 |
57 | return new DropForeignKey($table, $foreignKey);
58 | }
59 |
60 | /**
61 | * Returns the foreign key to remove
62 | *
63 | * @return \Migrations\Db\Table\ForeignKey
64 | */
65 | public function getForeignKey(): ForeignKey
66 | {
67 | return $this->foreignKey;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Db/Action/DropIndex.php:
--------------------------------------------------------------------------------
1 | index = $index;
33 | }
34 |
35 | /**
36 | * Creates a new DropIndex object after assembling the passed
37 | * arguments.
38 | *
39 | * @param \Migrations\Db\Table\Table $table The table where the index is
40 | * @param string[] $columns the indexed columns
41 | * @return self
42 | */
43 | public static function build(Table $table, array $columns = []): self
44 | {
45 | $index = new Index();
46 | $index->setColumns($columns);
47 |
48 | return new DropIndex($table, $index);
49 | }
50 |
51 | /**
52 | * Creates a new DropIndex when the name of the index to drop
53 | * is known.
54 | *
55 | * @param \Migrations\Db\Table\Table $table The table where the index is
56 | * @param string $name The name of the index
57 | * @return self
58 | */
59 | public static function buildFromName(Table $table, string $name): self
60 | {
61 | $index = new Index();
62 | $index->setName($name);
63 |
64 | return new DropIndex($table, $index);
65 | }
66 |
67 | /**
68 | * Returns the index to be dropped
69 | *
70 | * @return \Migrations\Db\Table\Index
71 | */
72 | public function getIndex(): Index
73 | {
74 | return $this->index;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/Db/Action/DropTable.php:
--------------------------------------------------------------------------------
1 | column = $column;
33 | }
34 |
35 | /**
36 | * Creates a new RemoveColumn object after assembling the
37 | * passed arguments.
38 | *
39 | * @param \Migrations\Db\Table\Table $table The table where the column is
40 | * @param string $columnName The name of the column to drop
41 | * @return self
42 | */
43 | public static function build(Table $table, string $columnName): self
44 | {
45 | $column = new Column();
46 | $column->setName($columnName);
47 |
48 | return new RemoveColumn($table, $column);
49 | }
50 |
51 | /**
52 | * Returns the column to be dropped
53 | *
54 | * @return \Migrations\Db\Table\Column
55 | */
56 | public function getColumn(): Column
57 | {
58 | return $this->column;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/Db/Action/RenameColumn.php:
--------------------------------------------------------------------------------
1 | newName = $newName;
41 | $this->column = $column;
42 | }
43 |
44 | /**
45 | * Creates a new RenameColumn object after building the passed
46 | * arguments
47 | *
48 | * @param \Migrations\Db\Table\Table $table The table where the column is
49 | * @param string $columnName The name of the column to be changed
50 | * @param string $newName The new name for the column
51 | * @return self
52 | */
53 | public static function build(Table $table, string $columnName, string $newName): self
54 | {
55 | $column = new Column();
56 | $column->setName($columnName);
57 |
58 | return new RenameColumn($table, $column, $newName);
59 | }
60 |
61 | /**
62 | * Returns the column to be changed
63 | *
64 | * @return \Migrations\Db\Table\Column
65 | */
66 | public function getColumn(): Column
67 | {
68 | return $this->column;
69 | }
70 |
71 | /**
72 | * Returns the new name for the column
73 | *
74 | * @return string
75 | */
76 | public function getNewName(): string
77 | {
78 | return $this->newName;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Db/Action/RenameTable.php:
--------------------------------------------------------------------------------
1 | newName = $newName;
32 | }
33 |
34 | /**
35 | * Return the new name for the table
36 | *
37 | * @return string
38 | */
39 | public function getNewName(): string
40 | {
41 | return $this->newName;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Db/Adapter/AdapterFactory.php:
--------------------------------------------------------------------------------
1 |
44 | * @phpstan-var array|\Closure>
45 | * @psalm-var array|\Closure>
46 | */
47 | protected array $adapters = [
48 | 'mysql' => MysqlAdapter::class,
49 | 'postgres' => PostgresAdapter::class,
50 | 'sqlite' => SqliteAdapter::class,
51 | 'sqlserver' => SqlserverAdapter::class,
52 | ];
53 |
54 | /**
55 | * Class map of adapters wrappers, indexed by name.
56 | *
57 | * @var array
58 | * @psalm-var array>
59 | */
60 | protected array $wrappers = [
61 | 'record' => RecordingAdapter::class,
62 | 'timed' => TimedOutputAdapter::class,
63 | ];
64 |
65 | /**
66 | * Register an adapter class with a given name.
67 | *
68 | * @param string $name Name
69 | * @param \Closure|string $class Class or factory method for the adapter.
70 | * @throws \RuntimeException
71 | * @return $this
72 | */
73 | public function registerAdapter(string $name, Closure|string $class)
74 | {
75 | if (
76 | !($class instanceof Closure || is_subclass_of($class, AdapterInterface::class))
77 | ) {
78 | throw new RuntimeException(sprintf(
79 | 'Adapter class "%s" must implement Migrations\\Db\\Adapter\\AdapterInterface',
80 | $class,
81 | ));
82 | }
83 | $this->adapters[$name] = $class;
84 |
85 | return $this;
86 | }
87 |
88 | /**
89 | * Get an adapter instance by name.
90 | *
91 | * @param string $name Name
92 | * @param array $options Options
93 | * @return \Migrations\Db\Adapter\AdapterInterface
94 | */
95 | public function getAdapter(string $name, array $options): AdapterInterface
96 | {
97 | if (empty($this->adapters[$name])) {
98 | throw new RuntimeException(sprintf(
99 | 'Adapter "%s" has not been registered',
100 | $name,
101 | ));
102 | }
103 | $classOrFactory = $this->adapters[$name];
104 | if ($classOrFactory instanceof Closure) {
105 | return $classOrFactory($options);
106 | }
107 |
108 | return new $classOrFactory($options);
109 | }
110 |
111 | /**
112 | * Add or replace a wrapper with a fully qualified class name.
113 | *
114 | * @param string $name Name
115 | * @param string $class Class
116 | * @throws \RuntimeException
117 | * @return $this
118 | */
119 | public function registerWrapper(string $name, string $class)
120 | {
121 | if (!is_subclass_of($class, WrapperInterface::class)) {
122 | throw new RuntimeException(sprintf(
123 | 'Wrapper class "%s" must implement Migrations\\Db\\Adapter\\WrapperInterface',
124 | $class,
125 | ));
126 | }
127 | $this->wrappers[$name] = $class;
128 |
129 | return $this;
130 | }
131 |
132 | /**
133 | * Get a wrapper class by name.
134 | *
135 | * @param string $name Name
136 | * @throws \RuntimeException
137 | * @return class-string<\Migrations\Db\Adapter\WrapperInterface>
138 | */
139 | protected function getWrapperClass(string $name): string
140 | {
141 | if (empty($this->wrappers[$name])) {
142 | throw new RuntimeException(sprintf(
143 | 'Wrapper "%s" has not been registered',
144 | $name,
145 | ));
146 | }
147 |
148 | return $this->wrappers[$name];
149 | }
150 |
151 | /**
152 | * Get a wrapper instance by name.
153 | *
154 | * @param string $name Name
155 | * @param \Migrations\Db\Adapter\AdapterInterface $adapter Adapter
156 | * @return \Migrations\Db\Adapter\WrapperInterface
157 | */
158 | public function getWrapper(string $name, AdapterInterface $adapter): WrapperInterface
159 | {
160 | $class = $this->getWrapperClass($name);
161 |
162 | return new $class($adapter);
163 | }
164 | }
165 |
--------------------------------------------------------------------------------
/src/Db/Adapter/DirectActionInterface.php:
--------------------------------------------------------------------------------
1 | commands[] = new CreateTable($table);
52 | }
53 |
54 | /**
55 | * @inheritDoc
56 | */
57 | public function executeActions(Table $table, array $actions): void
58 | {
59 | $this->commands = array_merge($this->commands, $actions);
60 | }
61 |
62 | /**
63 | * Gets an array of the recorded commands in reverse.
64 | *
65 | * @throws \Migrations\Migration\IrreversibleMigrationException if a command cannot be reversed.
66 | * @return \Migrations\Db\Plan\Intent
67 | */
68 | public function getInvertedCommands(): Intent
69 | {
70 | $inverted = new Intent();
71 |
72 | foreach (array_reverse($this->commands) as $command) {
73 | switch (true) {
74 | case $command instanceof CreateTable:
75 | /** @var \Migrations\Db\Action\CreateTable $command */
76 | $inverted->addAction(new DropTable($command->getTable()));
77 | break;
78 |
79 | case $command instanceof RenameTable:
80 | /** @var \Migrations\Db\Action\RenameTable $command */
81 | $inverted->addAction(new RenameTable(new Table($command->getNewName()), $command->getTable()->getName()));
82 | break;
83 |
84 | case $command instanceof AddColumn:
85 | /** @var \Migrations\Db\Action\AddColumn $command */
86 | $inverted->addAction(new RemoveColumn($command->getTable(), $command->getColumn()));
87 | break;
88 |
89 | case $command instanceof RenameColumn:
90 | /** @var \Migrations\Db\Action\RenameColumn $command */
91 | $column = clone $command->getColumn();
92 | $name = (string)$column->getName();
93 | $column->setName($command->getNewName());
94 | $inverted->addAction(new RenameColumn($command->getTable(), $column, $name));
95 | break;
96 |
97 | case $command instanceof AddIndex:
98 | /** @var \Migrations\Db\Action\AddIndex $command */
99 | $inverted->addAction(new DropIndex($command->getTable(), $command->getIndex()));
100 | break;
101 |
102 | case $command instanceof AddForeignKey:
103 | /** @var \Migrations\Db\Action\AddForeignKey $command */
104 | $inverted->addAction(new DropForeignKey($command->getTable(), $command->getForeignKey()));
105 | break;
106 |
107 | default:
108 | throw new IrreversibleMigrationException(sprintf(
109 | 'Cannot reverse a "%s" command',
110 | get_class($command),
111 | ));
112 | }
113 | }
114 |
115 | return $inverted;
116 | }
117 |
118 | /**
119 | * Execute the recorded commands in reverse.
120 | *
121 | * @return void
122 | */
123 | public function executeInvertedCommands(): void
124 | {
125 | $plan = new Plan($this->getInvertedCommands());
126 | $plan->executeInverse($this->getAdapter());
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/Db/Adapter/UnsupportedColumnTypeException.php:
--------------------------------------------------------------------------------
1 | alterParts = $alterParts;
36 | $this->postSteps = $postSteps;
37 | }
38 |
39 | /**
40 | * Adds another part to the single ALTER instruction
41 | *
42 | * @param string $part The SQL snipped to add as part of the ALTER instruction
43 | * @return void
44 | */
45 | public function addAlter(string $part): void
46 | {
47 | $this->alterParts[] = $part;
48 | }
49 |
50 | /**
51 | * Adds a SQL command to be executed after the ALTER instruction.
52 | * This method allows a callable, with will get an empty array as state
53 | * for the first time and will pass the return value of the callable to
54 | * the next callable, if present.
55 | *
56 | * This allows to keep a single state across callbacks.
57 | *
58 | * @param string|callable $sql The SQL to run after, or a callable to execute
59 | * @return void
60 | */
61 | public function addPostStep(string|callable $sql): void
62 | {
63 | $this->postSteps[] = $sql;
64 | }
65 |
66 | /**
67 | * Returns the alter SQL snippets
68 | *
69 | * @return string[]
70 | */
71 | public function getAlterParts(): array
72 | {
73 | return $this->alterParts;
74 | }
75 |
76 | /**
77 | * Returns the SQL commands to run after the ALTER instruction
78 | *
79 | * @return (string|callable)[]
80 | */
81 | public function getPostSteps(): array
82 | {
83 | return $this->postSteps;
84 | }
85 |
86 | /**
87 | * Merges another AlterInstructions object to this one
88 | *
89 | * @param \Migrations\Db\AlterInstructions $other The other collection of instructions to merge in
90 | * @return void
91 | */
92 | public function merge(AlterInstructions $other): void
93 | {
94 | $this->alterParts = array_merge($this->alterParts, $other->getAlterParts());
95 | $this->postSteps = array_merge($this->postSteps, $other->getPostSteps());
96 | }
97 |
98 | /**
99 | * Executes the ALTER instruction and all of the post steps.
100 | *
101 | * @param string $alterTemplate The template for the alter instruction
102 | * @param callable $executor The function to be used to execute all instructions
103 | * @return void
104 | */
105 | public function execute(string $alterTemplate, callable $executor): void
106 | {
107 | if ($this->alterParts) {
108 | $alter = sprintf($alterTemplate, implode(', ', $this->alterParts));
109 | $executor($alter);
110 | }
111 |
112 | $state = [];
113 |
114 | foreach ($this->postSteps as $instruction) {
115 | if (is_callable($instruction)) {
116 | $state = $instruction($state);
117 | continue;
118 | }
119 |
120 | $executor($instruction);
121 | }
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Db/Expression.php:
--------------------------------------------------------------------------------
1 | value = $value;
24 | }
25 |
26 | /**
27 | * @return string Returns the expression
28 | */
29 | public function __toString(): string
30 | {
31 | return $this->value;
32 | }
33 |
34 | /**
35 | * @param string $value The expression
36 | * @return self
37 | */
38 | public static function from(string $value): Expression
39 | {
40 | return new self($value);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Db/Literal.php:
--------------------------------------------------------------------------------
1 | value = $value;
28 | }
29 |
30 | /**
31 | * @return string Returns the literal's value
32 | */
33 | public function __toString(): string
34 | {
35 | return $this->value;
36 | }
37 |
38 | /**
39 | * @param string $value The literal's value
40 | * @return self
41 | */
42 | public static function from(string $value): Literal
43 | {
44 | return new self($value);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Db/Plan/AlterTable.php:
--------------------------------------------------------------------------------
1 | table = $table;
41 | }
42 |
43 | /**
44 | * Adds another action to the collection
45 | *
46 | * @param \Migrations\Db\Action\Action $action The action to add
47 | * @return void
48 | */
49 | public function addAction(Action $action): void
50 | {
51 | $this->actions[] = $action;
52 | }
53 |
54 | /**
55 | * Returns the table associated to this collection
56 | *
57 | * @return \Migrations\Db\Table\Table
58 | */
59 | public function getTable(): Table
60 | {
61 | return $this->table;
62 | }
63 |
64 | /**
65 | * Returns an array with all collected actions
66 | *
67 | * @return \Migrations\Db\Action\Action[]
68 | */
69 | public function getActions(): array
70 | {
71 | return $this->actions;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/Db/Plan/Intent.php:
--------------------------------------------------------------------------------
1 | actions[] = $action;
34 | }
35 |
36 | /**
37 | * Returns the full list of actions
38 | *
39 | * @return \Migrations\Db\Action\Action[]
40 | */
41 | public function getActions(): array
42 | {
43 | return $this->actions;
44 | }
45 |
46 | /**
47 | * Merges another Intent object with this one
48 | *
49 | * @param \Migrations\Db\Plan\Intent $another The other intent to merge in
50 | * @return void
51 | */
52 | public function merge(Intent $another): void
53 | {
54 | $this->actions = array_merge($this->actions, $another->getActions());
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Db/Plan/NewTable.php:
--------------------------------------------------------------------------------
1 | table = $table;
49 | }
50 |
51 | /**
52 | * Adds a column to the collection
53 | *
54 | * @param \Migrations\Db\Table\Column $column The column description
55 | * @return void
56 | */
57 | public function addColumn(Column $column): void
58 | {
59 | $this->columns[] = $column;
60 | }
61 |
62 | /**
63 | * Adds an index to the collection
64 | *
65 | * @param \Migrations\Db\Table\Index $index The index description
66 | * @return void
67 | */
68 | public function addIndex(Index $index): void
69 | {
70 | $this->indexes[] = $index;
71 | }
72 |
73 | /**
74 | * Returns the table object associated to this collection
75 | *
76 | * @return \Migrations\Db\Table\Table
77 | */
78 | public function getTable(): Table
79 | {
80 | return $this->table;
81 | }
82 |
83 | /**
84 | * Returns the columns collection
85 | *
86 | * @return \Migrations\Db\Table\Column[]
87 | */
88 | public function getColumns(): array
89 | {
90 | return $this->columns;
91 | }
92 |
93 | /**
94 | * Returns the indexes collection
95 | *
96 | * @return \Migrations\Db\Table\Index[]
97 | */
98 | public function getIndexes(): array
99 | {
100 | return $this->indexes;
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/src/Db/Plan/Solver/ActionSplitter.php:
--------------------------------------------------------------------------------
1 | conflictClass = $conflictClass;
59 | $this->conflictClassDual = $conflictClassDual;
60 | $this->conflictFilter = $conflictFilter;
61 | }
62 |
63 | /**
64 | * Returns a sequence of AlterTable instructions that are non conflicting
65 | * based on the constructor parameters.
66 | *
67 | * @param \Migrations\Db\Plan\AlterTable $alter The collection of actions to inspect
68 | * @return \Migrations\Db\Plan\AlterTable[] A list of AlterTable that can be executed without
69 | * this type of conflict
70 | */
71 | public function __invoke(AlterTable $alter): array
72 | {
73 | $conflictActions = array_filter($alter->getActions(), function ($action) {
74 | return $action instanceof $this->conflictClass;
75 | });
76 |
77 | $originalAlter = new AlterTable($alter->getTable());
78 | $newAlter = new AlterTable($alter->getTable());
79 |
80 | foreach ($alter->getActions() as $action) {
81 | if (!$action instanceof $this->conflictClassDual) {
82 | $originalAlter->addAction($action);
83 | continue;
84 | }
85 |
86 | $found = false;
87 | $matches = $this->conflictFilter;
88 | foreach ($conflictActions as $ca) {
89 | if ($matches($ca, $action)) {
90 | $found = true;
91 | break;
92 | }
93 | }
94 |
95 | if ($found) {
96 | $newAlter->addAction($action);
97 | } else {
98 | $originalAlter->addAction($action);
99 | }
100 | }
101 |
102 | return [$originalAlter, $newAlter];
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Db/Table/Table.php:
--------------------------------------------------------------------------------
1 |
26 | */
27 | protected array $options;
28 |
29 | /**
30 | * @param string $name The table name
31 | * @param array $options The creation options for this table
32 | * @throws \InvalidArgumentException
33 | */
34 | public function __construct(string $name, array $options = [])
35 | {
36 | if (!$name) {
37 | throw new InvalidArgumentException('Cannot use an empty table name');
38 | }
39 |
40 | $this->name = $name;
41 | $this->options = $options;
42 | }
43 |
44 | /**
45 | * Sets the table name.
46 | *
47 | * @param string $name The name of the table
48 | * @return $this
49 | */
50 | public function setName(string $name)
51 | {
52 | $this->name = $name;
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * Gets the table name.
59 | *
60 | * @return string
61 | */
62 | public function getName(): string
63 | {
64 | return $this->name;
65 | }
66 |
67 | /**
68 | * Gets the table options
69 | *
70 | * @return array
71 | */
72 | public function getOptions(): array
73 | {
74 | return $this->options;
75 | }
76 |
77 | /**
78 | * Sets the table options
79 | *
80 | * @param array $options The options for the table creation
81 | * @return $this
82 | */
83 | public function setOptions(array $options)
84 | {
85 | $this->options = $options;
86 |
87 | return $this;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Migration/BackendInterface.php:
--------------------------------------------------------------------------------
1 | $options Options to pass to the command
12 | * Available options are :
13 | *
14 | * - `format` Format to output the response. Can be 'json'
15 | * - `connection` The datasource connection to use
16 | * - `source` The folder where migrations are in
17 | * - `plugin` The plugin containing the migrations
18 | * @return array The migrations list and their statuses
19 | */
20 | public function status(array $options = []): array;
21 |
22 | /**
23 | * Migrates available migrations
24 | *
25 | * @param array $options Options to pass to the command
26 | * Available options are :
27 | *
28 | * - `target` The version number to migrate to. If not provided, will migrate
29 | * everything it can
30 | * - `connection` The datasource connection to use
31 | * - `source` The folder where migrations are in
32 | * - `plugin` The plugin containing the migrations
33 | * - `date` The date to migrate to
34 | * @return bool Success
35 | */
36 | public function migrate(array $options = []): bool;
37 |
38 | /**
39 | * Rollbacks migrations
40 | *
41 | * @param array $options Options to pass to the command
42 | * Available options are :
43 | *
44 | * - `target` The version number to migrate to. If not provided, will only migrate
45 | * the last migrations registered in the phinx log
46 | * - `connection` The datasource connection to use
47 | * - `source` The folder where migrations are in
48 | * - `plugin` The plugin containing the migrations
49 | * - `date` The date to rollback to
50 | * @return bool Success
51 | */
52 | public function rollback(array $options = []): bool;
53 |
54 | /**
55 | * Marks a migration as migrated
56 | *
57 | * @param int|string|null $version The version number of the migration to mark as migrated
58 | * @param array $options Options to pass to the command
59 | * Available options are :
60 | *
61 | * - `connection` The datasource connection to use
62 | * - `source` The folder where migrations are in
63 | * - `plugin` The plugin containing the migrations
64 | * @return bool Success
65 | */
66 | public function markMigrated(int|string|null $version = null, array $options = []): bool;
67 |
68 | /**
69 | * Seed the database using a seed file
70 | *
71 | * @param array $options Options to pass to the command
72 | * Available options are :
73 | *
74 | * - `connection` The datasource connection to use
75 | * - `source` The folder where migrations are in
76 | * - `plugin` The plugin containing the migrations
77 | * - `seed` The seed file to use
78 | * @return bool Success
79 | */
80 | public function seed(array $options = []): bool;
81 | }
82 |
--------------------------------------------------------------------------------
/src/Migration/BuiltinBackend.php:
--------------------------------------------------------------------------------
1 |
43 | */
44 | protected array $default = [];
45 |
46 | /**
47 | * Current command being run.
48 | * Useful if some logic needs to be applied in the ConfigurationTrait depending
49 | * on the command
50 | *
51 | * @var string
52 | */
53 | protected string $command;
54 |
55 | /**
56 | * Constructor
57 | *
58 | * @param array $default Default option to be used when calling a method.
59 | * Available options are :
60 | * - `connection` The datasource connection to use
61 | * - `source` The folder where migrations are in
62 | * - `plugin` The plugin containing the migrations
63 | */
64 | public function __construct(array $default = [])
65 | {
66 | if ($default) {
67 | $this->default = $default;
68 | }
69 | }
70 |
71 | /**
72 | * {@inheritDoc}
73 | */
74 | public function status(array $options = []): array
75 | {
76 | $manager = $this->getManager($options);
77 |
78 | return $manager->printStatus($options['format'] ?? null);
79 | }
80 |
81 | /**
82 | * {@inheritDoc}
83 | */
84 | public function migrate(array $options = []): bool
85 | {
86 | $manager = $this->getManager($options);
87 |
88 | if (!empty($options['date'])) {
89 | $date = new DateTime($options['date']);
90 |
91 | $manager->migrateToDateTime($date);
92 |
93 | return true;
94 | }
95 |
96 | $manager->migrate($options['target'] ?? null);
97 |
98 | return true;
99 | }
100 |
101 | /**
102 | * {@inheritDoc}
103 | */
104 | public function rollback(array $options = []): bool
105 | {
106 | $manager = $this->getManager($options);
107 |
108 | if (!empty($options['date'])) {
109 | $date = new DateTime($options['date']);
110 |
111 | $manager->rollbackToDateTime($date);
112 |
113 | return true;
114 | }
115 |
116 | $manager->rollback($options['target'] ?? null);
117 |
118 | return true;
119 | }
120 |
121 | /**
122 | * {@inheritDoc}
123 | */
124 | public function markMigrated(int|string|null $version = null, array $options = []): bool
125 | {
126 | if (
127 | isset($options['target']) &&
128 | isset($options['exclude']) &&
129 | isset($options['only'])
130 | ) {
131 | $exceptionMessage = 'You should use `exclude` OR `only` (not both) along with a `target` argument';
132 | throw new InvalidArgumentException($exceptionMessage);
133 | }
134 | $args = new Arguments([(string)$version], $options, ['version']);
135 |
136 | $manager = $this->getManager($options);
137 | $config = $manager->getConfig();
138 | $path = $config->getMigrationPath();
139 |
140 | $versions = $manager->getVersionsToMark($args);
141 | $manager->markVersionsAsMigrated($path, $versions);
142 |
143 | return true;
144 | }
145 |
146 | /**
147 | * {@inheritDoc}
148 | */
149 | public function seed(array $options = []): bool
150 | {
151 | $options['source'] ??= ConfigInterface::DEFAULT_SEED_FOLDER;
152 | $seed = $options['seed'] ?? null;
153 |
154 | $manager = $this->getManager($options);
155 | $manager->seed($seed);
156 |
157 | return true;
158 | }
159 |
160 | /**
161 | * Returns an instance of Manager
162 | *
163 | * @param array $options The options for manager creation
164 | * @return \Migrations\Migration\Manager Instance of Manager
165 | */
166 | public function getManager(array $options): Manager
167 | {
168 | $options += $this->default;
169 |
170 | $factory = new ManagerFactory([
171 | 'plugin' => $options['plugin'] ?? null,
172 | 'source' => $options['source'] ?? ConfigInterface::DEFAULT_MIGRATION_FOLDER,
173 | 'connection' => $options['connection'] ?? 'default',
174 | ]);
175 | $io = new ConsoleIo(
176 | new StubConsoleOutput(),
177 | new StubConsoleOutput(),
178 | new StubConsoleInput([]),
179 | );
180 |
181 | return $factory->createManager($io);
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/Migration/IrreversibleMigrationException.php:
--------------------------------------------------------------------------------
1 | options[$name])) {
59 | return null;
60 | }
61 |
62 | return $this->options[$name];
63 | }
64 |
65 | /**
66 | * Create a ConfigInterface instance based on the factory options.
67 | *
68 | * @return \Migrations\Config\ConfigInterface
69 | */
70 | public function createConfig(): ConfigInterface
71 | {
72 | $folder = (string)$this->getOption('source');
73 |
74 | // Get the filepath for migrations and seeds.
75 | // We rely on factory parameters to define which directory to use.
76 | $dir = ROOT . DS . 'config' . DS . $folder;
77 | if (defined('CONFIG')) {
78 | $dir = CONFIG . $folder;
79 | }
80 | $plugin = (string)$this->getOption('plugin') ?: null;
81 | if ($plugin) {
82 | $dir = Plugin::path($plugin) . 'config' . DS . $folder;
83 | }
84 |
85 | // Get the phinxlog table name. Plugins have separate migration history.
86 | // The names and separate table history is something we could change in the future.
87 | $table = Util::tableName($plugin);
88 | $templatePath = dirname(__DIR__) . DS . 'templates' . DS;
89 | $connectionName = (string)$this->getOption('connection');
90 |
91 | if (str_contains($connectionName, '://')) {
92 | /** @var array $connectionConfig */
93 | $connectionConfig = ConnectionManager::parseDsn($connectionName);
94 | $connectionName = 'tmp';
95 | if (!ConnectionManager::getConfig($connectionName)) {
96 | ConnectionManager::setConfig($connectionName, $connectionConfig);
97 | }
98 | } else {
99 | $connectionConfig = ConnectionManager::getConfig($connectionName);
100 | }
101 | if (!$connectionConfig) {
102 | throw new RuntimeException("Could not find connection `{$connectionName}`");
103 | }
104 | if (!isset($connectionConfig['database'])) {
105 | throw new RuntimeException("The `{$connectionName}` connection has no `database` key defined.");
106 | }
107 |
108 | /** @var array $connectionConfig */
109 | $adapter = $connectionConfig['scheme'] ?? null;
110 | $adapterConfig = [
111 | 'adapter' => $adapter,
112 | 'connection' => $connectionName,
113 | 'database' => $connectionConfig['database'],
114 | 'migration_table' => $table,
115 | 'dryrun' => $this->getOption('dry-run'),
116 | ];
117 |
118 | $configData = [
119 | 'paths' => [
120 | 'migrations' => $dir,
121 | 'seeds' => $dir,
122 | ],
123 | 'templates' => [
124 | 'file' => $templatePath . 'Phinx/create.php.template',
125 | ],
126 | 'migration_base_class' => 'Migrations\AbstractMigration',
127 | 'environment' => $adapterConfig,
128 | 'plugin' => $plugin,
129 | 'source' => (string)$this->getOption('source'),
130 | 'feature_flags' => [
131 | 'unsigned_primary_keys' => Configure::read('Migrations.unsigned_primary_keys'),
132 | 'column_null_default' => Configure::read('Migrations.column_null_default'),
133 | ],
134 | ];
135 |
136 | return new Config($configData);
137 | }
138 |
139 | /**
140 | * Get the migration manager for the current CLI options and application configuration.
141 | *
142 | * @param \Cake\Console\ConsoleIo $io The command io.
143 | * @param \Migrations\Config\ConfigInterface $config A config instance. Providing null will create a new Config
144 | * based on the factory constructor options.
145 | * @return \Migrations\Migration\Manager
146 | */
147 | public function createManager(ConsoleIo $io, ?ConfigInterface $config = null): Manager
148 | {
149 | $config ??= $this->createConfig();
150 |
151 | return new Manager($config, $io);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/MigrationsDispatcher.php:
--------------------------------------------------------------------------------
1 |
31 | * @psalm-return array|class-string<\Migrations\Command\Phinx\BaseCommand>>
32 | */
33 | public static function getCommands(): array
34 | {
35 | return [
36 | 'Create' => Phinx\Create::class,
37 | 'Dump' => Phinx\Dump::class,
38 | 'MarkMigrated' => Phinx\MarkMigrated::class,
39 | 'Migrate' => Phinx\Migrate::class,
40 | 'Rollback' => Phinx\Rollback::class,
41 | 'Seed' => Phinx\Seed::class,
42 | 'Status' => Phinx\Status::class,
43 | 'CacheBuild' => Phinx\CacheBuild::class,
44 | 'CacheClear' => Phinx\CacheClear::class,
45 | ];
46 | }
47 |
48 | /**
49 | * Initialize the Phinx console application.
50 | *
51 | * @param string $version The Application Version
52 | */
53 | public function __construct(string $version)
54 | {
55 | parent::__construct('Migrations plugin, based on Phinx by Rob Morgan.', $version);
56 | // Update this to use the methods
57 | foreach ($this->getCommands() as $value) {
58 | $this->add(new $value());
59 | }
60 | $this->setCatchExceptions(false);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/MigrationsPlugin.php:
--------------------------------------------------------------------------------
1 |
60 | * @psalm-var array>
61 | */
62 | protected array $migrationCommandsList = [
63 | MigrationsCommand::class,
64 | MigrationsCreateCommand::class,
65 | MigrationsDumpCommand::class,
66 | MigrationsMarkMigratedCommand::class,
67 | MigrationsMigrateCommand::class,
68 | MigrationsCacheBuildCommand::class,
69 | MigrationsCacheClearCommand::class,
70 | MigrationsRollbackCommand::class,
71 | MigrationsSeedCommand::class,
72 | MigrationsStatusCommand::class,
73 | ];
74 |
75 | /**
76 | * Initialize configuration with defaults.
77 | *
78 | * @param \Cake\Core\PluginApplicationInterface $app The application.
79 | * @return void
80 | */
81 | public function bootstrap(PluginApplicationInterface $app): void
82 | {
83 | parent::bootstrap($app);
84 |
85 | if (!Configure::check('Migrations.backend')) {
86 | Configure::write('Migrations.backend', 'builtin');
87 | }
88 | }
89 |
90 | /**
91 | * Add migrations commands.
92 | *
93 | * @param \Cake\Console\CommandCollection $commands The command collection to update
94 | * @return \Cake\Console\CommandCollection
95 | */
96 | public function console(CommandCollection $commands): CommandCollection
97 | {
98 | if (Configure::read('Migrations.backend') == 'builtin') {
99 | $classes = [
100 | DumpCommand::class,
101 | EntryCommand::class,
102 | MarkMigratedCommand::class,
103 | MigrateCommand::class,
104 | RollbackCommand::class,
105 | SeedCommand::class,
106 | StatusCommand::class,
107 | ];
108 | $hasBake = class_exists(SimpleBakeCommand::class);
109 | if ($hasBake) {
110 | $classes[] = BakeMigrationCommand::class;
111 | $classes[] = BakeMigrationDiffCommand::class;
112 | $classes[] = BakeMigrationSnapshotCommand::class;
113 | $classes[] = BakeSeedCommand::class;
114 | }
115 | $found = [];
116 | foreach ($classes as $class) {
117 | $name = $class::defaultName();
118 | // If the short name has been used, use the full name.
119 | // This allows app commands to have name preference.
120 | // and app commands to overwrite migration commands.
121 | if (!$commands->has($name)) {
122 | $found[$name] = $class;
123 | }
124 | $found['migrations.' . $name] = $class;
125 | }
126 | if ($hasBake) {
127 | $found['migrations create'] = BakeMigrationCommand::class;
128 | }
129 |
130 | $commands->addMany($found);
131 |
132 | return $commands;
133 | }
134 |
135 | if (class_exists(SimpleBakeCommand::class)) {
136 | $found = $commands->discoverPlugin($this->getName());
137 |
138 | return $commands->addMany($found);
139 | }
140 |
141 | $found = [];
142 | // Convert to a method and use config to toggle command names.
143 | foreach ($this->migrationCommandsList as $class) {
144 | $name = $class::defaultName();
145 | // If the short name has been used, use the full name.
146 | // This allows app commands to have name preference.
147 | // and app commands to overwrite migration commands.
148 | if (!$commands->has($name)) {
149 | $found[$name] = $class;
150 | }
151 | // full name
152 | $found['migrations.' . $name] = $class;
153 | }
154 |
155 | return $commands->addMany($found);
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/SeedInterface.php:
--------------------------------------------------------------------------------
1 | \Table class.
157 | *
158 | * You can use this class to create and manipulate tables.
159 | *
160 | * @param string $tableName Table name
161 | * @param array $options Options
162 | * @return \Migrations\Db\Table
163 | */
164 | public function table(string $tableName, array $options = []): Table;
165 |
166 | /**
167 | * Checks to see if the seed should be executed.
168 | *
169 | * Returns true by default.
170 | *
171 | * You can use this to prevent a seed from executing.
172 | *
173 | * @return bool
174 | */
175 | public function shouldExecute(): bool;
176 |
177 | /**
178 | * Gives the ability to a seeder to call another seeder.
179 | * This is particularly useful if you need to run the seeders of your applications in a specific sequences,
180 | * for instance to respect foreign key constraints.
181 | *
182 | * @param string $seeder Name of the seeder to call from the current seed
183 | * @param array $options The CLI options for the seeder.
184 | * @return void
185 | */
186 | public function call(string $seeder, array $options = []): void;
187 | }
188 |
--------------------------------------------------------------------------------
/src/Shim/OutputAdapter.php:
--------------------------------------------------------------------------------
1 | io->out($messages, $newline ? 1 : 0);
43 | }
44 |
45 | /**
46 | * @inheritDoc
47 | */
48 | public function writeln(string|iterable $messages, $options = 0): void
49 | {
50 | if ($messages instanceof Traversable) {
51 | $messages = iterator_to_array($messages);
52 | }
53 | $this->io->out($messages, 1);
54 | }
55 |
56 | /**
57 | * Sets the verbosity of the output.
58 | *
59 | * @param self::VERBOSITY_* $level
60 | * @return void
61 | */
62 | public function setVerbosity(int $level): void
63 | {
64 | // TODO map values
65 | $this->io->level($level);
66 | }
67 |
68 | /**
69 | * Gets the current verbosity of the output.
70 | *
71 | * @return self::VERBOSITY_*
72 | */
73 | public function getVerbosity(): int
74 | {
75 | // TODO map values
76 | return $this->io->level();
77 | }
78 |
79 | /**
80 | * Returns whether verbosity is quiet (-q).
81 | */
82 | public function isQuiet(): bool
83 | {
84 | return $this->io->level() === ConsoleIo::QUIET;
85 | }
86 |
87 | /**
88 | * Returns whether verbosity is verbose (-v).
89 | */
90 | public function isVerbose(): bool
91 | {
92 | return $this->io->level() === ConsoleIo::VERBOSE;
93 | }
94 |
95 | /**
96 | * Returns whether verbosity is very verbose (-vv).
97 | */
98 | public function isVeryVerbose(): bool
99 | {
100 | return false;
101 | }
102 |
103 | /**
104 | * Returns whether verbosity is debug (-vvv).
105 | */
106 | public function isDebug(): bool
107 | {
108 | return false;
109 | }
110 |
111 | /**
112 | * Sets the decorated flag.
113 | *
114 | * @return void
115 | */
116 | public function setDecorated(bool $decorated): void
117 | {
118 | throw new RuntimeException('setDecorated is not implemented');
119 | }
120 |
121 | /**
122 | * Gets the decorated flag.
123 | */
124 | public function isDecorated(): bool
125 | {
126 | throw new RuntimeException('isDecorated is not implemented');
127 | }
128 |
129 | /**
130 | * @return void
131 | */
132 | public function setFormatter(OutputFormatterInterface $formatter): void
133 | {
134 | throw new RuntimeException('setFormatter is not implemented');
135 | }
136 |
137 | /**
138 | * Returns current output formatter instance.
139 | */
140 | public function getFormatter(): OutputFormatterInterface
141 | {
142 | throw new RuntimeException('getFormatter is not implemented');
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/Util/SchemaTrait.php:
--------------------------------------------------------------------------------
1 | getOption('connection');
37 | /** @var \Cake\Database\Connection|\Cake\Datasource\ConnectionInterface $connection */
38 | $connection = ConnectionManager::get($connectionName);
39 |
40 | if (!method_exists($connection, 'getSchemaCollection')) {
41 | $msg = sprintf(
42 | 'The "%s" connection is not compatible with orm caching, ' .
43 | 'as it does not implement a "getSchemaCollection()" method.',
44 | $connectionName,
45 | );
46 | $output->writeln('' . $msg . '');
47 |
48 | return null;
49 | }
50 |
51 | $config = $connection->config();
52 |
53 | if (empty($config['cacheMetadata'])) {
54 | $output->writeln('Metadata cache was disabled in config. Enable to cache or clear.');
55 |
56 | return null;
57 | }
58 |
59 | $connection->cacheMetadata(true);
60 |
61 | /**
62 | * @var \Cake\Database\Schema\CachedCollection
63 | */
64 | return $connection->getSchemaCollection();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Util/UtilTrait.php:
--------------------------------------------------------------------------------
1 | getOption('plugin') ?: null;
34 |
35 | return $plugin;
36 | }
37 |
38 | /**
39 | * Get the phinx table name used to store migrations data
40 | *
41 | * @param string|null $plugin Plugin name
42 | * @return string
43 | */
44 | protected function getPhinxTable(?string $plugin = null): string
45 | {
46 | $table = 'phinxlog';
47 |
48 | if (!$plugin) {
49 | return $table;
50 | }
51 |
52 | $plugin = Inflector::underscore($plugin) . '_';
53 | $plugin = str_replace(['\\', '/', '.'], '_', $plugin);
54 |
55 | return $plugin . $table;
56 | }
57 |
58 | /**
59 | * Get the migrations or seeds files path based on the current InputInterface
60 | *
61 | * @param \Symfony\Component\Console\Input\InputInterface $input Input of the current command.
62 | * @param string $default Default folder to set if no source option is found in the $input param
63 | * @return string
64 | */
65 | protected function getOperationsPath(InputInterface $input, string $default = 'Migrations'): string
66 | {
67 | $folder = $input->getOption('source') ?: $default;
68 |
69 | $dir = ROOT . DS . 'config' . DS . $folder;
70 |
71 | if (defined('CONFIG')) {
72 | $dir = CONFIG . $folder;
73 | }
74 |
75 | $plugin = $this->getPlugin($input);
76 |
77 | if ($plugin !== null) {
78 | $dir = CorePlugin::path($plugin) . 'config' . DS . $folder;
79 | }
80 |
81 | return $dir;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/templates/Phinx/create.php.template:
--------------------------------------------------------------------------------
1 | table('{{ table }}');
58 | $table->insert($data)->save();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/templates/bake/config/skeleton.twig:
--------------------------------------------------------------------------------
1 | {#
2 | /**
3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5 | *
6 | * Licensed under The MIT License
7 | * For full copyright and license information, please see the LICENSE.txt
8 | * Redistributions of files must retain the above copyright notice.
9 | *
10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11 | * @link https://cakephp.org CakePHP(tm) Project
12 | * @since 3.0.0
13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License
14 | */
15 | #}
16 | {% set wantedOptions = {'length': '', 'limit': '', 'default': '', 'unsigned': '', 'null': '', 'comment': '', 'autoIncrement': '', 'precision': '', 'scale': ''} %}
17 | {% set tableMethod = Migration.tableMethod(action) %}
18 | {% set columnMethod = Migration.columnMethod(action) %}
19 | {% set indexMethod = Migration.indexMethod(action) %}
20 | table('{{ table }}');
52 | {% if tableMethod != 'drop' %}
53 | {% if columnMethod == 'removeColumn' %}
54 | {% for column, config in columns['fields'] %}
55 | $table->{{ columnMethod }}('{{ column }}');
56 | {% endfor %}
57 | {% for column, config in columns['indexes'] %}
58 | $table->{{ indexMethod }}([{{
59 | Migration.stringifyList(config['columns']) | raw
60 | }}]);
61 | {% endfor %}
62 | {% else %}
63 | {% for column, config in columns['fields'] %}
64 | $table->{{ columnMethod }}('{{ column }}', '{{ config['columnType'] }}', [{{
65 | Migration.stringifyList(config['options'], {'indent': 3}, wantedOptions) | raw
66 | }}]);
67 | {% endfor %}
68 | {% for column, config in columns['indexes'] %}
69 | $table->{{ indexMethod }}([{{
70 | Migration.stringifyList(config['columns'], {'indent': 3}) | raw }}
71 | ], [{{
72 | Migration.stringifyList(config['options'], {'indent': 3}) | raw
73 | }}]);
74 | {% endfor %}
75 | {% if tableMethod == 'create' and columns['primaryKey'] is not empty %}
76 | $table->addPrimaryKey([{{
77 | Migration.stringifyList(columns['primaryKey'], {'indent': 3}) | raw
78 | }}]);
79 | {% endif %}
80 | {% endif %}
81 | {% endif %}
82 | $table->{{ tableMethod }}(){% if tableMethod == 'drop' %}->save(){% endif %};
83 | {% endfor %}
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/templates/bake/config/snapshot.twig:
--------------------------------------------------------------------------------
1 | {#
2 | /**
3 | * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4 | * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5 | *
6 | * Licensed under The MIT License
7 | * For full copyright and license information, please see the LICENSE.txt
8 | * Redistributions of files must retain the above copyright notice.
9 | *
10 | * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11 | * @link https://cakephp.org CakePHP(tm) Project
12 | * @since 3.0.0
13 | * @license https://www.opensource.org/licenses/mit-license.php MIT License
14 | */
15 | #}
16 | {% set constraints = [] %}
17 | {% set foreignKeys = [] %}
18 | {% set dropForeignKeys = [] %}
19 | {% if autoId and Migration.hasAutoIdIncompatiblePrimaryKey(tables) %}
20 | {% set autoId = false %}
21 | {% endif %}
22 | table('{{ table }}')
70 | {% for key, columns in columnsList %}
71 | ->dropForeignKey(
72 | {{ columns|raw }}
73 | ){{ (key == maxKey) ? '->save();' : '' }}
74 | {% endfor %}
75 |
76 | {% endfor %}
77 | {% endif %}
78 | {% for table in tables %}
79 | $this->table('{{ table }}')->drop()->save();
80 | {% endfor %}
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/templates/bake/element/add-columns.twig:
--------------------------------------------------------------------------------
1 | {% for columnName, columnAttributes in columns %}
2 | {% set type = columnAttributes['type'] %}
3 | {% set columnAttributes = Migration.getColumnOption(columnAttributes) %}
4 | {% set columnAttributes = Migration.stringifyList(columnAttributes, {'indent': 4, 'remove': ['type']}) %}
5 | {% if columnAttributes is not empty %}
6 | ->addColumn('{{ columnName }}', '{{ type }}', [{{ columnAttributes | raw }}])
7 | {% else %}
8 | ->addColumn('{{ columnName }}', '{{ type }}')
9 | {% endif -%}
10 | {% endfor -%}
11 |
--------------------------------------------------------------------------------
/templates/bake/element/add-foreign-keys.twig:
--------------------------------------------------------------------------------
1 | {% set statement = Migration.tableStatement(table, true) %}
2 | {% set hasProcessedConstraint = false %}
3 | {% for constraintName, constraint in constraints %}
4 | {% set constraintColumns = constraint['columns']|sort %}
5 | {% if constraint['type'] == 'foreign' %}
6 | {% set hasProcessedConstraint = true %}
7 | {% set columnsList = '\'' ~ constraint['columns'][0] ~ '\'' %}
8 | {% set storedColumnList = columnsList %}
9 | {% set indent = backend == 'builtin' ? 6 : 5 %}
10 | {% if constraint['columns']|length > 1 %}
11 | {% set storedColumnList = '[' ~ Migration.stringifyList(constraint['columns'], {'indent': 5}) ~ ']' %}
12 | {% set columnsList = '[' ~ Migration.stringifyList(constraint['columns'], {'indent': indent}) ~ ']' %}
13 | {% endif %}
14 | {% set record = Migration.storeReturnedData(table, storedColumnList) %}
15 | {% if constraint['references'][1] is iterable %}
16 | {% set columnsReference = '[' ~ Migration.stringifyList(constraint['references'][1], {'indent': indent}) ~ ']' %}
17 | {% else %}
18 | {% set columnsReference = '\'' ~ constraint['references'][1] ~ '\'' %}
19 | {% endif %}
20 | {% if statement is not defined %}
21 | {% set statement = Migration.tableStatement(table) %}
22 | {% endif %}
23 | {% if statement is not empty %}
24 |
25 | {{ statement | raw }}
26 | {% set statement = null %}
27 | {% endif %}
28 | {% if backend == 'builtin' %}
29 | ->addForeignKey(
30 | $this->foreignKey({{ columnsList | raw }})
31 | ->setReferencedTable('{{ constraint['references'][0] }}')
32 | ->setReferencedColumns({{ columnsReference | raw }})
33 | ->setOnDelete('{{ Migration.formatConstraintAction(constraint['delete']) | raw }}')
34 | ->setOnUpdate('{{ Migration.formatConstraintAction(constraint['update']) | raw }}')
35 | ->setName('{{ constraintName }}')
36 | )
37 | {% else %}
38 | ->addForeignKey(
39 | {{ columnsList | raw }},
40 | '{{ constraint['references'][0] }}',
41 | {{ columnsReference | raw }},
42 | [
43 | 'update' => '{{ Migration.formatConstraintAction(constraint['update']) | raw }}',
44 | 'delete' => '{{ Migration.formatConstraintAction(constraint['delete']) | raw }}',
45 | 'constraint' => '{{ constraintName }}'
46 | ]
47 | )
48 | {% endif %}
49 | {% endif %}
50 | {% endfor %}
51 | {% if Migration.wasTableStatementGeneratedFor(table) and hasProcessedConstraint %}
52 | ->update();
53 | {% endif -%}
54 |
--------------------------------------------------------------------------------
/templates/bake/element/add-indexes.twig:
--------------------------------------------------------------------------------
1 | {% for indexName, index in indexes %}
2 | {% set columnsList = '\'' ~ index['columns'][0] ~ '\'' %}
3 | {% if index['columns']|length > 1 %}
4 | {% set columnsList = '[' ~ Migration.stringifyList(index['columns'], {'indent': 6}) ~ ']' %}
5 | {% endif %}
6 | ->addIndex(
7 | {% if backend == 'builtin' %}
8 | $this->index({{ columnsList | raw }})
9 | ->setName('{{ indexName }}')
10 | {% if index['type'] == 'unique' %}
11 | ->setType('unique')
12 | {% elseif index['type'] == 'fulltext' %}
13 | ->setType('fulltext')
14 | {% endif %}
15 | {% if index['options'] %}
16 | ->setOptions([{{ Migration.stringifyList(index['options'], {'indent': 6}) | raw }}])
17 | {% endif %}
18 | )
19 | {% else %}
20 | [{{ Migration.stringifyList(index['columns'], {'indent': 5}) | raw }}],
21 | {% set params = {'name': indexName} %}
22 | {% if index['type'] == 'unique' %}
23 | {% set params = params|merge({'unique': true}) %}
24 | {% endif %}
25 | [{{ Migration.stringifyList(params, {'indent': 5}) | raw }}]
26 | )
27 | {% endif %}
28 | {% endfor %}
29 |
--------------------------------------------------------------------------------
/templates/bake/element/create-tables.twig:
--------------------------------------------------------------------------------
1 | {% set createData = Migration.getCreateTablesElementData(tables) %}
2 | {% for table, schema in tables %}
3 | {% set tableArgForMethods = useSchema ? schema : table %}
4 | {% set tableArgForArray = useSchema ? table : schema %}
5 | {% set foreignKeys = [] %}
6 | {% set primaryKeysColumns = Migration.primaryKeysColumnsList(tableArgForMethods) %}
7 | {% set primaryKeys = Migration.primaryKeys(tableArgForMethods) %}
8 | {% set specialPk = primaryKeys and (primaryKeys|length > 1 or primaryKeys[0]['name'] != 'id' or primaryKeys[0]['info']['columnType'] != 'integer') and autoId %}
9 | {% if loop.index > 1 %}
10 |
11 | {% endif -%}
12 | {% if specialPk %}
13 | $this->table('{{ tableArgForArray }}', ['id' => false, 'primary_key' => ['{{ Migration.extract(primaryKeys)|join("', '")|raw }}']])
14 | {% elseif not primaryKeys and autoId %}
15 | $this->table('{{ tableArgForArray }}', ['id' => false])
16 | {% else %}
17 | $this->table('{{ tableArgForArray }}')
18 | {% endif %}
19 | {% if specialPk or not autoId %}
20 | {% for primaryKey in primaryKeys %}
21 | {% set columnOptions = Migration.getColumnOption(primaryKey['info']['options']) %}
22 | ->addColumn('{{ primaryKey['name'] }}', '{{ primaryKey['info']['columnType'] }}', [{{ Migration.stringifyList(columnOptions, {'indent': 4}) | raw }}])
23 | {% endfor %}
24 | {% if not autoId and primaryKeys %}
25 | ->addPrimaryKey(['{{ Migration.extract(primaryKeys)
26 | | join("', '") | raw }}'])
27 | {% endif %}
28 | {% endif %}
29 | {% for column, config in Migration.columns(tableArgForMethods) %}
30 | {% set columnOptions = Migration.getColumnOption(config['options']) %}
31 | {% if config['columnType'] == 'boolean' and columnOptions['default'] is defined and (Migration.value(columnOptions['default'])) is not same as('null') %}
32 | {% set default = columnOptions['default'] ? true : false %}
33 | {% set columnOptions = columnOptions|merge({'default': default}) %}
34 | {% endif %}
35 | ->addColumn('{{ column }}', '{{ config['columnType'] }}', [{{
36 | Migration.stringifyList(columnOptions, {'indent': 4}) | raw
37 | }}])
38 | {% endfor %}
39 | {% if createData.tables[table].constraints is not empty %}
40 | {% for name, constraint in createData.tables[table].constraints %}
41 | {% if constraint['type'] == 'unique' %}
42 | {{ element('Migrations.add-indexes', {
43 | indexes: {(name): constraint},
44 | backend: backend,
45 | }) -}}
46 | {% endif %}
47 | {% endfor %}
48 | {% endif %}
49 | {{- element('Migrations.add-indexes', {
50 | indexes: createData.tables[table].indexes,
51 | backend: backend,
52 | }) }} ->create();
53 | {% endfor -%}
54 | {% if createData.constraints %}
55 | {% for table, tableConstraints in createData.constraints %}
56 | {{- element('Migrations.add-foreign-keys', {
57 | constraints: tableConstraints,
58 | table: table,
59 | backend: backend,
60 | })
61 | -}}
62 | {% endfor -%}
63 | {% endif -%}
64 |
--------------------------------------------------------------------------------