diff --git a/.editorconfig b/.editorconfig index 9866c39..558bf79 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,3 @@ -# editorconfig.org - root = true [*] @@ -9,3 +7,12 @@ insert_final_newline = true indent_style = space indent_size = 4 trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false + +[*.{yml,yaml}] +indent_size = 2 + +[Makefile] +indent_style = tab diff --git a/.gitattributes b/.gitattributes index 3f6e40b..7a91380 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ -/.github export-ignore -/.editorconfig export-ignore -/.gitattributes export-ignore -/.gitignore export-ignore -/tests export-ignore +/.* export-ignore +/tests export-ignore +/*.xml export-ignore +/*.yml export-ignore +/*.lock export-ignore +/*.dist export-ignore diff --git a/.github/workflows/ci-mssql.yml b/.github/workflows/ci-mssql.yml index 8ce0c28..2f5f2ea 100644 --- a/.github/workflows/ci-mssql.yml +++ b/.github/workflows/ci-mssql.yml @@ -1,86 +1,16 @@ -on: - pull_request: +--- + +on: # yamllint disable-line rule:truthy push: branches: - '*.*' + - '*.*.*' + pull_request: null -name: ci-mssql +name: MSSQL jobs: - tests: - name: PHP ${{ matrix.php }}-mssql-${{ matrix.mssql }} - - env: - key: cache - - runs-on: ubuntu-latest - - strategy: - fail-fast: false - matrix: - include: - - php: '8.0' - extensions: pdo, pdo_sqlsrv - mssql: 'server:2017-latest' - - php: '8.0' - extensions: pdo, pdo_sqlsrv - mssql: 'server:2019-CU25-ubuntu-20.04' - - php: '8.1' - extensions: pdo, pdo_sqlsrv - mssql: 'server:2019-CU25-ubuntu-20.04' - - php: '8.2' - extensions: pdo, pdo_sqlsrv - mssql: 'server:2019-CU25-ubuntu-20.04' - - php: '8.3' - extensions: pdo, pdo_sqlsrv - mssql: 'server:2019-CU25-ubuntu-20.04' - - services: - mssql: - image: mcr.microsoft.com/mssql/${{ matrix.mssql }} - env: - SA_PASSWORD: SSpaSS__1 - ACCEPT_EULA: Y - MSSQL_PID: Developer - ports: - - 11433:1433 - options: --name=mssql --health-cmd="/opt/mssql-tools/bin/sqlcmd -S localhost -U SA -P 'SSpaSS__1' -Q 'SELECT 1'" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout - uses: actions/checkout@v2.3.4 - - - name: Install PHP with extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php }} - extensions: ${{ matrix.extensions }} - ini-values: date.timezone='UTC' - tools: composer:v2, pecl - - - name: Determine composer cache directory on Linux - run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - - name: Cache dependencies installed with composer - uses: actions/cache@v2 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php }}-composer- - - - name: Update composer - run: composer self-update - - - name: Install dependencies with composer - if: matrix.php != '8.4' - run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - - name: Install dependencies with composer php 8.4 - if: matrix.php == '8.4' - run: composer update --ignore-platform-reqs --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + phpunit: + uses: cycle/gh-actions/.github/workflows/db-mssql.yml@master - - name: Run tests with phpunit without coverage - env: - DB: sqlserver - run: vendor/bin/phpunit --group driver-sqlserver --colors=always +... diff --git a/.github/workflows/ci-mysql.yml b/.github/workflows/ci-mysql.yml index fa192ab..c0ccde1 100644 --- a/.github/workflows/ci-mysql.yml +++ b/.github/workflows/ci-mysql.yml @@ -1,96 +1,16 @@ -on: - pull_request: +--- + +on: # yamllint disable-line rule:truthy push: branches: - '*.*' + - '*.*.*' + pull_request: null -name: ci-mysql +name: MySQL jobs: - tests: - name: PHP ${{ matrix.php-version }}-mysql-${{ matrix.mysql-version }} - env: - extensions: curl, intl, pdo, pdo_mysql - key: cache-v1 - - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - php-version: - - "8.0" - - "8.1" - - "8.2" - - "8.3" - - mysql-version: - - "5.7" - - "8.0" - - services: - mysql: - image: mysql:${{ matrix.mysql-version }} - env: - MYSQL_ROOT_PASSWORD: root - MYSQL_DATABASE: spiral - MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password - ports: - - 13306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup cache environment - id: cache-env - uses: shivammathur/cache-extensions@v1 - with: - php-version: ${{ matrix.php-version }} - extensions: ${{ env.extensions }} - key: ${{ env.key }} - - - name: Cache extensions - uses: actions/cache@v2 - with: - path: ${{ steps.cache-env.outputs.dir }} - key: ${{ steps.cache-env.outputs.key }} - restore-keys: ${{ steps.cache-env.outputs.key }} - - - name: Install PHP with extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: ${{ env.extensions }} - ini-values: date.timezone='UTC' - coverage: pcov - - - name: Determine composer cache directory - if: matrix.os == 'ubuntu-latest' - run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - - name: Cache dependencies installed with composer - uses: actions/cache@v2 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}- - - - name: Install dependencies with composer - if: matrix.php-version != '8.4' - run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - - name: Install dependencies with composer php 8.4 - if: matrix.php-version == '8.4' - run: composer update --ignore-platform-reqs --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + phpunit: + uses: cycle/gh-actions/.github/workflows/db-mysql.yml@master - - name: Run mysql tests with phpunit - env: - DB: mysql - MYSQL: ${{ matrix.mysql-version }} - run: vendor/bin/phpunit --group driver-mysql --colors=always +... diff --git a/.github/workflows/ci-pgsql.yml b/.github/workflows/ci-pgsql.yml index a1e77df..2b3f8e7 100644 --- a/.github/workflows/ci-pgsql.yml +++ b/.github/workflows/ci-pgsql.yml @@ -1,97 +1,16 @@ -on: - pull_request: +--- + +on: # yamllint disable-line rule:truthy push: branches: - '*.*' + - '*.*.*' + pull_request: null -name: ci-pgsql +name: Postgres jobs: - tests: - name: PHP ${{ matrix.php-version }}-pgsql-${{ matrix.pgsql-version }} - env: - extensions: curl, intl, pdo, pdo_pgsql - key: cache-v1 - - runs-on: ${{ matrix.os }} - - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - php-version: - - "8.0" - - "8.1" - - "8.2" - - "8.3" - - pgsql-version: - - "10" - - "11" - - "12" - - "13" - - services: - postgres: - image: postgres:${{ matrix.pgsql-version }} - env: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: spiral - ports: - - 15432:5432 - options: --name=postgres --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3 - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup cache environment - id: cache-env - uses: shivammathur/cache-extensions@v1 - with: - php-version: ${{ matrix.php-version }} - extensions: ${{ env.extensions }} - key: ${{ env.key }} - - - name: Cache extensions - uses: actions/cache@v2 - with: - path: ${{ steps.cache-env.outputs.dir }} - key: ${{ steps.cache-env.outputs.key }} - restore-keys: ${{ steps.cache-env.outputs.key }} - - - name: Install PHP with extensions - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: ${{ env.extensions }} - ini-values: date.timezone='UTC' - coverage: pcov - - - name: Determine composer cache directory - if: matrix.os == 'ubuntu-latest' - run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV - - - name: Cache dependencies installed with composer - uses: actions/cache@v2 - with: - path: ${{ env.COMPOSER_CACHE_DIR }} - key: php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}-${{ hashFiles('**/composer.json') }} - restore-keys: | - php${{ matrix.php-version }}-composer-${{ matrix.dependencies }}- - - - name: Install dependencies with composer - if: matrix.php-version != '8.4' - run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - - name: Install dependencies with composer php 8.4 - if: matrix.php-version == '8.4' - run: composer update --ignore-platform-reqs --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + phpunit: + uses: cycle/gh-actions/.github/workflows/db-pgsql.yml@master - - name: Run pgsql tests with phpunit - env: - DB: postgres - POSTGRES: ${{ matrix.pgsql-version }} - run: vendor/bin/phpunit --group driver-postgres --colors=always +... diff --git a/.github/workflows/cs-fix.yml b/.github/workflows/cs-fix.yml new file mode 100644 index 0000000..db5860c --- /dev/null +++ b/.github/workflows/cs-fix.yml @@ -0,0 +1,16 @@ +--- + +on: # yamllint disable-line rule:truthy + push: + branches: + - '*' + +name: Fix Code Style + +jobs: + cs-fix: + permissions: + contents: write + uses: spiral/gh-actions/.github/workflows/cs-fix.yml@master + +... diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 83536a4..40e9321 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -22,7 +22,12 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: + - name: Install ODBC driver. + run: | + sudo curl https://packages.microsoft.com/config/ubuntu/$(lsb_release -rs)/prod.list | sudo tee /etc/apt/sources.list.d/mssql-release.list + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 - name: Checkout uses: actions/checkout@v2 - name: Setup DB services @@ -36,21 +41,21 @@ jobs: php-version: ${{ matrix.php-version }} coverage: pcov tools: pecl - extensions: mbstring, pdo, pdo_sqlite, pdo_pgsql, pdo_sqlsrv-5.10.0beta2, pdo_mysql + extensions: mbstring, pdo, pdo_sqlite, pdo_pgsql, pdo_sqlsrv-5.11, pdo_mysql - name: Get Composer Cache Directory id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Restore Composer Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies with composer - if: matrix.php-version != '8.4' + if: matrix.php-version != '8.5' run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - name: Install dependencies with composer php 8.4 - if: matrix.php-version == '8.4' + - name: Install dependencies with composer php 8.5 + if: matrix.php-version == '8.5' run: composer update --ignore-platform-reqs --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - name: Execute Tests run: | @@ -80,6 +85,7 @@ jobs: - "8.1" - "8.2" - "8.3" + - "8.4" steps: - name: Checkout uses: actions/checkout@v2 @@ -94,18 +100,18 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - name: Restore Composer Cache - uses: actions/cache@v2 + uses: actions/cache@v4 with: path: ${{ steps.composer-cache.outputs.dir }} key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} restore-keys: ${{ runner.os }}-composer- - name: Install dependencies with composer - if: matrix.php-version != '8.4' + if: matrix.php-version != '8.5' run: composer update --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - - name: Install dependencies with composer php 8.4 - if: matrix.php-version == '8.4' + - name: Install dependencies with composer php 8.5 + if: matrix.php-version == '8.5' run: composer update --ignore-platform-reqs --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi - name: Execute Tests diff --git a/.github/workflows/static.yml b/.github/workflows/psalm.yml similarity index 90% rename from .github/workflows/static.yml rename to .github/workflows/psalm.yml index 71d866e..143566f 100644 --- a/.github/workflows/static.yml +++ b/.github/workflows/psalm.yml @@ -1,5 +1,5 @@ on: - pull_request: + pull_request: null push: branches: - '*.*' diff --git a/.gitignore b/.gitignore index 9557de9..898c3b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ -.idea/ -.env +/.*/ +!/.github/ +/runtime/ +/vendor/ composer.lock -vendor/ -*.db -clover.xml -docker-compose.override.yml -.phpunit.result.cache diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 0000000..1174bb0 --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..f38652c --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,13 @@ +include(__DIR__ . '/src') + ->include(__DIR__ . '/tests') + ->include(__FILE__) + ->cache('./runtime/php-cs-fixer.cache') + ->allowRisky(true) + ->build(); diff --git a/.styleci.yml b/.styleci.yml deleted file mode 100644 index 1edd305..0000000 --- a/.styleci.yml +++ /dev/null @@ -1,75 +0,0 @@ -preset: psr12 -risky: true - -version: 8 - -enabled: - - alpha_ordered_traits - - array_indentation - - array_push - - combine_consecutive_issets - - combine_consecutive_unsets - - combine_nested_dirname - - declare_strict_types - - dir_constant - - fully_qualified_strict_types - - function_to_constant - - hash_to_slash_comment - - is_null - - logical_operators - - magic_constant_casing - - magic_method_casing - - method_separation - - modernize_types_casting - - native_function_casing - - native_function_type_declaration_casing - - no_alias_functions - - no_empty_comment - - no_empty_phpdoc - - no_empty_statement - - no_extra_block_blank_lines - - no_short_bool_cast - - no_superfluous_elseif - - no_unneeded_control_parentheses - - no_unneeded_curly_braces - - no_unneeded_final_method - - no_unset_cast - - no_unused_imports - - no_unused_lambda_imports - - no_useless_else - - no_useless_return - - normalize_index_brace - - php_unit_dedicate_assert - - php_unit_dedicate_assert_internal_type - - php_unit_expectation - - php_unit_mock - - php_unit_mock_short_will_return - - php_unit_namespaced - - php_unit_no_expectation_annotation - - phpdoc_no_empty_return - - phpdoc_no_useless_inheritdoc - - phpdoc_order - - phpdoc_property - - phpdoc_scalar - - phpdoc_separation - - phpdoc_singular_inheritdoc - - phpdoc_trim - - phpdoc_trim_consecutive_blank_line_separation - - phpdoc_type_to_var - - phpdoc_types - - phpdoc_types_order - - print_to_echo - - regular_callable_call - - return_assignment - - self_accessor - - self_static_accessor - - set_type_to_cast - - short_array_syntax - - short_list_syntax - - simplified_if_return - - single_quote - - standardize_not_equals - - ternary_to_null_coalescing - - trailing_comma_in_multiline_array - - unalign_double_arrow - - unalign_equals diff --git a/README.md b/README.md index aaed4b5..ec92f11 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ $container = new Container(); $commandGenerator = new EventDrivenCommandGenerator($schema, $container); $orm = new ORM( - factory: $factory, - schema: $schema, - commandGenerator: $commandGenerator + factory: $factory, + schema: $schema, + commandGenerator: $commandGenerator, ); ``` diff --git a/composer.json b/composer.json index 629e93a..4ae67e0 100644 --- a/composer.json +++ b/composer.json @@ -37,17 +37,18 @@ "require": { "php": ">=8.0", "psr/event-dispatcher": "^1", - "cycle/orm": "^2.7", + "cycle/orm": "^2.10", "cycle/schema-builder": "^2.8", "psr/container": "^1.0|^2.0", "yiisoft/injector": "^1.0" }, "require-dev": { "cycle/annotated": "^3.0", - "ramsey/uuid": "^4.5", "phpunit/phpunit": "^9.5", + "ramsey/uuid": "^4.5", + "spiral/code-style": "^2.2", "spiral/tokenizer": "^2.8 || ^3.0", - "vimeo/psalm": "^5.11" + "vimeo/psalm": "^5.11 || ^6.8" }, "autoload": { "psr-4": { @@ -63,5 +64,12 @@ "sort-packages": true }, "minimum-stability": "dev", - "prefer-stable": true + "prefer-stable": true, + "scripts": { + "cs:diff": "php-cs-fixer fix --dry-run -v --diff", + "cs:fix": "php-cs-fixer fix -v", + "psalm": "psalm", + "psalm:baseline": "psalm --set-baseline=psalm-baseline.xml", + "test": "phpunit --color=always" + } } diff --git a/phpunit.xml b/phpunit.xml index ee1e4b5..dd08bd9 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,10 +1,11 @@ src - \ No newline at end of file + diff --git a/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 0000000..8798835 --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + command]]> + command]]> + command]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/psalm.xml b/psalm.xml index 3aede9f..31b04bb 100644 --- a/psalm.xml +++ b/psalm.xml @@ -1,11 +1,12 @@ @@ -14,6 +15,8 @@ + + diff --git a/src/Attribute/Listen.php b/src/Attribute/Listen.php index 877b15b..5bc030a 100644 --- a/src/Attribute/Listen.php +++ b/src/Attribute/Listen.php @@ -4,17 +4,15 @@ namespace Cycle\ORM\Entity\Behavior\Attribute; -use Attribute; use Cycle\ORM\Entity\Behavior\Event\MapperEvent; -#[Attribute(flags: Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)] +#[\Attribute(flags: \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)] final class Listen { /** * @param class-string $event */ public function __construct( - public string $event - ) { - } + public string $event, + ) {} } diff --git a/src/CreatedAt.php b/src/CreatedAt.php index 10a286e..dc3d86d 100644 --- a/src/CreatedAt.php +++ b/src/CreatedAt.php @@ -41,23 +41,11 @@ final class CreatedAt extends BaseModifier public function __construct( private string $field = 'createdAt', - ?string $column = null + ?string $column = null, ) { $this->column = $column; } - protected function getListenerClass(): string - { - return Listener::class; - } - - protected function getListenerArgs(): array - { - return [ - 'field' => $this->field - ]; - } - public function compute(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); @@ -80,4 +68,16 @@ public function render(Registry $registry): void ->nullable(false) ->defaultValue(AbstractColumn::DATETIME_NOW); } + + protected function getListenerClass(): string + { + return Listener::class; + } + + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + ]; + } } diff --git a/src/Dispatcher/ListenerProvider.php b/src/Dispatcher/ListenerProvider.php index 2182fd0..afe8c0f 100644 --- a/src/Dispatcher/ListenerProvider.php +++ b/src/Dispatcher/ListenerProvider.php @@ -27,21 +27,21 @@ final class ListenerProvider implements ListenerProviderInterface public function __construct( SchemaInterface $schema, - ContainerInterface $container + ContainerInterface $container, ) { $this->configure($schema, new Injector($container)); } public function getListenersForEvent(object $event): iterable { - assert($event instanceof MapperEvent); + \assert($event instanceof MapperEvent); $role = $event->role; - if (!array_key_exists($role, $this->listeners)) { + if (!\array_key_exists($role, $this->listeners)) { return []; } - if (!array_key_exists($event::class, $this->listeners[$role])) { + if (!\array_key_exists($event::class, $this->listeners[$role])) { return []; } @@ -52,7 +52,7 @@ private function configure(SchemaInterface $schema, Injector $injector): void { foreach ($schema->getRoles() as $role) { $config = $schema->define($role, SchemaInterface::LISTENERS); - if (!is_array($config) || $config === []) { + if (!\is_array($config) || $config === []) { continue; } $this->resolveListeners($role, $config, $injector); @@ -62,9 +62,9 @@ private function configure(SchemaInterface $schema, Injector $injector): void private function resolveListeners(string $role, array $config, Injector $injector): void { foreach ($config as $definition) { - assert(is_array($definition) || is_string($definition)); + \assert(\is_array($definition) || \is_string($definition)); - $definition = (array)$definition; + $definition = (array) $definition; if (!$this->validateBehaviorDefinition($definition)) { continue; @@ -102,12 +102,12 @@ private function resolveListeners(string $role, array $config, Injector $injecto private function validateBehaviorDefinition(mixed $definition): bool { - if (!class_exists($definition[self::DEFINITION_CLASS], true)) { + if (!\class_exists($definition[self::DEFINITION_CLASS], true)) { return false; } if ( - array_key_exists(self::DEFINITION_ARGS, $definition) && - !is_array($definition[self::DEFINITION_ARGS]) + \array_key_exists(self::DEFINITION_ARGS, $definition) && + !\is_array($definition[self::DEFINITION_ARGS]) ) { return false; } @@ -122,23 +122,23 @@ private function getProvidedListeners(EventListProviderInterface $listener): arr $events = $listener->getEventsList(); foreach ($events as $event) { // validate - if (!is_array($event) || array_keys($event) !== [0, 1]) { + if (!\is_array($event) || \array_keys($event) !== [0, 1]) { throw new RuntimeException( - sprintf( + \sprintf( 'The method %s::getEventsList() should return list of tuples [event-class, listener-method].', - $listener::class - ) + $listener::class, + ), ); } $method = $event[1]; $callable = [$listener, $method]; if (!\is_callable($callable)) { throw new RuntimeException( - sprintf( + \sprintf( 'Cann\'t build callable from instance of `%s` and `%s` method name.', $listener::class, - $method - ) + $method, + ), ); } } @@ -158,13 +158,13 @@ private function findListenersInAttributes(string $class): array foreach ($method->getAttributes(Listen::class) as $attribute) { try { $listen = $attribute->newInstance(); - assert($listen instanceof Listen); + \assert($listen instanceof Listen); } catch (\Throwable $e) { - throw new RuntimeException(sprintf( + throw new RuntimeException(\sprintf( "Cann't instantiate attribute %s in the %s::%s method.", Listen::class, $class, - $method->getName() + $method->getName(), ), 0, $e); } $result[] = [$listen->event, $method->getName()]; diff --git a/src/Event/Mapper/Command/OnCreate.php b/src/Event/Mapper/Command/OnCreate.php index 6c995c7..0d1c68e 100644 --- a/src/Event/Mapper/Command/OnCreate.php +++ b/src/Event/Mapper/Command/OnCreate.php @@ -6,6 +6,4 @@ use Cycle\ORM\Entity\Behavior\Event\Mapper\QueueCommand; -final class OnCreate extends QueueCommand -{ -} +final class OnCreate extends QueueCommand {} diff --git a/src/Event/Mapper/Command/OnDelete.php b/src/Event/Mapper/Command/OnDelete.php index 35fbce1..4e3c3d3 100644 --- a/src/Event/Mapper/Command/OnDelete.php +++ b/src/Event/Mapper/Command/OnDelete.php @@ -6,6 +6,4 @@ use Cycle\ORM\Entity\Behavior\Event\Mapper\QueueCommand; -final class OnDelete extends QueueCommand -{ -} +final class OnDelete extends QueueCommand {} diff --git a/src/Event/Mapper/Command/OnUpdate.php b/src/Event/Mapper/Command/OnUpdate.php index d7cba57..94f09fa 100644 --- a/src/Event/Mapper/Command/OnUpdate.php +++ b/src/Event/Mapper/Command/OnUpdate.php @@ -6,6 +6,4 @@ use Cycle\ORM\Entity\Behavior\Event\Mapper\QueueCommand; -final class OnUpdate extends QueueCommand -{ -} +final class OnUpdate extends QueueCommand {} diff --git a/src/Event/MapperEvent.php b/src/Event/MapperEvent.php index 9510af2..e52795b 100644 --- a/src/Event/MapperEvent.php +++ b/src/Event/MapperEvent.php @@ -21,7 +21,6 @@ public function __construct( public Node $node, public State $state, public SourceInterface $source, - public \DateTimeImmutable $timestamp - ) { - } + public \DateTimeImmutable $timestamp, + ) {} } diff --git a/src/EventDrivenCommandGenerator.php b/src/EventDrivenCommandGenerator.php index ef94a7d..c848cbc 100644 --- a/src/EventDrivenCommandGenerator.php +++ b/src/EventDrivenCommandGenerator.php @@ -29,6 +29,28 @@ public function __construct(SchemaInterface $schema, ContainerInterface $contain $this->eventDispatcher = new Dispatcher($listenerProvider); } + public function generateStoreCommand(ORMInterface $orm, Tuple $tuple): ?CommandInterface + { + $this->timestamp = new \DateTimeImmutable(); + + $command = parent::generateStoreCommand($orm, $tuple); + + unset($this->timestamp); + + return $command; + } + + public function generateDeleteCommand(ORMInterface $orm, Tuple $tuple): ?CommandInterface + { + $this->timestamp = new \DateTimeImmutable(); + + $command = parent::generateDeleteCommand($orm, $tuple); + + unset($this->timestamp); + + return $command; + } + protected function storeEntity(ORMInterface $orm, Tuple $tuple, bool $isNew): ?CommandInterface { $role = $tuple->node->getRole(); @@ -52,7 +74,7 @@ protected function generateParentStoreCommand( ORMInterface $orm, Tuple $tuple, string $parentRole, - bool $isNew + bool $isNew, ): ?CommandInterface { $mapper = $orm->getMapper($parentRole); $source = $orm->getSource($parentRole); @@ -81,7 +103,7 @@ protected function deleteEntity(ORMInterface $orm, Tuple $tuple): ?CommandInterf $tuple->node, $tuple->state, $source, - $this->timestamp + $this->timestamp, ); $event->command = parent::deleteEntity($orm, $tuple); @@ -90,26 +112,4 @@ protected function deleteEntity(ORMInterface $orm, Tuple $tuple): ?CommandInterf return $event->command; } - - public function generateStoreCommand(ORMInterface $orm, Tuple $tuple): ?CommandInterface - { - $this->timestamp = new \DateTimeImmutable(); - - $command = parent::generateStoreCommand($orm, $tuple); - - unset($this->timestamp); - - return $command; - } - - public function generateDeleteCommand(ORMInterface $orm, Tuple $tuple): ?CommandInterface - { - $this->timestamp = new \DateTimeImmutable(); - - $command = parent::generateDeleteCommand($orm, $tuple); - - unset($this->timestamp); - - return $command; - } } diff --git a/src/EventListener.php b/src/EventListener.php index 7a020ab..bf4a11a 100644 --- a/src/EventListener.php +++ b/src/EventListener.php @@ -40,17 +40,12 @@ final class EventListener implements SchemaModifierInterface */ public function __construct( private string $listener, - private array $args = [] - ) { - } + private array $args = [], + ) {} - public function compute(Registry $registry): void - { - } + public function compute(Registry $registry): void {} - public function render(Registry $registry): void - { - } + public function render(Registry $registry): void {} public function modifySchema(array &$schema): void { diff --git a/src/Exception/BehaviorCompilationException.php b/src/Exception/BehaviorCompilationException.php index 458ff06..9660cd2 100644 --- a/src/Exception/BehaviorCompilationException.php +++ b/src/Exception/BehaviorCompilationException.php @@ -4,8 +4,4 @@ namespace Cycle\ORM\Entity\Behavior\Exception; -use RuntimeException; - -final class BehaviorCompilationException extends RuntimeException -{ -} +final class BehaviorCompilationException extends \RuntimeException {} diff --git a/src/Exception/Dispatcher/RuntimeException.php b/src/Exception/Dispatcher/RuntimeException.php index 6a51aff..26f44eb 100644 --- a/src/Exception/Dispatcher/RuntimeException.php +++ b/src/Exception/Dispatcher/RuntimeException.php @@ -4,6 +4,4 @@ namespace Cycle\ORM\Entity\Behavior\Exception\Dispatcher; -final class RuntimeException extends \RuntimeException -{ -} +final class RuntimeException extends \RuntimeException {} diff --git a/src/Exception/OptimisticLock/ChangedVersionException.php b/src/Exception/OptimisticLock/ChangedVersionException.php index 4c16457..1c0c736 100644 --- a/src/Exception/OptimisticLock/ChangedVersionException.php +++ b/src/Exception/OptimisticLock/ChangedVersionException.php @@ -8,6 +8,6 @@ class ChangedVersionException extends OptimisticLockException { public function __construct(mixed $old, mixed $new) { - parent::__construct(sprintf('Record version change detected. Old value `%s`, a new value `%s`.', $old, $new)); + parent::__construct(\sprintf('Record version change detected. Old value `%s`, a new value `%s`.', $old, $new)); } } diff --git a/src/Exception/OptimisticLock/OptimisticLockException.php b/src/Exception/OptimisticLock/OptimisticLockException.php index 2c629a2..7d64bc7 100644 --- a/src/Exception/OptimisticLock/OptimisticLockException.php +++ b/src/Exception/OptimisticLock/OptimisticLockException.php @@ -4,6 +4,4 @@ namespace Cycle\ORM\Entity\Behavior\Exception\OptimisticLock; -class OptimisticLockException extends \RuntimeException -{ -} +class OptimisticLockException extends \RuntimeException {} diff --git a/src/Exception/OptimisticLock/RecordIsLockedException.php b/src/Exception/OptimisticLock/RecordIsLockedException.php index 32c7dfb..88116d5 100644 --- a/src/Exception/OptimisticLock/RecordIsLockedException.php +++ b/src/Exception/OptimisticLock/RecordIsLockedException.php @@ -10,7 +10,7 @@ class RecordIsLockedException extends OptimisticLockException { public function __construct(Node $node) { - $message = sprintf('The `%s` record is locked.', $node->getRole()); + $message = \sprintf('The `%s` record is locked.', $node->getRole()); parent::__construct($message); } diff --git a/src/Hook.php b/src/Hook.php index 7ca8484..3595be8 100644 --- a/src/Hook.php +++ b/src/Hook.php @@ -35,7 +35,7 @@ final class Hook extends BaseModifier */ public function __construct( callable $callable, - private array|string $events + private array|string $events, ) { $this->callable = $callable; @@ -54,7 +54,7 @@ protected function getListenerArgs(): array { return [ 'callable' => $this->callable, - 'events' => $this->events + 'events' => $this->events, ]; } } diff --git a/src/Listener/CreatedAt.php b/src/Listener/CreatedAt.php index 9dd9cd8..4e66362 100644 --- a/src/Listener/CreatedAt.php +++ b/src/Listener/CreatedAt.php @@ -10,9 +10,8 @@ final class CreatedAt { public function __construct( - private string $field = 'createdAt' - ) { - } + private string $field = 'createdAt', + ) {} #[Listen(OnCreate::class)] public function __invoke(OnCreate $event): void diff --git a/src/Listener/Hook.php b/src/Listener/Hook.php index c296922..7dcbc99 100644 --- a/src/Listener/Hook.php +++ b/src/Listener/Hook.php @@ -14,7 +14,7 @@ final class Hook public function __construct( callable $callable, - private array $events + private array $events, ) { $this->callable = $callable; } diff --git a/src/Listener/OptimisticLock.php b/src/Listener/OptimisticLock.php index 87e87cd..d4ab6f8 100644 --- a/src/Listener/OptimisticLock.php +++ b/src/Listener/OptimisticLock.php @@ -16,8 +16,6 @@ use Cycle\ORM\Entity\Behavior\Exception\OptimisticLock\RecordIsLockedException; use Cycle\ORM\Heap\Node; use Cycle\ORM\Heap\State; -use DateTimeImmutable; -use DateTimeInterface; use JetBrains\PhpStorm\ExpectedValues; final class OptimisticLock @@ -28,18 +26,22 @@ final class OptimisticLock * Generates current timestamp with microseconds as string */ public const RULE_MICROTIME = 'microtime'; + /** * Uses `random_bytes(32)` under hood */ public const RULE_RAND_STR = 'random-string'; + /** * Only for the numeric column */ public const RULE_INCREMENT = 'increment'; + /** * Only for the column of the `datetime` type */ public const RULE_DATETIME = 'datetime'; + /** * This means that the user manually sets a new version and defines the field */ @@ -53,7 +55,7 @@ final class OptimisticLock public function __construct( private string $field = 'version', #[ExpectedValues(valuesFromClass: self::class)] - private string $rule = self::DEFAULT_RULE + private string $rule = self::DEFAULT_RULE, ) { $this->isKnownRule = $this->rule !== self::RULE_MANUAL; } @@ -108,13 +110,13 @@ private function lock(Node $node, State $state, ScopeCarrierInterface $command): }); } - private function getLockingValue(mixed $previousValue): int|string|DateTimeInterface + private function getLockingValue(mixed $previousValue): int|string|\DateTimeInterface { return match ($this->rule) { - self::RULE_INCREMENT => (int)$previousValue + 1, - self::RULE_DATETIME => new DateTimeImmutable(), + self::RULE_INCREMENT => (int) $previousValue + 1, + self::RULE_DATETIME => new \DateTimeImmutable(), self::RULE_RAND_STR => \bin2hex(\random_bytes(16)), - default => \number_format(\microtime(true), 6, '.', '') + default => \number_format(\microtime(true), 6, '.', ''), }; } } diff --git a/src/Listener/SoftDelete.php b/src/Listener/SoftDelete.php index 2ad133d..113a79b 100644 --- a/src/Listener/SoftDelete.php +++ b/src/Listener/SoftDelete.php @@ -13,8 +13,7 @@ final class SoftDelete { public function __construct( private string $field = 'deletedAt', - ) { - } + ) {} #[Listen(OnDelete::class)] public function __invoke(OnDelete $event): void diff --git a/src/Listener/UpdatedAt.php b/src/Listener/UpdatedAt.php index 626318e..b96a87d 100644 --- a/src/Listener/UpdatedAt.php +++ b/src/Listener/UpdatedAt.php @@ -13,8 +13,15 @@ final class UpdatedAt { public function __construct( private string $field = 'updatedAt', - private bool $nullable = false - ) { + private bool $nullable = false, + ) {} + + #[Listen(OnCreate::class)] + public function onCreate(OnCreate $event): void + { + if (!$this->nullable) { + $event->state->register($this->field, $event->timestamp); + } } #[Listen(OnUpdate::class)] @@ -24,12 +31,4 @@ public function __invoke(OnUpdate $event): void $event->command->registerAppendix($this->field, $event->timestamp); } } - - #[Listen(OnCreate::class)] - public function onCreate(OnCreate $event): void - { - if (!$this->nullable) { - $event->state->register($this->field, $event->timestamp); - } - } } diff --git a/src/OptimisticLock.php b/src/OptimisticLock.php index b37052d..1ed35b7 100644 --- a/src/OptimisticLock.php +++ b/src/OptimisticLock.php @@ -45,7 +45,6 @@ final class OptimisticLock extends BaseModifier public const RULE_INCREMENT = Listener::RULE_INCREMENT; public const RULE_DATETIME = Listener::RULE_DATETIME; public const RULE_MANUAL = Listener::RULE_MANUAL; - private const DEFAULT_INT_VERSION = 1; private const STRING_COLUMN_LENGTH = 32; @@ -61,25 +60,11 @@ public function __construct( ?string $column = null, /** @Enum({"microtime", "random-string", "increment", "datetime"}) */ #[ExpectedValues(valuesFromClass: Listener::class)] - private ?string $rule = null + private ?string $rule = null, ) { $this->column = $column; } - protected function getListenerClass(): string - { - return Listener::class; - } - - #[ArrayShape(['field' => 'string', 'rule' => 'null|string'])] - protected function getListenerArgs(): array - { - return [ - 'field' => $this->field, - 'rule' => $this->rule - ]; - } - public function compute(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); @@ -93,12 +78,26 @@ public function compute(Registry $registry): void public function render(Registry $registry): void { $this->column = (new RegistryModifier($registry, $this->role)) - ->findColumnName($this->field, $this->column) + ->findColumnName($this->field, $this->column) ?? $this->field; $this->addField($registry); } + protected function getListenerClass(): string + { + return Listener::class; + } + + #[ArrayShape(['field' => 'string', 'rule' => 'null|string'])] + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'rule' => $this->rule, + ]; + } + /** * Compute rule based on column type * @@ -114,7 +113,7 @@ private function computeRule(Field $field): string RegistryModifier::isIntegerType($type) => self::RULE_INCREMENT, RegistryModifier::isStringType($type) => self::RULE_MICROTIME, RegistryModifier::isDatetimeType($type) => self::RULE_DATETIME, - default => throw new BehaviorCompilationException('Failed to compute rule based on column type.') + default => throw new BehaviorCompilationException('Failed to compute rule based on column type.'), }; } @@ -122,7 +121,7 @@ private function addField(Registry $registry): void { $fields = $registry->getEntity($this->role)->getFields(); - assert($this->column !== null); + \assert($this->column !== null); $this->rule ??= $fields->has($this->field) ? $this->computeRule($fields->get($this->field)) @@ -137,7 +136,7 @@ private function addField(Registry $registry): void ->addIntegerColumn( $this->column, $this->field, - GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE + GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, ) ->nullable(false) ->defaultValue(self::DEFAULT_INT_VERSION); @@ -148,7 +147,7 @@ private function addField(Registry $registry): void ->addStringColumn( $this->column, $this->field, - GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE + GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, ) ->nullable(false) ->string(self::STRING_COLUMN_LENGTH); @@ -157,18 +156,18 @@ private function addField(Registry $registry): void $modifier->addDatetimeColumn( $this->column, $this->field, - GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE + GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, ); break; default: throw new BehaviorCompilationException( - sprintf( + \sprintf( 'Wrong rule `%s` for the %s behavior in the `%s.%s` field.', $this->rule, self::class, $this->role, - $this->field - ) + $this->field, + ), ); } } diff --git a/src/Schema/BaseModifier.php b/src/Schema/BaseModifier.php index 7e7a339..38dd329 100644 --- a/src/Schema/BaseModifier.php +++ b/src/Schema/BaseModifier.php @@ -13,23 +13,9 @@ abstract class BaseModifier implements SchemaModifierInterface { protected string $role; - /** - * @return class-string - */ - abstract protected function getListenerClass(): string; - - /** - * @return array - */ - abstract protected function getListenerArgs(): array; - - public function compute(Registry $registry): void - { - } + public function compute(Registry $registry): void {} - public function render(Registry $registry): void - { - } + public function render(Registry $registry): void {} final public function withRole(string $role): static { @@ -51,4 +37,14 @@ final public function modifySchema(array &$schema): void ListenerProvider::DEFINITION_ARGS => $args, ]; } + + /** + * @return class-string + */ + abstract protected function getListenerClass(): string; + + /** + * @return array + */ + abstract protected function getListenerArgs(): array; } diff --git a/src/Schema/RegistryModifier.php b/src/Schema/RegistryModifier.php index 52411ca..9744f81 100644 --- a/src/Schema/RegistryModifier.php +++ b/src/Schema/RegistryModifier.php @@ -51,11 +51,39 @@ public function __construct(Registry $registry, string $role) $this->defaults = $registry->getDefaults(); } + public static function isIntegerType(string $type): bool + { + \preg_match(self::DEFINITION, $type, $matches); + + return \in_array($matches['type'], self::INTEGER_TYPES, true); + } + + public static function isDatetimeType(string $type): bool + { + \preg_match(self::DEFINITION, $type, $matches); + + return \in_array($matches['type'], self::DATETIME_TYPES, true); + } + + public static function isStringType(string $type): bool + { + \preg_match(self::DEFINITION, $type, $matches); + + return $matches['type'] === 'string'; + } + + public static function isUuidType(string $type): bool + { + \preg_match(self::DEFINITION, $type, $matches); + + return $matches['type'] === 'uuid'; + } + public function addDatetimeColumn(string $columnName, string $fieldName, int|null $generated = null): AbstractColumn { if ($this->fields->has($fieldName)) { if (!static::isDatetimeType($this->fields->get($fieldName)->getType())) { - throw new BehaviorCompilationException(sprintf('Field %s must be of type datetime.', $fieldName)); + throw new BehaviorCompilationException(\sprintf('Field %s must be of type datetime.', $fieldName)); } $this->validateColumnName($fieldName, $columnName); $this->fields->get($fieldName)->setGenerated($generated); @@ -77,7 +105,7 @@ public function addIntegerColumn(string $columnName, string $fieldName, int|null { if ($this->fields->has($fieldName)) { if (!static::isIntegerType($this->fields->get($fieldName)->getType())) { - throw new BehaviorCompilationException(sprintf('Field %s must be of type integer.', $fieldName)); + throw new BehaviorCompilationException(\sprintf('Field %s must be of type integer.', $fieldName)); } $this->validateColumnName($fieldName, $columnName); $this->fields->get($fieldName)->setGenerated($generated); @@ -99,7 +127,7 @@ public function addStringColumn(string $columnName, string $fieldName, int|null { if ($this->fields->has($fieldName)) { if (!static::isStringType($this->fields->get($fieldName)->getType())) { - throw new BehaviorCompilationException(sprintf('Field %s must be of type string.', $fieldName)); + throw new BehaviorCompilationException(\sprintf('Field %s must be of type string.', $fieldName)); } $this->validateColumnName($fieldName, $columnName); $this->fields->get($fieldName)->setGenerated($generated); @@ -120,7 +148,7 @@ public function addUuidColumn(string $columnName, string $fieldName, int|null $g { if ($this->fields->has($fieldName)) { if (!static::isUuidType($this->fields->get($fieldName)->getType())) { - throw new BehaviorCompilationException(sprintf('Field %s must be of type uuid.', $fieldName)); + throw new BehaviorCompilationException(\sprintf('Field %s must be of type uuid.', $fieldName)); } $this->validateColumnName($fieldName, $columnName); $this->fields->get($fieldName)->setGenerated($generated); @@ -158,7 +186,7 @@ public function setTypecast(Field $field, array|string|null $rule, string $handl } $handlers = $this->entity->getTypecast() ?? []; - if (!is_array($handlers)) { + if (!\is_array($handlers)) { $handlers = [$handlers]; } @@ -178,13 +206,13 @@ protected function validateColumnName(string $fieldName, string $columnName): vo if ($field->getColumn() !== $columnName) { throw new BehaviorCompilationException( - sprintf( + \sprintf( 'Ambiguous column name definition. ' . 'The `%s` field already linked with the `%s` column but the behavior expects `%s`.', $fieldName, $field->getColumn(), - $columnName - ) + $columnName, + ), ); } } @@ -212,32 +240,4 @@ protected function isType(string $type, string $fieldName, string $columnName): return $this->table->column($columnName)->getType() === $type; } - - public static function isIntegerType(string $type): bool - { - \preg_match(self::DEFINITION, $type, $matches); - - return \in_array($matches['type'], self::INTEGER_TYPES, true); - } - - public static function isDatetimeType(string $type): bool - { - \preg_match(self::DEFINITION, $type, $matches); - - return \in_array($matches['type'], self::DATETIME_TYPES, true); - } - - public static function isStringType(string $type): bool - { - \preg_match(self::DEFINITION, $type, $matches); - - return $matches['type'] === 'string'; - } - - public static function isUuidType(string $type): bool - { - \preg_match(self::DEFINITION, $type, $matches); - - return $matches['type'] === 'uuid'; - } } diff --git a/src/SoftDelete.php b/src/SoftDelete.php index 02061ca..50b641f 100644 --- a/src/SoftDelete.php +++ b/src/SoftDelete.php @@ -43,23 +43,11 @@ final class SoftDelete extends BaseModifier public function __construct( private string $field = 'deletedAt', - ?string $column = null + ?string $column = null, ) { $this->column = $column; } - protected function getListenerClass(): string - { - return Listener::class; - } - - protected function getListenerArgs(): array - { - return [ - 'field' => $this->field, - ]; - } - public function compute(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); @@ -80,4 +68,16 @@ public function render(Registry $registry): void $modifier->addDatetimeColumn($this->column, $this->field, GeneratedField::BEFORE_UPDATE) ->nullable(true); } + + protected function getListenerClass(): string + { + return Listener::class; + } + + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + ]; + } } diff --git a/src/UpdatedAt.php b/src/UpdatedAt.php index 605c14d..a46acba 100644 --- a/src/UpdatedAt.php +++ b/src/UpdatedAt.php @@ -43,24 +43,11 @@ final class UpdatedAt extends BaseModifier public function __construct( private string $field = 'updatedAt', ?string $column = null, - private bool $nullable = false + private bool $nullable = false, ) { $this->column = $column; } - protected function getListenerClass(): string - { - return Listener::class; - } - - protected function getListenerArgs(): array - { - return [ - 'field' => $this->field, - 'nullable' => $this->nullable - ]; - } - public function compute(Registry $registry): void { $modifier = new RegistryModifier($registry, $this->role); @@ -80,7 +67,20 @@ public function render(Registry $registry): void $modifier->addDatetimeColumn( $this->column, $this->field, - GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE + GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, ); } + + protected function getListenerClass(): string + { + return Listener::class; + } + + protected function getListenerArgs(): array + { + return [ + 'field' => $this->field, + 'nullable' => $this->nullable, + ]; + } } diff --git a/tests/Behavior/Fixtures/CommentService.php b/tests/Behavior/Fixtures/CommentService.php index 7a9014a..af39203 100644 --- a/tests/Behavior/Fixtures/CommentService.php +++ b/tests/Behavior/Fixtures/CommentService.php @@ -10,7 +10,5 @@ class CommentService { #[Listen(OnCreate::class)] - public function eventListener(OnCreate $event): void - { - } + public function eventListener(OnCreate $event): void {} } diff --git a/tests/Behavior/Fixtures/CreatedAt/Post.php b/tests/Behavior/Fixtures/CreatedAt/Post.php index f413ebf..4606020 100644 --- a/tests/Behavior/Fixtures/CreatedAt/Post.php +++ b/tests/Behavior/Fixtures/CreatedAt/Post.php @@ -18,6 +18,7 @@ class Post #[Column(type: 'datetime', nullable: true)] public ?\DateTimeImmutable $createdAt = null; + public ?\DateTimeImmutable $customCreatedAt = null; public ?string $content = null; } diff --git a/tests/Behavior/Fixtures/Hook/Post.php b/tests/Behavior/Fixtures/Hook/Post.php index 3f617f4..36f5a5b 100644 --- a/tests/Behavior/Fixtures/Hook/Post.php +++ b/tests/Behavior/Fixtures/Hook/Post.php @@ -18,6 +18,7 @@ class Post { #[Column(type: 'primary')] public int $id; + public ?string $title = null; public ?string $content = null; public ?string $slug = null; @@ -28,7 +29,7 @@ class Post public static function onCreate(OnCreate $event): void { $event->state->register('createdAt', new \DateTimeImmutable('now', new \DateTimeZone('UTC'))); - $event->state->register('slug', strtolower($event->entity->title)); + $event->state->register('slug', \strtolower($event->entity->title)); } public static function touch(OnCreate|OnUpdate $event): void diff --git a/tests/Behavior/Fixtures/OptimisticLock/Author.php b/tests/Behavior/Fixtures/OptimisticLock/Author.php index 8c61d6d..e0fa34e 100644 --- a/tests/Behavior/Fixtures/OptimisticLock/Author.php +++ b/tests/Behavior/Fixtures/OptimisticLock/Author.php @@ -13,11 +13,7 @@ class Author public function __construct( #[Column(type: 'string', name: 'author_first_name')] public string $firstName, - #[Column(type: 'string', name: 'author_last_name')] public string $lastName, - ) - { - - } + ) {} } diff --git a/tests/Behavior/Fixtures/OptimisticLock/InquiredRelations/Product.php b/tests/Behavior/Fixtures/OptimisticLock/InquiredRelations/Product.php new file mode 100644 index 0000000..cbd16d2 --- /dev/null +++ b/tests/Behavior/Fixtures/OptimisticLock/InquiredRelations/Product.php @@ -0,0 +1,55 @@ +boxItems[] = $boxItem; + } + + public function getBoxItems(): array + { + return $this->boxItems; + } + + public function getBoxItemById(int $id): ?ProductBox + { + foreach ($this->boxItems as $boxItem) { + if ($boxItem->id === $id) { + return $boxItem; + } + } + + return null; + } +} diff --git a/tests/Behavior/Fixtures/OptimisticLock/InquiredRelations/ProductBox.php b/tests/Behavior/Fixtures/OptimisticLock/InquiredRelations/ProductBox.php new file mode 100644 index 0000000..e5fd6af --- /dev/null +++ b/tests/Behavior/Fixtures/OptimisticLock/InquiredRelations/ProductBox.php @@ -0,0 +1,50 @@ +parent = $parent; + $this->boxItem = $boxItem; + $this->count = 9999; + } +} diff --git a/tests/Behavior/Fixtures/OptimisticLock/Product.php b/tests/Behavior/Fixtures/OptimisticLock/Item.php similarity index 96% rename from tests/Behavior/Fixtures/OptimisticLock/Product.php rename to tests/Behavior/Fixtures/OptimisticLock/Item.php index 7a5572a..c587c6f 100644 --- a/tests/Behavior/Fixtures/OptimisticLock/Product.php +++ b/tests/Behavior/Fixtures/OptimisticLock/Item.php @@ -10,7 +10,7 @@ #[Entity] #[OptimisticLock(field: 'revision')] -class Product +class Item { #[Column(type: 'primary')] public int $id; diff --git a/tests/Behavior/Fixtures/OptimisticLock/WithAllParameters.php b/tests/Behavior/Fixtures/OptimisticLock/WithAllParameters.php index f270abd..5723b70 100644 --- a/tests/Behavior/Fixtures/OptimisticLock/WithAllParameters.php +++ b/tests/Behavior/Fixtures/OptimisticLock/WithAllParameters.php @@ -12,7 +12,7 @@ #[OptimisticLock( field: 'revision', column: 'revision_field', - rule: OptimisticLock::RULE_INCREMENT + rule: OptimisticLock::RULE_INCREMENT, )] final class WithAllParameters { diff --git a/tests/Behavior/Fixtures/PostService.php b/tests/Behavior/Fixtures/PostService.php index 038f01e..0744fee 100644 --- a/tests/Behavior/Fixtures/PostService.php +++ b/tests/Behavior/Fixtures/PostService.php @@ -12,9 +12,8 @@ class PostService { public function __construct( private string $foo, - private array $bar - ) { - } + private array $bar, + ) {} public static function update(OnUpdate $event): void { diff --git a/tests/Behavior/Fixtures/SoftDelete/Post.php b/tests/Behavior/Fixtures/SoftDelete/Post.php index 5edba57..ab08eb9 100644 --- a/tests/Behavior/Fixtures/SoftDelete/Post.php +++ b/tests/Behavior/Fixtures/SoftDelete/Post.php @@ -18,6 +18,7 @@ class Post #[Column(type: 'datetime', nullable: true)] public ?\DateTimeImmutable $deletedAt = null; + public ?\DateTimeImmutable $customDeletedAt = null; public ?string $content = null; } diff --git a/tests/Behavior/Fixtures/UpdatedAt/Post.php b/tests/Behavior/Fixtures/UpdatedAt/Post.php index c78b15a..33ab313 100644 --- a/tests/Behavior/Fixtures/UpdatedAt/Post.php +++ b/tests/Behavior/Fixtures/UpdatedAt/Post.php @@ -18,6 +18,7 @@ class Post #[Column(type: 'datetime', nullable: true)] public ?\DateTimeImmutable $updatedAt = null; + public ?\DateTimeImmutable $customUpdatedAt = null; public \DateTimeImmutable $notNullableUpdatedAt; public ?string $content = null; diff --git a/tests/Behavior/Functional/Driver/Common/BaseListenerTest.php b/tests/Behavior/Functional/Driver/Common/BaseListenerTest.php index 02da206..6eeaf6e 100644 --- a/tests/Behavior/Functional/Driver/Common/BaseListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/BaseListenerTest.php @@ -17,13 +17,6 @@ abstract class BaseListenerTest extends BaseTest { protected ?ORM $orm = null; - public function tearDown(): void - { - parent::tearDown(); - - $this->orm = null; - } - public function withSchema(SchemaInterface $schema): ORM { $this->orm = new ORM( @@ -31,15 +24,22 @@ public function withSchema(SchemaInterface $schema): ORM $this->dbal, RelationConfig::getDefault(), null, - new ArrayCollectionFactory() + new ArrayCollectionFactory(), ), $schema, - new EventDrivenCommandGenerator($schema, new SimpleContainer()) + new EventDrivenCommandGenerator($schema, new SimpleContainer()), ); return $this->orm; } + public function tearDown(): void + { + parent::tearDown(); + + $this->orm = null; + } + protected function save(object ...$entities): void { $tr = new Transaction($this->orm); diff --git a/tests/Behavior/Functional/Driver/Common/BaseTest.php b/tests/Behavior/Functional/Driver/Common/BaseTest.php index 39e050c..087c08d 100644 --- a/tests/Behavior/Functional/Driver/Common/BaseTest.php +++ b/tests/Behavior/Functional/Driver/Common/BaseTest.php @@ -23,10 +23,27 @@ abstract class BaseTest extends TestCase protected ?DriverInterface $driver = null; private static array $driverCache = []; + public function getDriver(): DriverInterface + { + if (isset(static::$driverCache[static::DRIVER])) { + return static::$driverCache[static::DRIVER]; + } + + $config = self::$config[static::DRIVER]; + if (!isset($this->driver)) { + $this->driver = $config->driver::create($config); + } + + return static::$driverCache[static::DRIVER] = $this->driver; + } + public function setUp(): void { + $this->setUpLogger($this->getDriver()); if (self::$config['debug'] ?? false) { $this->enableProfiling(); + } else { + $this->disableProfiling(); } $this->dbal = new DatabaseManager(new DatabaseConfig()); @@ -34,8 +51,8 @@ public function setUp(): void new Database( 'default', '', - $this->getDriver() - ) + $this->getDriver(), + ), ); } @@ -46,25 +63,11 @@ public function tearDown(): void $this->dbal = null; if (\function_exists('gc_collect_cycles')) { - gc_collect_cycles(); + \gc_collect_cycles(); } } - public function getDriver(): DriverInterface - { - if (isset(static::$driverCache[static::DRIVER])) { - return static::$driverCache[static::DRIVER]; - } - - $config = self::$config[static::DRIVER]; - if (!isset($this->driver)) { - $this->driver = $config->driver::create($config); - } - - return static::$driverCache[static::DRIVER] = $this->driver; - } - - protected function dropDatabase(Database $database = null): void + protected function dropDatabase(?Database $database = null): void { if ($database === null) { return; diff --git a/tests/Behavior/Functional/Driver/Common/CreatedAt/CreatedAtTest.php b/tests/Behavior/Functional/Driver/Common/CreatedAt/CreatedAtTest.php index 156551c..0e8ef3f 100644 --- a/tests/Behavior/Functional/Driver/Common/CreatedAt/CreatedAtTest.php +++ b/tests/Behavior/Functional/Driver/Common/CreatedAt/CreatedAtTest.php @@ -12,16 +12,6 @@ abstract class CreatedAtTest extends BaseSchemaTest { - public function setUp(): void - { - parent::setUp(); - - $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ - 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/CreatedAt'], - 'exclude' => [], - ]))); - } - public function testExistenceColumn(): void { $fields = $this->registry->getEntity(Post::class)->getFields(); @@ -44,4 +34,14 @@ public function testAddedColumn(): void $this->assertSame('datetime', $fields->get('newField')->getType()); $this->assertSame(GeneratedField::BEFORE_INSERT, $fields->get('newField')->getGenerated()); } + + public function setUp(): void + { + parent::setUp(); + + $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/CreatedAt'], + 'exclude' => [], + ]))); + } } diff --git a/tests/Behavior/Functional/Driver/Common/CreatedAt/ListenerTest.php b/tests/Behavior/Functional/Driver/Common/CreatedAt/ListenerTest.php index b1ba580..8a51764 100644 --- a/tests/Behavior/Functional/Driver/Common/CreatedAt/ListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/CreatedAt/ListenerTest.php @@ -17,6 +17,42 @@ abstract class ListenerTest extends BaseListenerTest { use TableTrait; + public function testCreate(): void + { + $post = new Post(); + + $this->save($post); + + $select = new Select($this->orm->with(heap: new Heap()), Post::class); + $data = $select->fetchOne(); + $this->assertNotNull($data->createdAt); + $this->assertNotNull($data->customCreatedAt); + } + + public function testUpdate(): void + { + $post = new Post(); + $this->save($post); + + $this->orm = $this->orm->with(heap: new Heap()); + $select = new Select($this->orm, Post::class); + + $post = $select->fetchOne(); + + $createdAt = $post->createdAt; + $customCreatedAt = $post->customCreatedAt; + $post->content = 'test'; + + $this->save($post); + + $select = new Select($this->orm->with(heap: new Heap()), Post::class); + $data = $select->fetchOne(); + + $this->assertSame(0, $data->createdAt <=> $createdAt); + $this->assertSame(0, $data->customCreatedAt <=> $customCreatedAt); + $this->assertSame('test', $data->content); + } + public function setUp(): void { parent::setUp(); @@ -27,8 +63,8 @@ public function setUp(): void 'id' => 'primary', 'created_at' => 'datetime,nullable', 'custom_created_at' => 'datetime,nullable', - 'content' => 'string,nullable' - ] + 'content' => 'string,nullable', + ], ); $this->withSchema(new Schema([ @@ -41,59 +77,23 @@ public function setUp(): void 'id' => 'id', 'createdAt' => 'created_at', 'customCreatedAt' => 'custom_created_at', - 'content' => 'content' + 'content' => 'content', ], SchemaInterface::LISTENERS => [ CreatedAt::class, [ CreatedAt::class, - ['field' => 'customCreatedAt'] - ] + ['field' => 'customCreatedAt'], + ], ], SchemaInterface::TYPECAST => [ 'id' => 'int', 'createdAt' => 'datetime', - 'customCreatedAt' => 'datetime' + 'customCreatedAt' => 'datetime', ], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], ], ])); } - - public function testCreate(): void - { - $post = new Post(); - - $this->save($post); - - $select = new Select($this->orm->with(heap: new Heap()), Post::class); - $data = $select->fetchOne(); - $this->assertNotNull($data->createdAt); - $this->assertNotNull($data->customCreatedAt); - } - - public function testUpdate(): void - { - $post = new Post(); - $this->save($post); - - $this->orm = $this->orm->with(heap: new Heap()); - $select = new Select($this->orm, Post::class); - - $post = $select->fetchOne(); - - $createdAt = $post->createdAt; - $customCreatedAt = $post->customCreatedAt; - $post->content = 'test'; - - $this->save($post); - - $select = new Select($this->orm->with(heap: new Heap()), Post::class); - $data = $select->fetchOne(); - - $this->assertSame(0, $data->createdAt <=> $createdAt); - $this->assertSame(0, $data->customCreatedAt <=> $customCreatedAt); - $this->assertSame('test', $data->content); - } } diff --git a/tests/Behavior/Functional/Driver/Common/EventListener/EventListenerTest.php b/tests/Behavior/Functional/Driver/Common/EventListener/EventListenerTest.php index 7286572..9de0ac0 100644 --- a/tests/Behavior/Functional/Driver/Common/EventListener/EventListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/EventListener/EventListenerTest.php @@ -15,16 +15,6 @@ abstract class EventListenerTest extends BaseSchemaTest { - public function setUp(): void - { - parent::setUp(); - - $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ - 'directories' => [dirname(__DIR__, 4) . '/Fixtures/EventListener'], - 'exclude' => [], - ]))); - } - public function testSchemaWithArgs(): void { $listeners = $this->schema->define(Post::class, SchemaInterface::LISTENERS); @@ -43,4 +33,14 @@ public function testSchema(): void $this->assertSame(CommentService::class, $listeners[0]); $this->assertCount(1, $listeners); } + + public function setUp(): void + { + parent::setUp(); + + $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/EventListener'], + 'exclude' => [], + ]))); + } } diff --git a/tests/Behavior/Functional/Driver/Common/EventListener/ListenerTest.php b/tests/Behavior/Functional/Driver/Common/EventListener/ListenerTest.php index 709fec2..015bf27 100644 --- a/tests/Behavior/Functional/Driver/Common/EventListener/ListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/EventListener/ListenerTest.php @@ -17,6 +17,19 @@ abstract class ListenerTest extends BaseListenerTest { use TableTrait; + public function testApply(): void + { + $post = new Post(); + + $this->save($post); + + $select = new Select($this->orm->with(heap: new Heap()), Post::class); + $data = $select->fetchOne(); + + $this->assertSame('modified by EventListener', $data->title); + $this->assertSame('baz', $data->content); + } + public function setUp(): void { parent::setUp(); @@ -26,8 +39,8 @@ public function setUp(): void [ 'id' => 'primary', 'title' => 'string,nullable', - 'content' => 'string,nullable' - ] + 'content' => 'string,nullable', + ], ); $this->withSchema(new Schema([ @@ -40,8 +53,8 @@ public function setUp(): void SchemaInterface::LISTENERS => [ [ PostService::class, - ['foo' => 'modified by EventListener', 'bar' => ['baz']] - ] + ['foo' => 'modified by EventListener', 'bar' => ['baz']], + ], ], SchemaInterface::TYPECAST => ['id' => 'int'], SchemaInterface::SCHEMA => [], @@ -49,17 +62,4 @@ public function setUp(): void ], ])); } - - public function testApply(): void - { - $post = new Post(); - - $this->save($post); - - $select = new Select($this->orm->with(heap: new Heap()), Post::class); - $data = $select->fetchOne(); - - $this->assertSame('modified by EventListener', $data->title); - $this->assertSame('baz', $data->content); - } } diff --git a/tests/Behavior/Functional/Driver/Common/Hook/HookTest.php b/tests/Behavior/Functional/Driver/Common/Hook/HookTest.php index 8f6f336..cfe7d10 100644 --- a/tests/Behavior/Functional/Driver/Common/Hook/HookTest.php +++ b/tests/Behavior/Functional/Driver/Common/Hook/HookTest.php @@ -16,16 +16,6 @@ abstract class HookTest extends BaseSchemaTest { - public function setUp(): void - { - parent::setUp(); - - $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ - 'directories' => [dirname(__DIR__, 4) . '/Fixtures/Hook'], - 'exclude' => [], - ]))); - } - public function testAddListener(): void { $listeners = $this->schema->define(Post::class, SchemaInterface::LISTENERS); @@ -38,13 +28,23 @@ public function testAddListener(): void $this->assertSame(Hook::class, $listeners[0][0]); $this->assertSame( ['callable' => [PostService::class, 'update'], 'events' => [OnUpdate::class]], - $listeners[0][1] + $listeners[0][1], ); $this->assertSame(Hook::class, $listeners[1][0]); $this->assertSame( ['callable' => [Post::class, 'touch'], 'events' => [OnCreate::class, OnUpdate::class]], - $listeners[1][1] + $listeners[1][1], ); } + + public function setUp(): void + { + parent::setUp(); + + $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/Hook'], + 'exclude' => [], + ]))); + } } diff --git a/tests/Behavior/Functional/Driver/Common/Hook/ListenerTest.php b/tests/Behavior/Functional/Driver/Common/Hook/ListenerTest.php index 4c6c1a7..0bfe964 100644 --- a/tests/Behavior/Functional/Driver/Common/Hook/ListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/Hook/ListenerTest.php @@ -20,6 +20,49 @@ abstract class ListenerTest extends BaseListenerTest { use TableTrait; + public function testCreate(): void + { + $post = $this->createPost(); + + $select = new Select($this->orm->with(heap: new Heap()), Post::class); + $data = $select->fetchOne(); + + $this->assertNotNull($data->createdAt); + $this->assertNotNull($data->updatedAt); + $this->assertNull($data->content); + $this->assertSame('test', $post->slug); + $this->assertSame(OnCreate::class, $data->lastEvent); + } + + public function testUpdate(): void + { + $this->createPost(); + + $this->orm = $this->orm->with(heap: new Heap()); + $select = new Select($this->orm, Post::class); + + $post = $select->fetchOne(); + $post->title = 'TEST 2'; + $content = $post->content; + $this->save($post); + + $select = new Select($this->orm->with(heap: new Heap()), Post::class); + $data = $select->fetchOne(); + + // Triggered OnUpdate event + $this->assertNull($content); + $this->assertSame('modified by service', $data->content); + + // Not triggered OnCreate event, slug NOT changed. + $this->assertSame('test', $data->slug); + $this->assertSame('TEST 2', $data->title); + + // Triggered union type event OnCreate|OnUpdate + $this->assertNotNull($data->updatedAt); + $this->assertNotNull($data->createdAt); + $this->assertSame(OnUpdate::class, $data->lastEvent); + } + public function setUp(): void { parent::setUp(); @@ -33,8 +76,8 @@ public function setUp(): void 'slug' => 'string,nullable', 'last_event' => 'string,nullable', 'created_at' => 'datetime,nullable', - 'updated_at' => 'datetime,nullable' - ] + 'updated_at' => 'datetime,nullable', + ], ); $this->withSchema(new Schema([ @@ -56,21 +99,21 @@ public function setUp(): void SchemaInterface::LISTENERS => [ [ Hook::class, - ['callable' => PostService::class . '::update', 'events' => [OnUpdate::class]] + ['callable' => PostService::class . '::update', 'events' => [OnUpdate::class]], ], [ Hook::class, - ['callable' => [Post::class, 'onCreate'], 'events' => [OnCreate::class]] + ['callable' => [Post::class, 'onCreate'], 'events' => [OnCreate::class]], ], [ Hook::class, - ['callable' => [Post::class, 'touch'], 'events' => [OnCreate::class, OnUpdate::class]] - ] + ['callable' => [Post::class, 'touch'], 'events' => [OnCreate::class, OnUpdate::class]], + ], ], SchemaInterface::TYPECAST => [ 'id' => 'int', 'createdAt' => 'datetime', - 'updatedAt' => 'datetime' + 'updatedAt' => 'datetime', ], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], @@ -78,49 +121,6 @@ public function setUp(): void ])); } - public function testCreate(): void - { - $post = $this->createPost(); - - $select = new Select($this->orm->with(heap: new Heap()), Post::class); - $data = $select->fetchOne(); - - $this->assertNotNull($data->createdAt); - $this->assertNotNull($data->updatedAt); - $this->assertNull($data->content); - $this->assertSame('test', $post->slug); - $this->assertSame(OnCreate::class, $data->lastEvent); - } - - public function testUpdate(): void - { - $this->createPost(); - - $this->orm = $this->orm->with(heap: new Heap()); - $select = new Select($this->orm, Post::class); - - $post = $select->fetchOne(); - $post->title = 'TEST 2'; - $content = $post->content; - $this->save($post); - - $select = new Select($this->orm->with(heap: new Heap()), Post::class); - $data = $select->fetchOne(); - - // Triggered OnUpdate event - $this->assertNull($content); - $this->assertSame('modified by service', $data->content); - - // Not triggered OnCreate event, slug NOT changed. - $this->assertSame('test', $data->slug); - $this->assertSame('TEST 2', $data->title); - - // Triggered union type event OnCreate|OnUpdate - $this->assertNotNull($data->updatedAt); - $this->assertNotNull($data->createdAt); - $this->assertSame(OnUpdate::class, $data->lastEvent); - } - private function createPost(): Post { $post = new Post(); diff --git a/tests/Behavior/Functional/Driver/Common/OptimisticLock/InquiredRelationTest.php b/tests/Behavior/Functional/Driver/Common/OptimisticLock/InquiredRelationTest.php new file mode 100644 index 0000000..4c9f857 --- /dev/null +++ b/tests/Behavior/Functional/Driver/Common/OptimisticLock/InquiredRelationTest.php @@ -0,0 +1,130 @@ +orm->getRepository(Product::class); + + // Make 2 products + $product1 = new Product(); + $product1->name = 'test'; + $product1->revision = 1; + + $product2 = new Product(); + $product2->name = 'test2'; + $product2->revision = 1; + + // Add box item + $product1->addBoxItem( + new ProductBox( + $product1, + $product2, + ), + ); + + // Persist 2 products + $this->save($product1, $product2); + + // Persist 2 with Fetch all products + $this->assertSame(1, $product1->revision); + $this->assertSame(1, $product2->revision); + $product1->name = '222'; + + // Persist 2 + $this->save($product1); + $this->assertSame(2, $product1->revision); + + // Persist 3 with Fetch all products + $product1->name = '333'; + + $products = $repo->findAll(); + $this->save($product1); + $this->assertEquals(3, $product1->revision); + } + + public function setUp(): void + { + parent::setUp(); + + $schema = $this->compileSchema(new Tokenizer(new TokenizerConfig([ + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/OptimisticLock/InquiredRelations'], + 'exclude' => [], + ]))); + + $this->orm = new ORM( + new Factory( + $this->dbal, + RelationConfig::getDefault(), + null, + new ArrayCollectionFactory(), + ), + $schema, + new EventDrivenCommandGenerator($schema, new SimpleContainer()), + ); + } + + private function compileSchema(Tokenizer $tokenizer): SchemaInterface + { + $reader = new AttributeReader(); + $classLocator = $tokenizer->classLocator(); + return new Schema( + (new Compiler()) + ->compile( + new Registry($this->dbal), + [ + new Embeddings($classLocator, $reader), + new Entities($classLocator, $reader), + new ResetTables(), + new MergeColumns($reader), + new MergeIndexes($reader), + new GenerateRelations(), + new GenerateModifiers(), + new ValidateEntities(), + new RenderTables(), + new RenderRelations(), + new RenderModifiers(), + new GenerateTypecast(), + new SyncTables(), + ], + ), + ); + } +} diff --git a/tests/Behavior/Functional/Driver/Common/OptimisticLock/ListenerTest.php b/tests/Behavior/Functional/Driver/Common/OptimisticLock/ListenerTest.php index 240551b..7ba38f3 100644 --- a/tests/Behavior/Functional/Driver/Common/OptimisticLock/ListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/OptimisticLock/ListenerTest.php @@ -22,92 +22,7 @@ abstract class ListenerTest extends BaseListenerTest { use TableTrait; - public function setUp(): void - { - parent::setUp(); - - $this->makeTable( - 'comments', - [ - 'id' => 'primary', - 'version_int' => 'int', - 'version_str' => 'string', - 'version_datetime' => 'datetime', - 'version_microtime' => 'string', - 'version_custom' => 'int,nullable', - 'content' => 'string,nullable', - 'author_first_name' => 'string', - 'author_last_name' => 'string', - ] - ); - - $this->withSchema(new Schema([ - Comment::class => [ - SchemaInterface::ROLE => 'comment', - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'comments', - SchemaInterface::PRIMARY_KEY => 'id', - SchemaInterface::COLUMNS => [ - 'id' => 'id', - 'versionInt' => 'version_int', - 'versionStr' => 'version_str', - 'versionDatetime' => 'version_datetime', - 'versionMicrotime' => 'version_microtime', - 'versionCustom' => 'version_custom', - 'content' => 'content' - ], - SchemaInterface::LISTENERS => [ - [ - OptimisticLock::class, - ['field' => 'versionInt'] - ], - [ - OptimisticLock::class, - ['field' => 'versionStr', 'rule' => OptimisticLock::RULE_RAND_STR] - ], - [ - OptimisticLock::class, - ['field' => 'versionDatetime', 'rule' => OptimisticLock::RULE_DATETIME] - ], - [ - OptimisticLock::class, - ['field' => 'versionMicrotime', 'rule' => OptimisticLock::RULE_MICROTIME] - ], - [ - OptimisticLock::class, - ['field' => 'versionCustom', 'rule' => OptimisticLock::RULE_MANUAL] - ] - ], - SchemaInterface::TYPECAST => [ - 'id' => 'int', - 'versionInt' => 'int', - 'versionCustom' => 'int', - 'versionDatetime' => 'datetime' - ], - SchemaInterface::SCHEMA => [], - SchemaInterface::RELATIONS => [ - 'author' => [ - Relation::TYPE => Relation::EMBEDDED, - Relation::TARGET => Author::class, - Relation::LOAD => Relation::LOAD_EAGER, - Relation::SCHEMA => [], - ] - ], - ], - Author::class => [ - SchemaInterface::ENTITY => Author::class, - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'comments', - SchemaInterface::PRIMARY_KEY => ['id'], - SchemaInterface::COLUMNS => [ - 'first_name' => 'author_first_name', - 'last_name' => 'author_last_name', - ], - ] - ])); - } - - public function testAddVersionOnCreate() + public function testAddVersionOnCreate(): void { $comment = new Comment(); $comment->content = 'test'; @@ -116,7 +31,7 @@ public function testAddVersionOnCreate() $this->orm = $this->orm->with(heap: new Heap()); $select = new Select($this->orm, Comment::class); - + $comment = $select->fetchOne(); $this->assertSame(1, $comment->versionInt); @@ -125,7 +40,7 @@ public function testAddVersionOnCreate() $this->assertIsString($comment->versionStr); } - public function testManualAddVersionOnCreate() + public function testManualAddVersionOnCreate(): void { $comment = new Comment(); $comment->content = 'test'; @@ -147,7 +62,7 @@ public function testManualAddVersionOnCreate() $this->assertSame('b0b55bb7237bb75611f7df8175e926d1', $comment->versionStr); } - public function testManualVersionControl() + public function testManualVersionControl(): void { $comment = new Comment(); $comment->content = 'test'; @@ -167,7 +82,7 @@ public function testManualVersionControl() $this->assertSame(2, $comment->versionCustom); } - public function testExceptionOnChangeVersion() + public function testExceptionOnChangeVersion(): void { $comment = new Comment(); $comment->content = 'test'; @@ -235,4 +150,89 @@ public function testLock(): void $this->expectExceptionMessage('The `comment` record is locked.'); (new Transaction($orm2))->persist($data2)->run(); } + + public function setUp(): void + { + parent::setUp(); + + $this->makeTable( + 'comments', + [ + 'id' => 'primary', + 'version_int' => 'int', + 'version_str' => 'string', + 'version_datetime' => 'datetime', + 'version_microtime' => 'string', + 'version_custom' => 'int,nullable', + 'content' => 'string,nullable', + 'author_first_name' => 'string', + 'author_last_name' => 'string', + ], + ); + + $this->withSchema(new Schema([ + Comment::class => [ + SchemaInterface::ROLE => 'comment', + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'comments', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => [ + 'id' => 'id', + 'versionInt' => 'version_int', + 'versionStr' => 'version_str', + 'versionDatetime' => 'version_datetime', + 'versionMicrotime' => 'version_microtime', + 'versionCustom' => 'version_custom', + 'content' => 'content', + ], + SchemaInterface::LISTENERS => [ + [ + OptimisticLock::class, + ['field' => 'versionInt'], + ], + [ + OptimisticLock::class, + ['field' => 'versionStr', 'rule' => OptimisticLock::RULE_RAND_STR], + ], + [ + OptimisticLock::class, + ['field' => 'versionDatetime', 'rule' => OptimisticLock::RULE_DATETIME], + ], + [ + OptimisticLock::class, + ['field' => 'versionMicrotime', 'rule' => OptimisticLock::RULE_MICROTIME], + ], + [ + OptimisticLock::class, + ['field' => 'versionCustom', 'rule' => OptimisticLock::RULE_MANUAL], + ], + ], + SchemaInterface::TYPECAST => [ + 'id' => 'int', + 'versionInt' => 'int', + 'versionCustom' => 'int', + 'versionDatetime' => 'datetime', + ], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [ + 'author' => [ + Relation::TYPE => Relation::EMBEDDED, + Relation::TARGET => Author::class, + Relation::LOAD => Relation::LOAD_EAGER, + Relation::SCHEMA => [], + ], + ], + ], + Author::class => [ + SchemaInterface::ENTITY => Author::class, + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'comments', + SchemaInterface::PRIMARY_KEY => ['id'], + SchemaInterface::COLUMNS => [ + 'first_name' => 'author_first_name', + 'last_name' => 'author_last_name', + ], + ], + ])); + } } diff --git a/tests/Behavior/Functional/Driver/Common/OptimisticLock/OptimisticLockTest.php b/tests/Behavior/Functional/Driver/Common/OptimisticLock/OptimisticLockTest.php index 3726263..7a3772e 100644 --- a/tests/Behavior/Functional/Driver/Common/OptimisticLock/OptimisticLockTest.php +++ b/tests/Behavior/Functional/Driver/Common/OptimisticLock/OptimisticLockTest.php @@ -4,12 +4,12 @@ namespace Cycle\ORM\Entity\Behavior\Tests\Functional\Driver\Common\OptimisticLock; -use Cycle\Database\Schema\AbstractColumn; +use Cycle\Database\ColumnInterface; use Cycle\ORM\Entity\Behavior\Tests\Fixtures\OptimisticLock\Comment; use Cycle\ORM\Entity\Behavior\Tests\Fixtures\OptimisticLock\News; use Cycle\ORM\Entity\Behavior\Tests\Fixtures\OptimisticLock\Page; use Cycle\ORM\Entity\Behavior\Tests\Fixtures\OptimisticLock\Post; -use Cycle\ORM\Entity\Behavior\Tests\Fixtures\OptimisticLock\Product; +use Cycle\ORM\Entity\Behavior\Tests\Fixtures\OptimisticLock\Item; use Cycle\ORM\Entity\Behavior\Tests\Fixtures\OptimisticLock\WithAllParameters; use Cycle\ORM\Entity\Behavior\Tests\Functional\Driver\Common\BaseSchemaTest; use Cycle\ORM\Schema\GeneratedField; @@ -18,16 +18,6 @@ abstract class OptimisticLockTest extends BaseSchemaTest { - public function setUp(): void - { - parent::setUp(); - - $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ - 'directories' => [dirname(__DIR__, 4) . '/Fixtures/OptimisticLock'], - 'exclude' => [], - ]))); - } - public function testAddIntColumn(): void { $fields = $this->registry->getEntity(Post::class)->getFields(); @@ -37,7 +27,7 @@ public function testAddIntColumn(): void $this->assertSame('integer', $fields->get('version')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('version')->getGenerated() + $fields->get('version')->getGenerated(), ); } @@ -47,10 +37,10 @@ public function testAddStringColumn(): void $this->assertTrue($fields->has('version')); $this->assertTrue($fields->hasColumn('version')); - $this->assertSame(AbstractColumn::STRING, $fields->get('version')->getType()); + $this->assertSame(ColumnInterface::STRING, $fields->get('version')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('version')->getGenerated() + $fields->get('version')->getGenerated(), ); } @@ -63,20 +53,20 @@ public function testAddDatetimeColumn(): void $this->assertSame('datetime', $fields->get('version')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('version')->getGenerated() + $fields->get('version')->getGenerated(), ); } public function testExistColumn(): void { - $fields = $this->registry->getEntity(Product::class)->getFields(); + $fields = $this->registry->getEntity(Item::class)->getFields(); $this->assertTrue($fields->has('revision')); $this->assertTrue($fields->hasColumn('revision_field')); $this->assertSame('integer', $fields->get('revision')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('revision')->getGenerated() + $fields->get('revision')->getGenerated(), ); // not added new columns @@ -92,7 +82,7 @@ public function testExistColumnAndAllOptimisticLockParameters(): void $this->assertSame('integer', $fields->get('revision')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('revision')->getGenerated() + $fields->get('revision')->getGenerated(), ); // not added new columns @@ -108,9 +98,19 @@ public function testAddDefaultColumAndRule(): void $this->assertSame('integer', $fields->get('version')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('version')->getGenerated() + $fields->get('version')->getGenerated(), ); $this->assertSame(2, $fields->count()); } + + public function setUp(): void + { + parent::setUp(); + + $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/OptimisticLock'], + 'exclude' => [], + ]))); + } } diff --git a/tests/Behavior/Functional/Driver/Common/Schema/ErrorsTest.php b/tests/Behavior/Functional/Driver/Common/Schema/ErrorsTest.php index d9d7238..6c03a83 100644 --- a/tests/Behavior/Functional/Driver/Common/Schema/ErrorsTest.php +++ b/tests/Behavior/Functional/Driver/Common/Schema/ErrorsTest.php @@ -11,11 +11,13 @@ abstract class ErrorsTest extends BaseSchemaTest { - /** @dataProvider errorsProvider */ + /** + * @dataProvider errorsProvider + */ public function testErrors(Tokenizer $tokenizer, string $message): void { $this->expectException(BehaviorCompilationException::class); - $this->expectExceptionMessageMatches(sprintf('/%s/', $message)); + $this->expectExceptionMessageMatches(\sprintf('/%s/', $message)); $this->compileWithTokenizer($tokenizer); } @@ -25,18 +27,18 @@ public function errorsProvider(): array return [ 'Field type' => [ new Tokenizer(new TokenizerConfig([ - 'directories' => [dirname(__DIR__, 4) . '/Fixtures/Wrong/FieldType'], + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/Wrong/FieldType'], 'exclude' => [], ])), - 'Field wrongCreatedAt must be of type datetime.' + 'Field wrongCreatedAt must be of type datetime.', ], 'Column name' => [ new Tokenizer(new TokenizerConfig([ - 'directories' => [dirname(__DIR__, 4) . '/Fixtures/Wrong/ColumnName'], + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/Wrong/ColumnName'], 'exclude' => [], ])), - 'Ambiguous column name definition. The `updatedAt` field already linked with the `custom_updated_at`' - ] + 'Ambiguous column name definition. The `updatedAt` field already linked with the `custom_updated_at`', + ], ]; } } diff --git a/tests/Behavior/Functional/Driver/Common/Schema/RegistryModifierTest.php b/tests/Behavior/Functional/Driver/Common/Schema/RegistryModifierTest.php index 4efe292..c1f9427 100644 --- a/tests/Behavior/Functional/Driver/Common/Schema/RegistryModifierTest.php +++ b/tests/Behavior/Functional/Driver/Common/Schema/RegistryModifierTest.php @@ -21,19 +21,6 @@ abstract class RegistryModifierTest extends BaseTest protected RegistryModifier $modifier; protected Registry $registry; - public function setUp(): void - { - parent::setUp(); - - $this->registry = new Registry($this->dbal); - - $entity = (new Entity())->setRole(self::ROLE_TEST); - $this->registry->register($entity); - $this->registry->linkTable($entity, 'default', 'tests'); - - $this->modifier = new RegistryModifier($this->registry, self::ROLE_TEST); - } - public function testAddDatetimeField(): void { $this->modifier->addDatetimeColumn('created_at', 'createdAt'); @@ -99,7 +86,7 @@ public function testAddTypecast(): void // entity has default typecast $this->assertSame( [Typecast::class, CustomTypecast::class], - $this->registry->getEntity(self::ROLE_TEST)->getTypecast() + $this->registry->getEntity(self::ROLE_TEST)->getTypecast(), ); } @@ -118,7 +105,7 @@ public function testAddTypecastEntityWithTypecast(): void // entity has default typecast and custom typecast $this->assertSame( [CustomTypecast::class, Typecast::class], - $this->registry->getEntity(self::ROLE_TEST)->getTypecast() + $this->registry->getEntity(self::ROLE_TEST)->getTypecast(), ); } @@ -152,4 +139,17 @@ public function testCustomTypecastNotOverridden(): void $this->assertSame(['foo', 'bar'], $field->getTypecast()); } + + public function setUp(): void + { + parent::setUp(); + + $this->registry = new Registry($this->dbal); + + $entity = (new Entity())->setRole(self::ROLE_TEST); + $this->registry->register($entity); + $this->registry->linkTable($entity, 'default', 'tests'); + + $this->modifier = new RegistryModifier($this->registry, self::ROLE_TEST); + } } diff --git a/tests/Behavior/Functional/Driver/Common/SoftDelete/ListenerTest.php b/tests/Behavior/Functional/Driver/Common/SoftDelete/ListenerTest.php index fbb3e76..10e1213 100644 --- a/tests/Behavior/Functional/Driver/Common/SoftDelete/ListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/SoftDelete/ListenerTest.php @@ -18,6 +18,28 @@ abstract class ListenerTest extends BaseListenerTest { use TableTrait; + public function testDelete(): void + { + $this->save(new Post()); + + $select = new Select($this->orm, Post::class); + $post = $select->fetchOne(); + + $this->assertNull($post->deletedAt); + $this->assertNull($post->customDeletedAt); + + $tr = new Transaction($this->orm); + $tr->delete($post); + $tr->run(); + + $select = new Select($this->orm->with(heap: new Heap()), Post::class); + $data = $select->fetchOne(); + + $this->assertInstanceOf(Post::class, $data); + $this->assertNotNull($data->deletedAt); + $this->assertNotNull($data->customDeletedAt); + } + public function setUp(): void { parent::setUp(); @@ -28,8 +50,8 @@ public function setUp(): void 'id' => 'primary', 'deleted_at' => 'datetime,nullable', 'custom_deleted_at' => 'datetime,nullable', - 'content' => 'string,nullable' - ] + 'content' => 'string,nullable', + ], ); $this->withSchema(new Schema([ @@ -42,44 +64,23 @@ public function setUp(): void 'id' => 'id', 'deletedAt' => 'deleted_at', 'customDeletedAt' => 'custom_deleted_at', - 'content' => 'content' + 'content' => 'content', ], SchemaInterface::LISTENERS => [ SoftDelete::class, [ SoftDelete::class, - ['field' => 'customDeletedAt'] - ] + ['field' => 'customDeletedAt'], + ], ], SchemaInterface::TYPECAST => [ 'id' => 'int', 'deletedAt' => 'datetime', - 'customDeletedAt' => 'datetime' + 'customDeletedAt' => 'datetime', ], SchemaInterface::SCHEMA => [], SchemaInterface::RELATIONS => [], ], ])); } - public function testDelete(): void - { - $this->save(new Post()); - - $select = new Select($this->orm, Post::class); - $post = $select->fetchOne(); - - $this->assertNull($post->deletedAt); - $this->assertNull($post->customDeletedAt); - - $tr = new Transaction($this->orm); - $tr->delete($post); - $tr->run(); - - $select = new Select($this->orm->with(heap: new Heap()), Post::class); - $data = $select->fetchOne(); - - $this->assertInstanceOf(Post::class, $data); - $this->assertNotNull($data->deletedAt); - $this->assertNotNull($data->customDeletedAt); - } } diff --git a/tests/Behavior/Functional/Driver/Common/SoftDelete/SoftDeleteTest.php b/tests/Behavior/Functional/Driver/Common/SoftDelete/SoftDeleteTest.php index 0219a75..0f8764c 100644 --- a/tests/Behavior/Functional/Driver/Common/SoftDelete/SoftDeleteTest.php +++ b/tests/Behavior/Functional/Driver/Common/SoftDelete/SoftDeleteTest.php @@ -12,16 +12,6 @@ abstract class SoftDeleteTest extends BaseSchemaTest { - public function setUp(): void - { - parent::setUp(); - - $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ - 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/SoftDelete'], - 'exclude' => [], - ]))); - } - public function testExistenceColumn(): void { $fields = $this->registry->getEntity(Post::class)->getFields(); @@ -44,4 +34,14 @@ public function testAddedColumn(): void $this->assertSame('datetime', $fields->get('newField')->getType()); $this->assertSame(GeneratedField::BEFORE_UPDATE, $fields->get('newField')->getGenerated()); } + + public function setUp(): void + { + parent::setUp(); + + $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/SoftDelete'], + 'exclude' => [], + ]))); + } } diff --git a/tests/Behavior/Functional/Driver/Common/UpdatedAt/ListenerTest.php b/tests/Behavior/Functional/Driver/Common/UpdatedAt/ListenerTest.php index cd9981d..a25a6c6 100644 --- a/tests/Behavior/Functional/Driver/Common/UpdatedAt/ListenerTest.php +++ b/tests/Behavior/Functional/Driver/Common/UpdatedAt/ListenerTest.php @@ -17,60 +17,6 @@ abstract class ListenerTest extends BaseListenerTest { use TableTrait; - public function setUp(): void - { - parent::setUp(); - - $this->makeTable( - 'posts', - [ - 'id' => 'primary', - 'updated_at' => 'datetime,nullable', - 'custom_updated_at' => 'datetime,nullable', - 'not_nullable_updated_at' => 'datetime', - 'content' => 'string,nullable' - ] - ); - - $this->withSchema(new Schema([ - Post::class => [ - SchemaInterface::ROLE => 'post', - SchemaInterface::DATABASE => 'default', - SchemaInterface::TABLE => 'posts', - SchemaInterface::PRIMARY_KEY => 'id', - SchemaInterface::COLUMNS => [ - 'id' => 'id', - 'updatedAt' => 'updated_at', - 'customUpdatedAt' => 'custom_updated_at', - 'notNullableUpdatedAt' => 'not_nullable_updated_at', - 'content' => 'content' - ], - SchemaInterface::LISTENERS => [ - [ - UpdatedAt::class, - ['nullable' => true] - ], - [ - UpdatedAt::class, - ['field' => 'customUpdatedAt', 'nullable' => true] - ], - [ - UpdatedAt::class, - ['field' => 'notNullableUpdatedAt', 'nullable' => false] - ] - ], - SchemaInterface::TYPECAST => [ - 'id' => 'int', - 'updatedAt' => 'datetime', - 'customUpdatedAt' => 'datetime', - 'notNullableUpdatedAt' => 'datetime' - ], - SchemaInterface::SCHEMA => [], - SchemaInterface::RELATIONS => [], - ], - ])); - } - public function testCreate(): void { $post = new Post(); @@ -118,4 +64,58 @@ public function testUpdate(): void $this->assertGreaterThan(0, $data->customUpdatedAt <=> $customUpdatedAt); $this->assertSame('test', $data->content); } + + public function setUp(): void + { + parent::setUp(); + + $this->makeTable( + 'posts', + [ + 'id' => 'primary', + 'updated_at' => 'datetime,nullable', + 'custom_updated_at' => 'datetime,nullable', + 'not_nullable_updated_at' => 'datetime', + 'content' => 'string,nullable', + ], + ); + + $this->withSchema(new Schema([ + Post::class => [ + SchemaInterface::ROLE => 'post', + SchemaInterface::DATABASE => 'default', + SchemaInterface::TABLE => 'posts', + SchemaInterface::PRIMARY_KEY => 'id', + SchemaInterface::COLUMNS => [ + 'id' => 'id', + 'updatedAt' => 'updated_at', + 'customUpdatedAt' => 'custom_updated_at', + 'notNullableUpdatedAt' => 'not_nullable_updated_at', + 'content' => 'content', + ], + SchemaInterface::LISTENERS => [ + [ + UpdatedAt::class, + ['nullable' => true], + ], + [ + UpdatedAt::class, + ['field' => 'customUpdatedAt', 'nullable' => true], + ], + [ + UpdatedAt::class, + ['field' => 'notNullableUpdatedAt', 'nullable' => false], + ], + ], + SchemaInterface::TYPECAST => [ + 'id' => 'int', + 'updatedAt' => 'datetime', + 'customUpdatedAt' => 'datetime', + 'notNullableUpdatedAt' => 'datetime', + ], + SchemaInterface::SCHEMA => [], + SchemaInterface::RELATIONS => [], + ], + ])); + } } diff --git a/tests/Behavior/Functional/Driver/Common/UpdatedAt/UpdatedAtTest.php b/tests/Behavior/Functional/Driver/Common/UpdatedAt/UpdatedAtTest.php index 058b47e..7611859 100644 --- a/tests/Behavior/Functional/Driver/Common/UpdatedAt/UpdatedAtTest.php +++ b/tests/Behavior/Functional/Driver/Common/UpdatedAt/UpdatedAtTest.php @@ -12,16 +12,6 @@ abstract class UpdatedAtTest extends BaseSchemaTest { - public function setUp(): void - { - parent::setUp(); - - $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ - 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/UpdatedAt'], - 'exclude' => [], - ]))); - } - public function testExistenceColumn(): void { $fields = $this->registry->getEntity(Post::class)->getFields(); @@ -31,7 +21,7 @@ public function testExistenceColumn(): void $this->assertSame('datetime', $fields->get('updatedAt')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('updatedAt')->getGenerated() + $fields->get('updatedAt')->getGenerated(), ); // No new fields added @@ -47,7 +37,17 @@ public function testAddedColumn(): void $this->assertSame('datetime', $fields->get('newField')->getType()); $this->assertSame( GeneratedField::BEFORE_INSERT | GeneratedField::BEFORE_UPDATE, - $fields->get('newField')->getGenerated() + $fields->get('newField')->getGenerated(), ); } + + public function setUp(): void + { + parent::setUp(); + + $this->compileWithTokenizer(new Tokenizer(new TokenizerConfig([ + 'directories' => [\dirname(__DIR__, 4) . '/Fixtures/UpdatedAt'], + 'exclude' => [], + ]))); + } } diff --git a/tests/Behavior/Functional/Driver/MySQL/OptimisticLock/InquiredRelationTest.php b/tests/Behavior/Functional/Driver/MySQL/OptimisticLock/InquiredRelationTest.php new file mode 100644 index 0000000..59f6826 --- /dev/null +++ b/tests/Behavior/Functional/Driver/MySQL/OptimisticLock/InquiredRelationTest.php @@ -0,0 +1,17 @@ +getDatabase()->table($table)->getSchema(); $renderer = new TableRenderer(); @@ -25,7 +25,7 @@ public function makeTable( if (isset($options['from'])) { $column = $options['from']; } - $fkState = $schema->foreignKey((array)$column)->references($options['table'], (array)$options['column']); + $fkState = $schema->foreignKey((array) $column)->references($options['table'], (array) $options['column']); $fkState->onUpdate($options['onUpdate'] ?? ForeignKeyInterface::CASCADE); $fkState->onDelete($options['onDelete'] ?? ForeignKeyInterface::CASCADE); } @@ -43,11 +43,11 @@ public function makeFK( string $to, string|array $toColumn, string $onDelete = ForeignKeyInterface::CASCADE, - string $onUpdate = ForeignKeyInterface::CASCADE + string $onUpdate = ForeignKeyInterface::CASCADE, ): void { $schema = $this->getDatabase()->table($from)->getSchema(); - $schema->foreignKey((array)$fromKey) - ->references($to, (array)$toColumn) + $schema->foreignKey((array) $fromKey) + ->references($to, (array) $toColumn) ->onDelete($onDelete) ->onUpdate($onUpdate); $schema->save(); @@ -59,7 +59,7 @@ public function makeCompositeFK( string $to, array $toColumns, string $onDelete = ForeignKeyInterface::CASCADE, - string $onUpdate = ForeignKeyInterface::CASCADE + string $onUpdate = ForeignKeyInterface::CASCADE, ): void { $schema = $this->getDatabase()->table($from)->getSchema(); $schema->foreignKey($fromKeys)->references($to, $toColumns)->onDelete($onDelete)->onUpdate($onUpdate); @@ -69,7 +69,7 @@ public function makeCompositeFK( public function makeIndex( string $table, array $columns, - bool $unique + bool $unique, ): void { $schema = $this->getDatabase()->table($table)->getSchema(); $schema->index($columns)->unique($unique); diff --git a/tests/Behavior/Unit/Schema/RegistryModifierTest.php b/tests/Behavior/Unit/Schema/RegistryModifierTest.php index 6e85224..d5ef5f4 100644 --- a/tests/Behavior/Unit/Schema/RegistryModifierTest.php +++ b/tests/Behavior/Unit/Schema/RegistryModifierTest.php @@ -9,6 +9,40 @@ final class RegistryModifierTest extends TestCase { + public static function integerDataProvider(): \Traversable + { + yield ['int']; + yield ['smallint']; + yield ['tinyint']; + yield ['bigint']; + yield ['integer']; + yield ['tinyInteger']; + yield ['smallInteger']; + yield ['bigInteger']; + yield ['integer(4)']; + } + + public static function datetimeDataProvider(): \Traversable + { + yield ['datetime']; + yield ['datetime2']; + yield ['datetime2(7)']; + } + + public static function stringDataProvider(): \Traversable + { + yield ['string']; + yield ['string(32)']; + } + + public static function invalidDataProvider(): \Traversable + { + yield ['text']; + yield ['json']; + yield ['foo']; + yield ['bar(32)']; + } + /** * @dataProvider integerDataProvider */ @@ -78,38 +112,4 @@ public function testIsUuidTypeFalse(mixed $type): void { $this->assertFalse(RegistryModifier::isUuidType($type)); } - - public static function integerDataProvider(): \Traversable - { - yield ['int']; - yield ['smallint']; - yield ['tinyint']; - yield ['bigint']; - yield ['integer']; - yield ['tinyInteger']; - yield ['smallInteger']; - yield ['bigInteger']; - yield ['integer(4)']; - } - - public static function datetimeDataProvider(): \Traversable - { - yield ['datetime']; - yield ['datetime2']; - yield ['datetime2(7)']; - } - - public static function stringDataProvider(): \Traversable - { - yield ['string']; - yield ['string(32)']; - } - - public static function invalidDataProvider(): \Traversable - { - yield ['text']; - yield ['json']; - yield ['foo']; - yield ['bar(32)']; - } } diff --git a/tests/Behavior/Utils/SimpleContainer.php b/tests/Behavior/Utils/SimpleContainer.php index f5fb8fc..67ebbd9 100644 --- a/tests/Behavior/Utils/SimpleContainer.php +++ b/tests/Behavior/Utils/SimpleContainer.php @@ -5,28 +5,22 @@ namespace Cycle\ORM\Entity\Behavior\Tests\Utils; use Closure; -use Exception; use Psr\Container\ContainerInterface; use Psr\Container\NotFoundExceptionInterface; -use Throwable; final class SimpleContainer implements ContainerInterface { private array $definitions; - private Closure $factory; + private \Closure $factory; /** - * @param array $definitions - * @param Closure|null $factory Should be closure that works like ContainerInterface::get(string $id): mixed + * @param \Closure|null $factory Should be closure that works like ContainerInterface::get(string $id): mixed */ - public function __construct(array $definitions = [], Closure $factory = null) + public function __construct(array $definitions = [], ?\Closure $factory = null) { $this->definitions = $definitions; $this->factory = $factory ?? static function (string $id): void { - throw new class ("No definition or class found for \"$id\".") - extends Exception - implements NotFoundExceptionInterface { - }; + throw new class("No definition or class found for \"$id\".") extends \Exception implements NotFoundExceptionInterface {}; }; } @@ -46,7 +40,7 @@ public function has($id): bool try { $this->get($id); return true; - } catch (Throwable $e) { + } catch (\Throwable $e) { return false; } } diff --git a/tests/Behavior/Utils/TableRenderer.php b/tests/Behavior/Utils/TableRenderer.php index 22f3c41..9db0f49 100644 --- a/tests/Behavior/Utils/TableRenderer.php +++ b/tests/Behavior/Utils/TableRenderer.php @@ -34,10 +34,6 @@ final class TableRenderer * ] * ); * - * @param AbstractTable $table - * @param array $columns - * @param array $defaults - * * @throws SchemaException */ public function renderColumns(AbstractTable $table, array $columns, array $defaults): void @@ -53,12 +49,12 @@ public function renderColumns(AbstractTable $table, array $columns, array $defau $this->renderColumn( $table->column($name), $type, - array_key_exists($name, $defaults), - $defaults[$name] ?? null + \array_key_exists($name, $defaults), + $defaults[$name] ?? null, ); } - if (count($primaryKeys)) { + if (\count($primaryKeys)) { $table->setPrimaryKeys($primaryKeys); } } @@ -82,8 +78,6 @@ public function renderColumns(AbstractTable $table, array $columns, array $defau * * Attention, column state will be affected! * - * @param AbstractColumn $column - * @param array $type * @param bool $hasDefault Must be set to true if default value was set by user. * @param mixed $default Default value declared by record schema. * @@ -103,16 +97,16 @@ protected function renderColumn(AbstractColumn $column, array $type, bool $hasDe try { // bypassing call to AbstractColumn->__call method (or specialized column method) - call_user_func_array([$column, $type['type']], $type['options']); + \call_user_func_array([$column, $type['type']], $type['options']); } catch (\Throwable $e) { throw new SchemaException( "Invalid column type definition in '{$column->getTable()}'.'{$column->getName()}'", $e->getCode(), - $e + $e, ); } - if (in_array($column->getAbstractType(), ['primary', 'bigPrimary'])) { + if (\in_array($column->getAbstractType(), ['primary', 'bigPrimary'])) { // no default value can be set of primary keys return; } @@ -126,7 +120,7 @@ protected function renderColumn(AbstractColumn $column, array $type, bool $hasDe return; } - if (null === $default) { + if ($default === null) { // default value is stated and NULL, clear what to do $column->nullable(true); } @@ -134,20 +128,13 @@ protected function renderColumn(AbstractColumn $column, array $type, bool $hasDe $column->defaultValue($default); } - /** - * @param string $table - * @param string $column - * @param string $definition - * - * @return array - */ protected function parse(string $table, string $column, string $definition): array { if ( - !preg_match( + !\preg_match( '/(?P[a-z]+)(?: *\((?P[^\)]+)\))?(?: *, *(?P.+))?/i', $definition, - $type + $type, ) ) { throw new SchemaException("Invalid column type definition in '{$table}'.'{$column}'"); @@ -156,13 +143,13 @@ protected function parse(string $table, string $column, string $definition): arr if (empty($type['options'])) { $type['options'] = []; } else { - $type['options'] = array_map('trim', explode(',', $type['options'] ?? '')); + $type['options'] = \array_map('trim', \explode(',', $type['options'] ?? '')); } if (empty($type['flags'])) { $type['flags'] = []; } else { - $type['flags'] = array_map('trim', explode(',', $type['flags'] ?? '')); + $type['flags'] = \array_map('trim', \explode(',', $type['flags'] ?? '')); } unset($type[0], $type[1], $type[2], $type[3]); @@ -170,28 +157,20 @@ protected function parse(string $table, string $column, string $definition): arr return $type; } - /** - * @param array $type - * @param string $flag - * - * @return bool - */ protected function hasFlag(array $type, string $flag): bool { - return in_array($flag, $type['flags'], true); + return \in_array($flag, $type['flags'], true); } /** * Cast default value based on column type. Required to prevent conflicts when not nullable * column added to existed table with data in. * - * @param AbstractColumn $column - * * @return mixed */ protected function castDefault(AbstractColumn $column) { - if (in_array($column->getAbstractType(), ['timestamp', 'datetime', 'time', 'date'])) { + if (\in_array($column->getAbstractType(), ['timestamp', 'datetime', 'time', 'date'])) { return 0; } diff --git a/tests/Behavior/Utils/TestLogger.php b/tests/Behavior/Utils/TestLogger.php index 3f62c2a..d0bcd03 100644 --- a/tests/Behavior/Utils/TestLogger.php +++ b/tests/Behavior/Utils/TestLogger.php @@ -34,9 +34,9 @@ public function log(mixed $level, mixed $message, array $context = []): void echo " \n! \033[31m" . $message . "\033[0m"; } elseif ($level === LogLevel::ALERT) { echo " \n! \033[35m" . $message . "\033[0m"; - } elseif (strpos($message, 'SHOW') === 0) { + } elseif (\strpos($message, 'SHOW') === 0) { echo " \n> \033[34m" . $message . "\033[0m"; - } elseif (strpos($message, 'SELECT') === 0) { + } elseif (\strpos($message, 'SELECT') === 0) { echo " \n> \033[32m" . $message . "\033[0m"; } else { echo " \n> \033[33m" . $message . "\033[0m"; diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 676bd1e..b44ce1f 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -5,18 +5,21 @@ use Cycle\Database; // phpcs:disable -define('SPIRAL_INITIAL_TIME', microtime(true)); +\define('SPIRAL_INITIAL_TIME', \microtime(true)); -error_reporting(E_ALL | E_STRICT); -ini_set('display_errors', '1'); -mb_internal_encoding('UTF-8'); +\error_reporting(E_ALL | E_STRICT); +\ini_set('display_errors', '1'); +\mb_internal_encoding('UTF-8'); //Composer -require dirname(__DIR__) . '/vendor/autoload.php'; +require \dirname(__DIR__) . '/vendor/autoload.php'; $drivers = [ 'sqlite' => new Database\Config\SQLiteDriverConfig( queryCache: true, + options: [ + 'logInterpolatedQueries' => true, + ], ), 'mysql' => new Database\Config\MySQLDriverConfig( connection: new Database\Config\MySQL\TcpConnectionConfig( @@ -24,9 +27,12 @@ host: '127.0.0.1', port: 13306, user: 'root', - password: 'root', + password: 'YourStrong!Passw0rd', ), - queryCache: true + queryCache: true, + options: [ + 'logInterpolatedQueries' => true, + ], ), 'postgres' => new Database\Config\PostgresDriverConfig( connection: new Database\Config\Postgres\TcpConnectionConfig( @@ -34,27 +40,35 @@ host: '127.0.0.1', port: 15432, user: 'postgres', - password: 'postgres', + password: 'YourStrong!Passw0rd', ), schema: 'public', queryCache: true, + options: [ + 'logInterpolatedQueries' => true, + ], ), 'sqlserver' => new Database\Config\SQLServerDriverConfig( connection: new Database\Config\SQLServer\TcpConnectionConfig( database: 'tempdb', host: '127.0.0.1', port: 11433, + trustServerCertificate: true, user: 'SA', - password: 'SSpaSS__1' + password: 'YourStrong!Passw0rd', ), - queryCache: true + queryCache: true, + options: [ + 'logInterpolatedQueries' => true, + ], ), ]; -$db = getenv('DB') ?: null; +$db = \getenv('DB') ?: null; \Cycle\ORM\Entity\Behavior\Tests\Functional\Driver\Common\BaseTest::$config = [ - 'debug' => getenv('DB_DEBUG') ?: false, - ] + ($db === null - ? $drivers - : array_intersect_key($drivers, array_flip((array)$db)) - ); + 'debug' => \getenv('DB_DEBUG') ?: false, +] + ( + $db === null + ? $drivers + : \array_intersect_key($drivers, \array_flip((array) $db)) +); diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index fcabddc..4a6c6c1 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,10 +1,10 @@ services: sqlserver: - image: mcr.microsoft.com/mssql/server:2019-CU25-ubuntu-20.04 + image: mcr.microsoft.com/mssql/server:2019-latest ports: - "11433:1433" environment: - SA_PASSWORD: "SSpaSS__1" + SA_PASSWORD: "YourStrong!Passw0rd" ACCEPT_EULA: "Y" mysql_latest: @@ -14,7 +14,7 @@ services: - "13306:3306" environment: MYSQL_DATABASE: "spiral" - MYSQL_ROOT_PASSWORD: "root" + MYSQL_ROOT_PASSWORD: "YourStrong!Passw0rd" MYSQL_ROOT_HOST: "%" postgres: @@ -24,4 +24,4 @@ services: environment: POSTGRES_DB: "spiral" POSTGRES_USER: "postgres" - POSTGRES_PASSWORD: "postgres" + POSTGRES_PASSWORD: "YourStrong!Passw0rd" diff --git a/tests/generate.php b/tests/generate.php index e74e762..a0e51a1 100644 --- a/tests/generate.php +++ b/tests/generate.php @@ -4,11 +4,11 @@ use Spiral\Tokenizer; -error_reporting(E_ALL | E_STRICT); -ini_set('display_errors', '1'); +\error_reporting(E_ALL | E_STRICT); +\ini_set('display_errors', '1'); //Composer -require dirname(__DIR__) . '/vendor/autoload.php'; +require \dirname(__DIR__) . '/vendor/autoload.php'; $tokenizer = new Tokenizer\Tokenizer(new Tokenizer\Config\TokenizerConfig([ 'directories' => [__DIR__ . '/Behavior/Functional/Driver/Common'], @@ -60,31 +60,31 @@ echo "Found {$class->getName()}\n"; - $path = str_replace( - [str_replace('\\', '/', __DIR__), 'Behavior/Functional/Driver/Common/'], + $path = \str_replace( + [\str_replace('\\', '/', __DIR__), 'Behavior/Functional/Driver/Common/'], '', - str_replace('\\', '/', $class->getFileName()) + \str_replace('\\', '/', $class->getFileName()), ); - $path = ltrim($path, '/'); + $path = \ltrim($path, '/'); foreach ($databases as $driver => $details) { - $filename = sprintf('%s%s', $details['directory'], $path); - $dir = pathinfo($filename, PATHINFO_DIRNAME); + $filename = \sprintf('%s%s', $details['directory'], $path); + $dir = \pathinfo($filename, PATHINFO_DIRNAME); - $namespace = str_replace( + $namespace = \str_replace( 'Cycle\\ORM\\Entity\\Behavior\\Tests\\Functional\\Driver\\Common', $details['namespace'], - $class->getNamespaceName() + $class->getNamespaceName(), ); - if (!is_dir($dir)) { - mkdir($dir, recursive: true); + if (!\is_dir($dir)) { + \mkdir($dir, recursive: true); } - file_put_contents( + \file_put_contents( $filename, - sprintf( + \sprintf( <<getName(), $driver, $class->getShortName(), - $driver - ) + $driver, + ), ); } }