Skip to content

Add support for CakePHP 5 #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3']
php-versions: ['8.1', '8.2', '8.3']
db-type: [mysql]
prefer-lowest: ['', 'prefer-lowest']

Expand All @@ -31,7 +31,9 @@ jobs:

- name: Setup MySQL 8
if: matrix.db-type == 'mysql'
run: docker run --rm --name=mysqld -e MYSQL_ROOT_PASSWORD=root -e MYSQL_DATABASE=cakephp -p 3306:3306 -d mysql:8 --default-authentication-plugin=mysql_native_password --disable-log-bin
run: |
sudo service mysql start
mysql -h 127.0.0.1 -u root -proot -e 'CREATE DATABASE pagination;'

- name: Validate composer.json and composer.lock
run: composer validate --strict
Expand Down Expand Up @@ -66,15 +68,11 @@ jobs:
composer update
fi

- name: Wait for MySQL
if: matrix.db-type == 'mysql'
run: while ! `mysqladmin ping -h 127.0.0.1 --silent`; do printf 'Waiting for MySQL...\n'; sleep 2; done;

- name: Run PHPUnit testsuite
run: |
if [[ ${{ matrix.db-type }} == 'mysql' ]]; then
export DB_URL='mysql://root:[email protected]/cakephp';
mysql -h 127.0.0.1 -u root -proot cakephp < ./tests/Schema/articles.sql
export DB_URL='mysql://root:[email protected]/pagination';
mysql -h 127.0.0.1 -u root -proot pagination < ./tests/Schema/articles.sql
fi
vendor/bin/phpunit --stderr;

Expand All @@ -87,7 +85,7 @@ jobs:

strategy:
matrix:
php-versions: ['7.4']
php-versions: ['8.1']

steps:
- name: Checkout
Expand Down
12 changes: 6 additions & 6 deletions composer.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "bcrowe/cakephp-api-pagination",
"description": "CakePHP 4 plugin that injects pagination information into API responses.",
"description": "CakePHP 5 plugin that injects pagination information into API responses.",
"type": "cakephp-plugin",
"keywords": [
"cakephp", "api", "pagination", "cakephp3", "cakephp4"
"cakephp", "api", "pagination", "cakephp3", "cakephp4", "cakephp5"
],
"homepage": "https://github.com/bcrowe/cakephp-api-pagination",
"license": "MIT",
Expand All @@ -16,13 +16,13 @@
}
],
"require": {
"php": ">=7.2",
"cakephp/cakephp": "^4.2"
"php": ">=8.1",
"cakephp/cakephp": "^5.0"
},
"require-dev": {
"phpunit/phpunit" : "^8.5.23",
"phpunit/phpunit" : "^10.5.5",
"scrutinizer/ocular": "1.7",
"cakephp/cakephp-codesniffer": "^4.7"
"cakephp/cakephp-codesniffer": "^5.0"
},
"autoload": {
"psr-4": {
Expand Down
32 changes: 7 additions & 25 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
@@ -1,37 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="./tests/bootstrap.php"
backupGlobals="false"
backupStaticAttributes="false"
colors="true"
verbose="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
displayDetailsOnTestsThatTriggerDeprecations="true"
displayDetailsOnTestsThatTriggerErrors="true"
displayDetailsOnTestsThatTriggerNotices="true"
displayDetailsOnTestsThatTriggerWarnings="true"
processIsolation="false"
stopOnFailure="false">
<testsuites>
<testsuite name="ApiPagination Test Suite">
<directory>tests</directory>
</testsuite>
</testsuites>
<listeners>
<listener
class="\Cake\TestSuite\Fixture\FixtureInjector">
<arguments>
<object class="\Cake\TestSuite\Fixture\FixtureManager" />
</arguments>
</listener>
</listeners>
<filter>
<whitelist>
<directory suffix=".php">src/</directory>
</whitelist>
</filter>
<logging>
<log type="tap" target="build/report.tap"/>
<log type="junit" target="build/report.junit.xml"/>
<log type="coverage-html" target="build/coverage"/>
<log type="coverage-text" target="build/coverage.txt"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
<extensions>
<bootstrap class="Cake\TestSuite\Fixture\Extension\PHPUnitExtension"/>
</extensions>
</phpunit>
50 changes: 36 additions & 14 deletions src/Controller/Component/ApiPaginationComponent.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
namespace BryanCrowe\ApiPagination\Controller\Component;

use Cake\Controller\Component;
use Cake\Datasource\Paging\PaginatedInterface;
use Cake\Event\Event;

/**
Expand All @@ -18,18 +19,25 @@ class ApiPaginationComponent extends Component
*
* @var array
*/
protected $_defaultConfig = [
protected array $_defaultConfig = [
'key' => 'pagination',
'aliases' => [],
'visible' => [],
];

/**
* Paging params of paginated result set (if any).
*
* @var array
*/
protected array $pagingParams = [];

/**
* Holds the paging information array from the request.
*
* @var array
*/
protected $pagingInfo = [];
protected array $pagingInfo = [];

/**
* Injects the pagination info into the response if the current request is a
Expand All @@ -38,16 +46,16 @@ class ApiPaginationComponent extends Component
* @param \Cake\Event\Event $event The Controller.beforeRender event.
* @return void
*/
public function beforeRender(Event $event)
public function beforeRender(Event $event): void
{
if (!$this->isPaginatedApiRequest()) {
return;
}

$subject = $event->getSubject();
$modelName = ucfirst($this->getConfig('model', $subject->getName()));
if (isset($this->getController()->getRequest()->getAttribute('paging')[$modelName])) {
$this->pagingInfo = $this->getController()->getRequest()->getAttribute('paging')[$modelName];
if (isset($this->pagingParams[$modelName])) {
$this->pagingInfo = $this->pagingParams[$modelName];
}

$config = $this->getConfig();
Expand Down Expand Up @@ -75,7 +83,7 @@ public function beforeRender(Event $event)
*
* @return void
*/
protected function setAliases()
protected function setAliases(): void
{
foreach ($this->getConfig('aliases') as $key => $value) {
$this->pagingInfo[$value] = $this->pagingInfo[$key];
Expand All @@ -89,7 +97,7 @@ protected function setAliases()
*
* @return void
*/
protected function setVisibility()
protected function setVisibility(): void
{
$visible = $this->getConfig('visible');
foreach ($this->pagingInfo as $key => $value) {
Expand All @@ -105,15 +113,29 @@ protected function setVisibility()
*
* @return bool True if JSON or XML with paging, otherwise false.
*/
protected function isPaginatedApiRequest()
protected function isPaginatedApiRequest(): bool
{
if (
$this->getController()->getRequest()->getAttribute('paging')
&& $this->getController()->getRequest()->is(['json', 'xml'])
) {
return true;
if (!$this->getController()->getRequest()->is(['json', 'xml'])) {
return false;
}

// Cake 4 way for the people who want to keep embracing paging attribute pattern
if ($this->getController()->getRequest()->getAttribute('paging')) {
$this->pagingParams = $this->getController()->getRequest()->getAttribute('paging');

return !empty($this->pagingParams);
}

// Since cake 5, paging params are no longer part of the request attribute.
// Hence, we check for all the view vars and if paginated interface found then we pick the first one and use it.
// @see https://github.com/cakephp/cakephp/pull/16317#issuecomment-1045873277
foreach ($this->getController()->viewBuilder()->getVars() as $value) {
if ($value instanceof PaginatedInterface) {
$this->pagingParams[$value->pagingParam('alias')] = $value->pagingParams();
break;
}
}

return false;
return !empty($this->pagingParams);
}
}
4 changes: 2 additions & 2 deletions tests/Fixture/ArticlesFixture.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

class ArticlesFixture extends TestFixture
{
public $table = 'bryancrowe_articles';
public string $table = 'bryancrowe_articles';

public $records = [
public array $records = [
['title' => 'Post #1', 'body' => 'This is the article body.'],
['title' => 'Post #2', 'body' => 'This is the article body.'],
['title' => 'Post #3', 'body' => 'This is the article body.'],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<?php
declare(strict_types=1);

namespace BryanCrowe\ApiPagination\Test;
namespace BryanCrowe\ApiPagination\Test\TestCase\Controller\Component;

use BryanCrowe\ApiPagination\Controller\Component\ApiPaginationComponent;
use BryanCrowe\ApiPagination\TestApp\Controller\ArticlesIndexController;
use Cake\Controller\Controller;
use Cake\Event\Event;
use Cake\Http\Response;
use Cake\Http\ServerRequest as Request;
use Cake\ORM\Table;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\TestCase;

Expand All @@ -17,7 +20,15 @@
*/
class ApiPaginationComponentOnNonConventionalControllerNameTest extends TestCase
{
public $fixtures = ['plugin.BryanCrowe/ApiPagination.Articles'];
public array $fixtures = ['plugin.BryanCrowe/ApiPagination.Articles'];

protected ?Request $request = null;

protected ?Response $response = null;

protected ?Controller $controller = null;

protected ?Table $Articles = null;

/**
* setUp method
Expand All @@ -28,7 +39,8 @@ public function setUp(): void
{
$this->request = new Request(['url' => '/articles']);
$this->response = $this->createMock('Cake\Http\Response');
$this->controller = new ArticlesIndexController($this->request, $this->response);
$this->controller = new ArticlesIndexController($this->request);
$this->controller = $this->controller->setResponse($this->response);
$this->Articles = TableRegistry::getTableLocator()->get('BryanCrowe/ApiPagination.Articles', ['table' => 'bryancrowe_articles']);
parent::setUp();
}
Expand Down Expand Up @@ -71,12 +83,12 @@ public function testVariousModelValueOnNonConventionalController(array $config,
*
* @return array[]
*/
public function dataForTestVariousModelValueOnNonConventionalController(): array
public static function dataForTestVariousModelValueOnNonConventionalController(): array
{
return [
[[], []],
[['model' => 'Articles'], $this->getDefaultPagination()],
[['model' => 'articles'], $this->getDefaultPagination()],
[['model' => 'Articles'], self::getDefaultPagination()],
[['model' => 'articles'], self::getDefaultPagination()],
[['model' => 'NonExistingModel'], []],
];
}
Expand All @@ -86,27 +98,27 @@ public function dataForTestVariousModelValueOnNonConventionalController(): array
*
* @return array
*/
private function getDefaultPagination(): array
private static function getDefaultPagination(): array
{
return [
'count' => 23,
'current' => 20,
'perPage' => 20,
'page' => 1,
'requestedPage' => 1,
'pageCount' => 2,
'start' => 1,
'end' => 20,
'prevPage' => false,
'nextPage' => true,
'sort' => null,
'direction' => null,
'sortDefault' => false,
'directionDefault' => false,
'completeSort' => [],
'limit' => null,
'perPage' => 20,
'requestedPage' => 1,
'alias' => 'Articles',
'scope' => null,
'finder' => 'all',
'limit' => null,
'count' => 20,
'totalCount' => 23,
'pageCount' => 2,
'currentPage' => 1,
'start' => 1,
'end' => 20,
'hasPrevPage' => false,
'hasNextPage' => true,
];
}
}
Loading