diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..677e36e2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..004297ab --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,13 @@ +daysUntilStale: 180 +daysUntilClose: 28 +exemptLabels: + - keep-open +# Label to use when marking an issue as stale +staleLabel: stale +# Comment to post when marking an issue as stale. Set to `false` to disable +markComment: > + This issue has been automatically marked as stale because it has not had + recent activity. It will be closed after 4 weeks if no further activity occurs. Thank you + for your contributions. +# Comment to post when closing a stale issue. Set to `false` to disable +closeComment: false diff --git a/.github/workflows/.editorconfig b/.github/workflows/.editorconfig new file mode 100644 index 00000000..7bd3346f --- /dev/null +++ b/.github/workflows/.editorconfig @@ -0,0 +1,2 @@ +[*.yml] +indent_size = 2 diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000..f01fdbe3 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,31 @@ +name: The PHP League Tests + +on: [push, pull_request] + +jobs: + ci: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-20.04] + php: [7.4, 8.0, 8.1] + + name: League - PHP ${{ matrix.php }} on ${{ matrix.os }} + + steps: + + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + tools: phpcs, phpunit + + - name: Download dependencies + uses: ramsey/composer-install@v2 + + - name: Run Tests + run: vendor/bin/phpunit diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml new file mode 100644 index 00000000..328a6c8f --- /dev/null +++ b/.github/workflows/static.yml @@ -0,0 +1,46 @@ +on: [ pull_request ] +name: Static analysis + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-20.04 + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: apcu, redis + coverage: none + tools: phpstan:1.4.6, cs2pr + + - name: Download dependencies + uses: ramsey/composer-install@v1 + + - name: PHPStan + run: phpstan analyze --no-progress --error-format=checkstyle | cs2pr + + psalm: + name: Psalm + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + extensions: apcu, redis + coverage: none + tools: vimeo/psalm:4.22.0 + + - name: Download dependencies + uses: ramsey/composer-install@v1 + + - name: Psalm + run: psalm --no-progress --output-format=github diff --git a/.github/workflows/style-checker.yml b/.github/workflows/style-checker.yml new file mode 100644 index 00000000..68b72297 --- /dev/null +++ b/.github/workflows/style-checker.yml @@ -0,0 +1,32 @@ +name: The PHP League Style Checks + +on: [push, pull_request] + +jobs: + style: + runs-on: ubuntu-latest + name: The PHP League Style Checks + steps: + + - name: Checkout Code + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + + - name: Cache Dependencies + id: composer-cache-style + uses: actions/cache@v2 + with: + path: vendor + key: ubuntu-composer-cache-style-${{ hashFiles('**/composer.lock') }} + restore-keys: ubuntu-php-style + + - name: Install Dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Check Coding Style + run: vendor/bin/phpcs src/ \ No newline at end of file diff --git a/.gitignore b/.gitignore index 458385ec..19906bc4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,4 @@ composer.lock build vendor -Gemfile.lock -coverage +.phpunit.result.cache \ No newline at end of file diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 05e77e14..00000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,22 +0,0 @@ -filter: - excluded_paths: [tests, vendor] - -checks: - php: - remove_extra_empty_lines: true - remove_php_closing_tag: true - remove_trailing_whitespace: true - fix_use_statements: - remove_unused: true - preserve_multiple: false - preserve_blanklines: true - order_alphabetically: true - fix_php_opening_tag: true - fix_linefeed: true - fix_line_ending: true - fix_identation_4spaces: true - fix_doc_comments: true - -tools: - external_code_coverage: - timeout: 1800 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 137c4e40..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: php - -sudo: false - -matrix: - include: - - php: 7.2 - env: COLLECT_COVERAGE=true - - php: 7.3 - - php: hhvm - dist: trusty - - allow_failures: - - php: hhvm - -install: - - travis_retry composer install --no-interaction --prefer-source - -script: - - if [[ "$COLLECT_COVERAGE" == "true" ]]; then vendor/bin/phpunit --coverage-text --coverage-clover=coverage.clover; else vendor/bin/phpunit --no-coverage; fi - -after_script: - - if [[ "$COLLECT_COVERAGE" == "true" ]]; then wget https://scrutinizer-ci.com/ocular.phar && php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b7b1171..83b2e97f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## Next + +### Enhancements +- Add links for all available includes to `JsonApiSerializer` #331 - Thanks @matt-allan +- Implement interface `\JsonSerializable` in Scopes to allow for direct usage with `json_encode()` + + + ## 0.18.0 - 2019-05-09 ### Enhancements diff --git a/README.md b/README.md index 4c6aaf98..a69cea2b 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,7 @@ [![Latest Version](https://img.shields.io/github/release/thephpleague/fractal.svg?style=flat-square)](https://github.com/thephpleague/fractal/releases) [![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) -[![Build Status](https://img.shields.io/travis/thephpleague/fractal/1.0.x.svg?style=flat-square&label=tests)](https://travis-ci.org/thephpleague/fractal) -[![Build Status](https://img.shields.io/circleci/build/gh/thephpleague/fractal/1.0.x.svg?style=flat-square&label=code+style)](https://circleci.com/gh/thephpleague/fractal) -[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/fractal/1.0.x.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/fractal/code-structure) -[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/fractal/1.0.x.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/fractal) +![The PHP League Tests](https://github.com/thephpleague/fractal/workflows/The%20PHP%20League%20Tests/badge.svg) [![Total Downloads](https://img.shields.io/packagist/dt/league/fractal.svg?style=flat-square)](https://packagist.org/packages/league/fractal) Fractal provides a presentation and transformation layer for complex data output, the like found in @@ -42,15 +39,9 @@ $ composer require league/fractal ## Requirements -The following versions of PHP are supported by this version. +The following versions of PHP are supported by this version: -* PHP 5.4 -* PHP 5.5 -* PHP 5.6 -* PHP 7.0 -* PHP 7.1 -* PHP 7.2 -* HHVM +>= PHP 7.4 ## Documentation @@ -72,6 +63,12 @@ $ phpunit Please see [CONTRIBUTING](https://github.com/thephpleague/fractal/blob/master/CONTRIBUTING.md) and [CONDUCT](https://github.com/thephpleague/fractal/blob/master/CONDUCT.md) for details. + +## Maintainers + +- [Korvin Szanto](https://github.com/korvinszanto) +- [Matt Trask](https://github.com/matthewtrask) + ## Credits - [Graham Daniels](https://github.com/greydnls) diff --git a/composer.json b/composer.json index e5d7eb21..83beee6e 100644 --- a/composer.json +++ b/composer.json @@ -21,13 +21,14 @@ "sort-packages": true }, "require": { - "php": ">=7.2" + "php": ">=7.4" }, "require-dev": { - "mockery/mockery": "~1.0", - "phpstan/phpstan": "^0.11.8", - "phpunit/phpunit": "~8.0", - "squizlabs/php_codesniffer": "~3.4" + "mockery/mockery": ">=1.5", + "phpstan/phpstan": ">=1.5", + "phpunit/phpunit": ">=8|>=9", + "squizlabs/php_codesniffer": "~3.6", + "vimeo/psalm": "^4.22" }, "suggest": { "league/fractal-serializer-jsonapi": "JSON:API Serialization Support", @@ -47,9 +48,14 @@ "League\\Fractal\\Test\\": "test" } }, + "scripts": { + "check": "vendor/bin/phpcs src/", + "test": "vendor/bin/phpunit --testdox --colors=always", + "test:coverage": "vendor/bin/phpunit --coverage-html build/coverage" + }, "extra": { "branch-alias": { - "dev-master": "0.13-dev" + "dev-master": "0.20.x-dev" } } } diff --git a/phpcs.xml.dist b/phpcs.xml.dist index 12539b16..8419ff9c 100644 --- a/phpcs.xml.dist +++ b/phpcs.xml.dist @@ -10,11 +10,4 @@ - - - - - - - diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 00000000..eea23caa --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,6 @@ +parameters: + ignoreErrors: + - + message: "#^Parameter \\#1 \\$separator of function explode expects non\\-empty\\-string, string given\\.$#" + count: 1 + path: src/Manager.php \ No newline at end of file diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 00000000..1b347546 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,8 @@ +includes: + - ./phpstan-baseline.neon + +parameters: + level: 5 + reportUnmatchedIgnoredErrors: false + paths: + - src diff --git a/psalm.baseline.xml b/psalm.baseline.xml new file mode 100644 index 00000000..c9e27b07 --- /dev/null +++ b/psalm.baseline.xml @@ -0,0 +1,11 @@ + + + + + getMeta + + + transform + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000..e044bd6b --- /dev/null +++ b/psalm.xml @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/src/Manager.php b/src/Manager.php index e6cdf891..3366a78a 100644 --- a/src/Manager.php +++ b/src/Manager.php @@ -13,7 +13,7 @@ use League\Fractal\Resource\ResourceInterface; use League\Fractal\Serializer\DataArraySerializer; -use League\Fractal\Serializer\SerializerAbstract; +use League\Fractal\Serializer\Serializer; /** * Manager @@ -26,59 +26,40 @@ class Manager { /** * Array of scope identifiers for resources to include. - * - * @var array */ - protected $requestedIncludes = []; + protected array $requestedIncludes = []; /** * Array of scope identifiers for resources to exclude. - * - * @var array */ - protected $requestedExcludes = []; + protected array $requestedExcludes = []; /** * Array of requested fieldsets. - * - * @var array */ - protected $requestedFieldsets = []; + protected array $requestedFieldsets = []; /** * Array containing modifiers as keys and an array value of params. - * - * @var array */ - protected $includeParams = []; + protected array $includeParams = []; /** * The character used to separate modifier parameters. - * - * @var string */ - protected $paramDelimiter = '|'; + protected string $paramDelimiter = '|'; /** * Upper limit to how many levels of included data are allowed. - * - * @var int */ - protected $recursionLimit = 10; + protected int $recursionLimit = 10; - /** - * Serializer. - * - * @var SerializerAbstract - */ - protected $serializer; + protected ?Serializer $serializer = null; /** * Factory used to create new configured scopes. - * - * @var ScopeFactoryInterface */ - private $scopeFactory; + private ScopeFactoryInterface $scopeFactory; public function __construct(ScopeFactoryInterface $scopeFactory = null) { @@ -86,18 +67,13 @@ public function __construct(ScopeFactoryInterface $scopeFactory = null) } /** - * Create Data. - * * Main method to kick this all off. Make a resource then pass it over, and use toArray() - * - * @param ResourceInterface $resource - * @param string $scopeIdentifier - * @param Scope $parentScopeInstance - * - * @return Scope */ - public function createData(ResourceInterface $resource, $scopeIdentifier = null, Scope $parentScopeInstance = null) - { + public function createData( + ResourceInterface $resource, + ?string $scopeIdentifier = null, + Scope $parentScopeInstance = null + ): Scope { if ($parentScopeInstance !== null) { return $this->scopeFactory->createChildScopeFor($this, $parentScopeInstance, $resource, $scopeIdentifier); } @@ -105,65 +81,40 @@ public function createData(ResourceInterface $resource, $scopeIdentifier = null, return $this->scopeFactory->createScopeFor($this, $resource, $scopeIdentifier); } - /** - * Get Include Params. - * - * @param string $include - * - * @return \League\Fractal\ParamBag - */ - public function getIncludeParams($include) + public function getIncludeParams(string $include): ParamBag { $params = isset($this->includeParams[$include]) ? $this->includeParams[$include] : []; return new ParamBag($params); } - /** - * Get Requested Includes. - * - * @return array - */ - public function getRequestedIncludes() + public function getRequestedIncludes(): array { return $this->requestedIncludes; } - /** - * Get Requested Excludes. - * - * @return array - */ - public function getRequestedExcludes() + public function getRequestedExcludes(): array { return $this->requestedExcludes; } - /** - * Get Serializer. - * - * @return SerializerAbstract - */ - public function getSerializer() + public function getSerializer(): Serializer { - if ($this->serializer === null) { - $this->setSerializer(new DataArraySerializer()); + if (! $this->serializer) { + $this->serializer = new DataArraySerializer(); } return $this->serializer; } /** - * Parse Include String. - * * @param array|string $includes Array or csv string of resources to include - * - * @return $this */ - public function parseIncludes($includes) + public function parseIncludes($includes): self { // Wipe these before we go again $this->requestedIncludes = $this->includeParams = []; + $subRelations = ''; if (is_string($includes)) { $includes = explode(',', $includes); @@ -176,7 +127,9 @@ public function parseIncludes($includes) } foreach ($includes as $include) { - list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, null); + list($includeName, $allModifiersStr) = array_pad(explode(':', $include, 2), 2, ''); + $a = $allModifiersStr ? explode('.', $allModifiersStr, 2) : ['']; + list($allModifiersStr, $subRelations) = array_pad($a, 2, null); // Trim it down to a cool level of recursion $includeName = $this->trimToAcceptableRecursionLevel($includeName); @@ -212,6 +165,10 @@ public function parseIncludes($includes) } $this->includeParams[$includeName] = $modifierArr; + + if ($subRelations) { + $this->requestedIncludes[] = $this->trimToAcceptableRecursionLevel($includeName . '.' . $subRelations); + } } // This should be optional and public someday, but without it includes would never show up @@ -226,10 +183,8 @@ public function parseIncludes($includes) * @param array $fieldsets Array of fields to include. It must be an array whose keys * are resource types and values an array or a string * of the fields to return, separated by a comma - * - * @return $this */ - public function parseFieldsets(array $fieldsets) + public function parseFieldsets(array $fieldsets): self { $this->requestedFieldsets = []; foreach ($fieldsets as $type => $fields) { @@ -242,25 +197,15 @@ public function parseFieldsets(array $fieldsets) } return $this; } - - /** - * Get requested fieldsets. - * - * @return array - */ - public function getRequestedFieldsets() + public function getRequestedFieldsets(): array { return $this->requestedFieldsets; } /** * Get fieldset params for the specified type. - * - * @param string $type - * - * @return \League\Fractal\ParamBag|null */ - public function getFieldset($type) + public function getFieldset(string $type): ?ParamBag { return !isset($this->requestedFieldsets[$type]) ? null : @@ -268,13 +213,9 @@ public function getFieldset($type) } /** - * Parse Exclude String. - * * @param array|string $excludes Array or csv string of resources to exclude - * - * @return $this */ - public function parseExcludes($excludes) + public function parseExcludes($excludes): self { $this->requestedExcludes = []; @@ -301,28 +242,14 @@ public function parseExcludes($excludes) return $this; } - /** - * Set Recursion Limit. - * - * @param int $recursionLimit - * - * @return $this - */ - public function setRecursionLimit($recursionLimit) + public function setRecursionLimit(int $recursionLimit): self { $this->recursionLimit = $recursionLimit; return $this; } - /** - * Set Serializer - * - * @param SerializerAbstract $serializer - * - * @return $this - */ - public function setSerializer(SerializerAbstract $serializer) + public function setSerializer(Serializer $serializer): self { $this->serializer = $serializer; @@ -330,16 +257,12 @@ public function setSerializer(SerializerAbstract $serializer) } /** - * Auto-include Parents - * * Look at the requested includes and automatically include the parents if they * are not explicitly requested. E.g: [foo, bar.baz] becomes [foo, bar, bar.baz] * * @internal - * - * @return void */ - protected function autoIncludeParents() + protected function autoIncludeParents(): void { $parsed = []; @@ -359,18 +282,12 @@ protected function autoIncludeParents() } /** - * Trim to Acceptable Recursion Level - * * Strip off any requested resources that are too many levels deep, to avoid DiCaprio being chased * by trains or whatever the hell that movie was about. * * @internal - * - * @param string $includeName - * - * @return string */ - protected function trimToAcceptableRecursionLevel($includeName) + protected function trimToAcceptableRecursionLevel(string $includeName): string { return implode('.', array_slice(explode('.', $includeName), 0, $this->recursionLimit)); } diff --git a/src/Pagination/Cursor.php b/src/Pagination/Cursor.php index 0b578dea..6156a61b 100644 --- a/src/Pagination/Cursor.php +++ b/src/Pagination/Cursor.php @@ -42,10 +42,8 @@ class Cursor implements CursorInterface /** * Items being held for the current cursor position. - * - * @var int|null */ - protected $count; + protected ?int $count; /** * Create a new Cursor instance. @@ -53,11 +51,8 @@ class Cursor implements CursorInterface * @param mixed $current * @param mixed $prev * @param mixed $next - * @param int $count - * - * @return void */ - public function __construct($current = null, $prev = null, $next = null, $count = null) + public function __construct($current = null, $prev = null, $next = null, ?int $count = null) { $this->current = $current; $this->prev = $prev; @@ -66,10 +61,9 @@ public function __construct($current = null, $prev = null, $next = null, $count } /** - * Get the current cursor value. - * - * @return mixed + * {@inheritDoc} */ + #[\ReturnTypeWillChange] public function getCurrent() { return $this->current; @@ -78,11 +72,9 @@ public function getCurrent() /** * Set the current cursor value. * - * @param int $current - * - * @return Cursor + * @param mixed $current */ - public function setCurrent($current) + public function setCurrent($current): self { $this->current = $current; @@ -90,10 +82,9 @@ public function setCurrent($current) } /** - * Get the prev cursor value. - * - * @return mixed + * {@inheritDoc} */ + #[\ReturnTypeWillChange] public function getPrev() { return $this->prev; @@ -102,11 +93,9 @@ public function getPrev() /** * Set the prev cursor value. * - * @param int $prev - * - * @return Cursor + * @param mixed $prev */ - public function setPrev($prev) + public function setPrev($prev): self { $this->prev = $prev; @@ -114,10 +103,9 @@ public function setPrev($prev) } /** - * Get the next cursor value. - * - * @return mixed + * {@inheritDoc} */ + #[\ReturnTypeWillChange] public function getNext() { return $this->next; @@ -126,11 +114,9 @@ public function getNext() /** * Set the next cursor value. * - * @param int $next - * - * @return Cursor + * @param mixed $next */ - public function setNext($next) + public function setNext($next): self { $this->next = $next; @@ -138,23 +124,17 @@ public function setNext($next) } /** - * Returns the total items in the current cursor. - * - * @return int + * {@inheritDoc} */ - public function getCount() + public function getCount(): ?int { return (int) $this->count; } /** * Set the total items in the current cursor. - * - * @param int $count - * - * @return Cursor */ - public function setCount($count) + public function setCount(int $count): self { $this->count = $count; diff --git a/src/Pagination/CursorInterface.php b/src/Pagination/CursorInterface.php index 9d6af130..12e1f114 100644 --- a/src/Pagination/CursorInterface.php +++ b/src/Pagination/CursorInterface.php @@ -23,6 +23,7 @@ interface CursorInterface * * @return mixed */ + #[\ReturnTypeWillChange] public function getCurrent(); /** @@ -30,6 +31,7 @@ public function getCurrent(); * * @return mixed */ + #[\ReturnTypeWillChange] public function getPrev(); /** @@ -37,12 +39,11 @@ public function getPrev(); * * @return mixed */ + #[\ReturnTypeWillChange] public function getNext(); /** * Returns the total items in the current cursor. - * - * @return int */ - public function getCount(); + public function getCount(): ?int; } diff --git a/src/Pagination/PaginatorInterface.php b/src/Pagination/PaginatorInterface.php index e56a2912..00aabc1a 100644 --- a/src/Pagination/PaginatorInterface.php +++ b/src/Pagination/PaginatorInterface.php @@ -20,45 +20,31 @@ interface PaginatorInterface { /** * Get the current page. - * - * @return int */ - public function getCurrentPage(); + public function getCurrentPage(): int; /** * Get the last page. - * - * @return int */ - public function getLastPage(); + public function getLastPage(): int; /** * Get the total. - * - * @return int */ - public function getTotal(); + public function getTotal(): int; /** * Get the count. - * - * @return int */ - public function getCount(); + public function getCount(): int; /** * Get the number per page. - * - * @return int */ - public function getPerPage(); + public function getPerPage(): int; /** * Get the url for the given page. - * - * @param int $page - * - * @return string */ - public function getUrl($page); + public function getUrl(int $page): string; } diff --git a/src/Pagination/SimplePaginationAdapter.php b/src/Pagination/SimplePaginationAdapter.php index 3f8b630d..89c8b3ab 100644 --- a/src/Pagination/SimplePaginationAdapter.php +++ b/src/Pagination/SimplePaginationAdapter.php @@ -4,7 +4,6 @@ class SimplePaginationAdapter implements PaginatorInterface { - /** * @var int The total number of items in this list */ @@ -49,50 +48,40 @@ public function __construct(int $currentPage, int $itemCount, int $perPage, int /** * Get the current page. - * - * @return int */ - public function getCurrentPage() + public function getCurrentPage(): int { return $this->current; } /** * Get the last page. - * - * @return int */ - public function getLastPage() + public function getLastPage(): int { - return $this->total / $this->perPage; + return (int) floor($this->total / $this->perPage); } /** * Get the total. - * - * @return int */ - public function getTotal() + public function getTotal(): int { return $this->total; } /** * Get the count. - * - * @return int */ - public function getCount() + public function getCount(): int { return $this->itemCount; } /** * Get the number per page. - * - * @return int */ - public function getPerPage() + public function getPerPage(): int { return $this->perPage; } @@ -101,10 +90,8 @@ public function getPerPage() * Get the url for the given page. * * @param int $page - * - * @return string */ - public function getUrl($page) + public function getUrl($page): string { return ($this->urlFactory)($page); } diff --git a/src/ParamBag.php b/src/ParamBag.php index 0ddb7321..476fd5bf 100644 --- a/src/ParamBag.php +++ b/src/ParamBag.php @@ -16,17 +16,10 @@ */ class ParamBag implements \ArrayAccess, \IteratorAggregate { - /** - * @var array - */ - protected $params = []; + protected array $params = []; /** * Create a new parameter bag instance. - * - * @param array $params - * - * @return void */ public function __construct(array $params) { @@ -36,11 +29,10 @@ public function __construct(array $params) /** * Get parameter values out of the bag. * - * @param string $key - * * @return mixed */ - public function get($key) + #[\ReturnTypeWillChange] + public function get(string $key) { return $this->__get($key); } @@ -48,23 +40,18 @@ public function get($key) /** * Get parameter values out of the bag via the property access magic method. * - * @param string $key - * * @return mixed */ - public function __get($key) + #[\ReturnTypeWillChange] + public function __get(string $key) { return isset($this->params[$key]) ? $this->params[$key] : null; } /** * Check if a param exists in the bag via an isset() check on the property. - * - * @param string $key - * - * @return bool */ - public function __isset($key) + public function __isset(string $key): bool { return isset($this->params[$key]); } @@ -72,14 +59,11 @@ public function __isset($key) /** * Disallow changing the value of params in the data bag via property access. * - * @param string $key * @param mixed $value * * @throws \LogicException - * - * @return void */ - public function __set($key, $value) + public function __set(string $key, $value): void { throw new \LogicException('Modifying parameters is not permitted'); } @@ -87,13 +71,11 @@ public function __set($key, $value) /** * Disallow unsetting params in the data bag via property access. * - * @param string $key - * * @throws \LogicException * * @return void */ - public function __unset($key) + public function __unset(string $key): void { throw new \LogicException('Modifying parameters is not permitted'); } @@ -102,10 +84,8 @@ public function __unset($key) * Check if a param exists in the bag via an isset() and array access. * * @param string $key - * - * @return bool */ - public function offsetExists($key) + public function offsetExists($key): bool { return $this->__isset($key); } @@ -117,6 +97,7 @@ public function offsetExists($key) * * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($key) { return $this->__get($key); @@ -129,10 +110,8 @@ public function offsetGet($key) * @param mixed $value * * @throws \LogicException - * - * @return void */ - public function offsetSet($key, $value) + public function offsetSet($key, $value): void { throw new \LogicException('Modifying parameters is not permitted'); } @@ -143,20 +122,16 @@ public function offsetSet($key, $value) * @param string $key * * @throws \LogicException - * - * @return void */ - public function offsetUnset($key) + public function offsetUnset($key): void { throw new \LogicException('Modifying parameters is not permitted'); } /** * IteratorAggregate for iterating over the object like an array. - * - * @return \ArrayIterator */ - public function getIterator() + public function getIterator(): \ArrayIterator { return new \ArrayIterator($this->params); } diff --git a/src/Resource/Collection.php b/src/Resource/Collection.php index 610bb1ae..fd288e7f 100644 --- a/src/Resource/Collection.php +++ b/src/Resource/Collection.php @@ -30,82 +30,47 @@ class Collection extends ResourceAbstract */ protected $data; - /** - * The paginator instance. - * - * @var PaginatorInterface - */ - protected $paginator; + protected ?PaginatorInterface $paginator = null; - /** - * The cursor instance. - * - * @var CursorInterface - */ - protected $cursor; + protected ?CursorInterface $cursor = null; - /** - * Get the paginator instance. - * - * @return PaginatorInterface - */ - public function getPaginator() + public function getPaginator(): ?PaginatorInterface { return $this->paginator; } /** * Determine if the resource has a paginator implementation. - * - * @return bool */ - public function hasPaginator() + public function hasPaginator(): bool { - return $this->paginator instanceof PaginatorInterface; + return $this->paginator !== null; } /** * Get the cursor instance. - * - * @return CursorInterface */ - public function getCursor() + public function getCursor(): ?CursorInterface { return $this->cursor; } /** * Determine if the resource has a cursor implementation. - * - * @return bool */ - public function hasCursor() + public function hasCursor(): bool { - return $this->cursor instanceof CursorInterface; + return $this->cursor !== null; } - /** - * Set the paginator instance. - * - * @param PaginatorInterface $paginator - * - * @return $this - */ - public function setPaginator(PaginatorInterface $paginator) + public function setPaginator(PaginatorInterface $paginator): self { $this->paginator = $paginator; return $this; } - /** - * Set the cursor instance. - * - * @param CursorInterface $cursor - * - * @return $this - */ - public function setCursor(CursorInterface $cursor) + public function setCursor(CursorInterface $cursor): self { $this->cursor = $cursor; diff --git a/src/Resource/Item.php b/src/Resource/Item.php index a1df70be..a1b05765 100644 --- a/src/Resource/Item.php +++ b/src/Resource/Item.php @@ -12,13 +12,10 @@ namespace League\Fractal\Resource; /** - * Item Resource - * * The Item Resource can store any mixed data, usually an ORM, ODM or * other sort of intelligent result, DataMapper model, etc but could * be a basic array, object, or whatever you like. */ class Item extends ResourceAbstract { - // } diff --git a/src/Resource/MetaDataInterface.php b/src/Resource/MetaDataInterface.php index a9a2724a..9458f6ca 100644 --- a/src/Resource/MetaDataInterface.php +++ b/src/Resource/MetaDataInterface.php @@ -17,9 +17,8 @@ public function getMeta(); /** * Get the meta data. * - * @param string $metaKey - * - * @return array + * @return mixed */ - public function getMetaValue($metaKey); + #[\ReturnTypeWillChange] + public function getMetaValue(string $metaKey); } diff --git a/src/Resource/NullResource.php b/src/Resource/NullResource.php index c8396b75..d153584a 100644 --- a/src/Resource/NullResource.php +++ b/src/Resource/NullResource.php @@ -12,8 +12,6 @@ namespace League\Fractal\Resource; /** - * Null Resource - * * The Null Resource represents a resource that doesn't exist. This can be * useful to indicate that a certain relationship is null in some output * formats (e.g. JSON API), which require even a relationship that is null at @@ -26,8 +24,10 @@ class NullResource extends ResourceAbstract * * @return mixed */ + #[\ReturnTypeWillChange] public function getData() { // Null has no data associated with it. + return null; } } diff --git a/src/Resource/Primitive.php b/src/Resource/Primitive.php index c0bedd61..dd512733 100644 --- a/src/Resource/Primitive.php +++ b/src/Resource/Primitive.php @@ -19,5 +19,4 @@ */ class Primitive extends ResourceAbstract { - // } diff --git a/src/Resource/ResourceAbstract.php b/src/Resource/ResourceAbstract.php index e36bbe6b..b8cbb86b 100644 --- a/src/Resource/ResourceAbstract.php +++ b/src/Resource/ResourceAbstract.php @@ -24,17 +24,13 @@ abstract class ResourceAbstract implements ResourceInterface, MetaDataInterface /** * Array of meta data. - * - * @var array */ - protected $meta = []; + protected array $meta = []; /** * The resource key. - * - * @var string|null */ - protected $resourceKey; + protected ?string $resourceKey; /** * A callable to process the data attached to this resource. @@ -44,13 +40,10 @@ abstract class ResourceAbstract implements ResourceInterface, MetaDataInterface protected $transformer; /** - * Create a new resource instance. - * * @param mixed $data * @param callable|TransformerAbstract|null $transformer - * @param string $resourceKey */ - public function __construct($data = null, $transformer = null, $resourceKey = null) + public function __construct($data = null, $transformer = null, ?string $resourceKey = null) { $this->data = $data; $this->transformer = $transformer; @@ -62,6 +55,7 @@ public function __construct($data = null, $transformer = null, $resourceKey = nu * * @return mixed */ + #[\ReturnTypeWillChange] public function getData() { return $this->data; @@ -71,10 +65,8 @@ public function getData() * Set the data. * * @param mixed $data - * - * @return $this */ - public function setData($data) + public function setData($data): self { $this->data = $data; @@ -83,10 +75,8 @@ public function setData($data) /** * Get the meta data. - * - * @return array */ - public function getMeta() + public function getMeta(): array { return $this->meta; } @@ -94,23 +84,17 @@ public function getMeta() /** * Get the meta data. * - * @param string $metaKey - * - * @return array + * @return mixed */ - public function getMetaValue($metaKey) + #[\ReturnTypeWillChange] + public function getMetaValue(string $metaKey) { return $this->meta[$metaKey]; } - /** - * Get the resource key. - * - * @return string - */ - public function getResourceKey() + public function getResourceKey(): string { - return (string) $this->resourceKey; + return $this->resourceKey ?? ''; } /** @@ -127,10 +111,8 @@ public function getTransformer() * Set the transformer. * * @param callable|TransformerAbstract $transformer - * - * @return $this */ - public function setTransformer($transformer) + public function setTransformer($transformer): self { $this->transformer = $transformer; @@ -139,12 +121,8 @@ public function setTransformer($transformer) /** * Set the meta data. - * - * @param array $meta - * - * @return $this */ - public function setMeta(array $meta) + public function setMeta(array $meta): self { $this->meta = $meta; @@ -152,28 +130,18 @@ public function setMeta(array $meta) } /** - * Set the meta data. + * Set one meta data value. * - * @param string $metaKey * @param mixed $metaValue - * - * @return $this */ - public function setMetaValue($metaKey, $metaValue) + public function setMetaValue(string $metaKey, $metaValue): self { $this->meta[$metaKey] = $metaValue; return $this; } - /** - * Set the resource key. - * - * @param string $resourceKey - * - * @return $this - */ - public function setResourceKey($resourceKey) + public function setResourceKey(string $resourceKey): self { $this->resourceKey = $resourceKey; diff --git a/src/Resource/ResourceInterface.php b/src/Resource/ResourceInterface.php index 4e0ef219..dc8b8751 100644 --- a/src/Resource/ResourceInterface.php +++ b/src/Resource/ResourceInterface.php @@ -15,16 +15,15 @@ interface ResourceInterface { /** * Get the resource key. - * - * @return string */ - public function getResourceKey(); + public function getResourceKey(): string; /** * Get the data. * * @return mixed */ + #[\ReturnTypeWillChange] public function getData(); /** @@ -38,17 +37,18 @@ public function getTransformer(); * Set the data. * * @param mixed $data - * - * @return $this */ - public function setData($data); + public function setData($data): self; /** * Set the transformer. * * @param callable|\League\Fractal\TransformerAbstract $transformer - * - * @return $this */ - public function setTransformer($transformer); + public function setTransformer($transformer): self; + + /** + * Get the meta data. + */ + public function getMeta(): array; } diff --git a/src/Scope.php b/src/Scope.php index 0cee9404..5acae5d1 100644 --- a/src/Scope.php +++ b/src/Scope.php @@ -18,7 +18,7 @@ use League\Fractal\Resource\Primitive; use League\Fractal\Resource\NullResource; use League\Fractal\Resource\ResourceInterface; -use League\Fractal\Serializer\SerializerAbstract; +use League\Fractal\Serializer\Serializer; /** * Scope @@ -27,43 +27,19 @@ * context. For example, the same resource could be attached to multiple scopes. * There are root scopes, parent scopes and child scopes. */ -class Scope +class Scope implements \JsonSerializable { - /** - * @var array - */ - protected $availableIncludes = []; + protected array $availableIncludes = []; - /** - * @var string|null - */ - protected $scopeIdentifier; + protected ?string $scopeIdentifier; - /** - * @var \League\Fractal\Manager - */ - protected $manager; + protected Manager $manager; - /** - * @var ResourceInterface - */ - protected $resource; + protected ResourceInterface $resource; - /** - * @var array - */ - protected $parentScopes = []; + protected array $parentScopes = []; - /** - * Create a new scope instance. - * - * @param Manager $manager - * @param ResourceInterface $resource - * @param string $scopeIdentifier - * - * @return void - */ - public function __construct(Manager $manager, ResourceInterface $resource, $scopeIdentifier = null) + public function __construct(Manager $manager, ResourceInterface $resource, ?string $scopeIdentifier = null) { $this->manager = $manager; $this->resource = $resource; @@ -74,35 +50,24 @@ public function __construct(Manager $manager, ResourceInterface $resource, $scop * Embed a scope as a child of the current scope. * * @internal - * - * @param string $scopeIdentifier - * @param ResourceInterface $resource - * - * @return \League\Fractal\Scope */ - public function embedChildScope($scopeIdentifier, $resource) + public function embedChildScope(string $scopeIdentifier, ResourceInterface $resource): Scope { return $this->manager->createData($resource, $scopeIdentifier, $this); } /** * Get the current identifier. - * - * @return string */ - public function getScopeIdentifier() + public function getScopeIdentifier(): ?string { return (string) $this->scopeIdentifier; } /** * Get the unique identifier for this scope. - * - * @param string $appendIdentifier - * - * @return string */ - public function getIdentifier($appendIdentifier = null) + public function getIdentifier(?string $appendIdentifier = null): string { $identifierParts = array_merge($this->parentScopes, [$this->scopeIdentifier, $appendIdentifier]); @@ -110,49 +75,34 @@ public function getIdentifier($appendIdentifier = null) } /** - * Getter for parentScopes. - * * @return mixed */ + #[\ReturnTypeWillChange] public function getParentScopes() { return $this->parentScopes; } - /** - * Getter for resource. - * - * @return ResourceInterface - */ - public function getResource() + public function getResource(): ResourceInterface { return $this->resource; } - /** - * Getter for manager. - * - * @return \League\Fractal\Manager - */ - public function getManager() + public function getManager(): Manager { return $this->manager; } /** - * Is Requested. - * * Check if - in relation to the current scope - this specific segment is allowed. * That means, if a.b.c is requested and the current scope is a.b, then c is allowed. If the current * scope is a then c is not allowed, even if it is there and potentially transformable. * * @internal * - * @param string $checkScopeSegment - * * @return bool Returns the new number of elements in the array. */ - public function isRequested($checkScopeSegment) + public function isRequested(string $checkScopeSegment): bool { if ($this->parentScopes) { $scopeArray = array_slice($this->parentScopes, 1); @@ -161,26 +111,20 @@ public function isRequested($checkScopeSegment) $scopeArray = [$checkScopeSegment]; } - $scopeString = implode('.', (array) $scopeArray); + $scopeString = implode('.', $scopeArray); return in_array($scopeString, $this->manager->getRequestedIncludes()); } /** - * Is Excluded. - * * Check if - in relation to the current scope - this specific segment should * be excluded. That means, if a.b.c is excluded and the current scope is a.b, * then c will not be allowed in the transformation whether it appears in * the list of default or available, requested includes. * * @internal - * - * @param string $checkScopeSegment - * - * @return bool */ - public function isExcluded($checkScopeSegment) + public function isExcluded(string $checkScopeSegment): bool { if ($this->parentScopes) { $scopeArray = array_slice($this->parentScopes, 1); @@ -189,7 +133,7 @@ public function isExcluded($checkScopeSegment) $scopeArray = [$checkScopeSegment]; } - $scopeString = implode('.', (array) $scopeArray); + $scopeString = implode('.', $scopeArray); return in_array($scopeString, $this->manager->getRequestedExcludes()); } @@ -201,11 +145,9 @@ public function isExcluded($checkScopeSegment) * * @internal * - * @param string $identifierSegment - * * @return int Returns the new number of elements in the array. */ - public function pushParentScope($identifierSegment) + public function pushParentScope(string $identifierSegment): int { return array_push($this->parentScopes, $identifierSegment); } @@ -216,10 +158,8 @@ public function pushParentScope($identifierSegment) * @internal * * @param string[] $parentScopes Value to set. - * - * @return $this */ - public function setParentScopes($parentScopes) + public function setParentScopes(array $parentScopes): self { $this->parentScopes = $parentScopes; @@ -228,10 +168,8 @@ public function setParentScopes($parentScopes) /** * Convert the current data for this scope to an array. - * - * @return array|null */ - public function toArray() + public function toArray(): ?array { list($rawData, $rawIncludedData) = $this->executeResourceTransformers(); @@ -296,14 +234,19 @@ public function toArray() return $data + $meta; } + /** + * @return mixed + */ + #[\ReturnTypeWillChange] + public function jsonSerialize() + { + return $this->toArray(); + } + /** * Convert the current data for this scope to JSON. - * - * @param int $options - * - * @return string */ - public function toJson($options = 0) + public function toJson(int $options = 0): string { $result = json_encode($this->toArray(), $options); return $result ?: ''; @@ -314,6 +257,7 @@ public function toJson($options = 0) * * @return mixed */ + #[\ReturnTypeWillChange] public function transformPrimitiveResource() { if (! ($this->resource instanceof Primitive)) { @@ -341,10 +285,8 @@ public function transformPrimitiveResource() * Execute the resources transformer and return the data and included data. * * @internal - * - * @return array */ - protected function executeResourceTransformers() + protected function executeResourceTransformers(): array { $transformer = $this->resource->getTransformer(); $data = $this->resource->getData(); @@ -375,21 +317,27 @@ protected function executeResourceTransformers() * * @internal * - * @param SerializerAbstract $serializer - * @param mixed $data - * - * @return array|null + * @param mixed $data */ - protected function serializeResource(SerializerAbstract $serializer, $data) + protected function serializeResource(Serializer $serializer, $data): ?array { $resourceKey = $this->resource->getResourceKey(); if ($this->resource instanceof Collection) { - return $serializer->collection($resourceKey, $data); + if (!empty($resourceKey)) { + return $serializer->collection($resourceKey, $data); + } + + return $serializer->collection(null, $data); } if ($this->resource instanceof Item) { - return $serializer->item($resourceKey, $data); + // this is where it breaks now. + if (!empty($resourceKey)) { + return $serializer->item($resourceKey, $data); + } + + return $serializer->item(null, $data); } return $serializer->null(); @@ -402,10 +350,8 @@ protected function serializeResource(SerializerAbstract $serializer, $data) * * @param TransformerAbstract|callable|null $transformer * @param mixed $data - * - * @return array */ - protected function fireTransformer($transformer, $data) + protected function fireTransformer($transformer, $data): array { $includedData = []; @@ -438,10 +384,8 @@ protected function fireTransformer($transformer, $data) * * @param \League\Fractal\TransformerAbstract $transformer * @param mixed $data - * - * @return array */ - protected function fireIncludedTransformers($transformer, $data) + protected function fireIncludedTransformers($transformer, $data): array { $this->availableIncludes = $transformer->getAvailableIncludes(); @@ -454,10 +398,8 @@ protected function fireIncludedTransformers($transformer, $data) * @internal * * @param TransformerAbstract|callable $transformer - * - * @return bool */ - protected function transformerHasIncludes($transformer) + protected function transformerHasIncludes($transformer): bool { if (! $transformer instanceof TransformerAbstract) { return false; @@ -471,10 +413,8 @@ protected function transformerHasIncludes($transformer) /** * Check, if this is the root scope. - * - * @return bool */ - protected function isRootScope() + protected function isRootScope(): bool { return empty($this->parentScopes); } @@ -484,12 +424,8 @@ protected function isRootScope() * the scope resource * * @internal - * - * @param array $data - * - * @return array */ - protected function filterFieldsets(array $data) + protected function filterFieldsets(array $data): array { if (!$this->hasFilterFieldset()) { return $data; @@ -511,10 +447,8 @@ protected function filterFieldsets(array $data) * Return the requested filter fieldset for the scope resource * * @internal - * - * @return \League\Fractal\ParamBag|null */ - protected function getFilterFieldset() + protected function getFilterFieldset(): ?ParamBag { return $this->manager->getFieldset($this->getResourceType()); } @@ -523,10 +457,8 @@ protected function getFilterFieldset() * Check if there are requested filter fieldsets for the scope resource. * * @internal - * - * @return bool */ - protected function hasFilterFieldset() + protected function hasFilterFieldset(): bool { return $this->getFilterFieldset() !== null; } @@ -535,10 +467,8 @@ protected function hasFilterFieldset() * Return the scope resource type. * * @internal - * - * @return string */ - protected function getResourceType() + protected function getResourceType(): string { return $this->resource->getResourceKey(); } diff --git a/src/ScopeFactory.php b/src/ScopeFactory.php index 1d173650..5e719321 100644 --- a/src/ScopeFactory.php +++ b/src/ScopeFactory.php @@ -15,31 +15,25 @@ class ScopeFactory implements ScopeFactoryInterface { - /** - * @param Manager $manager - * @param ResourceInterface $resource - * @param string|null $scopeIdentifier - * @return Scope - */ - public function createScopeFor(Manager $manager, ResourceInterface $resource, $scopeIdentifier = null) - { + public function createScopeFor( + Manager $manager, + ResourceInterface $resource, + ?string $scopeIdentifier = null + ): Scope { return new Scope($manager, $resource, $scopeIdentifier); } - /** - * @param Manager $manager - * @param Scope $parentScopeInstance - * @param ResourceInterface $resource - * @param string|null $scopeIdentifier - * @return Scope - */ - public function createChildScopeFor(Manager $manager, Scope $parentScopeInstance, ResourceInterface $resource, $scopeIdentifier = null) - { + public function createChildScopeFor( + Manager $manager, + Scope $parentScope, + ResourceInterface $resource, + ?string $scopeIdentifier = null + ): Scope { $scopeInstance = $this->createScopeFor($manager, $resource, $scopeIdentifier); // This will be the new children list of parents (parents parents, plus the parent) - $scopeArray = $parentScopeInstance->getParentScopes(); - $scopeArray[] = $parentScopeInstance->getScopeIdentifier(); + $scopeArray = $parentScope->getParentScopes(); + $scopeArray[] = $parentScope->getScopeIdentifier(); $scopeInstance->setParentScopes($scopeArray); diff --git a/src/ScopeFactoryInterface.php b/src/ScopeFactoryInterface.php index 1175712b..fe4fa680 100644 --- a/src/ScopeFactoryInterface.php +++ b/src/ScopeFactoryInterface.php @@ -14,26 +14,20 @@ use League\Fractal\Resource\ResourceInterface; /** - * ScopeFactoryInterface - * * Creates Scope Instances for resources */ interface ScopeFactoryInterface { - /** - * @param Manager $manager - * @param ResourceInterface $resource - * @param string|null $scopeIdentifier - * @return Scope - */ - public function createScopeFor(Manager $manager, ResourceInterface $resource, $scopeIdentifier = null); + public function createScopeFor( + Manager $manager, + ResourceInterface $resource, + ?string $scopeIdentifier = null + ): Scope; - /** - * @param Manager $manager - * @param Scope $parentScope - * @param ResourceInterface $resource - * @param string|null $scopeIdentifier - * @return Scope - */ - public function createChildScopeFor(Manager $manager, Scope $parentScope, ResourceInterface $resource, $scopeIdentifier = null); + public function createChildScopeFor( + Manager $manager, + Scope $parentScope, + ResourceInterface $resource, + ?string $scopeIdentifier = null + ): Scope; } diff --git a/src/Serializer/ArraySerializer.php b/src/Serializer/ArraySerializer.php index 52489603..1b620008 100644 --- a/src/Serializer/ArraySerializer.php +++ b/src/Serializer/ArraySerializer.php @@ -18,62 +18,41 @@ class ArraySerializer extends SerializerAbstract { /** - * Serialize a collection. - * - * @param string $resourceKey - * @param array $data - * - * @return array + * {@inheritDoc} */ - public function collection($resourceKey, array $data) + public function collection(?string $resourceKey, array $data): array { return [$resourceKey ?: 'data' => $data]; } /** - * Serialize an item. - * - * @param string $resourceKey - * @param array $data - * - * @return array + * {@inheritDoc} */ - public function item($resourceKey, array $data) + public function item(?string $resourceKey, array $data): array { return $data; } /** - * Serialize null resource. - * - * @return array + * {@inheritDoc} */ - public function null() + public function null(): ?array { return []; } /** - * Serialize the included data. - * - * @param ResourceInterface $resource - * @param array $data - * - * @return array + * {@inheritDoc} */ - public function includedData(ResourceInterface $resource, array $data) + public function includedData(ResourceInterface $resource, array $data): array { return $data; } /** - * Serialize the meta. - * - * @param array $meta - * - * @return array + * {@inheritDoc} */ - public function meta(array $meta) + public function meta(array $meta): array { if (empty($meta)) { return []; @@ -83,21 +62,17 @@ public function meta(array $meta) } /** - * Serialize the paginator. - * - * @param PaginatorInterface $paginator - * - * @return array + * {@inheritDoc} */ - public function paginator(PaginatorInterface $paginator) + public function paginator(PaginatorInterface $paginator): array { - $currentPage = (int) $paginator->getCurrentPage(); - $lastPage = (int) $paginator->getLastPage(); + $currentPage = $paginator->getCurrentPage(); + $lastPage = $paginator->getLastPage(); $pagination = [ - 'total' => (int) $paginator->getTotal(), - 'count' => (int) $paginator->getCount(), - 'per_page' => (int) $paginator->getPerPage(), + 'total' => $paginator->getTotal(), + 'count' => $paginator->getCount(), + 'per_page' => $paginator->getPerPage(), 'current_page' => $currentPage, 'total_pages' => $lastPage, ]; @@ -120,19 +95,15 @@ public function paginator(PaginatorInterface $paginator) } /** - * Serialize the cursor. - * - * @param CursorInterface $cursor - * - * @return array + * {@inheritDoc} */ - public function cursor(CursorInterface $cursor) + public function cursor(CursorInterface $cursor): array { $cursor = [ 'current' => $cursor->getCurrent(), 'prev' => $cursor->getPrev(), 'next' => $cursor->getNext(), - 'count' => (int) $cursor->getCount(), + 'count' => $cursor->getCount(), ]; return ['cursor' => $cursor]; diff --git a/src/Serializer/DataArraySerializer.php b/src/Serializer/DataArraySerializer.php index 70339833..2f382ef8 100644 --- a/src/Serializer/DataArraySerializer.php +++ b/src/Serializer/DataArraySerializer.php @@ -14,37 +14,25 @@ class DataArraySerializer extends ArraySerializer { /** - * Serialize a collection. - * - * @param string $resourceKey - * @param array $data - * - * @return array + * {@inheritDoc} */ - public function collection($resourceKey, array $data) + public function collection(?string $resourceKey, array $data): array { return ['data' => $data]; } /** - * Serialize an item. - * - * @param string $resourceKey - * @param array $data - * - * @return array + * {@inheritDoc} */ - public function item($resourceKey, array $data) + public function item(?string $resourceKey, array $data): array { return ['data' => $data]; } /** - * Serialize null resource. - * - * @return array + * {@inheritDoc} */ - public function null() + public function null(): ?array { return ['data' => []]; } diff --git a/src/Serializer/Serializer.php b/src/Serializer/Serializer.php index e3252b08..434671e1 100644 --- a/src/Serializer/Serializer.php +++ b/src/Serializer/Serializer.php @@ -1,4 +1,5 @@ availableIncludes; } /** * Getter for defaultIncludes. - * - * @return array */ - public function getDefaultIncludes() + public function getDefaultIncludes(): array { return $this->defaultIncludes; } /** * Getter for currentScope. - * - * @return \League\Fractal\Scope */ - public function getCurrentScope() + public function getCurrentScope(): ?Scope { return $this->currentScope; } /** * Figure out which includes we need. - * - * @internal - * - * @param Scope $scope - * - * @return array */ - private function figureOutWhichIncludes(Scope $scope) + private function figureOutWhichIncludes(Scope $scope): array { $includes = $this->getDefaultIncludes(); @@ -114,7 +94,6 @@ private function figureOutWhichIncludes(Scope $scope) * * @internal * - * @param Scope $scope * @param mixed $data * * @return array|false @@ -140,21 +119,14 @@ public function processIncludedResources(Scope $scope, $data) /** * Include a resource only if it is available on the method. * - * @internal - * - * @param Scope $scope * @param mixed $data - * @param array $includedData - * @param string $include - * - * @return array */ private function includeResourceIfAvailable( Scope $scope, $data, - $includedData, - $include - ) { + array $includedData, + string $include + ): array { if ($resource = $this->callIncludeMethod($scope, $include, $data)) { $childScope = $scope->embedChildScope($include, $resource); @@ -173,21 +145,34 @@ private function includeResourceIfAvailable( * * @internal * - * @param Scope $scope - * @param string $includeName * @param mixed $data * * @throws \Exception * * @return \League\Fractal\Resource\ResourceInterface|false */ - protected function callIncludeMethod(Scope $scope, $includeName, $data) + protected function callIncludeMethod(Scope $scope, string $includeName, $data) { $scopeIdentifier = $scope->getIdentifier($includeName); + $params = $scope->getManager()->getIncludeParams($scopeIdentifier); // Check if the method name actually exists - $methodName = 'include' . str_replace(' ', '', ucwords(str_replace('_', ' ', str_replace('-', ' ', $includeName)))); + $methodName = 'include' . str_replace( + ' ', + '', + ucwords( + str_replace( + '_', + ' ', + str_replace( + '-', + ' ', + $includeName + ) + ) + ) + ); $resource = $this->{$methodName}($data, $params); if ($resource === null) { @@ -209,12 +194,8 @@ protected function callIncludeMethod(Scope $scope, $includeName, $data) /** * Setter for availableIncludes. - * - * @param array $availableIncludes - * - * @return $this */ - public function setAvailableIncludes($availableIncludes) + public function setAvailableIncludes(array $availableIncludes): self { $this->availableIncludes = $availableIncludes; @@ -223,12 +204,8 @@ public function setAvailableIncludes($availableIncludes) /** * Setter for defaultIncludes. - * - * @param array $defaultIncludes - * - * @return $this */ - public function setDefaultIncludes($defaultIncludes) + public function setDefaultIncludes(array $defaultIncludes): self { $this->defaultIncludes = $defaultIncludes; @@ -237,12 +214,8 @@ public function setDefaultIncludes($defaultIncludes) /** * Setter for currentScope. - * - * @param Scope $currentScope - * - * @return $this */ - public function setCurrentScope($currentScope) + public function setCurrentScope(Scope $currentScope): self { $this->currentScope = $currentScope; @@ -254,11 +227,8 @@ public function setCurrentScope($currentScope) * * @param mixed $data * @param callable|null $transformer - * @param string $resourceKey - * - * @return Primitive */ - protected function primitive($data, $transformer = null, $resourceKey = null) + protected function primitive($data, ?callable $transformer = null, ?string $resourceKey = null): Primitive { return new Primitive($data, $transformer, $resourceKey); } @@ -268,11 +238,8 @@ protected function primitive($data, $transformer = null, $resourceKey = null) * * @param mixed $data * @param TransformerAbstract|callable $transformer - * @param string $resourceKey - * - * @return Item */ - protected function item($data, $transformer, $resourceKey = null) + protected function item($data, $transformer, ?string $resourceKey = null): Item { return new Item($data, $transformer, $resourceKey); } @@ -282,21 +249,16 @@ protected function item($data, $transformer, $resourceKey = null) * * @param mixed $data * @param TransformerAbstract|callable $transformer - * @param string $resourceKey - * - * @return Collection */ - protected function collection($data, $transformer, $resourceKey = null) + protected function collection($data, $transformer, ?string $resourceKey = null): Collection { return new Collection($data, $transformer, $resourceKey); } /** * Create a new null resource object. - * - * @return NullResource */ - protected function null() + protected function null(): NullResource { return new NullResource(); } diff --git a/test/ManagerTest.php b/test/ManagerTest.php index f7b47f3d..1305e011 100755 --- a/test/ManagerTest.php +++ b/test/ManagerTest.php @@ -19,7 +19,7 @@ public function testParseIncludeSelfie() public function testInvalidParseInclude() { - $this->expectException(InvalidArgumentException::class, 'The parseIncludes() method expects a string or an array. NULL given'); + $this->expectExceptionObject(new InvalidArgumentException('The parseIncludes() method expects a string or an array. NULL given')); $manager = new Manager(); @@ -28,7 +28,7 @@ public function testInvalidParseInclude() public function testIceTParseInclude() { - $this->expectException(InvalidArgumentException::class, 'The parseIncludes() method expects a string or an array. integer given'); + $this->expectExceptionObject(new InvalidArgumentException('The parseIncludes() method expects a string or an array. integer given')); $manager = new Manager(); @@ -52,6 +52,10 @@ public function testParseIncludes() $manager->parseIncludes(['foo', 'foo', 'bar']); $this->assertSame(['foo', 'bar'], $manager->getRequestedIncludes()); + $manager->parseIncludes(['foo.bar', 'foo:limit(10|1).bar']); + $this->assertSame(['foo', 'foo.bar'], $manager->getRequestedIncludes()); + $this->assertSame(['10', '1'], $manager->getIncludeParams('foo')->get('limit')); + // Do requests for `baz.bart` also request `baz`? $manager->parseIncludes(['foo.bar']); $this->assertSame(['foo', 'foo.bar'], $manager->getRequestedIncludes()); @@ -70,6 +74,17 @@ public function testParseIncludes() $this->assertSame([''], $params['anotherparam']); $this->assertNull($params['totallymadeup']); + + // Relation with params and sub relation + $manager->parseIncludes('foo:limit(5|1):order(name).bar,baz'); + + $params = $manager->getIncludeParams('foo'); + + $this->assertInstanceOf('League\Fractal\ParamBag', $params); + + $this->assertSame(['5', '1'], $params['limit']); + $this->assertSame(['name'], $params['order']); + $this->assertSame(['foo', 'foo.bar', 'baz'], $manager->getRequestedIncludes()); } public function testParseExcludeSelfie() @@ -82,8 +97,7 @@ public function testParseExcludeSelfie() public function testInvalidParseExclude() { - $this->expectException(InvalidArgumentException::class, 'The parseExcludes() method expects a string or an array. NULL given'); - + $this->expectExceptionObject(new InvalidArgumentException('The parseExcludes() method expects a string or an array. NULL given')); $manager = new Manager(); @@ -92,7 +106,7 @@ public function testInvalidParseExclude() public function testIceTParseExclude() { - $this->expectException(InvalidArgumentException::class, 'The parseExcludes() method expects a string or an array. integer given'); + $this->expectExceptionObject(new InvalidArgumentException('The parseExcludes() method expects a string or an array. integer given')); $manager = new Manager(); @@ -144,6 +158,24 @@ public function testRecursionLimiting() $manager->getRequestedIncludes() ); + $manager->parseIncludes('a:limit(5|1).b.c.d.e.f.g.h.i.j.NEVER'); + + $this->assertSame( + [ + 'a', + 'a.b', + 'a.b.c', + 'a.b.c.d', + 'a.b.c.d.e', + 'a.b.c.d.e.f', + 'a.b.c.d.e.f.g', + 'a.b.c.d.e.f.g.h', + 'a.b.c.d.e.f.g.h.i', + 'a.b.c.d.e.f.g.h.i.j', + ], + $manager->getRequestedIncludes() + ); + // Try setting to 3 and see what happens $manager->setRecursionLimit(3); $manager->parseIncludes('a.b.c.NEVER'); @@ -156,6 +188,17 @@ public function testRecursionLimiting() ], $manager->getRequestedIncludes() ); + + $manager->parseIncludes('a:limit(5|1).b.c.NEVER'); + + $this->assertSame( + [ + 'a', + 'a.b', + 'a.b.c', + ], + $manager->getRequestedIncludes() + ); } public function testCreateDataWithCallback() diff --git a/test/ParamBagTest.php b/test/ParamBagTest.php index 304d5dcc..38b7bffd 100644 --- a/test/ParamBagTest.php +++ b/test/ParamBagTest.php @@ -35,7 +35,7 @@ public function testArrayAccess() public function testArrayAccessSetFails() { - $this->expectException(LogicException::class, 'Modifying parameters is not permitted'); + $this->expectExceptionObject(new LogicException('Modifying parameters is not permitted')); $params = new ParamBag(['foo' => 'bar']); @@ -44,7 +44,8 @@ public function testArrayAccessSetFails() public function testArrayAccessUnsetFails() { - $this->expectException(LogicException::class, 'Modifying parameters is not permitted'); + $this->expectExceptionObject(new LogicException('Modifying parameters is not permitted')); + $params = new ParamBag(['foo' => 'bar']); unset($params['foo']); @@ -62,7 +63,8 @@ public function testObjectAccess() public function testObjectAccessSetFails() { - $this->expectException(LogicException::class, 'Modifying parameters is not permitted'); + $this->expectExceptionObject(new LogicException('Modifying parameters is not permitted')); + $params = new ParamBag(['foo' => 'bar']); $params->foo = 'someothervalue'; @@ -70,7 +72,8 @@ public function testObjectAccessSetFails() public function testObjectAccessUnsetFails() { - $this->expectException(LogicException::class, 'Modifying parameters is not permitted'); + $this->expectExceptionObject(new LogicException('Modifying parameters is not permitted')); + $params = new ParamBag(['foo' => 'bar']); unset($params->foo); diff --git a/test/ScopeTest.php b/test/ScopeTest.php index 717a6b78..4b62de62 100755 --- a/test/ScopeTest.php +++ b/test/ScopeTest.php @@ -74,6 +74,23 @@ public function testToArray() $this->assertSame(['data' => ['foo' => 'bar']], $scope->toArray()); } + /** + * @covers \League\Fractal\Scope::jsonSerialize() + */ + public function testJsonSerializable() + { + $manager = new Manager(); + + $resource = new Item(['foo' => 'bar'], function ($data) { + return $data; + }); + + $scope = new Scope($manager, $resource); + + $this->assertInstanceOf('\JsonSerializable', $scope); + $this->assertEquals($scope->jsonSerialize(), $scope->toArray()); + } + public function testToJson() { $data = [ @@ -209,9 +226,9 @@ public function testIsExcluded() public function testScopeRequiresConcreteImplementation() { - $this->expectException(InvalidArgumentException::class); + $this->expectException(InvalidArgumentException::class); - $manager = new Manager(); + $manager = new Manager(); $manager->parseIncludes('book'); $resource = Mockery::mock('League\Fractal\Resource\ResourceAbstract', [ @@ -317,7 +334,7 @@ public function testRunAppropriateTransformerWithPrimitive() $transformer = Mockery::mock('League\Fractal\TransformerAbstract'); $transformer->shouldReceive('transform')->once()->andReturn('simple string'); - $transformer->shouldReceive('setCurrentScope')->once()->andReturn([]); + $transformer->shouldReceive('setCurrentScope')->once()->andReturnSelf(); $transformer->shouldNotReceive('getAvailableIncludes'); $transformer->shouldNotReceive('getDefaultIncludes'); @@ -342,7 +359,7 @@ public function testRunAppropriateTransformerWithItem() $transformer->shouldReceive('transform')->once()->andReturn($this->simpleItem); $transformer->shouldReceive('getAvailableIncludes')->once()->andReturn([]); $transformer->shouldReceive('getDefaultIncludes')->once()->andReturn([]); - $transformer->shouldReceive('setCurrentScope')->once()->andReturn([]); + $transformer->shouldReceive('setCurrentScope')->once()->andReturnSelf(); $resource = new Item($this->simpleItem, $transformer); $scope = $manager->createData($resource); @@ -358,7 +375,7 @@ public function testRunAppropriateTransformerWithCollection() $transformer->shouldReceive('transform')->once()->andReturn(['foo' => 'bar']); $transformer->shouldReceive('getAvailableIncludes')->once()->andReturn([]); $transformer->shouldReceive('getDefaultIncludes')->once()->andReturn([]); - $transformer->shouldReceive('setCurrentScope')->once()->andReturn([]); + $transformer->shouldReceive('setCurrentScope')->once()->andReturnSelf(); $resource = new Collection([['foo' => 'bar']], $transformer); $scope = $manager->createData($resource); @@ -371,7 +388,7 @@ public function testRunAppropriateTransformerWithCollection() */ public function testCreateDataWithClassFuckKnows() { - $this->expectException(InvalidArgumentException::class, '$resource should be an instance of League\Fractal\Resource\Item or League\Fractal\Resource\Collection'); + $this->expectExceptionObject(new InvalidArgumentException('Argument $resource should be an instance of League\Fractal\Resource\Item or League\Fractal\Resource\Collection')); $manager = new Manager(); diff --git a/test/Serializer/CustomArraySerializerTest.php b/test/Serializer/CustomArraySerializerTest.php new file mode 100644 index 00000000..f1b5b962 --- /dev/null +++ b/test/Serializer/CustomArraySerializerTest.php @@ -0,0 +1,76 @@ +parseIncludes('author'); + $manager->setSerializer(new RootSerializer()); + + $bookData = [ + 'title' => 'Foo', + 'year' => '1991', + '_author' => [ + 'name' => 'Dave', + ], + ]; + + $resource = new Item($bookData, new GenericBookNoResourceKeyTransformer(), 'data'); + $scope = new Scope($manager, $resource); + + $expected = [ + 'data' => [ + 'title' => 'Foo', + 'year' => 1991, + 'author' => [ + 'name' => 'Dave', + ], + ], + ]; + + $this->assertSame($expected, $scope->toArray()); + } + + public function testMismatchedResourceKey() + { + $manager = new Manager(); + $manager->parseIncludes('author'); + $manager->setSerializer(new RootSerializer()); + + $bookData = [ + 'title' => 'Foo', + 'year' => '1991', + '_author' => [ + 'name' => 'Dave', + ], + ]; + + $resource = new Item($bookData, new GenericBookNoResourceKeyTransformer(), 'data'); + $scope = new Scope($manager, $resource); + + $expected = [ + 'data' => [ + 'title' => 'Foo', + 'year' => 1991, + 'author' => [ + 'data' => [ + 'name' => 'Dave', + ] + ], + ], + ]; + + $this->assertNotSame($expected, $scope->toArray()); + } +} diff --git a/test/Stub/ArraySerializerWithNull.php b/test/Stub/ArraySerializerWithNull.php index 9943ae61..7b5074eb 100644 --- a/test/Stub/ArraySerializerWithNull.php +++ b/test/Stub/ArraySerializerWithNull.php @@ -7,12 +7,7 @@ class ArraySerializerWithNull extends ArraySerializer { - /** - * Serialize null resource. - * - * @return null - */ - public function null() + public function null(): ?array { return null; } diff --git a/test/Stub/Serializer/RootSerializer.php b/test/Stub/Serializer/RootSerializer.php new file mode 100644 index 00000000..fe04c519 --- /dev/null +++ b/test/Stub/Serializer/RootSerializer.php @@ -0,0 +1,32 @@ + $data]; + } + + /** + * Serialize an item. + * + * @param string $resourceKey + * @param array $data + * @return array + */ + public function item($resourceKey, array $data): array + { + return is_null($resourceKey) ? $data : [$resourceKey => $data]; + } +} diff --git a/test/Stub/Transformer/DefaultIncludeBookTransformer.php b/test/Stub/Transformer/DefaultIncludeBookTransformer.php index 54ec2a19..b3e57096 100644 --- a/test/Stub/Transformer/DefaultIncludeBookTransformer.php +++ b/test/Stub/Transformer/DefaultIncludeBookTransformer.php @@ -5,11 +5,11 @@ class DefaultIncludeBookTransformer extends TransformerAbstract { - protected $defaultIncludes = [ + protected array $defaultIncludes = [ 'author', ]; - public function transform() + public function transform(): array { return ['a' => 'b']; } diff --git a/test/Stub/Transformer/GenericBookNoResourceKeyTransformer.php b/test/Stub/Transformer/GenericBookNoResourceKeyTransformer.php new file mode 100644 index 00000000..b18e3a1a --- /dev/null +++ b/test/Stub/Transformer/GenericBookNoResourceKeyTransformer.php @@ -0,0 +1,29 @@ +item($book['_author'], new GenericAuthorTransformer()); + } +} diff --git a/test/Stub/Transformer/GenericBookTransformer.php b/test/Stub/Transformer/GenericBookTransformer.php index 59734ac1..f8649c63 100644 --- a/test/Stub/Transformer/GenericBookTransformer.php +++ b/test/Stub/Transformer/GenericBookTransformer.php @@ -5,11 +5,11 @@ class GenericBookTransformer extends TransformerAbstract { - protected $availableIncludes = [ + protected array $availableIncludes = [ 'author', ]; - public function transform(array $book) + public function transform(array $book): array { $book['year'] = (int) $book['year']; unset($book['_author']); diff --git a/test/Stub/Transformer/NullIncludeBookTransformer.php b/test/Stub/Transformer/NullIncludeBookTransformer.php index 35e61b3a..d20f7ef7 100644 --- a/test/Stub/Transformer/NullIncludeBookTransformer.php +++ b/test/Stub/Transformer/NullIncludeBookTransformer.php @@ -5,11 +5,11 @@ class NullIncludeBookTransformer extends TransformerAbstract { - protected $defaultIncludes = [ + protected array $defaultIncludes = [ 'author', ]; - public function transform() + public function transform(): array { return ['a' => 'b']; } diff --git a/test/Stub/Transformer/PrimitiveIncludeBookTransformer.php b/test/Stub/Transformer/PrimitiveIncludeBookTransformer.php index 3bd0aea8..79d286d7 100644 --- a/test/Stub/Transformer/PrimitiveIncludeBookTransformer.php +++ b/test/Stub/Transformer/PrimitiveIncludeBookTransformer.php @@ -1,20 +1,21 @@ 'b']; } - public function includePrice(array $book) + public function includePrice(array $book): Primitive { return $this->primitive($book['price'], function ($price) { return (int) $price; diff --git a/test/TransformerAbstractTest.php b/test/TransformerAbstractTest.php index 3a31a990..96137b61 100755 --- a/test/TransformerAbstractTest.php +++ b/test/TransformerAbstractTest.php @@ -74,6 +74,16 @@ public function testGetCurrentScope() $this->assertSame($transformer->getCurrentScope(), $scope); } + /** + * @covers \League\Fractal\TransformerAbstract::getCurrentScope + */ + public function testCanAccessScopeBeforeInitialization() + { + $transformer = $this->getMockForAbstractClass('League\Fractal\TransformerAbstract'); + $currentScope = $transformer->getCurrentScope(); + $this->assertNull($currentScope); + } + public function testProcessEmbeddedResourcesNoAvailableIncludes() { $transformer = m::mock('League\Fractal\TransformerAbstract')->makePartial(); @@ -102,7 +112,7 @@ public function testProcessEmbeddedResourcesNoDefaultIncludes() */ public function testProcessEmbeddedResourcesInvalidAvailableEmbed() { - $this->expectException(BadMethodCallException::class); + $this->expectException(BadMethodCallException::class); $transformer = m::mock('League\Fractal\TransformerAbstract')->makePartial(); @@ -122,7 +132,7 @@ public function testProcessEmbeddedResourcesInvalidAvailableEmbed() */ public function testProcessEmbeddedResourcesInvalidDefaultEmbed() { - $this->expectException(BadMethodCallException::class); + $this->expectException(BadMethodCallException::class); $transformer = m::mock('League\Fractal\TransformerAbstract')->makePartial(); @@ -235,7 +245,7 @@ public function testProcessIncludedAvailableResourcesEmptyEmbed() */ public function testCallEmbedMethodReturnsCrap() { - $this->expectException(Exception::class, 'Invalid return value from League\Fractal\TransformerAbstract::includeBook().'); + $this->expectExceptionObject(new Exception('Invalid return value from League\Fractal\TransformerAbstract::includeBook().')); $manager = new Manager(); $manager->parseIncludes('book');