From 559e93609fa61ed8eedb4a5f30153a15d855066b Mon Sep 17 00:00:00 2001 From: Alejandro Ibarra Date: Tue, 28 Apr 2026 15:58:53 +0200 Subject: [PATCH] Upgrade to cakephp 5.3 / cakephp migrations 5.x / users 16 --- .github/workflows/ci.yml | 10 +- composer.json | 17 +++- .../20180313201241_initial_jwt_auth.php | 8 +- .../Migrations/20231003201241_auth_store.php | 8 +- patches/flysystem-vfs-null-str-replace.patch | 11 +++ src/ApiInitializer.php | 40 +++++--- src/ApiPlugin.php | 99 +++++++++++++++++++ src/Model/Entity/AuthStore.php | 4 +- src/Plugin.php | 91 +---------------- src/Rbac/ApiRbac.php | 2 +- src/Rbac/CachedApiRbac.php | 8 +- src/Rbac/Permissions/AbstractProvider.php | 8 +- src/Rbac/Permissions/ApiConfigProvider.php | 4 +- src/Service/Action/Action.php | 2 +- src/Service/Action/Auth/JwtLoginAction.php | 4 +- src/Service/Action/Auth/LoginAction.php | 6 +- src/Service/Action/Auth/OtpVerifyAction.php | 2 +- src/Service/Action/Auth/RegisterAction.php | 6 +- .../Auth/ResetPasswordRequestAction.php | 2 +- .../Auth/ValidateAccountRequestAction.php | 2 +- src/Service/Action/CrudDescribeAction.php | 2 +- src/Service/Action/CrudIndexAction.php | 2 +- .../Auth/UserFormattingExtension.php | 18 ++-- .../CrudAutocompleteListExtension.php | 6 +- .../Extension/CrudRelationsExtension.php | 12 +-- .../Extension/CursorPaginateExtension.php | 6 +- .../Extension/ExtendedSortExtension.php | 8 +- .../Action/Extension/FilterExtension.php | 8 +- .../Action/Extension/NestedExtension.php | 20 ++-- .../Action/Extension/PaginateExtension.php | 6 +- .../Action/Extension/SortExtension.php | 8 +- src/Service/Action/ExtensionRegistry.php | 2 +- src/Service/ExtensionRegistry.php | 2 +- src/Service/Locator/ServiceLocator.php | 2 +- src/Service/Service.php | 28 +++--- src/TestSuite/IntegrationTestCase.php | 8 +- src/Transformer/AbstractTransformer.php | 8 +- src/Transformer/TransformerInterface.php | 2 +- tests/Config/api.php | 1 + tests/Config/api_permissions.php | 1 + tests/Config/bootstrap.php | 2 + tests/Config/routes.php | 2 + tests/Config/users.php | 2 + tests/Fixture/ArticlesFixture.php | 2 + tests/Fixture/ArticlesTagsFixture.php | 2 + tests/Fixture/AuthorsFixture.php | 2 + tests/Fixture/SocialAccountsFixture.php | 2 + tests/Fixture/TagsFixture.php | 2 + tests/bootstrap.php | 7 +- 49 files changed, 290 insertions(+), 217 deletions(-) create mode 100644 patches/flysystem-vfs-null-str-replace.patch create mode 100644 src/ApiPlugin.php diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 095a273..45b706f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php-version: ['8.1', '8.2', '8.3'] + php-version: ['8.2', '8.3', '8.4'] db-type: [sqlite, mysql, pgsql] prefer-lowest: [''] @@ -59,7 +59,7 @@ jobs: fi - name: Setup problem matchers for PHPUnit - if: matrix.php-version == '8.1' && matrix.db-type == 'mysql' + if: matrix.php-version == '8.2' && matrix.db-type == 'mysql' run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" - name: Run PHPUnit @@ -67,14 +67,14 @@ jobs: if [[ ${{ matrix.db-type }} == 'sqlite' ]]; then export DB_URL='sqlite:///:memory:'; fi if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp?encoding=utf8'; fi if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi - if [[ ${{ matrix.php-version }} == '8.1' ]]; then + if [[ ${{ matrix.php-version }} == '8.2' ]]; then export CODECOVERAGE=1 && vendor/bin/phpunit --display-deprecations --display-incomplete --display-skipped --coverage-clover=coverage.xml else vendor/bin/phpunit fi - name: Submit code coverage - if: matrix.php-version == '8.1' + if: matrix.php-version == '8.2' uses: codecov/codecov-action@v5 cs-stan: @@ -87,7 +87,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.1' + php-version: '8.2' extensions: mbstring, intl, apcu coverage: none diff --git a/composer.json b/composer.json index f312650..474f887 100644 --- a/composer.json +++ b/composer.json @@ -27,15 +27,16 @@ "source": "https://github.com/CakeDC/cakephp-api" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "ext-json": "*", - "cakephp/cakephp": "^5.0", - "cakedc/users": "^15.1", + "cakephp/cakephp": "^5.1", + "cakedc/users": "^16.0", "lcobucci/jwt": "^5.5.0", - "firebase/php-jwt": "^6.3" + "firebase/php-jwt": "^6.3 || ^7.0" }, "require-dev": { "cakephp/cakephp-codesniffer": "^4.5", + "cweagans/composer-patches": "^1.7", "endroid/qr-code": "^4", "google/recaptcha": "@stable", "laminas/laminas-diactoros": "^3.0", @@ -59,9 +60,17 @@ } }, "prefer-stable": true, + "extra": { + "patches": { + "league/flysystem-vfs": { + "Fix null passed to str_replace in PHP 8.1+": "patches/flysystem-vfs-null-str-replace.patch" + } + } + }, "config": { "sort-packages": true, "allow-plugins": { + "cweagans/composer-patches": true, "dealerdirect/phpcodesniffer-composer-installer": true } }, diff --git a/config/Migrations/20180313201241_initial_jwt_auth.php b/config/Migrations/20180313201241_initial_jwt_auth.php index 63b977e..d89171d 100644 --- a/config/Migrations/20180313201241_initial_jwt_auth.php +++ b/config/Migrations/20180313201241_initial_jwt_auth.php @@ -1,4 +1,6 @@ table('jwt_refresh_tokens', ['id' => false, 'primary_key' => ['id']]) ->addColumn('id', 'uuid', [ diff --git a/config/Migrations/20231003201241_auth_store.php b/config/Migrations/20231003201241_auth_store.php index fcc98ec..102442e 100644 --- a/config/Migrations/20231003201241_auth_store.php +++ b/config/Migrations/20231003201241_auth_store.php @@ -1,4 +1,6 @@ table('auth_store', ['id' => false, 'primary_key' => ['id']]) ->addColumn('id', 'string', [ diff --git a/patches/flysystem-vfs-null-str-replace.patch b/patches/flysystem-vfs-null-str-replace.patch new file mode 100644 index 0000000..67a6aec --- /dev/null +++ b/patches/flysystem-vfs-null-str-replace.patch @@ -0,0 +1,11 @@ +--- a/src/VfsAdapter.php ++++ b/src/VfsAdapter.php +@@ -106,7 +106,7 @@ class VfsAdapter extends Local + protected function wrapPath($path) + { + $scheme = $this->vfs->scheme().'://'; +- $path = str_replace($scheme, null, $path); ++ $path = str_replace($scheme, '', $path ?? ''); + + return $scheme.$path; + } diff --git a/src/ApiInitializer.php b/src/ApiInitializer.php index 03e86f9..10f30b5 100644 --- a/src/ApiInitializer.php +++ b/src/ApiInitializer.php @@ -35,28 +35,39 @@ class ApiInitializer implements AuthorizationServiceProviderInterface public function getAuthenticationService(): AuthenticationService { $service = new AuthenticationService(); - $service->loadIdentifier('Authentication.JwtSubject', []); - $service->loadIdentifier('Authentication.Password', [ - 'resolver' => [ - 'className' => 'Authentication.Orm', - 'userModel' => 'CakeDC/Users.Users', - 'finder' => 'active', - ], - ]); $service->loadAuthenticator('Authentication.Session', [ 'sessionKey' => 'Auth', + 'identifier' => [ + 'Authentication.Password' => [ + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'userModel' => 'CakeDC/Users.Users', + 'finder' => 'active', + ], + ], + ], ]); $service->loadAuthenticator('CakeDC/Auth.Form', [ - // 'sessionKey' => 'Auth', + 'identifier' => [ + 'Authentication.Password' => [ + 'resolver' => [ + 'className' => 'Authentication.Orm', + 'userModel' => 'CakeDC/Users.Users', + 'finder' => 'active', + ], + ], + ], ]); - $service->loadIdentifier('Authentication.Token', [ - 'dataField' => 'token', - 'tokenField' => 'api_token', - ]); $service->loadAuthenticator('Authentication.Token', [ 'queryParam' => 'token', + 'identifier' => [ + 'Authentication.Token' => [ + 'dataField' => 'token', + 'tokenField' => 'api_token', + ], + ], ]); $service->loadAuthenticator('Authentication.Jwt', [ @@ -66,6 +77,9 @@ public function getAuthenticationService(): AuthenticationService 'algorithm' => 'HS512', 'returnPayload' => false, 'secretKey' => Configure::read('Api.Jwt.AccessToken.secret'), + 'identifier' => [ + 'Authentication.JwtSubject' => [], + ], ]); return $service; diff --git a/src/ApiPlugin.php b/src/ApiPlugin.php new file mode 100644 index 0000000..9308566 --- /dev/null +++ b/src/ApiPlugin.php @@ -0,0 +1,99 @@ + $middleware) { + $class = $middleware['class']; + if (array_key_exists('request', $middleware)) { + $requestClass = $middleware['request']; + $request = new $requestClass(); + if (array_key_exists('method', $middleware)) { + $request = $request->{$middleware['method']}(); + } + if (array_key_exists('params', $middleware)) { + $options = $middleware['params']; + $this->registerMiddleware($routes, $alias, new $class($request, $options)); + } else { + $this->registerMiddleware($routes, $alias, new $class($request)); + } + } else { + if (array_key_exists('params', $middleware)) { + $options = $middleware['params']; + $this->registerMiddleware($routes, $alias, new $class($options)); + } else { + $this->registerMiddleware($routes, $alias, new $class()); + } + } + } + + parent::routes($routes); + } + + /** + * Middleware registrator and holder. + * + * @param \Cake\Routing\RouteBuilder $routes Routes. + * @param string $alias Middleware alias. + * @param string $class Middleware class instance. + * @return void + */ + protected function registerMiddleware($routes, $alias, $class) + { + $routes->registerMiddleware($alias, $class); + $this->middlewares[$alias] = $class; + } + + /** + * Register container services for this plugin. + * + * @param \Cake\Core\ContainerInterface $container The container to add services to. + * @return void + */ + public function services(ContainerInterface $container): void + { + if (array_key_exists('apiParser', $this->middlewares)) { + $this->middlewares['apiParser']->setContainer($container); + } + } + + /** + * Add console commands for the plugin. + * + * @param \Cake\Console\CommandCollection $commands The command collection to update + * @return \Cake\Console\CommandCollection + */ + public function console(CommandCollection $commands): CommandCollection + { + return $commands->add('service routes', ServiceRoutesCommand::class); + } +} diff --git a/src/Model/Entity/AuthStore.php b/src/Model/Entity/AuthStore.php index 2006d5f..cfbbb60 100644 --- a/src/Model/Entity/AuthStore.php +++ b/src/Model/Entity/AuthStore.php @@ -10,8 +10,8 @@ * * @property string $id * @property array|null $store - * @property \Cake\I18n\FrozenTime $created - * @property \Cake\I18n\FrozenTime $modified + * @property \Cake\I18n\DateTime $created + * @property \Cake\I18n\DateTime $modified */ class AuthStore extends Entity { diff --git a/src/Plugin.php b/src/Plugin.php index 802ac1f..8c371cc 100644 --- a/src/Plugin.php +++ b/src/Plugin.php @@ -2,98 +2,11 @@ declare(strict_types=1); /** - * Copyright 2016 - 2019, Cake Development Corporation (http://cakedc.com) - * - * Licensed under The MIT License - * Redistributions of files must retain the above copyright notice. - * - * @copyright Copyright 2016 - 2019, Cake Development Corporation (http://cakedc.com) - * @license MIT License (http://www.opensource.org/licenses/mit-license.php) + * @deprecated Use ApiPlugin instead. */ namespace CakeDC\Api; -use Cake\Console\CommandCollection; -use Cake\Core\BasePlugin; -use Cake\Core\Configure; -use Cake\Core\ContainerInterface; -use CakeDC\Api\Command\ServiceRoutesCommand; - -/** - * Api plugin - */ -class Plugin extends BasePlugin +class Plugin extends ApiPlugin { - protected array $middlewares = []; - - /** - * @inheritDoc - */ - public function routes($routes): void - { - $middlewares = Configure::read('Api.Middleware', []); - foreach ($middlewares as $alias => $middleware) { - $class = $middleware['class']; - if (array_key_exists('request', $middleware)) { - $requestClass = $middleware['request']; - $request = new $requestClass(); - if (array_key_exists('method', $middleware)) { - $request = $request->{$middleware['method']}(); - } - if (array_key_exists('params', $middleware)) { - $options = $middleware['params']; - $this->registerMiddleware($routes, $alias, new $class($request, $options)); - } else { - $this->registerMiddleware($routes, $alias, new $class($request)); - } - } else { - if (array_key_exists('params', $middleware)) { - $options = $middleware['params']; - $this->registerMiddleware($routes, $alias, new $class($options)); - } else { - $this->registerMiddleware($routes, $alias, new $class()); - } - } - } - - parent::routes($routes); - } - - /** - * Middleware registrator and holder. - * - * @param \Cake\Routing\RouteBuilder $routes Routes. - * @param string $alias Middleware alias. - * @param string $class Middleware class instance. - * @return void - */ - protected function registerMiddleware($routes, $alias, $class) - { - $routes->registerMiddleware($alias, $class); - $this->middlewares[$alias] = $class; - } - - /** - * Register container services for this plugin. - * - * @param \Cake\Core\ContainerInterface $container The container to add services to. - * @return void - */ - public function services(ContainerInterface $container): void - { - if (array_key_exists('apiParser', $this->middlewares)) { - $this->middlewares['apiParser']->setContainer($container); - } - } - - /** - * Add console commands for the plugin. - * - * @param \Cake\Console\CommandCollection $commands The command collection to update - * @return \Cake\Console\CommandCollection - */ - public function console(CommandCollection $commands): CommandCollection - { - return $commands->add('service routes', ServiceRoutesCommand::class); - } } diff --git a/src/Rbac/ApiRbac.php b/src/Rbac/ApiRbac.php index e09a9d7..f86e963 100644 --- a/src/Rbac/ApiRbac.php +++ b/src/Rbac/ApiRbac.php @@ -68,7 +68,7 @@ class ApiRbac implements RbacInterface * * @param array $config Class configuration */ - public function __construct($config = []) + public function __construct(array $config = []) { if (!isset($config['log'])) { $config['log'] = Configure::read('debug'); diff --git a/src/Rbac/CachedApiRbac.php b/src/Rbac/CachedApiRbac.php index aa75145..9c0188e 100644 --- a/src/Rbac/CachedApiRbac.php +++ b/src/Rbac/CachedApiRbac.php @@ -32,14 +32,14 @@ class CachedApiRbac extends ApiRbac * * @var array[] rules array */ - protected $permissionsMap = []; + protected array $permissionsMap = []; /** * CachedApiRbac constructor. * * @param array $config Class configuration */ - public function __construct($config = []) + public function __construct(array $config = []) { parent::__construct($config); $this->permissionsMap = Cache::remember('api_permissions_map', function () { @@ -52,7 +52,7 @@ public function __construct($config = []) * * @return array */ - public function buildPermissionsMap() + public function buildPermissionsMap(): array { $asArray = function ($permission, $key, $default = null) { if ($default !== null && !array_key_exists($key, $permission)) { @@ -95,7 +95,7 @@ public function buildPermissionsMap() * @param \Psr\Http\Message\ServerRequestInterface $request request * @return bool true if there is a match in permissions */ - public function checkPermissions($user, ServerRequestInterface $request) + public function checkPermissions(array|\ArrayAccess $user, ServerRequestInterface $request): bool { $roleField = $this->getConfig('role_field'); $defaultRole = $this->getConfig('default_role'); diff --git a/src/Rbac/Permissions/AbstractProvider.php b/src/Rbac/Permissions/AbstractProvider.php index 9f86662..2bef020 100644 --- a/src/Rbac/Permissions/AbstractProvider.php +++ b/src/Rbac/Permissions/AbstractProvider.php @@ -35,7 +35,7 @@ abstract class AbstractProvider * * @param array $config config */ - public function __construct($config = []) + public function __construct(array $config = []) { $this->setConfig($config); $this->defaultPermissions = [ @@ -82,12 +82,12 @@ public function __construct($config = []) * * @return array Array of permissions */ - abstract public function getPermissions(); + abstract public function getPermissions(): array; /** * @return array */ - public function getDefaultPermissions() + public function getDefaultPermissions(): array { return $this->defaultPermissions; } @@ -96,7 +96,7 @@ public function getDefaultPermissions() * @param array $defaultPermissions default permissions * @return void */ - public function setDefaultPermissions($defaultPermissions) + public function setDefaultPermissions(array $defaultPermissions): void { $this->defaultPermissions = $defaultPermissions; } diff --git a/src/Rbac/Permissions/ApiConfigProvider.php b/src/Rbac/Permissions/ApiConfigProvider.php index e486097..7d228e2 100644 --- a/src/Rbac/Permissions/ApiConfigProvider.php +++ b/src/Rbac/Permissions/ApiConfigProvider.php @@ -35,7 +35,7 @@ class ApiConfigProvider extends AbstractProvider * * @return array Array of permissions */ - public function getPermissions() + public function getPermissions(): array { $autoload = $this->getConfig('autoload_config'); if ($autoload) { @@ -53,7 +53,7 @@ public function getPermissions() * @param string $key name of the configuration file to read permissions from * @return array permissions */ - protected function _loadPermissions($key) + protected function _loadPermissions(string $key): array { $permissions = null; try { diff --git a/src/Service/Action/Action.php b/src/Service/Action/Action.php index e9854b4..20db40d 100644 --- a/src/Service/Action/Action.php +++ b/src/Service/Action/Action.php @@ -53,7 +53,7 @@ abstract class Action implements EventListenerInterface, EventDispatcherInterfac * * @var array */ - public $extensions = []; + public array $extensions = []; /** * An Auth instance. diff --git a/src/Service/Action/Auth/JwtLoginAction.php b/src/Service/Action/Auth/JwtLoginAction.php index 1d79b96..747de29 100644 --- a/src/Service/Action/Auth/JwtLoginAction.php +++ b/src/Service/Action/Auth/JwtLoginAction.php @@ -31,12 +31,12 @@ class JwtLoginAction extends Action * @param bool $socialLogin is social login * @return array|bool */ - protected function _afterIdentifyUser($user, $socialLogin = false) + protected function _afterIdentifyUser(?array $user, bool $socialLogin = false): array { $user = parent::_afterIdentifyUser($user, $socialLogin); if (empty($user)) { - return false; + return []; } return $this->generateTokenResponse($user, 'login'); diff --git a/src/Service/Action/Auth/LoginAction.php b/src/Service/Action/Auth/LoginAction.php index aa33ade..470d9c9 100644 --- a/src/Service/Action/Auth/LoginAction.php +++ b/src/Service/Action/Auth/LoginAction.php @@ -80,7 +80,7 @@ public function validates(): bool * * @return mixed */ - public function execute() + public function execute(): mixed { $socialLogin = false; $user = $this->Auth->getIdentity(); @@ -107,7 +107,7 @@ public function execute() * @param bool $socialLogin is social login * @return array */ - protected function _afterIdentifyUser($user, $socialLogin = false) + protected function _afterIdentifyUser(?array $user, bool $socialLogin = false): array { if (!empty($user)) { //??? $this->Auth->setUser($user); @@ -118,7 +118,7 @@ protected function _afterIdentifyUser($user, $socialLogin = false) $user = $event->getResult(); } - return $user; + return $user ?? []; } /** diff --git a/src/Service/Action/Auth/OtpVerifyAction.php b/src/Service/Action/Auth/OtpVerifyAction.php index 24bc23f..f3fcb4e 100644 --- a/src/Service/Action/Auth/OtpVerifyAction.php +++ b/src/Service/Action/Auth/OtpVerifyAction.php @@ -30,7 +30,7 @@ abstract class OtpVerifyAction extends Action /** * @var \RobThree\Auth\TwoFactorAuth $tfa */ - public $tfa; + public ?\RobThree\Auth\TwoFactorAuth $tfa = null; /** * initialize diff --git a/src/Service/Action/Auth/RegisterAction.php b/src/Service/Action/Auth/RegisterAction.php index a874e85..d1ce52f 100644 --- a/src/Service/Action/Auth/RegisterAction.php +++ b/src/Service/Action/Auth/RegisterAction.php @@ -51,7 +51,7 @@ public function initialize(array $config): void */ public function validates(): bool { - $validator = $this->getUsersTable()->getRegisterValidators($this->_registerOptions()); + $validator = $this->getUsersTable()->getBehavior('Register')->getRegisterValidators($this->_registerOptions()); $errors = $validator->validate($this->getData()); if (!empty($errors)) { @@ -79,7 +79,7 @@ public function execute() ]); if ($event->getResult() instanceof EntityInterface) { - $userSaved = $usersTable->register($user, $event->getResult()->toArray(), $options); + $userSaved = $usersTable->getBehavior('Register')->register($user, $event->getResult()->toArray(), $options); if ($userSaved) { return $this->_afterRegister($userSaved); } @@ -87,7 +87,7 @@ public function execute() if ($event->isStopped()) { return false; } - $userSaved = $usersTable->register($user, $requestData, $options); + $userSaved = $usersTable->getBehavior('Register')->register($user, $requestData, $options); if (!$userSaved) { $message = __d('CakeDC/Api', 'The user could not be saved'); throw new ValidationException($message, 0, null, $user->getErrors()); diff --git a/src/Service/Action/Auth/ResetPasswordRequestAction.php b/src/Service/Action/Auth/ResetPasswordRequestAction.php index 953fa80..9288957 100644 --- a/src/Service/Action/Auth/ResetPasswordRequestAction.php +++ b/src/Service/Action/Auth/ResetPasswordRequestAction.php @@ -88,7 +88,7 @@ public function execute() }; } - $resetUser = $this->getUsersTable()->resetToken($reference, $options); + $resetUser = $this->getUsersTable()->getBehavior('Password')->resetToken($reference, $options); if ($resetUser) { return __d('CakeDC/Api', 'Please check your email to continue with password reset process'); } else { diff --git a/src/Service/Action/Auth/ValidateAccountRequestAction.php b/src/Service/Action/Auth/ValidateAccountRequestAction.php index 85af62f..b68c5af 100644 --- a/src/Service/Action/Auth/ValidateAccountRequestAction.php +++ b/src/Service/Action/Auth/ValidateAccountRequestAction.php @@ -75,7 +75,7 @@ public function execute() $reference = $data['reference']; try { if ( - $this->getUsersTable()->resetToken($reference, [ + $this->getUsersTable()->getBehavior('Password')->resetToken($reference, [ 'expiration' => Configure::read('Users.Token.expiration'), 'checkActive' => true, 'sendEmail' => true, diff --git a/src/Service/Action/CrudDescribeAction.php b/src/Service/Action/CrudDescribeAction.php index e60dc17..7340990 100644 --- a/src/Service/Action/CrudDescribeAction.php +++ b/src/Service/Action/CrudDescribeAction.php @@ -20,7 +20,7 @@ */ class CrudDescribeAction extends CrudAction { - public $extensions = []; + public array $extensions = []; /** * Execute action. diff --git a/src/Service/Action/CrudIndexAction.php b/src/Service/Action/CrudIndexAction.php index 1ac8cbc..2100eb5 100644 --- a/src/Service/Action/CrudIndexAction.php +++ b/src/Service/Action/CrudIndexAction.php @@ -20,7 +20,7 @@ */ class CrudIndexAction extends CrudAction { - public $extensions = []; + public array $extensions = []; /** * Execute action. diff --git a/src/Service/Action/Extension/Auth/UserFormattingExtension.php b/src/Service/Action/Extension/Auth/UserFormattingExtension.php index 1644db1..939aa61 100644 --- a/src/Service/Action/Extension/Auth/UserFormattingExtension.php +++ b/src/Service/Action/Extension/Auth/UserFormattingExtension.php @@ -45,22 +45,28 @@ public function implementedEvents(): array * On Login Format. * * @param \Cake\Event\Event $event An Event instance - * @return array|null + * @return void */ - public function onLoginFormat(Event $event): ?array + public function onLoginFormat(Event $event): void { - return $this->_userCleanup($event->getData('user')); + $result = $this->_userCleanup($event->getData('user')); + if ($result !== null) { + $event->setResult($result); + } } /** * On Register Format. * * @param \Cake\Event\Event $event An Event instance - * @return array|null + * @return void */ - public function onRegisterFormat(Event $event): ?array + public function onRegisterFormat(Event $event): void { - return $this->_userCleanup($event->getData('user')); + $result = $this->_userCleanup($event->getData('user')); + if ($result !== null) { + $event->setResult($result); + } } /** diff --git a/src/Service/Action/Extension/CrudAutocompleteListExtension.php b/src/Service/Action/Extension/CrudAutocompleteListExtension.php index f79e0b5..50ec97f 100644 --- a/src/Service/Action/Extension/CrudAutocompleteListExtension.php +++ b/src/Service/Action/Extension/CrudAutocompleteListExtension.php @@ -42,16 +42,16 @@ public function implementedEvents(): array * On find entities. * * @param \Cake\Event\Event $event An Event instance. - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(Event $event): SelectQuery + public function findEntities(Event $event): void { /** @var \CakeDC\Api\Service\Action\CrudAction $action */ $action = $event->getSubject(); /** @var \Cake\ORM\Query\SelectQuery $query */ $query = $event->getData('query'); - return $this->_autocompleteList($action, $query); + $event->setResult($this->_autocompleteList($action, $query)); } /** diff --git a/src/Service/Action/Extension/CrudRelationsExtension.php b/src/Service/Action/Extension/CrudRelationsExtension.php index 143d7e9..78ebe86 100644 --- a/src/Service/Action/Extension/CrudRelationsExtension.php +++ b/src/Service/Action/Extension/CrudRelationsExtension.php @@ -48,32 +48,32 @@ public function implementedEvents(): array * On find entity * * @param \Cake\Event\Event $event An Event instance. - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntity(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntity(Event $event): void { /** @var \CakeDC\Api\Service\Action\CrudAction $action */ $action = $event->getSubject(); /** @var \Cake\ORM\Query\SelectQuery $query */ $query = $event->getData('query'); - return $this->_attachAssociations($action, $query); + $event->setResult($this->_attachAssociations($action, $query)); } /** * On find entities. * * @param \Cake\Event\Event $event An Event instance. - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntities(Event $event): void { /** @var \CakeDC\Api\Service\Action\CrudAction $action */ $action = $event->getSubject(); /** @var \Cake\ORM\Query\SelectQuery $query */ $query = $event->getData('query'); - return $this->_attachAssociations($action, $query); + $event->setResult($this->_attachAssociations($action, $query)); } /** diff --git a/src/Service/Action/Extension/CursorPaginateExtension.php b/src/Service/Action/Extension/CursorPaginateExtension.php index c416c61..dc4430a 100644 --- a/src/Service/Action/Extension/CursorPaginateExtension.php +++ b/src/Service/Action/Extension/CursorPaginateExtension.php @@ -69,9 +69,9 @@ public function implementedEvents(): array * find entities * * @param \Cake\Event\Event $event An Event instance. - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntities(Event $event): void { /** @var \CakeDC\Api\Service\Action\Action $action */ $action = $event->getSubject(); @@ -94,7 +94,7 @@ public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery } $query->orderBy([$cursorField => $orderDirection]); - return $query; + $event->setResult($query); } /** diff --git a/src/Service/Action/Extension/ExtendedSortExtension.php b/src/Service/Action/Extension/ExtendedSortExtension.php index bc70915..719e67a 100644 --- a/src/Service/Action/Extension/ExtendedSortExtension.php +++ b/src/Service/Action/Extension/ExtendedSortExtension.php @@ -44,9 +44,9 @@ public function implementedEvents(): array * find entities * * @param \Cake\Event\Event $event An Event instance - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntities(Event $event): void { $action = $event->getSubject(); $query = $event->getData('query'); @@ -61,9 +61,9 @@ public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery $sort = json_decode($data[$sortField], true, 512, JSON_THROW_ON_ERROR); } if (is_array($sort)) { - $query->order($sort); + $query->orderBy($sort); } - return $query; + $event->setResult($query); } } diff --git a/src/Service/Action/Extension/FilterExtension.php b/src/Service/Action/Extension/FilterExtension.php index 0365a62..46a809f 100644 --- a/src/Service/Action/Extension/FilterExtension.php +++ b/src/Service/Action/Extension/FilterExtension.php @@ -40,9 +40,9 @@ public function implementedEvents(): array * find entities * * @param \Cake\Event\Event $event An Event instance - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntities(Event $event): void { $action = $event->getSubject(); $query = $event->getData('query'); @@ -73,7 +73,7 @@ public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery $filter = collection($data) ->filter(function ($item, $key) use ($fields, $postfix, $postfixDelimeter) { if ($postfix !== '') { - if (strpos($key, $postfixDelimeter . $postfix) === false) { + if (!str_contains($key, $postfixDelimeter . $postfix)) { return false; } $key = str_replace($postfixDelimeter . $postfix, '', $key); @@ -110,6 +110,6 @@ public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery } } - return $query; + $event->setResult($query); } } diff --git a/src/Service/Action/Extension/NestedExtension.php b/src/Service/Action/Extension/NestedExtension.php index 991b3eb..58603bd 100644 --- a/src/Service/Action/Extension/NestedExtension.php +++ b/src/Service/Action/Extension/NestedExtension.php @@ -42,9 +42,9 @@ public function implementedEvents(): array * On find entities. * * @param \Cake\Event\Event $event An Event instance - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntities(Event $event): void { /** @var \CakeDC\Api\Service\Action\CrudAction $action */ $action = $event->getSubject(); @@ -59,16 +59,16 @@ public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery $query = $event->getResult(); } - return $query; + $event->setResult($query); } /** * On find entity. * * @param \Cake\Event\Event $event An Event instance - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntity(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntity(Event $event): void { /** @var \CakeDC\Api\Service\Action\CrudAction $action */ $action = $event->getSubject(); @@ -83,16 +83,16 @@ public function findEntity(Event $event): \Cake\ORM\Query\SelectQuery $query = $event->getResult(); } - return $query; + $event->setResult($query); } /** * On patch entity. * * @param \Cake\Event\Event $event An Event instance - * @return \Cake\ORM\Entity + * @return void */ - public function patchEntity(Event $event): \Cake\ORM\Entity + public function patchEntity(Event $event): void { /** @var \CakeDC\Api\Service\Action\CrudAction $action */ $action = $event->getSubject(); @@ -100,8 +100,6 @@ public function patchEntity(Event $event): \Cake\ORM\Entity /** @var \Cake\ORM\Entity $entity */ $entity = $event->getData('entity'); - /** @var \Cake\ORM\Query\SelectQuery $query */ - $query = $event->getData('query'); if ($event->getResult()) { $entity = $event->getResult(); } @@ -112,6 +110,6 @@ public function patchEntity(Event $event): \Cake\ORM\Entity $entity->set($field, $foreignKey); } - return $entity; + $event->setResult($entity); } } diff --git a/src/Service/Action/Extension/PaginateExtension.php b/src/Service/Action/Extension/PaginateExtension.php index b013afe..9024dba 100644 --- a/src/Service/Action/Extension/PaginateExtension.php +++ b/src/Service/Action/Extension/PaginateExtension.php @@ -49,9 +49,9 @@ public function implementedEvents(): array * Find entities * * @param \Cake\Event\EventInterface $event An Event instance - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(EventInterface $event): SelectQuery + public function findEntities(EventInterface $event): void { /** @var \CakeDC\Api\Service\Action\Action $action */ $action = $event->getSubject(); @@ -62,7 +62,7 @@ public function findEntities(EventInterface $event): SelectQuery $query->limit($this->_limit($action)); $query->page($this->_page($action)); - return $query; + $event->setResult($query); } /** diff --git a/src/Service/Action/Extension/SortExtension.php b/src/Service/Action/Extension/SortExtension.php index 4d2cc21..2be43e8 100644 --- a/src/Service/Action/Extension/SortExtension.php +++ b/src/Service/Action/Extension/SortExtension.php @@ -48,9 +48,9 @@ public function implementedEvents(): array * find entities * * @param \Cake\Event\Event $event An Event instance - * @return \Cake\ORM\Query\SelectQuery + * @return void */ - public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery + public function findEntities(Event $event): void { $action = $event->getSubject(); $query = $event->getData('query'); @@ -68,9 +68,9 @@ public function findEntities(Event $event): \Cake\ORM\Query\SelectQuery } if (!empty($data[$sortField])) { $sort = $data[$sortField]; - $query->order([$sort => $direction]); + $query->orderBy([$sort => $direction]); } - return $query; + $event->setResult($query); } } diff --git a/src/Service/Action/ExtensionRegistry.php b/src/Service/Action/ExtensionRegistry.php index ce2e1ec..55aee73 100644 --- a/src/Service/Action/ExtensionRegistry.php +++ b/src/Service/Action/ExtensionRegistry.php @@ -60,7 +60,7 @@ public function __construct(?Action $action = null) protected function _resolveClassName($class): ?string { $result = App::className($class, 'Service/Action/Extension', 'Extension'); - if ($result || strpos($class, '.') !== false) { + if ($result || str_contains($class, '.')) { return $result; } diff --git a/src/Service/ExtensionRegistry.php b/src/Service/ExtensionRegistry.php index 127a537..ecb1255 100644 --- a/src/Service/ExtensionRegistry.php +++ b/src/Service/ExtensionRegistry.php @@ -60,7 +60,7 @@ public function __construct(?Service $service = null) protected function _resolveClassName($class): ?string { $result = App::className($class, 'Service/Extension', 'Extension'); - if ($result || strpos($class, '.') !== false) { + if ($result || str_contains($class, '.')) { return $result; } diff --git a/src/Service/Locator/ServiceLocator.php b/src/Service/Locator/ServiceLocator.php index 8582b45..7305059 100644 --- a/src/Service/Locator/ServiceLocator.php +++ b/src/Service/Locator/ServiceLocator.php @@ -155,7 +155,7 @@ public function get(string $alias, array $options = []): Service if ($lookupPlugins === null) { $lookupPlugins = ['CakeDC/Api']; } - if (!$className && strpos($options['className'], '.') === false) { + if (!$className && !str_contains($options['className'], '.')) { foreach ($lookupPlugins as $candidate) { $_options = $options; if ($lookupMode === 'dasherize') { diff --git a/src/Service/Service.php b/src/Service/Service.php index 532fef7..a4db08d 100644 --- a/src/Service/Service.php +++ b/src/Service/Service.php @@ -242,7 +242,7 @@ public function getName(): ?string * @param string $name Service name. * @return $this */ - public function setName(string $name) + public function setName(string $name): static { $this->_name = $name; @@ -286,7 +286,7 @@ public function getParser(): ?BaseParser * @param \CakeDC\Api\Service\RequestParser\BaseParser $parser A Parser instance. * @return $this */ - public function setParser(BaseParser $parser) + public function setParser(BaseParser $parser): static { $this->_parser = $parser; @@ -328,7 +328,7 @@ public function routes(): array * @param callable $callable Wrapped router instance. * @return mixed */ - protected function _routesWrapper(callable $callable) + protected function _routesWrapper(callable $callable): mixed { $this->resetRoutes(); $this->loadRoutes(); @@ -411,7 +411,7 @@ public function routerDefaultOptions(): array * @return string Full translated URL with base path. * @throws \Cake\Core\Exception\CakeException When the route name is not found */ - public function routeUrl($route): string + public function routeUrl(string|array|null $route): string { return $this->_routesWrapper(fn() => ApiRouter::url($route)); } @@ -423,7 +423,7 @@ public function routeUrl($route): string * Cake\Http\ServerRequest object that needs to be reversed. * @return string The string that is the reversed result of the array */ - public function routeReverse($params): ?string + public function routeReverse(ServerRequest|array $params): ?string { return $this->_routesWrapper(function () use ($params) { try { @@ -506,7 +506,7 @@ public function dispatchPrepareAction(): ?Result * @param \Cake\Http\ServerRequest|\Psr\Http\Message\ServerRequestInterface $request A Request object. * @return \CakeDC\Api\Service\Action\Result */ - public function dispatchProcessAction($request): Result + public function dispatchProcessAction(ServerRequest|\Psr\Http\Message\ServerRequestInterface $request): Result { try { $this->setRequest($request); @@ -539,9 +539,9 @@ public function dispatchProcessAction($request): Result /** * Dispatch service call through callbacks and action. * - * @return \CakeDC\Api\Service\Action\Result|null + * @return mixed */ - protected function _dispatch() + protected function _dispatch(): mixed { $this->_prepareAction(); @@ -553,7 +553,7 @@ protected function _dispatch() * * @return \CakeDC\Api\Service\Action\Result|null */ - protected function _prepareAction() + protected function _prepareAction(): ?Result { $event = $this->triggerBeforeDispatch(); if ($event->getResult() instanceof Result) { @@ -568,9 +568,9 @@ protected function _prepareAction() /** * Execute action. * - * @return \CakeDC\Api\Service\Action\Result|null + * @return mixed */ - protected function _processAction() + protected function _processAction(): mixed { $response = $this->dispatchEvent('Service.beforeProcess', ['service' => $this, 'action' => $this]); if ($response->getResult() instanceof Result) { @@ -672,7 +672,7 @@ public function getBaseUrl(): string * * @return self|null */ - public function getParentService() + public function getParentService(): ?self { return $this->_parentService; } @@ -683,7 +683,7 @@ public function getParentService() * @param \CakeDC\Api\Service\Service $parentService Parent Service * @return $this */ - public function setParentService(Service $parentService) + public function setParentService(Service $parentService): static { $this->_parentService = $parentService; @@ -698,7 +698,7 @@ public function setParentService(Service $parentService) * @param array $actionName Action name. * @return mixed */ - public function buildActionClass(string $class, array $route, $actionName = null) + public function buildActionClass(string $class, array $route, ?string $actionName = null): mixed { if ($this->container !== null) { $reflectedClass = new ReflectionClass($class); diff --git a/src/TestSuite/IntegrationTestCase.php b/src/TestSuite/IntegrationTestCase.php index 3144fc2..9cb242e 100644 --- a/src/TestSuite/IntegrationTestCase.php +++ b/src/TestSuite/IntegrationTestCase.php @@ -176,7 +176,7 @@ public function sendRequest( */ protected function _appendGetParam(string $url, string $key, string $value): string { - $appendChar = strpos($url, '?') !== false ? '&' : '?'; + $appendChar = str_contains($url, '?') ? '&' : '?'; return $url . $appendChar . urlencode($key) . '=' . urlencode($value); } @@ -197,7 +197,7 @@ public function assertSuccess($result): void /** * @return mixed */ - public function getJsonResponse() + public function getJsonResponse(): mixed { $body = (string)$this->_response->getBody(); @@ -228,7 +228,7 @@ public function assertError($result, ?int $code = null): void * @param string $message The error message. * @return void */ - public function assertStatus($code, $message = null) + public function assertStatus(int $code, ?string $message = null): void { if ($message === null) { $message = "Status code $code does not match"; @@ -246,6 +246,6 @@ public function assertStatus($code, $message = null) public function assertErrorMessage(array $result, string $expectedMessage): void { $message = Hash::get($result, 'message'); - $this->assertTrue(is_string($message) && strpos($message, $expectedMessage) === 0); + $this->assertTrue(is_string($message) && str_starts_with($message, $expectedMessage)); } } diff --git a/src/Transformer/AbstractTransformer.php b/src/Transformer/AbstractTransformer.php index 502d4c9..6bb451c 100644 --- a/src/Transformer/AbstractTransformer.php +++ b/src/Transformer/AbstractTransformer.php @@ -14,8 +14,8 @@ namespace CakeDC\Api\Transformer; use Cake\Datasource\EntityInterface; -use Cake\I18n\FrozenDate; -use Cake\I18n\FrozenTime; +use Cake\I18n\Date; +use Cake\I18n\DateTime; /** * Abstract Transformer @@ -85,7 +85,7 @@ protected function timestamp($date): ?string return null; } - if ($date instanceof FrozenTime || $date instanceof FrozenDate) { + if ($date instanceof DateTime || $date instanceof Date) { return $date->toIso8601String(); } @@ -95,7 +95,7 @@ protected function timestamp($date): ?string if (is_string($date)) { try { - return (new FrozenTime($date))->toIso8601String(); + return (new DateTime($date))->toIso8601String(); } catch (\Exception $e) { return null; } diff --git a/src/Transformer/TransformerInterface.php b/src/Transformer/TransformerInterface.php index 9b14b8d..1696de9 100644 --- a/src/Transformer/TransformerInterface.php +++ b/src/Transformer/TransformerInterface.php @@ -28,6 +28,6 @@ interface TransformerInterface * @param mixed $data Entity or array to transform * @return array Transformed data */ - public function transform($data): array; + public function transform(mixed $data): array; } diff --git a/tests/Config/api.php b/tests/Config/api.php index fbd0550..bdfb07a 100644 --- a/tests/Config/api.php +++ b/tests/Config/api.php @@ -1,4 +1,5 @@ 'php', ]); session_id('cli'); +date_default_timezone_set('UTC'); Cake\Core\Configure::write('Security.salt', 'bc8b5b70eb0e18bac40204dc3a5b9fbc8b5b70eb0e18bac40204dc3a5b9f');