From b3edc3d81569cca635330da32c1e13e07135b9c0 Mon Sep 17 00:00:00 2001 From: Alen Pokos Date: Tue, 27 Nov 2018 12:26:57 +0100 Subject: [PATCH] Update from internal 0.14 release --- .gitlab-ci.yml | 98 +++++++++++- .php_cs.dist | 2 + CHANGELOG.md | 23 ++- README.md | 2 + composer.json | 25 +-- phpunit.xml.dist | 2 + src/Bridge/Doctrine/DoctrineRepository.php | 3 - src/Bridge/Doctrine/ObjectListCollection.php | 7 +- src/Bridge/Doctrine/RepositoryFactory.php | 7 +- .../SchemaAutoMapCompilerPass.php | 147 ++++++++++++++++++ src/Config/Annotation/IndexConfig.php | 5 + src/Config/ApiConfig.php | 5 - src/Config/Config.php | 6 - src/Config/CreateConfig.php | 4 - src/Config/DeleteConfig.php | 2 - src/Config/IndexConfig.php | 25 ++- src/Config/Traits/LoadLazyPropertyTrait.php | 2 - src/Config/UpdateConfig.php | 3 - src/Contracts/Config/ConfigInterface.php | 5 + src/Contracts/Config/IndexConfigInterface.php | 7 + src/Contracts/ErrorFactoryInterface.php | 4 - .../ModelTools/ModelInputHandlerInterface.php | 2 - src/Contracts/ResponseFactoryInterface.php | 36 ++--- src/Controller/AbstractController.php | 25 ++- src/Controller/JsonApiEnabledInterface.php | 8 +- src/Controller/Traits/Actions/CreateTrait.php | 36 ++--- src/Controller/Traits/Actions/DeleteTrait.php | 2 - src/Controller/Traits/Actions/IndexTrait.php | 21 +-- src/Controller/Traits/Actions/UpdateTrait.php | 17 +- src/Controller/Traits/CreateActionTrait.php | 10 +- src/Controller/Traits/DeleteActionTrait.php | 12 +- src/Controller/Traits/IndexActionTrait.php | 10 +- .../Traits/PaginatedIndexActionTrait.php | 10 +- .../SymfonyAutowiredServicesTrait.php | 19 ++- src/Controller/Traits/ShowActionTrait.php | 8 +- src/Controller/Traits/UpdateActionTrait.php | 10 +- src/DependencyInjection/Configuration.php | 4 + .../TrikoderJsonApiExtension.php | 3 - src/Listener/ControllerConfigListener.php | 1 - .../JsonApiEnabledControllerDetectorTrait.php | 10 +- src/Listener/KernelListener.php | 20 +-- src/Model/Factory/SimpleModelFactory.php | 2 - src/Model/ModelFactoryInterface.php | 2 - src/Model/ModelFactoryResolver.php | 6 - src/Model/ModelFactoryResolverInterface.php | 4 - src/Repository/RepositoryFactoryInterface.php | 4 +- src/Repository/RepositoryResolver.php | 7 +- .../RepositoryResolverInterface.php | 7 +- src/Resources/config/services.yml | 13 +- .../doc/configuration/configuration.md | 1 - .../doc/configuration/examples/example.yml | 2 + .../examples/exampleAnnotation.php | 3 +- src/Resources/doc/configuration/services.md | 2 + .../examples/ExampleController.php | 2 +- .../examples/ExampleServiceSchema.php | 10 +- .../examples/ExampleServiceSchemaClassMap.php | 2 +- src/Resources/doc/getting_started/quick.md | 7 +- .../doc/getting_started/schema_automapping.md | 71 +++++++++ .../doc/getting_started/schema_class_map.md | 12 +- src/Resources/doc/getting_started/schemas.md | 14 ++ src/Response/AbstractResponse.php | 9 -- src/Response/DataResponse.php | 5 - .../UnresolvedDependencyException.php | 15 ++ src/Schema/MappableInterface.php | 11 ++ .../AbstractSchemaClassMapService.php | 3 +- src/Services/ConfigBuilder.php | 31 ++-- .../AbstractFormModelInputHandler.php | 12 +- .../CustomFormModelInputHandler.php | 2 - .../GenericFormModelInputHandler.php | 3 - src/Services/ModelInput/ModelToolsFactory.php | 5 - src/Services/ModelInput/ModelValidator.php | 4 +- .../ConstraintViolationToErrorTransformer.php | 2 - .../Traits/FormErrorToErrorTransformer.php | 4 - src/Services/Neomerx/Container.php | 34 +--- src/Services/Neomerx/EncoderService.php | 9 -- src/Services/Neomerx/ErrorFactory.php | 2 +- src/Services/Neomerx/FactoryService.php | 2 - src/Services/Neomerx/ServiceContainer.php | 2 +- src/Services/RequestBodyDecoderService.php | 6 +- src/Services/RequestDecoder.php | 8 +- src/Services/ResponseFactoryService.php | 20 +-- src/TrikoderJsonApiBundle.php | 12 ++ .../Controller/CreateActionTest.php | 46 +++++- .../Demo/CustomMetaResponseActionTest.php | 3 +- .../Functional/Controller/IndexActionTest.php | 3 +- .../Controller/PaginatedIndexActionTest.php | 3 +- .../Controller/RequiredRolesTest.php | 106 +++++++++++++ .../Functional/Controller/ShowActionTest.php | 26 ++++ tests/Integration/SchemaCreationTest.php | 28 ++++ .../AutomapTestSchema/MappableTestSchema.php | 44 ++++++ .../Api/Demo/CustomMetaResponseController.php | 10 +- .../Api/Demo/CustomResponseController.php | 12 +- .../Api/Demo/ExceptionController.php | 2 +- .../Api/Demo/SimpleFileUploadController.php | 8 +- .../Api/Test/CreateOnlyController.php | 20 +++ .../Api/User/CrazyPostController.php | 2 +- .../Api/User/CustomerController.php | 3 +- .../Api/User/LimitedAccessUserController.php | 44 ++++++ .../Api/User/PaginatedUserController.php | 2 +- .../Controller/Api/User/PostController.php | 2 +- .../Controller/Api/User/ProfileController.php | 17 +- .../User/ReducedResponseUserController.php | 2 +- .../Controller/Api/User/UserController.php | 2 +- .../User/VariablePaginatedUserController.php | 8 +- .../Controller/DefaultController.php | 2 +- .../Resources/Controller/NonApiController.php | 2 +- .../DataFixtures/ORM/AbstractBaseFixture.php | 1 - .../DataFixtures/ORM/Demo/LoadPostData.php | 1 - tests/Resources/Entity/Post.php | 5 - tests/Resources/Form/SimpleFileType.php | 3 - tests/Resources/JsonApi/Schema/PostSchema.php | 15 +- .../Schema/Test/PrivateServiceSchema.php | 49 ++++++ tests/Resources/Model/SimpleFileModel.php | 9 -- .../Security/AuthenticatedUserProvider.php | 39 +++++ .../AuthenticatedUserProviderInterface.php | 17 ++ tests/Resources/app/config/config.yml | 32 ++++ tests/Resources/app/config/jsonapi.yml | 2 + tests/Resources/app/config/services.yml | 5 +- .../Resources/app/config/symfony34/config.yml | 89 +++++++++++ .../SchemaAutoMapCompilerPassTest.php | 73 +++++++++ tests/Unit/Config/ConfigBuilderTest.php | 51 +++--- tests/Unit/Config/ConfigurationTest.php | 2 + ...nApiEnabledControllerDetectorTraitTest.php | 13 +- tests/Unit/ResponseFactoryServiceTest.php | 21 +++ ...ntViolationToErrorTransformerTraitTest.php | 2 +- .../FormErrorToErrorTransformerTraitTest.php | 2 +- .../Neomerx/ContainerAutowiringTest.php | 3 +- tests/Unit/Services/RequestDecoderTest.php | 3 - 128 files changed, 1366 insertions(+), 488 deletions(-) create mode 100644 src/CompilerPass/SchemaAutoMapCompilerPass.php create mode 100644 src/Resources/doc/getting_started/schema_automapping.md create mode 100644 src/Schema/Autowire/Exception/UnresolvedDependencyException.php create mode 100644 src/Schema/MappableInterface.php create mode 100644 tests/Functional/Controller/RequiredRolesTest.php create mode 100644 tests/Integration/SchemaCreationTest.php create mode 100644 tests/Resources/AutomapTestSchema/MappableTestSchema.php create mode 100644 tests/Resources/Controller/Api/Test/CreateOnlyController.php create mode 100644 tests/Resources/Controller/Api/User/LimitedAccessUserController.php create mode 100644 tests/Resources/JsonApi/Schema/Test/PrivateServiceSchema.php create mode 100644 tests/Resources/Security/AuthenticatedUserProvider.php create mode 100644 tests/Resources/Security/AuthenticatedUserProviderInterface.php create mode 100644 tests/Resources/app/config/symfony34/config.yml create mode 100644 tests/Unit/CompilerPass/SchemaAutoMapCompilerPassTest.php diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0dfd3fc..9855be4 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,8 +2,9 @@ variables: COMPOSE_PROJECT_NAME: "jsonapibundle${CI_BUILD_ID}" stages: - - test + - test-latest - lint + - test-versions after_script: - cd tests/Resources/docker/ @@ -12,8 +13,9 @@ after_script: - docker-compose down - echo "All Done!" -ci: - stage: test +# any latest built +ci-latest: + stage: test-latest tags: - docker-compose script: @@ -23,6 +25,96 @@ ci: - docker-compose run --no-deps --rm php php vendor/bin/phpunit --debug --colors=never --coverage-text=php://stdout --coverage-html=logs/coverage - bin/php php ../../../vendor/bin/security-checker security:check ../../../composer.lock +# symfony 4.1 build +ci-symfony4.1: + stage: test-versions + tags: + - docker-compose + script: + - cd tests/Resources/docker/ + - docker-compose build + - docker-compose up -d + - bin/composer require "symfony/framework-bundle:^4.1" "symfony/form:^4.1" "symfony/validator:^4.1" --no-interaction --no-ansi --prefer-dist + - docker-compose run --no-deps --rm php composer install --no-interaction --no-ansi --prefer-dist + - bin/setup_fixtures.sh + - docker-compose run --no-deps --rm php php vendor/bin/phpunit --debug --colors=never + +# supported 4.0 build +ci-symfony4.0: + stage: test-versions + tags: + - docker-compose + script: + - cd tests/Resources/docker/ + - docker-compose build + - docker-compose up -d + - bin/composer require "symfony/framework-bundle:^4.0" "symfony/form:^4.0" "symfony/validator:^4.0" --no-interaction --no-ansi --prefer-dist + - docker-compose run --no-deps --rm php composer install --no-interaction --no-ansi --prefer-dist + - bin/setup_fixtures.sh + - docker-compose run --no-deps --rm php php vendor/bin/phpunit --debug --colors=never + +# supported 3.4 build +ci-symfony3.4: + stage: test-versions + tags: + - docker-compose + script: + - cd tests/Resources/docker/ + - docker-compose build + - docker-compose up -d + - mv -f ../app/config/symfony34/config.yml ../app/config/ + - bin/composer require "symfony/framework-bundle:^3.4" "symfony/form:^3.4" "symfony/validator:^3.4" --no-interaction --no-ansi --prefer-dist + - docker-compose run --no-deps --rm php composer install --no-interaction --no-ansi --prefer-dist + - bin/setup_fixtures.sh + - docker-compose run --no-deps --rm php php vendor/bin/phpunit --debug --colors=never + +# un-supported 3.3 build +ci-symfony3.3: + stage: test-versions + tags: + - docker-compose + script: + - cd tests/Resources/docker/ + - docker-compose build + - docker-compose up -d + - mv -f ../app/config/symfony34/config.yml ../app/config/ + - bin/composer require "symfony/framework-bundle:^3.3" "symfony/form:^3.3" "symfony/validator:^3.3" --no-interaction --no-ansi --prefer-dist + - docker-compose run --no-deps --rm php composer install --no-interaction --no-ansi --prefer-dist + - bin/setup_fixtures.sh + - docker-compose run --no-deps --rm php php vendor/bin/phpunit --debug --colors=never + + +# un-supported 3.2 build +ci-symfony3.2: + stage: test-versions + tags: + - docker-compose + script: + - cd tests/Resources/docker/ + - docker-compose build + - docker-compose up -d + - mv -f ../app/config/symfony34/config.yml ../app/config/ + - bin/composer require "symfony/framework-bundle:^3.2" "symfony/form:^3.2" "symfony/validator:^3.2" --no-interaction --no-ansi --prefer-dist + - docker-compose run --no-deps --rm php composer install --no-interaction --no-ansi --prefer-dist + - bin/setup_fixtures.sh + - docker-compose run --no-deps --rm php php vendor/bin/phpunit --debug --colors=never + + +# supported 3.1 build +ci-symfony3.1: + stage: test-versions + tags: + - docker-compose + script: + - cd tests/Resources/docker/ + - docker-compose build + - docker-compose up -d + - mv -f ../app/config/symfony34/config.yml ../app/config/ + - bin/composer require "symfony/framework-bundle:^3.1" "symfony/form:^3.1" "symfony/validator:^3.1" --no-interaction --no-ansi --prefer-dist + - docker-compose run --no-deps --rm php composer install --no-interaction --no-ansi --prefer-dist + - bin/setup_fixtures.sh + - docker-compose run --no-deps --rm php php vendor/bin/phpunit --debug --colors=never + lint: stage: lint tags: diff --git a/.php_cs.dist b/.php_cs.dist index 8c51bec..2fda672 100644 --- a/.php_cs.dist +++ b/.php_cs.dist @@ -8,6 +8,7 @@ $finder = PhpCsFixer\Finder::create() __DIR__ . '/src', __DIR__ . '/tests', ]) + ->exclude('cache') ; return PhpCsFixer\Config::create() @@ -37,6 +38,7 @@ return PhpCsFixer\Config::create() 'yoda_style' => true, 'compact_nullable_typehint' => true, 'visibility_required' => true, + 'no_superfluous_phpdoc_tags' => true, ]) ->setRiskyAllowed(true) ->setFinder($finder) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6069d8..91d8554 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,30 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +## Unreleased +## [0.14.0] 2018-10-23 +### Added +- Added support for automatically mapped schema files, see [schema automapping docs](src/Resources/doc/getting_started/schema_automapping.md) +- Implemented support for required roles config directive, see [config reference](src/Resources/doc/configuration/configuration.md) + +### Changed +- `\Trikoder\JsonApiBundle\Controller\AbstractController` is now auto-tagged with the `controller.service_arguments` tag. + + +## [0.11.0] 2018-08-03 + +### Changed +- Changed route annotation use to symfony/routing, previously was sensio/framework-extra-bundle +- Added symfony/routing as dependancy on 3.4 +- Added "doctrine/common": "<2.9" as dependancy to cover deprication notices + +### Removed +- Removed second argument (ServiceContainer) of schema as closure definition. see [Manual](src/Resources/doc/getting_started/schema_class_map.md) + +### Deprecated +- Deprecated RepositoryFactoryInterface (`\Trikoder\JsonApiBundle\Repository\RepositoryFactoryInterface`) in favour of using DIC factory options ## [0.1.0] 2017-07-27 diff --git a/README.md b/README.md index 494675c..9d00084 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,8 @@ Copyright (C) 2017 Trikoder Author: Alen Pokos. +Contributors (in alphabetic order): Antonio Pauletich, Alen Pokos, Antonio Šunjić, Damir Trputec, Juraj Juričić, Krešo Kunjas, Petar Obradović, Vedran Krizek, Vedran Mihočinec + ## License Package is licensed under [MIT License](./LICENSE) diff --git a/composer.json b/composer.json index fc551af..d0f683d 100644 --- a/composer.json +++ b/composer.json @@ -4,19 +4,21 @@ "type": "symfony-bundle", "require": { "php": ">=7.0.0|>=7.1.0", - "symfony/framework-bundle": "^3.1|^4.0", - "symfony/dependency-injection": "^3.1|^4.0", + "symfony/framework-bundle": ">=3.1 <5.0", + "symfony/dependency-injection": ">=3.1 <5.0", + "sensio/framework-extra-bundle": ">3.0|^5.1|^5.2", + "symfony/form": ">=3.1 <5.0", + "symfony/routing": ">=3.1 <5.0", + "symfony/security-bundle": "^3.1|^4.0", + "symfony/translation": ">=3.1 <5.0", + "symfony/validator": ">=3.1 <5.0", + "symfony/monolog-bundle": ">=3.1 <5.0", "neomerx/json-api": "^1.0", - "doctrine/orm": "~2.4,<2.5|2.5|^2.6", - "doctrine/doctrine-bundle": "~1.6|^1.8", - "sensio/framework-extra-bundle": "^3.0|^5.1", - "symfony/form": "^3.1|^4.0", - "symfony/translation": "^3.3|^4.0", - "symfony/validator": "^3.3|^4.0", - "symfony/monolog-bundle": "^3.1" + "doctrine/orm": "^2.4", + "doctrine/common": "^2", + "doctrine/doctrine-bundle": "~1.6|^1.8|^1.9" }, "require-dev": { - "sensio/generator-bundle": "3.0.*|3.1.*|3.2.*|3.3.*", "phpunit/phpunit": "^5.7", "phpunit/php-code-coverage": "^4.0", "symfony/phpunit-bridge": "^3.0|^4.0", @@ -24,14 +26,13 @@ "doctrine/doctrine-fixtures-bundle": "^2.3", "fzaninotto/faker": "^1.5", "sensiolabs/security-checker": "^4.1", - "symfony/security-bundle": "^3.1|^4.0", "symfony/twig-bundle": "^3.3|^4.0", "doctrine/cache": "^1.6", "symfony/debug-bundle": "^3.3|^4.0", "symfony/web-profiler-bundle": "^3.3|^4.0", "symfony/web-server-bundle": "^3.3|^4.0", "symfony/browser-kit": "^3.3|^4.0", - "friendsofphp/php-cs-fixer": "^2.8" + "friendsofphp/php-cs-fixer": "2.13.0" }, "autoload": { "psr-4": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 85ec3f7..4e624b4 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -10,6 +10,8 @@ + + diff --git a/src/Bridge/Doctrine/DoctrineRepository.php b/src/Bridge/Doctrine/DoctrineRepository.php index 3a798ed..6b522b6 100644 --- a/src/Bridge/Doctrine/DoctrineRepository.php +++ b/src/Bridge/Doctrine/DoctrineRepository.php @@ -22,9 +22,6 @@ class DoctrineRepository implements RepositoryInterface /** * DoctrineRepository constructor. - * - * @param EntityRepository $entityRepository - * @param EntityManager $entityManager */ public function __construct(EntityRepository $entityRepository, EntityManager $entityManager) { diff --git a/src/Bridge/Doctrine/ObjectListCollection.php b/src/Bridge/Doctrine/ObjectListCollection.php index e2dcadd..5b69485 100644 --- a/src/Bridge/Doctrine/ObjectListCollection.php +++ b/src/Bridge/Doctrine/ObjectListCollection.php @@ -19,20 +19,17 @@ class ObjectListCollection implements ObjectListCollectionInterface /** * ObjectListCollection constructor. - * - * @param array $collection - * @param null $total */ public function __construct(array $collection, $total = null) { - if (false === is_array($collection) || null === $collection) { + if (false === \is_array($collection) || null === $collection) { $collection = []; } $this->collection = $collection; // if null, we presume full list is returned if (null === $total) { - $total = count($collection); + $total = \count($collection); } $this->total = $total; diff --git a/src/Bridge/Doctrine/RepositoryFactory.php b/src/Bridge/Doctrine/RepositoryFactory.php index 564325b..cd09ac4 100644 --- a/src/Bridge/Doctrine/RepositoryFactory.php +++ b/src/Bridge/Doctrine/RepositoryFactory.php @@ -6,6 +6,9 @@ use Trikoder\JsonApiBundle\Contracts\RepositoryInterface; use Trikoder\JsonApiBundle\Repository\RepositoryFactoryInterface; +/** + * @deprecated @see \Trikoder\JsonApiBundle\Repository\RepositoryFactoryInterface + */ class RepositoryFactory implements RepositoryFactoryInterface { /** @@ -15,8 +18,6 @@ class RepositoryFactory implements RepositoryFactoryInterface /** * RepositoryFactory constructor. - * - * @param EntityManager $entityManager */ public function __construct(EntityManager $entityManager) { @@ -24,9 +25,7 @@ public function __construct(EntityManager $entityManager) } /** - * @param string $modelClass * - * @return RepositoryInterface */ public function create(string $modelClass): RepositoryInterface { diff --git a/src/CompilerPass/SchemaAutoMapCompilerPass.php b/src/CompilerPass/SchemaAutoMapCompilerPass.php new file mode 100644 index 0000000..f6540b5 --- /dev/null +++ b/src/CompilerPass/SchemaAutoMapCompilerPass.php @@ -0,0 +1,147 @@ +getDefinition(SchemaClassMapProviderInterface::class); + + if (false === (null === $classMapProviderDefinition)) { + // we need configuration values to determine which folders should we scan for schema files + $configs = $container->getExtensionConfig('trikoder_json_api'); + $configuration = new Configuration(); + $processor = new Processor(); + $config = $processor->processConfiguration($configuration, $configs); + + $schemaDirScanPatterns = $config['schema_automap_scan_patterns']; + + $schemaFilenames = $this->getSchemaFilenames($schemaDirScanPatterns); + + foreach ($schemaFilenames as $schemaFilename) { + // we need fqn for reflection and service definition changes + $schemaFqn = $this->getClassFQNFromFileName($schemaFilename); + + if (false !== $schemaFqn) { + if ($this->isSchemaAutoMappable($schemaFqn)) { + $this->mapSchema($classMapProviderDefinition, $schemaFqn); + } + } + } + } + } + + /** + * @return string[] + */ + protected function getSchemaFilenames(array $schemaDirScanPatterns): array + { + if (empty($schemaDirScanPatterns)) { + return []; + } + + $schemaFiles = Finder::create()->files()->name('*.php')->ignoreUnreadableDirs()->in($schemaDirScanPatterns); + + $schemaFilenames = []; + foreach ($schemaFiles as $schemaFile) { + $schemaFilenames[] = $schemaFile->getRealPath(); + } + + return $schemaFilenames; + } + + private function mapSchema(Definition $classMapProviderDefinition, string $schemaFqn): Definition + { + $mappedClassnames = \call_user_func([$schemaFqn, 'getMappedClassnames']); + foreach ($mappedClassnames as $mappedClassname) { + $classMapProviderDefinition->addMethodCall('add', [ + $mappedClassname, + $schemaFqn, + ]); + } + + return $classMapProviderDefinition; + } + + private function isSchemaAutoMappable(string $schemaFqn): bool + { + if (empty($schemaFqn)) { + return false; + } + + if (false == class_exists($schemaFqn)) { + return false; + } + + $reflection = new \ReflectionClass($schemaFqn); + $isSchema = $reflection->isSubclassOf(AbstractSchema::class); + $isMappable = $reflection->implementsInterface(MappableInterface::class); + + // now let's figure out if this class is a schema class and implements methods required for auto mapping + return $isMappable && $isSchema; + } + + /** + * The body of this method was fully lifted (with one line change) from https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Routing/Loader/AnnotationFileLoader.php + * therefore any credit for it should go to authors of Symfony Routing component + */ + private function getClassFQNFromFileName($file): string + { + $class = false; + $namespace = false; + $tokens = token_get_all(file_get_contents($file)); + if (1 === \count($tokens) && T_INLINE_HTML === $tokens[0][0]) { + return false; + } + for ($i = 0; isset($tokens[$i]); ++$i) { + $token = $tokens[$i]; + if (!isset($token[1])) { + continue; + } + if (true === $class && T_STRING === $token[0]) { + return $namespace . '\\' . $token[1]; + } + if (true === $namespace && T_STRING === $token[0]) { + $namespace = $token[1]; + while (isset($tokens[++$i][1]) && \in_array($tokens[$i][0], [T_NS_SEPARATOR, T_STRING])) { + $namespace .= $tokens[$i][1]; + } + $token = $tokens[$i]; + } + if (T_CLASS === $token[0]) { + // Skip usage of ::class constant and anonymous classes + $skipClassToken = false; + for ($j = $i - 1; $j > 0; --$j) { + if (!isset($tokens[$j][1])) { + break; + } + if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) { + $skipClassToken = true; + break; + } elseif (!\in_array($tokens[$j][0], [T_WHITESPACE, T_DOC_COMMENT, T_COMMENT])) { + break; + } + } + if (!$skipClassToken) { + $class = true; + } + } + if (T_NAMESPACE === $token[0]) { + $namespace = true; + } + } + + return false; + } +} diff --git a/src/Config/Annotation/IndexConfig.php b/src/Config/Annotation/IndexConfig.php index 752ebb4..6b2194e 100644 --- a/src/Config/Annotation/IndexConfig.php +++ b/src/Config/Annotation/IndexConfig.php @@ -35,4 +35,9 @@ class IndexConfig * @return array|null */ public $allowedFields; + + /** + * @return array|null + */ + public $requiredRoles; } diff --git a/src/Config/ApiConfig.php b/src/Config/ApiConfig.php index b7a7a93..00c514f 100644 --- a/src/Config/ApiConfig.php +++ b/src/Config/ApiConfig.php @@ -48,12 +48,7 @@ class ApiConfig implements ApiConfigInterface /** * ApiConfig constructor. * - * @param string $modelClass * @param $repository - * @param array|null $fixedFiltering - * @param array|null $allowedIncludePaths - * @param RequestBodyDecoderInterface $requestBodyDecoder - * @param bool $allowExtraParams */ public function __construct( string $modelClass, diff --git a/src/Config/Config.php b/src/Config/Config.php index e10f348..ed35887 100644 --- a/src/Config/Config.php +++ b/src/Config/Config.php @@ -41,12 +41,6 @@ final class Config implements ConfigInterface /** * Config constructor. - * - * @param ApiConfigInterface $api - * @param CreateConfigInterface $create - * @param IndexConfigInterface $index - * @param UpdateConfigInterface $update - * @param DeleteConfigInterface $delete */ public function __construct( ApiConfigInterface $api, diff --git a/src/Config/CreateConfig.php b/src/Config/CreateConfig.php index a9f45e5..728d311 100644 --- a/src/Config/CreateConfig.php +++ b/src/Config/CreateConfig.php @@ -30,10 +30,6 @@ final class CreateConfig implements CreateConfigInterface /** * CreateConfig constructor. - * - * @param ModelFactoryInterface $factory - * @param array|null $allowedFields - * @param array|null $requiredRoles */ public function __construct( ModelFactoryInterface $factory, diff --git a/src/Config/DeleteConfig.php b/src/Config/DeleteConfig.php index 17aa29e..54d063e 100644 --- a/src/Config/DeleteConfig.php +++ b/src/Config/DeleteConfig.php @@ -16,8 +16,6 @@ final class DeleteConfig implements DeleteConfigInterface /** * DeleteConfig constructor. - * - * @param array|null $requiredRoles */ public function __construct( array $requiredRoles = null diff --git a/src/Config/IndexConfig.php b/src/Config/IndexConfig.php index 21387ec..a3cde11 100644 --- a/src/Config/IndexConfig.php +++ b/src/Config/IndexConfig.php @@ -34,27 +34,28 @@ final class IndexConfig implements IndexConfigInterface */ private $allowedFields; + /** + * @return array|null + */ + private $requiredRoles; + /** * IndexConfig constructor. - * - * @param array|null $allowedSortFields - * @param array|null $allowedFilteringParameters - * @param array $defaultSort - * @param array $defaultPagination - * @param array|null $allowedFields */ public function __construct( array $allowedSortFields = null, array $allowedFilteringParameters = null, array $defaultSort = [], array $defaultPagination = [], - array $allowedFields = null + array $allowedFields = null, + array $requiredRoles = null ) { $this->allowedSortFields = $allowedSortFields; $this->allowedFilteringParameters = $allowedFilteringParameters; $this->defaultSort = $defaultSort; $this->defaultPagination = $defaultPagination; $this->allowedFields = $allowedFields; + $this->requiredRoles = $requiredRoles; } /** @@ -96,4 +97,14 @@ public function getIndexAllowedFields() { return $this->allowedFields; } + + /** + * List of roles required to access action, [] for nothing is allowed, null for everything is allowed + * + * @return array|null + */ + public function getIndexRequiredRoles() + { + return $this->requiredRoles; + } } diff --git a/src/Config/Traits/LoadLazyPropertyTrait.php b/src/Config/Traits/LoadLazyPropertyTrait.php index 6d8b2dc..33b13d7 100644 --- a/src/Config/Traits/LoadLazyPropertyTrait.php +++ b/src/Config/Traits/LoadLazyPropertyTrait.php @@ -8,8 +8,6 @@ trait LoadLazyPropertyTrait { /** * @param $property - * - * @return mixed */ private function lazyLoadProperty($property) { diff --git a/src/Config/UpdateConfig.php b/src/Config/UpdateConfig.php index 28fbe4a..7549ff7 100644 --- a/src/Config/UpdateConfig.php +++ b/src/Config/UpdateConfig.php @@ -21,9 +21,6 @@ final class UpdateConfig implements UpdateConfigInterface /** * UpdateConfig constructor. - * - * @param array|null $allowedFields - * @param array|null $requiredRoles */ public function __construct( array $allowedFields = null, diff --git a/src/Contracts/Config/ConfigInterface.php b/src/Contracts/Config/ConfigInterface.php index c8f63c3..4d17e9d 100644 --- a/src/Contracts/Config/ConfigInterface.php +++ b/src/Contracts/Config/ConfigInterface.php @@ -26,4 +26,9 @@ public function getIndex(); * @return UpdateConfigInterface */ public function getUpdate(); + + /** + * @return DeleteConfigInterface + */ + public function getDelete(); } diff --git a/src/Contracts/Config/IndexConfigInterface.php b/src/Contracts/Config/IndexConfigInterface.php index 4c9b968..b46a106 100644 --- a/src/Contracts/Config/IndexConfigInterface.php +++ b/src/Contracts/Config/IndexConfigInterface.php @@ -48,4 +48,11 @@ public function getIndexDefaultPagination(); * @return array|null */ public function getIndexAllowedFields(); + + /** + * List of roles required to access action, [] for nothing is allowed, null for everything is allowed + * + * @return array|null + */ + public function getIndexRequiredRoles(); } diff --git a/src/Contracts/ErrorFactoryInterface.php b/src/Contracts/ErrorFactoryInterface.php index 8960fd9..e987022 100644 --- a/src/Contracts/ErrorFactoryInterface.php +++ b/src/Contracts/ErrorFactoryInterface.php @@ -11,16 +11,12 @@ interface ErrorFactoryInterface { /** - * @param string $error * - * @return ErrorInterface */ public function fromString(string $error): ErrorInterface; /** - * @param Exception $exception * - * @return ErrorInterface */ public function fromException(Exception $exception): ErrorInterface; } diff --git a/src/Contracts/ModelTools/ModelInputHandlerInterface.php b/src/Contracts/ModelTools/ModelInputHandlerInterface.php index 4e1a359..c1d3a5d 100644 --- a/src/Contracts/ModelTools/ModelInputHandlerInterface.php +++ b/src/Contracts/ModelTools/ModelInputHandlerInterface.php @@ -16,8 +16,6 @@ interface ModelInputHandlerInterface public function forModel($model); /** - * @param array $input - * * @return $this */ public function handle(array $input); diff --git a/src/Contracts/ResponseFactoryInterface.php b/src/Contracts/ResponseFactoryInterface.php index dd0314f..933ce0e 100644 --- a/src/Contracts/ResponseFactoryInterface.php +++ b/src/Contracts/ResponseFactoryInterface.php @@ -9,37 +9,35 @@ interface ResponseFactoryInterface { /** * @param string $data body of the response - * @param Response|null $response any response that should be used as base + * @param Response $response any response that should be used as base * * @return Response created response */ - public function createResponse(string $data, Response $response = null); + public function createResponse(string $data, Response $response = null): Response; /** - * @param string $data - * @param Response|null $response - * - * @return Response + * @param Response $response */ - public function createConflict(string $data, Response $response = null); - - public function createCreated($data, $location, Response $response = null); + public function createConflict(string $data, Response $response = null): Response; - public function createNoContent(Response $response = null); + /** + * @param string $location + * @param Response $response + */ + public function createCreated(string $data, string $location = null, Response $response = null): Response; /** - * @param string $data - * @param Response|null $response * - * @return Response */ - public function createError(string $data, Response $response = null); + public function createNoContent(Response $response = null): Response; /** - * @param Exception $exception - * @param Response|null $response - * - * @return Response + * @param Response $response + */ + public function createError(string $data, Response $response = null): Response; + + /** + * @param Response $response */ - public function createErrorFromException(Exception $exception, Response $response = null); + public function createErrorFromException(Exception $exception, Response $response = null): Response; } diff --git a/src/Controller/AbstractController.php b/src/Controller/AbstractController.php index 00a7099..daa9857 100644 --- a/src/Controller/AbstractController.php +++ b/src/Controller/AbstractController.php @@ -3,8 +3,7 @@ namespace Trikoder\JsonApiBundle\Controller; use Exception; -use Symfony\Bundle\FrameworkBundle\Controller\Controller; -use Trikoder\JsonApiBundle\Config\Config; +use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Trikoder\JsonApiBundle\Contracts\Config\ConfigInterface; use Trikoder\JsonApiBundle\Contracts\RepositoryInterface; use Trikoder\JsonApiBundle\Contracts\SchemaClassMapProviderInterface; @@ -28,7 +27,7 @@ abstract class AbstractController implements JsonApiEnabledInterface public function setSchemaClassMapProvider(SchemaClassMapProviderInterface $schemaClassMapProvider) { if (null !== $this->schemaClassMapProvider) { - throw new \RuntimeException("Controller already has it's schema map defined. This action would override current value. If this is acceptable for this controller, you should override this method to set the value."); + throw new \RuntimeException("Controller already has it's schema map defined. This action would override current value. If this is acceptable for this controller, you should override setSchemaClassMapProvider method to set the value."); } $this->schemaClassMapProvider = $schemaClassMapProvider; } @@ -58,7 +57,6 @@ public function getSchemaClassMapProvider() } /** - * @param ConfigInterface $config */ public function setJsonApiConfig(ConfigInterface $config) { @@ -72,12 +70,27 @@ public function setJsonApiConfig(ConfigInterface $config) /** * Returns config * - * @return ConfigInterface * * @throws Exception */ - public function getJsonApiConfig() + public function getJsonApiConfig(): ConfigInterface { return $this->config; } + + /** + * Evaluate if user has access to current action + * + * @throws AccessDeniedHttpException + */ + protected function evaluateRequiredRole($requiredRoles) + { + if (empty($requiredRoles)) { + return; + } + + if (!$this->getAuthorizationChecker()->isGranted($requiredRoles)) { + throw new AccessDeniedHttpException('Access Denied.'); + } + } } diff --git a/src/Controller/JsonApiEnabledInterface.php b/src/Controller/JsonApiEnabledInterface.php index 87d40fe..69f7f72 100644 --- a/src/Controller/JsonApiEnabledInterface.php +++ b/src/Controller/JsonApiEnabledInterface.php @@ -19,8 +19,10 @@ public function getSchemaClassMapProvider(); /** * Returns controllers config - * - * @return ConfigInterface */ - public function getJsonApiConfig(); + public function getJsonApiConfig(): ConfigInterface; + + /** + */ + public function setJsonApiConfig(ConfigInterface $config); } diff --git a/src/Controller/Traits/Actions/CreateTrait.php b/src/Controller/Traits/Actions/CreateTrait.php index d45816a..e43c24d 100644 --- a/src/Controller/Traits/Actions/CreateTrait.php +++ b/src/Controller/Traits/Actions/CreateTrait.php @@ -3,10 +3,9 @@ namespace Trikoder\JsonApiBundle\Controller\Traits\Actions; use RuntimeException; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Component\HttpFoundation\Request; -use Symfony\Component\HttpKernel\Exception\HttpException; +use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouterInterface; use Trikoder\JsonApiBundle\Contracts\Config\ConfigInterface; use Trikoder\JsonApiBundle\Contracts\ModelTools\ModelInputHandlerInterface; @@ -22,9 +21,7 @@ trait CreateTrait { /** - * @param ConfigInterface $config * @param object $emptyModel - * @param Request $request * * @return object * @@ -69,7 +66,6 @@ protected function handleCreateModelInputFromRequest(ConfigInterface $config, $e } /** - * @param ConfigInterface $config * @param object $model * * @throws ModelValidationException @@ -96,8 +92,6 @@ protected function validateCreatedModel(ConfigInterface $config, $model) } /** - * @param Request $request - * * @return object * * @throws ModelValidationException @@ -124,8 +118,6 @@ protected function createModelFromRequest(Request $request) } /** - * @param Request $request - * * @return \Symfony\Component\HttpFoundation\Response */ protected function createCreatedFromRequest(Request $request) @@ -148,12 +140,16 @@ protected function createCreatedFromRequest(Request $request) return $response; } - // return - // TODO change to injected + $showLocation = null; + if (true === method_exists($this, 'getRouter')) { + $showRouteName = $this->findShowRouteName(); + if (null !== $showRouteName) { + $showLocation = $this->getRouter()->generate($showRouteName, ['id' => $model->getId()], RouterInterface::ABSOLUTE_URL); + } + } $response = $responseFactory->createCreated( $encoder->encode($schemaProvider, $model), - $this->getRouter()->generate($this->findShowRouteName(), ['id' => $model->getId()], - RouterInterface::ABSOLUTE_URL) + $showLocation ); return $response; @@ -161,15 +157,20 @@ protected function createCreatedFromRequest(Request $request) /** * Find showAction route to this controller. + * Returns null if route cannot be found + * + * @internal * - * @return string + * @return string|null */ protected function findShowRouteName() { - // TODO change to injected + if (true !== method_exists($this, 'getRouter')) { + return null; + } /** @var Router $router */ $router = $this->getRouter(); - $controllerName = get_class($this) . '::showAction'; + $controllerName = \get_class($this) . '::showAction'; $showRouteName = null; /** @var Route $route */ foreach ($router->getRouteCollection() as $routeName => $route) { @@ -180,8 +181,7 @@ protected function findShowRouteName() } } if (null === $showRouteName) { - // TODO - update this to be more agnostic - throw new HttpException(500, 'Show route not found for ' . $controllerName); + return null; } return $showRouteName; diff --git a/src/Controller/Traits/Actions/DeleteTrait.php b/src/Controller/Traits/Actions/DeleteTrait.php index f4e6fa3..05c1cce 100644 --- a/src/Controller/Traits/Actions/DeleteTrait.php +++ b/src/Controller/Traits/Actions/DeleteTrait.php @@ -13,8 +13,6 @@ trait DeleteTrait { /** * @param $id - * - * @return null */ public function deleteModelById($id) { diff --git a/src/Controller/Traits/Actions/IndexTrait.php b/src/Controller/Traits/Actions/IndexTrait.php index a60c37e..d02fe19 100644 --- a/src/Controller/Traits/Actions/IndexTrait.php +++ b/src/Controller/Traits/Actions/IndexTrait.php @@ -15,8 +15,6 @@ trait IndexTrait { /** - * @param Request $request - * * @return array|null|\Trikoder\JsonApiBundle\Contracts\ObjectListCollectionInterface */ private function createCollectionFromRequest(Request $request) @@ -35,10 +33,6 @@ private function createCollectionFromRequest(Request $request) } /** - * @param ObjectListCollectionInterface $collection - * @param Request $request - * @param RouterInterface $router - * * @return DataResponse */ private function createPaginatedDataResponseFromCollectionAndRequest( @@ -57,10 +51,6 @@ private function createPaginatedDataResponseFromCollectionAndRequest( } /** - * @param RouterInterface $router - * @param Request $request - * @param array $overrideParams - * * @return string */ private function generateSelfUrlFromRequest(RouterInterface $router, Request $request, array $overrideParams) @@ -80,13 +70,13 @@ private function generateSelfUrlFromRequest(RouterInterface $router, Request $re $routeParams = array_merge($routeParams, $overrideParams); foreach ($routeParams as $paramName => &$paramValues) { - if (in_array($paramName, ['fields', 'filter']) && is_array($paramValues)) { + if (\in_array($paramName, ['fields', 'filter']) && \is_array($paramValues)) { foreach ($paramValues as $fieldName => $fieldProperties) { - if (is_array($fieldProperties)) { + if (\is_array($fieldProperties)) { $paramValues[$fieldName] = implode(',', $fieldProperties); } } - } elseif ('include' === $paramName && is_array($paramValues)) { + } elseif ('include' === $paramName && \is_array($paramValues)) { $routeParams[$paramName] = implode(',', $paramValues); } } @@ -99,7 +89,6 @@ private function generateSelfUrlFromRequest(RouterInterface $router, Request $re } /** - * @param ObjectListCollectionInterface $collection * @param array $paginationParams Array as returned from resolvePaginationArguments * @param callable $urlGenerator Callable that accepts override params as first argument * @@ -205,8 +194,6 @@ private function calculatePagesForPageSize(int $offset, int $limit, int $total): } /** - * @param null $arguments - * * @return array */ private function resolvePaginationArguments($arguments = null) @@ -221,7 +208,7 @@ private function resolvePaginationArguments($arguments = null) ], $config->getIndex()->getIndexDefaultPagination()); - if (true === is_array($arguments)) { + if (true === \is_array($arguments)) { // calculate limit first // page size strategy if (true === array_key_exists('size', $arguments)) { diff --git a/src/Controller/Traits/Actions/UpdateTrait.php b/src/Controller/Traits/Actions/UpdateTrait.php index 417ccd2..f47ffa3 100644 --- a/src/Controller/Traits/Actions/UpdateTrait.php +++ b/src/Controller/Traits/Actions/UpdateTrait.php @@ -19,7 +19,6 @@ trait UpdateTrait { /** - * @param Request $request * @param $id * * @return null|object @@ -34,12 +33,13 @@ protected function updateModelFromRequest(Request $request, $id) } /** - * @param Request $request * @param $id * * @return null|object * * @throws ModelValidationException + * + * @internal */ protected function updateModelFromRequestUsingId(Request $request, $id) { @@ -66,13 +66,13 @@ protected function updateModelFromRequestUsingId(Request $request, $id) } /** - * @param ConfigInterface $config * @param $model - * @param Request $request * * @return object * * @throws ModelValidationException + * + * @internal */ protected function handleUpdateModelInputFromRequest(ConfigInterface $config, $model, Request $request) { @@ -114,10 +114,11 @@ protected function handleUpdateModelInputFromRequest(ConfigInterface $config, $m } /** - * @param ConfigInterface $config * @param $model * * @throws ModelValidationException + * + * @internal */ protected function validateUpdatedModel(ConfigInterface $config, $model) { @@ -139,12 +140,13 @@ protected function validateUpdatedModel(ConfigInterface $config, $model) } /** - * @param Request $request * @param $model * * @return object * * @throws ModelValidationException + * + * @internal */ protected function updateModelFromRequestUsingModel(Request $request, $model) { @@ -170,7 +172,6 @@ protected function updateModelFromRequestUsingModel(Request $request, $model) } /** - * @param Request $request * @param $id * * @return null|object|\Symfony\Component\HttpFoundation\Response @@ -184,7 +185,7 @@ protected function updateRequestFromRequest(Request $request, $id) $encoder = $this->getJsonApiEncoder(); try { - $model = $this->updateModelFromRequest($request, $id); + $model = $this->updateModelFromRequestUsingId($request, $id); } catch (ModelValidationException $modelValidationException) { // TODO this should return conflict response (similar to DataResponse) or HttConflictException $response = $responseFactory->createConflict($encoder->encodeErrors($modelValidationException->getViolations())); diff --git a/src/Controller/Traits/CreateActionTrait.php b/src/Controller/Traits/CreateActionTrait.php index 41e2dc5..3b003ac 100644 --- a/src/Controller/Traits/CreateActionTrait.php +++ b/src/Controller/Traits/CreateActionTrait.php @@ -2,9 +2,8 @@ namespace Trikoder\JsonApiBundle\Controller\Traits; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; /** * Class CreateActionTrait @@ -14,15 +13,14 @@ trait CreateActionTrait use Actions\CreateTrait; /** - * @param Request $request - * - * @Route("{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}) - * @Method("POST") + * @Route("{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}, methods={"POST"}) * * @return \Symfony\Component\HttpFoundation\Response */ public function createAction(Request $request) { + $this->evaluateRequiredRole($this->getJsonApiConfig()->getCreate()->getCreateRequiredRoles()); + return $this->createCreatedFromRequest($request); } } diff --git a/src/Controller/Traits/DeleteActionTrait.php b/src/Controller/Traits/DeleteActionTrait.php index 5ae8290..4e9b441 100644 --- a/src/Controller/Traits/DeleteActionTrait.php +++ b/src/Controller/Traits/DeleteActionTrait.php @@ -2,9 +2,8 @@ namespace Trikoder\JsonApiBundle\Controller\Traits; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; /** * Class DeleteActionTrait @@ -14,15 +13,12 @@ trait DeleteActionTrait use Actions\DeleteTrait; /** - * @param Request $request - * - * @Route("/{id}{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}) - * @Method({"DELETE"}) - * - * @return null + * @Route("/{id}{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}, methods={"DELETE"}) */ public function deleteAction(Request $request, $id) { + $this->evaluateRequiredRole($this->getJsonApiConfig()->getDelete()->getDeleteRequiredRoles()); + $this->deleteModelById($id); return null; diff --git a/src/Controller/Traits/IndexActionTrait.php b/src/Controller/Traits/IndexActionTrait.php index 8224b76..ec478cf 100644 --- a/src/Controller/Traits/IndexActionTrait.php +++ b/src/Controller/Traits/IndexActionTrait.php @@ -2,9 +2,8 @@ namespace Trikoder\JsonApiBundle\Controller\Traits; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; /** * Class IndexActionTrait @@ -14,15 +13,14 @@ trait IndexActionTrait use Actions\IndexTrait; /** - * @param Request $request - * - * @Route("{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}) - * @Method("GET") + * @Route("{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}, methods={"GET"}) * * @return array|null|\Trikoder\JsonApiBundle\Contracts\ObjectListCollectionInterface */ public function indexAction(Request $request) { + $this->evaluateRequiredRole($this->getJsonApiConfig()->getIndex()->getIndexRequiredRoles()); + return $this->createCollectionFromRequest($request); } } diff --git a/src/Controller/Traits/PaginatedIndexActionTrait.php b/src/Controller/Traits/PaginatedIndexActionTrait.php index ce9e010..b9c8728 100644 --- a/src/Controller/Traits/PaginatedIndexActionTrait.php +++ b/src/Controller/Traits/PaginatedIndexActionTrait.php @@ -2,9 +2,8 @@ namespace Trikoder\JsonApiBundle\Controller\Traits; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; use Trikoder\JsonApiBundle\Controller\Traits\Actions\IndexTrait; use Trikoder\JsonApiBundle\Response\DataResponse; @@ -16,15 +15,14 @@ trait PaginatedIndexActionTrait use IndexTrait; /** - * @param Request $request - * - * @Route("{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}) - * @Method("GET") + * @Route("{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}, methods={"GET"}) * * @return DataResponse */ public function indexAction(Request $request) { + $this->evaluateRequiredRole($this->getJsonApiConfig()->getIndex()->getIndexRequiredRoles()); + // TODO change to injected $router = $this->getRouter(); diff --git a/src/Controller/Traits/Polyfill/SymfonyAutowiredServicesTrait.php b/src/Controller/Traits/Polyfill/SymfonyAutowiredServicesTrait.php index 18397e3..4902e23 100644 --- a/src/Controller/Traits/Polyfill/SymfonyAutowiredServicesTrait.php +++ b/src/Controller/Traits/Polyfill/SymfonyAutowiredServicesTrait.php @@ -3,6 +3,7 @@ namespace Trikoder\JsonApiBundle\Controller\Traits\Polyfill; use Symfony\Component\Routing\RouterInterface; +use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; use Trikoder\JsonApiBundle\Contracts\ResponseFactoryInterface; use Trikoder\JsonApiBundle\Services\ModelInput\ModelToolsFactory; use Trikoder\JsonApiBundle\Services\Neomerx\EncoderService; @@ -17,8 +18,9 @@ trait SymfonyAutowiredServicesTrait protected $jsonapiEncoder; + protected $authorizationChecker; + /** - * @param RouterInterface $router * @required */ public function setRouter(RouterInterface $router) @@ -27,7 +29,7 @@ public function setRouter(RouterInterface $router) } /** - * @return mixed + * @return RouterInterface */ public function getRouter() { @@ -72,4 +74,17 @@ public function getJsonApiEncoder() { return $this->jsonapiEncoder; } + + /** + * @required + */ + public function setAuthorizationChecker(AuthorizationCheckerInterface $authorizationChecker) + { + $this->authorizationChecker = $authorizationChecker; + } + + public function getAuthorizationChecker(): AuthorizationCheckerInterface + { + return $this->authorizationChecker; + } } diff --git a/src/Controller/Traits/ShowActionTrait.php b/src/Controller/Traits/ShowActionTrait.php index 5ed0654..7b5f717 100644 --- a/src/Controller/Traits/ShowActionTrait.php +++ b/src/Controller/Traits/ShowActionTrait.php @@ -2,9 +2,8 @@ namespace Trikoder\JsonApiBundle\Controller\Traits; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; /** * Class ShowActionTrait @@ -14,10 +13,7 @@ trait ShowActionTrait use Actions\ShowTrait; /** - * @param Request $request - * - * @Route("/{id}{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}) - * @Method("GET") + * @Route("/{id}{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}, methods={"GET"}) * * @return null|object */ diff --git a/src/Controller/Traits/UpdateActionTrait.php b/src/Controller/Traits/UpdateActionTrait.php index 697c4a2..6828983 100644 --- a/src/Controller/Traits/UpdateActionTrait.php +++ b/src/Controller/Traits/UpdateActionTrait.php @@ -2,9 +2,8 @@ namespace Trikoder\JsonApiBundle\Controller\Traits; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; /** * Class UpdateActionTrait @@ -14,15 +13,14 @@ trait UpdateActionTrait use Actions\UpdateTrait; /** - * @param Request $request - * - * @Route("/{id}{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}) - * @Method({"PATCH", "PUT"}) + * @Route("/{id}{trailingSlash}", requirements={"trailingSlash": "[/]{0,1}"}, defaults={"trailingSlash": ""}, methods={"PATCH", "PUT", "POST"}) * * @return object */ public function updateAction(Request $request, $id) { + $this->evaluateRequiredRole($this->getJsonApiConfig()->getUpdate()->getUpdateRequiredRoles()); + return $this->updateRequestFromRequest($request, $id); } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 46e4095..643bc14 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -41,6 +41,7 @@ public function getConfigTreeBuilder() ->variableNode('default_sort')->defaultValue([])->end() ->variableNode('default_pagination')->defaultValue([])->end() ->variableNode('allowed_fields')->defaultNull()->end() + ->variableNode('required_roles')->defaultNull()->end() ->end() ->addDefaultsIfNotSet() ->end() @@ -65,6 +66,9 @@ public function getConfigTreeBuilder() ->end() ->addDefaultsIfNotSet() ->end() + ->arrayNode('schema_automap_scan_patterns') + ->scalarPrototype()->end()->defaultValue([]) + ->end() ->end(); $rootNode->addDefaultsIfNotSet(); diff --git a/src/DependencyInjection/TrikoderJsonApiExtension.php b/src/DependencyInjection/TrikoderJsonApiExtension.php index c105622..d67e199 100644 --- a/src/DependencyInjection/TrikoderJsonApiExtension.php +++ b/src/DependencyInjection/TrikoderJsonApiExtension.php @@ -17,9 +17,6 @@ class TrikoderJsonApiExtension extends ConfigurableExtension { /** * Configures the passed container according to the merged configuration. - * - * @param array $mergedConfig - * @param ContainerBuilder $container */ protected function loadInternal(array $mergedConfig, ContainerBuilder $container) { diff --git a/src/Listener/ControllerConfigListener.php b/src/Listener/ControllerConfigListener.php index db76692..1301154 100644 --- a/src/Listener/ControllerConfigListener.php +++ b/src/Listener/ControllerConfigListener.php @@ -32,7 +32,6 @@ public function __construct(Reader $annotationReader, ConfigBuilder $configBuild } /** - * @param FilterControllerEvent $event */ public function onKernelController(FilterControllerEvent $event) { diff --git a/src/Listener/JsonApiEnabledControllerDetectorTrait.php b/src/Listener/JsonApiEnabledControllerDetectorTrait.php index f97c914..e682683 100644 --- a/src/Listener/JsonApiEnabledControllerDetectorTrait.php +++ b/src/Listener/JsonApiEnabledControllerDetectorTrait.php @@ -15,13 +15,13 @@ trait JsonApiEnabledControllerDetectorTrait protected function isJsonApiEnabledController($controller) { // we cannot support Closure as we cannot look inside it safely - if (true === is_callable($controller) && false === ($controller instanceof Closure)) { + if (true === \is_callable($controller) && false === ($controller instanceof Closure)) { if ($controller[0] instanceof JsonApiEnabledInterface) { return true; } else { return false; } - } elseif (true === is_object($controller)) { + } elseif (true === \is_object($controller)) { if ($controller instanceof JsonApiEnabledInterface) { return true; } else { @@ -39,11 +39,11 @@ protected function isJsonApiEnabledController($controller) */ protected function resolveControllerFromEventController($eventController) { - if (true === is_callable($eventController) && false === ($eventController instanceof Closure)) { + if (true === \is_callable($eventController) && false === ($eventController instanceof Closure)) { return $eventController[0]; - } elseif (true === is_callable($eventController) && true === ($eventController instanceof Closure)) { + } elseif (true === \is_callable($eventController) && true === ($eventController instanceof Closure)) { return null; - } elseif (true === is_object($eventController)) { + } elseif (true === \is_object($eventController)) { return $eventController; } else { return null; diff --git a/src/Listener/KernelListener.php b/src/Listener/KernelListener.php index 22b7051..f888483 100644 --- a/src/Listener/KernelListener.php +++ b/src/Listener/KernelListener.php @@ -76,12 +76,6 @@ class KernelListener /** * KernelListener constructor. - * - * @param FactoryService $neomerxFactoryService - * @param RequestBodyDecoderInterface $requestBodyDecoder - * @param ResponseFactoryInterface $responseFactory - * @param EncoderService $encoderService - * @param Logger $logger */ public function __construct( FactoryService $neomerxFactoryService, @@ -100,8 +94,6 @@ public function __construct( /** * Setter of schema class map provider - * - * @param SchemaClassMapProviderInterface $schemaClassMapProvider */ protected function setSchemaClassMapProvider(SchemaClassMapProviderInterface $schemaClassMapProvider) { @@ -110,8 +102,6 @@ protected function setSchemaClassMapProvider(SchemaClassMapProviderInterface $sc /** * Check if controller is json api and also get it's schema map - * - * @param FilterControllerEvent $event */ public function onKernelController(FilterControllerEvent $event) { @@ -129,7 +119,6 @@ public function onKernelController(FilterControllerEvent $event) /** * Transforms controller result to valid json api response if possible * - * @param GetResponseForControllerResultEvent $event * * @throws \Exception */ @@ -157,8 +146,6 @@ public function onKernelView(GetResponseForControllerResultEvent $event) /** * @param $controllerResult - * @param array $resultMeta - * @param array $resultLinks * * @return Response * @@ -211,7 +198,7 @@ private function getResponseFromControllerResult($controllerResult, array $resul break; // if you got array or object, try to encode it and package in response - case is_array($controllerResult) || is_object($controllerResult): + case \is_array($controllerResult) || \is_object($controllerResult): $response = $this->responseFactory->createResponse($this->encode($controllerResult, $resultMeta, $resultLinks)); @@ -238,8 +225,6 @@ private function getResponseFromControllerResult($controllerResult, array $resul /** * @param array|Iterator|null|object|string $data - * @param array|null $meta - * @param array $links * * @return string */ @@ -256,8 +241,6 @@ protected function encode($data = '', array $meta = null, array $links = []) /** * Event is called when controller arguments are being resolved, here we can replace original request with translated request - * - * @param FilterControllerArgumentsEvent $event */ public function onKernelControllerArguments(FilterControllerArgumentsEvent $event) { @@ -293,7 +276,6 @@ public function onKernelResponse(FilterResponseEvent $event) } /** - * @param GetResponseForExceptionEvent $event */ public function onKernelException(GetResponseForExceptionEvent $event) { diff --git a/src/Model/Factory/SimpleModelFactory.php b/src/Model/Factory/SimpleModelFactory.php index 3a41413..8bcdb7a 100644 --- a/src/Model/Factory/SimpleModelFactory.php +++ b/src/Model/Factory/SimpleModelFactory.php @@ -10,8 +10,6 @@ class SimpleModelFactory implements ModelFactoryInterface { /** - * @param string $modelClass - * * @return object */ public function create(string $modelClass) diff --git a/src/Model/ModelFactoryInterface.php b/src/Model/ModelFactoryInterface.php index dfc73bd..8a418b3 100644 --- a/src/Model/ModelFactoryInterface.php +++ b/src/Model/ModelFactoryInterface.php @@ -8,8 +8,6 @@ interface ModelFactoryInterface { /** - * @param string $modelClass - * * @return object */ public function create(string $modelClass); diff --git a/src/Model/ModelFactoryResolver.php b/src/Model/ModelFactoryResolver.php index 406ed65..806902e 100644 --- a/src/Model/ModelFactoryResolver.php +++ b/src/Model/ModelFactoryResolver.php @@ -20,10 +20,6 @@ class ModelFactoryResolver implements ModelFactoryResolverInterface private $registry = []; /** - * @param string $modelClass - * - * @return ModelFactoryInterface - * * @throws RuntimeException */ public function resolve(string $modelClass): ModelFactoryInterface @@ -39,8 +35,6 @@ public function resolve(string $modelClass): ModelFactoryInterface } /** - * @param ModelFactoryInterface $factory - * @param string|null $modelClass */ public function registerFactory(ModelFactoryInterface $factory, string $modelClass = null) { diff --git a/src/Model/ModelFactoryResolverInterface.php b/src/Model/ModelFactoryResolverInterface.php index 08f6afd..77548b6 100644 --- a/src/Model/ModelFactoryResolverInterface.php +++ b/src/Model/ModelFactoryResolverInterface.php @@ -8,15 +8,11 @@ interface ModelFactoryResolverInterface { /** - * @param string $modelClass * - * @return ModelFactoryInterface */ public function resolve(string $modelClass): ModelFactoryInterface; /** - * @param ModelFactoryInterface $factory - * @param string|null $modelClass */ public function registerFactory(ModelFactoryInterface $factory, string $modelClass = null); } diff --git a/src/Repository/RepositoryFactoryInterface.php b/src/Repository/RepositoryFactoryInterface.php index c7874cb..9d28e35 100644 --- a/src/Repository/RepositoryFactoryInterface.php +++ b/src/Repository/RepositoryFactoryInterface.php @@ -5,14 +5,12 @@ use Trikoder\JsonApiBundle\Contracts\RepositoryInterface; /** - * Interface RepositoryFactoryInterface + * @deprecated in favour of DIC factory option, use https://symfony.com/doc/current/service_container/factories.html */ interface RepositoryFactoryInterface { /** - * @param string $modelClass * - * @return RepositoryInterface */ public function create(string $modelClass): RepositoryInterface; } diff --git a/src/Repository/RepositoryResolver.php b/src/Repository/RepositoryResolver.php index d115162..2d65401 100644 --- a/src/Repository/RepositoryResolver.php +++ b/src/Repository/RepositoryResolver.php @@ -26,9 +26,7 @@ class RepositoryResolver implements RepositoryResolverInterface private $repositoryRegistry = []; /** - * @param string $modelClass * - * @return RepositoryInterface */ public function resolve(string $modelClass): RepositoryInterface { @@ -58,8 +56,6 @@ public function resolve(string $modelClass): RepositoryInterface } /** - * @param RepositoryInterface $repository - * @param string $modelClass */ public function registerRepository(RepositoryInterface $repository, string $modelClass) { @@ -71,8 +67,7 @@ public function registerRepository(RepositoryInterface $repository, string $mode } /** - * @param RepositoryFactoryInterface $factory - * @param string|null $modelClass + * @deprecated @see \Trikoder\JsonApiBundle\Repository\RepositoryFactoryInterface */ public function registerFactory(RepositoryFactoryInterface $factory, string $modelClass = null) { diff --git a/src/Repository/RepositoryResolverInterface.php b/src/Repository/RepositoryResolverInterface.php index 068878f..68bb6e4 100644 --- a/src/Repository/RepositoryResolverInterface.php +++ b/src/Repository/RepositoryResolverInterface.php @@ -10,21 +10,16 @@ interface RepositoryResolverInterface { /** - * @param string $modelClass * - * @return RepositoryInterface */ public function resolve(string $modelClass): RepositoryInterface; /** - * @param RepositoryFactoryInterface $factory - * @param string|null $modelClass + * @deprecated @see \Trikoder\JsonApiBundle\Repository\RepositoryFactoryInterface */ public function registerFactory(RepositoryFactoryInterface $factory, string $modelClass = null); /** - * @param RepositoryInterface $repository - * @param string $modelClass */ public function registerRepository(RepositoryInterface $repository, string $modelClass); } diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index fb593fd..6d161e1 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -24,11 +24,10 @@ services: - "@logger" tags: - { name: kernel.event_listener, event: kernel.controller, priority: 16 } - - { name: kernel.event_listener, event: kernel.controller_arguments } + - { name: kernel.event_listener, event: kernel.controller_arguments, priority: -10 } - { name: kernel.event_listener, event: kernel.view } - { name: kernel.event_listener, event: kernel.response } - { name: kernel.event_listener, event: kernel.exception } - lazy: true trikoder.jsonapi.controller_config_listener: class: "%trikoder.jsonapi.controller_config_listener.class%" @@ -37,26 +36,21 @@ services: - "@trikoder.jsonapi.config_builder" tags: - { name: kernel.event_listener, event: kernel.controller, priority: 8 } - lazy: true Trikoder\JsonApiBundle\Contracts\SchemaClassMapProviderInterface: class: "%trikoder.jsonapi.schema_class_map_provider.class%" - lazy: true trikoder.jsonapi.schema_class_map_provider: '@Trikoder\JsonApiBundle\Contracts\SchemaClassMapProviderInterface' Trikoder\JsonApiBundle\Services\Neomerx\ServiceContainer: arguments: ["@service_container"] - lazy: true trikoder.jsonapi.factory: class: "%trikoder.jsonapi.factory.class%" arguments: ['@Trikoder\JsonApiBundle\Services\Neomerx\ServiceContainer', "@logger"] - lazy: true trikoder.jsonapi.request_body_decoder: class: "%trikoder.jsonapi.request_body_decoder.class%" - lazy: true public: true @@ -65,7 +59,6 @@ services: arguments: - "@trikoder.jsonapi.encoder" - "@trikoder.jsonapi.error_factory" - lazy: true public: true trikoder.jsonapi.response_factory: '@Trikoder\JsonApiBundle\Contracts\ResponseFactoryInterface' @@ -73,7 +66,6 @@ services: Trikoder\JsonApiBundle\Services\Neomerx\EncoderService: class: "%trikoder.jsonapi.encoder.class%" arguments: ["@trikoder.jsonapi.factory"] - lazy: true public: true trikoder.jsonapi.encoder: '@Trikoder\JsonApiBundle\Services\Neomerx\EncoderService' @@ -81,19 +73,16 @@ services: Trikoder\JsonApiBundle\Services\ModelInput\ModelToolsFactory: class: "%trikoder.jsonapi.model_tools_factory.class%" arguments: ["@form.factory", "@validator", "@doctrine.orm.entity_manager"] - lazy: true public: true trikoder.jsonapi.model_tools_factory: '@Trikoder\JsonApiBundle\Services\ModelInput\ModelToolsFactory' trikoder.jsonapi.error_factory: class: "%trikoder.jsonapi.error_factory.class%" - lazy: true trikoder.jsonapi.config_builder: class: "%trikoder.jsonapi.config_builder.class%" arguments: [[], "@service_container"] - lazy: true trikoder.jsonapi.doctrine_repository_factory: class: "%trikoder.jsonapi.doctrine_repository_factory.class%" diff --git a/src/Resources/doc/configuration/configuration.md b/src/Resources/doc/configuration/configuration.md index e4da11c..cb1b40e 100644 --- a/src/Resources/doc/configuration/configuration.md +++ b/src/Resources/doc/configuration/configuration.md @@ -14,7 +14,6 @@ Model class that this controller is responsible of. Defaults to `\stdClass`. ### repository Can be name of the service or reference to instance that implements any of these interfaces: - `\Trikoder\JsonApiBundle\Contracts\RepositoryInterface` -- `\Trikoder\JsonApiBundle\Repository\RepositoryFactoryInterface` - `\Trikoder\JsonApiBundle\Repository\RepositoryResolverInterface` Defaults to built-in service `trikoder.jsonapi.doctrine_repository_factory` that is by default `Trikoder\JsonApiBundle\Bridge\Doctrine\RepositoryFactory` diff --git a/src/Resources/doc/configuration/examples/example.yml b/src/Resources/doc/configuration/examples/example.yml index ec862f4..c7650a3 100644 --- a/src/Resources/doc/configuration/examples/example.yml +++ b/src/Resources/doc/configuration/examples/example.yml @@ -11,6 +11,7 @@ trikoder_json_api: default_sort: [] default_pagination: [] allowed_fields: null + required_roles: null create: factory: "trikoder.jsonapi.simple_model_factory" allowed_fields: null @@ -20,3 +21,4 @@ trikoder_json_api: required_roles: null delete: required_roles: null + schema_automap_scan_patterns: ['src/*Schema/'] diff --git a/src/Resources/doc/configuration/examples/exampleAnnotation.php b/src/Resources/doc/configuration/examples/exampleAnnotation.php index 1ea43cc..2c1ac9a 100644 --- a/src/Resources/doc/configuration/examples/exampleAnnotation.php +++ b/src/Resources/doc/configuration/examples/exampleAnnotation.php @@ -15,7 +15,8 @@ * allowedFilteringParameters=null, * defaultSort={}, * defaultPagination={}, - * allowedFields=null + * allowedFields=null, + * requiredRoles=null * ), * create=@JsonApiConfig\CreateConfig( * factory="trikoder.jsonapi.simple_model_factory", diff --git a/src/Resources/doc/configuration/services.md b/src/Resources/doc/configuration/services.md index 1630a0d..853187f 100644 --- a/src/Resources/doc/configuration/services.md +++ b/src/Resources/doc/configuration/services.md @@ -2,3 +2,5 @@ Any of the services defined by bundle can be redefined to use other class implementation. Configuration can be done by redefining class parameter. + +For the list of classes that are defined as parameters, look into [services.yml](../../config/services.yml) diff --git a/src/Resources/doc/getting_started/examples/ExampleController.php b/src/Resources/doc/getting_started/examples/ExampleController.php index 2008275..f0e64c4 100644 --- a/src/Resources/doc/getting_started/examples/ExampleController.php +++ b/src/Resources/doc/getting_started/examples/ExampleController.php @@ -2,7 +2,7 @@ namespace Trikoder\JsonApiBundle\Tests\Controller\Api; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; +use Symfony\Component\Routing\Annotation\Route; use Trikoder\JsonApiBundle\Config\Annotation as JsonApiConfig; use Trikoder\JsonApiBundle\Controller\AbstractController as JsonApiController; use Trikoder\JsonApiBundle\Controller\Traits\CreateActionTrait; diff --git a/src/Resources/doc/getting_started/examples/ExampleServiceSchema.php b/src/Resources/doc/getting_started/examples/ExampleServiceSchema.php index 9c93864..79a7b00 100644 --- a/src/Resources/doc/getting_started/examples/ExampleServiceSchema.php +++ b/src/Resources/doc/getting_started/examples/ExampleServiceSchema.php @@ -20,9 +20,6 @@ class ExampleServiceSchema extends AbstractSchema /** * ExampleServiceSchema constructor. - * - * @param SchemaFactoryInterface $factory - * @param RouterInterface $router */ public function __construct(SchemaFactoryInterface $factory, RouterInterface $router) { @@ -51,9 +48,14 @@ public function getId($resource) */ public function getAttributes($resource) { + $router = $this->router; + return [ 'attribute' => $resource->getValue(), - 'url' => $this->router->generate('route_to_something_great'), + // any attribute or value can be closure that is evaluated on first ready. lazy af + 'url' => function () use ($router) { + return $router->generate('route_to_something_great'); + }, ]; } } diff --git a/src/Resources/doc/getting_started/examples/ExampleServiceSchemaClassMap.php b/src/Resources/doc/getting_started/examples/ExampleServiceSchemaClassMap.php index c13fafb..cdec999 100644 --- a/src/Resources/doc/getting_started/examples/ExampleServiceSchemaClassMap.php +++ b/src/Resources/doc/getting_started/examples/ExampleServiceSchemaClassMap.php @@ -15,6 +15,6 @@ class ExampleServiceSchemaClassMap extends AbstractSchemaClassMapService */ public function __construct() { - $this->add('\Example', ExampleServiceSchema::class); + $this->add(Example::class, ExampleServiceSchema::class); } } diff --git a/src/Resources/doc/getting_started/quick.md b/src/Resources/doc/getting_started/quick.md index 37b370b..2378dae 100644 --- a/src/Resources/doc/getting_started/quick.md +++ b/src/Resources/doc/getting_started/quick.md @@ -3,7 +3,7 @@ Quick start guide is the fastest way to get started with jsonapi. There are some prerequisites to comply to be as fast as possible. -## Assumtions ! +## Assumptions ! - models are defined as doctrine entities @@ -32,8 +32,11 @@ If you followed steps from first time setup you can register it by service call `- [add, ['\stdClass', '\Trikoder\JsonApiBundle\Schema\Builtin\StdClassSchema']]` To find out more methods and usages on schema class map see (schema_class_map.md) +Alternatively (and especially if you like your life to be as easy as possible), use [schema automapping feature](schema_automapping.md) ### 3. Create controller -Create your api controller that extends `Trikoder\JsonApiBundle\Controller\AbstractController`. +Create your API controller that extends `\Trikoder\JsonApiBundle\Controller\AbstractController`. +Your controller will be automatically registered as a service as the +`\Trikoder\JsonApiBundle\Controller\AbstractController` is already tagged with the `controller.service_arguments` tag. Minimal non-symfony configuration is to define model class this controller serves eg.: ```php use Trikoder\JsonApiBundle\Config\Annotation as JsonApiConfig; diff --git a/src/Resources/doc/getting_started/schema_automapping.md b/src/Resources/doc/getting_started/schema_automapping.md new file mode 100644 index 0000000..0f3ea77 --- /dev/null +++ b/src/Resources/doc/getting_started/schema_automapping.md @@ -0,0 +1,71 @@ +# Schema automapping + +## General mechanism +During a custom compiler pass, all directories matching the patterns defined in config under schema_automap_scan_patterns key (default is src/*Schema/) are scanned for php files. + +All those files are then additionally checked to see if they actually are schema classes and if they are, if they implement the Trikoder\JsonApiBundle\Schema\MappableInterface interface. + +All files for which both is true are then automapped by automatically modifying the classmap service definition by adding a call to it's "add" method, where the actual class that the schema is mapped to is determined by the return value of the static method getMappedClassnames of the schema class (required by the Trikoder\JsonApiBundle\Schema\MappableInterface interface) + +## How to automap my schemas? +Extremely simple, just do following: + +1. make sure that your schemas implement Trikoder\JsonApiBundle\Schema\MappableInterface +2. make sure that your schema is located in one of the directories which match the pattern defined in your config.yml under schema_automap_scan_patterns key (default is src/*Schema/) +3. clear your symfony cache (cache:clear) + +bam. you're done. your schemas are now all mapped into a classmap and are ready to use. + +And you didn't have to soil your fingers by editing any yml files + +## Automappable schema example +````php +getId(); + } + + /** + * Get resource attributes. + * + * @param object $resource + * + * @return array + */ + public function getAttributes($resource) + { + return [ + 'someAttribute' => $resource->getSomeAttribute(), + ]; + } +} + +```` + +## Known side-effects +None. \ No newline at end of file diff --git a/src/Resources/doc/getting_started/schema_class_map.md b/src/Resources/doc/getting_started/schema_class_map.md index ef0dda2..8b04a1e 100644 --- a/src/Resources/doc/getting_started/schema_class_map.md +++ b/src/Resources/doc/getting_started/schema_class_map.md @@ -6,6 +6,15 @@ It must implement `\Trikoder\JsonApiBundle\Contracts\SchemaClassMapProviderInter The schema class map can be provided and used in several ways. ## Defining + +Recommended way is to define as service and register all schemas in yml: +```yaml +Trikoder\JsonApiBundle\Contracts\SchemaClassMapProviderInterface: + class: "%trikoder.jsonapi.schema_class_map_provider.class%" + calls: + - [add, ['\stdClass', '\Trikoder\JsonApiBundle\Schema\Builtin\StdClassSchema']] +``` + ### 1. Method in controller Api engine calls controller method `getSchemaClassMapProvider` to get map provider. In your code, you can override this method to return your implementation of SchemaClassMapProviderInterface. @@ -25,4 +34,5 @@ In your services configuration you can redefine `Trikoder\JsonApiBundle\Contract Add method receives two parameters: - $class - FQN of class that provided schema should be used for - $schema - FQN or class of closure that returns instance of the schema that should be used for the model. -Closure must accept two arguments `SchemaFactoryInterface $factory, ContainerInterface $serviceContainer` (!IMORTANT - second argument for Closure - service container is deprecated and will be removed in the future in favour of autowiring). +Using Closure is not recommended as any dependancies can be autowired and injected by the bundle. See [schemas](schemas.md) for more details. +Closure must accept one argument: `SchemaFactoryInterface $factory` diff --git a/src/Resources/doc/getting_started/schemas.md b/src/Resources/doc/getting_started/schemas.md index 342489c..cc59f93 100644 --- a/src/Resources/doc/getting_started/schemas.md +++ b/src/Resources/doc/getting_started/schemas.md @@ -10,3 +10,17 @@ For simple schema see [simple schema example](examples/ExampleSimpleSchema.php). Schema can can have outside dependancies from service container. Any dependencies will be autowired using service container. For example see [schema with services example](examples/ExampleServiceSchema.php) See example of [schema class map](examples/ExampleServiceSchemaClassMap.php) + +As by default, all Symfony services are private, any non-public services used by your schemas need to be whitelisted +by registering them in bundles service container: +```yaml +Trikoder\JsonApiBundle\Services\Neomerx\ServiceContainer: + calls: + - method: set + arguments: + - 'Symfony\Component\Routing\RouterInterface' + - '@router' +``` + +The first argument in the set call is the ID of the service as hinted in requirement. +Second argument is the service reference. diff --git a/src/Response/AbstractResponse.php b/src/Response/AbstractResponse.php index 4c92137..54f160d 100644 --- a/src/Response/AbstractResponse.php +++ b/src/Response/AbstractResponse.php @@ -15,9 +15,6 @@ abstract class AbstractResponse /** * AbstractResponse constructor. - * - * @param array $meta - * @param array $links */ public function __construct(array $meta = [], array $links = []) { @@ -28,8 +25,6 @@ public function __construct(array $meta = [], array $links = []) /** * @param $meta * @param $value - * - * @return self */ public function addMeta($meta, $value): self { @@ -39,7 +34,6 @@ public function addMeta($meta, $value): self } /** - * @return array */ public function getMeta(): array { @@ -47,7 +41,6 @@ public function getMeta(): array } /** - * @return array */ public function getLinks(): array { @@ -57,8 +50,6 @@ public function getLinks(): array /** * @param $link * @param $value - * - * @return self */ public function addLink($link, $value): self { diff --git a/src/Response/DataResponse.php b/src/Response/DataResponse.php index 027d5ea..00f108d 100644 --- a/src/Response/DataResponse.php +++ b/src/Response/DataResponse.php @@ -14,8 +14,6 @@ class DataResponse extends AbstractResponse * Response constructor. * * @param $data - * @param array $meta - * @param array $links */ public function __construct($data, array $meta = [], array $links = []) { @@ -24,7 +22,6 @@ public function __construct($data, array $meta = [], array $links = []) } /** - * @return mixed */ public function getData() { @@ -32,8 +29,6 @@ public function getData() } /** - * @param mixed $data - * * @return $this */ public function setData($data) diff --git a/src/Schema/Autowire/Exception/UnresolvedDependencyException.php b/src/Schema/Autowire/Exception/UnresolvedDependencyException.php new file mode 100644 index 0000000..83192f2 --- /dev/null +++ b/src/Schema/Autowire/Exception/UnresolvedDependencyException.php @@ -0,0 +1,15 @@ +annotationValueIfNotNull($configAnnotation, function ($configAnnotation) { return $configAnnotation->repository; }, $this->defaults['repository']); - if (true === is_string($repository)) { + if (true === \is_string($repository)) { if ($this->serviceContainer->has($repository)) { $repository = $this->serviceContainer->get($repository); } else { @@ -118,7 +112,7 @@ protected function createApiConfig(Annotation\Config $configAnnotation = null) $requestBodyDecoder = $this->annotationValueIfNotNull($configAnnotation, function ($configAnnotation) { return $configAnnotation->requestBodyDecoder; }, $this->defaults['request_body_decoder']); - if (true === is_string($requestBodyDecoder)) { + if (true === \is_string($requestBodyDecoder)) { if ($this->serviceContainer->has($requestBodyDecoder)) { $requestBodyDecoder = $this->serviceContainer->get($requestBodyDecoder); } else { @@ -152,8 +146,6 @@ protected function createApiConfig(Annotation\Config $configAnnotation = null) } /** - * @param Annotation\Config|null $configAnnotation - * * @return IndexConfig */ protected function createIndexConfig(Annotation\Config $configAnnotation = null) @@ -178,21 +170,23 @@ protected function createIndexConfig(Annotation\Config $configAnnotation = null) return $configAnnotation->index->allowedFields; }, $this->defaults['index']['allowed_fields']); + $requiredRoles = $this->annotationValueIfNotNull($configAnnotation, function ($configAnnotation) { + return $configAnnotation->index->requiredRoles; + }, $this->defaults['index']['required_roles']); + $config = new IndexConfig( $allowedSortFields, $allowedFilteringParameters, $defaultSort, $defaultPagination, - $allowedFields + $allowedFields, + $requiredRoles ); return $config; } /** - * @param Annotation\Config|null $configAnnotation - * @param ApiConfigInterface|null $apiConfig - * * @return CreateConfig */ protected function createCreateConfig( @@ -202,7 +196,7 @@ protected function createCreateConfig( $factory = $this->annotationValueIfNotNull($configAnnotation, function ($configAnnotation) { return $configAnnotation->create->factory; }, $this->defaults['create']['factory']); - if (true === is_string($factory)) { + if (true === \is_string($factory)) { if ($this->serviceContainer->has($factory)) { $factory = $this->serviceContainer->get($factory); } else { @@ -228,8 +222,6 @@ protected function createCreateConfig( } /** - * @param Annotation\Config|null $configAnnotation - * * @return UpdateConfig */ protected function createUpdateConfig(Annotation\Config $configAnnotation = null) @@ -248,8 +240,6 @@ protected function createUpdateConfig(Annotation\Config $configAnnotation = null } /** - * @param Annotation\Config|null $configAnnotation - * * @return DeleteConfig */ protected function createDeleteConfig(Annotation\Config $configAnnotation = null) @@ -267,10 +257,7 @@ protected function createDeleteConfig(Annotation\Config $configAnnotation = null * Helper method to select between config options * * @param Annotation\Config $configAnnotation - * @param Closure $propertyFetcher * @param $alternativeValue - * - * @return mixed */ protected function annotationValueIfNotNull( Annotation\Config $configAnnotation = null, diff --git a/src/Services/ModelInput/AbstractFormModelInputHandler.php b/src/Services/ModelInput/AbstractFormModelInputHandler.php index fdd4077..975e1f5 100644 --- a/src/Services/ModelInput/AbstractFormModelInputHandler.php +++ b/src/Services/ModelInput/AbstractFormModelInputHandler.php @@ -2,7 +2,7 @@ namespace Trikoder\JsonApiBundle\Services\ModelInput; -use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Persistence\Proxy; use Symfony\Component\Form\FormInterface; use Trikoder\JsonApiBundle\Contracts\ModelTools\ModelInputHandlerInterface; @@ -39,14 +39,18 @@ public function forModel($model) } } $this->model = $model; - $this->modelClass = ClassUtils::getClass($this->model); + + // make sure we are not working with doctrine proxy + if ($this->model instanceof Proxy) { + $this->modelClass = get_parent_class($this->model); + } else { + $this->modelClass = \get_class($this->model); + } return $this; } /** - * @param array $input - * * @return $this */ public function handle(array $input) diff --git a/src/Services/ModelInput/CustomFormModelInputHandler.php b/src/Services/ModelInput/CustomFormModelInputHandler.php index 694fec8..87957fd 100644 --- a/src/Services/ModelInput/CustomFormModelInputHandler.php +++ b/src/Services/ModelInput/CustomFormModelInputHandler.php @@ -16,8 +16,6 @@ class CustomFormModelInputHandler extends AbstractFormModelInputHandler /** * FormModelInputHandler constructor. - * - * @param FormInterface $form */ public function __construct(FormInterface $form) { diff --git a/src/Services/ModelInput/GenericFormModelInputHandler.php b/src/Services/ModelInput/GenericFormModelInputHandler.php index ef49b05..9b689c6 100644 --- a/src/Services/ModelInput/GenericFormModelInputHandler.php +++ b/src/Services/ModelInput/GenericFormModelInputHandler.php @@ -38,9 +38,6 @@ class GenericFormModelInputHandler extends AbstractFormModelInputHandler * GenericFormModelInputHandler constructor. * * @param object $model - * @param null $allowedFields - * @param FormFactoryInterface $formFactory - * @param ObjectManager $objectManager */ public function __construct( $model, diff --git a/src/Services/ModelInput/ModelToolsFactory.php b/src/Services/ModelInput/ModelToolsFactory.php index 64628dc..08baefd 100644 --- a/src/Services/ModelInput/ModelToolsFactory.php +++ b/src/Services/ModelInput/ModelToolsFactory.php @@ -28,10 +28,6 @@ class ModelToolsFactory /** * ModelToolsFactory constructor. - * - * @param FormFactoryInterface $formFactory - * @param ValidatorInterface $validator - * @param ObjectManager $objectManager */ public function __construct( FormFactoryInterface $formFactory, @@ -45,7 +41,6 @@ public function __construct( /** * @param $model - * @param array|null $allowedFields * * @return GenericFormModelInputHandler */ diff --git a/src/Services/ModelInput/ModelValidator.php b/src/Services/ModelInput/ModelValidator.php index cd3f6ac..c589f06 100644 --- a/src/Services/ModelInput/ModelValidator.php +++ b/src/Services/ModelInput/ModelValidator.php @@ -22,8 +22,6 @@ class ModelValidator implements ModelValidatorInterface /** * ModelValidator constructor. - * - * @param ValidatorInterface $validator */ public function __construct(ValidatorInterface $validator) { @@ -52,7 +50,7 @@ public function validate(array $validationGroups = null) /** @var ConstraintViolationListInterface $validationResult */ $validationResult = $this->validator->validate($this->model, null, $validationGroups); - if (0 == count($validationResult)) { + if (0 == \count($validationResult)) { return true; } else { return $this->convertViolationsToErrors($validationResult); diff --git a/src/Services/ModelInput/Traits/ConstraintViolationToErrorTransformer.php b/src/Services/ModelInput/Traits/ConstraintViolationToErrorTransformer.php index ad32126..eade346 100644 --- a/src/Services/ModelInput/Traits/ConstraintViolationToErrorTransformer.php +++ b/src/Services/ModelInput/Traits/ConstraintViolationToErrorTransformer.php @@ -30,8 +30,6 @@ protected function convertViolationsToErrors(ConstraintViolationListInterface $v } /** - * @param ConstraintViolationInterface $violation - * * @return Error */ protected function convertViolationToError(ConstraintViolationInterface $violation) diff --git a/src/Services/ModelInput/Traits/FormErrorToErrorTransformer.php b/src/Services/ModelInput/Traits/FormErrorToErrorTransformer.php index 4cd9d75..242d2ed 100644 --- a/src/Services/ModelInput/Traits/FormErrorToErrorTransformer.php +++ b/src/Services/ModelInput/Traits/FormErrorToErrorTransformer.php @@ -13,8 +13,6 @@ trait FormErrorToErrorTransformer { /** - * @param FormErrorIterator $formErrors - * * @return array */ protected function convertFormErrorsToErrors(FormErrorIterator $formErrors) @@ -30,8 +28,6 @@ protected function convertFormErrorsToErrors(FormErrorIterator $formErrors) } /** - * @param FormError $violation - * * @return Error */ protected function convertFormErrorToError(FormError $violation) diff --git a/src/Services/Neomerx/Container.php b/src/Services/Neomerx/Container.php index 06880ab..ff238fa 100644 --- a/src/Services/Neomerx/Container.php +++ b/src/Services/Neomerx/Container.php @@ -2,14 +2,14 @@ namespace Trikoder\JsonApiBundle\Services\Neomerx; -use Closure; -use Doctrine\Common\Util\ClassUtils; +use Doctrine\Common\Persistence\Proxy; use Neomerx\JsonApi\Contracts\Schema\SchemaFactoryInterface; use Neomerx\JsonApi\Schema\Container as BaseContainer; use ReflectionClass; use ReflectionParameter; use RuntimeException; use Symfony\Component\DependencyInjection\ContainerInterface; +use Trikoder\JsonApiBundle\Schema\Autowire\Exception\UnresolvedDependencyException; /** * Class Container @@ -23,10 +23,6 @@ class Container extends BaseContainer /** * Container constructor. - * - * @param ContainerInterface $serviceContainer - * @param SchemaFactoryInterface $factory - * @param array $schemas */ public function __construct( ContainerInterface $serviceContainer, @@ -42,26 +38,13 @@ public function __construct( */ protected function getResourceType($resource) { - return ClassUtils::getRealClass(get_class($resource)); - } - - /** - * @param Closure $closure - * - * @return mixed - */ - protected function createSchemaFromClosure(Closure $closure) - { - $schema = $closure($this->getFactory(), $this->serviceContainer); + if ($resource instanceof Proxy) { + return get_parent_class($resource); + } - return $schema; + return \get_class($resource); } - /** - * @param string $className - * - * @return mixed - */ protected function createSchemaFromClassName($className) { $callArguments = []; @@ -84,8 +67,7 @@ protected function createSchemaFromClassName($className) // if we cannot autowire it we should fail if (null === $resolvedDependacy) { - throw new RuntimeException(sprintf('Cannot resolve argument %s for schema %s with hint %s. Did you forget to register service or alias?', - $argumentIndex, $className, $argumentClassHint->getName())); + throw new UnresolvedDependencyException($argumentIndex, $className, $argumentClassHint->getName()); } $callArguments[$argumentIndex] = $resolvedDependacy; } @@ -97,8 +79,6 @@ protected function createSchemaFromClassName($className) } /** - * @param ReflectionClass $dependancyClass - * * @return object */ private function resolveSchemaClassDependancy(ReflectionClass $dependancyClass) diff --git a/src/Services/Neomerx/EncoderService.php b/src/Services/Neomerx/EncoderService.php index b3fa3ed..7833572 100644 --- a/src/Services/Neomerx/EncoderService.php +++ b/src/Services/Neomerx/EncoderService.php @@ -31,8 +31,6 @@ class EncoderService /** * EncoderService constructor. - * - * @param FactoryService $factoryService */ public function __construct(FactoryService $factoryService) { @@ -41,11 +39,7 @@ public function __construct(FactoryService $factoryService) } /** - * @param SchemaClassMapProviderInterface $classMapProvider * @param array|Iterator|null|object|string $data - * @param EncodingParametersInterface|null $encodingParameters - * @param array|null $meta - * @param array $links * * @return string */ @@ -77,8 +71,6 @@ public function encode( /** * @param ErrorInterface[] $errors - * @param array|null $meta - * @param array $links * * @return string */ @@ -106,7 +98,6 @@ public function encodeErrors( } /** - * @param bool $prettyPrint */ public function configureSetPrettyPrint(bool $prettyPrint = true) { diff --git a/src/Services/Neomerx/ErrorFactory.php b/src/Services/Neomerx/ErrorFactory.php index 655e460..c9f5011 100644 --- a/src/Services/Neomerx/ErrorFactory.php +++ b/src/Services/Neomerx/ErrorFactory.php @@ -24,7 +24,7 @@ public function fromString(string $error): ErrorInterface */ public function fromException(Exception $exception): ErrorInterface { - $errorDescription = sprintf('Exception of type: %s', get_class($exception)); + $errorDescription = sprintf('Exception of type: %s', \get_class($exception)); if (false === empty($exception->getMessage())) { $errorTitle = $exception->getMessage(); } else { diff --git a/src/Services/Neomerx/FactoryService.php b/src/Services/Neomerx/FactoryService.php index 0efc597..b5e12e6 100644 --- a/src/Services/Neomerx/FactoryService.php +++ b/src/Services/Neomerx/FactoryService.php @@ -41,8 +41,6 @@ public function __construct(ServiceContainer $serviceContainer, LoggerInterface * This is symfony way for creating with factory as service, * neomerx way is @see \Neomerx\JsonApi\Encoder\Encoder::instance * - * @param array $schemas - * @param EncoderOptions|null $encoderOptions * * @return Encoder */ diff --git a/src/Services/Neomerx/ServiceContainer.php b/src/Services/Neomerx/ServiceContainer.php index 7dd2e8b..10eef69 100644 --- a/src/Services/Neomerx/ServiceContainer.php +++ b/src/Services/Neomerx/ServiceContainer.php @@ -12,7 +12,7 @@ class ServiceContainer implements ContainerInterface /** * @var string[] */ - protected $services; + protected $services = []; /** * @var ContainerInterface diff --git a/src/Services/RequestBodyDecoderService.php b/src/Services/RequestBodyDecoderService.php index fdb7e58..d749bb1 100644 --- a/src/Services/RequestBodyDecoderService.php +++ b/src/Services/RequestBodyDecoderService.php @@ -18,13 +18,13 @@ public function decode(array $body = null) $decoded = []; // from data take attributes, id and relations and flatten them in array - if (true === is_array($body) && true === array_key_exists('data', $body)) { + if (true === \is_array($body) && true === array_key_exists('data', $body)) { $data = $body['data']; // parse relations first if (array_key_exists('relationships', $data)) { $relationships = $data['relationships']; - if (true === is_array($relationships)) { + if (true === \is_array($relationships)) { foreach ($relationships as $relationshipName => $relationshipData) { // needs data if (true === array_key_exists('data', $relationshipData)) { @@ -40,7 +40,7 @@ public function decode(array $body = null) } if ($relationshipIsMultiple) { // TODO - can it be non-empty and non-array - if (true === empty($relationshipData) || false === is_array($relationshipData)) { + if (true === empty($relationshipData) || false === \is_array($relationshipData)) { $decoded[$relationshipName] = []; } else { foreach ($relationshipData as $relationshipDataItem) { diff --git a/src/Services/RequestDecoder.php b/src/Services/RequestDecoder.php index b56d024..d4105d1 100644 --- a/src/Services/RequestDecoder.php +++ b/src/Services/RequestDecoder.php @@ -39,8 +39,6 @@ public function __construct(FactoryService $jsonApiFactory, JsonApiEnabledInterf } /** - * @param Request $currentRequest - * * @return Request */ public function decode(Request $currentRequest) @@ -66,7 +64,7 @@ function () use ($currentRequest, $config) { $configFields = $config->getIndex()->getIndexAllowedFields(); // if all request fields are empty so we apply for all types $currentRequestQueryFieldsEmpty = (false === array_key_exists('fields', - $currentRequestQuery) || false === is_array($currentRequestQuery['fields'])); + $currentRequestQuery) || false === \is_array($currentRequestQuery['fields'])); if (true === $currentRequestQueryFieldsEmpty) { $currentRequestQuery['fields'] = []; } @@ -138,7 +136,7 @@ function () use ($currentRequest, $config) { $currentRequest->cookies->all(), $currentRequest->files->all(), $currentRequest->server->all(), - is_array($decodedContent) ? http_build_query($decodedContent) : '' + \is_array($decodedContent) ? http_build_query($decodedContent) : '' ); return $transformedRequest; @@ -187,7 +185,7 @@ private function decodeFilterParams($sourceFilterParams) { $decodedParams = []; - if (true === is_array($sourceFilterParams)) { + if (true === \is_array($sourceFilterParams)) { foreach ($sourceFilterParams as $field => $filters) { if (false !== strpos($filters, ',')) { $decodedParams[$field] = explode(',', $filters); diff --git a/src/Services/ResponseFactoryService.php b/src/Services/ResponseFactoryService.php index c474dbb..684755b 100644 --- a/src/Services/ResponseFactoryService.php +++ b/src/Services/ResponseFactoryService.php @@ -22,9 +22,6 @@ class ResponseFactoryService implements ResponseFactoryInterface /** * ResponseFactoryService constructor. - * - * @param EncoderService $encoderService - * @param ErrorFactoryInterface $errorFactory */ public function __construct(EncoderService $encoderService, ErrorFactoryInterface $errorFactory) { @@ -35,7 +32,7 @@ public function __construct(EncoderService $encoderService, ErrorFactoryInterfac /** * {@inheritdoc} */ - public function createResponse(string $data, Response $response = null) + public function createResponse(string $data, Response $response = null): Response { if (null === $response) { $response = new Response(); // TODO move to JsonResponse @@ -54,7 +51,7 @@ public function createResponse(string $data, Response $response = null) /** * {@inheritdoc} */ - public function createConflict(string $data, Response $response = null) + public function createConflict(string $data, Response $response = null): Response { if (null === $response) { $response = new Response(); // TODO move to JsonResponse @@ -68,14 +65,17 @@ public function createConflict(string $data, Response $response = null) /** * {@inheritdoc} */ - public function createCreated($data, $location, Response $response = null) + public function createCreated(string $data, string $location = null, Response $response = null): Response { if (null === $response) { $response = new Response(); // TODO move to JsonResponse } $response->setStatusCode(Response::HTTP_CREATED); - $response->headers->add(['Location', $location]); + + if (null !== $location) { + $response->headers->add(['Location' => $location]); + } return $this->createResponse($data, $response); } @@ -83,7 +83,7 @@ public function createCreated($data, $location, Response $response = null) /** * {@inheritdoc} */ - public function createNoContent(Response $response = null) + public function createNoContent(Response $response = null): Response { if (null === $response) { $response = new Response(); // TODO move to JsonResponse @@ -96,7 +96,7 @@ public function createNoContent(Response $response = null) /** * {@inheritdoc} */ - public function createError(string $data, Response $response = null) + public function createError(string $data, Response $response = null): Response { if (null === $response) { $response = new Response(); // TODO move to JsonResponse @@ -110,7 +110,7 @@ public function createError(string $data, Response $response = null) /** * {@inheritdoc} */ - public function createErrorFromException(Exception $exception, Response $response = null) + public function createErrorFromException(Exception $exception, Response $response = null): Response { $error = $this->errorFactory->fromException($exception); $encoded = $this->encoderService->encodeErrors([$error]); diff --git a/src/TrikoderJsonApiBundle.php b/src/TrikoderJsonApiBundle.php index 772c802..e6aee22 100644 --- a/src/TrikoderJsonApiBundle.php +++ b/src/TrikoderJsonApiBundle.php @@ -2,8 +2,20 @@ namespace Trikoder\JsonApiBundle; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Trikoder\JsonApiBundle\CompilerPass\SchemaAutoMapCompilerPass; +use Trikoder\JsonApiBundle\Controller\AbstractController; class TrikoderJsonApiBundle extends Bundle { + public function build(ContainerBuilder $container) + { + parent::build($container); + + $container->addCompilerPass(new SchemaAutoMapCompilerPass()); + $container->registerForAutoconfiguration(AbstractController::class) + ->addTag('controller.service_arguments') + ; + } } diff --git a/tests/Functional/Controller/CreateActionTest.php b/tests/Functional/Controller/CreateActionTest.php index e00056d..3c8b3f4 100644 --- a/tests/Functional/Controller/CreateActionTest.php +++ b/tests/Functional/Controller/CreateActionTest.php @@ -4,7 +4,6 @@ use Symfony\Component\HttpFoundation\Response; use Trikoder\JsonApiBundle\Tests\Functional\JsonapiWebTestCase; -use Trikoder\JsonApiBundle\Tests\Resources\Entity\User; class CreateActionTest extends JsonapiWebTestCase { @@ -96,4 +95,49 @@ public function testActionWithoutTrailingSlash() $this->assertNotEquals(Response::HTTP_MOVED_PERMANENTLY, $response->getStatusCode()); $this->assertIsJsonapiResponse($response); } + + /** + * test simple create + */ + public function testCreateOnlyController() + { + $client = static::createClient(); + + $client->request( + 'POST', + '/api/create-only/', + [], + [], + [], + json_encode([ + 'data' => [ + 'type' => 'user', + 'attributes' => [ + 'email' => 'createonly@domain.com', + 'active' => true, + ], + ], + ]) + ); + + $response = $client->getResponse(); + + $this->assertIsJsonapiResponse($response); + + $this->assertEquals(Response::HTTP_CREATED, $response->getStatusCode()); + + $data = $this->getResponseContentJson($response); + + $this->assertEquals([ + 'type' => 'user', + 'id' => (int) $data['data']['id'], // get take id from response - if none, this will cause error + 'attributes' => [ + 'email' => 'createonly@domain.com', + 'active' => true, + ], + 'links' => [ + 'self' => '/user/' . $data['data']['id'], + ], + ], $data['data']); + } } diff --git a/tests/Functional/Controller/Demo/CustomMetaResponseActionTest.php b/tests/Functional/Controller/Demo/CustomMetaResponseActionTest.php index ec313df..8592b85 100644 --- a/tests/Functional/Controller/Demo/CustomMetaResponseActionTest.php +++ b/tests/Functional/Controller/Demo/CustomMetaResponseActionTest.php @@ -23,7 +23,8 @@ public function testIndexAction() $this->assertEquals([ 'customInfo' => 'valid', - 'total' => 5, + // TODO refactor this to count from database + 'total' => 6, ], $data['meta']); $this->assertNotEmpty($data['data']); diff --git a/tests/Functional/Controller/IndexActionTest.php b/tests/Functional/Controller/IndexActionTest.php index 6abd992..02ff89b 100644 --- a/tests/Functional/Controller/IndexActionTest.php +++ b/tests/Functional/Controller/IndexActionTest.php @@ -138,7 +138,8 @@ public function testSorting() $response = $client->getResponse(); $this->assertIsJsonapiResponse($response); $content = $this->getResponseContentJson($response); - $this->assertEquals(6, $content['data'][0]['id']); + // TODO refactor total of 7 to count from database + $this->assertEquals(7, $content['data'][0]['id']); } /** diff --git a/tests/Functional/Controller/PaginatedIndexActionTest.php b/tests/Functional/Controller/PaginatedIndexActionTest.php index dd8909f..637b70b 100644 --- a/tests/Functional/Controller/PaginatedIndexActionTest.php +++ b/tests/Functional/Controller/PaginatedIndexActionTest.php @@ -28,7 +28,8 @@ public function testUserIndexAction() $this->assertArrayHasKey('meta', $content); $this->assertEquals([ - 'total' => 5, + // TODO refactor this to count from database + 'total' => 6, ], $content['meta']); $this->assertArrayHasKey('links', $content); diff --git a/tests/Functional/Controller/RequiredRolesTest.php b/tests/Functional/Controller/RequiredRolesTest.php new file mode 100644 index 0000000..114ea20 --- /dev/null +++ b/tests/Functional/Controller/RequiredRolesTest.php @@ -0,0 +1,106 @@ +request('GET', '/api/user-limited-access/'); + $response = $client->getResponse(); + + $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testIndexAccessGrantedForCorrectRole() + { + $client = static::createClient([], ['PHP_AUTH_USER' => 'admin_tester', + 'PHP_AUTH_PW' => 'admin_tester', ]); + $client->request('GET', '/api/user-limited-access/'); + $response = $client->getResponse(); + + $this->assertEquals(Response::HTTP_OK, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testIndexForbiddenForWrongRole() + { + $client = static::createClient([], ['PHP_AUTH_USER' => 'tester', + 'PHP_AUTH_PW' => 'tester', ]); + $client->request('GET', '/api/user-limited-access/'); + $response = $client->getResponse(); + + $this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testCreateAuthorizationRequiredResponse() + { + $client = static::createClient(); + $client->request('POST', '/api/user-limited-access/'); + $response = $client->getResponse(); + + $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testCreateAccessGrantedRequiredRolesDefinedAsArray() + { + $client = static::createClient([], ['PHP_AUTH_USER' => 'tester', + 'PHP_AUTH_PW' => 'tester', ]); + $client->request('POST', '/api/user-limited-access/'); + $response = $client->getResponse(); + + $this->assertEquals(Response::HTTP_CONFLICT, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testDeleteAuthorizationRequiredResponse() + { + $client = static::createClient(); + $client->request('DELETE', '/api/user-limited-access/666'); + $response = $client->getResponse(); + + $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testDeleteAccessGrantedForCorrectRole() + { + $client = static::createClient([], ['PHP_AUTH_USER' => 'tester', + 'PHP_AUTH_PW' => 'tester', ]); + $client->request('DELETE', '/api/user-limited-access/666'); + $response = $client->getResponse(); + + $this->assertEquals(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testUpdateAuthorizationRequiredResponse() + { + $client = static::createClient(); + $client->request('PATCH', '/api/user-limited-access/666'); + $response = $client->getResponse(); + + $this->assertSame(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } + + public function testUpdateAccessGrantedForCorrect() + { + $client = static::createClient([], ['PHP_AUTH_USER' => 'tester', + 'PHP_AUTH_PW' => 'tester', ]); + $client->request('PATCH', '/api/user-limited-access/666'); + $response = $client->getResponse(); + + $this->assertSame(Response::HTTP_NOT_FOUND, $response->getStatusCode()); + $this->assertIsJsonapiResponse($response); + } +} diff --git a/tests/Functional/Controller/ShowActionTest.php b/tests/Functional/Controller/ShowActionTest.php index f77135c..b3d752c 100644 --- a/tests/Functional/Controller/ShowActionTest.php +++ b/tests/Functional/Controller/ShowActionTest.php @@ -4,6 +4,7 @@ use Symfony\Component\HttpFoundation\Response; use Trikoder\JsonApiBundle\Tests\Functional\JsonapiWebTestCase; +use Trikoder\JsonApiBundle\Tests\Resources\Entity\Post; use Trikoder\JsonApiBundle\Tests\Resources\Entity\User; class ShowActionTest extends JsonapiWebTestCase @@ -179,16 +180,30 @@ public function testRelationshipsDefault() 'attributes' => [ 'title' => 'Post 1', ], + 'relationships' => [ + 'author' => [ + 'data' => [ + 'type' => 'user', + 'id' => 3, + ], + ], + ], 'links' => [ 'self' => '/post/1', ], ], $data['data']); + + $this->assertArrayNotHasKey('include', $data); } public function testRelationshipsDefaultRequested() { $client = static::createClient(); + // load post + /** @var Post post */ + $post = $client->getContainer()->get('doctrine.orm.entity_manager')->getRepository(Post::class)->find(1); + $client->request('GET', '/api/posts/1', ['include' => 'author']); $response = $client->getResponse(); @@ -217,6 +232,17 @@ public function testRelationshipsDefaultRequested() ], ], $data['data']); + $this->assertEquals([ + [ + 'type' => 'user', + 'id' => '3', + 'attributes' => [ + 'email' => $post->getAuthor()->getEmail(), + 'active' => $post->getAuthor()->isActive(), + ], + ], + ], $data['included']); + $this->assertArrayHasKey('included', $data); $this->assertNotEmpty($data['included']); $this->assertEquals('user', $data['included'][0]['type']); diff --git a/tests/Integration/SchemaCreationTest.php b/tests/Integration/SchemaCreationTest.php new file mode 100644 index 0000000..3089ca6 --- /dev/null +++ b/tests/Integration/SchemaCreationTest.php @@ -0,0 +1,28 @@ +getContainer()->get(EncoderService::class); + $schemaClassMap = new SchemaClassMapService(); + $schemaClassMap->add(\stdClass::class, PrivateServiceSchema::class); + + $this->expectException(UnresolvedDependencyException::class); + + $encoded = $encoderService->encode( + $schemaClassMap, + (object) ['id' => 1, 'title' => 'test'] + ); + } +} diff --git a/tests/Resources/AutomapTestSchema/MappableTestSchema.php b/tests/Resources/AutomapTestSchema/MappableTestSchema.php new file mode 100644 index 0000000..712ce27 --- /dev/null +++ b/tests/Resources/AutomapTestSchema/MappableTestSchema.php @@ -0,0 +1,44 @@ +getId(); + } + + /** + * Get resource attributes. + * + * @param object $resource + * + * @return array + */ + public function getAttributes($resource) + { + return [ + 'someAttribute' => $resource->getSomeAttribute(), + ]; + } +} diff --git a/tests/Resources/Controller/Api/Demo/CustomMetaResponseController.php b/tests/Resources/Controller/Api/Demo/CustomMetaResponseController.php index ce214c4..ff926af 100644 --- a/tests/Resources/Controller/Api/Demo/CustomMetaResponseController.php +++ b/tests/Resources/Controller/Api/Demo/CustomMetaResponseController.php @@ -2,15 +2,15 @@ namespace Trikoder\JsonApiBundle\Tests\Resources\Controller\Api\Demo; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; use Trikoder\JsonApiBundle\Config\Annotation as JsonApiConfig; use Trikoder\JsonApiBundle\Controller\AbstractController as JsonApiController; use Trikoder\JsonApiBundle\Controller\Traits\Actions\IndexTrait; use Trikoder\JsonApiBundle\Response\DataResponse; /** - * @Route("/custom-meta-response") + * @Route(path="/custom-meta-response") * * @JsonApiConfig\Config( * modelClass="Trikoder\JsonApiBundle\Tests\Resources\Entity\User" @@ -21,7 +21,7 @@ class CustomMetaResponseController extends JsonApiController use IndexTrait; /** - * @Route("") + * @Route(path="") */ public function indexAction(Request $request) { @@ -36,7 +36,7 @@ public function indexAction(Request $request) } /** - * @Route("/empty") + * @Route(path="/empty") */ public function emptyAction(Request $request) { @@ -49,7 +49,7 @@ public function emptyAction(Request $request) } /** - * @Route("/empty-all") + * @Route(path="/empty-all") */ public function emptyAllAction(Request $request) { diff --git a/tests/Resources/Controller/Api/Demo/CustomResponseController.php b/tests/Resources/Controller/Api/Demo/CustomResponseController.php index 72fee70..2d40419 100644 --- a/tests/Resources/Controller/Api/Demo/CustomResponseController.php +++ b/tests/Resources/Controller/Api/Demo/CustomResponseController.php @@ -3,8 +3,8 @@ namespace Trikoder\JsonApiBundle\Tests\Resources\Controller\Api\Demo; use Exception; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; use Trikoder\JsonApiBundle\Contracts\ResponseFactoryInterface; use Trikoder\JsonApiBundle\Contracts\SchemaClassMapProviderInterface; use Trikoder\JsonApiBundle\Controller\AbstractController as JsonApiController; @@ -14,13 +14,13 @@ use Trikoder\JsonApiBundle\Tests\Resources\Entity\User; /** - * @Route("/custom-response") + * @Route(path="/custom-response") */ class CustomResponseController extends JsonApiController { /** - * @Route("") - * @Route("/from-array") + * @Route(path="") + * @Route(path="/from-array") */ public function defaultAction() { @@ -41,7 +41,7 @@ public function getSchemaClassMapProvider() } /** - * @Route("/manual") + * @Route(path="/manual") */ public function manualListAction(Request $request) { @@ -63,7 +63,7 @@ public function manualListAction(Request $request) } /** - * @Route("") + * @Route(path="") */ public function exceptionAction() { diff --git a/tests/Resources/Controller/Api/Demo/ExceptionController.php b/tests/Resources/Controller/Api/Demo/ExceptionController.php index 1d939c5..221f0f0 100644 --- a/tests/Resources/Controller/Api/Demo/ExceptionController.php +++ b/tests/Resources/Controller/Api/Demo/ExceptionController.php @@ -3,7 +3,7 @@ namespace Trikoder\JsonApiBundle\Tests\Resources\Controller\Api\Demo; use Exception; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; +use Symfony\Component\Routing\Annotation\Route; use Trikoder\JsonApiBundle\Config\Annotation as JsonApiConfig; use Trikoder\JsonApiBundle\Controller\AbstractController as JsonApiController; diff --git a/tests/Resources/Controller/Api/Demo/SimpleFileUploadController.php b/tests/Resources/Controller/Api/Demo/SimpleFileUploadController.php index 4080326..9caf890 100644 --- a/tests/Resources/Controller/Api/Demo/SimpleFileUploadController.php +++ b/tests/Resources/Controller/Api/Demo/SimpleFileUploadController.php @@ -2,10 +2,9 @@ namespace Trikoder\JsonApiBundle\Tests\Resources\Controller\Api\Demo; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; -use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; use Symfony\Component\Form\FormFactoryInterface; use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Annotation\Route; use Trikoder\JsonApiBundle\Config\Annotation as JsonApiConfig; use Trikoder\JsonApiBundle\Controller\AbstractController as JsonApiController; use Trikoder\JsonApiBundle\Controller\Traits\Actions\CreateTrait; @@ -41,10 +40,7 @@ public function setFormFactory(FormFactoryInterface $formFactory) } /** - * @Route("") - * @Method("POST") - * - * @param Request $request + * @Route(path="", methods={"POST"}) * * @return SimpleFileModel */ diff --git a/tests/Resources/Controller/Api/Test/CreateOnlyController.php b/tests/Resources/Controller/Api/Test/CreateOnlyController.php new file mode 100644 index 0000000..5874c15 --- /dev/null +++ b/tests/Resources/Controller/Api/Test/CreateOnlyController.php @@ -0,0 +1,20 @@ + function () use ($resource) { - return $resource->getAuthor(); - }, - ]; - } + $relationships['author'] = [ + self::DATA => function () use ($resource) { + return $resource->getAuthor(); + }, + ]; return $relationships; } diff --git a/tests/Resources/JsonApi/Schema/Test/PrivateServiceSchema.php b/tests/Resources/JsonApi/Schema/Test/PrivateServiceSchema.php new file mode 100644 index 0000000..612df28 --- /dev/null +++ b/tests/Resources/JsonApi/Schema/Test/PrivateServiceSchema.php @@ -0,0 +1,49 @@ +userProvider = $userProvider; + } + + /** + * Get resource identity. + * + * @param object $resource + * + * @return string + */ + public function getId($resource) + { + return $resource->getId(); + } + + /** + * Get resource attributes. + * + * @param object $resource + * + * @return array + */ + public function getAttributes($resource) + { + return [ + 'username' => $this->userProvider->getUser()->getUsername(), + ]; + } +} diff --git a/tests/Resources/Model/SimpleFileModel.php b/tests/Resources/Model/SimpleFileModel.php index 2a22654..9f47861 100644 --- a/tests/Resources/Model/SimpleFileModel.php +++ b/tests/Resources/Model/SimpleFileModel.php @@ -16,7 +16,6 @@ class SimpleFileModel private $title; /** - * @return mixed */ public function getTitle() { @@ -24,8 +23,6 @@ public function getTitle() } /** - * @param mixed $title - * * @return $this */ public function setTitle($title) @@ -47,7 +44,6 @@ public function hasSimpleFileBinary() } /** - * @return mixed */ public function getName() { @@ -55,8 +51,6 @@ public function getName() } /** - * @param mixed $name - * * @return $this */ public function setName($name) @@ -67,7 +61,6 @@ public function setName($name) } /** - * @return mixed */ public function getExtension() { @@ -75,8 +68,6 @@ public function getExtension() } /** - * @param mixed $extension - * * @return $this */ public function setExtension($extension) diff --git a/tests/Resources/Security/AuthenticatedUserProvider.php b/tests/Resources/Security/AuthenticatedUserProvider.php new file mode 100644 index 0000000..79fcab0 --- /dev/null +++ b/tests/Resources/Security/AuthenticatedUserProvider.php @@ -0,0 +1,39 @@ +tokenStorage = $tokenStorage; + } + + /** + * Returns true if there is UserInterface object in the token storage + */ + public function hasUser(): bool + { + return ($this->tokenStorage->getToken()->getUser() instanceof UserInterface) ? true : false; + } + + /** + */ + public function getUser(): UserInterface + { + if (false === $this->hasUser()) { + throw new RuntimeException('No user to provide. Use hasUser to check if there is User object to provide!'); + } + + return $this->tokenStorage->getToken()->getUser(); + } +} diff --git a/tests/Resources/Security/AuthenticatedUserProviderInterface.php b/tests/Resources/Security/AuthenticatedUserProviderInterface.php new file mode 100644 index 0000000..4bcec0b --- /dev/null +++ b/tests/Resources/Security/AuthenticatedUserProviderInterface.php @@ -0,0 +1,17 @@ +setClass(SchemaClassMapProviderInterface::class); + $containerBuilder->setDefinition(SchemaClassMapProviderInterface::class, $serviceDefinition); + + $compilerPass->process($containerBuilder); + + // there should be exactly one invocation of add method after this is completed + $definition = $containerBuilder->getDefinition(SchemaClassMapProviderInterface::class); + $this->assertNotEmpty($definition->getMethodCalls(), 'There should be classmap method calls in the service definition, but there are none'); + $this->assertCount(1, $definition->getMethodCalls(), 'There should be exactly one call of add method of schema class map provider in service definition'); + } + + public function testSchemaFileFinder() + { + $schemaFileFinder = new \ReflectionMethod(SchemaAutoMapCompilerPass::class, 'getSchemaFilenames'); + $schemaFileFinder->setAccessible(true); + + $compilerPass = new SchemaAutoMapCompilerPass(); + + $schemaFiles = $schemaFileFinder->invoke($compilerPass, ['tests/Resources/AutomapTestSchema/']); + + // there should be at least one entry in the schemafiles array that ends in "MappableTestSchema.php" + $foundMappableTestSchemaFile = false; + foreach ($schemaFiles as $schemaFile) { + if (false !== strpos($schemaFile, 'MappableTestSchema.php')) { + $foundMappableTestSchemaFile = true; + break; + } + } + + $this->assertTrue($foundMappableTestSchemaFile); + } + + public function testSchemaFileFinderWithoutDirPattern() + { + $schemaFileFinder = new \ReflectionMethod(SchemaAutoMapCompilerPass::class, 'getSchemaFilenames'); + $schemaFileFinder->setAccessible(true); + + $compilerPass = new SchemaAutoMapCompilerPass(); + + $schemaFiles = $schemaFileFinder->invoke($compilerPass, []); + + $this->assertEmpty($schemaFiles); + } +} diff --git a/tests/Unit/Config/ConfigBuilderTest.php b/tests/Unit/Config/ConfigBuilderTest.php index 04dd172..b928b76 100644 --- a/tests/Unit/Config/ConfigBuilderTest.php +++ b/tests/Unit/Config/ConfigBuilderTest.php @@ -115,24 +115,25 @@ public function testFromAnnotation() $this->assertEquals('modelClass', $config->getApi()->getModelClass()); $this->assertInstanceOf(RepositoryInterface::class, $config->getApi()->getRepository()); $this->assertInstanceOf(RequestBodyDecoderInterface::class, $config->getApi()->getRequestBodyDecoder()); - $this->assertEquals([1], $config->getApi()->getFixedFiltering()); - $this->assertEquals([2], $config->getApi()->getAllowedIncludePaths()); + $this->assertEquals(['fixed_filtering'], $config->getApi()->getFixedFiltering()); + $this->assertEquals(['allowed_include_paths'], $config->getApi()->getAllowedIncludePaths()); $this->assertTrue($config->getApi()->getAllowExtraParams()); - $this->assertEquals([3], $config->getIndex()->getIndexAllowedSortFields()); - $this->assertEquals([4], $config->getIndex()->getIndexAllowedFilteringParameters()); - $this->assertEquals([5], $config->getIndex()->getIndexDefaultSort()); - $this->assertEquals([6], $config->getIndex()->getIndexDefaultPagination()); - $this->assertEquals([7], $config->getIndex()->getIndexAllowedFields()); + $this->assertEquals(['allowed_sort_fields'], $config->getIndex()->getIndexAllowedSortFields()); + $this->assertEquals(['allowed_filtering_parameters'], $config->getIndex()->getIndexAllowedFilteringParameters()); + $this->assertEquals(['default_sort'], $config->getIndex()->getIndexDefaultSort()); + $this->assertEquals(['default_pagination'], $config->getIndex()->getIndexDefaultPagination()); + $this->assertEquals(['index_allowed_fields'], $config->getIndex()->getIndexAllowedFields()); + $this->assertEquals(['index_required_roles'], $config->getIndex()->getIndexRequiredRoles()); - $this->assertEquals([8], $config->getCreate()->getCreateAllowedFields()); - $this->assertEquals([9], $config->getCreate()->getCreateRequiredRoles()); + $this->assertEquals(['create_allowed_fields'], $config->getCreate()->getCreateAllowedFields()); + $this->assertEquals(['create_required_roles'], $config->getCreate()->getCreateRequiredRoles()); $this->assertInstanceOf(ModelFactoryInterface::class, $config->getCreate()->getCreateFactory()); - $this->assertEquals([10], $config->getUpdate()->getUpdateAllowedFields()); - $this->assertEquals([11], $config->getUpdate()->getUpdateRequiredRoles()); + $this->assertEquals(['update_allowed_fields'], $config->getUpdate()->getUpdateAllowedFields()); + $this->assertEquals(['update_required_roles'], $config->getUpdate()->getUpdateRequiredRoles()); - $this->assertEquals([12], $config->getDelete()->getDeleteRequiredRoles()); + $this->assertEquals(['delete_required_roles'], $config->getDelete()->getDeleteRequiredRoles()); } public function testFromAnnotationWithModelResolver() @@ -223,24 +224,25 @@ protected function getAnnotationsConfig() $annotationConfig->modelClass = 'modelClass'; $annotationConfig->repository = 'repository'; $annotationConfig->requestBodyDecoder = 'request_body_decoder'; - $annotationConfig->fixedFiltering = [1]; - $annotationConfig->allowedIncludePaths = [2]; + $annotationConfig->fixedFiltering = ['fixed_filtering']; + $annotationConfig->allowedIncludePaths = ['allowed_include_paths']; $annotationConfig->allowExtraParams = true; - $annotationConfig->index->allowedSortFields = [3]; - $annotationConfig->index->allowedFilteringParameters = [4]; - $annotationConfig->index->defaultSort = [5]; - $annotationConfig->index->defaultPagination = [6]; - $annotationConfig->index->allowedFields = [7]; + $annotationConfig->index->allowedSortFields = ['allowed_sort_fields']; + $annotationConfig->index->allowedFilteringParameters = ['allowed_filtering_parameters']; + $annotationConfig->index->defaultSort = ['default_sort']; + $annotationConfig->index->defaultPagination = ['default_pagination']; + $annotationConfig->index->allowedFields = ['index_allowed_fields']; + $annotationConfig->index->requiredRoles = ['index_required_roles']; $annotationConfig->create->factory = $this->getModelFactoryMock(); - $annotationConfig->create->allowedFields = [8]; - $annotationConfig->create->requiredRoles = [9]; + $annotationConfig->create->allowedFields = ['create_allowed_fields']; + $annotationConfig->create->requiredRoles = ['create_required_roles']; - $annotationConfig->update->allowedFields = [10]; - $annotationConfig->update->requiredRoles = [11]; + $annotationConfig->update->allowedFields = ['update_allowed_fields']; + $annotationConfig->update->requiredRoles = ['update_required_roles']; - $annotationConfig->delete->requiredRoles = [12]; + $annotationConfig->delete->requiredRoles = ['delete_required_roles']; return $annotationConfig; } @@ -263,6 +265,7 @@ protected function getArrayConfig() 'default_sort' => '', 'default_pagination' => '', 'allowed_fields' => '', + 'required_roles' => '', ], 'create' => [ 'factory' => '', diff --git a/tests/Unit/Config/ConfigurationTest.php b/tests/Unit/Config/ConfigurationTest.php index 19206c1..7b0ff3c 100644 --- a/tests/Unit/Config/ConfigurationTest.php +++ b/tests/Unit/Config/ConfigurationTest.php @@ -31,6 +31,7 @@ protected function getExpectedConfig() 'default_sort' => [], 'default_pagination' => [], 'allowed_fields' => null, + 'required_roles' => null, ], 'create' => [ 'factory' => 'trikoder.jsonapi.simple_model_factory', @@ -44,6 +45,7 @@ protected function getExpectedConfig() 'delete' => [ 'required_roles' => null, ], + 'schema_automap_scan_patterns' => [], ]; } } diff --git a/tests/Unit/Listener/JsonApiEnabledControllerDetectorTraitTest.php b/tests/Unit/Listener/JsonApiEnabledControllerDetectorTraitTest.php index 99a53af..1d1e83e 100644 --- a/tests/Unit/Listener/JsonApiEnabledControllerDetectorTraitTest.php +++ b/tests/Unit/Listener/JsonApiEnabledControllerDetectorTraitTest.php @@ -2,6 +2,7 @@ namespace Trikoder\JsonApiBundle\Tests\Unit\Services; +use Trikoder\JsonApiBundle\Contracts\Config\ConfigInterface; use Trikoder\JsonApiBundle\Controller\JsonApiEnabledInterface; use Trikoder\JsonApiBundle\Listener\JsonApiEnabledControllerDetectorTrait; @@ -29,6 +30,9 @@ public function testIsJsonApiEnabledInterface() } catch (\LogicException $exception) { $this->assertEquals('Unsupported type provided as controller', $exception->getMessage()); } + + // test some class + $this->assertFalse($trait->isJsonApiEnabledControllerTest($this)); } public function testResolveControllerFromEventController() @@ -72,7 +76,14 @@ public function getSchemaClassMapProvider() // NOOP, stub } - public function getJsonApiConfig() + public function getJsonApiConfig(): ConfigInterface + { + // NOOP, stub + } + + /** + */ + public function setJsonApiConfig(ConfigInterface $config) { // NOOP, stub } diff --git a/tests/Unit/ResponseFactoryServiceTest.php b/tests/Unit/ResponseFactoryServiceTest.php index aa8d29f..ef7171b 100644 --- a/tests/Unit/ResponseFactoryServiceTest.php +++ b/tests/Unit/ResponseFactoryServiceTest.php @@ -168,4 +168,25 @@ private function createErrorFactoryMock() return $errorFactory; } + + /** + * + */ + public function testCreateCreated() + { + $responseFactoryService = new ResponseFactoryService( + $this->createEncoderServiceMock(), + $this->createErrorFactoryMock() + ); + + $response = $responseFactoryService->createCreated( + json_encode(['data' => []]), + 'custom.url/test' + ); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(Response::HTTP_CREATED, $response->getStatusCode()); + $this->assertSame('application/json', $response->headers->get('Content-type')); + $this->assertSame('custom.url/test', $response->headers->get('location')); + } } diff --git a/tests/Unit/Services/ModelInput/Traits/ConstraintViolationToErrorTransformerTraitTest.php b/tests/Unit/Services/ModelInput/Traits/ConstraintViolationToErrorTransformerTraitTest.php index b0104fb..fb4e31d 100644 --- a/tests/Unit/Services/ModelInput/Traits/ConstraintViolationToErrorTransformerTraitTest.php +++ b/tests/Unit/Services/ModelInput/Traits/ConstraintViolationToErrorTransformerTraitTest.php @@ -13,7 +13,7 @@ public function testConvertViolationToError() $trait = new ConstraintViolationToErrorTransformerTraitClass(); $error = $trait->publicConvertViolationToError($this->getTestViolation(91, 'Constraint violation for test')); - $this->assertEquals(Error::class, get_class($error)); + $this->assertEquals(Error::class, \get_class($error)); $this->assertEquals(91, $error->getCode()); $this->assertEquals('Constraint violation for test', $error->getTitle()); $this->assertEquals('Constraint violation "Constraint violation for test"', $error->getDetail()); diff --git a/tests/Unit/Services/ModelInput/Traits/FormErrorToErrorTransformerTraitTest.php b/tests/Unit/Services/ModelInput/Traits/FormErrorToErrorTransformerTraitTest.php index d0d3aba..efe57b4 100644 --- a/tests/Unit/Services/ModelInput/Traits/FormErrorToErrorTransformerTraitTest.php +++ b/tests/Unit/Services/ModelInput/Traits/FormErrorToErrorTransformerTraitTest.php @@ -13,7 +13,7 @@ public function testFormErrorToError() $trait = new FormErrorToErrorTransformerTraitClass(); $error = $trait->publicFormErrorToError($this->getTestError('Form error for test')); - $this->assertEquals(Error::class, get_class($error)); + $this->assertEquals(Error::class, \get_class($error)); $this->assertEquals('Form error for test', $error->getTitle()); $this->assertEquals('Form error "Form error for test"', $error->getDetail()); } diff --git a/tests/Unit/Services/Neomerx/ContainerAutowiringTest.php b/tests/Unit/Services/Neomerx/ContainerAutowiringTest.php index 6b50d1e..6b2a51f 100644 --- a/tests/Unit/Services/Neomerx/ContainerAutowiringTest.php +++ b/tests/Unit/Services/Neomerx/ContainerAutowiringTest.php @@ -6,6 +6,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Routing\RouterInterface; use Symfony\Component\Security\Http\RememberMe\RememberMeServicesInterface; +use Trikoder\JsonApiBundle\Schema\Autowire\Exception\UnresolvedDependencyException; use Trikoder\JsonApiBundle\Services\Neomerx\Container; use Trikoder\JsonApiBundle\Tests\Resources\JsonApi\Schema\CustomerSchema; use Trikoder\JsonApiBundle\Tests\Resources\JsonApi\Schema\Test\CrazySchema; @@ -75,7 +76,7 @@ public function testFailedResolving() { $container = new TestContainer($this->getServiceContainer(), $this->getSchemaFactory(), []); - $this->expectException(\RuntimeException::class); + $this->expectException(UnresolvedDependencyException::class); $this->expectExceptionMessage(sprintf('Cannot resolve argument %s for schema %s with hint %s. Did you forget to register service or alias?', 1, CrazySchema::class, RememberMeServicesInterface::class)); $schema = $container->createSchemaFromClassNameForTest(CrazySchema::class); diff --git a/tests/Unit/Services/RequestDecoderTest.php b/tests/Unit/Services/RequestDecoderTest.php index 76a4812..34b719d 100644 --- a/tests/Unit/Services/RequestDecoderTest.php +++ b/tests/Unit/Services/RequestDecoderTest.php @@ -2,7 +2,6 @@ namespace Trikoder\JsonApiBundle\Tests\Unit\Services; -use Monolog\Logger; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Session\SessionInterface; @@ -159,8 +158,6 @@ private function getRequestBodyDecoderMock() } /** - * @param JsonApiEnabledInterface $controller - * * @return RequestDecoder */ private function getRequestDecoder(JsonApiEnabledInterface $controller)