├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── src ├── CollectionDataProvider.php ├── DependencyInjection │ └── PommApiPlatformExtension.php ├── Filter │ ├── Filter.php │ ├── FilterInterface.php │ ├── PgType.php │ └── SearchFilter.php ├── ItemDataProvider.php ├── Paginator.php ├── PommApiPlatformBundle.php ├── Resources │ └── config │ │ └── pomm.yml └── WriteListener.php └── test ├── .gitignore ├── Makefile ├── app ├── AppKernel.php └── config │ ├── config.yml │ ├── config_dev.yml │ ├── config_prod.yml │ ├── parameters.yml.dist │ ├── routing.yml │ └── routing_dev.yml ├── behat.yml.dist ├── bin └── console ├── composer.json ├── src ├── AppBundle.php ├── Entity │ ├── AutoStructure │ │ ├── Config.php │ │ └── Filter.php │ ├── Config.php │ ├── ConfigModel.php │ ├── Filter.php │ └── FilterModel.php └── Resources │ └── config │ ├── api_resources │ └── resources.yml │ └── routing.yml ├── tests └── Features │ ├── filter.feature │ └── pomm.feature └── web ├── favicon.ico ├── index.php └── robots.txt /.gitignore: -------------------------------------------------------------------------------- 1 | /composer.lock 2 | /vendor/ 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | php: 6 | - "7.0" 7 | - "7.1" 8 | - "7.2" 9 | - "nightly" 10 | 11 | matrix: 12 | allow_failures: 13 | - php: nightly 14 | fast_finish: true 15 | 16 | cache: 17 | directories: 18 | - $HOME/.composer/cache 19 | 20 | services: postgresql 21 | 22 | before_install: 23 | - psql -c 'CREATE DATABASE pomm_test' -U postgres -h 127.0.0.1 postgres 24 | - psql -c 'CREATE TABLE config (name character varying(25) PRIMARY KEY, value character varying(25), status integer default 1)' -U postgres -h 127.0.0.1 pomm_test 25 | - psql -c 'CREATE TABLE filter (name character varying(25) PRIMARY KEY, value character varying(25), value_partial character varying(25), value_start character varying(25), value_end character varying(25), value_word_start character varying(25), value_ipartial character varying(25))' -U postgres -h 127.0.0.1 pomm_test 26 | 27 | - php -S localhost:8080 -t test/web &> /dev/null & 28 | - ln -fs parameters.yml.dist test/app/config/parameters.yml 29 | 30 | install: 31 | - composer install 32 | - cd test/ 33 | - composer install 34 | - rm -rf vendor/pomm-project/api-platform 35 | - ln -s ../../../ vendor/pomm-project/api-platform 36 | 37 | script: 38 | - ./bin/behat 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | Copyright (c) 2017 Sanpi 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pomm bridge for Api platform 2 | 3 | [![Build Status](https://travis-ci.org/pomm-project/pomm-api-platform.svg?branch=master)](https://travis-ci.org/pomm-project/pomm-api-platform) 4 | 5 | Use [Pomm](http://www.pomm-project.org/) with [Api platform](https://api-platform.com/). 6 | 7 | ## Filters 8 | 9 | ### Search Filter 10 | 11 | The search filter supports: 12 | 13 | * ```partial``` strategy uses ```LIKE %text%``` to search for fields that containing the text. 14 | * ```start strategy``` uses ```LIKE text%``` to search for fields that starts with text. 15 | * ```end strategy``` uses ```LIKE %text``` to search for fields that ends with text. 16 | * ```word_start``` strategy uses ```LIKE text% OR LIKE % text%``` to search for fields that contains the word starting with text. 17 | 18 | Prepend the letter ```i``` to the filter if you want it to be case insensitive. For example ```ipartial``` or ```iexact```. 19 | 20 | Add an entry in `services.yml`: 21 | 22 | ```yml 23 | services: 24 | app.book.search_filter: 25 | parent: 'api_platform.pomm.search_filter' 26 | arguments: [ { 'title': 'exact', 'description': 'partial'} ] 27 | tags: [ { name: 'api_platform.filter', id: 'book.search' } ] 28 | ``` 29 | 30 | And in `resources.yml` use the service filter: 31 | 32 | ```yml 33 | resources: 34 | AppBundle\Entity\Book: 35 | collectionOperations: 36 | get: 37 | filters: ['book.search'] 38 | ``` 39 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pomm-project/api-platform", 3 | "description": "Api platform bridge for pomm", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Sanpi", 9 | "email": "sanpi@homecomputing.fr" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=7.0", 14 | "api-platform/core": "~2.0", 15 | "pomm-project/pomm-bundle": "~2.4" 16 | }, 17 | "autoload": { 18 | "psr-4": { "PommProject\\ApiPlatform\\": "src/" } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/CollectionDataProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PommProject\ApiPlatform; 13 | 14 | use ApiPlatform\Core\Api\FilterLocatorTrait; 15 | use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface; 16 | use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; 17 | use ApiPlatform\Core\Metadata\Resource\Factory\ResourceMetadataFactoryInterface; 18 | use PommProject\ApiPlatform\Filter\FilterInterface; 19 | use PommProject\Foundation\Pomm; 20 | use PommProject\Foundation\Where; 21 | use PommProject\ModelManager\Model\Model; 22 | use Psr\Container\ContainerInterface; 23 | use Symfony\Component\HttpFoundation\Request; 24 | use Symfony\Component\HttpFoundation\RequestStack; 25 | 26 | class CollectionDataProvider implements CollectionDataProviderInterface 27 | { 28 | const PAGE_PARAMETER_NAME_DEFAULT = 'page'; 29 | 30 | use FilterLocatorTrait; 31 | 32 | private $pomm; 33 | private $requestStack; 34 | private $pagination; 35 | private $order; 36 | private $resourceMetadataFactory; 37 | 38 | public function __construct( 39 | Pomm $pomm, 40 | RequestStack $requestStack, 41 | ResourceMetadataFactoryInterface $resourceMetadataFactory, 42 | ContainerInterface $filterLocator, 43 | array $pagination, 44 | array $order 45 | ) { 46 | $this->pomm = $pomm; 47 | $this->requestStack = $requestStack; 48 | $this->pagination = $pagination; 49 | $this->order = $order; 50 | $this->resourceMetadataFactory = $resourceMetadataFactory; 51 | $this->setFilterLocator($filterLocator); 52 | } 53 | 54 | /** 55 | * {@inheritdoc} 56 | */ 57 | public function getCollection(string $resourceClass, string $operationName = null) 58 | { 59 | $request = $this->requestStack->getCurrentRequest(); 60 | if (null === $request) { 61 | return; 62 | } 63 | 64 | $modelName = "${resourceClass}Model"; 65 | if (!class_exists($modelName)) { 66 | throw new ResourceClassNotSupportedException(); 67 | } 68 | 69 | $session = $this->pomm->getDefaultSession(); 70 | $model = $session->getModel($modelName); 71 | $paginator = $model->paginateFindWhere( 72 | $this->getWhere($request, $model, $resourceClass), 73 | $this->getItemsPerPage($request), 74 | $this->getCurrentPage($request), 75 | $this->getOrderSuffix($request) 76 | ); 77 | 78 | return new Paginator($paginator); 79 | } 80 | 81 | private function getCurrentPage(Request $request): int 82 | { 83 | return $request->query->get( 84 | $this->pagination['page_parameter_name'] ?? static::PAGE_PARAMETER_NAME_DEFAULT, 85 | 1 86 | ); 87 | } 88 | 89 | private function getItemsPerPage(Request $request): int 90 | { 91 | return $request->query->get( 92 | $this->pagination['items_per_page_parameter_name'], 93 | $this->pagination['items_per_page'] 94 | ); 95 | } 96 | 97 | private function getOrderSuffix(Request $request): string 98 | { 99 | 100 | $properties = $request->query->get($this->order['order_parameter_name']); 101 | if ($properties === null) { 102 | return ''; 103 | } 104 | 105 | $suffix = []; 106 | foreach ($properties as $property => $order) { 107 | $suffix[] = "$property $order"; 108 | } 109 | 110 | return 'order by ' . implode(',', $suffix); 111 | } 112 | 113 | private function getWhere(Request $request, Model $model, string $resourceClass): Where 114 | { 115 | $properties = $model->getStructure() 116 | ->getFieldNames(); 117 | $where = new Where(); 118 | 119 | foreach ($properties as $property) { 120 | if ($request->query->has($property)) { 121 | $value = $request->query->get($property); 122 | $where = $this->getClauseFilter($resourceClass, 'get', $where, $property, $value); 123 | } 124 | } 125 | 126 | return $where; 127 | } 128 | 129 | private function getClauseFilter(string $resourceClass, string $operationName, Where $where, string $property, $value): Where 130 | { 131 | $resourceMetadata = $this->resourceMetadataFactory->create($resourceClass); 132 | $resourceFilters = $resourceMetadata->getCollectionOperationAttribute($operationName, 'filters', [], true); 133 | 134 | if (empty($resourceFilters)) { 135 | return $where; 136 | } 137 | 138 | foreach ($resourceFilters as $filterName) { 139 | $filter = $this->getFilter($filterName); 140 | if ($filter instanceof FilterInterface) { 141 | $where = $filter->addClause($property, $value, $resourceClass, $where); 142 | } elseif ($property === $filterName) { 143 | $where->andWhere("$property = \$*", [$value]); 144 | } 145 | } 146 | 147 | return $where; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/DependencyInjection/PommApiPlatformExtension.php: -------------------------------------------------------------------------------- 1 | load('pomm.yml'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Filter/Filter.php: -------------------------------------------------------------------------------- 1 | 17 | */ 18 | abstract class Filter 19 | { 20 | protected $pomm; 21 | 22 | protected $properties; 23 | 24 | public function __construct(Pomm $pomm, array $properties = []) 25 | { 26 | $this->pomm = $pomm; 27 | $this->properties = $properties; 28 | } 29 | 30 | protected function getTypePgForProperty(string $property, string $resourceClass): string 31 | { 32 | $structure = $this->getStructureForResourceClass($resourceClass); 33 | 34 | try{ 35 | return $structure->getTypeFor($property); 36 | }catch (ModelException $e) { 37 | return 'varchar'; 38 | } 39 | } 40 | 41 | protected function getFieldNamesForResource(string $resourceClass): array 42 | { 43 | $structure = $this->getStructureForResourceClass($resourceClass); 44 | 45 | return $structure->getFieldNames(); 46 | } 47 | 48 | private function getStructureForResourceClass(string $resourceClass): RowStructure 49 | { 50 | $modelName = "${resourceClass}Model"; 51 | $session = $this->pomm->getDefaultSession(); 52 | 53 | return $session->getModel($modelName)->getStructure(); 54 | } 55 | } -------------------------------------------------------------------------------- /src/Filter/FilterInterface.php: -------------------------------------------------------------------------------- 1 | 16 | */ 17 | interface FilterInterface extends BaseFilterInterface 18 | { 19 | 20 | public function addClause(string $property, $value, string $resourceClass, Where $where): Where; 21 | } -------------------------------------------------------------------------------- /src/Filter/PgType.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class PgType 15 | { 16 | const ARRAY = [ 17 | 'array' 18 | ]; 19 | 20 | const BOOLEAN = [ 21 | 'bool', 22 | 'pg_catalog.bool', 23 | 'boolean' 24 | ]; 25 | 26 | const INTEGER = [ 27 | 'int2', 28 | 'pg_catalog.int2', 29 | 'int4', 30 | 'pg_catalog.int4', 31 | 'int', 32 | 'integer', 33 | 'int8', 34 | 'pg_catalog.int8' 35 | ]; 36 | 37 | const FLOAT = [ 38 | 'numeric', 39 | 'pg_catalog.numeric', 40 | 'float4', 41 | 'pg_catalog.float4', 42 | 'float', 43 | 'float8', 44 | 'pg_catalog.float8', 45 | 'oid', 46 | 'pg_catalog.oid' 47 | ]; 48 | 49 | const STRING = [ 50 | 'varchar', 'pg_catalog.varchar', 51 | 'char', 'pg_catalog.char', 52 | 'text', 'pg_catalog.text', 53 | 'uuid', 'pg_catalog.uuid', 54 | 'tsvector', 'pg_catalog.tsvector', 55 | 'xml', 'pg_catalog.xml', 56 | 'bpchar', 'pg_catalog.bpchar', 57 | 'name', 'pg_catalog.name', 58 | 'character varying', 59 | 'regclass', 'pg_catalog.regclass', 60 | 'regproc', 'pg_catalog.regproc', 61 | 'regprocedure', 'pg_catalog.regprocedure', 62 | 'regoper', 'pg_catalog.regoper', 63 | 'regoperator', 'pg_catalog.regoperator', 64 | 'regtype', 'pg_catalog.regtype', 65 | 'regrole', 'pg_catalog.regrole', 66 | 'regnamespace', 'pg_catalog.regnamespace', 67 | 'regconfig', 'pg_catalog.regconfig', 68 | 'regdictionary', 'pg_catalog.regdictionary', 69 | 'inet', 'pg_catalog.inet', 70 | 'cidr', 'pg_catalog.cidr', 71 | 'macaddr', 'pg_catalog.macaddr' 72 | ]; 73 | 74 | const TIMESTAMP = [ 75 | 'timestamp', 'pg_catalog.timestamp', 76 | 'date', 'pg_catalog.date', 77 | 'time', 'pg_catalog.time', 78 | 'timestamptz', 'pg_catalog.timestamptz' 79 | ]; 80 | 81 | const INTERVAL = [ 82 | 'interval', 83 | 'pg_catalog.interval' 84 | ]; 85 | 86 | const BINARY = [ 87 | 'bytea', 88 | 'pg_catalog.bytea' 89 | ]; 90 | 91 | const POINT = [ 92 | 'point', 93 | 'pg_catalog.point' 94 | ]; 95 | 96 | const CIRCLE = [ 97 | 'circle', 98 | 'pg_catalog.circle' 99 | ]; 100 | 101 | const JSON = [ 102 | 'json', 103 | 'jsonb', 104 | 'pg_catalog.json', 105 | 'pg_catalog.jsonb' 106 | ]; 107 | 108 | const NUMBER_RANGE = [ 109 | 'int4range', 110 | 'pg_catalog.int4range', 111 | 'int8range', 112 | 'pg_catalog.int8range', 113 | 'numrange', 114 | 'pg_catalog.numrange' 115 | ]; 116 | 117 | const TS_RANGE = [ 118 | 'tsrange', 119 | 'pg_catalog.tsrange', 120 | 'daterange', 121 | 'pg_catalog.daterange', 122 | 'tstzrange', 123 | 'pg_catalog.tstzrange' 124 | ]; 125 | 126 | const ARRAY_TYPE = 'array'; 127 | const BOOLEAN_TYPE = 'bool'; 128 | const INTEGER_TYPE = 'int'; 129 | const FLOAT_TYPE = 'float'; 130 | const STRING_TYPE = 'string'; 131 | const TIMESTAMP_TYPE = 'timestamp'; 132 | const INTERVAL_TYPE = 'interval'; 133 | const BINARY_TYPE = 'binary'; 134 | const POINT_TYPE = 'point'; 135 | const CIRCLE_TYPE = 'circle'; 136 | const JSON_TYPE = 'json'; 137 | const NUMBER_RANGE_TYPE = 'number_range'; 138 | const TS_RANGE_TYPE = 'ts_range'; 139 | 140 | const TYPE_AVAILABLE = [ 141 | 'ARRAY', 'BOOLEAN', 'INTEGER', 'FLOAT', 'STRING', 142 | 'TIMESTAMP', 'INTERVAL', 'BINARY', 'POINT', 'CIRCLE', 143 | 'JSON', 'NUMBER_RANGE', 'TS_RANGE' 144 | ]; 145 | 146 | public static function getTypePhp(string $typePg): string 147 | { 148 | foreach (self::TYPE_AVAILABLE as $type) { 149 | $valuesType = constant('self::' . $type); 150 | 151 | if (in_array($typePg, $valuesType)) { 152 | return constant('self::' . $type . '_TYPE'); 153 | } 154 | } 155 | 156 | return 'string'; 157 | } 158 | 159 | } -------------------------------------------------------------------------------- /src/Filter/SearchFilter.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | class SearchFilter extends Filter implements FilterInterface 14 | { 15 | /** 16 | * @var string Exact matching 17 | */ 18 | const STRATEGY_EXACT = 'exact'; 19 | 20 | /** 21 | * @var string The value must be contained in the field 22 | */ 23 | const STRATEGY_PARTIAL = 'partial'; 24 | 25 | /** 26 | * @var string Finds fields that are starting with the value 27 | */ 28 | const STRATEGY_START = 'start'; 29 | 30 | /** 31 | * @var string Finds fields that are ending with the value 32 | */ 33 | const STRATEGY_END = 'end'; 34 | 35 | /** 36 | * @var string Finds fields that are starting with the word 37 | */ 38 | const STRATEGY_WORD_START = 'word_start'; 39 | 40 | public function getDescription(string $resourceClass): array 41 | { 42 | $description = []; 43 | 44 | foreach ($this->properties as $property => $strategy) 45 | { 46 | if ($strategy === null) { 47 | $strategy = self::STRATEGY_EXACT; 48 | } 49 | 50 | $description[$property] = [ 51 | 'property' => $property, 52 | 'strategy' => $strategy, 53 | 'type' => $this->getTypePhp($property, $resourceClass), 54 | 'required' => false 55 | ]; 56 | 57 | if (self::STRATEGY_EXACT === $strategy) { 58 | $description[$property.'[]'] = $description[$property]; 59 | } 60 | } 61 | 62 | return $description; 63 | } 64 | 65 | public function addClause(string $property, $value, string $resourceClass, Where $where): Where 66 | { 67 | if (array_key_exists($property, $this->properties)) { 68 | $strategy = $this->properties[$property]; 69 | if ($strategy === null) { 70 | $strategy = self::STRATEGY_EXACT; 71 | } 72 | 73 | $where = $this->andWhereByStrategy($property, $value, $strategy, $where); 74 | } 75 | 76 | return $where; 77 | } 78 | 79 | protected function andWhereByStrategy(string $property, $value, string $strategy = self::STRATEGY_EXACT, Where $where): Where 80 | { 81 | $operator = 'like'; 82 | $caseSensitive = true; 83 | 84 | if (0 === strpos($strategy, 'i')) { 85 | $strategy = substr($strategy, 1); 86 | $operator = 'ilike'; 87 | $caseSensitive = false; 88 | } 89 | 90 | switch ($strategy) { 91 | case self::STRATEGY_EXACT: 92 | if (!$caseSensitive) { 93 | $property = sprintf('lower(%s)', $property); 94 | $value = sprintf('lower(%s)', $value); 95 | } 96 | 97 | if (is_array($value)) { 98 | $whereIn = Where::createWhereIn($property, $value); 99 | $where->andWhere($whereIn); 100 | } else { 101 | $where->andWhere("$property = \$*", [$value]); 102 | } 103 | break; 104 | case self::STRATEGY_PARTIAL: 105 | $where->andWhere("$property $operator \$*", ["%$value%"]); 106 | break; 107 | case self::STRATEGY_START: 108 | $where->andWhere("$property $operator \$*", ["$value%"]); 109 | break; 110 | case self::STRATEGY_END: 111 | $where->andWhere("$property $operator \$*", ["%$value"]); 112 | break; 113 | case self::STRATEGY_WORD_START: 114 | $where->andWhere("$property $operator \$* or $property $operator \$*", ["$value%", "% $value%"]); 115 | break; 116 | default: 117 | throw new InvalidArgumentException(sprintf('strategy %s does not exist.', $strategy)); 118 | } 119 | 120 | return $where; 121 | } 122 | 123 | private function getTypePhp(string $property, string $resourceClass): string 124 | { 125 | $typePg = $this->getTypePgForProperty($property, $resourceClass); 126 | 127 | return PgType::getTypePhp($typePg); 128 | } 129 | } -------------------------------------------------------------------------------- /src/ItemDataProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PommProject\ApiPlatform; 13 | 14 | use ApiPlatform\Core\DataProvider\ItemDataProviderInterface; 15 | use ApiPlatform\Core\Exception\ResourceClassNotSupportedException; 16 | use PommProject\Foundation\Pomm; 17 | 18 | class ItemDataProvider implements ItemDataProviderInterface 19 | { 20 | private $pomm; 21 | 22 | public function __construct(Pomm $pomm) 23 | { 24 | $this->pomm = $pomm; 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 31 | { 32 | if (isset($context['session:name'])) { 33 | $session = $this->pomm->getSession($context['session:name']); 34 | } else { 35 | $session = $this->pomm->getDefaultSession(); 36 | } 37 | 38 | if (isset($context['model:name'])) { 39 | $modelName = $context['model:name']; 40 | } else { 41 | $modelName = "${resourceClass}Model"; 42 | } 43 | 44 | if (!class_exists($modelName)) { 45 | throw new ResourceClassNotSupportedException(); 46 | } 47 | 48 | $model = $session->getModel($modelName); 49 | $primaryKeys = $model->getStructure() 50 | ->getPrimaryKey(); 51 | 52 | return $model->findByPk([$primaryKeys[0] => $id]); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Paginator.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PommProject\ApiPlatform; 13 | 14 | use ApiPlatform\Core\DataProvider\PaginatorInterface; 15 | use PommProject\Foundation\Pager; 16 | 17 | /** 18 | * Extends the Pomm pager. 19 | * 20 | * @author Kévin Dunglas 21 | */ 22 | final class Paginator implements \IteratorAggregate, PaginatorInterface 23 | { 24 | private $paginator; 25 | 26 | public function __construct(Pager $paginator) 27 | { 28 | $this->paginator = $paginator; 29 | } 30 | 31 | public function getCurrentPage(): float 32 | { 33 | return $this->paginator->getPage(); 34 | } 35 | 36 | public function getLastPage(): float 37 | { 38 | return $this->paginator->getLastPage(); 39 | } 40 | 41 | public function getItemsPerPage(): float 42 | { 43 | return $this->paginator->getMaxPerPage(); 44 | } 45 | 46 | public function getTotalItems(): float 47 | { 48 | return $this->paginator->getResultCount(); 49 | } 50 | 51 | public function count() 52 | { 53 | return $this->paginator->getTotalItems(); 54 | } 55 | 56 | public function getIterator() 57 | { 58 | return $this->paginator->getIterator(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/PommApiPlatformBundle.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace PommProject\ApiPlatform; 13 | 14 | use ApiPlatform\Core\Exception\ItemNotFoundException; 15 | use PommProject\Foundation\Pomm; 16 | use Symfony\Component\HttpFoundation\Request; 17 | use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; 18 | 19 | final class WriteListener 20 | { 21 | private $pomm; 22 | 23 | public function __construct(Pomm $pomm) 24 | { 25 | $this->pomm = $pomm; 26 | } 27 | 28 | public function onKernelView(GetResponseForControllerResultEvent $event) 29 | { 30 | $request = $event->getRequest(); 31 | if ($request->isMethodSafe(false)) { 32 | return; 33 | } 34 | 35 | $model = $this->getModel($request); 36 | if (null === $model) { 37 | return; 38 | } 39 | 40 | $entity = $event->getControllerResult(); 41 | 42 | $action_handled = false; 43 | switch ($request->getMethod()) { 44 | case Request::METHOD_POST: 45 | $model->insertOne($entity); 46 | $action_handled = true; 47 | break; 48 | 49 | case Request::METHOD_PUT: 50 | $fields = array_keys($entity->fields()); 51 | $model->updateOne($entity, $fields); 52 | $action_handled = true; 53 | break; 54 | 55 | case Request::METHOD_DELETE: 56 | $model->deleteOne($entity); 57 | $action_handled = true; 58 | break; 59 | } 60 | 61 | if ($action_handled) { 62 | $this->setResult($event, $entity); 63 | } 64 | } 65 | 66 | private function getModel(Request $request) 67 | { 68 | $resourceClass = $request->attributes->get('_api_resource_class'); 69 | if (null === $resourceClass) { 70 | return; 71 | } 72 | 73 | $sessionName = $request->attributes->get('_pomm_session_name'); 74 | if (null === $sessionName) { 75 | $session = $this->pomm->getDefaultSession(); 76 | } else { 77 | $session = $this->pomm->getSession($sessionName); 78 | } 79 | 80 | $modelName = $request->attributes->get('_pomm_model_name'); 81 | if (null === $modelName) { 82 | $modelName = "${resourceClass}Model"; 83 | } 84 | 85 | return $session->getModel($modelName); 86 | } 87 | 88 | /** 89 | * setResult 90 | * 91 | * @param GetResponseForControllerResultEvent $event 92 | * @param \PommProject\ModelManager\Model\FlexibleEntity|null $entity 93 | * @return WriteListener 94 | */ 95 | private function setResult(GetResponseForControllerResultEvent $event, $entity): self 96 | { 97 | if (null === $entity) { 98 | throw new ItemNotFoundException(); 99 | } 100 | 101 | $event->setControllerResult($entity); 102 | 103 | return $this; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | /app/config/parameters.yml 2 | /bin/* 3 | !bin/console 4 | /composer.phar 5 | /composer.lock 6 | /src/Resources/public/ 7 | /var/* 8 | /vendor/ 9 | /web/css/ 10 | /web/images/ 11 | /web/js/ 12 | /web/bundles/ 13 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | BOWER_FLAGS= 2 | COMPOSER_FLAGS=--no-interaction 3 | 4 | ifeq ($(APP_ENVIRONMENT),prod) 5 | BOWER_FLAGS+=--production 6 | COMPOSER_FLAGS+=--prefer-dist --no-dev --classmap-authoritative 7 | endif 8 | 9 | TASKS= 10 | ifneq ("$(wildcard composer.json)","") 11 | TASKS+=vendor 12 | endif 13 | 14 | ifneq ("$(wildcard bower.json)","") 15 | TASKS+=assets 16 | endif 17 | 18 | all: $(TASKS) 19 | 20 | vendor: composer.lock 21 | 22 | composer.lock: composer.json 23 | composer install $(COMPOSER_FLAGS) 24 | 25 | assets: src/Resources/public/lib 26 | bin/console cache:clear 27 | bin/console assets:install --symlink --relative web 28 | bin/console assetic:dump 29 | 30 | src/Resources/public/lib: bower.json 31 | bower install $(BOWER_FLAGS) 32 | 33 | distclean: 34 | rm -rf vendor composer.lock src/Resources/public/lib 35 | 36 | .PHONY: all assets distclean 37 | -------------------------------------------------------------------------------- /test/app/AppKernel.php: -------------------------------------------------------------------------------- 1 | getEnvironment() === 'dev') { 35 | $bundles[] = new \Symfony\Bundle\DebugBundle\DebugBundle(); 36 | $bundles[] = new \Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); 37 | } 38 | 39 | return $bundles; 40 | } 41 | 42 | public function getRootDir(): string 43 | { 44 | return __DIR__; 45 | } 46 | 47 | public function getCacheDir(): string 48 | { 49 | return __DIR__ . '/../var/cache/' . $this->getEnvironment(); 50 | } 51 | 52 | public function getLogDir(): string 53 | { 54 | return __DIR__ . '/../var/logs/' . $this->getEnvironment(); 55 | } 56 | 57 | public function registerContainerConfiguration(LoaderInterface $loader) 58 | { 59 | $loader->load(__DIR__ . '/config/config_' . $this->getEnvironment() . '.yml'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/app/config/config.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: parameters.yml } 3 | 4 | parameters: 5 | session_name: symbiose 6 | 7 | framework: 8 | assets: ~ 9 | #esi: ~ 10 | #translator: { fallback: "%locale%" } 11 | secret: "%secret%" 12 | router: 13 | resource: "%kernel.root_dir%/config/routing.yml" 14 | strict_requirements: ~ 15 | form: ~ 16 | csrf_protection: ~ 17 | validation: { enable_annotations: true } 18 | templating: 19 | engines: ['twig'] 20 | #assets_version: SomeVersionScheme 21 | default_locale: "%locale%" 22 | trusted_hosts: ~ 23 | trusted_proxies: ~ 24 | session: 25 | # handler_id set to null will use default session handler from php.ini 26 | handler_id: ~ 27 | name: "%session_name%" 28 | fragments: ~ 29 | http_method_override: true 30 | 31 | # Twig Configuration 32 | twig: 33 | debug: "%kernel.debug%" 34 | strict_variables: "%kernel.debug%" 35 | 36 | # Assetic Configuration 37 | assetic: 38 | debug: "%kernel.debug%" 39 | use_controller: "%kernel.debug%" 40 | bundles: [ 'AppBundle' ] 41 | #java: /usr/bin/java 42 | filters: 43 | cssrewrite: ~ 44 | #closure: 45 | # jar: "%kernel.root_dir%/Resources/java/compiler.jar" 46 | #yui_css: 47 | # jar: "%kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar" 48 | 49 | # Logging configuration 50 | monolog: 51 | handlers: 52 | file_log: 53 | type: stream 54 | path: "%kernel.logs_dir%/%kernel.environment%.log" 55 | level: debug 56 | 57 | pomm: 58 | configuration: 59 | default: 60 | dsn: "%database_dsn%" 61 | logger: 62 | service: "@logger" 63 | 64 | api_platform: 65 | title: 'Pomm test' 66 | collection: 67 | pagination: 68 | page_parameter_name: myPage 69 | items_per_page_parameter_name: myItemsPerPage 70 | 71 | services: 72 | app.filter.search_filter: 73 | parent: 'api_platform.pomm.search_filter' 74 | arguments: 75 | - { 'value': ~, 'value_partial': 'partial', 'value_start': 'start', 'value_end': 'end', 'value_word_start': 'word_start', 'value_ipartial': 'ipartial' } 76 | tags: 77 | - { name: 'api_platform.filter', id: 'filter.search' } 78 | -------------------------------------------------------------------------------- /test/app/config/config_dev.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: config.yml } 3 | 4 | framework: 5 | router: 6 | resource: "%kernel.root_dir%/config/routing_dev.yml" 7 | strict_requirements: true 8 | profiler: { only_exceptions: false } 9 | 10 | web_profiler: 11 | toolbar: true 12 | intercept_redirects: false 13 | -------------------------------------------------------------------------------- /test/app/config/config_prod.yml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: config.yml } 3 | 4 | #framework: 5 | # validation: 6 | # cache: apc 7 | -------------------------------------------------------------------------------- /test/app/config/parameters.yml.dist: -------------------------------------------------------------------------------- 1 | # This file is auto-generated during the composer install 2 | parameters: 3 | locale: en 4 | secret: ThisTokenIsNotSoSecretChangeIt 5 | database_dsn: pgsql://postgres:12345@127.0.0.1:5432/pomm_test 6 | -------------------------------------------------------------------------------- /test/app/config/routing.yml: -------------------------------------------------------------------------------- 1 | app: 2 | resource: "@AppBundle/Resources/config/routing.yml" 3 | 4 | api: 5 | resource: '.' 6 | type: 'api_platform' 7 | prefix: '/api' 8 | -------------------------------------------------------------------------------- /test/app/config/routing_dev.yml: -------------------------------------------------------------------------------- 1 | _wdt: 2 | resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml" 3 | prefix: /_wdt 4 | 5 | _profiler: 6 | resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml" 7 | prefix: /_profiler 8 | 9 | _pomm: 10 | resource: "@PommBundle/Resources/config/routing.yml" 11 | prefix: /_pomm 12 | 13 | _main: 14 | resource: routing.yml 15 | -------------------------------------------------------------------------------- /test/behat.yml.dist: -------------------------------------------------------------------------------- 1 | default: 2 | suites: 3 | default: 4 | paths: [ %paths.base%/tests/Features ] 5 | contexts: 6 | - Behat\MinkExtension\Context\MinkContext 7 | - behatch:context:json 8 | - behatch:context:rest 9 | extensions: 10 | Behat\MinkExtension\ServiceContainer\MinkExtension: 11 | base_url: 'http://localhost:8080' 12 | goutte: ~ 13 | Behatch\Extension: ~ 14 | -------------------------------------------------------------------------------- /test/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], getenv('APP_ENVIRONMENT') ?: 'dev'); 22 | $debug = getenv('APP_DEBUG') !== '0' && !$input->hasParameterOption(['--no-debug', '']) && $env !== 'prod'; 23 | 24 | if ($debug) { 25 | Debug::enable(); 26 | } 27 | 28 | $kernel = new AppKernel($env, $debug); 29 | $application = new Application($kernel); 30 | $application->run($input); 31 | -------------------------------------------------------------------------------- /test/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "php": ">=7.0", 4 | "api-platform/core": "~2.0", 5 | "symfony/symfony": "~3.0", 6 | "symfony/assetic-bundle": "~2.7", 7 | "symfony/monolog-bundle": "~3.0", 8 | "pomm-project/pomm-bundle": "~2.4", 9 | "pomm-project/api-platform": "dev-master" 10 | }, 11 | "require-dev": { 12 | "behatch/contexts": "~2.6", 13 | "behat/mink-goutte-driver": "*" 14 | }, 15 | "autoload": { 16 | "psr-4": { "AppBundle\\": "src/" }, 17 | "classmap": [ "app/AppKernel.php" ] 18 | }, 19 | "config": { 20 | "bin-dir": "bin" 21 | }, 22 | "extra": { 23 | "symfony-app-dir": "app", 24 | "symfony-bin-dir": "bin", 25 | "symfony-var-dir": "var", 26 | "symfony-web-dir": "web", 27 | "symfony-assets-install": "relative" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /test/src/AppBundle.php: -------------------------------------------------------------------------------- 1 | setRelation('public.config') 38 | ->setPrimaryKey(['name']) 39 | ->addField('name', 'varchar') 40 | ->addField('value', 'varchar') 41 | ->addField('status', 'int') 42 | ; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/src/Entity/AutoStructure/Filter.php: -------------------------------------------------------------------------------- 1 | setRelation('public.filter') 40 | ->setPrimaryKey(['name']) 41 | ->addField('name', 'varchar') 42 | ->addField('value', 'varchar') 43 | ->addField('value_partial', 'varchar') 44 | ->addField('value_start', 'varchar') 45 | ->addField('value_end', 'varchar') 46 | ->addField('value_word_start', 'varchar') 47 | ->addField('value_ipartial', 'varchar') 48 | ; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/src/Entity/Config.php: -------------------------------------------------------------------------------- 1 | structure = new ConfigStructure; 35 | $this->flexible_entity_class = '\AppBundle\Entity\Config'; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/src/Entity/Filter.php: -------------------------------------------------------------------------------- 1 | structure = new FilterStructure; 37 | $this->flexible_entity_class = Filter::class; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/src/Resources/config/api_resources/resources.yml: -------------------------------------------------------------------------------- 1 | resources: 2 | AppBundle\Entity\Config: 3 | collectionOperations: 4 | get: 5 | filters: ['value'] 6 | post: ~ 7 | AppBundle\Entity\Filter: 8 | collectionOperations: 9 | get: 10 | filters: ['filter.search'] 11 | post: ~ 12 | -------------------------------------------------------------------------------- /test/src/Resources/config/routing.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomm-project/pomm-api-platform/f89e887be8d5739227aa0366ff736803c1f4b042/test/src/Resources/config/routing.yml -------------------------------------------------------------------------------- /test/tests/Features/filter.feature: -------------------------------------------------------------------------------- 1 | Feature: Pomm filter for api platform 2 | Background: 3 | Given I add "accept" header equal to "application/json" 4 | 5 | Scenario Outline: Create a resource 6 | Given I add "Content-Type" header equal to "application/ld+json" 7 | When I send a POST request to "/api/filters" with body: 8 | """ 9 | { 10 | "name": "", 11 | "value": "", 12 | "value_partial": "", 13 | "value_start": "", 14 | "value_end": "", 15 | "value_word_start": "", 16 | "value_ipartial": "" 17 | } 18 | """ 19 | 20 | Then the response status code should be 201 21 | And the JSON should be equal to: 22 | """ 23 | { 24 | "name": "", 25 | "value": "", 26 | "value_partial": "", 27 | "value_start": "", 28 | "value_end": "", 29 | "value_word_start": "", 30 | "value_ipartial": "" 31 | } 32 | """ 33 | 34 | Examples: 35 | | name | value | value_partial | value_start | value_end | value_word_start | value_ipartial | 36 | | test1 | value1 | test | test | test_value | test_value | TEST_VALUE_TEST | 37 | | test2 | value2 | test | value_test | value_test | test value_test | test | 38 | | test3 | value3 | test_value_test | test_value | test_value_test | test test_value | test | 39 | | test4 | value4 | test | test | test | value_test test | test | 40 | 41 | Scenario: Retreive resources with search filter and strategy exact 42 | When I send a GET request to "/api/filters?value=value1" 43 | Then the JSON should be equal to: 44 | """ 45 | [ 46 | { 47 | "name": "test1", 48 | "value": "value1", 49 | "value_partial": "test", 50 | "value_start": "test", 51 | "value_end": "test_value", 52 | "value_word_start": "test_value", 53 | "value_ipartial": "TEST_VALUE_TEST" 54 | } 55 | ] 56 | """ 57 | 58 | Scenario: Retreive resources with search multiple filter 59 | When I send a GET request to "/api/filters?value[]=value1&value[]=value2" 60 | Then the JSON should be equal to: 61 | """ 62 | [ 63 | { 64 | "name": "test1", 65 | "value": "value1", 66 | "value_partial": "test", 67 | "value_start": "test", 68 | "value_end": "test_value", 69 | "value_word_start": "test_value", 70 | "value_ipartial": "TEST_VALUE_TEST" 71 | }, 72 | { 73 | "name": "test2", 74 | "value": "value2", 75 | "value_partial": "test", 76 | "value_start": "value_test", 77 | "value_end": "value_test", 78 | "value_word_start": "test value_test", 79 | "value_ipartial": "test" 80 | } 81 | ] 82 | """ 83 | 84 | Scenario: Retreive resources with search filter and strategy partial 85 | When I send a GET request to "/api/filters?value_partial=value" 86 | Then the JSON should be equal to: 87 | """ 88 | [ 89 | { 90 | "name": "test3", 91 | "value": "value3", 92 | "value_partial": "test_value_test", 93 | "value_start": "test_value", 94 | "value_end": "test_value_test", 95 | "value_word_start": "test test_value", 96 | "value_ipartial": "test" 97 | } 98 | ] 99 | """ 100 | 101 | Scenario: Retreive resources with search filter and strategy start 102 | When I send a GET request to "/api/filters?value_start=value" 103 | Then the JSON should be equal to: 104 | """ 105 | [ 106 | { 107 | "name": "test2", 108 | "value": "value2", 109 | "value_partial": "test", 110 | "value_start": "value_test", 111 | "value_end": "value_test", 112 | "value_word_start": "test value_test", 113 | "value_ipartial": "test" 114 | } 115 | ] 116 | """ 117 | 118 | Scenario: Retreive resources with search filter and strategy end 119 | When I send a GET request to "/api/filters?value_end=value" 120 | Then the JSON should be equal to: 121 | """ 122 | [ 123 | { 124 | "name": "test1", 125 | "value": "value1", 126 | "value_partial": "test", 127 | "value_start": "test", 128 | "value_end": "test_value", 129 | "value_word_start": "test_value", 130 | "value_ipartial": "TEST_VALUE_TEST" 131 | } 132 | ] 133 | """ 134 | 135 | Scenario: Retreive resources with search filter and strategy word start 136 | When I send a GET request to "/api/filters?value_word_start=value" 137 | Then the JSON should be equal to: 138 | """ 139 | [ 140 | { 141 | "name": "test2", 142 | "value": "value2", 143 | "value_partial": "test", 144 | "value_start": "value_test", 145 | "value_end": "value_test", 146 | "value_word_start": "test value_test", 147 | "value_ipartial": "test" 148 | }, 149 | { 150 | "name": "test4", 151 | "value": "value4", 152 | "value_partial": "test", 153 | "value_start": "test", 154 | "value_end": "test", 155 | "value_word_start": "value_test test", 156 | "value_ipartial": "test" 157 | } 158 | ] 159 | """ 160 | 161 | Scenario: Retreive resources with search filter not declared 162 | When I send a GET request to "/api/filters?name=test1" 163 | Then the JSON should be equal to: 164 | """ 165 | [ 166 | { 167 | "name": "test1", 168 | "value": "value1", 169 | "value_partial": "test", 170 | "value_start": "test", 171 | "value_end": "test_value", 172 | "value_word_start": "test_value", 173 | "value_ipartial": "TEST_VALUE_TEST" 174 | }, 175 | { 176 | "name": "test2", 177 | "value": "value2", 178 | "value_partial": "test", 179 | "value_start": "value_test", 180 | "value_end": "value_test", 181 | "value_word_start": "test value_test", 182 | "value_ipartial": "test" 183 | }, 184 | { 185 | "name": "test3", 186 | "value": "value3", 187 | "value_partial": "test_value_test", 188 | "value_start": "test_value", 189 | "value_end": "test_value_test", 190 | "value_word_start": "test test_value", 191 | "value_ipartial": "test" 192 | }, 193 | { 194 | "name": "test4", 195 | "value": "value4", 196 | "value_partial": "test", 197 | "value_start": "test", 198 | "value_end": "test", 199 | "value_word_start": "value_test test", 200 | "value_ipartial": "test" 201 | } 202 | ] 203 | """ 204 | Scenario: Retreive resources with search filter and strategy ipartial 205 | When I send a GET request to "/api/filters?value_ipartial=VaLuE" 206 | Then the JSON should be equal to: 207 | """ 208 | [ 209 | { 210 | "name": "test1", 211 | "value": "value1", 212 | "value_partial": "test", 213 | "value_start": "test", 214 | "value_end": "test_value", 215 | "value_word_start": "test_value", 216 | "value_ipartial": "TEST_VALUE_TEST" 217 | } 218 | ] 219 | """ -------------------------------------------------------------------------------- /test/tests/Features/pomm.feature: -------------------------------------------------------------------------------- 1 | Feature: Pomm bridge for api platform 2 | Background: 3 | Given I add "accept" header equal to "application/json" 4 | 5 | Scenario: Index 6 | When I send a GET request to "/api" 7 | Then the JSON should be equal to: 8 | """ 9 | { 10 | "resourceNameCollection": [ 11 | "AppBundle\\Entity\\Config", 12 | "AppBundle\\Entity\\Filter" 13 | ] 14 | } 15 | """ 16 | 17 | Scenario: Retreive an empty collection 18 | When I send a GET request to "/api/configs" 19 | Then the JSON should be equal to: 20 | """ 21 | [] 22 | """ 23 | 24 | Scenario Outline: Create a resource 25 | Given I add "Content-Type" header equal to "application/ld+json" 26 | When I send a POST request to "/api/configs" with body: 27 | """ 28 | { 29 | "name": "", 30 | "value": "" 31 | } 32 | """ 33 | 34 | Then the response status code should be 201 35 | And the JSON should be equal to: 36 | """ 37 | { 38 | "name": "", 39 | "value": "", 40 | "status": 1 41 | } 42 | """ 43 | 44 | Examples: 45 | | name | value | 46 | | test1 | value1 | 47 | | test2 | value2 | 48 | | test3 | value3 | 49 | | test4 | value4 | 50 | 51 | Scenario: Retreive a collection 52 | When I send a GET request to "/api/configs" 53 | Then the JSON should be equal to: 54 | """ 55 | [ 56 | { 57 | "name": "test1", 58 | "value": "value1", 59 | "status": 1 60 | }, 61 | { 62 | "name": "test2", 63 | "value": "value2", 64 | "status": 1 65 | }, 66 | { 67 | "name": "test3", 68 | "value": "value3", 69 | "status": 1 70 | }, 71 | { 72 | "name": "test4", 73 | "value": "value4", 74 | "status": 1 75 | } 76 | ] 77 | """ 78 | 79 | Scenario: Retreive resources order by desc 80 | When I send a GET request to "/api/configs?order[value]=desc" 81 | Then the JSON should be equal to: 82 | """ 83 | [ 84 | { 85 | "name": "test4", 86 | "value": "value4", 87 | "status": 1 88 | }, 89 | { 90 | "name": "test3", 91 | "value": "value3", 92 | "status": 1 93 | }, 94 | { 95 | "name": "test2", 96 | "value": "value2", 97 | "status": 1 98 | }, 99 | { 100 | "name": "test1", 101 | "value": "value1", 102 | "status": 1 103 | } 104 | ] 105 | """ 106 | 107 | Scenario: Retreive resources using pagination 108 | When I send a GET request to "/api/configs?order[value]=asc&myPage=2&myItemsPerPage=3" 109 | Then the JSON should be equal to: 110 | """ 111 | [ 112 | { 113 | "name": "test4", 114 | "value": "value4", 115 | "status": 1 116 | } 117 | ] 118 | """ 119 | 120 | Scenario: Modify a resource 121 | Given I add "Content-Type" header equal to "application/ld+json" 122 | When I send a PUT request to "/api/configs/test1" with body: 123 | """ 124 | { 125 | "name": "test1", 126 | "value": "new_value", 127 | "status": 1 128 | } 129 | """ 130 | Then the response status code should be 200 131 | And the JSON should be equal to: 132 | """ 133 | { 134 | "name": "test1", 135 | "value": "new_value", 136 | "status": 1 137 | } 138 | """ 139 | 140 | Scenario: Retreive a resource 141 | When I send a GET request to "/api/configs/test1" 142 | Then the JSON should be equal to: 143 | """ 144 | { 145 | "name": "test1", 146 | "value": "new_value", 147 | "status": 1 148 | } 149 | """ 150 | 151 | Scenario: Retreive resources with search filter 152 | When I send a GET request to "/api/configs?value=new_value" 153 | Then the JSON should be equal to: 154 | """ 155 | [ 156 | { 157 | "name": "test1", 158 | "value": "new_value", 159 | "status": 1 160 | } 161 | ] 162 | """ 163 | 164 | Scenario Outline: Delete a resource 165 | When I send a DELETE request to "/api/configs/" 166 | Then the response status code should be 204 167 | 168 | Examples: 169 | | id | 170 | | test1 | 171 | | test2 | 172 | | test3 | 173 | | test4 | 174 | 175 | Scenario: 176 | When I send a GET request to "/api/configs" 177 | Then the JSON should be equal to: 178 | """ 179 | [] 180 | """ 181 | -------------------------------------------------------------------------------- /test/web/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pomm-project/pomm-api-platform/f89e887be8d5739227aa0366ff736803c1f4b042/test/web/favicon.ico -------------------------------------------------------------------------------- /test/web/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 12 | $response->send(); 13 | $kernel->terminate($request, $response); 14 | -------------------------------------------------------------------------------- /test/web/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | # www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449 3 | 4 | User-agent: * 5 | --------------------------------------------------------------------------------