diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b094f67d..356a5ff2e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,94 +1,21 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - master + pull_request: + branches: + - '*' + +permissions: + contents: read jobs: testsuite: - runs-on: ubuntu-22.04 - strategy: - fail-fast: false - matrix: - php-version: ['7.4', '8.0', '8.1', '8.2'] - db-type: [sqlite, mysql, pgsql] - prefer-lowest: [''] - include: - - php-version: '7.2' - db-type: 'sqlite' - prefer-lowest: 'prefer-lowest' - - services: - postgres: - image: postgres - ports: - - 5432:5432 - env: - POSTGRES_PASSWORD: postgres - - steps: - - uses: actions/checkout@v3 - - - name: Setup Service - if: matrix.db-type == 'mysql' - run: | - sudo service mysql start - mysql -h 127.0.0.1 -u root -proot -e 'CREATE DATABASE cakephp;' - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl, pdo_${{ matrix.db-type }} - coverage: pcov - - - name: Composer install - run: | - if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then - composer update --prefer-lowest --prefer-stable - else - composer update - fi - - - name: Run PHPUnit - run: | - 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'; fi - if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi - - if [[ ${{ matrix.php-version }} == '7.4' && ${{ matrix.db-type }} == 'sqlite' ]]; then - vendor/bin/phpunit --coverage-clover=coverage.xml - else - vendor/bin/phpunit - fi - - - name: Code Coverage Report - if: success() && matrix.php-version == '7.4' && matrix.db-type == 'sqlite' - uses: codecov/codecov-action@v2 + uses: ADmad/.github/.github/workflows/testsuite-with-db.yml@master + secrets: inherit cs-stan: - name: Coding Standard & Static Analysis - runs-on: ubuntu-22.04 - - steps: - - uses: actions/checkout@v3 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - extensions: mbstring, intl - coverage: none - tools: vimeo/psalm:4.23, phpstan:1.9, cs2pr - - - name: Composer Install - run: composer require --dev cakephp/cakephp-codesniffer:^4.1 - - - name: Run phpcs - run: vendor/bin/phpcs --report=checkstyle --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/ | cs2pr - - - name: Run psalm - if: always() - run: psalm --output-format=github - - - name: Run phpstan - if: always() - run: phpstan analyse --error-format=github + uses: ADmad/.github/.github/workflows/cs-stan.yml@master + secrets: inherit diff --git a/.gitignore b/.gitignore index 5209c9163..eff5a25c7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ phpunit.xml vendor/ composer.lock tmp +.phpunit.cache .phpunit.result.cache diff --git a/composer.json b/composer.json index c4487516c..66e146591 100644 --- a/composer.json +++ b/composer.json @@ -39,12 +39,12 @@ } ], "require": { - "cakephp/cakephp": "^4.0" + "cakephp/cakephp": "^5.0" }, "require-dev": { - "phpunit/phpunit": "^8.5.23 || ^9.3", - "friendsofcake/cakephp-test-utilities": "^2.0.1", - "friendsofcake/search": "^6.0" + "friendsofcake/cakephp-test-utilities": "^3.0", + "friendsofcake/search": "^7.0", + "phpunit/phpunit": "^10.1" }, "autoload": { "psr-4": { diff --git a/docs/_code/action_index.php b/docs/_code/action_index.php index 34a49eeb6..d2468aff0 100644 --- a/docs/_code/action_index.php +++ b/docs/_code/action_index.php @@ -3,27 +3,36 @@ class Index extends BaseAction { - /** * Generic handler for all HTTP verbs * - * @return void + * @return \Cake\Http\Response|null */ - protected function _handle() + protected function _handle(): ?Response { - $subject = $this->_subject(); - $subject->set(['success' => true, 'viewVar' => $this->viewVar()]); + [$finder, $options] = $this->_extractFinder(); + $query = $this->_model()->find($finder, ...$options); + $subject = $this->_subject(['success' => true, 'query' => $query]); $this->_trigger('beforePaginate', $subject); + try { + $items = $this->_controller()->paginate($subject->query); + } catch (NotFoundException $e) { + /** @var \Cake\Core\Exception\CakeException $previous */ + $previous = $e->getPrevious(); + $pagingParams = $previous->getAttributes()['pagingParams']; - $controller = $this->_controller(); - $items = $controller->paginate(); - $subject->set(['items' => $items]); + $url = Router::reverseToArray($this->_request()); + $url['?']['page'] = $pagingParams['pageCount']; - $this->_trigger('afterPaginate', $subject); + return $this->_controller()->redirect($url); + } + + $subject->set(['entities' => $items]); - $controller->set(['success' => $subject->success, $subject->viewVar => $subject->items]); + $this->_trigger('afterPaginate', $subject); $this->_trigger('beforeRender', $subject); - } + return null; + } } diff --git a/docs/_partials/events/before_find.rst b/docs/_partials/events/before_find.rst index 6b77ff631..b1f9c3026 100644 --- a/docs/_partials/events/before_find.rst +++ b/docs/_partials/events/before_find.rst @@ -34,7 +34,7 @@ Add Conditions public function delete($id) { $this->Crud->on('beforeFind', function(\Cake\Event\EventInterface $event) { - $event->getSubject()->query->where(['author' => $this->Auth->user('id')]); + $event->getSubject()->query->where(['author' => $this->Authentication->getIdentity()->id]); }); return $this->Crud->execute(); diff --git a/docs/_partials/events/before_paginate.rst b/docs/_partials/events/before_paginate.rst index ec7ff4a9a..0bfa575c6 100644 --- a/docs/_partials/events/before_paginate.rst +++ b/docs/_partials/events/before_paginate.rst @@ -11,7 +11,7 @@ Add Conditions public function index() { $this->Crud->on('beforePaginate', function(\Cake\Event\EventInterface $event) { - $this->paginate['conditions']['is_active'] = true; + $event->getSubject()->query->where(['is_active' => true]); }); return $this->Crud->execute(); diff --git a/docs/actions.rst b/docs/actions.rst index 1b400a66d..8fa7310d4 100644 --- a/docs/actions.rst +++ b/docs/actions.rst @@ -50,7 +50,7 @@ namespace your action class accordingly. .. literalinclude:: _code/action_index.php :language: php :linenos: - :emphasize-lines: 2-4, 25-27 + :emphasize-lines: 2-4 Request Methods --------------- @@ -66,7 +66,7 @@ executed. .. literalinclude:: _code/action_index.php :language: php :linenos: - :emphasize-lines: 6-11,25 + :emphasize-lines: 6-10 You can treat the ``_handle()`` method as a catch-all, if your crud action wants to process all possible HTTP verbs. @@ -83,14 +83,14 @@ Events & Subject ---------------- All Crud actions emit a range of events, and all of these events always contain a Crud Subject. The Crud Subject can -change its state between emitted events. This object is a simple ``StdClass`` which contains the current state of the Crud request. +change its state between emitted events. This object is a ``Crud\Event\Subject`` instance which contains the current state of the Crud request. The real beauty of Crud is the events and the flexibility they provide. All calls to ``_trigger()`` emit an event, that you as a developer can listen to and inject your own application logic. These events are in no way magical, they are simply normal -`CakePHP events `_, dispatched like all -other `events in CakePHP `_. +`CakePHP events `_, dispatched like all +other `events in CakePHP `_. You can for example listen for the ``beforePaginate`` event and add conditions to your pagination query, just with a few lines of code. Those few lines of code is what makes your application unique. The rest of the code you would @@ -99,21 +99,16 @@ normally have is simply repeated boiler plate code. .. literalinclude:: _code/action_index.php :language: php :linenos: - :emphasize-lines: 12-15,19,21,24 + :emphasize-lines: 60,76-77 Boilerplate ----------- Only the code that you would normally have in your controller is left now. -While these 3 lines seem simple, and the whole Crud implementation a bit overkill at first, the true power of this setup +While the whole Crud implementation might seem a bit overkill at first, the true power of this setup will be clear when your application grows and the requirements increase. -.. literalinclude:: _code/action_index.php - :language: php - :linenos: - :emphasize-lines: 17,18,23 - For example :doc:`adding an API layer` to your application later in time will be easy because you don't need to edit all your applications many controllers. diff --git a/docs/actions/custom.rst b/docs/actions/custom.rst index 40fe3d905..be8d4f55a 100644 --- a/docs/actions/custom.rst +++ b/docs/actions/custom.rst @@ -18,14 +18,16 @@ A default custom index action might be as simple as the following: namespace App\Crud\Action; - class MyIndexAction extends \Crud\Action\BaseAction + use Crud\Action\BaseAction; + + class MyIndexAction extends BaseAction { /** * Default settings * - * @var array + * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'table', 'findMethod' => 'all', @@ -47,7 +49,7 @@ A default custom index action might be as simple as the following: * * @return void */ - protected function _handle() + protected function _handle(): void { $query = $this->_table()->find($this->findMethod()); $items = $this->_controller()->paginate($query); diff --git a/docs/configuration.rst b/docs/configuration.rst index 4d99a689a..e114891ab 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -236,7 +236,7 @@ The easiest way to achieve this is to create an ``AppController`` for the prefix extend from that one. Then you can configure Crud in your prefixes ``AppController``. Let's look at an example, using an ``api`` prefix. For this example, we'll assume your -`prefix routing `_ is already configured. +`prefix routing `_ is already configured. First step is to create your new ``ApiAppController`` which should be in ``src/Controller/Api/``. diff --git a/docs/index.rst b/docs/index.rst index 62c3ddb50..1e4be7c70 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -39,11 +39,11 @@ If you happen to stumble upon a bug, please `feel free to create a pull request Features ======== -If you have a good idea for a Crud feature, `please join us in #friendsofcake channel on Slack `_ and let's discuss it. +If you have a good idea for a Crud feature, `please join us in #friendsofcake channel on Slack `_ and let's discuss it. Opening a `pull request `_ is always more than welcome, and a great way to start a discussion. Please check our `contribution guidelines `_. Support / Questions =================== -You can `join us CakePHP's #support channel `_ on Slack for any support or questions. +You can `join us CakePHP's #support channel `_ on Slack for any support or questions. diff --git a/docs/installation.rst b/docs/installation.rst index ebff17b3b..63f08fb72 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -2,12 +2,6 @@ Installation ************ -Requirements -============ - -* CakePHP 3.2+ -* PHP 5.5.9+ - Using composer ============== @@ -28,14 +22,14 @@ Run the following command bin/cake plugin load Crud -Depending on your CakePHP version your ``src/Application.php`` or ``config/bootstrap.php`` +Depending on your CakePHP version your ``src/Application.php`` or ``config/plugins.php`` will be modified to load the plugin. Configuring the controller ========================== -The Crud plugin provides a trait which will catch a MissingActionException and then step in to provide scaffold actions -to the controllers. +The Crud plugin provides a trait which will catch a ``MissingActionException`` and +then step in to provide scaffold actions to the controllers. To enable Crud across your whole application add the trait to your ``src/Controller/AppController.php`` @@ -53,7 +47,7 @@ To enable Crud across your whole application add the trait to your ``src/Control To have Crud just scaffold a single controller you can just add the ``ControllerTrait`` to that specific controller. Adding the ``ControllerTrait`` itself do not enable anything Crud, but simply installs the code to handle -the ``\Cake\Error\MissingActionException`` exception so you don't have to implement an action in your controller +the ``\Cake\Controller\Exception\MissingActionException`` exception so you don't have to implement an action in your controller for Crud to work. The next step is to load the Crud component in your controller. A basic example is as follows, and will enable the Crud @@ -79,8 +73,4 @@ plugin to scaffold all your controllers index actions. } } -For controllers like ``ErrorController`` where you usually don't call ``parent::initialize()`` -you can avoid errors due to ``CrudComponent`` not being loaded by adding -``$this->dispatchComponents['Crud'] = false`` in the controller's ``initialize()`` method. - Further configuration options are detailed on the :doc:`configuration page`. diff --git a/docs/listeners.rst b/docs/listeners.rst index 3f69f436a..47b824be5 100644 --- a/docs/listeners.rst +++ b/docs/listeners.rst @@ -16,7 +16,7 @@ The Anatomy Of A Listener ========================= The listener system is simply the -`Events System `_ from +`Events System `_ from CakePHP, and all the official documentation and usage also applies to Crud. The Crud event system uses two methods ``trigger()`` and ``on()`` to interface @@ -47,7 +47,7 @@ as an action creator. Implemented Events ------------------ -As documented in the `CakePHP Events System `_ +As documented in the `CakePHP Events System `_ all listeners must contain a ``implementedEvents`` method. In this example, we simply request that ``beforeRender`` in our class is executed diff --git a/docs/listeners/api-pagination.rst b/docs/listeners/api-pagination.rst index d770387b6..bf8bec9e0 100644 --- a/docs/listeners/api-pagination.rst +++ b/docs/listeners/api-pagination.rst @@ -60,9 +60,11 @@ below: ], "pagination":{ - "page_count": 13, + "page_count": 5, "current_page": 1, - "count": 25, + "count": 10, + "total_count": 45, + "page_page": 10, "has_prev_page": false, "has_next_page": true } @@ -72,7 +74,7 @@ Configuration ------------- Configure this listener by setting the -`CakePHP Pagination `_ options directly to the +`CakePHP Pagination `_ options directly to the query object. .. code-block:: php diff --git a/docs/listeners/api.rst b/docs/listeners/api.rst index c2caf68ca..7beb5a409 100644 --- a/docs/listeners/api.rst +++ b/docs/listeners/api.rst @@ -9,7 +9,7 @@ Introduction The ``API listener`` depends on the ``RequestHandler`` to be loaded **before** ``Crud``. -`Please also see the CakePHP documentation on JSON and XML views `_ +`Please also see the CakePHP documentation on JSON and XML views `_ Setup ----- @@ -17,16 +17,9 @@ Setup Routing ^^^^^^^ -You need to tell the ``Router`` to parse extensions else it won't be able toprocess and render ``json`` and ``xml`` +You need to tell the ``Router`` to `parse extensions `_ else it won't be able to process and render ``json`` and ``xml`` URL extension. -.. code-block:: phpinline - - // config/routes.php - Router::extensions(['json', 'xml']); - -Ensure this statement is used before connecting any routes, and is in the routing global scope. - Controller ^^^^^^^^^^ @@ -101,7 +94,7 @@ In case of an error, in order to get a standardized response in either ``json`` or ``xml`` - according to the API request type, for `api` requests, the default CakePHP exception renderer needs to be overridden. -Set the ``Error.exceptionRenderer`` config in ``config/app.php`` to +Set the ``Error.exceptionRenderer`` config in ``config/app.php`` to ``\Crud\Error\ExceptionRenderer::class`` as following: .. code-block:: php diff --git a/docs/quick-start.rst b/docs/quick-start.rst index 9b285b697..28b07a3fd 100644 --- a/docs/quick-start.rst +++ b/docs/quick-start.rst @@ -63,7 +63,7 @@ So, our new Blog needs a Posts controller to allow us to create, read, update an This is all the code we need in the ``PostsController`` as Crud will scaffold the controller actions for us. If you are not using `Crud-View `_ then you will have -to `bake your templates `_. +to `bake your templates `_. .. code-block:: sh diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 907d1ed07..75a4da41b 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,57 +1,26 @@ parameters: ignoreErrors: - - message: "#^Method Crud\\\\Action\\\\AddAction\\:\\:publishViewVar\\(\\) should return false\\|null but return statement is missing\\.$#" - count: 1 - path: src/Action/AddAction.php - - - - message: "#^Method Crud\\\\Action\\\\EditAction\\:\\:publishViewVar\\(\\) should return false\\|null but return statement is missing\\.$#" - count: 1 - path: src/Action/EditAction.php - - - - message: "#^Call to an undefined method Crud\\\\Action\\\\BaseAction\\:\\:view\\(\\)\\.$#" + message: "#^Call to an undefined method Crud\\\\Action\\\\BaseAction\\:\\:findMethod\\(\\)\\.$#" count: 2 path: src/Controller/Component/CrudComponent.php - - message: "#^Call to an undefined method Crud\\\\Action\\\\BaseAction\\:\\:viewVar\\(\\)\\.$#" + message: "#^Call to an undefined method Crud\\\\Action\\\\BaseAction\\:\\:view\\(\\)\\.$#" count: 2 path: src/Controller/Component/CrudComponent.php - - message: "#^Call to an undefined method Crud\\\\Action\\\\BaseAction\\:\\:findMethod\\(\\)\\.$#" + message: "#^Call to an undefined method Crud\\\\Action\\\\BaseAction\\:\\:viewVar\\(\\)\\.$#" count: 2 path: src/Controller/Component/CrudComponent.php - - - message: "#^Method Crud\\\\Core\\\\BaseObject\\:\\:_table\\(\\) should return Cake\\\\ORM\\\\Table but returns Cake\\\\Datasource\\\\RepositoryInterface\\.$#" - count: 1 - path: src/Core/BaseObject.php - - message: "#^Access to an undefined property Cake\\\\Controller\\\\Controller\\:\\:\\$Crud\\.$#" count: 1 path: src/Core/BaseObject.php - - message: "#^Method Crud\\\\Core\\\\BaseObject\\:\\:_crud\\(\\) should return Crud\\\\Controller\\\\Component\\\\CrudComponent but returns Cake\\\\Controller\\\\Component\\.$#" + message: "#^Call to an undefined method Cake\\\\Controller\\\\Controller\\:\\:setViewClasses\\(\\)\\.$#" count: 1 - path: src/Core/BaseObject.php - - - - message: "#^Method Crud\\\\Action\\\\IndexAction\\:\\:publishViewVar\\(\\) should return false\\|null but return statement is missing\\.$#" - count: 1 - path: src/Action/IndexAction.php - - - - message: "#^Method Crud\\\\Action\\\\LookupAction\\:\\:publishViewVar\\(\\) should return false\\|null but return statement is missing\\.$#" - count: 1 - path: src/Action/LookupAction.php - - - - message: "#^Method Crud\\\\Action\\\\ViewAction\\:\\:publishViewVar\\(\\) should return false\\|null but return statement is missing\\.$#" - count: 1 - path: src/Action/ViewAction.php - + path: src/Listener/ApiListener.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 8295b63a1..9e6f6a126 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,28 +1,20 @@ - - + + ./tests/ - - - - - - - - - - - - ./src/ - ./src/ - - - + + + + + + + + + + src/ + + diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 58bd48317..b04e4e1df 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -1,19 +1,18 @@ - - - \Cake\Error\ExceptionRenderer - parent::_outputMessage($template) - - - - $controller->RequestHandler->setConfig('viewClassMap', [$type => $class]) - + + setViewClasses + + + + + new QueryLogger() + - \Cake\Database\Log\QueryLogger + CakeQueryLogger parent::log($level, $message, $context) diff --git a/psalm.xml b/psalm.xml index 4d96f78dd..745177567 100644 --- a/psalm.xml +++ b/psalm.xml @@ -5,6 +5,8 @@ xmlns="https://getpsalm.org/schema/config" xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd" errorBaseline="psalm-baseline.xml" + findUnusedBaselineEntry="true" + findUnusedCode="false" > diff --git a/src/Action/AddAction.php b/src/Action/AddAction.php index 786616a00..ff4989e6e 100644 --- a/src/Action/AddAction.php +++ b/src/Action/AddAction.php @@ -43,7 +43,7 @@ class AddAction extends BaseAction * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'entity', 'inflection' => 'singular', @@ -112,9 +112,9 @@ protected function _get(): void /** * HTTP POST handler * - * @return \Cake\Http\Response|void + * @return \Cake\Http\Response|null */ - protected function _post() + protected function _post(): ?Response { $subject = $this->_subject([ 'entity' => $this->_entity($this->_request()->getData(), $this->saveOptions()), @@ -127,12 +127,14 @@ protected function _post() return $this->_stopped($subject); } - $saveCallback = [$this->_table(), $subject->saveMethod]; + $saveCallback = [$this->_model(), $subject->saveMethod]; if (call_user_func($saveCallback, $subject->entity, $subject->saveOptions)) { return $this->_success($subject); } $this->_error($subject); + + return null; } /** @@ -140,7 +142,7 @@ protected function _post() * * @return \Cake\Http\Response|null */ - protected function _put() + protected function _put(): ?Response { return $this->_post(); } diff --git a/src/Action/BaseAction.php b/src/Action/BaseAction.php index efd76dda1..e00f7ec72 100644 --- a/src/Action/BaseAction.php +++ b/src/Action/BaseAction.php @@ -12,6 +12,7 @@ use Crud\Event\Subject; use Exception; use RuntimeException; +use function Cake\I18n\__d; /** * Base Crud class @@ -26,14 +27,14 @@ abstract class BaseAction extends BaseObject * * @var bool */ - protected $_responding = false; + protected bool $_responding = false; /** * Default configuration * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * Handle callback @@ -48,27 +49,26 @@ abstract class BaseAction extends BaseObject * @return mixed * @throws \Cake\Http\Exception\NotImplementedException if the action can't handle the request */ - public function handle(array $args = []) + public function handle(array $args = []): mixed { if (!$this->enabled()) { return false; } - $method = '_' . strtolower($this->_request()->getMethod()); + $methods = [ + '_' . strtolower($this->_request()->getMethod()), + '_handle', + ]; - if (method_exists($this, $method)) { - $this->_responding = true; - $this->_controller()->getEventManager()->on($this); + foreach ($methods as $method) { + if (method_exists($this, $method)) { + $this->_responding = true; + $this->_controller()->getEventManager()->on($this); - return call_user_func_array([$this, $method], $args); - } - - if (method_exists($this, '_handle')) { - $this->_responding = true; - $this->_controller()->getEventManager()->on($this); + $closure = $this->$method(...); - /** @psalm-suppress InvalidArgument */ - return call_user_func_array([$this, '_handle'], $args); + return $closure(...$args); + } } throw new NotImplementedException(sprintf( @@ -154,6 +154,7 @@ public function message(string $type, array $replacements = []): array throw new Exception(sprintf('Invalid message config for "%s" no text key found', $type)); } + /** @psalm-suppress PossiblyInvalidArgument */ $config['params']['original'] = ucfirst(str_replace('{name}', $config['name'], $config['text'])); $domain = $this->getConfig('messages.domain'); @@ -219,11 +220,11 @@ public function setFlash(string $type, Subject $subject): void * - key : the key to read inside the reader * - url : the URL to redirect to * - * @param null|string $name Name of the redirection rule - * @param null|array $config Redirection configuration + * @param string|null $name Name of the redirection rule + * @param array|null $config Redirection configuration * @return mixed */ - public function redirectConfig(?string $name = null, ?array $config = null) + public function redirectConfig(?string $name = null, ?array $config = null): mixed { if ($name === null && $config === null) { return $this->getConfig('redirect'); @@ -253,7 +254,7 @@ public function scope(): ?string /** * Set "success" variable for view. * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return bool|null */ public function publishSuccess(EventInterface $event): ?bool @@ -274,7 +275,7 @@ public function publishSuccess(EventInterface $event): ?bool * using the "name" configuration property * * @param string $value Name to set - * @return string|$this + * @return $this|string */ public function resourceName(?string $value = null) { @@ -304,11 +305,11 @@ protected function _deriveResourceName(): string if ($inflectionType === 'singular') { return strtolower(Inflector::humanize( - Inflector::singularize(Inflector::underscore($this->_table()->getAlias())) + Inflector::singularize(Inflector::underscore($this->_model()->getAlias())) )); } - return strtolower(Inflector::humanize(Inflector::underscore($this->_table()->getAlias()))); + return strtolower(Inflector::humanize(Inflector::underscore($this->_model()->getAlias()))); } /** diff --git a/src/Action/Bulk/BaseAction.php b/src/Action/Bulk/BaseAction.php index f9b207bf4..15b6fe2b6 100644 --- a/src/Action/Bulk/BaseAction.php +++ b/src/Action/Bulk/BaseAction.php @@ -3,9 +3,10 @@ namespace Crud\Action\Bulk; +use Cake\Database\Query; use Cake\Http\Exception\BadRequestException; use Cake\Http\Response; -use Cake\ORM\Query; +use Cake\ORM\Table; use Cake\Utility\Hash; use Crud\Action\BaseAction as CrudBaseAction; use Crud\Event\Subject; @@ -28,11 +29,10 @@ abstract class BaseAction extends CrudBaseAction * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'bulk', - 'findMethod' => 'all', - 'findConfig' => [], + 'queryType' => Query::TYPE_UPDATE, 'messages' => [ 'success' => [ 'text' => 'Bulk action successfully completed', @@ -76,18 +76,13 @@ protected function _processIds(): array { $ids = $this->_controller()->getRequest()->getData('id'); - $all = false; - if (is_array($ids)) { - $all = Hash::get($ids, '_all', false); - unset($ids['_all']); - } - - // @phpstan-ignore-next-line if (!is_array($ids)) { throw new BadRequestException('Bad request data'); } - // @phpstan-ignore-next-line + $all = Hash::get($ids, '_all', false); + unset($ids['_all']); + if ($all) { foreach ($ids as $key => $value) { $ids[$key] = 1; @@ -95,9 +90,7 @@ protected function _processIds(): array $ids = array_keys($ids); } - $ids = array_filter($ids); - - return array_values($ids); + return array_values(array_filter($ids)); } /** @@ -108,11 +101,14 @@ protected function _processIds(): array */ protected function _constructSubject(array $ids): Subject { - $repository = $this->_table(); + $repository = $this->_model(); + assert($repository instanceof Table); + + $method = strtolower($this->getConfig('queryType')) . 'Query'; + $primaryKey = $repository->getPrimaryKey(); - [$finder, $options] = $this->_extractFinder(); - $options = array_merge($options, $this->_getFindConfig($ids)); - $query = $repository->find($finder, $options); + $query = $repository->{$method}() + ->where(fn ($exp) => $exp->in($primaryKey, $ids)); $subject = $this->_subject(); $subject->set([ @@ -124,28 +120,6 @@ protected function _constructSubject(array $ids): Subject return $subject; } - /** - * Get the query configuration - * - * @param array $ids An array of ids to retrieve - * @return array - */ - protected function _getFindConfig(array $ids): array - { - $config = (array)$this->getConfig('findConfig'); - if (!empty($config)) { - return $config; - } - - $primaryKey = $this->_table()->getPrimaryKey(); - $config['conditions'] = []; - $config['conditions'][] = function ($exp) use ($primaryKey, $ids) { - return $exp->in($primaryKey, $ids); - }; - - return $config; - } - /** * Success callback * @@ -189,9 +163,9 @@ protected function _stopped(Subject $subject): ?Response } /** - * Handle a bulk event + * Handle a bulk event. * - * @param \Cake\ORM\Query $query The query to act upon + * @param \Cake\Database\Query $query The query to act upon * @return bool */ abstract protected function _bulk(Query $query): bool; diff --git a/src/Action/Bulk/DeleteAction.php b/src/Action/Bulk/DeleteAction.php index c2bd9e5c4..8180378ca 100644 --- a/src/Action/Bulk/DeleteAction.php +++ b/src/Action/Bulk/DeleteAction.php @@ -4,7 +4,8 @@ namespace Crud\Action\Bulk; use Cake\Controller\Controller; -use Cake\ORM\Query; +use Cake\Database\Query; +use Cake\ORM\Query\DeleteQuery; /** * Handles Bulk 'Delete' Crud actions @@ -23,6 +24,8 @@ class DeleteAction extends BaseAction */ public function __construct(Controller $Controller, array $config = []) { + $this->_defaultConfig['queryType'] = Query::TYPE_DELETE; + $this->_defaultConfig['messages'] = [ 'success' => [ 'text' => 'Delete completed successfully', @@ -38,15 +41,13 @@ public function __construct(Controller $Controller, array $config = []) /** * Handle a bulk delete * - * @param \Cake\ORM\Query $query The query to act upon + * @param \Cake\Database\Query $query The query to act upon * @return bool */ protected function _bulk(Query $query): bool { - $query = $query->delete(); - $statement = $query->execute(); - $statement->closeCursor(); + assert($query instanceof DeleteQuery); - return (bool)$statement->rowCount(); + return (bool)$query->rowCountAndClose(); } } diff --git a/src/Action/Bulk/SetValueAction.php b/src/Action/Bulk/SetValueAction.php index 1babdc68f..6468a7d6e 100644 --- a/src/Action/Bulk/SetValueAction.php +++ b/src/Action/Bulk/SetValueAction.php @@ -4,8 +4,9 @@ namespace Crud\Action\Bulk; use Cake\Controller\Controller; +use Cake\Database\Query; use Cake\Http\Response; -use Cake\ORM\Query; +use Cake\ORM\Query\UpdateQuery; use Crud\Error\Exception\ActionNotConfiguredException; /** @@ -57,17 +58,15 @@ protected function _handle(): ?Response /** * Handle a bulk value set * - * @param \Cake\ORM\Query $query The query to act upon + * @param \Cake\Database\Query $query The query to act upon * @return bool */ protected function _bulk(Query $query): bool { - $field = $this->getConfig('field'); - $value = $this->getConfig('value'); - $query->update()->set([$field => $value]); - $statement = $query->execute(); - $statement->closeCursor(); + assert($query instanceof UpdateQuery); + + $query->set([$this->getConfig('field') => $this->getConfig('value')]); - return (bool)$statement->rowCount(); + return (bool)$query->rowCountAndClose(); } } diff --git a/src/Action/Bulk/ToggleAction.php b/src/Action/Bulk/ToggleAction.php index a7877c187..83dc371ef 100644 --- a/src/Action/Bulk/ToggleAction.php +++ b/src/Action/Bulk/ToggleAction.php @@ -5,8 +5,9 @@ use Cake\Controller\Controller; use Cake\Database\Expression\QueryExpression; +use Cake\Database\Query; use Cake\Http\Response; -use Cake\ORM\Query; +use Cake\ORM\Query\UpdateQuery; use Crud\Error\Exception\ActionNotConfiguredException; /** @@ -57,17 +58,18 @@ protected function _handle(): ?Response /** * Handle a bulk toggle * - * @param \Cake\ORM\Query $query The query to act upon + * @param \Cake\Database\Query $query The query to act upon * @return bool */ protected function _bulk(Query $query): bool { + assert($query instanceof UpdateQuery); + $field = $this->getConfig('field'); $expression = [new QueryExpression(sprintf('%1$s= NOT %1$s', $field))]; - $query->update()->set($expression); - $statement = $query->execute(); - $statement->closeCursor(); - return (bool)$statement->rowCount(); + $query->set($expression); + + return (bool)$query->rowCountAndClose(); } } diff --git a/src/Action/DeleteAction.php b/src/Action/DeleteAction.php index 20f412621..c88c5f771 100644 --- a/src/Action/DeleteAction.php +++ b/src/Action/DeleteAction.php @@ -30,7 +30,7 @@ class DeleteAction extends BaseAction * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'entity', 'findMethod' => 'all', @@ -56,10 +56,10 @@ class DeleteAction extends BaseAction /** * HTTP POST handler * - * @param string|null $id Record id + * @param string|int|null $id Record id * @return \Cake\Http\Response|null */ - protected function _post(?string $id = null): ?Response + protected function _post(string|int|null $id = null): ?Response { $subject = $this->_subject(); $subject->set(['id' => $id]); @@ -72,7 +72,7 @@ protected function _post(?string $id = null): ?Response } $method = $this->getConfig('deleteMethod'); - if ($this->_table()->$method($entity)) { + if ($this->_model()->$method($entity)) { $this->_success($subject); } else { $this->_error($subject); @@ -84,10 +84,10 @@ protected function _post(?string $id = null): ?Response /** * HTTP DELETE handler * - * @param string|null $id Record id + * @param string|int|null $id Record id * @return \Cake\Http\Response|null */ - protected function _delete(?string $id = null): ?Response + protected function _delete(string|int|null $id = null): ?Response { return $this->_post($id); } diff --git a/src/Action/EditAction.php b/src/Action/EditAction.php index 6f8ed5449..45c3c5a20 100644 --- a/src/Action/EditAction.php +++ b/src/Action/EditAction.php @@ -47,7 +47,7 @@ class EditAction extends BaseAction * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'entity', 'findMethod' => 'all', @@ -93,11 +93,11 @@ class EditAction extends BaseAction /** * HTTP GET handler * - * @param string|null $id Record id + * @param string|int|null $id Record id * @return void * @throws \Cake\Http\Exception\NotFoundException If record not found */ - protected function _get(?string $id = null): void + protected function _get(string|int|null $id = null): void { $subject = $this->_subject(); $subject->set(['id' => $id]); @@ -109,26 +109,28 @@ protected function _get(?string $id = null): void /** * HTTP PUT handler * - * @param string|null $id Record id - * @return \Cake\Http\Response|void + * @param string|int|null $id Record id + * @return \Cake\Http\Response|null */ - protected function _put(?string $id = null) + protected function _put(string|int|null $id = null): ?Response { $subject = $this->_subject(); $subject->set(['id' => $id]); - $entity = $this->_table()->patchEntity( + $entity = $this->_model()->patchEntity( $this->_findRecord($id, $subject), $this->_request()->getData(), $this->saveOptions() ); $this->_trigger('beforeSave', $subject); - if (call_user_func([$this->_table(), $this->saveMethod()], $entity, $this->saveOptions())) { + if (call_user_func([$this->_model(), $this->saveMethod()], $entity, $this->saveOptions())) { return $this->_success($subject); } $this->_error($subject); + + return null; } /** @@ -136,10 +138,10 @@ protected function _put(?string $id = null) * * Thin proxy for _put * - * @param string|null $id Record id - * @return \Cake\Http\Response|void + * @param string|int|null $id Record id + * @return \Cake\Http\Response|null */ - protected function _post(?string $id = null) + protected function _post(string|int|null $id = null): ?Response { return $this->_put($id); } @@ -149,10 +151,10 @@ protected function _post(?string $id = null) * * Thin proxy for _put * - * @param string|null $id Record id - * @return \Cake\Http\Response|void + * @param string|int|null $id Record id + * @return \Cake\Http\Response|null */ - protected function _patch(?string $id = null) + protected function _patch(string|int|null $id = null): ?Response { return $this->_put($id); } diff --git a/src/Action/IndexAction.php b/src/Action/IndexAction.php index ce1d20be1..e3710ce4f 100644 --- a/src/Action/IndexAction.php +++ b/src/Action/IndexAction.php @@ -29,7 +29,7 @@ class IndexAction extends BaseAction * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'table', 'findMethod' => 'all', @@ -54,17 +54,19 @@ class IndexAction extends BaseAction protected function _handle(): ?Response { [$finder, $options] = $this->_extractFinder(); - $query = $this->_table()->find($finder, $options); + $query = $this->_model()->find($finder, ...$options); $subject = $this->_subject(['success' => true, 'query' => $query]); $this->_trigger('beforePaginate', $subject); try { $items = $this->_controller()->paginate($subject->query); } catch (NotFoundException $e) { - $pagingParams = $this->_request()->getAttribute('paging'); + /** @var \Cake\Core\Exception\CakeException $previous */ + $previous = $e->getPrevious(); + $pagingParams = $previous->getAttributes()['pagingParams']; $url = Router::reverseToArray($this->_request()); - $url['?']['page'] = $pagingParams[$this->_table()->getAlias()]['pageCount']; + $url['?']['page'] = $pagingParams['pageCount']; return $this->_controller()->redirect($url); } diff --git a/src/Action/LookupAction.php b/src/Action/LookupAction.php index 0899c0f2d..188e7ed3f 100644 --- a/src/Action/LookupAction.php +++ b/src/Action/LookupAction.php @@ -3,6 +3,7 @@ namespace Crud\Action; +use Cake\ORM\Table; use Crud\Traits\FindMethodTrait; use Crud\Traits\SerializeTrait; use Crud\Traits\ViewTrait; @@ -26,7 +27,7 @@ class LookupAction extends BaseAction * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'table', 'findMethod' => 'list', @@ -41,7 +42,7 @@ protected function _handle(): void { [$finder, $options] = $this->_extractFinder(); $options = array_merge($options, $this->_getFindConfig()); - $query = $this->_table()->find($finder, $options); + $query = $this->_model()->find($finder, ...$options); $subject = $this->_subject(['success' => true, 'query' => $query]); $this->_trigger('beforeLookup', $subject); @@ -59,8 +60,10 @@ protected function _handle(): void protected function _getFindConfig(): array { $request = $this->_request(); + $model = $this->_model(); + assert($model instanceof Table); - $columns = $this->_table()->getSchema()->columns(); + $columns = $model->getSchema()->columns(); $config = (array)$this->getConfig('findConfig'); $idField = $request->getQuery('key_field') ?: $request->getQuery('id'); diff --git a/src/Action/ViewAction.php b/src/Action/ViewAction.php index d6150d775..bdd2a1e9e 100644 --- a/src/Action/ViewAction.php +++ b/src/Action/ViewAction.php @@ -33,7 +33,7 @@ class ViewAction extends BaseAction * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => true, 'scope' => 'entity', 'findMethod' => 'all', @@ -45,10 +45,10 @@ class ViewAction extends BaseAction /** * Generic HTTP handler * - * @param string|null $id Record id + * @param string|int|null $id Record id * @return void */ - protected function _handle(?string $id = null): void + protected function _handle(string|int|null $id = null): void { $subject = $this->_subject(); $subject->set(['id' => $id]); diff --git a/src/Controller/Component/CrudComponent.php b/src/Controller/Component/CrudComponent.php index fe9aa2c57..4a506ab6d 100644 --- a/src/Controller/Component/CrudComponent.php +++ b/src/Controller/Component/CrudComponent.php @@ -8,12 +8,14 @@ use Cake\Controller\Controller; use Cake\Core\App; use Cake\Datasource\EntityInterface; +use Cake\Datasource\RepositoryInterface; use Cake\Event\Event; use Cake\Event\EventInterface; +use Cake\Event\EventManagerInterface; use Cake\Http\Exception\BadRequestException; use Cake\Http\Exception\MethodNotAllowedException; use Cake\Http\Exception\NotFoundException; -use Cake\ORM\Table; +use Cake\Http\ServerRequest; use Cake\Utility\Inflector; use Crud\Action\BaseAction; use Crud\Error\Exception\ActionNotConfiguredException; @@ -24,6 +26,7 @@ use Crud\Event\Subject; use Crud\Listener\BaseListener; use Psr\Http\Message\ResponseInterface; +use function Cake\Core\pluginSplit; /** * Crud component @@ -40,57 +43,56 @@ class CrudComponent extends Component * * @var string */ - protected $_action; + protected string $_action; /** * Reference to the current controller. * * @var \Cake\Controller\Controller */ - protected $_controller; + protected Controller $_controller; /** * Reference to the current request. * * @var \Cake\Http\ServerRequest */ - protected $_request; + protected ServerRequest $_request; /** * A flat array of the events triggered. * * @var array */ - protected $_eventLog = []; + protected array $_eventLog = []; /** * Reference to the current event manager. * * @var \Cake\Event\EventManagerInterface */ - protected $_eventManager; + protected EventManagerInterface $_eventManager; /** - * Cached property for Controller::$modelClass. This is - * the model name of the current model. + * This is the model name of the current model. * - * @var string + * @var string|null */ - protected $_modelName; + protected ?string $_modelName = null; /** * List of listener objects attached to Crud. * - * @var \Crud\Listener\BaseListener[] + * @var array<\Crud\Listener\BaseListener> */ - protected $_listenerInstances = []; + protected array $_listenerInstances = []; /** * List of crud actions. * - * @var \Crud\Action\BaseAction[] + * @var array<\Crud\Action\BaseAction> */ - protected $_actionInstances = []; + protected array $_actionInstances = []; /** * Components settings. @@ -110,7 +112,7 @@ class CrudComponent extends Component * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'actions' => [], 'eventPrefix' => 'Crud', 'listeners' => [], @@ -138,7 +140,7 @@ class CrudComponent extends Component /** * Constructor * - * @param \Cake\Controller\ComponentRegistry $collection A ComponentCollection this component + * @param \Cake\Controller\ComponentRegistry<\Cake\Controller\Controller> $collection A ComponentCollection this component * can use to lazy load its components. * @param array $config Array of configuration settings. */ @@ -196,7 +198,7 @@ public function initialize(array $config): void /** * Loads listeners * - * @param \Cake\Event\EventInterface $event Event instance + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event instance * @return void * @throws \Exception */ @@ -209,7 +211,7 @@ public function beforeFilter(EventInterface $event): void /** * Called after the Controller::beforeFilter() and before the controller action. * - * @param \Cake\Event\EventInterface $event Event instance + * @param \Cake\Event\EventInterface<\Cake\Controller\Controller> $event Event instance * @return void * @throws \Exception */ @@ -285,12 +287,12 @@ public function action(?string $name = null): BaseAction /** * Enable one or multiple CRUD actions. * - * @param string|array $actions The action to enable. + * @param array|string $actions The action to enable. * @return void * @throws \Crud\Error\Exception\ActionNotConfiguredException * @throws \Crud\Error\Exception\MissingActionException */ - public function enable($actions): void + public function enable(array|string $actions): void { foreach ((array)$actions as $action) { $this->action($action)->enable(); @@ -300,12 +302,12 @@ public function enable($actions): void /** * Disable one or multiple CRUD actions. * - * @param string|array $actions The action to disable. + * @param array|string $actions The action to disable. * @return void * @throws \Crud\Error\Exception\ActionNotConfiguredException * @throws \Crud\Error\Exception\MissingActionException */ - public function disable($actions): void + public function disable(array|string $actions): void { foreach ((array)$actions as $action) { $this->action($action)->disable(); @@ -317,13 +319,13 @@ public function disable($actions): void * * To map multiple action views in one go pass an array as first argument and no second argument. * - * @param string|array $action Action or array of actions + * @param array|string $action Action or array of actions * @param string|null $view View name * @return void * @throws \Crud\Error\Exception\ActionNotConfiguredException * @throws \Crud\Error\Exception\MissingActionException */ - public function view($action, ?string $view = null): void + public function view(array|string $action, ?string $view = null): void { if (is_array($action)) { foreach ($action as $realAction => $realView) { @@ -343,13 +345,13 @@ public function view($action, ?string $view = null): void * * To map multiple action viewVars in one go pass an array as first argument and no second argument. * - * @param string|array $action Action or array of actions. + * @param array|string $action Action or array of actions. * @param string|null $viewVar View var name. * @return void * @throws \Crud\Error\Exception\ActionNotConfiguredException * @throws \Crud\Error\Exception\MissingActionException */ - public function viewVar($action, ?string $viewVar = null): void + public function viewVar(array|string $action, ?string $viewVar = null): void { if (is_array($action)) { foreach ($action as $realAction => $realViewVar) { @@ -369,13 +371,13 @@ public function viewVar($action, ?string $viewVar = null): void * * To map multiple findMethods in one go pass an array as first argument and no second argument. * - * @param string|array $action Action or array of actions. + * @param array|string $action Action or array of actions. * @param string|null $method Find method name * @return void * @throws \Crud\Error\Exception\ActionNotConfiguredException * @throws \Crud\Error\Exception\MissingActionException */ - public function findMethod($action, ?string $method = null): void + public function findMethod(array|string $action, ?string $method = null): void { if (is_array($action)) { foreach ($action as $realAction => $realMethod) { @@ -394,13 +396,13 @@ public function findMethod($action, ?string $method = null): void * Map action to an internal request type. * * @param string $action The Controller action to provide an implementation for. - * @param string|array $config Config array or class name like Crud.Index. + * @param array|string $config Config array or class name like Crud.Index. * @param bool $enable Should the mapping be enabled right away? * @return void * @throws \Crud\Error\Exception\ActionNotConfiguredException * @throws \Crud\Error\Exception\MissingActionException */ - public function mapAction(string $action, $config = [], bool $enable = true): void + public function mapAction(string $action, array|string $config = [], bool $enable = true): void { if (is_string($config)) { $config = ['className' => $config]; @@ -428,8 +430,7 @@ public function isActionMapped(?string $action = null): bool } $action = Inflector::variable($action); - $test = $this->getConfig('actions.' . $action); - if (empty($test)) { + if (!$this->getConfig('actions.' . $action)) { return false; } @@ -439,12 +440,12 @@ public function isActionMapped(?string $action = null): bool /** * Attaches an event listener function to the controller for Crud Events. * - * @param string|array $events Name of the Crud Event you want to attach to controller. + * @param array|string $events Name of the Crud Event you want to attach to controller. * @param callable $callback Callable method or closure to be executed on event. * @param array $options Used to set the `priority` and `passParams` flags to the listener. * @return void */ - public function on($events, callable $callback, array $options = []): void + public function on(array|string $events, callable $callback, array $options = []): void { foreach ((array)$events as $event) { if (!strpos($event, '.')) { @@ -533,9 +534,9 @@ public function removeListener(string $name): bool * @param string $eventName Event name * @param \Crud\Event\Subject|array|null $data Event subject / data * @throws \Exception if any event listener return a CakeResponse object. - * @return \Cake\Event\EventInterface + * @return \Cake\Event\EventInterface<\Crud\Event\Subject> */ - public function trigger(string $eventName, $data = null): EventInterface + public function trigger(string $eventName, Subject|array|null $data = null): EventInterface { $eventName = $this->_config['eventPrefix'] . '.' . $eventName; @@ -574,13 +575,13 @@ public function logEvent(string $eventName, Subject $subject): void * Set or get defaults for listeners and actions. * * @param string $type Can be anything, but 'listeners' or 'actions' is currently only used. - * @param string|array $name The name of the $type - e.g. 'api', 'relatedModels' + * @param array|string $name The name of the $type - e.g. 'api', 'relatedModels' * or an array ('api', 'relatedModels'). If $name is an array, the $config will be applied * to each entry in the $name array. * @param mixed $config If NULL, the defaults is returned, else the defaults are changed. * @return mixed */ - public function defaults(string $type, $name, $config = null) + public function defaults(string $type, array|string $name, mixed $config = null): mixed { if ($config !== null) { if (!is_array($name)) { @@ -620,21 +621,17 @@ public function useModel(string $modelName): void } /** - * Returns controller's table instance. + * Returns controller's model instance. * - * @return \Cake\ORM\Table + * @return \Cake\Datasource\RepositoryInterface */ - public function table(): Table + public function model(): RepositoryInterface { - if (method_exists($this->_controller, 'fetchTable')) { - return $this->_controller->fetchTable($this->_modelName); + if (method_exists($this->_controller, 'fetchModel')) { + return $this->_controller->fetchModel($this->_modelName); } - /** - * @var \Cake\ORM\Table - * @psalm-suppress DeprecatedMethod - */ - return $this->_controller->loadModel($this->_modelName); + return $this->_controller->fetchTable($this->_modelName); } /** @@ -645,7 +642,7 @@ public function table(): Table */ public function entity(array $data = []): EntityInterface { - return $this->table()->newEntity($data); + return $this->model()->newEntity($data); } /** diff --git a/src/Controller/ControllerTrait.php b/src/Controller/ControllerTrait.php index ae5827b6f..f16aaa201 100644 --- a/src/Controller/ControllerTrait.php +++ b/src/Controller/ControllerTrait.php @@ -3,7 +3,6 @@ namespace Crud\Controller; -use Cake\Controller\Component; use Cake\Controller\Exception\MissingActionException; use Closure; @@ -13,23 +12,17 @@ * * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt + * + * @property \Crud\Controller\Component\CrudComponent $Crud */ trait ControllerTrait { /** - * List of components that are capable of dispatching an action that is - * not already implemented - * - * @var array - */ - protected $dispatchComponents = ['Crud' => true]; - - /** - * Reference to component which should handle the mapped action. + * Whether current action is mapped to a Crud action. * - * @var \Controller\Component\CrudComponent|null + * @var bool */ - protected $mappedComponent; + protected bool $mappedAction = false; /** * Get the closure for action to be invoked by ControllerFactory. @@ -42,9 +35,10 @@ public function getAction(): Closure try { return parent::getAction(); } catch (MissingActionException $e) { - $this->mappedComponent = $this->_isActionMapped(); - if ($this->mappedComponent) { - return function () { + $this->mappedAction = $this->Crud->isActionMapped($this->request->getParam('action')); + + if ($this->mappedAction) { + return function (): void { // Dummy closure without arguments. // This is to prevent the ControllerFactory from trying to type cast the method args. // invokeAction() below simply ignores the $action argument for Crud mapped actions @@ -59,17 +53,16 @@ public function getAction(): Closure /** * Dispatches the controller action. * - * If a controller method with required name does not exist we attempt to execute Crud action. + * If the action is mapped to a Crud action we execute it. * * @param \Closure $action The action closure. * @param array $args The arguments to be passed when invoking action. * @return void - * @throws \UnexpectedValueException If return value of action is not `null` or `ResponseInterface` instance. */ public function invokeAction(Closure $action, array $args): void { - if ($this->mappedComponent) { - $this->response = $this->mappedComponent->execute( + if ($this->mappedAction) { + $this->response = $this->Crud->execute( $this->request->getParam('action'), array_values($this->getRequest()->getParam('pass')) ); @@ -81,37 +74,29 @@ public function invokeAction(Closure $action, array $args): void } /** - * Check if an action can be dispatched using CRUD. + * Set view classes map for content negotiation. * - * @return \Cake\Controller\Component|null The component instance if action is - * mapped else `null`. + * @param array> $map View class map. + * @return void */ - protected function _isActionMapped(): ?Component + public function setViewClasses(array $map): void { - foreach ($this->dispatchComponents as $component => $enabled) { - if (empty($enabled)) { - continue; - } - - // Skip if isActionMapped isn't defined in the Component - if (!method_exists($this->{$component}, 'isActionMapped')) { - continue; - } - - // Skip if the action isn't mapped - if (!$this->{$component}->isActionMapped()) { - continue; - } - - // Skip if execute isn't defined in the Component - if (!method_exists($this->{$component}, 'execute')) { - continue; - } - - // Return the component instance. - return $this->{$component}; - } + $this->viewClasses = $map; + } - return null; + /** + * Get the View classes this controller can perform content negotiation with. + * + * Each view class must implement the `getContentType()` hook method + * to participate in negotiation. + * + * This overrides the Controller::viewClasses() of core. + * + * @see Cake\Http\ContentTypeNegotiation + * @return array> + */ + public function viewClasses(): array + { + return $this->viewClasses; } } diff --git a/src/Core/BaseObject.php b/src/Core/BaseObject.php index 1baf03507..603a11102 100644 --- a/src/Core/BaseObject.php +++ b/src/Core/BaseObject.php @@ -26,14 +26,14 @@ abstract class BaseObject implements EventListenerInterface * * @var \Cake\Controller\Controller */ - protected $_controller; + protected Controller $_controller; /** * Default configuration * * @var array */ - protected $_defaultConfig = []; + protected array $_defaultConfig = []; /** * Constructor @@ -60,10 +60,10 @@ public function implementedEvents(): array /** * Convenient method for Request::is * - * @param string|array $method Method(s) to check for + * @param array|string $method Method(s) to check for * @return bool */ - protected function _checkRequestType($method): bool + protected function _checkRequestType(array|string $method): bool { return $this->_request()->is($method); } diff --git a/src/Core/ProxyTrait.php b/src/Core/ProxyTrait.php index 9f9bb01a1..c7bba02db 100644 --- a/src/Core/ProxyTrait.php +++ b/src/Core/ProxyTrait.php @@ -5,6 +5,7 @@ use Cake\Controller\Controller; use Cake\Datasource\EntityInterface; +use Cake\Datasource\RepositoryInterface; use Cake\Event\EventInterface; use Cake\Http\Response; use Cake\Http\ServerRequest; @@ -19,7 +20,7 @@ trait ProxyTrait /** * @var \Cake\Datasource\EntityInterface|null */ - protected $_entity; + protected ?EntityInterface $_entity = null; /** * Proxy method for `$this->_crud()->action()` @@ -42,7 +43,7 @@ protected function _action(?string $name = null): BaseAction * * @param string $eventName Event name * @param \Crud\Event\Subject|null $data Event data - * @return \Cake\Event\EventInterface + * @return \Cake\Event\EventInterface<\Crud\Event\Subject> * @throws \Exception * @codeCoverageIgnore */ @@ -120,25 +121,14 @@ protected function _response(): Response } /** - * Get a table instance + * Get the model instance * - * @return \Cake\ORM\Table + * @return \Cake\Datasource\RepositoryInterface * @psalm-suppress MoreSpecificReturnType */ - protected function _table() + protected function _model(): RepositoryInterface { - $modelType = $this->getConfig('modelFactory'); - - if (!$modelType && method_exists($this->_controller(), 'fetchTable')) { - return $this->_controller()->fetchTable(); - } - - /** @psalm-suppress DeprecatedMethod */ - return $this->_controller() - ->loadModel( - null, - $modelType ?: $this->_controller()->getModelType() - ); + return $this->_crud()->model(); } /** @@ -154,7 +144,7 @@ protected function _entity(array $data = [], array $options = []): EntityInterfa return $this->_entity; } - return $this->_table()->newEntity($data, $options); + return $this->_model()->newEntity($data, $options); } /** @@ -169,17 +159,13 @@ protected function _subject(array $additional = []): Subject } /** - * Proxy method for `$this->_container->_crud` + * Proxy method for `$this->_controller->Crud` * * @return \Crud\Controller\Component\CrudComponent */ protected function _crud(): CrudComponent { /** @psalm-suppress UndefinedMagicPropertyFetch */ - if (!$this->_controller->Crud) { - return $this->_controller->components()->load('Crud.Crud'); - } - return $this->_controller->Crud; } } diff --git a/src/CrudPlugin.php b/src/CrudPlugin.php new file mode 100644 index 000000000..ec37d7db4 --- /dev/null +++ b/src/CrudPlugin.php @@ -0,0 +1,23 @@ +getLogger(); - if (method_exists($logger, 'getLogs')) { + $logger = ConnectionManager::get($source)->getDriver()->getLogger(); + if ($logger && method_exists($logger, 'getLogs')) { $queryLog[$source] = $logger->getLogs(); } } diff --git a/src/Event/Subject.php b/src/Event/Subject.php index bb69acbc5..a617fca07 100644 --- a/src/Event/Subject.php +++ b/src/Event/Subject.php @@ -13,6 +13,7 @@ * Licensed under The MIT License * For full copyright and license information, please see the LICENSE.txt */ +// phpcs:ignore #[\AllowDynamicProperties] class Subject { @@ -21,7 +22,7 @@ class Subject * * @var array */ - protected $_events = []; + protected array $_events = []; /** * Constructor @@ -70,7 +71,7 @@ public function hasEvent(string $name): bool * Set a list of key / values for this object * * @param array $fields Fields - * @return \Crud\Event\Subject + * @return $this */ public function set(array $fields) { @@ -81,6 +82,17 @@ public function set(array $fields) return $this; } + /** + * Get a value. + * + * @param string $field Field name. + * @return mixed + */ + public function get(string $field): mixed + { + return $this->{$field}; + } + /** * Check if the called action is white listed or blacklisted * depending on the mode @@ -94,7 +106,7 @@ public function set(array $fields) * @return bool * @throws \Exception In case of invalid mode */ - public function shouldProcess(string $mode, $actions = []): bool + public function shouldProcess(string $mode, mixed $actions = []): bool { if (is_string($actions)) { $actions = [$actions]; diff --git a/src/Listener/ApiListener.php b/src/Listener/ApiListener.php index b5f0a99f3..9df05bf98 100644 --- a/src/Listener/ApiListener.php +++ b/src/Listener/ApiListener.php @@ -10,6 +10,8 @@ use Cake\Http\ServerRequest; use Cake\Utility\Hash; use Cake\Utility\Text; +use Cake\View\JsonView; +use Cake\View\XmlView; use Crud\Event\Subject; /** @@ -28,10 +30,10 @@ class ApiListener extends BaseListener * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'viewClasses' => [ - 'json' => 'Json', - 'xml' => 'Xml', + 'json' => JsonView::class, + 'xml' => XmlView::class, ], 'detectors' => [ 'json' => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'], @@ -81,7 +83,7 @@ public function implementedEvents(): array * * Called before the crud action is executed * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void */ public function beforeHandle(EventInterface $event): void @@ -92,7 +94,7 @@ public function beforeHandle(EventInterface $event): void /** * Handle response * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return \Cake\Http\Response|null * @throws \Exception */ @@ -145,7 +147,7 @@ protected function _checkRequestMethods(): void /** * Throw an exception based on API configuration * - * @param \Cake\Event\EventInterface $Event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $Event Event * @param array $exceptionConfig Exception config * @return void * @throws \Exception @@ -334,10 +336,7 @@ protected function _expandPath(Subject $subject, string $path): string */ public function injectViewClasses(): void { - $controller = $this->_controller(); - foreach ($this->getConfig('viewClasses') as $type => $class) { - $controller->RequestHandler->setConfig('viewClassMap', [$type => $class]); - } + $this->_controller()->setViewClasses($this->getConfig('viewClasses')); } /** @@ -354,7 +353,7 @@ public function injectViewClasses(): void * @param string|null $class Class name * @return mixed */ - public function viewClass(string $type, ?string $class = null) + public function viewClass(string $type, ?string $class = null): mixed { if ($class === null) { return $this->getConfig('viewClasses.' . $type); @@ -368,7 +367,7 @@ public function viewClass(string $type, ?string $class = null) * * An API request doesn't need flash messages - so stop them being processed * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void */ public function setFlash(EventInterface $event): void diff --git a/src/Listener/ApiPaginationListener.php b/src/Listener/ApiPaginationListener.php index 10dac4031..c527c1025 100644 --- a/src/Listener/ApiPaginationListener.php +++ b/src/Listener/ApiPaginationListener.php @@ -3,6 +3,7 @@ namespace Crud\Listener; +use Cake\Datasource\Paging\PaginatedInterface; use Cake\Event\EventInterface; /** @@ -36,28 +37,32 @@ public function implementedEvents(): array /** * Appends the pagination information to the JSON or XML output * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void */ public function beforeRender(EventInterface $event): void { - $paging = $this->_request()->getAttribute('paging'); - if (empty($paging)) { - return; + $viewVar = 'data'; + + $action = $this->_action(); + if (method_exists($action, 'viewVar')) { + $viewVar = $action->viewVar(); } - $pagination = current($paging); - if (empty($pagination)) { + $paginatedResultset = $this->_controller()->viewBuilder()->getVar($viewVar); + + if (!$paginatedResultset instanceof PaginatedInterface) { return; } $paginationResponse = [ - 'page_count' => $pagination['pageCount'], - 'current_page' => $pagination['page'], - 'has_next_page' => $pagination['nextPage'], - 'has_prev_page' => $pagination['prevPage'], - 'count' => $pagination['count'], - 'limit' => $pagination['limit'], + 'page_count' => $paginatedResultset->pageCount(), + 'current_page' => $paginatedResultset->currentPage(), + 'has_next_page' => $paginatedResultset->hasNextPage(), + 'has_prev_page' => $paginatedResultset->hasPrevPage(), + 'count' => $paginatedResultset->count(), + 'total_count' => $paginatedResultset->totalCount(), + 'per_page' => $paginatedResultset->perPage(), ]; $this->_controller()->set('pagination', $paginationResponse); diff --git a/src/Listener/ApiQueryLogListener.php b/src/Listener/ApiQueryLogListener.php index 94ecb8370..99a105760 100644 --- a/src/Listener/ApiQueryLogListener.php +++ b/src/Listener/ApiQueryLogListener.php @@ -4,6 +4,7 @@ namespace Crud\Listener; use Cake\Core\Configure; +use Cake\Datasource\ConnectionInterface; use Cake\Datasource\ConnectionManager; use Cake\Datasource\Exception\MissingDatasourceConfigException; use Cake\Event\EventInterface; @@ -27,7 +28,7 @@ class ApiQueryLogListener extends BaseListener * * `connections` List of connection names to log. Empty means all defined connections. */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'connections' => [], ]; @@ -54,7 +55,7 @@ public function implementedEvents(): array /** * Setup logging for all connections * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void */ public function setupLogging(EventInterface $event): void @@ -64,9 +65,7 @@ public function setupLogging(EventInterface $event): void foreach ($connections as $connectionName) { try { $connection = $this->_getSource($connectionName); - $connection->enableQueryLogging(true); - /** @psalm-suppress InternalMethod */ - $connection->setLogger(new QueryLogger()); + $connection->getDriver()->setLogger(new QueryLogger()); } catch (MissingDatasourceConfigException $e) { //Safe to ignore this :-) } @@ -76,7 +75,7 @@ public function setupLogging(EventInterface $event): void /** * Appends the query log to the JSON or XML output * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void */ public function beforeRender(EventInterface $event): void @@ -100,7 +99,7 @@ protected function _getQueryLogs(): array $queryLog = []; foreach ($sources as $source) { - $logger = $this->_getSource($source)->getLogger(); + $logger = $this->_getSource($source)->getDriver()->getLogger(); if (method_exists($logger, 'getLogs')) { $queryLog[$source] = $logger->getLogs(); } @@ -137,7 +136,7 @@ protected function _getSources(): array * @return \Cake\Datasource\ConnectionInterface * @codeCoverageIgnore */ - protected function _getSource(string $source) + protected function _getSource(string $source): ConnectionInterface { return ConnectionManager::get($source); } diff --git a/src/Listener/BaseListener.php b/src/Listener/BaseListener.php index 87bb01f55..0211c5d6f 100644 --- a/src/Listener/BaseListener.php +++ b/src/Listener/BaseListener.php @@ -76,7 +76,7 @@ public function implementedEvents(): array * using the "name" configuration property * * @param string|null $value Value - * @return string|$this + * @return $this|string */ public function resourceName(?string $value = null) { diff --git a/src/Listener/RedirectListener.php b/src/Listener/RedirectListener.php index ecdcb1444..d718c075b 100644 --- a/src/Listener/RedirectListener.php +++ b/src/Listener/RedirectListener.php @@ -22,7 +22,7 @@ class RedirectListener extends BaseListener * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'readers' => [], ]; @@ -88,7 +88,7 @@ public function setup(): void * @param mixed $reader Reader * @return mixed */ - public function reader(string $key, $reader = null) + public function reader(string $key, mixed $reader = null): mixed { if ($reader === null) { return $this->getConfig('readers.' . $key); @@ -103,7 +103,7 @@ public function reader(string $key, $reader = null) * If a special redirect key is provided, change the * redirection URL target * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void * @throws \Exception */ @@ -165,7 +165,7 @@ protected function _getUrl(Subject $subject, array $url): array * @return mixed * @throws \Exception if the reader is invalid */ - protected function _getKey(Subject $subject, string $reader, string $key) + protected function _getKey(Subject $subject, string $reader, string $key): mixed { $callable = $this->reader($reader); diff --git a/src/Listener/RelatedModelsListener.php b/src/Listener/RelatedModelsListener.php index a8c130e2c..a68aaae0d 100644 --- a/src/Listener/RelatedModelsListener.php +++ b/src/Listener/RelatedModelsListener.php @@ -6,8 +6,10 @@ use Cake\Datasource\EntityInterface; use Cake\Event\EventInterface; use Cake\ORM\Association; +use Cake\ORM\Table; use Cake\Utility\Inflector; use RuntimeException; +use function Cake\Core\pluginSplit; /** * Implements beforeRender event listener to set related models' lists to @@ -34,7 +36,7 @@ public function implementedEvents(): array /** * Automatically parse and contain related table classes * - * @param \Cake\Event\EventInterface $event Before paginate event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Before paginate event * @return void */ public function beforePaginate(EventInterface $event): void @@ -60,7 +62,7 @@ public function beforePaginate(EventInterface $event): void /** * Fetches related models' list and sets them to a variable for the view * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void * @codeCoverageIgnore */ @@ -77,8 +79,8 @@ public function beforeRender(EventInterface $event): void * Find and publish all related models to the view * for an action * - * @param null|string $action If NULL the current action will be used - * @param null|\Cake\Datasource\EntityInterface $entity The optional entity for which we we trying to find related + * @param string|null $action If NULL the current action will be used + * @param \Cake\Datasource\EntityInterface|null $entity The optional entity for which we we trying to find related * @return void */ public function publishRelatedModels(?string $action = null, ?EntityInterface $entity = null): void @@ -99,7 +101,7 @@ public function publishRelatedModels(?string $action = null, ?EntityInterface $e } $finder = $this->finder($association); - $query = $association->find($finder, $this->_findOptions($association)); + $query = $association->find($finder, ...$this->_findOptions($association)); $subject = $this->_subject(compact('name', 'viewVar', 'query', 'association', 'entity')); $event = $this->_trigger('relatedModel', $subject); @@ -171,7 +173,7 @@ public function models(?string $action = null): array * @param string|null $action The action to configure * @return mixed */ - public function relatedModels($related = null, ?string $action = null) + public function relatedModels(mixed $related = null, ?string $action = null): mixed { if ($related === null) { return $this->_action($action)->getConfig('relatedModels'); @@ -191,7 +193,9 @@ public function getAssociatedByType(array $types = []): array { $return = []; - $table = $this->_table(); + $table = $this->_model(); + assert($table instanceof Table); + foreach ($table->associations()->keys() as $association) { /** @var \Cake\ORM\Association $associationClass */ $associationClass = $table->associations()->get($association); @@ -217,7 +221,8 @@ public function getAssociatedByName(array $names): array { $return = []; - $table = $this->_table(); + $table = $this->_model(); + assert($table instanceof Table); foreach ($names as $association) { $associationClass = $table->associations()->get($association); if (!$associationClass) { diff --git a/src/Listener/SearchListener.php b/src/Listener/SearchListener.php index ec8876ba7..c422e52f5 100644 --- a/src/Listener/SearchListener.php +++ b/src/Listener/SearchListener.php @@ -14,7 +14,7 @@ class SearchListener extends BaseListener * * @var array */ - protected $_defaultConfig = [ + protected array $_defaultConfig = [ 'enabled' => [ 'Crud.beforeLookup', 'Crud.beforePaginate', @@ -39,7 +39,7 @@ public function implementedEvents(): array /** * Inject search conditions into the query object. * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return void */ public function injectSearch(EventInterface $event): void @@ -48,17 +48,20 @@ public function injectSearch(EventInterface $event): void return; } - $repository = $this->_table(); - if ($repository instanceof Table && !$repository->behaviors()->has('Search')) { + $repository = $this->_model(); + assert($repository instanceof Table); + + if (!$repository->behaviors()->has('Search')) { throw new RuntimeException(sprintf( 'Missing Search.Search behavior on %s', get_class($repository) )); } - $filterParams = ['search' => $this->_request()->getQuery()]; - $filterParams['collection'] = $this->getConfig('collection'); - - $event->getSubject()->query->find('search', $filterParams); + $event->getSubject()->query->find( + 'search', + search: $this->_request()->getQuery(), + collection: $this->getConfig('collection') + ); } } diff --git a/src/Log/QueryLogger.php b/src/Log/QueryLogger.php index bd2550053..f41142023 100644 --- a/src/Log/QueryLogger.php +++ b/src/Log/QueryLogger.php @@ -3,14 +3,17 @@ namespace Crud\Log; -class QueryLogger extends \Cake\Database\Log\QueryLogger +use Cake\Database\Log\QueryLogger as CakeQueryLogger; +use Stringable; + +class QueryLogger extends CakeQueryLogger { /** * Logs * * @var array */ - protected $_logs = []; + protected array $_logs = []; /** * Get logs @@ -25,7 +28,7 @@ public function getLogs(): array /** * @inheritDoc */ - public function log($level, $message, array $context = []) + public function log($level, string|Stringable $message, array $context = []): void { $this->_logs[] = (string)$context['query']; diff --git a/src/Plugin.php b/src/Plugin.php deleted file mode 100644 index a67663c0a..000000000 --- a/src/Plugin.php +++ /dev/null @@ -1,10 +0,0 @@ -getMockBuilder($class) - ->setMethods($methods) - ->setConstructorArgs([['alias' => $alias, 'table' => $table]]) - ->getMock(); - /** @psalm-suppress UndefinedInterfaceMethod */ - $mock->setConnection(ConnectionManager::get('test')); - - return $mock; - } - /** * Assert these CRUD events was emitted during the life cycle * @@ -84,17 +58,17 @@ public function getModel(string $class, $methods, string $alias, string $table): * automatically before comparison * * @param array $expected An array of CRUD events we expected to be fired - * @param array|null $actual Can be an Event class, Crud subject or array with event names + * @param \Cake\Event\EventInterface|array|null $actual Can be an Event class, Crud subject or array with event names * @return void * @throws \Exception */ - public function assertEvents(array $expected, ?array $actual = null): void + public function assertEvents(array $expected, EventInterface|array|null $actual = null): void { if ($actual === null) { $actual = $this->_subject; } - if ($actual instanceof Event) { + if ($actual instanceof EventInterface) { $actual = $actual->getSubject()->getEvents(); } diff --git a/src/Traits/FindMethodTrait.php b/src/Traits/FindMethodTrait.php index 0d7a7dcdb..378b8c07c 100644 --- a/src/Traits/FindMethodTrait.php +++ b/src/Traits/FindMethodTrait.php @@ -4,6 +4,7 @@ namespace Crud\Traits; use Cake\Datasource\EntityInterface; +use Cake\ORM\Table; use Crud\Event\Subject; trait FindMethodTrait @@ -14,11 +15,11 @@ trait FindMethodTrait * If `$method` is NULL the current value is returned * else the `findMethod` is changed * - * @param string|array|null $method Method name as string or array where + * @param array|string|null $method Method name as string or array where * key is finder name and value is find options. - * @return string|array|$this + * @return $this|array|string */ - public function findMethod($method = null) + public function findMethod(string|array|null $method = null) { if ($method === null) { return $this->getConfig('findMethod'); @@ -50,17 +51,18 @@ protected function _extractFinder(): array /** * Find a record from the ID * - * @param string|null $id Record id + * @param string|int|null $id Record id * @param \Crud\Event\Subject $subject Event subject * @return \Cake\Datasource\EntityInterface * @throws \Exception */ - protected function _findRecord(?string $id, Subject $subject): EntityInterface + protected function _findRecord(string|int|null $id, Subject $subject): EntityInterface { - $repository = $this->_table(); + $repository = $this->_model(); + assert($repository instanceof Table); [$finder, $options] = $this->_extractFinder(); - $query = $repository->find($finder, $options); + $query = $repository->find($finder, ...$options); /** * @psalm-suppress PossiblyInvalidArgument * @psalm-suppress InvalidArrayOffset @@ -88,12 +90,12 @@ protected function _findRecord(?string $id, Subject $subject): EntityInterface /** * Throw exception if a record is not found * - * @param string|null $id Record id + * @param string|int|null $id Record id * @param \Crud\Event\Subject $subject Event subject * @return void * @throws \Exception */ - protected function _notFound(?string $id, Subject $subject): void + protected function _notFound(string|int|null $id, Subject $subject): void { $subject->set(['success' => false]); $this->_trigger('recordNotFound', $subject); diff --git a/src/Traits/RedirectTrait.php b/src/Traits/RedirectTrait.php index a840aadf3..9b18cbc2a 100644 --- a/src/Traits/RedirectTrait.php +++ b/src/Traits/RedirectTrait.php @@ -25,11 +25,11 @@ trait RedirectTrait * - key : the key to read inside the reader * - url : the URL to redirect to * - * @param null|string $name Name of the redirection rule - * @param null|array $config Redirection configuration + * @param string|null $name Name of the redirection rule + * @param array|null $config Redirection configuration * @return mixed */ - public function redirectConfig(?string $name = null, ?array $config = null) + public function redirectConfig(?string $name = null, ?array $config = null): mixed { if ($name === null && $config === null) { return $this->getConfig('redirect'); @@ -47,10 +47,10 @@ public function redirectConfig(?string $name = null, ?array $config = null) /** * Returns the redirect_url for this request, with a fallback to the referring page * - * @param string|null $default Default URL to use redirect_url is not found in request or data - * @return mixed + * @param array|string|null $default Default URL to use redirect_url is not found in request or data + * @return array|string */ - protected function _refererRedirectUrl(?string $default = null) + protected function _refererRedirectUrl(array|string|null $default = null): array|string { $controller = $this->_controller(); @@ -60,10 +60,10 @@ protected function _refererRedirectUrl(?string $default = null) /** * Returns the _redirect_url for this request. * - * @param string|array|null $default Default URL to use if _redirect_url if not found in request or data. - * @return mixed + * @param array|string|null $default Default URL to use if _redirect_url if not found in request or data. + * @return array|string */ - protected function _redirectUrl($default = null) + protected function _redirectUrl(array|string|null $default = null): array|string { $request = $this->_request(); @@ -87,11 +87,11 @@ protected function _redirectUrl($default = null) * Called for all redirects inside CRUD * * @param \Crud\Event\Subject $subject Event subject - * @param string|array|null $url URL + * @param array|string|null $url URL * @param int $status Status code * @return \Cake\Http\Response|null */ - protected function _redirect(Subject $subject, $url = null, int $status = 302): ?Response + protected function _redirect(Subject $subject, string|array|null $url = null, int $status = 302): ?Response { $url = $this->_redirectUrl($url); diff --git a/src/Traits/SaveMethodTrait.php b/src/Traits/SaveMethodTrait.php index 3f89216d5..f813e4ab3 100644 --- a/src/Traits/SaveMethodTrait.php +++ b/src/Traits/SaveMethodTrait.php @@ -14,7 +14,7 @@ trait SaveMethodTrait * @param mixed $method Method name * @return mixed */ - public function saveMethod($method = null) + public function saveMethod(mixed $method = null): mixed { if ($method === null) { return $this->getConfig('saveMethod'); @@ -34,7 +34,7 @@ public function saveMethod($method = null) * @param mixed $config Configuration array * @return mixed */ - public function saveOptions($config = null) + public function saveOptions(mixed $config = null): mixed { if ($config === null) { return $this->getConfig('saveOptions'); diff --git a/src/Traits/SerializeTrait.php b/src/Traits/SerializeTrait.php index 97e2746e0..7743bfd80 100644 --- a/src/Traits/SerializeTrait.php +++ b/src/Traits/SerializeTrait.php @@ -11,10 +11,10 @@ trait SerializeTrait * If `$keys` is NULL the current configuration is returned * else the `$serialize` configuration is changed. * - * @param null|array $keys Keys to serialize + * @param array|null $keys Keys to serialize * @return mixed */ - public function serialize(?array $keys = null) + public function serialize(?array $keys = null): mixed { if ($keys === null) { return (array)$this->getConfig('serialize'); diff --git a/src/Traits/ViewTrait.php b/src/Traits/ViewTrait.php index 5e81da89e..83e1ace5a 100644 --- a/src/Traits/ViewTrait.php +++ b/src/Traits/ViewTrait.php @@ -17,7 +17,7 @@ trait ViewTrait * @param mixed $view View name * @return mixed */ - public function view($view = null) + public function view(mixed $view = null): mixed { if (empty($view)) { return $this->getConfig('view') ?: $this->_request()->getParam('action'); diff --git a/src/Traits/ViewVarTrait.php b/src/Traits/ViewVarTrait.php index 39a341e2b..34b100a93 100644 --- a/src/Traits/ViewVarTrait.php +++ b/src/Traits/ViewVarTrait.php @@ -13,14 +13,14 @@ trait ViewVarTrait * Publish the viewVar so people can do $$viewVar and end up * wit the entity in the view * - * @param \Cake\Event\EventInterface $event Event - * @return false|null + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event + * @return void * @throws \Exception */ - public function publishViewVar(EventInterface $event) + public function publishViewVar(EventInterface $event): void { if (!$this->responding()) { - return false; + return; } $viewVar = $this->viewVar(); @@ -38,7 +38,7 @@ public function publishViewVar(EventInterface $event) * @return mixed * @throws \Exception */ - public function viewVar($name = null) + public function viewVar(mixed $name = null): mixed { if (empty($name)) { return $this->getConfig('viewVar') ?: $this->_deriveViewVar(); @@ -73,11 +73,11 @@ protected function _deriveViewVar(): string * Derive the viewVar value based on the scope of the action * as well as the Event being handled * - * @param \Cake\Event\EventInterface $event Event + * @param \Cake\Event\EventInterface<\Crud\Event\Subject> $event Event * @return mixed * @throws \Exception */ - protected function _deriveViewValue(EventInterface $event) + protected function _deriveViewValue(EventInterface $event): mixed { $key = $this->_action()->subjectEntityKey(); diff --git a/tests/Fixture/BlogsFixture.php b/tests/Fixture/BlogsFixture.php index a06d86d7f..99b1aa755 100644 --- a/tests/Fixture/BlogsFixture.php +++ b/tests/Fixture/BlogsFixture.php @@ -5,15 +5,7 @@ class BlogsFixture extends TestFixture { - public $fields = [ - 'id' => ['type' => 'integer'], - 'is_active' => ['type' => 'boolean', 'default' => true, 'null' => false], - 'name' => ['type' => 'string', 'length' => 255, 'null' => false], - 'body' => ['type' => 'text', 'null' => false], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - public $records = [ + public array $records = [ ['name' => '1st post', 'body' => '1st post body'], ['name' => '2nd post', 'body' => '2nd post body'], ['name' => '3rd post', 'body' => '3rd post body'], diff --git a/tests/Fixture/UsersFixture.php b/tests/Fixture/UsersFixture.php index 860441ea6..c8697c3cd 100644 --- a/tests/Fixture/UsersFixture.php +++ b/tests/Fixture/UsersFixture.php @@ -5,14 +5,7 @@ class UsersFixture extends TestFixture { - public $fields = [ - 'id' => ['type' => 'uuid'], - 'is_active' => ['type' => 'boolean', 'default' => true, 'null' => false], - 'username' => ['type' => 'string', 'length' => 255, 'null' => false], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - - public $records = [ + public array $records = [ ['id' => '0acad6f2-b47e-4fc1-9086-cbc906dc45fd', 'username' => 'test_1'], ['id' => '968ad2b3-f41d-4de3-909a-74a3ce85e826', 'username' => 'test_2'], ['id' => 'fac4fb37-7d1e-4063-adef-4dcde4c009ef', 'username' => 'test_3'], diff --git a/tests/TestCase/Action/AddActionTest.php b/tests/TestCase/Action/AddActionTest.php index a34631d9f..a489c0667 100644 --- a/tests/TestCase/Action/AddActionTest.php +++ b/tests/TestCase/Action/AddActionTest.php @@ -19,14 +19,14 @@ class AddActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = ['plugin.Crud.Blogs']; + protected array $fixtures = ['plugin.Crud.Blogs']; /** * Table class to mock on * * @var string */ - public $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; + public string $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; /** * Test the normal HTTP GET flow of _get @@ -41,10 +41,10 @@ public function testActionGet() $expected = 'New Blog'; $this->assertStringContainsString($expected, $result, 'legend do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"id" do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"name" do not match the expected value'); $expected = ''; @@ -66,10 +66,10 @@ public function testActionGetWithQueryArgs() $expected = 'New Blog'; $this->assertStringContainsString($expected, $result, 'legend do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"id" do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"name" do not match the expected value'); $expected = ''; @@ -89,7 +89,7 @@ public function testActionPost() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -134,7 +134,7 @@ public function testActionPostWithAddRedirect() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -179,7 +179,7 @@ public function testActionPostWithEditRedirect() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -223,7 +223,7 @@ public function testActionPostErrorSave() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -277,7 +277,7 @@ public function testActionPostValidationErrors() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -333,7 +333,7 @@ function ($event) { * * @return array */ - public function apiGetHttpMethodProvider() + public static function apiGetHttpMethodProvider() { return [ ['get'], @@ -365,7 +365,7 @@ public function testApiGet($method) * * @return array */ - public function apiUpdateHttpMethodProvider() + public static function apiUpdateHttpMethodProvider() { return [ ['put'], @@ -388,7 +388,7 @@ public function testApiCreate($method) function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -433,7 +433,7 @@ function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -485,7 +485,7 @@ function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -531,7 +531,7 @@ public function testStopAddWithDefaultSubjectSuccess() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -581,7 +581,7 @@ public function testStopAddWithManuallySetSubjectSuccess() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash diff --git a/tests/TestCase/Action/BaseActionTest.php b/tests/TestCase/Action/BaseActionTest.php index 0d680c8bc..51cd99078 100644 --- a/tests/TestCase/Action/BaseActionTest.php +++ b/tests/TestCase/Action/BaseActionTest.php @@ -11,7 +11,6 @@ use Cake\Http\Exception\MethodNotAllowedException; use Cake\Http\Exception\NotFoundException; use Cake\Http\Exception\NotImplementedException; -use Cake\Http\Response; use Cake\Http\ServerRequest; use Crud\Action\BaseAction; use Crud\Controller\Component\CrudComponent; @@ -25,17 +24,18 @@ */ class BaseActionTest extends TestCase { + protected ServerRequest $Request; + public function setUp(): void { parent::setUp(); - $this->Request = $this->getMockBuilder(ServerRequest::class) - ->getMock(); + $this->Request = (new ServerRequest()) + ->withParam('action', 'index'); $this->Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['set']) ->setConstructorArgs([ $this->Request, - new Response(), 'CrudExamples', EventManager::instance(), ]) @@ -46,7 +46,6 @@ public function setUp(): void ->addMethods(['foobar']) ->getMock(); $this->Controller->Crud = $this->Crud; - $this->Controller->defaultTable = 'CrudExamples'; $this->getTableLocator()->get('CrudExamples')->setAlias('MyModel'); @@ -188,14 +187,12 @@ public function testEnabledActionWorks() */ public function testDisable() { - $i = 0; - $Action = $this->getMockBuilder(BaseAction::class) ->onlyMethods(['setConfig']) ->setConstructorArgs([$this->Controller]) ->getMock(); $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('setConfig', 'enabled was not changed to false by config()') ->with('enabled', false); @@ -212,14 +209,12 @@ public function testDisable() */ public function testEnable() { - $i = 0; - $Action = $this->getMockBuilder(BaseAction::class) ->onlyMethods(['setConfig']) ->setConstructorArgs([$this->Controller]) ->getMock(); $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('setConfig', 'enabled was not changed to false by config()') ->with('enabled', true); @@ -532,18 +527,17 @@ public function testHandle() ->method('getMethod') ->will($this->returnValue('GET')); - $i = 0; $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('getConfig') ->with('enabled') ->will($this->returnValue(true)); $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_request') ->will($this->returnValue($Request)); $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_get'); $Action->handle(); @@ -565,9 +559,8 @@ public function testHandleDisabled() ->setConstructorArgs([$this->Controller]) ->getMock(); - $i = 0; $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('getConfig') ->with('enabled') ->will($this->returnValue(false)); @@ -602,14 +595,13 @@ public function testGenericHandle() ->method('getMethod') ->will($this->returnValue('GET')); - $i = 0; $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('getConfig') ->with('enabled') ->will($this->returnValue(true)); $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_request') ->will($this->returnValue($Request)); $Action @@ -644,14 +636,13 @@ public function testHandleException() ->method('getMethod') ->will($this->returnValue('GET')); - $i = 0; $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('getConfig') ->with('enabled') ->will($this->returnValue(true)); $Action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_request') ->will($this->returnValue($Request)); diff --git a/tests/TestCase/Action/Bulk/DeleteActionTest.php b/tests/TestCase/Action/Bulk/DeleteActionTest.php index b5d6cecde..f4072222a 100644 --- a/tests/TestCase/Action/Bulk/DeleteActionTest.php +++ b/tests/TestCase/Action/Bulk/DeleteActionTest.php @@ -17,7 +17,7 @@ class DeleteActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = [ + protected array $fixtures = [ 'plugin.Crud.Blogs', 'plugin.Crud.Users', ]; @@ -27,14 +27,14 @@ class DeleteActionTest extends IntegrationTestCase * * @var string */ - public $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; + public string $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; /** * Data provider with all HTTP verbs * * @return array */ - public function allHttpMethodProvider() + public static function allHttpMethodProvider() { return [ ['post'], @@ -56,7 +56,7 @@ public function testAllRequestMethods($method) function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -100,7 +100,7 @@ public function testStopBeforeBulk() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -148,7 +148,7 @@ public function testUuidRequestData() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash diff --git a/tests/TestCase/Action/Bulk/SetValueActionTest.php b/tests/TestCase/Action/Bulk/SetValueActionTest.php index 195ea5e75..2ef8d9e79 100644 --- a/tests/TestCase/Action/Bulk/SetValueActionTest.php +++ b/tests/TestCase/Action/Bulk/SetValueActionTest.php @@ -17,7 +17,7 @@ class SetValueActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = [ + protected array $fixtures = [ 'plugin.Crud.Blogs', 'plugin.Crud.Users', ]; @@ -27,14 +27,14 @@ class SetValueActionTest extends IntegrationTestCase * * @var string */ - public $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; + public string $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; /** * Data provider with all HTTP verbs * * @return array */ - public function allHttpMethodProvider() + public static function allHttpMethodProvider() { return [ ['post'], @@ -56,7 +56,7 @@ public function testAllRequestMethods($method) function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -100,7 +100,7 @@ public function testStopBeforeBulk() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -148,7 +148,7 @@ public function testUuidRequestData() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -178,31 +178,4 @@ function ($event) { $this->assertTrue($this->_subject->success); $this->assertRedirect('/users'); } - - /** - * Test custom finder with options - * - * @return void - */ - public function testPostWithCustomFinder() - { - $this->_eventManager->on( - 'Controller.initialize', - ['priority' => 11], - function ($event) { - $this->_subscribeToEvents($this->_controller); - $this->_controller->Crud->action('deactivateAll') - ->findMethod(['withCustomOptions' => ['foo' => 'bar']]); - } - ); - - $this->post('/blogs/deactivateAll', [ - 'id' => [ - 1, - 2, - ], - ]); - - $this->assertSame(['foo' => 'bar'], $this->_controller->Blogs->customOptions); - } } diff --git a/tests/TestCase/Action/Bulk/ToggleActionTest.php b/tests/TestCase/Action/Bulk/ToggleActionTest.php index 843697554..d9b6a141a 100644 --- a/tests/TestCase/Action/Bulk/ToggleActionTest.php +++ b/tests/TestCase/Action/Bulk/ToggleActionTest.php @@ -17,7 +17,7 @@ class ToggleActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = [ + protected array $fixtures = [ 'plugin.Crud.Blogs', 'plugin.Crud.Users', ]; @@ -27,14 +27,14 @@ class ToggleActionTest extends IntegrationTestCase * * @var string */ - public $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; + public string $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; /** * Data provider with all HTTP verbs * * @return array */ - public function allHttpMethodProvider() + public static function allHttpMethodProvider() { return [ ['post'], @@ -56,7 +56,7 @@ public function testAllRequestMethods($method) function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -100,7 +100,7 @@ public function testStopBeforeBulk() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -148,7 +148,7 @@ public function testUuidRequestData() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash diff --git a/tests/TestCase/Action/DeleteActionTest.php b/tests/TestCase/Action/DeleteActionTest.php index 567d50901..b5cdcad98 100644 --- a/tests/TestCase/Action/DeleteActionTest.php +++ b/tests/TestCase/Action/DeleteActionTest.php @@ -17,21 +17,21 @@ class DeleteActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = ['plugin.Crud.Blogs']; + protected array $fixtures = ['plugin.Crud.Blogs']; /** * Table class to mock on * * @var string */ - public $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; + public string $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; /** * Data provider with HTTP verbs * * @return array */ - public function allHttpMethodProvider() + public static function allHttpMethodProvider() { return [ ['post'], @@ -53,7 +53,7 @@ public function testAllRequestMethods($method) function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -104,7 +104,7 @@ public function testStopDelete() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -159,7 +159,7 @@ public function testStopBeforeRedirect() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash diff --git a/tests/TestCase/Action/EditActionTest.php b/tests/TestCase/Action/EditActionTest.php index cd80acd9e..68dec77c3 100644 --- a/tests/TestCase/Action/EditActionTest.php +++ b/tests/TestCase/Action/EditActionTest.php @@ -18,14 +18,14 @@ class EditActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = ['plugin.Crud.Blogs']; + protected array $fixtures = ['plugin.Crud.Blogs']; /** * Table class to mock on * * @var string */ - public $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; + public string $tableClass = 'Crud\Test\App\Model\Table\BlogsTable'; /** * Test the normal HTTP GET flow of _get @@ -40,10 +40,10 @@ public function testActionGet() $expected = 'Edit Blog'; $this->assertStringContainsString($expected, $result, 'legend do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"id" do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"name" do not match the expected value'); $expected = ''; @@ -65,10 +65,10 @@ public function testActionGetWithQueryArgs() $expected = 'Edit Blog'; $this->assertStringContainsString($expected, $result, 'legend do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"id" do not match the expected value'); - $expected = ''; + $expected = ''; $this->assertStringContainsString($expected, $result, '"name" do not match the expected value'); $expected = ''; @@ -108,7 +108,7 @@ public function testActionPost() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -151,7 +151,7 @@ public function testActionPostErrorSave() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -205,7 +205,7 @@ public function testActionPostValidationErrors() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash @@ -269,7 +269,7 @@ public function testActionPatch() function ($event) { $this->_controller->Flash = $this->getMockBuilder(FlashComponent::class) ->onlyMethods(['set']) - ->disableOriginalConstructor() + ->setConstructorArgs([$this->_controller->components()]) ->getMock(); $this->_controller->Flash diff --git a/tests/TestCase/Action/IndexActionTest.php b/tests/TestCase/Action/IndexActionTest.php index c57a571d9..6eff63904 100644 --- a/tests/TestCase/Action/IndexActionTest.php +++ b/tests/TestCase/Action/IndexActionTest.php @@ -16,14 +16,14 @@ class IndexActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = ['plugin.Crud.Blogs']; + protected array $fixtures = ['plugin.Crud.Blogs']; /** * Data provider with all HTTP verbs * * @return array */ - public function allHttpMethodProvider() + public static function allHttpMethodProvider() { return [ ['get'], diff --git a/tests/TestCase/Action/LookupActionTest.php b/tests/TestCase/Action/LookupActionTest.php index 3731faf36..42e431d6a 100644 --- a/tests/TestCase/Action/LookupActionTest.php +++ b/tests/TestCase/Action/LookupActionTest.php @@ -16,7 +16,7 @@ class LookupActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = ['plugin.Crud.Blogs']; + protected array $fixtures = ['plugin.Crud.Blogs']; /** * Test with no extra options diff --git a/tests/TestCase/Action/ViewActionTest.php b/tests/TestCase/Action/ViewActionTest.php index 8507924ee..476eba1c4 100644 --- a/tests/TestCase/Action/ViewActionTest.php +++ b/tests/TestCase/Action/ViewActionTest.php @@ -16,14 +16,14 @@ class ViewActionTest extends IntegrationTestCase * * @var array */ - protected $fixtures = ['plugin.Crud.Blogs']; + protected array $fixtures = ['plugin.Crud.Blogs']; /** * Data provider with all HTTP verbs * * @return array */ - public function allHttpMethodProvider() + public static function allHttpMethodProvider() { return [ ['get'], diff --git a/tests/TestCase/ActionsTest.php b/tests/TestCase/ActionsTest.php deleted file mode 100644 index 8bd9f0fba..000000000 --- a/tests/TestCase/ActionsTest.php +++ /dev/null @@ -1,23 +0,0 @@ -addTestDirectoryRecursive($testPath . '/Action'); - - return $suite; - } -} diff --git a/tests/TestCase/AllTest.php b/tests/TestCase/AllTest.php deleted file mode 100644 index b1f2e55ca..000000000 --- a/tests/TestCase/AllTest.php +++ /dev/null @@ -1,23 +0,0 @@ -addTestDirectoryRecursive($testPath); - - return $suite; - } -} diff --git a/tests/TestCase/Controller/Component/CrudComponentTest.php b/tests/TestCase/Controller/Component/CrudComponentTest.php index 9e1f85b4e..396c0f444 100644 --- a/tests/TestCase/Controller/Component/CrudComponentTest.php +++ b/tests/TestCase/Controller/Component/CrudComponentTest.php @@ -3,13 +3,15 @@ namespace Crud\TestCase\Controller\Crud; +use Cake\Controller\ComponentRegistry; +use Cake\Controller\Controller; use Cake\Event\Event; use Cake\Event\EventManager; use Cake\Http\Exception\BadRequestException; use Cake\Http\Exception\MethodNotAllowedException; use Cake\Http\Exception\NotFoundException; -use Cake\Http\Response; use Cake\Http\ServerRequest; +use Cake\ORM\Table; use Crud\Controller\Component\CrudComponent; use Crud\Test\App\Controller\Component\TestCrudComponent; use Crud\Test\App\Controller\CrudExamplesController; @@ -29,10 +31,16 @@ class CrudComponentTest extends TestCase * Use the core posts fixture to have something to work on. * What fixture is used is almost irrelevant, was chosen as it is simple */ - protected $fixtures = [ + protected array $fixtures = [ 'core.Posts', ]; + protected Table $model; + protected ServerRequest $request; + protected Controller $controller; + protected ComponentRegistry $Registry; + protected CrudComponent $Crud; + /** * setUp * @@ -48,16 +56,15 @@ public function setUp(): void $this->request = $this->getMockBuilder(ServerRequest::class) ->onlyMethods(['is', 'getMethod']) - ->getMock(); + ->getMock() + ->withParam('action', 'index'); $this->request->expects($this->any())->method('is')->will($this->returnValue(true)); - $response = new Response(); $this->controller = $this->getMockBuilder(CrudExamplesController::class) ->onlyMethods(['redirect', 'render']) - ->setConstructorArgs([$this->request, $response, 'CrudExamples', EventManager::instance()]) + ->setConstructorArgs([$this->request, 'CrudExamples', EventManager::instance()]) ->getMock(); - $this->controller->defaultTable = 'CrudExamples'; $this->Registry = $this->controller->components(); @@ -237,7 +244,7 @@ public function testView() ->method('render'); $this->Crud->view('view', 'cupcakes'); - $this->Crud->execute('view', [1]); + $this->Crud->execute('view', ['1']); } /** @@ -406,12 +413,8 @@ public function testViewWithArrayIndexAction() */ public function testSetModelPropertiesDefault() { - $this->markTestSkipped( - 'Tests still not updated.' - ); - $this->Crud->setAction('index'); - $this->assertSame('CrudExamples', $this->Crud->getModelName()); + $this->assertNull($this->Crud->getModelName()); } /** @@ -701,10 +704,6 @@ public function testViewVarMultipleActions() */ public function testFindMethodMultipleActions() { - $this->markTestSkipped( - 'Tests still not updated.' - ); - $this->Crud->findMethod(['index' => 'my_all', 'view' => 'my_view']); $expected = 'my_all'; @@ -875,10 +874,13 @@ public function testUseModel() $this->Crud = new CrudComponent($this->Registry, ['actions' => ['Crud.Index']]); $this->Crud->beforeFilter(new Event('Controller.beforeFilter')); $this->controller->Crud = $this->Crud; - $class = $this->getMockClass('Model'); - $this->Crud->useModel($class); + $this->controller->getTableLocator()->set('MyModel', new Table([ + 'table' => 'posts', + 'alias' => 'MyModel', + ])); + $this->Crud->useModel('MyModel'); - $this->assertEquals($class, $this->Crud->table()->getAlias()); + $this->assertEquals('MyModel', $this->Crud->model()->getAlias()); } /** diff --git a/tests/TestCase/Error/ExceptionRendererTest.php b/tests/TestCase/Error/ExceptionRendererTest.php index f7a2eb1bd..746254aab 100644 --- a/tests/TestCase/Error/ExceptionRendererTest.php +++ b/tests/TestCase/Error/ExceptionRendererTest.php @@ -5,9 +5,8 @@ use Cake\Controller\Controller; use Cake\Core\Configure; -use Cake\Core\Exception\Exception; +use Cake\Core\Exception\CakeException; use Cake\Datasource\ConnectionManager; -use Cake\Http\Response; use Cake\Http\ServerRequest; use Cake\ORM\Entity; use Cake\TestSuite\TestCase; @@ -25,10 +24,11 @@ public function setUp(): void public function testNormalExceptionRendering() { - $Exception = new Exception('Hello World'); + $Exception = new CakeException('Hello World'); $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) @@ -63,11 +63,6 @@ public function testNormalExceptionRendering() ], ]; - if (version_compare(Configure::version(), '4.2.0', '<')) { - $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; - $expected['exception']['code'] = 500; - } - $actual = $viewVars['data']; unset($actual['trace'], $actual['file'], $actual['line']); $this->assertEquals($expected, $actual); @@ -92,13 +87,13 @@ public function testNormalExceptionRendering() public function testNormalExceptionRenderingQueryLog() { - $Exception = new Exception('Hello World'); + $Exception = new CakeException('Hello World'); $QueryLogger = $this->getMockBuilder(QueryLogger::class) ->onlyMethods(['getLogs']) ->getMock(); - $currentLogger = ConnectionManager::get('test')->getLogger(); - ConnectionManager::get('test')->setLogger($QueryLogger); + $currentLogger = ConnectionManager::get('test')->getDriver()->getLogger(); + ConnectionManager::get('test')->getDriver()->setLogger($QueryLogger); $QueryLogger ->expects($this->once()) @@ -108,9 +103,8 @@ public function testNormalExceptionRenderingQueryLog() $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); - $Controller->request = new ServerRequest(); - $Controller->response = new Response(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) ->onlyMethods(['_getController']) @@ -144,11 +138,6 @@ public function testNormalExceptionRenderingQueryLog() ], ]; - if (version_compare(Configure::version(), '4.2.0', '<')) { - $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; - $expected['exception']['code'] = 500; - } - $actual = $viewVars['data']; $queryLog = $viewVars['queryLog']; @@ -174,18 +163,19 @@ public function testNormalExceptionRenderingQueryLog() $this->assertTrue(isset($viewVars['error'])); $this->assertSame($Exception, $viewVars['error']); - ConnectionManager::get('test')->setLogger($currentLogger); + if ($currentLogger) { + ConnectionManager::get('test')->getDriver()->setLogger($currentLogger); + } } public function testNormalNestedExceptionRendering() { - $Exception = new Exception('Hello World'); + $Exception = new CakeException('Hello World'); $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); - $Controller->request = new ServerRequest(); - $Controller->response = new Response(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) ->onlyMethods(['_getController']) @@ -219,11 +209,6 @@ public function testNormalNestedExceptionRendering() ], ]; - if (version_compare(Configure::version(), '4.2.0', '<')) { - $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; - $expected['exception']['code'] = 500; - } - $actual = $viewVars['data']; unset($actual['trace'], $actual['file'], $actual['line']); $this->assertEquals($expected, $actual); @@ -246,10 +231,11 @@ public function testNormalNestedExceptionRendering() public function testMissingViewExceptionDuringRendering() { - $Exception = new Exception('Hello World'); + $Exception = new CakeException('Hello World'); $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) @@ -284,11 +270,6 @@ public function testMissingViewExceptionDuringRendering() ], ]; - if (version_compare(Configure::version(), '4.2.0', '<')) { - $expected['exception']['class'] = 'Cake\Core\Exception\Exception'; - $expected['exception']['code'] = 500; - } - $actual = $viewVars['data']; unset($actual['trace'], $actual['file'], $actual['line']); $this->assertEquals($expected, $actual); @@ -311,13 +292,11 @@ public function testMissingViewExceptionDuringRendering() public function testGenericExceptionDuringRendering() { - $this->markTestSkipped(); - - $Exception = new Exception('Hello World'); - $NestedException = new Exception('Generic Exception Description'); + $Exception = new CakeException('Hello World'); $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) @@ -336,22 +315,23 @@ public function testGenericExceptionDuringRendering() $serialize = $Controller->viewBuilder()->getOption('serialize'); $this->assertNotEmpty($serialize); - $this->assertEquals(['success', 'data'], $serialize); + $this->assertEquals(['success', 'data', 'queryLog'], $serialize); $viewVars = $Controller->viewBuilder()->getVars(); + // dd($viewVars); $expected = [ 'code' => 500, 'url' => $Controller->getRequest()->getRequestTarget(), 'message' => 'Hello World', 'exception' => [ - 'class' => 'Cake\Core\Exception\Exception', - 'code' => 500, + 'class' => 'Cake\Core\Exception\CakeException', + 'code' => 0, 'message' => 'Hello World', ], ]; $actual = $viewVars['data']; - unset($actual['trace']); + unset($actual['trace'], $actual['file'], $actual['line']); $this->assertEquals($expected, $actual); $this->assertTrue(isset($viewVars['success'])); @@ -364,10 +344,10 @@ public function testGenericExceptionDuringRendering() $this->assertSame($Controller->getRequest()->getRequestTarget(), $viewVars['url']); $this->assertTrue(isset($viewVars['message'])); - $this->assertSame('Generic Exception Description', $viewVars['message']); + $this->assertSame('Hello World', $viewVars['message']); $this->assertTrue(isset($viewVars['error'])); - $this->assertSame($NestedException, $viewVars['error']); + $this->assertSame($Exception, $viewVars['error']); } public function testValidationErrorSingleKnownError() @@ -381,9 +361,8 @@ public function testValidationErrorSingleKnownError() $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); - $Controller->request = new ServerRequest(); - $Controller->response = new Response(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) ->onlyMethods(['_getController']) @@ -424,6 +403,7 @@ public function testValidationErrorSingleKnownErrorWithDebug() /** @var \Cake\Controller\Controller&\PHPUnit\Framework\MockObject\MockObject $Controller */ $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) @@ -474,6 +454,7 @@ public function testValidationErrorMultipleMessages() $Controller = $this->getMockBuilder(Controller::class) ->onlyMethods(['render']) + ->setConstructorArgs([new ServerRequest()]) ->getMock(); $Renderer = $this->getMockBuilder(ExceptionRenderer::class) diff --git a/tests/TestCase/Listener/ApiListenerTest.php b/tests/TestCase/Listener/ApiListenerTest.php index e85483ab5..d123b5fad 100644 --- a/tests/TestCase/Listener/ApiListenerTest.php +++ b/tests/TestCase/Listener/ApiListenerTest.php @@ -3,12 +3,13 @@ namespace Crud\Test\TestCase\Listener; -use Cake\Controller\Component\RequestHandlerComponent; use Cake\Controller\Controller; use Cake\Event\Event; use Cake\Http\Response; use Cake\Http\ServerRequest; use Cake\ORM\Entity; +use Cake\View\JsonView; +use Cake\View\XmlView; use Crud\Action\AddAction; use Crud\Action\BaseAction; use Crud\Action\DeleteAction; @@ -39,10 +40,10 @@ public function testImplementedEvents() ->disableOriginalConstructor() ->getMock(); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('setupDetectors'); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_checkRequestType') ->with('api') ->will($this->returnValue(true)); @@ -71,10 +72,10 @@ public function testImplementedEventsWithoutApi() ->disableOriginalConstructor() ->getMock(); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('setupDetectors'); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_checkRequestType') ->with('api') ->will($this->returnValue(false)); @@ -97,7 +98,7 @@ public function testBeforeHandle() ->disableOriginalConstructor() ->getMock(); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_checkRequestMethods'); $listener->beforeHandle(new Event('Crud.beforeHandle')); @@ -134,22 +135,22 @@ public function testResponse() ->onlyMethods(['_action', 'render']) ->getMock(); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_action') ->with() ->will($this->returnValue($action)); $action - ->expects($this->nextCounter($action)) + ->expects($this->once()) ->method('getConfig') ->with('api.success') ->will($this->returnValue(['code' => 200])); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('render') ->with($subject) ->will($this->returnValue($response)); $response - ->expects($this->nextCounter($response)) + ->expects($this->once()) ->method('withStatus') ->with(200); @@ -186,17 +187,17 @@ public function testResponseDeleteError() ->onlyMethods(['_action', 'render']) ->getMock(); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_action') ->with() ->will($this->returnValue($action)); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('render') ->with($subject) ->will($this->returnValue($response)); $response - ->expects($this->nextCounter($response)) + ->expects($this->once()) ->method('withStatus') ->with(400); @@ -235,17 +236,17 @@ public function testResponseWithStatusCodeNotSpecified() ->onlyMethods(['_action', 'render']) ->getMock(); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_action') ->with() ->will($this->returnValue($action)); $action - ->expects($this->nextCounter($action)) + ->expects($this->once()) ->method('getConfig') ->with('api.success') ->will($this->returnValue(null)); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('render') ->with($subject) ->will($this->returnValue($response)); @@ -253,7 +254,7 @@ public function testResponseWithStatusCodeNotSpecified() ->expects($this->never()) ->method('withStatus'); - $response = $listener->respond($event); + $listener->respond($event); } /** @@ -275,25 +276,23 @@ public function testResponseWithExceptionConfig() $event = new Event('Crud.afterSave', $subject); - $i = 0; - $listener = $this ->getMockBuilder(ApiListener::class) ->disableOriginalConstructor() ->onlyMethods(['_action', 'render', '_exceptionResponse']) ->getMock(); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_action') ->with() ->will($this->returnValue($action)); $action - ->expects($this->nextCounter($action)) + ->expects($this->once()) ->method('getConfig') ->with('api.success') ->will($this->returnValue(['exception' => ['SomethingExceptional']])); $listener - ->expects($this->nextCounter($listener)) + ->expects($this->once()) ->method('_exceptionResponse') ->with($event, ['SomethingExceptional']); $listener @@ -318,8 +317,8 @@ public function testDefaultConfiguration() $expected = [ 'viewClasses' => [ - 'json' => 'Json', - 'xml' => 'Xml', + 'json' => JsonView::class, + 'xml' => XmlView::class, ], 'detectors' => [ 'json' => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'], @@ -347,7 +346,7 @@ public function testDefaultConfiguration() * * @return array */ - public function dataExceptionResponse() + public static function dataExceptionResponse() { return [ 'default configuration' => [ @@ -358,8 +357,8 @@ public function dataExceptionResponse() ], 'change exception class' => [ - ['class' => '\Cake\Core\Exception\Exception'], - '\Cake\Core\Exception\Exception', + ['class' => '\Cake\Core\Exception\CakeException'], + '\Cake\Core\Exception\CakeException', 'Unknown error', 0, ], @@ -473,17 +472,16 @@ public function testEnsureSerializeWithViewVar() ->disableOriginalConstructor() ->getMock(); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $action - ->expects($this->at(0)) + ->expects($this->once()) ->method('viewVar') ->will($this->returnValue('items')); @@ -501,7 +499,7 @@ public function testEnsureSerializeWithViewVar() * * @return array */ - public function dataSerializeTraitActions() + public static function dataSerializeTraitActions() { return [ 'View Action' => ['\Crud\Action\ViewAction'], @@ -537,22 +535,20 @@ public function testEnsureSerializeWithSerializeTrait($action) ->disableOriginalConstructor() ->getMock(); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); - $i = 0; $action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('setConfig') ->with('serialize', ['something']); $action - ->expects($this->at($i++)) + ->expects($this->once()) ->method('viewVar') ->will($this->returnValue(null)); $action @@ -600,9 +596,8 @@ public function testEnsureSerializeAlreadySet() ->disableOriginalConstructor() ->getMock(); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener @@ -649,17 +644,16 @@ public function testEnsureSerializeWithViewVarChanged() ->disableOriginalConstructor() ->getMock(); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $action - ->expects($this->at(0)) + ->expects($this->once()) ->method('viewVar') ->will($this->returnValue('helloWorld')); @@ -697,13 +691,12 @@ public function testEnsureSerializeWithoutViewVar() ->disableOriginalConstructor() ->getMock(); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $controller @@ -747,9 +740,8 @@ public function testEnsureSuccess() ->disableOriginalConstructor() ->getMock(); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $controller @@ -790,17 +782,16 @@ public function testEnsureData() $config = []; - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $action - ->expects($this->at(0)) + ->expects($this->once()) ->method('getConfig') ->with('api.success') ->will($this->returnValue($config)); @@ -810,7 +801,7 @@ public function testEnsureData() ->with('data', []); $this->setReflectionClassInstance($listener); - $result = $this->callProtectedMethod('_ensureData', [$subject], $listener); + $this->callProtectedMethod('_ensureData', [$subject], $listener); } /** @@ -847,17 +838,16 @@ public function testEnsureDataSubject() ], ]]; - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $action - ->expects($this->at(0)) + ->expects($this->once()) ->method('getConfig') ->with('api.success') ->will($this->returnValue($config)); @@ -867,7 +857,7 @@ public function testEnsureDataSubject() ->with('data', ['modelClass' => 'MyModel', 'MyModel' => ['id' => 1]]); $this->setReflectionClassInstance($listener); - $result = $this->callProtectedMethod('_ensureData', [$subject], $listener); + $this->callProtectedMethod('_ensureData', [$subject], $listener); } /** @@ -899,17 +889,16 @@ public function testEnsureDataRaw() $config = ['data' => ['raw' => ['{modelClass}.id' => 1]]]; - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $action - ->expects($this->at(0)) + ->expects($this->once()) ->method('getConfig') ->with('api.success') ->will($this->returnValue($config)); @@ -919,7 +908,7 @@ public function testEnsureDataRaw() ->with('data', ['MyModel' => ['id' => 1]]); $this->setReflectionClassInstance($listener); - $result = $this->callProtectedMethod('_ensureData', [$subject], $listener); + $this->callProtectedMethod('_ensureData', [$subject], $listener); } /** @@ -951,17 +940,16 @@ public function testEnsureDataError() $config = []; - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $action - ->expects($this->at(0)) + ->expects($this->once()) ->method('getConfig') ->with('api.error') ->will($this->returnValue($config)); @@ -971,7 +959,7 @@ public function testEnsureDataError() ->with('data', []); $this->setReflectionClassInstance($listener); - $result = $this->callProtectedMethod('_ensureData', [$subject], $listener); + $this->callProtectedMethod('_ensureData', [$subject], $listener); } /** @@ -997,9 +985,8 @@ public function testEnsureSuccessAlreadySet() $controller->viewBuilder()->setVar('success', true); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_controller') ->will($this->returnValue($controller)); $controller @@ -1076,7 +1063,7 @@ public function testFlashMessageEnabled() * * @return array */ - public function dataExpandPath() + public static function dataExpandPath() { return [ 'simple string' => [ @@ -1119,61 +1106,11 @@ public function testExpandPath($subject, $path, $expected) } /** - * testSetupDetectors + * testSetupDetectorsIntegration * * @return void */ - public function testSetupDetectors() - { - $this->skipIf(true); - - $detectors = ['xml' => [], 'json' => []]; - - $listener = $this - ->getMockBuilder(ApiListener::class) - ->onlyMethods(['_request', 'config']) - ->disableOriginalConstructor() - ->getMock(); - - $request = $this - ->getMockBuilder(ServerRequest::class) - ->onlyMethods(['addDetector']) - ->disableOriginalConstructor() - ->getMock(); - - $i = 0; - $listener - ->expects($this->at($i++)) - ->method('_request') - ->will($this->returnValue($request)); - $listener - ->expects($this->at($i++)) - ->method('config') - ->with('detectors') - ->will($this->returnValue($detectors)); - - $r = 0; - foreach ($detectors as $name => $config) { - $request - ->expects($this->at($r++)) - ->method('addDetector') - ->with($name); - } - - $request - ->expects($this->at($r++)) - ->method('addDetector') - ->with('api'); - - $listener->setupDetectors(); - } - - /** - * testSetupDetectorsIntigration - * - * @return void - */ - public function testSetupDetectorsIntigration() + public function testSetupDetectorsIntegration() { $detectors = [ 'json' => ['accept' => ['application/json'], 'param' => '_ext', 'value' => 'json'], @@ -1192,19 +1129,15 @@ public function testSetupDetectorsIntigration() ->disableOriginalConstructor() ->getMock(); - $request = $this - ->getMockBuilder(ServerRequest::class) - ->onlyMethods(['_acceptHeaderDetector']) - ->disableOriginalConstructor() - ->getMock(); + $request = new ServerRequest(); + $request = $request->withAddedHeader('accept', 'application/vnd.api+json'); - $i = 0; $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('_request') ->will($this->returnValue($request)); $listener - ->expects($this->at($i++)) + ->expects($this->once()) ->method('getConfig') ->with('detectors') ->will($this->returnValue($detectors)); @@ -1223,47 +1156,18 @@ public function testSetupDetectorsIntigration() } } - $request = $request->withParam('_ext', null); - $request->clearDetectorCache(); - - // Test with "accepts" - $r = 0; - foreach ($detectors as $name => $configuration) { - $request - ->expects($this->at($r++)) - ->method('_acceptHeaderDetector') - ->with($configuration) - ->will($this->returnValue(true)); - } + $this->assertTrue($request->is('jsonapi')); - foreach ($detectors as $name => $config) { - $request->clearDetectorCache(); - $this->assertTrue($request->is($name)); - } + $request = $request->withParam('_ext', null)->withoutHeader('accept'); - $request = $request->withParam('_ext', 'xml'); $request->clearDetectorCache(); + $this->assertFalse($request->is('jsonapi')); - $this->assertTrue( - $request->is('api'), - 'A request with xml extensions should be considered an api request' - ); - - $request = $request->withParam('_ext', null); $request->clearDetectorCache(); - $this->assertFalse( $request->is('api'), 'A request with no extensions should not be considered an api request' ); - - //Ensure that no set extension will not result in a true - $request->clearDetectorCache(); - $request->expects($this->any()) - ->method('_acceptHeaderDetector') - ->will($this->returnValue(false)); - - $this->assertFalse($request->is('jsonapi'), 'A request with no extensions should not be considered an jsonapi request'); } /** @@ -1271,7 +1175,7 @@ public function testSetupDetectorsIntigration() * * @return array */ - public function dataCheckRequestMethods() + public static function dataCheckRequestMethods() { return [ 'defaults' => [ @@ -1324,29 +1228,31 @@ public function testCheckRequestMethods($apiConfig, $exception, $requestMethods) ->getMock(); $listener - ->expects($this->at(0)) + ->expects($this->once()) ->method('_action') ->will($this->returnValue($action)); $action - ->expects($this->at(0)) + ->expects($this->once()) ->method('getConfig') ->with('api') ->will($this->returnValue($apiConfig)); if (!empty($apiConfig['methods'])) { $listener - ->expects($this->at(1)) + ->expects($this->once()) ->method('_request') ->will($this->returnValue($request)); - $r = 0; + $withs = $returns = []; foreach ($requestMethods as $method => $bool) { - $request - ->expects($this->at($r++)) - ->method('is') - ->with($method) - ->will($this->returnValue($bool)); + $withs[] = [$method]; + $returns[] = $bool; } + $request + ->expects($this->exactly(count($withs))) + ->method('is') + ->with(...self::withConsecutive(...$withs)) + ->willReturnOnConsecutiveCalls(...$returns); } else { $listener ->expects($this->never()) @@ -1400,8 +1306,8 @@ public function testViewClassDefaults() $result = $apiListener->getConfig('viewClasses'); $expected = [ - 'json' => 'Json', - 'xml' => 'Xml', + 'json' => JsonView::class, + 'xml' => XmlView::class, ]; $this->assertEquals($expected, $result, 'The default viewClasses setting has changed'); } @@ -1415,22 +1321,13 @@ public function testInjectViewClasses() { $controller = $this ->getMockBuilder(Controller::class) - ->addMethods(['foobar']) + ->addMethods(['foobar', 'setViewClasses']) ->disableOriginalConstructor() ->getMock(); - - $controller->RequestHandler = $this->getMockBuilder(RequestHandlerComponent::class) - ->onlyMethods(['setConfig']) - ->disableOriginalConstructor() - ->getMock(); - $controller->RequestHandler - ->expects($this->at(0)) - ->method('setConfig') - ->with('viewClassMap', ['json' => 'Json']); - $controller->RequestHandler - ->expects($this->at(1)) - ->method('setConfig') - ->with('viewClassMap', ['xml' => 'Xml']); + $controller + ->expects($this->once()) + ->method('setViewClasses') + ->with(['json' => JsonView::class, 'xml' => XmlView::class]); $apiListener = $this->getMockBuilder(ApiListener::class) ->disableOriginalConstructor() diff --git a/tests/TestCase/Listener/ApiPaginationListenerTest.php b/tests/TestCase/Listener/ApiPaginationListenerTest.php index ca125e618..f8180c78d 100644 --- a/tests/TestCase/Listener/ApiPaginationListenerTest.php +++ b/tests/TestCase/Listener/ApiPaginationListenerTest.php @@ -3,7 +3,9 @@ namespace Crud\Test\TestCase\Listener; +use ArrayIterator; use Cake\Controller\Controller; +use Cake\Datasource\Paging\PaginatedResultSet; use Cake\Event\Event; use Cake\Http\ServerRequest; use Crud\Action\BaseAction; @@ -48,179 +50,97 @@ public function testImplementedEvents() */ public function testBeforeRenderNoPaginationData() { - $Request = $this - ->getMockBuilder(ServerRequest::class) - ->onlyMethods([]) - ->getMock(); - $Request = $Request->withAttribute('paging', ['MyModel' => []]); - $Controller = $this ->getMockBuilder(Controller::class) ->disableOriginalConstructor() ->onlyMethods([]) ->getMock(); - $Instance = $this - ->getMockBuilder(ApiPaginationListener::class) + $Action = $this + ->getMockBuilder(BaseAction::class) ->disableOriginalConstructor() - ->onlyMethods(['_request']) - ->getMock(); - $Instance - ->expects($this->once()) - ->method('_request') - ->will($this->returnValue($Request)); - - $Controller->modelClass = 'MyModel'; - - $Instance->beforeRender(new Event('something')); - } - - /** - * Test that API requests do not get processed - * if there if pagination data is NULL - * - * @return void - */ - public function testBeforeRenderPaginationDataIsNull() - { - $Request = $this - ->getMockBuilder(ServerRequest::class) ->onlyMethods([]) ->getMock(); $Instance = $this ->getMockBuilder(ApiPaginationListener::class) ->disableOriginalConstructor() - ->onlyMethods(['_request', '_controller']) + ->onlyMethods(['_request', '_action', '_controller']) ->getMock(); $Instance ->expects($this->once()) - ->method('_request') - ->will($this->returnValue($Request)); + ->method('_action') + ->will($this->returnValue($Action)); $Instance - ->expects($this->never()) - ->method('_controller'); - - $Request = $Request->withAttribute('paging', null); + ->expects($this->once()) + ->method('_controller') + ->will($this->returnValue($Controller)); $Instance->beforeRender(new Event('something')); + + $this->assertNull($Controller->viewBuilder()->getVar('pagination')); } /** - * Test that API requests do get processed - * if there is pagination data + * Test that API requests do not get processed + * if there if the view var is not a PaginatedInterface instance. * * @return void */ - public function testBeforeRenderWithPaginationData() + public function testBeforeRenderViewVarNotPaginatedInterface() { - $Request = $this - ->getMockBuilder(ServerRequest::class) - ->onlyMethods([]) - ->getMock(); - $Request = $Request->withAttribute('paging', [ - 'MyModel' => [ - 'pageCount' => 10, - 'page' => 2, - 'nextPage' => true, - 'prevPage' => true, - 'count' => 100, - 'limit' => 10, - ], - ]); - - $expected = [ - 'page_count' => 10, - 'current_page' => 2, - 'has_next_page' => true, - 'has_prev_page' => true, - 'count' => 100, - 'limit' => 10, - ]; - - $Controller = $this - ->getMockBuilder(Controller::class) - ->disableOriginalConstructor() - ->onlyMethods(['set']) - ->getMock(); - $Controller - ->expects($this->once()) - ->method('set') - ->with('pagination', $expected); + $Controller = new Controller(new ServerRequest(), 'MyModel'); + $Controller->set('data', []); $Action = $this ->getMockBuilder(BaseAction::class) ->disableOriginalConstructor() - ->onlyMethods(['setConfig']) + ->onlyMethods([]) ->getMock(); - $Action - ->expects($this->once()) - ->method('setConfig') - ->with('serialize.pagination', 'pagination'); $Instance = $this ->getMockBuilder(ApiPaginationListener::class) ->disableOriginalConstructor() - ->onlyMethods(['_request', '_controller', '_action']) + ->onlyMethods(['_request', '_action', '_controller']) ->getMock(); - $Instance - ->expects($this->once()) - ->method('_request') - ->will($this->returnValue($Request)); $Instance ->expects($this->once()) ->method('_controller') - ->will($this->returnValue($Controller)); + ->willReturn($Controller); $Instance ->expects($this->once()) ->method('_action') ->will($this->returnValue($Action)); - $Controller->modelClass = 'MyModel'; - $Instance->beforeRender(new Event('something')); + + $this->assertNull($Controller->viewBuilder()->getVar('pagination')); } /** - * Test with pagination data for plugin model + * Test that API requests do get processed + * if there is pagination data * * @return void */ - public function testBeforeRenderWithPaginationDataForPluginModel() + public function testBeforeRenderWithPaginationData() { - $Request = $this - ->getMockBuilder(ServerRequest::class) - ->onlyMethods([]) - ->getMock(); - $Request = $Request->withAttribute('paging', [ - 'MyModel' => [ - 'pageCount' => 10, - 'page' => 2, - 'nextPage' => true, - 'prevPage' => true, - 'count' => 100, - 'limit' => 10, - ], - ]); - - $expected = [ - 'page_count' => 10, - 'current_page' => 2, - 'has_next_page' => true, - 'has_prev_page' => true, - 'count' => 100, - 'limit' => 10, - ]; - - $Controller = $this - ->getMockBuilder(Controller::class) - ->disableOriginalConstructor() - ->onlyMethods(['set']) - ->getMock(); - $Controller - ->expects($this->once()) - ->method('set') - ->with('pagination', $expected); + $Request = new ServerRequest(); + $paginatedResultset = new PaginatedResultSet( + new ArrayIterator([]), + [ + 'pageCount' => 5, + 'currentPage' => 2, + 'hasNextPage' => true, + 'hasPrevPage' => true, + 'totalCount' => 50, + 'perPage' => 10, + 'count' => 10, + ] + ); + + $Controller = new Controller($Request, 'MyModel'); + $Controller->set('data', $paginatedResultset); $Action = $this ->getMockBuilder(BaseAction::class) @@ -238,92 +158,25 @@ public function testBeforeRenderWithPaginationDataForPluginModel() ->onlyMethods(['_request', '_controller', '_action']) ->getMock(); $Instance - ->expects($this->once()) - ->method('_request') - ->will($this->returnValue($Request)); - $Instance - ->expects($this->once()) + ->expects($this->any()) ->method('_controller') ->will($this->returnValue($Controller)); $Instance - ->expects($this->once()) + ->expects($this->any()) ->method('_action') ->will($this->returnValue($Action)); - $Controller->modelClass = 'MyPlugin.MyModel'; - $Instance->beforeRender(new Event('something')); - } - - /** - * Test if the pagination is set to be serialized in the beforeRender event - * - * @return void - */ - public function testBeforeRenderMakeSurePaginationDataIsSetToBeSerialized() - { - $Request = $this - ->getMockBuilder(ServerRequest::class) - ->onlyMethods([]) - ->getMock(); - $Request = $Request->withAttribute('paging', [ - 'MyModel' => [ - 'pageCount' => 10, - 'page' => 2, - 'nextPage' => true, - 'prevPage' => true, - 'count' => 100, - 'limit' => 10, - ], - ]); $expected = [ - 'page_count' => 10, + 'page_count' => 5, 'current_page' => 2, 'has_next_page' => true, 'has_prev_page' => true, - 'count' => 100, - 'limit' => 10, + 'total_count' => 50, + 'count' => 10, + 'per_page' => 10, ]; - - $Controller = $this - ->getMockBuilder(Controller::class) - ->disableOriginalConstructor() - ->onlyMethods(['set']) - ->getMock(); - $Controller - ->expects($this->once()) - ->method('set') - ->with('pagination', $expected); - - $Action = $this - ->getMockBuilder(BaseAction::class) - ->disableOriginalConstructor() - ->onlyMethods([]) - ->getMock(); - - $Instance = $this - ->getMockBuilder(ApiPaginationListener::class) - ->disableOriginalConstructor() - ->onlyMethods(['_request', '_controller', '_action']) - ->getMock(); - $Instance - ->expects($this->once()) - ->method('_request') - ->will($this->returnValue($Request)); - $Instance - ->expects($this->once()) - ->method('_controller') - ->will($this->returnValue($Controller)); - $Instance - ->expects($this->once()) - ->method('_action') - ->will($this->returnValue($Action)); - - $Controller->modelClass = 'MyModel'; - - $Instance->beforeRender(new Event('something')); - - $this->assertSame('pagination', $Action->getConfig('serialize.pagination')); + $this->assertEquals($expected, $Controller->viewBuilder()->getVar('pagination')); } } diff --git a/tests/TestCase/Listener/ApiQueryLogListenerTest.php b/tests/TestCase/Listener/ApiQueryLogListenerTest.php index 50395804f..eaf3716e9 100644 --- a/tests/TestCase/Listener/ApiQueryLogListenerTest.php +++ b/tests/TestCase/Listener/ApiQueryLogListenerTest.php @@ -6,7 +6,9 @@ use Cake\Controller\Controller; use Cake\Core\Configure; use Cake\Database\Connection; +use Cake\Database\Driver; use Cake\Event\Event; +use Cake\Http\ServerRequest; use Crud\Action\BaseAction; use Crud\Listener\ApiQueryLogListener; use Crud\Log\QueryLogger; @@ -23,6 +25,7 @@ class ApiQueryLogListenerTest extends TestCase public function setUp(): void { parent::setUp(); + $this->_debug = Configure::read('debug'); } @@ -164,24 +167,22 @@ public function testBeforeRenderDebugTrue() */ public function testSetupLogging() { - $methodName = 'enableQueryLogging'; - if (version_compare(Configure::version(), '3.7.0RC', '<')) { - $methodName = 'logQueries'; - } - - $DefaultSource = $this - ->getMockBuilder(Connection::class) - ->disableOriginalConstructor() - ->onlyMethods([$methodName, 'setLogger']) + $driver = $this + ->getMockBuilder(Driver::class) ->getMock(); - $DefaultSource - ->expects($this->once()) - ->method($methodName) - ->with(true); - $DefaultSource + $driver ->expects($this->once()) ->method('setLogger') ->with($this->isInstanceOf(QueryLogger::class)); + $driver + ->expects($this->any()) + ->method('enabled') + ->willReturn(true); + + $DefaultSource = new Connection([ + 'name' => 'default', + 'driver' => $driver, + ]); $Instance = $this ->getMockBuilder(ApiQueryLogListener::class) @@ -208,38 +209,29 @@ public function testSetupLogging() */ public function testSetupLoggingConfiguredSources() { - $methodName = 'enableQueryLogging'; - if (version_compare(Configure::version(), '3.7.0RC', '<')) { - $methodName = 'logQueries'; - } - - $DefaultSource = $this - ->getMockBuilder(Connection::class) + $driver = $this->getMockBuilder(Driver::class) ->disableOriginalConstructor() - ->onlyMethods([$methodName, 'setLogger']) ->getMock(); - $DefaultSource - ->expects($this->once()) - ->method($methodName) - ->with(true); - $DefaultSource - ->expects($this->once()) - ->method('setLogger') - ->with($this->isInstanceOf(QueryLogger::class)); - - $TestSource = $this - ->getMockBuilder(Connection::class) + $driver + ->expects($this->any()) + ->method('enabled') + ->willReturn(true); + $driver2 = $this->getMockBuilder(Driver::class) ->disableOriginalConstructor() - ->onlyMethods([$methodName, 'setLogger']) ->getMock(); - $TestSource - ->expects($this->once()) - ->method($methodName) - ->with(true); - $TestSource - ->expects($this->once()) - ->method('setLogger') - ->with($this->isInstanceOf(QueryLogger::class)); + $driver2 + ->expects($this->any()) + ->method('enabled') + ->willReturn(true); + + $DefaultSource = new Connection([ + 'name' => 'default', + 'driver' => $driver, + ]); + $TestSource = new Connection([ + 'name' => 'test', + 'driver' => $driver2, + ]); $Instance = $this ->getMockBuilder(ApiQueryLogListener::class) @@ -251,16 +243,10 @@ public function testSetupLoggingConfiguredSources() ->method('_getSources'); $Instance - ->expects($this->at(0)) - ->method('_getSource') - ->with('default') - ->will($this->returnValue($DefaultSource)); - - $Instance - ->expects($this->at(1)) + ->expects($this->exactly(2)) ->method('_getSource') - ->with('test') - ->will($this->returnValue($TestSource)); + ->with(...self::withConsecutive(['default'], ['test'])) + ->willReturnOnConsecutiveCalls($this->returnValue($DefaultSource), $this->returnValue($TestSource)); $Instance->setConfig('connections', ['default', 'test']); $Instance->setupLogging(new Event('something')); @@ -273,7 +259,7 @@ public function testSetupLoggingConfiguredSources() */ public function testProtectedGetQueryLogs() { - $listener = new ApiQueryLogListener(new Controller()); + $listener = new ApiQueryLogListener(new Controller(new ServerRequest())); $listener->setupLogging(new Event('something')); $this->setReflectionClassInstance($listener); @@ -291,7 +277,7 @@ public function testProtectedGetQueryLogs() */ public function testPublicGetQueryLogs() { - $listener = new ApiQueryLogListener(new Controller()); + $listener = new ApiQueryLogListener(new Controller(new ServerRequest())); $listener->setupLogging(new Event('something')); $expected = [ diff --git a/tests/TestCase/Listener/RedirectListenerTest.php b/tests/TestCase/Listener/RedirectListenerTest.php index 67d66f9f4..c38e21f7a 100644 --- a/tests/TestCase/Listener/RedirectListenerTest.php +++ b/tests/TestCase/Listener/RedirectListenerTest.php @@ -405,7 +405,7 @@ public function testRedirectWithConfigAndValidKey() * * @return array */ - public function dataProviderGetUrl() + public static function dataProviderGetUrl() { $Request = new ServerRequest(); $Request = $Request->withParam('action', 'index') diff --git a/tests/TestCase/Listener/RelatedModelsListenerTest.php b/tests/TestCase/Listener/RelatedModelsListenerTest.php index 7657186f9..89e3914e4 100644 --- a/tests/TestCase/Listener/RelatedModelsListenerTest.php +++ b/tests/TestCase/Listener/RelatedModelsListenerTest.php @@ -3,13 +3,12 @@ namespace Crud\Test\TestCase\Listener; -use Cake\Database\Connection; use Cake\Database\Schema\TableSchema; use Cake\Event\Event; use Cake\ORM\Association; use Cake\ORM\Association\BelongsTo; use Cake\ORM\AssociationCollection; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Crud\Event\Subject; use Crud\Listener\RelatedModelsListener; @@ -21,7 +20,7 @@ */ class RelatedModelsListenerTest extends TestCase { - protected $fixtures = ['core.NumberTrees']; + protected array $fixtures = ['core.NumberTrees']; /** * testModels @@ -124,7 +123,7 @@ public function testModelsTrue() ->method('getAssociatedByType') ->with(['oneToOne', 'manyToMany', 'manyToOne']); - $result = $listener->models(); + $listener->models(); } /** @@ -139,7 +138,7 @@ public function testGetAssociatedByTypeReturnValue() $listener = $this ->getMockBuilder(RelatedModelsListener::class) ->disableOriginalConstructor() - ->onlyMethods(['relatedModels', '_table']) + ->onlyMethods(['relatedModels', '_model']) ->getMock(); $table = $this ->getMockBuilder(Table::class) @@ -159,7 +158,7 @@ public function testGetAssociatedByTypeReturnValue() $listener ->expects($this->once()) - ->method('_table') + ->method('_model') ->withAnyParameters() ->will($this->returnValue($table)); $table @@ -202,7 +201,7 @@ public function testGetAssociatedByNameReturnValue() $listener = $this ->getMockBuilder(RelatedModelsListener::class) ->disableOriginalConstructor() - ->onlyMethods(['relatedModels', '_table']) + ->onlyMethods(['relatedModels', '_model']) ->getMock(); $table = $this ->getMockBuilder(Table::class) @@ -222,7 +221,7 @@ public function testGetAssociatedByNameReturnValue() $listener ->expects($this->once()) - ->method('_table') + ->method('_model') ->withAnyParameters() ->will($this->returnValue($table)); $table @@ -304,11 +303,7 @@ public function testbeforePaginate() ->method('models') ->will($this->returnValue(['Users' => 'manyToOne'])); - $db = $this->getMockBuilder(Connection::class) - ->disableOriginalConstructor() - ->getMock(); - - $query = new Query($db, $table); + $query = new SelectQuery($table); $subject = new Subject(['query' => $query]); $event = new Event('beforePaginate', $subject); diff --git a/tests/TestCase/Listener/SearchListenerTest.php b/tests/TestCase/Listener/SearchListenerTest.php index ced5ab674..b93cbc464 100644 --- a/tests/TestCase/Listener/SearchListenerTest.php +++ b/tests/TestCase/Listener/SearchListenerTest.php @@ -9,12 +9,12 @@ use Cake\Http\Response; use Cake\Http\ServerRequest; use Cake\ORM\BehaviorRegistry; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; use Crud\Event\Subject; use Crud\Listener\SearchListener; use Crud\TestSuite\TestCase; use Muffin\Webservice\Model\EndpointRegistry; -use Muffin\Webservice\Query; use RuntimeException; /** @@ -23,6 +23,13 @@ */ class SearchListenerTest extends TestCase { + public function setUp(): void + { + parent::setUp(); + + $this->skipIf(!class_exists('\Search\SearchPlugin'), 'Search plugin is not loaded'); + } + public function tearDown(): void { $this->removePlugins(['Search']); @@ -61,9 +68,9 @@ public function testInjectSearchException() $this->expectException(RuntimeException::class); $request = new ServerRequest(); - $response = new Response(); $eventManager = new EventManager(); - $controller = new Controller($request, $response, 'Search', $eventManager); + $controller = new Controller($request, 'Search', $eventManager); + $controller->loadComponent('Crud'); $behaviorRegistryMock = $this->getMockBuilder(BehaviorRegistry::class) ->setMockClassName('BehaviorRegistry') @@ -75,7 +82,6 @@ public function testInjectSearchException() $tableMock = $this->getMockBuilder(Table::class) ->setMockClassName('SearchesTable') ->onlyMethods(['behaviors']) - ->addMethods(['filterParams']) ->getMock(); $tableMock->expects($this->any()) ->method('behaviors') @@ -83,7 +89,7 @@ public function testInjectSearchException() $this->getTableLocator()->set('Search', $tableMock); - $queryMock = $this->getMockBuilder(\Cake\ORM\Query::class) + $queryMock = $this->getMockBuilder(SelectQuery::class) ->disableOriginalConstructor() ->getMock(); @@ -115,11 +121,13 @@ public function testInjectSearch() 'collection' => 'search', ]; - $request = (new ServerRequest())->withQueryParams($params['search']); + $request = (new ServerRequest()) + ->withParam('action', 'index') + ->withQueryParams($params['search']); - $response = new Response(); $eventManager = new EventManager(); - $controller = new Controller($request, $response, 'Search', $eventManager); + $controller = new Controller($request, 'Search', $eventManager); + $controller->loadComponent('Crud.Crud'); $behaviorRegistryMock = $this->getMockBuilder(BehaviorRegistry::class) ->setMockClassName('BehaviorRegistry') @@ -139,13 +147,17 @@ public function testInjectSearch() $this->getTableLocator()->set('Search', $tableMock); - $queryMock = $this->getMockBuilder(\Cake\ORM\Query::class) - ->disableOriginalConstructor() - ->getMock(); - $queryMock->expects($this->once()) - ->method('find') - ->with('search', $params) - ->will($this->returnValue($queryMock)); + $queryMock = new class { + public TestCase $testCase; + + public function find($query, $search, $collection) + { + $this->testCase->assertSame(['name' => '1st post'], $search); + $this->testCase->assertSame('search', $collection); + } + }; + + $queryMock->testCase = $this; $subject = new Subject(); $subject->query = $queryMock; @@ -191,7 +203,7 @@ public function testInjectSearchWebserviceEndpoint() $controller->modelFactory('Endpoint', ['Muffin\Webservice\Model\EndpointRegistry', 'get']); $controller->setModelType('Endpoint'); - $queryMock = $this->getMockBuilder(Query::class) + $queryMock = $this->getMockBuilder(SelectQuery::class) ->disableOriginalConstructor() ->getMock(); $queryMock->expects($this->once()) diff --git a/tests/TestCase/ListenersTest.php b/tests/TestCase/ListenersTest.php deleted file mode 100644 index 7a1473893..000000000 --- a/tests/TestCase/ListenersTest.php +++ /dev/null @@ -1,23 +0,0 @@ -addTestDirectoryRecursive($testPath . DS . 'Listener'); - - return $suite; - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index d53343c50..46a2e7c21 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,10 +1,12 @@ 'Crud\Test\App', 'encoding' => 'UTF-8', - 'fullBaseUrl' => 'http://localhost' + 'fullBaseUrl' => 'http://localhost', ]); -Cake\Core\Configure::write('debug', true); +Configure::write('debug', true); $cache = [ 'default' => [ - 'engine' => 'File' + 'engine' => 'File', ], '_cake_core_' => [ 'className' => 'File', 'prefix' => 'crud_myapp_cake_core_', 'path' => CACHE . 'persistent/', 'serialize' => true, - 'duration' => '+10 seconds' + 'duration' => '+10 seconds', ], '_cake_model_' => [ 'className' => 'File', 'prefix' => 'crud_my_app_cake_model_', 'path' => CACHE . 'models/', 'serialize' => 'File', - 'duration' => '+10 seconds' - ] + 'duration' => '+10 seconds', + ], ]; -Cake\Cache\Cache::setConfig($cache); -Cake\Core\Configure::write('Session', [ - 'defaults' => 'php' +Cache::setConfig($cache); +Configure::write('Session', [ + 'defaults' => 'php', ]); // Ensure default test connection is defined @@ -80,14 +82,15 @@ putenv('DB_URL=sqlite:///:memory:'); } -Cake\Datasource\ConnectionManager::setConfig('test', [ +ConnectionManager::setConfig('test', [ 'url' => getenv('DB_URL'), - 'timezone' => 'UTC' + 'timezone' => 'UTC', ]); -Plugin::getCollection()->add(new \Crud\Plugin()); +Plugin::getCollection()->add(new CrudPlugin()); -Configure::write( - 'Error.ignoredDeprecationPaths', - ['vendor/cakephp/cakephp/src/TestSuite/Fixture/FixtureInjector.php'] -); +// Create test database schema +if (getenv('FIXTURE_SCHEMA_METADATA')) { + $loader = new SchemaLoader(); + $loader->loadInternalFile(getenv('FIXTURE_SCHEMA_METADATA')); +} diff --git a/tests/schema.php b/tests/schema.php new file mode 100644 index 000000000..83184decd --- /dev/null +++ b/tests/schema.php @@ -0,0 +1,91 @@ + 'blogs', + 'columns' => [ + 'id' => 'integer', + 'name' => ['type' => 'string', 'length' => 255], + 'body' => 'text', + 'is_active' => ['type' => 'boolean', 'default' => true, 'null' => false], + ], + 'constraints' => [ + 'primary' => [ + 'type' => 'primary', + 'columns' => [ + 'id', + ], + ], + ], + ], + [ + 'table' => 'users', + 'columns' => [ + 'id' => ['type' => 'uuid'], + 'is_active' => ['type' => 'boolean', 'default' => true, 'null' => false], + 'username' => ['type' => 'string', 'length' => 255, 'null' => false], + ] , + 'constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], + ], + [ + 'table' => 'number_trees', + 'columns' => [ + 'id' => [ + 'type' => 'integer', + ], + 'name' => [ + 'type' => 'string', + 'null' => false, + ], + 'parent_id' => 'integer', + 'lft' => [ + 'type' => 'integer', + ], + 'rght' => [ + 'type' => 'integer', + ], + 'depth' => [ + 'type' => 'integer', + ], + ], + 'constraints' => [ + 'primary' => [ + 'type' => 'primary', + 'columns' => [ + 'id', + ], + ], + ], + ], + [ + 'table' => 'posts', + 'columns' => [ + 'id' => [ + 'type' => 'integer', + ], + 'author_id' => [ + 'type' => 'integer', + 'null' => false, + ], + 'title' => [ + 'type' => 'string', + 'null' => false, + ], + 'body' => 'text', + 'published' => [ + 'type' => 'string', + 'length' => 1, + 'default' => 'N', + ], + ], + 'constraints' => [ + 'primary' => [ + 'type' => 'primary', + 'columns' => [ + 'id', + ], + ], + ], + ], +]; diff --git a/tests/test_app/src/Controller/BlogsController.php b/tests/test_app/src/Controller/BlogsController.php index 0f5a9cd2c..7882e0b48 100644 --- a/tests/test_app/src/Controller/BlogsController.php +++ b/tests/test_app/src/Controller/BlogsController.php @@ -10,11 +10,10 @@ class BlogsController extends Controller { use ControllerTrait; - public $paginate = ['limit' => 3]; + protected array $paginate = ['limit' => 3]; public function initialize(): void { - $this->loadComponent('RequestHandler'); $this->loadComponent('Flash'); $this->loadComponent('Crud.Crud', [ 'actions' => [ diff --git a/tests/test_app/src/Controller/CrudExamplesController.php b/tests/test_app/src/Controller/CrudExamplesController.php index 9b56dc91f..4a7ffdc72 100644 --- a/tests/test_app/src/Controller/CrudExamplesController.php +++ b/tests/test_app/src/Controller/CrudExamplesController.php @@ -10,7 +10,7 @@ class CrudExamplesController extends Controller { use ControllerTrait; - public $modelClass = 'CrudExamples'; + protected ?string $modelClass = 'CrudExamples'; public static $componentsArray = [ 'Crud.Crud' => [ @@ -24,7 +24,7 @@ class CrudExamplesController extends Controller ], ]; - public $paginate = [ + public array $paginate = [ 'limit' => 1000, ]; diff --git a/tests/test_app/src/Controller/UsersController.php b/tests/test_app/src/Controller/UsersController.php index 632e42e02..3c7b011a7 100644 --- a/tests/test_app/src/Controller/UsersController.php +++ b/tests/test_app/src/Controller/UsersController.php @@ -10,11 +10,10 @@ class UsersController extends Controller { use ControllerTrait; - public $paginate = ['limit' => 3]; + public array $paginate = ['limit' => 3]; public function initialize(): void { - $this->loadComponent('RequestHandler'); $this->loadComponent('Flash'); $this->loadComponent('Crud.Crud', [ 'actions' => [ diff --git a/tests/test_app/src/Model/Table/BlogsTable.php b/tests/test_app/src/Model/Table/BlogsTable.php index 5dbf9a229..14132af5f 100644 --- a/tests/test_app/src/Model/Table/BlogsTable.php +++ b/tests/test_app/src/Model/Table/BlogsTable.php @@ -3,21 +3,21 @@ namespace Crud\Test\App\Model\Table; -use Cake\ORM\Query; +use Cake\ORM\Query\SelectQuery; use Cake\ORM\Table; class BlogsTable extends Table { - public $customOptions; + public array $customOptions; /** * findWithCustomOptions * - * @param Query $query - * @param array $options - * @return void + * @param \Cake\ORM\Query\SelectQuery $query + * @param mixed ...$options + * @return \Cake\ORM\Query\SelectQuery */ - public function findWithCustomOptions(Query $query, array $options) + public function findWithCustomOptions(SelectQuery $query, mixed ...$options) { $this->customOptions = $options; diff --git a/tests/test_app/src/Model/Table/CrudExamplesTable.php b/tests/test_app/src/Model/Table/CrudExamplesTable.php index 5b45f5e0d..8f721cfff 100644 --- a/tests/test_app/src/Model/Table/CrudExamplesTable.php +++ b/tests/test_app/src/Model/Table/CrudExamplesTable.php @@ -14,7 +14,7 @@ */ class CrudExamplesTable extends Table { - protected $_alias = 'CrudExamples'; + protected ?string $_alias = 'CrudExamples'; /** * [initialize description]