├── .env ├── .env.test ├── .gitignore ├── Makefile ├── README.md ├── bin ├── console └── phpunit ├── composer.json ├── composer.lock ├── config ├── bundles.php ├── packages │ ├── api_platform.yaml │ ├── cache.yaml │ ├── dev │ │ ├── debug.yaml │ │ ├── hautelook_alice.yaml │ │ ├── monolog.yaml │ │ ├── nelmio_alice.yaml │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── framework.yaml │ ├── mailer.yaml │ ├── nelmio_cors.yaml │ ├── notifier.yaml │ ├── prod │ │ ├── deprecations.yaml │ │ ├── doctrine.yaml │ │ └── monolog.yaml │ ├── routing.yaml │ ├── security.yaml │ ├── sensio_framework_extra.yaml │ ├── test │ │ ├── doctrine.yaml │ │ ├── hautelook_alice.yaml │ │ ├── monolog.yaml │ │ ├── nelmio_alice.yaml │ │ ├── validator.yaml │ │ └── web_profiler.yaml │ ├── translation.yaml │ ├── twig.yaml │ └── validator.yaml ├── preload.php ├── routes.yaml ├── routes │ ├── annotations.yaml │ ├── api_platform.yaml │ ├── dev │ │ └── web_profiler.yaml │ └── framework.yaml └── services.yaml ├── docker-compose.override.yml ├── docker-compose.yaml ├── docker ├── nginx │ ├── Dockerfile │ ├── conf.d │ │ ├── default.dev.conf │ │ └── default.prod.conf │ └── prod │ │ └── nginx.conf └── php │ ├── Dockerfile │ ├── conf.d │ ├── api-platform.dev.ini │ └── api-platform.prod.ini │ └── docker-entrypoint.sh ├── fixtures ├── .gitignore ├── album.yaml ├── artist.yaml ├── book.yaml ├── car.yaml ├── comment.yaml ├── discussion.yaml ├── employee.yaml ├── furniture.yaml ├── job.yaml ├── message.yaml ├── movie.yaml └── post.yaml ├── migrations └── .gitignore ├── phpunit.xml.dist ├── public └── index.php ├── src ├── Controller │ ├── .gitignore │ └── CustomMovieAction.php ├── DataProvider │ ├── BookCollectionDataProvider.php │ ├── CarCollectionDataProvider.php │ ├── CityCollectionDataProvider.php │ ├── CommentSubresourceDataProvider.php │ ├── DiscussionSubresourceDataProvider.php │ ├── Extension │ │ ├── CityCollectionExtensionInterface.php │ │ ├── CityPaginationExtension.php │ │ ├── MovieCollectionExtensionInterface.php │ │ └── MoviePaginationExtension.php │ ├── FurnitureDataProvider.php │ ├── JobCollectionDataProvider.php │ ├── JobEmployeeDataProvider.php │ ├── MovieCollectionDataProvider.php │ ├── Pagination │ │ └── PostPaginator.php │ └── PostCollectionDataProvider.php ├── Doctrine │ └── BookExtension.php ├── Entity │ ├── .gitignore │ ├── Album.php │ ├── Artist.php │ ├── Book.php │ ├── Car.php │ ├── City.php │ ├── Comment.php │ ├── Discussion.php │ ├── Employee.php │ ├── Furniture.php │ ├── Job.php │ ├── Message.php │ ├── Movie.php │ └── Post.php ├── Filter │ └── CityFilter.php ├── Kernel.php └── Repository │ ├── .gitignore │ ├── AlbumRepository.php │ ├── ArtistRepository.php │ ├── BookRepository.php │ ├── CarRepository.php │ ├── City │ ├── CityCachedDataRepository.php │ ├── CityDataInterface.php │ ├── CityDataRepository.php │ └── data │ │ └── worldcities.csv │ ├── CommentRepository.php │ ├── DiscussionRepository.php │ ├── EmployeeRepository.php │ ├── FurnitureRepository.php │ ├── JobRepository.php │ ├── MessageRepository.php │ ├── MovieRepository.php │ └── PostRepository.php ├── symfony.lock ├── templates └── base.html.twig ├── tests └── bootstrap.php └── translations └── .gitignore /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration 15 | 16 | ###> symfony/framework-bundle ### 17 | APP_ENV=dev 18 | APP_SECRET=2355f0072e2baf3e8df34b7439f5feac 19 | ###< symfony/framework-bundle ### 20 | 21 | ###> symfony/mailer ### 22 | # MAILER_DSN=smtp://localhost 23 | ###< symfony/mailer ### 24 | 25 | ###> doctrine/doctrine-bundle ### 26 | # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url 27 | # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml 28 | # 29 | # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" 30 | # DATABASE_URL="mysql://db_user:db_password@127.0.0.1:3306/db_name?serverVersion=5.7" 31 | #DATABASE_URL="postgresql://api-platform:!ChangeMe!@database:5432/api-platform-pagination?serverVersion=13&charset=utf8" 32 | DATABASE_URL="postgresql://main:main@database:5432/api-platform-pagination?serverVersion=13&charset=utf8" 33 | ###< doctrine/doctrine-bundle ### 34 | 35 | ###> nelmio/cors-bundle ### 36 | CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$' 37 | ###< nelmio/cors-bundle ### 38 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='$ecretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | PANTHER_APP_ENV=panther 6 | PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /config/secrets/prod/prod.decrypt.private.php 7 | /public/bundles/ 8 | /var/ 9 | /vendor/ 10 | ###< symfony/framework-bundle ### 11 | 12 | ###> symfony/phpunit-bridge ### 13 | .phpunit.result.cache 14 | /phpunit.xml 15 | ###< symfony/phpunit-bridge ### 16 | 17 | ###> phpunit/phpunit ### 18 | /phpunit.xml 19 | .phpunit.result.cache 20 | ###< phpunit/phpunit ### 21 | 22 | /docker/db 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Executables (local) 2 | DOCKER_COMP = docker-compose 3 | 4 | # Docker containers 5 | PHP_CONT = $(DOCKER_COMP) exec php 6 | 7 | # Executables 8 | PHP = $(PHP_CONT) php 9 | COMPOSER = $(PHP_CONT) composer 10 | SYMFONY = $(PHP_CONT) bin/console 11 | 12 | # Misc 13 | .DEFAULT_GOAL = help 14 | .PHONY = help build up start down logs sh composer vendor sf cc 15 | 16 | help: ## Outputs this help screen 17 | @grep -E '(^[a-zA-Z0-9_-]+:.*?##.*$$)|(^##)' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}{printf "\033[32m%-30s\033[0m %s\n", $$1, $$2}' | sed -e 's/\[32m##/[33m/' 18 | 19 | ## —— Docker 🐳 ———————————————————————————————————————————————————————————————— 20 | build: ## Builds the Docker images 21 | @$(DOCKER_COMP) build --pull --no-cache 22 | 23 | up: ## Start the docker hub in detached mode (no logs) 24 | @$(DOCKER_COMP) up --detach 25 | 26 | start: up ## Start the containers 27 | 28 | stop: 29 | @$(DOCKER_COMP) stop 30 | 31 | restart: stop start 32 | 33 | down: ## Stop the docker hub 34 | @$(DOCKER_COMP) down --remove-orphans 35 | 36 | logs: ## Show live logs 37 | @$(DOCKER_COMP) logs --tail=0 --follow 38 | 39 | sh: ## Connect to the PHP FPM container 40 | @$(PHP_CONT) sh 41 | 42 | ## —— Composer 🧙 —————————————————————————————————————————————————————————————— 43 | composer: ## Run composer, pass the parameter "c=" to run a given command, example: make composer c='req symfony/orm-pack' 44 | @$(eval c ?=) 45 | @$(COMPOSER) $(c) 46 | 47 | vendor: ## Install vendors according to the current composer.lock file 48 | vendor: c=install --prefer-dist --no-dev --no-progress --no-scripts --no-interaction 49 | vendor: composer 50 | 51 | ## —— Symfony 🎵 ——————————————————————————————————————————————————————————————— 52 | sf: ## List all Symfony commands or pass the parameter "c=" to run a given command, example: make sf c=about 53 | @$(eval c ?=) 54 | @$(SYMFONY) $(c) 55 | 56 | db-update: 57 | @$(PHP) bin/console doctrine:schema:update --force 58 | 59 | load-fixtures: 60 | @$(PHP) bin/console hautelook:fixtures:load -n --purge-with-truncate 61 | 62 | init-fixtures: db-update load-fixtures 63 | cc: c=c:c ## Clear the cache 64 | cc: sf 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Api Platform Sort, Filter and Pagination on custom data 2 | 3 | Inspired by https://github.com/api-platform/demo 4 | 5 | # Summary 6 | 7 | - [First Example use raw data from a csv file](#first-example-use-raw-data-from-a-csv-file) 8 | - [Second example use custom controller](#second-example-use-custom-controller) 9 | - [Third example use MovieCollectionDataProvider and repository](#third-example-use-moviecollectiondataprovider-and-repository) 10 | - [Fourth example use QueryBuilder in CarCollectionDataProvider (collectionExtensions)](#fourth-example-use-querybuilder-in-carcollectiondataprovider-collectionextensions) 11 | - [Fifth example use JobCollectionDataProvider (paginationExtension)](#fifth-example-use-jobcollectiondataprovider-paginationextension) 12 | - [Sixth example use FurnitureDataProvider (collectionDataProvider)](#sixth-example-use-furnituredataprovider-collectiondataprovider) 13 | - [Seventh example use QueryBuilder in subresource](#seventh-example--simple-dataprovider-using-subresourcedataprovider) 14 | - [Eighth example use QueryBuilder in subresource](#eighth-example-use-querybuilder-in-subresource) 15 | - [Ninth use custom subresource with provider (without subresourceDataProvider)](#ninth-example---custom-subresource-with-provider-without-subresourcedataprovider) 16 | - [Tenth example - Custom Paginator in Provider with QueryBuilder](#tenth-example---custom-paginator-in-provider-with-querybuilder) 17 | - [🚀How to create a Collection Data Provider and keep Doctrine Extension, Filters and Pagination on it](#how-to-create-a-collection-data-provider-and-keep-doctrine-extension-filters-and-pagination-on-it) 18 | 19 | ## Install 20 | 21 | Build the project `make build` 22 | 23 | Start the project `make start` 24 | 25 | Enter the php container with `make sh` 26 | 27 | Install dependencies (in php container) with `composer install` 28 | 29 | Load fixtures with `make init-fixtures` 30 | 31 | You can go to `http://127.0.0.1:8000/api/` 32 | 33 | 34 | ## First Example use raw data from a csv file 35 | 36 | Example using a custom `CityFilter` filtering on raw data from the file `Repository/City/data/worldcities.csv` 37 | 38 | CityFilter pass the `sort` and `order` params to the context and this context it's processed in the `CityCollectionDataProvider` 39 | 40 | The pagination is available 41 | 42 | ### Usage 43 | 44 | sort and filter on raw data from csv 45 | `/api/cities?search[key]=tokyo&order[key]=desc&page=1` 46 | 47 | #### Keys available 48 | 49 | - id 50 | - city 51 | - city_ascii 52 | - lat 53 | - lng 54 | - country 55 | - iso2 56 | - iso3 57 | - admin_name 58 | - capital 59 | - population 60 | 61 | ###### Commits related 62 | 63 | - https://github.com/aratinau/api-platform-pagination/commit/f40232ed62d2720fa3140f6ce6cc89c36798dd6a 64 | - https://github.com/aratinau/api-platform-pagination/commit/1d34f49c1520040a88c63b3f1d391ad3d24e8248 65 | 66 | 67 | ## Second example use custom controller 68 | 69 | On path `/movies/custom-action` the action `CustomMovieAction` is called and page and order use `getCustom` method from `MovieRepository`. 70 | The pagination is available 71 | 72 | ### Usage 73 | 74 | custom controller action from database using Paginator 75 | `api/movies/custom-action?page=1&order[id]=asc&order[title]=desc` 76 | 77 | ###### Commits related 78 | 79 | - https://github.com/aratinau/api-platform-pagination/commit/ea7db84cb60b1a6ad141894849e8f9ad5b982e84 80 | 81 | ## Third example use MovieCollectionDataProvider and repository 82 | 83 | On the normalization_context group `normalization-custom-action-using-dataprovider` the MovieCollectionDataProvider is called and the resust is from the repository. The param `isPublished=true|false` can be used and the result is filtered by the value asked. 84 | The param `order` can be `title` or `id` and ordered by `asc|desc`. The pagination is available 85 | 86 | ### Usage 87 | 88 | data provider using repository (by group from normalization_context on Movie entity) 89 | `api/movies/custom-action-using-dataprovider?page=2&order[title]=desc&isPublished=false` 90 | 91 | ###### Commits related 92 | 93 | - https://github.com/aratinau/api-platform-pagination/commit/c33a7940a367152b9457607e11c5e65970aceb9b 94 | - https://github.com/aratinau/api-platform-pagination/commit/861cfc9267958825640ff549befb29f386b0a252 95 | 96 | ## Fourth example use QueryBuilder in CarCollectionDataProvider (collectionExtensions) 97 | 98 | This example show how use QueryBuilder and filters with CollectionExtensions in CarCollectionDataProvider. 99 | The collection is filtered by color from the `context` with the QueryBuilder and filtered by `name` and `isPublished` from SearchFilter in Car entity. 100 | The pagination is available 101 | 102 | ### Usage 103 | 104 | `/api/cars?color=color_name&isPublished=true|false&page=1` 105 | 106 | #### Color name available 107 | 108 | - red 109 | - orange 110 | - green 111 | - yellow 112 | - black 113 | 114 | ###### Commits related 115 | 116 | - https://github.com/aratinau/api-platform-pagination/commit/4d8d22a4ccad265b7de1bffc15729df06eb5ded4 117 | 118 | ## Fifth example use JobCollectionDataProvider (paginationExtension) 119 | 120 | This example show how use PaginationExtension in JobCollectionDataProvider 121 | 122 | ### Usage 123 | 124 | `api/jobs?page=1` 125 | 126 | ###### Commits related 127 | 128 | - https://github.com/aratinau/api-platform-pagination/commit/53ce8298d7c28e17fc0a55c5c78ecb0ce2fc9a1d 129 | 130 | ## Sixth example use FurnitureDataProvider (collectionDataProvider) 131 | 132 | Basic example showing how use and configure CollectionDataProvider 133 | 134 | ### Usage 135 | 136 | `api/furniture?page=2` 137 | 138 | ###### Commits related 139 | 140 | - https://github.com/aratinau/api-platform-pagination/commit/2cab86c65dd40ba218bca175f217581e94660b7b 141 | 142 | ## Seventh example : simple DataProvider using subresourceDataProvider 143 | 144 | CommentSubresourceDataProvider show how use the standard behaviour 145 | 146 | ### Usage 147 | 148 | `api/movies/{id}/comments` 149 | 150 | ###### Commits related 151 | 152 | - https://github.com/aratinau/api-platform-pagination/commit/6addf42683e8278b8f81afadc11d269a81a17f9e 153 | 154 | ## Eighth example use QueryBuilder in subresource 155 | 156 | DiscussionSubresourceDataProvider show how use QueryBuilder and order by DESC the result 157 | 158 | ### Usage 159 | 160 | `api/discussions/{id}/messages?page=1` 161 | 162 | ###### Commits related 163 | 164 | - https://github.com/aratinau/api-platform-pagination/commit/29ac71e2f98024439a8cffc81287a41ec0afe4a3 165 | 166 | ## Ninth example - custom subresource with provider (without subresourceDataProvider) 167 | 168 | With JobDataProvider and path `jobs/{id}/employees` return employees from id's job 169 | 170 | [ ] TODO Pagination 171 | 172 | ### Usage 173 | 174 | `api/jobs/{id}/employees/{arg1}` 175 | 176 | ###### Commits related 177 | 178 | - https://github.com/aratinau/api-platform-pagination/commit/5be041c859ce58e0ccc894fe1efd0ed7855b6dc4 179 | 180 | ## Tenth example - Custom Paginator in Provider with QueryBuilder 181 | 182 | PostCollectionDataProvider call the method findLatest from PostRepository and PostRepository call PostPaginator 183 | 184 | ### Usage 185 | 186 | `/api/posts?page=2` 187 | 188 | ###### Commits related 189 | 190 | - https://github.com/aratinau/api-platform-pagination/commit/9a0989e3cab1ef7faceeabc3217098a54c75906d 191 | 192 | ## Notes 193 | 194 | `Album` and `Artist` are ready to be used 195 | 196 | ## How to create a Collection Data Provider and keep Doctrine Extension, Filters and Pagination on it 197 | 198 | We're going to create a Provider who will return only books not archived when the param `includeArchived` is missing. 199 | It will return the books by the locale corresponding in a Doctrine Extension 200 | 201 | ### Usage 202 | `/api/books` or `/api/books?includeArchived=false` return only books not archived 203 | 204 | `/api/books?includeArchived=true` return all books (archived or not) 205 | 206 | ### Quick explanation 207 | 208 | In our `BookCollectionDataProvider` we have this condition: 209 | 210 | ```php 211 | $includeArchived = $context['filters']['includeArchived'] ?? 'false'; 212 | // ... 213 | if ($includeArchived === 'false') { 214 | $queryBuilder->andWhere("$alias.isArchived = false"); 215 | } 216 | ``` 217 | 218 | Then we create a `BookExtension` and add this condition to return book by locale defined. 219 | 220 | ```php 221 | ->andWhere("$rootAlias.locale = :locale") 222 | ->setParameter('locale', self::LOCALE) 223 | ``` 224 | 225 | Then in `BookCollectionDataProvider` we can loop over all the extensions on `$collectionExtensions` (we defined $collectionExtensions on `services.yaml`) so it apply to all extensions. 226 | 227 | Extensions called are (in this order): 228 | 229 | - `"App\Doctrine\BookExtension"` 230 | - `"ApiPlatform\Doctrine\Orm\Extension\FilterExtension"` 231 | - `"ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension"` 232 | - `"ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension"` 233 | - `"ApiPlatform\Doctrine\Orm\Extension\OrderExtension"` 234 | - `"ApiPlatform\Doctrine\Orm\Extension\PaginationExtension"` 235 | 236 | Now you can still use the `SortOrder` on the `Book` entity for example, and add the param `isArchived` and the result will be pass through the DoctrineExtension to set the locale. 237 | 238 | ###### Commits related 239 | 240 | - https://github.com/aratinau/api-platform-pagination/commit/1d27f16adecda1c0956fcc5d0da81f017d915c1b 241 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =8.0", 8 | "ext-ctype": "*", 9 | "ext-iconv": "*", 10 | "api-platform/core": "v2.6", 11 | "composer/package-versions-deprecated": "1.11.99.2", 12 | "doctrine/annotations": "^1.13", 13 | "doctrine/doctrine-bundle": "^2.4", 14 | "doctrine/doctrine-migrations-bundle": "^3.1", 15 | "doctrine/orm": "^2.9", 16 | "nelmio/cors-bundle": "^2.1", 17 | "phpdocumentor/reflection-docblock": "^5.2", 18 | "sensio/framework-extra-bundle": "^6.1", 19 | "symfony/asset": "5.4.*", 20 | "symfony/console": "5.4.*", 21 | "symfony/dotenv": "5.4.*", 22 | "symfony/expression-language": "5.4.*", 23 | "symfony/flex": "^1.3.1", 24 | "symfony/form": "5.4.*", 25 | "symfony/framework-bundle": "5.4.*", 26 | "symfony/http-client": "5.4.*", 27 | "symfony/intl": "5.4.*", 28 | "symfony/mailer": "5.4.*", 29 | "symfony/mime": "5.4.*", 30 | "symfony/monolog-bundle": "^3.1", 31 | "symfony/notifier": "5.4.*", 32 | "symfony/process": "5.4.*", 33 | "symfony/property-access": "5.4.*", 34 | "symfony/property-info": "5.4.*", 35 | "symfony/proxy-manager-bridge": "5.4.*", 36 | "symfony/runtime": "5.4.*", 37 | "symfony/security-bundle": "5.4.*", 38 | "symfony/serializer": "5.4.*", 39 | "symfony/string": "5.4.*", 40 | "symfony/translation": "5.4.*", 41 | "symfony/twig-bundle": "^5.3", 42 | "symfony/validator": "5.4.*", 43 | "symfony/web-link": "5.4.*", 44 | "symfony/yaml": "5.4.*", 45 | "twig/extra-bundle": "^2.12|^3.0", 46 | "twig/twig": "^2.12|^3.0" 47 | }, 48 | "require-dev": { 49 | "hautelook/alice-bundle": "^2.9", 50 | "phpunit/phpunit": "^9.5", 51 | "symfony/browser-kit": "^5.3", 52 | "symfony/css-selector": "^5.3", 53 | "symfony/debug-bundle": "^5.3", 54 | "symfony/maker-bundle": "^1.0", 55 | "symfony/phpunit-bridge": "^5.3", 56 | "symfony/stopwatch": "^5.3", 57 | "symfony/var-dumper": "^5.3", 58 | "symfony/web-profiler-bundle": "^5.3" 59 | }, 60 | "config": { 61 | "optimize-autoloader": true, 62 | "preferred-install": { 63 | "*": "dist" 64 | }, 65 | "sort-packages": true, 66 | "allow-plugins": { 67 | "symfony/flex": true, 68 | "symfony/runtime": true 69 | } 70 | }, 71 | "autoload": { 72 | "psr-4": { 73 | "App\\": "src/" 74 | } 75 | }, 76 | "autoload-dev": { 77 | "psr-4": { 78 | "App\\Tests\\": "tests/" 79 | } 80 | }, 81 | "replace": { 82 | "symfony/polyfill-ctype": "*", 83 | "symfony/polyfill-iconv": "*", 84 | "symfony/polyfill-php72": "*" 85 | }, 86 | "scripts": { 87 | "auto-scripts": { 88 | "cache:clear": "symfony-cmd", 89 | "assets:install %PUBLIC_DIR%": "symfony-cmd" 90 | }, 91 | "post-install-cmd": [ 92 | "@auto-scripts" 93 | ], 94 | "post-update-cmd": [ 95 | "@auto-scripts" 96 | ] 97 | }, 98 | "conflict": { 99 | "symfony/symfony": "*" 100 | }, 101 | "extra": { 102 | "symfony": { 103 | "allow-contrib": false, 104 | "require": "5.4.*" 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 6 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 7 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 8 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 9 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], 10 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 11 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 12 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 13 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 14 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], 15 | Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], 16 | ApiPlatform\Core\Bridge\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true], 17 | Nelmio\Alice\Bridge\Symfony\NelmioAliceBundle::class => ['dev' => true, 'test' => true], 18 | Fidry\AliceDataFixtures\Bridge\Symfony\FidryAliceDataFixturesBundle::class => ['dev' => true, 'test' => true], 19 | Hautelook\AliceBundle\HautelookAliceBundle::class => ['dev' => true, 'test' => true], 20 | ]; 21 | -------------------------------------------------------------------------------- /config/packages/api_platform.yaml: -------------------------------------------------------------------------------- 1 | api_platform: 2 | mapping: 3 | paths: ['%kernel.project_dir%/src/Entity'] 4 | patch_formats: 5 | json: ['application/merge-patch+json'] 6 | swagger: 7 | versions: [3] 8 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /config/packages/dev/hautelook_alice.yaml: -------------------------------------------------------------------------------- 1 | hautelook_alice: 2 | fixtures_path: fixtures 3 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | # uncomment to get logging in your browser 9 | # you may have to allow bigger header sizes in your Web server configuration 10 | #firephp: 11 | # type: firephp 12 | # level: info 13 | #chromephp: 14 | # type: chromephp 15 | # level: info 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine", "!console"] 20 | -------------------------------------------------------------------------------- /config/packages/dev/nelmio_alice.yaml: -------------------------------------------------------------------------------- 1 | nelmio_alice: 2 | functions_blacklist: 3 | - 'current' 4 | - 'shuffle' 5 | - 'date' 6 | - 'time' 7 | - 'file' 8 | - 'md5' 9 | - 'sha1' 10 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | url: '%env(resolve:DATABASE_URL)%' 4 | 5 | # IMPORTANT: You MUST configure your server version, 6 | # either here or in the DATABASE_URL env var (see .env file) 7 | #server_version: '13' 8 | orm: 9 | auto_generate_proxy_classes: true 10 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware 11 | auto_mapping: true 12 | mappings: 13 | App: 14 | is_bundle: false 15 | type: annotation 16 | dir: '%kernel.project_dir%/src/Entity' 17 | prefix: 'App\Entity' 18 | alias: App 19 | -------------------------------------------------------------------------------- /config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | enable_profiler: '%kernel.debug%' 7 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | #csrf_protection: true 5 | http_method_override: false 6 | 7 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 8 | # Remove or comment this section to explicitly disable session support. 9 | session: 10 | handler_id: null 11 | cookie_secure: auto 12 | cookie_samesite: lax 13 | storage_factory_id: session.storage.factory.native 14 | 15 | #esi: true 16 | #fragments: true 17 | php_errors: 18 | log: true 19 | 20 | when@test: 21 | framework: 22 | test: true 23 | session: 24 | storage_factory_id: session.storage.factory.mock_file 25 | -------------------------------------------------------------------------------- /config/packages/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | dsn: '%env(MAILER_DSN)%' 4 | -------------------------------------------------------------------------------- /config/packages/nelmio_cors.yaml: -------------------------------------------------------------------------------- 1 | nelmio_cors: 2 | defaults: 3 | origin_regex: true 4 | allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] 5 | allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] 6 | allow_headers: ['Content-Type', 'Authorization'] 7 | expose_headers: ['Link'] 8 | max_age: 3600 9 | paths: 10 | '^/': null 11 | -------------------------------------------------------------------------------- /config/packages/notifier.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | notifier: 3 | #chatter_transports: 4 | # slack: '%env(SLACK_DSN)%' 5 | # telegram: '%env(TELEGRAM_DSN)%' 6 | #texter_transports: 7 | # twilio: '%env(TWILIO_DSN)%' 8 | # nexmo: '%env(NEXMO_DSN)%' 9 | channel_policy: 10 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo 11 | urgent: ['email'] 12 | high: ['email'] 13 | medium: ['email'] 14 | low: ['email'] 15 | admin_recipients: 16 | - { email: admin@example.com } 17 | -------------------------------------------------------------------------------- /config/packages/prod/deprecations.yaml: -------------------------------------------------------------------------------- 1 | # As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists 2 | #monolog: 3 | # channels: [deprecation] 4 | # handlers: 5 | # deprecation: 6 | # type: stream 7 | # channels: [deprecation] 8 | # path: php://stderr 9 | -------------------------------------------------------------------------------- /config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: pool 6 | pool: doctrine.system_cache_pool 7 | query_cache_driver: 8 | type: pool 9 | pool: doctrine.system_cache_pool 10 | result_cache_driver: 11 | type: pool 12 | pool: doctrine.result_cache_pool 13 | 14 | framework: 15 | cache: 16 | pools: 17 | doctrine.result_cache_pool: 18 | adapter: cache.app 19 | doctrine.system_cache_pool: 20 | adapter: cache.system 21 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | nested: 10 | type: stream 11 | path: php://stderr 12 | level: debug 13 | formatter: monolog.formatter.json 14 | console: 15 | type: console 16 | process_psr_3_messages: false 17 | channels: ["!event", "!doctrine"] 18 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | 9 | when@prod: 10 | framework: 11 | router: 12 | strict_requirements: null 13 | -------------------------------------------------------------------------------- /config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | # https://symfony.com/doc/current/security/experimental_authenticators.html 3 | enable_authenticator_manager: true 4 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers 5 | providers: 6 | users_in_memory: { memory: null } 7 | firewalls: 8 | dev: 9 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 10 | security: false 11 | main: 12 | lazy: true 13 | provider: users_in_memory 14 | 15 | # activate different ways to authenticate 16 | # https://symfony.com/doc/current/security.html#firewalls-authentication 17 | 18 | # https://symfony.com/doc/current/security/impersonating_user.html 19 | # switch_user: true 20 | 21 | # Easy way to control access for large sections of your site 22 | # Note: Only the *first* access control that matches will be used 23 | access_control: 24 | # - { path: ^/admin, roles: ROLE_ADMIN } 25 | # - { path: ^/profile, roles: ROLE_USER } 26 | -------------------------------------------------------------------------------- /config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /config/packages/test/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | # "TEST_TOKEN" is typically set by ParaTest 4 | dbname_suffix: '_test%env(default::TEST_TOKEN)%' 5 | -------------------------------------------------------------------------------- /config/packages/test/hautelook_alice.yaml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ../dev/hautelook_alice.yaml } 3 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | channels: ["!event"] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /config/packages/test/nelmio_alice.yaml: -------------------------------------------------------------------------------- 1 | imports: 2 | - { resource: ../dev/nelmio_alice.yaml } 3 | -------------------------------------------------------------------------------- /config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: en 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - en 7 | # providers: 8 | # crowdin: 9 | # dsn: '%env(CROWDIN_DSN)%' 10 | # loco: 11 | # dsn: '%env(LOCO_DSN)%' 12 | # lokalise: 13 | # dsn: '%env(LOKALISE_DSN)%' 14 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | 4 | when@test: 5 | twig: 6 | strict_variables: true 7 | -------------------------------------------------------------------------------- /config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /config/preload.php: -------------------------------------------------------------------------------- 1 | > /etc/apk/repositories 7 | RUN apk --no-cache add shadow && usermod -u 1000 nginx -------------------------------------------------------------------------------- /docker/nginx/conf.d/default.dev.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | client_max_body_size 10M; 4 | client_body_buffer_size 128k; 5 | 6 | root /srv/api/public; 7 | 8 | location / { 9 | # try to serve file directly, fallback to index.php 10 | try_files $uri /index.php$is_args$args; 11 | } 12 | 13 | location ~ ^/index\.php(/|$) { 14 | # Comment the next line and uncomment the next to enable dynamic resolution (incompatible with Kubernetes) 15 | fastcgi_pass php:9000; 16 | #resolver 127.0.0.11; 17 | #set $upstream_host php; 18 | #fastcgi_pass $upstream_host:9000; 19 | # Increase the buffer size to handle large cache invalidation headers 20 | fastcgi_buffer_size 32k; 21 | fastcgi_buffers 32 4k; 22 | 23 | fastcgi_split_path_info ^(.+\.php)(/.*)$; 24 | include fastcgi_params; 25 | 26 | fastcgi_param APP_ENV dev; 27 | fastcgi_param HTTPS off; 28 | 29 | # When you are using symlinks to link the document root to the 30 | # current version of your application, you should pass the real 31 | # application path instead of the path to the symlink to PHP 32 | # FPM. 33 | # Otherwise, PHP's OPcache may not properly detect changes to 34 | # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 35 | # for more information). 36 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 37 | fastcgi_param DOCUMENT_ROOT $realpath_root; 38 | # Prevents URIs that include the front controller. This will 404: 39 | # http://domain.tld/index.php/some-path 40 | # Remove the internal directive to allow URIs like this 41 | internal; 42 | } 43 | 44 | # return 404 for all other php files not matching the front controller 45 | # this prevents access to other php files you don't want to be accessible. 46 | location ~ \.php$ { 47 | return 404; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docker/nginx/conf.d/default.prod.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | client_max_body_size 10M; 4 | client_body_buffer_size 128k; 5 | 6 | root /srv/api/public; 7 | 8 | location / { 9 | # try to serve file directly, fallback to index.php 10 | try_files $uri /index.php$is_args$args; 11 | } 12 | 13 | location ~ ^/index\.php(/|$) { 14 | # Comment the next line and uncomment the next to enable dynamic resolution (incompatible with Kubernetes) 15 | fastcgi_pass php:9000; 16 | #resolver 127.0.0.11; 17 | #set $upstream_host php; 18 | #fastcgi_pass $upstream_host:9000; 19 | # Increase the buffer size to handle large cache invalidation headers 20 | fastcgi_buffer_size 32k; 21 | fastcgi_buffers 32 4k; 22 | 23 | fastcgi_split_path_info ^(.+\.php)(/.*)$; 24 | include fastcgi_params; 25 | 26 | fastcgi_param APP_ENV dev; 27 | #fastcgi_param HTTPS on; 28 | 29 | # When you are using symlinks to link the document root to the 30 | # current version of your application, you should pass the real 31 | # application path instead of the path to the symlink to PHP 32 | # FPM. 33 | # Otherwise, PHP's OPcache may not properly detect changes to 34 | # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 35 | # for more information). 36 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 37 | fastcgi_param DOCUMENT_ROOT $realpath_root; 38 | # Prevents URIs that include the front controller. This will 404: 39 | # http://domain.tld/index.php/some-path 40 | # Remove the internal directive to allow URIs like this 41 | internal; 42 | } 43 | 44 | # return 404 for all other php files not matching the front controller 45 | # this prevents access to other php files you don't want to be accessible. 46 | location ~ \.php$ { 47 | return 404; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /docker/nginx/prod/nginx.conf: -------------------------------------------------------------------------------- 1 | daemon off; 2 | user www-data; 3 | worker_processes auto; 4 | pid /run/nginx.pid; 5 | 6 | error_log /dev/stdout info; 7 | 8 | events { 9 | worker_connections 2048; 10 | # multi_accept on; 11 | } 12 | 13 | http { 14 | access_log /dev/stdout; 15 | 16 | ## 17 | # Basic Settings 18 | ## 19 | 20 | sendfile on; 21 | tcp_nopush on; 22 | tcp_nodelay on; 23 | keepalive_timeout 65; 24 | types_hash_max_size 2048; 25 | server_tokens off; 26 | include /etc/nginx/mime.types; 27 | default_type application/octet-stream; 28 | 29 | ## 30 | # Gzip Settings 31 | ## 32 | 33 | gzip on; 34 | gzip_disable "msie6"; 35 | gzip_min_length 1000; 36 | gzip_comp_level 6; 37 | gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; 38 | 39 | ## 40 | # Virtual Host Configs 41 | ## 42 | 43 | server { 44 | listen 80 default_server; 45 | 46 | root /srv/api/public; 47 | client_max_body_size 20M; 48 | client_body_buffer_size 128k; 49 | 50 | add_header X-Content-Type-Options nosniff; 51 | add_header X-XSS-Protection "1; mode=block"; 52 | add_header X-Frame-Options DENY; 53 | 54 | location / { 55 | index index.php; 56 | try_files $uri /index.php$is_args$args; 57 | } 58 | 59 | location ~ \.php(/|$) { 60 | internal; 61 | 62 | fastcgi_pass 127.0.0.1:9000; 63 | fastcgi_buffer_size 32k; 64 | fastcgi_buffers 32 4k; 65 | 66 | fastcgi_split_path_info ^(.+\.php)(/.*)$; 67 | include fastcgi_params; 68 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 69 | fastcgi_param DOCUMENT_ROOT $realpath_root; 70 | fastcgi_param APP_ENV prod; 71 | fastcgi_param HTTPS on; 72 | } 73 | 74 | location ~ \.php$ { 75 | return 404; 76 | } 77 | 78 | # Deny all . files 79 | location ~ /\. { 80 | deny all; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | # the different stages of this Dockerfile are meant to be built into separate images 2 | # https://docs.docker.com/develop/develop-images/multistage-build/#stop-at-a-specific-build-stage 3 | # https://docs.docker.com/compose/compose-file/#target 4 | 5 | 6 | # https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact 7 | ARG PHP_VERSION=8.0.12 8 | ARG NGINX_VERSION=1.17 9 | 10 | 11 | # "php" stage 12 | FROM php:${PHP_VERSION}-fpm-alpine3.13 AS api_platform_php 13 | 14 | # persistent / runtime deps 15 | RUN apk add --no-cache \ 16 | acl \ 17 | fcgi \ 18 | file \ 19 | gettext \ 20 | git \ 21 | gnu-libiconv \ 22 | make \ 23 | tesseract-ocr \ 24 | ; 25 | 26 | ARG APCU_VERSION=5.1.18 27 | RUN set -eux; \ 28 | apk add --no-cache --virtual .build-deps \ 29 | $PHPIZE_DEPS \ 30 | icu-dev \ 31 | libzip-dev \ 32 | zlib-dev \ 33 | libxslt-dev \ 34 | postgresql-dev \ 35 | ; \ 36 | \ 37 | docker-php-ext-configure zip; \ 38 | docker-php-ext-install -j$(nproc) \ 39 | intl \ 40 | pdo_mysql \ 41 | zip \ 42 | xsl \ 43 | pdo_pgsql \ 44 | ; \ 45 | pecl install \ 46 | apcu-${APCU_VERSION} \ 47 | ; \ 48 | pecl clear-cache; \ 49 | docker-php-ext-enable \ 50 | apcu \ 51 | opcache \ 52 | ; \ 53 | \ 54 | runDeps="$( \ 55 | scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions \ 56 | | tr ',' '\n' \ 57 | | sort -u \ 58 | | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \ 59 | )"; \ 60 | apk add --no-cache --virtual .api-phpexts-rundeps $runDeps; \ 61 | \ 62 | apk del .build-deps 63 | 64 | COPY --from=composer:latest /usr/bin/composer /usr/bin/composer 65 | 66 | RUN ln -s $PHP_INI_DIR/php.ini-production $PHP_INI_DIR/php.ini 67 | COPY conf.d/api-platform.dev.ini $PHP_INI_DIR/conf.d/api-platform.ini 68 | 69 | 70 | # Workaround to allow using PHPUnit 8 with Symfony 4.3 71 | ENV SYMFONY_PHPUNIT_VERSION=9 72 | 73 | # https://getcomposer.org/doc/03-cli.md#composer-allow-superuser 74 | ENV COMPOSER_ALLOW_SUPERUSER=1 75 | # install Symfony Flex globally to speed up download of Composer packages (parallelized prefetching) 76 | RUN set -eux; \ 77 | composer global require "symfony/flex" --prefer-dist --no-progress --no-suggest --classmap-authoritative; 78 | ENV PATH="${PATH}:/root/.composer/vendor/bin" 79 | 80 | WORKDIR /srv/api 81 | 82 | # build for production 83 | ARG APP_ENV=prod 84 | 85 | CMD ["php-fpm"] 86 | -------------------------------------------------------------------------------- /docker/php/conf.d/api-platform.dev.ini: -------------------------------------------------------------------------------- 1 | apc.enable_cli = 1 2 | date.timezone = Europe/Paris 3 | session.auto_start = Off 4 | short_open_tag = Off 5 | 6 | # https://symfony.com/doc/current/performance.html 7 | opcache.interned_strings_buffer = 16 8 | opcache.max_accelerated_files = 20000 9 | opcache.memory_consumption = 256 10 | realpath_cache_size = 4096K 11 | realpath_cache_ttl = 600 12 | 13 | upload_max_filesize = 20M 14 | post_max_size = 20M -------------------------------------------------------------------------------- /docker/php/conf.d/api-platform.prod.ini: -------------------------------------------------------------------------------- 1 | apc.enable_cli = 1 2 | date.timezone = Europe/Paris 3 | session.auto_start = Off 4 | short_open_tag = Off 5 | 6 | # https://symfony.com/doc/current/performance.html 7 | opcache.interned_strings_buffer = 16 8 | opcache.max_accelerated_files = 20000 9 | opcache.memory_consumption = 256 10 | opcache.validate_timestamps = 0 11 | realpath_cache_size = 4096K 12 | realpath_cache_ttl = 600 13 | opcache.preload_user=www-data 14 | opcache.preload=/srv/api/var/cache/prod/App_KernelProdContainer.preload.php 15 | 16 | upload_max_filesize = 20M 17 | post_max_size = 20M 18 | -------------------------------------------------------------------------------- /docker/php/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # first arg is `-f` or `--some-option` 5 | if [ "${1#-}" != "$1" ]; then 6 | set -- php-fpm "$@" 7 | fi 8 | 9 | if [[ "$1" = "supervisord" ]]; then 10 | 11 | echo "Waiting for db to be ready..." 12 | until bin/console doctrine:query:sql "SELECT 1" > /dev/null 2>&1; do 13 | sleep 1 14 | done 15 | 16 | #if ls -A src/Migrations/*.php > /dev/null 2>&1; then 17 | # bin/console doctrine:migrations:migrate --no-interaction 18 | #fi 19 | # TODO REMOVE THIS (use migrations) 20 | bin/console doctrine:schema:update -n --force 21 | fi 22 | 23 | exec docker-php-entrypoint "$@" 24 | -------------------------------------------------------------------------------- /fixtures/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aratinau/api-platform-pagination/1b7f864c4040811b0f6e657133bc724f31e00de2/fixtures/.gitignore -------------------------------------------------------------------------------- /fixtures/album.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Album: 2 | album_{1..1000}: 3 | title: 4 | year: '' 5 | artist: '@artist*' 6 | -------------------------------------------------------------------------------- /fixtures/artist.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Artist: 2 | artist_{1..10}: 3 | author: 4 | -------------------------------------------------------------------------------- /fixtures/book.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Book: 2 | book_{1..200}: 3 | title: 4 | isArchived: '' 5 | locale: 6 | -------------------------------------------------------------------------------- /fixtures/car.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Car: 2 | car_{1..1000}: 3 | name: 4 | color: '' 5 | isPublished: '' 6 | -------------------------------------------------------------------------------- /fixtures/comment.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Comment: 2 | comment_{1..1000}: 3 | author: 4 | content: 5 | -------------------------------------------------------------------------------- /fixtures/discussion.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Discussion: 2 | discussion_{1..100}: 3 | name: 4 | -------------------------------------------------------------------------------- /fixtures/employee.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Employee: 2 | employee_{1..1000}: 3 | author: 4 | -------------------------------------------------------------------------------- /fixtures/furniture.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Furniture: 2 | furniture_{1..1000}: 3 | name: 4 | isPublished: '' 5 | -------------------------------------------------------------------------------- /fixtures/job.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Job: 2 | job_{1..100}: 3 | name: 4 | isPublished: '' 5 | employees: 'x @employee*' 6 | -------------------------------------------------------------------------------- /fixtures/message.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Message: 2 | message_{1..5000}: 3 | content: 4 | discussion: '@discussion_*' 5 | -------------------------------------------------------------------------------- /fixtures/movie.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Movie: 2 | movie_{1..1000}: 3 | title: 4 | isPublished: '' 5 | comments: 'x @comment*' 6 | -------------------------------------------------------------------------------- /fixtures/post.yaml: -------------------------------------------------------------------------------- 1 | App\Entity\Post: 2 | post_{1..100}: 3 | title: 4 | slug: 5 | summary: 6 | content: 7 | publishedAt: '' 8 | -------------------------------------------------------------------------------- /migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aratinau/api-platform-pagination/1b7f864c4040811b0f6e657133bc724f31e00de2/migrations/.gitignore -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | tests 22 | 23 | 24 | 25 | 26 | 27 | src 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | movieRepository = $movieRepository; 22 | } 23 | 24 | public function __invoke(Request $request): Paginator 25 | { 26 | $page = (int) $request->query->get('page', 1); 27 | $order = $request->query->get('order', []); 28 | 29 | return $this->movieRepository->getCustom($page, $order); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/DataProvider/BookCollectionDataProvider.php: -------------------------------------------------------------------------------- 1 | managerRegistry->getManagerForClass($resourceClass); 36 | $repository = $manager->getRepository($resourceClass); 37 | $queryBuilder = $repository->createQueryBuilder('o'); 38 | $alias = $queryBuilder->getRootAliases()[0]; 39 | 40 | if ($includeArchived === 'false') { 41 | $queryBuilder->andWhere("$alias.isArchived = false"); 42 | } 43 | 44 | $queryNameGenerator = new QueryNameGenerator(); 45 | foreach ($this->collectionExtensions as $extension) { 46 | /** 47 | * Extensions are (in this order) 48 | * - "App\Doctrine\BookExtension" 49 | * - "ApiPlatform\Doctrine\Orm\Extension\FilterExtension" 50 | * - "ApiPlatform\Doctrine\Orm\Extension\FilterEagerLoadingExtension" 51 | * - "ApiPlatform\Doctrine\Orm\Extension\EagerLoadingExtension" 52 | * - "ApiPlatform\Doctrine\Orm\Extension\OrderExtension" 53 | * - "ApiPlatform\Doctrine\Orm\Extension\PaginationExtension" 54 | */ 55 | $extension->applyToCollection( 56 | $queryBuilder, 57 | $queryNameGenerator, 58 | $resourceClass, 59 | $operationName, 60 | $context 61 | ); 62 | 63 | if ( 64 | $extension instanceof QueryResultCollectionExtensionInterface 65 | && 66 | $extension->supportsResult($resourceClass, $operationName, $context) 67 | ) { 68 | return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); 69 | } 70 | } 71 | 72 | 73 | return $queryBuilder->getQuery()->getResult(); 74 | } 75 | 76 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 77 | { 78 | return Book::class === $resourceClass; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/DataProvider/CarCollectionDataProvider.php: -------------------------------------------------------------------------------- 1 | managerRegistry = $managerRegistry; 23 | $this->collectionExtensions = $collectionExtensions; 24 | } 25 | 26 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 27 | { 28 | return Car::class === $resourceClass; 29 | } 30 | 31 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable 32 | { 33 | $queryNameGenerator = new QueryNameGenerator(); 34 | $queryBuilder = $this->managerRegistry 35 | ->getManagerForClass($resourceClass) 36 | ->getRepository($resourceClass) 37 | ->createQueryBuilder('c'); 38 | 39 | if (isset($context['filters']['color'])) { 40 | $queryBuilder 41 | ->where('c.color = :color') 42 | ->setParameter('color', $context['filters']['color']) 43 | ; 44 | } 45 | 46 | /** @var FilterExtension $extension */ 47 | foreach ($this->collectionExtensions as $extension) { 48 | $extension->applyToCollection($queryBuilder, $queryNameGenerator, $resourceClass, $operationName, $context); 49 | if ($extension instanceof QueryResultCollectionExtensionInterface && 50 | $extension->supportsResult($resourceClass, $operationName, $context) 51 | ) { 52 | return $extension->getResult($queryBuilder, $resourceClass, $operationName, $context); 53 | } 54 | } 55 | 56 | return $queryBuilder->getQuery()->getResult(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/DataProvider/CityCollectionDataProvider.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 25 | $this->paginationExtension = $paginationExtension; 26 | } 27 | 28 | /** 29 | * @param array $context 30 | */ 31 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 32 | { 33 | return City::class === $resourceClass; 34 | } 35 | 36 | /** 37 | * @param array $context 38 | * 39 | * @throws \RuntimeException 40 | * 41 | * @return iterable 42 | */ 43 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable 44 | { 45 | $cityFilterSearch = $context[CityFilter::CITY_FILTER_SEARCH_CONTEXT] ?? null; 46 | $cityFilterOrder = $context[CityFilter::CITY_FILTER_ORDER_CONTEXT] ?? null; 47 | 48 | try { 49 | /** @var CityDataRepository $repository */ 50 | $repository = $this->repository; 51 | 52 | if ($cityFilterSearch) { 53 | $repository->setFilter($cityFilterSearch); 54 | } 55 | if ($cityFilterOrder) { 56 | $repository->setOrder($cityFilterOrder); 57 | } 58 | 59 | $collection = $repository->getCities(); 60 | } catch (\Exception $e) { 61 | throw new \RuntimeException(sprintf('Unable to retrieve cities from external source: %s', $e->getMessage())); 62 | } 63 | 64 | if (!$this->paginationExtension->isEnabled($resourceClass, $operationName, $context)) { 65 | return $collection; 66 | } 67 | 68 | return $this->paginationExtension->getResult($collection, $resourceClass, $operationName, $context); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/DataProvider/CommentSubresourceDataProvider.php: -------------------------------------------------------------------------------- 1 | subresourceDataProvider = $subresourceDataProvider; 19 | } 20 | 21 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 22 | { 23 | return false === $this->alreadyInvoked && $resourceClass === Comment::class; 24 | } 25 | 26 | public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) 27 | { 28 | $this->alreadyInvoked = true; 29 | 30 | return $this->subresourceDataProvider->getSubresource($resourceClass, $identifiers, $context, $operationName); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/DataProvider/DiscussionSubresourceDataProvider.php: -------------------------------------------------------------------------------- 1 | managerRegistry = $managerRegistry; 24 | $this->paginationExtension = $paginationExtension; 25 | } 26 | 27 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 28 | { 29 | return $resourceClass === Message::class; 30 | } 31 | 32 | public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) 33 | { 34 | $queryBuilder = $this->managerRegistry 35 | ->getManagerForClass($resourceClass) 36 | ->getRepository($resourceClass) 37 | ->createQueryBuilder('m') 38 | ->orderBy('m.id', 'DESC') 39 | ->innerJoin('m.discussion', 'd', 'WITH', 'm.discussion = :discussion') 40 | ->setParameter('discussion', $identifiers['id']['id']) 41 | ; 42 | 43 | $this->paginationExtension->applyToCollection($queryBuilder, new QueryNameGenerator(), $resourceClass, $operationName, $context); 44 | 45 | if ($this->paginationExtension instanceof QueryResultCollectionExtensionInterface && 46 | $this->paginationExtension->supportsResult($resourceClass, $operationName, $context)) 47 | { 48 | return $this->paginationExtension->getResult($queryBuilder, $resourceClass, $operationName, $context); 49 | } 50 | 51 | return $queryBuilder->getQuery()->getResult(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/DataProvider/Extension/CityCollectionExtensionInterface.php: -------------------------------------------------------------------------------- 1 | $collection 15 | * @param array $context 16 | * 17 | * @return iterable 18 | */ 19 | public function getResult(array $collection, string $resourceClass, string $operationName = null, array $context = []): iterable; 20 | 21 | /** 22 | * Tells if the extension is enabled or not. 23 | * 24 | * @param array $context 25 | */ 26 | public function isEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool; 27 | } 28 | -------------------------------------------------------------------------------- /src/DataProvider/Extension/CityPaginationExtension.php: -------------------------------------------------------------------------------- 1 | pagination = $pagination; 16 | } 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function getResult(array $collection, string $resourceClass, string $operationName = null, array $context = []): iterable 22 | { 23 | [, $offset, $itemPerPage] = $this->pagination->getPagination($resourceClass, $operationName, $context); 24 | 25 | return new ArrayPaginator($collection, $offset, $itemPerPage); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function isEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool 32 | { 33 | return $this->pagination->isEnabled($resourceClass, $operationName, $context); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/DataProvider/Extension/MovieCollectionExtensionInterface.php: -------------------------------------------------------------------------------- 1 | $collection 15 | * @param array $context 16 | * 17 | * @return iterable 18 | */ 19 | public function getResult(array $collection, string $resourceClass, string $operationName = null, array $context = []): iterable; 20 | 21 | /** 22 | * Tells if the extension is enabled or not. 23 | * 24 | * @param array $context 25 | */ 26 | public function isEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool; 27 | } 28 | -------------------------------------------------------------------------------- /src/DataProvider/Extension/MoviePaginationExtension.php: -------------------------------------------------------------------------------- 1 | pagination = $pagination; 16 | } 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function getResult(array $collection, string $resourceClass, string $operationName = null, array $context = []): iterable 22 | { 23 | [, $offset, $itemPerPage] = $this->pagination->getPagination($resourceClass, $operationName, $context); 24 | 25 | return new ArrayPaginator($collection, $offset, $itemPerPage); 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function isEnabled(string $resourceClass = null, string $operationName = null, array $context = []): bool 32 | { 33 | return $this->pagination->isEnabled($resourceClass, $operationName, $context); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/DataProvider/FurnitureDataProvider.php: -------------------------------------------------------------------------------- 1 | collectionDataProvider = $collectionDataProvider; 17 | } 18 | 19 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 20 | { 21 | return $resourceClass === Furniture::class; 22 | } 23 | 24 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []) 25 | { 26 | return $this->collectionDataProvider->getCollection($resourceClass, $operationName, $context); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/DataProvider/JobCollectionDataProvider.php: -------------------------------------------------------------------------------- 1 | managerRegistry = $managerRegistry; 21 | $this->paginationExtension = $paginationExtension; 22 | } 23 | 24 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 25 | { 26 | return Job::class === $resourceClass; 27 | } 28 | 29 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable 30 | { 31 | $queryBuilder = $this->managerRegistry 32 | ->getManagerForClass($resourceClass) 33 | ->getRepository($resourceClass) 34 | ->createQueryBuilder('j') 35 | ; 36 | 37 | $this->paginationExtension->applyToCollection($queryBuilder, new QueryNameGenerator(), $resourceClass, $operationName, $context); 38 | 39 | if ($this->paginationExtension instanceof QueryResultCollectionExtensionInterface && 40 | // next line is from `attributes={"pagination_enabled"=true}` on Job entity 41 | $this->paginationExtension->supportsResult($resourceClass, $operationName, $context)) 42 | { 43 | return $this->paginationExtension->getResult($queryBuilder, $resourceClass, $operationName, $context); 44 | } 45 | 46 | return $queryBuilder->getQuery()->getResult(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/DataProvider/JobEmployeeDataProvider.php: -------------------------------------------------------------------------------- 1 | itemDataProvider = $itemDataProvider; 21 | $this->pagination = $pagination; 22 | } 23 | 24 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 25 | { 26 | return 27 | $resourceClass === Job::class && 28 | $operationName === "custom-subresource-job-employees" 29 | ; 30 | } 31 | 32 | public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []) 33 | { 34 | $itemDataProvider = $this->itemDataProvider->getItem($resourceClass, $id, $operationName, $context); 35 | 36 | [$page, $offset, $itemPerPage] = $this->pagination->getPagination($resourceClass, $operationName, $context); 37 | 38 | return $itemDataProvider->getEmployees()->getValues(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/DataProvider/MovieCollectionDataProvider.php: -------------------------------------------------------------------------------- 1 | paginationExtension = $paginationExtension; 23 | $this->movieRepository = $movieRepository; 24 | } 25 | 26 | /** 27 | * @param array $context 28 | */ 29 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 30 | { 31 | if (empty($context["groups"])) { 32 | return false; 33 | } 34 | 35 | return 36 | Movie::class === $resourceClass && 37 | $context["groups"] === "normalization-custom-action-using-dataprovider" 38 | ; 39 | } 40 | 41 | /** 42 | * @param array $context 43 | * 44 | * @throws \RuntimeException 45 | * 46 | * @return iterable 47 | */ 48 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []): iterable 49 | { 50 | $isPublished = []; 51 | if (isset($context['filters']['isPublished'])) { 52 | $isPublished['isPublished'] = $context['filters']['isPublished']; 53 | } 54 | 55 | $movies = $this->movieRepository->findBy($isPublished, 56 | $context['filters']['order'] 57 | ); 58 | 59 | return $this->paginationExtension->getResult($movies, $resourceClass, $operationName, $context); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/DataProvider/Pagination/PostPaginator.php: -------------------------------------------------------------------------------- 1 | currentPage = $currentPage; 25 | $this->maxResults = $maxResults; 26 | $this->queryBuilder = $queryBuilder; 27 | $this->totalResult = $totalResult; 28 | } 29 | 30 | public function getLastPage(): float 31 | { 32 | return ceil($this->getTotalItems() / $this->getItemsPerPage()) ?: 1.; 33 | } 34 | 35 | public function getTotalItems(): float 36 | { 37 | return $this->totalResult; 38 | } 39 | 40 | public function getCurrentPage(): float 41 | { 42 | return $this->currentPage; 43 | } 44 | 45 | public function getItemsPerPage(): float 46 | { 47 | return $this->maxResults; 48 | } 49 | 50 | public function count() 51 | { 52 | return iterator_count($this->getIterator()); 53 | } 54 | 55 | public function getIterator() 56 | { 57 | if ($this->postsIterator === null) { 58 | $offset = ($this->currentPage - 1) * $this->maxResults; 59 | 60 | $query = $this->queryBuilder 61 | ->setFirstResult($offset) 62 | ->setMaxResults($this->maxResults) 63 | ->getQuery() 64 | ; 65 | $this->results = $query->getResult(); 66 | 67 | $this->postsIterator = new \ArrayIterator( 68 | $this->results 69 | ); 70 | } 71 | 72 | return $this->postsIterator; 73 | } 74 | 75 | public function getResults(): \Traversable 76 | { 77 | return $this->results; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/DataProvider/PostCollectionDataProvider.php: -------------------------------------------------------------------------------- 1 | postRepository = $postRepository; 20 | $this->pagination = $pagination; 21 | } 22 | 23 | public function supports(string $resourceClass, string $operationName = null, array $context = []): bool 24 | { 25 | return $resourceClass === Post::class; 26 | } 27 | 28 | public function getCollection(string $resourceClass, string $operationName = null, array $context = []) 29 | { 30 | [$page] = $this->pagination->getPagination($resourceClass, $operationName, $context); 31 | 32 | return $this->postRepository->findLatest($page); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Doctrine/BookExtension.php: -------------------------------------------------------------------------------- 1 | getRootAliases()[0]; 22 | $queryBuilder 23 | ->andWhere("$rootAlias.locale = :locale") 24 | ->setParameter('locale', self::LOCALE) 25 | ; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aratinau/api-platform-pagination/1b7f864c4040811b0f6e657133bc724f31e00de2/src/Entity/.gitignore -------------------------------------------------------------------------------- /src/Entity/Album.php: -------------------------------------------------------------------------------- 1 | id; 44 | } 45 | 46 | public function getTitle(): ?string 47 | { 48 | return $this->title; 49 | } 50 | 51 | public function setTitle(string $title): self 52 | { 53 | $this->title = $title; 54 | 55 | return $this; 56 | } 57 | 58 | public function getYear(): ?int 59 | { 60 | return $this->year; 61 | } 62 | 63 | public function setYear(int $year): self 64 | { 65 | $this->year = $year; 66 | 67 | return $this; 68 | } 69 | 70 | public function getArtist(): ?Artist 71 | { 72 | return $this->artist; 73 | } 74 | 75 | public function setArtist(?Artist $artist): self 76 | { 77 | $this->artist = $artist; 78 | 79 | return $this; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Entity/Artist.php: -------------------------------------------------------------------------------- 1 | albums = new ArrayCollection(); 51 | } 52 | 53 | public function getId(): ?int 54 | { 55 | return $this->id; 56 | } 57 | 58 | public function getAuthor(): ?string 59 | { 60 | return $this->author; 61 | } 62 | 63 | public function setAuthor(string $author): self 64 | { 65 | $this->author = $author; 66 | 67 | return $this; 68 | } 69 | 70 | /** 71 | * @return Collection|Album[] 72 | */ 73 | public function getAlbums(): Collection 74 | { 75 | return $this->albums; 76 | } 77 | 78 | public function addAlbum(Album $album): self 79 | { 80 | if (!$this->albums->contains($album)) { 81 | $this->albums[] = $album; 82 | $album->setArtist($this); 83 | } 84 | 85 | return $this; 86 | } 87 | 88 | public function removeAlbum(Album $album): self 89 | { 90 | if ($this->albums->removeElement($album)) { 91 | // set the owning side to null (unless already changed) 92 | if ($album->getArtist() === $this) { 93 | $album->setArtist(null); 94 | } 95 | } 96 | 97 | return $this; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Entity/Book.php: -------------------------------------------------------------------------------- 1 | id; 43 | } 44 | 45 | public function getTitle(): ?string 46 | { 47 | return $this->title; 48 | } 49 | 50 | public function setTitle(string $title): self 51 | { 52 | $this->title = $title; 53 | 54 | return $this; 55 | } 56 | 57 | public function getIsArchived(): ?bool 58 | { 59 | return $this->isArchived; 60 | } 61 | 62 | public function setIsArchived(bool $isArchived): self 63 | { 64 | $this->isArchived = $isArchived; 65 | 66 | return $this; 67 | } 68 | 69 | public function getLocale(): ?string 70 | { 71 | return $this->locale; 72 | } 73 | 74 | public function setLocale(string $locale): self 75 | { 76 | $this->locale = $locale; 77 | 78 | return $this; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Entity/Car.php: -------------------------------------------------------------------------------- 1 | id; 46 | } 47 | 48 | public function getName(): ?string 49 | { 50 | return $this->name; 51 | } 52 | 53 | public function setName(string $name): self 54 | { 55 | $this->name = $name; 56 | 57 | return $this; 58 | } 59 | 60 | public function getColor(): ?string 61 | { 62 | return $this->color; 63 | } 64 | 65 | public function setColor(string $color): self 66 | { 67 | $this->color = $color; 68 | 69 | return $this; 70 | } 71 | 72 | public function getIsPublished(): ?bool 73 | { 74 | return $this->isPublished; 75 | } 76 | 77 | public function setIsPublished(bool $isPublished): self 78 | { 79 | $this->isPublished = $isPublished; 80 | 81 | return $this; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/Entity/City.php: -------------------------------------------------------------------------------- 1 | id = $id; 48 | $this->city = $city; 49 | $this->cityAscii = $cityAscii; 50 | $this->lat = $lat; 51 | $this->lng = $lng; 52 | $this->country = $country; 53 | $this->iso2 = $iso2; 54 | $this->iso3 = $iso3; 55 | $this->capital = $capital; 56 | $this->population = $population; 57 | $this->density = $density; 58 | } 59 | 60 | /** 61 | * @ApiProperty( 62 | * identifier=true 63 | * ) 64 | */ 65 | public function getId(): int 66 | { 67 | return $this->id; 68 | } 69 | 70 | public function setId(int $id): City 71 | { 72 | $this->id = $id; 73 | 74 | return $this; 75 | } 76 | 77 | public function getCity(): ?string 78 | { 79 | return $this->city; 80 | } 81 | 82 | public function setCity(string $city): City 83 | { 84 | $this->city = $city; 85 | 86 | return $this; 87 | } 88 | 89 | public function getCityAscii(): ?string 90 | { 91 | return $this->cityAscii; 92 | } 93 | 94 | public function setCityAscii(string $city_ascii): City 95 | { 96 | $this->cityAscii = $city_ascii; 97 | 98 | return $this; 99 | } 100 | 101 | public function getLat(): ?string 102 | { 103 | return $this->lat; 104 | } 105 | 106 | public function setLat(string $lat): City 107 | { 108 | $this->lat = $lat; 109 | 110 | return $this; 111 | } 112 | 113 | public function getLng(): ?string 114 | { 115 | return $this->lng; 116 | } 117 | 118 | public function setLng(string $lng): City 119 | { 120 | $this->lng = $lng; 121 | 122 | return $this; 123 | } 124 | 125 | public function getCountry(): ?string 126 | { 127 | return $this->country; 128 | } 129 | 130 | public function setCountry(string $country): City 131 | { 132 | $this->country = $country; 133 | 134 | return $this; 135 | } 136 | 137 | public function getIso2(): ?string 138 | { 139 | return $this->iso2; 140 | } 141 | 142 | public function setIso2(string $iso2): City 143 | { 144 | $this->iso2 = $iso2; 145 | 146 | return $this; 147 | } 148 | 149 | public function getIso3(): ?string 150 | { 151 | return $this->iso3; 152 | } 153 | 154 | public function setIso3(string $iso3): City 155 | { 156 | $this->iso3 = $iso3; 157 | 158 | return $this; 159 | } 160 | 161 | public function getCapital(): ?string 162 | { 163 | return $this->capital; 164 | } 165 | 166 | public function setCapital(string $capital): City 167 | { 168 | $this->capital = $capital; 169 | 170 | return $this; 171 | } 172 | 173 | public function getPopulation(): ?string 174 | { 175 | return $this->population; 176 | } 177 | 178 | public function setPopulation(string $population): City 179 | { 180 | $this->population = $population; 181 | 182 | return $this; 183 | } 184 | 185 | public function getDensity(): ?string 186 | { 187 | return $this->density; 188 | } 189 | 190 | public function setDensity(string $density): City 191 | { 192 | $this->density = $density; 193 | 194 | return $this; 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/Entity/Comment.php: -------------------------------------------------------------------------------- 1 | id; 40 | } 41 | 42 | public function getAuthor(): ?string 43 | { 44 | return $this->author; 45 | } 46 | 47 | public function setAuthor(string $author): self 48 | { 49 | $this->author = $author; 50 | 51 | return $this; 52 | } 53 | 54 | public function getContent(): ?string 55 | { 56 | return $this->content; 57 | } 58 | 59 | public function setContent(string $content): self 60 | { 61 | $this->content = $content; 62 | 63 | return $this; 64 | } 65 | 66 | public function getMovie(): ?Movie 67 | { 68 | return $this->movie; 69 | } 70 | 71 | public function setMovie(?Movie $movie): self 72 | { 73 | $this->movie = $movie; 74 | 75 | return $this; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Entity/Discussion.php: -------------------------------------------------------------------------------- 1 | messages = new ArrayCollection(); 39 | } 40 | 41 | public function getId(): ?int 42 | { 43 | return $this->id; 44 | } 45 | 46 | public function getName(): ?string 47 | { 48 | return $this->name; 49 | } 50 | 51 | public function setName(string $name): self 52 | { 53 | $this->name = $name; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * @return Collection|Message[] 60 | */ 61 | public function getMessages(): Collection 62 | { 63 | return $this->messages; 64 | } 65 | 66 | public function addMessage(Message $message): self 67 | { 68 | if (!$this->messages->contains($message)) { 69 | $this->messages[] = $message; 70 | $message->setDiscussion($this); 71 | } 72 | 73 | return $this; 74 | } 75 | 76 | public function removeMessage(Message $message): self 77 | { 78 | if ($this->messages->removeElement($message)) { 79 | // set the owning side to null (unless already changed) 80 | if ($message->getDiscussion() === $this) { 81 | $message->setDiscussion(null); 82 | } 83 | } 84 | 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Entity/Employee.php: -------------------------------------------------------------------------------- 1 | id; 39 | } 40 | 41 | public function getAuthor(): ?string 42 | { 43 | return $this->author; 44 | } 45 | 46 | public function setAuthor(string $author): self 47 | { 48 | $this->author = $author; 49 | 50 | return $this; 51 | } 52 | 53 | public function getJob(): ?Job 54 | { 55 | return $this->job; 56 | } 57 | 58 | public function setJob(?Job $job): self 59 | { 60 | $this->job = $job; 61 | 62 | return $this; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Entity/Furniture.php: -------------------------------------------------------------------------------- 1 | id; 36 | } 37 | public function getName(): ?string 38 | { 39 | return $this->name; 40 | } 41 | 42 | public function setName(string $name): self 43 | { 44 | $this->name = $name; 45 | 46 | return $this; 47 | } 48 | 49 | public function getIsPublished(): ?bool 50 | { 51 | return $this->isPublished; 52 | } 53 | 54 | public function setIsPublished(bool $isPublished): self 55 | { 56 | $this->isPublished = $isPublished; 57 | 58 | return $this; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Entity/Job.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'method' => 'GET', 20 | 'deserialize' => false, 21 | 'path' => '/jobs/{id}/employees', 22 | ], 23 | ], 24 | attributes: ['pagination_enabled' => true] 25 | )] 26 | class Job 27 | { 28 | /** 29 | * @ORM\Id 30 | * @ORM\GeneratedValue 31 | * @ORM\Column(type="integer") 32 | * @Groups({"normalization-custom-subresource-job-employees"}) 33 | */ 34 | private $id; 35 | 36 | /** 37 | * @ORM\Column(type="string", length=255) 38 | * @Groups({"normalization-custom-subresource-job-employees"}) 39 | */ 40 | private $name; 41 | 42 | /** 43 | * @ORM\Column(type="boolean") 44 | * @Groups({"normalization-custom-subresource-job-employees"}) 45 | */ 46 | private $isPublished; 47 | 48 | /** 49 | * @ORM\OneToMany(targetEntity=Employee::class, mappedBy="job") 50 | * @Groups({"normalization-custom-subresource-job-employees"}) 51 | */ 52 | private $employees; 53 | 54 | public function __construct() 55 | { 56 | $this->employees = new ArrayCollection(); 57 | } 58 | 59 | public function getId(): ?int 60 | { 61 | return $this->id; 62 | } 63 | public function getName(): ?string 64 | { 65 | return $this->name; 66 | } 67 | 68 | public function setName(string $name): self 69 | { 70 | $this->name = $name; 71 | 72 | return $this; 73 | } 74 | 75 | public function getIsPublished(): ?bool 76 | { 77 | return $this->isPublished; 78 | } 79 | 80 | public function setIsPublished(bool $isPublished): self 81 | { 82 | $this->isPublished = $isPublished; 83 | 84 | return $this; 85 | } 86 | 87 | /** 88 | * @return Collection|Employee[] 89 | */ 90 | public function getEmployees(): Collection 91 | { 92 | return $this->employees; 93 | } 94 | 95 | public function addEmployee(Employee $employee): self 96 | { 97 | if (!$this->employees->contains($employee)) { 98 | $this->employees[] = $employee; 99 | $employee->setJob($this); 100 | } 101 | 102 | return $this; 103 | } 104 | 105 | public function removeEmployee(Employee $employee): self 106 | { 107 | if ($this->employees->removeElement($employee)) { 108 | // set the owning side to null (unless already changed) 109 | if ($employee->getJob() === $this) { 110 | $employee->setJob(null); 111 | } 112 | } 113 | 114 | return $this; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/Entity/Message.php: -------------------------------------------------------------------------------- 1 | id; 36 | } 37 | 38 | public function getContent(): ?string 39 | { 40 | return $this->content; 41 | } 42 | 43 | public function setContent(string $content): self 44 | { 45 | $this->content = $content; 46 | 47 | return $this; 48 | } 49 | 50 | public function getDiscussion(): ?Discussion 51 | { 52 | return $this->discussion; 53 | } 54 | 55 | public function setDiscussion(?Discussion $discussion): self 56 | { 57 | $this->discussion = $discussion; 58 | 59 | return $this; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Entity/Movie.php: -------------------------------------------------------------------------------- 1 | comments = new ArrayCollection(); 64 | } 65 | 66 | public function getId(): ?int 67 | { 68 | return $this->id; 69 | } 70 | 71 | public function getTitle(): ?string 72 | { 73 | return $this->title; 74 | } 75 | 76 | public function setTitle(string $title): self 77 | { 78 | $this->title = $title; 79 | 80 | return $this; 81 | } 82 | 83 | public function getIsPublished(): ?bool 84 | { 85 | return $this->isPublished; 86 | } 87 | 88 | public function setIsPublished(bool $isPublished): self 89 | { 90 | $this->isPublished = $isPublished; 91 | 92 | return $this; 93 | } 94 | 95 | /** 96 | * @return Collection|Comment[] 97 | */ 98 | public function getComments(): Collection 99 | { 100 | return $this->comments; 101 | } 102 | 103 | public function addComment(Comment $comment): self 104 | { 105 | if (!$this->comments->contains($comment)) { 106 | $this->comments[] = $comment; 107 | $comment->setMovie($this); 108 | } 109 | 110 | return $this; 111 | } 112 | 113 | public function removeComment(Comment $comment): self 114 | { 115 | if ($this->comments->removeElement($comment)) { 116 | // set the owning side to null (unless already changed) 117 | if ($comment->getMovie() === $this) { 118 | $comment->setMovie(null); 119 | } 120 | } 121 | 122 | return $this; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Entity/Post.php: -------------------------------------------------------------------------------- 1 | publishedAt = new \DateTime(); 77 | // $this->comments = new ArrayCollection(); 78 | // $this->tags = new ArrayCollection(); 79 | } 80 | 81 | public function getId(): ?int 82 | { 83 | return $this->id; 84 | } 85 | 86 | public function getTitle(): ?string 87 | { 88 | return $this->title; 89 | } 90 | 91 | public function setTitle(?string $title): void 92 | { 93 | $this->title = $title; 94 | } 95 | 96 | public function getSlug(): ?string 97 | { 98 | return $this->slug; 99 | } 100 | 101 | public function setSlug(string $slug): void 102 | { 103 | $this->slug = $slug; 104 | } 105 | 106 | public function getContent(): ?string 107 | { 108 | return $this->content; 109 | } 110 | 111 | public function setContent(?string $content): void 112 | { 113 | $this->content = $content; 114 | } 115 | 116 | public function getPublishedAt(): \DateTime 117 | { 118 | return $this->publishedAt; 119 | } 120 | 121 | public function setPublishedAt(\DateTime $publishedAt): void 122 | { 123 | $this->publishedAt = $publishedAt; 124 | } 125 | 126 | public function getSummary(): ?string 127 | { 128 | return $this->summary; 129 | } 130 | 131 | public function setSummary(?string $summary): void 132 | { 133 | $this->summary = $summary; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/Filter/CityFilter.php: -------------------------------------------------------------------------------- 1 | throwOnInvalid = $throwOnInvalid; 17 | } 18 | 19 | public function apply(Request $request, bool $normalization, array $attributes, array &$context) 20 | { 21 | $search = $request->query->get('search'); 22 | $order = $request->query->get('order'); 23 | 24 | if (!$search && $this->throwOnInvalid) { 25 | return; 26 | } 27 | 28 | if ($search) { 29 | $context[self::CITY_FILTER_SEARCH_CONTEXT] = $search; 30 | } 31 | 32 | if ($order) { 33 | $context[self::CITY_FILTER_ORDER_CONTEXT] = $order; 34 | } 35 | } 36 | 37 | public function getDescription(string $resourceClass): array 38 | { 39 | return [ 40 | 'search' => [ 41 | 'property' => null, 42 | 'type' => 'string', 43 | 'required' => false, 44 | 'openapi' => [ 45 | 'description' => 'Search and Order by key from array', 46 | ], 47 | ] 48 | ]; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | import('../config/{packages}/*.yaml'); 17 | $container->import('../config/{packages}/'.$this->environment.'/*.yaml'); 18 | 19 | if (is_file(\dirname(__DIR__).'/config/services.yaml')) { 20 | $container->import('../config/services.yaml'); 21 | $container->import('../config/{services}_'.$this->environment.'.yaml'); 22 | } else { 23 | $container->import('../config/{services}.php'); 24 | } 25 | } 26 | 27 | protected function configureRoutes(RoutingConfigurator $routes): void 28 | { 29 | $routes->import('../config/{routes}/'.$this->environment.'/*.yaml'); 30 | $routes->import('../config/{routes}/*.yaml'); 31 | 32 | if (is_file(\dirname(__DIR__).'/config/routes.yaml')) { 33 | $routes->import('../config/routes.yaml'); 34 | } else { 35 | $routes->import('../config/{routes}.php'); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Repository/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aratinau/api-platform-pagination/1b7f864c4040811b0f6e657133bc724f31e00de2/src/Repository/.gitignore -------------------------------------------------------------------------------- /src/Repository/AlbumRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('a') 29 | ->andWhere('a.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('a.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Album 41 | { 42 | return $this->createQueryBuilder('a') 43 | ->andWhere('a.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/ArtistRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('a') 29 | ->andWhere('a.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('a.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Artist 41 | { 42 | return $this->createQueryBuilder('a') 43 | ->andWhere('a.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/BookRepository.php: -------------------------------------------------------------------------------- 1 | 13 | * 14 | * @method Book|null find($id, $lockMode = null, $lockVersion = null) 15 | * @method Book|null findOneBy(array $criteria, array $orderBy = null) 16 | * @method Book[] findAll() 17 | * @method Book[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 18 | */ 19 | class BookRepository extends ServiceEntityRepository 20 | { 21 | public function __construct(ManagerRegistry $registry) 22 | { 23 | parent::__construct($registry, Book::class); 24 | } 25 | 26 | /** 27 | * @throws ORMException 28 | * @throws OptimisticLockException 29 | */ 30 | public function add(Book $entity, bool $flush = true): void 31 | { 32 | $this->_em->persist($entity); 33 | if ($flush) { 34 | $this->_em->flush(); 35 | } 36 | } 37 | 38 | /** 39 | * @throws ORMException 40 | * @throws OptimisticLockException 41 | */ 42 | public function remove(Book $entity, bool $flush = true): void 43 | { 44 | $this->_em->remove($entity); 45 | if ($flush) { 46 | $this->_em->flush(); 47 | } 48 | } 49 | 50 | // /** 51 | // * @return Book[] Returns an array of Book objects 52 | // */ 53 | /* 54 | public function findByExampleField($value) 55 | { 56 | return $this->createQueryBuilder('b') 57 | ->andWhere('b.exampleField = :val') 58 | ->setParameter('val', $value) 59 | ->orderBy('b.id', 'ASC') 60 | ->setMaxResults(10) 61 | ->getQuery() 62 | ->getResult() 63 | ; 64 | } 65 | */ 66 | 67 | /* 68 | public function findOneBySomeField($value): ?Book 69 | { 70 | return $this->createQueryBuilder('b') 71 | ->andWhere('b.exampleField = :val') 72 | ->setParameter('val', $value) 73 | ->getQuery() 74 | ->getOneOrNullResult() 75 | ; 76 | } 77 | */ 78 | } 79 | -------------------------------------------------------------------------------- /src/Repository/CarRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 29 | ->andWhere('c.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('c.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Car 41 | { 42 | return $this->createQueryBuilder('c') 43 | ->andWhere('c.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/City/CityCachedDataRepository.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 20 | $this->cache = $cache; 21 | } 22 | 23 | /** 24 | * Local caching is done so the CSV isn't reloaded at every call. 25 | * 26 | * @throws \InvalidArgumentException 27 | * 28 | * @return array 29 | */ 30 | public function getCities(): array 31 | { 32 | return $this->cache->get('cities', function () { 33 | return $this->repository->getCities(); 34 | }); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Repository/City/CityDataInterface.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | public function getCities(): array; 15 | } 16 | -------------------------------------------------------------------------------- /src/Repository/City/CityDataRepository.php: -------------------------------------------------------------------------------- 1 | 22 | */ 23 | public function getCities(): array 24 | { 25 | return $this->getFromCsv(); 26 | } 27 | 28 | /** 29 | * @return array 30 | */ 31 | public function getFromCsv(): array 32 | { 33 | foreach ($this->getFileAsArray() as $line) { 34 | $data[] = str_getcsv($line, ','); 35 | } 36 | 37 | $cpt = 0; 38 | foreach ($data ?? [] as $row) { 39 | if (1 === ++$cpt) { 40 | continue; 41 | } 42 | if (self::FIELDS_COUNT !== count($row)) { 43 | throw new \RuntimeException(sprintf('Invalid data at row: %d', count($row))); 44 | } 45 | 46 | if ($this->filter) { 47 | foreach ($this->filter as $key => $filter) { 48 | switch ($key) { 49 | case "city": 50 | $rowId = 0; 51 | break; 52 | case "city_ascii": 53 | $rowId = 1; 54 | break; 55 | case "lat": 56 | $rowId = 2; 57 | break; 58 | case "lng": 59 | $rowId = 3; 60 | break; 61 | case "country": 62 | $rowId = 4; 63 | break; 64 | case "iso2": 65 | $rowId = 5; 66 | break; 67 | case "iso3": 68 | $rowId = 6; 69 | break; 70 | case "admin_name": 71 | $rowId = 7; 72 | break; 73 | case "capital": 74 | $rowId = 8; 75 | break; 76 | case "population": 77 | $rowId = 9; 78 | break; 79 | } 80 | } 81 | $city = strtolower($row[$rowId]); 82 | if (strpos($city, $filter) === false) { 83 | continue; 84 | } 85 | } 86 | 87 | $city = new City( 88 | $cpt - 1, 89 | $this->sanitize($row[0] ?? ''), 90 | $this->sanitize($row[1] ?? ''), 91 | $this->sanitize($row[2] ?? ''), 92 | $this->sanitize($row[3] ?? ''), 93 | $this->sanitize($row[4] ?? ''), 94 | $this->sanitize($row[5] ?? ''), 95 | $this->sanitize($row[6] ?? ''), 96 | $this->sanitize($row[7] ?? ''), 97 | $this->sanitize($row[8] ?? ''), 98 | $this->sanitize($row[9] ?? ''), 99 | $this->sanitize($row[10] ?? '') 100 | ); 101 | $cities[$cpt - 1] = $city; 102 | } 103 | 104 | if ($this->order) { 105 | foreach ($this->order as $key => $order) 106 | { 107 | usort($cities, function ($a, $b) use($key, $order) { 108 | $getMethod = 'get' . ucfirst($key); 109 | if ($a->{$getMethod}() === $b->{$getMethod}()) { 110 | return 0; 111 | } 112 | if ($order === 'desc') { 113 | return ($a->{$getMethod}() < $b->{$getMethod}()); 114 | } else { 115 | return ($a->{$getMethod}() > $b->{$getMethod}()); 116 | } 117 | }); 118 | } 119 | } 120 | 121 | return $cities ?? []; 122 | } 123 | 124 | /** 125 | * @return array 126 | */ 127 | private function getFileAsArray(): array 128 | { 129 | $csvFileName = __DIR__.'/data/'.self::DATA_SOURCE; 130 | if (!is_file($csvFileName)) { 131 | throw new \RuntimeException(sprintf("Can't find data source: %s", $csvFileName)); 132 | } 133 | $file = file($csvFileName); 134 | if (!is_array($file)) { 135 | throw new \RuntimeException(sprintf("Can't load data source: %s", $csvFileName)); 136 | } 137 | 138 | return $file; 139 | } 140 | 141 | /** 142 | * @param string|null $str 143 | * @return string 144 | */ 145 | private function sanitize(?string $str): string 146 | { 147 | return trim(utf8_encode((string) $str)); 148 | } 149 | 150 | /** 151 | * @return string|null 152 | */ 153 | public function getFilter(): ?string 154 | { 155 | return $this->filter; 156 | } 157 | 158 | /** 159 | * @param array $filter 160 | * @return $this 161 | */ 162 | public function setFilter(array $filter): CityDataRepository 163 | { 164 | $this->filter = $filter; 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * @return string|null 171 | */ 172 | public function getOrder(): ?string 173 | { 174 | return $this->order; 175 | } 176 | 177 | /** 178 | * @param array $order 179 | * @return $this 180 | */ 181 | public function setOrder(array $order): CityDataRepository 182 | { 183 | $this->order = $order; 184 | 185 | return $this; 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Repository/CommentRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('c') 29 | ->andWhere('c.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('c.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Comment 41 | { 42 | return $this->createQueryBuilder('c') 43 | ->andWhere('c.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/DiscussionRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('d') 29 | ->andWhere('d.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('d.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Discussion 41 | { 42 | return $this->createQueryBuilder('d') 43 | ->andWhere('d.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/EmployeeRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('e') 29 | ->andWhere('e.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('e.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Employee 41 | { 42 | return $this->createQueryBuilder('e') 43 | ->andWhere('e.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/FurnitureRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('f') 29 | ->andWhere('f.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('f.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Furniture 41 | { 42 | return $this->createQueryBuilder('f') 43 | ->andWhere('f.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/JobRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('j') 29 | ->andWhere('j.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('j.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Job 41 | { 42 | return $this->createQueryBuilder('j') 43 | ->andWhere('j.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/MessageRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('m') 29 | ->andWhere('m.exampleField = :val') 30 | ->setParameter('val', $value) 31 | ->orderBy('m.id', 'ASC') 32 | ->setMaxResults(10) 33 | ->getQuery() 34 | ->getResult() 35 | ; 36 | } 37 | */ 38 | 39 | /* 40 | public function findOneBySomeField($value): ?Message 41 | { 42 | return $this->createQueryBuilder('m') 43 | ->andWhere('m.exampleField = :val') 44 | ->setParameter('val', $value) 45 | ->getQuery() 46 | ->getOneOrNullResult() 47 | ; 48 | } 49 | */ 50 | } 51 | -------------------------------------------------------------------------------- /src/Repository/MovieRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('b'); 33 | 34 | if (false === empty($orders)) { 35 | foreach ($orders as $sort => $order) { 36 | $queryBuilder->addOrderBy('b.'.$sort, $order); 37 | } 38 | } 39 | 40 | $criteria = Criteria::create() 41 | ->setFirstResult($firstResult) 42 | ->setMaxResults(self::ITEMS_PER_PAGE) 43 | ; 44 | $queryBuilder->addCriteria($criteria); 45 | 46 | $doctrinePaginator = new DoctrinePaginator($queryBuilder); 47 | $paginator = new Paginator($doctrinePaginator); 48 | 49 | return $paginator; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/Repository/PostRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder('p') 20 | ->where('p.publishedAt <= :now') 21 | ->orderBy('p.publishedAt', 'DESC') 22 | ->setParameter('now', new \DateTime()) 23 | ; 24 | 25 | $totalItems = $this->createQueryBuilder('p') 26 | ->select('count(p.id)') 27 | ->getQuery() 28 | ->getSingleScalarResult() 29 | ; 30 | 31 | return (new PostPaginator($qb, $page, $totalItems)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "api-platform/core": { 3 | "version": "2.5", 4 | "recipe": { 5 | "repo": "github.com/symfony/recipes", 6 | "branch": "master", 7 | "version": "2.5", 8 | "ref": "a93061567140e386f107be75340ac2aee3f86cbf" 9 | }, 10 | "files": [ 11 | "config/packages/api_platform.yaml", 12 | "config/routes/api_platform.yaml", 13 | "src/Entity/.gitignore" 14 | ] 15 | }, 16 | "composer/package-versions-deprecated": { 17 | "version": "1.11.99.2" 18 | }, 19 | "doctrine/annotations": { 20 | "version": "1.0", 21 | "recipe": { 22 | "repo": "github.com/symfony/recipes", 23 | "branch": "master", 24 | "version": "1.0", 25 | "ref": "a2759dd6123694c8d901d0ec80006e044c2e6457" 26 | }, 27 | "files": [ 28 | "config/routes/annotations.yaml" 29 | ] 30 | }, 31 | "doctrine/cache": { 32 | "version": "2.0.3" 33 | }, 34 | "doctrine/collections": { 35 | "version": "1.6.7" 36 | }, 37 | "doctrine/common": { 38 | "version": "3.1.2" 39 | }, 40 | "doctrine/data-fixtures": { 41 | "version": "1.5.0" 42 | }, 43 | "doctrine/dbal": { 44 | "version": "2.13.2" 45 | }, 46 | "doctrine/deprecations": { 47 | "version": "v0.5.3" 48 | }, 49 | "doctrine/doctrine-bundle": { 50 | "version": "2.4", 51 | "recipe": { 52 | "repo": "github.com/symfony/recipes", 53 | "branch": "master", 54 | "version": "2.4", 55 | "ref": "dda18c8830b143bc31c0e0457fb13b9029614d76" 56 | }, 57 | "files": [ 58 | "config/packages/doctrine.yaml", 59 | "config/packages/prod/doctrine.yaml", 60 | "config/packages/test/doctrine.yaml", 61 | "src/Entity/.gitignore", 62 | "src/Repository/.gitignore" 63 | ] 64 | }, 65 | "doctrine/doctrine-migrations-bundle": { 66 | "version": "3.1", 67 | "recipe": { 68 | "repo": "github.com/symfony/recipes", 69 | "branch": "master", 70 | "version": "3.1", 71 | "ref": "ee609429c9ee23e22d6fa5728211768f51ed2818" 72 | }, 73 | "files": [ 74 | "config/packages/doctrine_migrations.yaml", 75 | "migrations/.gitignore" 76 | ] 77 | }, 78 | "doctrine/event-manager": { 79 | "version": "1.1.1" 80 | }, 81 | "doctrine/inflector": { 82 | "version": "2.0.3" 83 | }, 84 | "doctrine/instantiator": { 85 | "version": "1.4.0" 86 | }, 87 | "doctrine/lexer": { 88 | "version": "1.2.1" 89 | }, 90 | "doctrine/migrations": { 91 | "version": "3.1.4" 92 | }, 93 | "doctrine/orm": { 94 | "version": "2.9.3" 95 | }, 96 | "doctrine/persistence": { 97 | "version": "2.2.1" 98 | }, 99 | "doctrine/sql-formatter": { 100 | "version": "1.1.1" 101 | }, 102 | "egulias/email-validator": { 103 | "version": "3.1.1" 104 | }, 105 | "fakerphp/faker": { 106 | "version": "v1.14.1" 107 | }, 108 | "fig/link-util": { 109 | "version": "1.1.2" 110 | }, 111 | "friendsofphp/proxy-manager-lts": { 112 | "version": "v1.0.5" 113 | }, 114 | "hautelook/alice-bundle": { 115 | "version": "2.1", 116 | "recipe": { 117 | "repo": "github.com/symfony/recipes", 118 | "branch": "master", 119 | "version": "2.1", 120 | "ref": "71822522faf7ed2792d86b7f94ce73443358ccb9" 121 | }, 122 | "files": [ 123 | "config/packages/dev/hautelook_alice.yaml", 124 | "config/packages/test/hautelook_alice.yaml", 125 | "fixtures/.gitignore" 126 | ] 127 | }, 128 | "laminas/laminas-code": { 129 | "version": "4.4.0" 130 | }, 131 | "monolog/monolog": { 132 | "version": "2.2.0" 133 | }, 134 | "myclabs/deep-copy": { 135 | "version": "1.10.2" 136 | }, 137 | "nelmio/alice": { 138 | "version": "3.2", 139 | "recipe": { 140 | "repo": "github.com/symfony/recipes", 141 | "branch": "master", 142 | "version": "3.2", 143 | "ref": "0b9900ece737bec7752e4155c0164639dd9b0cb0" 144 | }, 145 | "files": [ 146 | "config/packages/dev/nelmio_alice.yaml", 147 | "config/packages/test/nelmio_alice.yaml" 148 | ] 149 | }, 150 | "nelmio/cors-bundle": { 151 | "version": "1.5", 152 | "recipe": { 153 | "repo": "github.com/symfony/recipes", 154 | "branch": "master", 155 | "version": "1.5", 156 | "ref": "6bea22e6c564fba3a1391615cada1437d0bde39c" 157 | }, 158 | "files": [ 159 | "config/packages/nelmio_cors.yaml" 160 | ] 161 | }, 162 | "nikic/php-parser": { 163 | "version": "v4.10.5" 164 | }, 165 | "phar-io/manifest": { 166 | "version": "2.0.1" 167 | }, 168 | "phar-io/version": { 169 | "version": "3.1.0" 170 | }, 171 | "phpdocumentor/reflection-common": { 172 | "version": "2.2.0" 173 | }, 174 | "phpdocumentor/reflection-docblock": { 175 | "version": "5.2.2" 176 | }, 177 | "phpdocumentor/type-resolver": { 178 | "version": "1.4.0" 179 | }, 180 | "phpspec/prophecy": { 181 | "version": "1.13.0" 182 | }, 183 | "phpunit/php-code-coverage": { 184 | "version": "9.2.6" 185 | }, 186 | "phpunit/php-file-iterator": { 187 | "version": "3.0.5" 188 | }, 189 | "phpunit/php-invoker": { 190 | "version": "3.1.1" 191 | }, 192 | "phpunit/php-text-template": { 193 | "version": "2.0.4" 194 | }, 195 | "phpunit/php-timer": { 196 | "version": "5.0.3" 197 | }, 198 | "phpunit/phpunit": { 199 | "version": "9.3", 200 | "recipe": { 201 | "repo": "github.com/symfony/recipes", 202 | "branch": "master", 203 | "version": "9.3", 204 | "ref": "68450b8fef6804047d9424c55d758c28b4340791" 205 | }, 206 | "files": [ 207 | ".env.test", 208 | "phpunit.xml.dist", 209 | "tests/bootstrap.php" 210 | ] 211 | }, 212 | "psr/cache": { 213 | "version": "1.0.1" 214 | }, 215 | "psr/container": { 216 | "version": "1.1.1" 217 | }, 218 | "psr/event-dispatcher": { 219 | "version": "1.0.0" 220 | }, 221 | "psr/link": { 222 | "version": "1.0.0" 223 | }, 224 | "psr/log": { 225 | "version": "1.1.4" 226 | }, 227 | "sebastian/cli-parser": { 228 | "version": "1.0.1" 229 | }, 230 | "sebastian/code-unit": { 231 | "version": "1.0.8" 232 | }, 233 | "sebastian/code-unit-reverse-lookup": { 234 | "version": "2.0.3" 235 | }, 236 | "sebastian/comparator": { 237 | "version": "4.0.6" 238 | }, 239 | "sebastian/complexity": { 240 | "version": "2.0.2" 241 | }, 242 | "sebastian/diff": { 243 | "version": "4.0.4" 244 | }, 245 | "sebastian/environment": { 246 | "version": "5.1.3" 247 | }, 248 | "sebastian/exporter": { 249 | "version": "4.0.3" 250 | }, 251 | "sebastian/global-state": { 252 | "version": "5.0.3" 253 | }, 254 | "sebastian/lines-of-code": { 255 | "version": "1.0.3" 256 | }, 257 | "sebastian/object-enumerator": { 258 | "version": "4.0.4" 259 | }, 260 | "sebastian/object-reflector": { 261 | "version": "2.0.4" 262 | }, 263 | "sebastian/recursion-context": { 264 | "version": "4.0.4" 265 | }, 266 | "sebastian/resource-operations": { 267 | "version": "3.0.3" 268 | }, 269 | "sebastian/type": { 270 | "version": "2.3.4" 271 | }, 272 | "sebastian/version": { 273 | "version": "3.0.2" 274 | }, 275 | "sensio/framework-extra-bundle": { 276 | "version": "5.2", 277 | "recipe": { 278 | "repo": "github.com/symfony/recipes", 279 | "branch": "master", 280 | "version": "5.2", 281 | "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" 282 | }, 283 | "files": [ 284 | "config/packages/sensio_framework_extra.yaml" 285 | ] 286 | }, 287 | "symfony/asset": { 288 | "version": "v5.3.2" 289 | }, 290 | "symfony/browser-kit": { 291 | "version": "v5.3.0" 292 | }, 293 | "symfony/cache": { 294 | "version": "v5.3.3" 295 | }, 296 | "symfony/cache-contracts": { 297 | "version": "v2.4.0" 298 | }, 299 | "symfony/config": { 300 | "version": "v5.3.3" 301 | }, 302 | "symfony/console": { 303 | "version": "5.3", 304 | "recipe": { 305 | "repo": "github.com/symfony/recipes", 306 | "branch": "master", 307 | "version": "5.3", 308 | "ref": "da0c8be8157600ad34f10ff0c9cc91232522e047" 309 | }, 310 | "files": [ 311 | "bin/console" 312 | ] 313 | }, 314 | "symfony/css-selector": { 315 | "version": "v5.3.0" 316 | }, 317 | "symfony/debug-bundle": { 318 | "version": "4.1", 319 | "recipe": { 320 | "repo": "github.com/symfony/recipes", 321 | "branch": "master", 322 | "version": "4.1", 323 | "ref": "0ce7a032d344fb7b661cd25d31914cd703ad445b" 324 | }, 325 | "files": [ 326 | "config/packages/dev/debug.yaml" 327 | ] 328 | }, 329 | "symfony/dependency-injection": { 330 | "version": "v5.3.3" 331 | }, 332 | "symfony/deprecation-contracts": { 333 | "version": "v2.4.0" 334 | }, 335 | "symfony/doctrine-bridge": { 336 | "version": "v5.3.3" 337 | }, 338 | "symfony/dom-crawler": { 339 | "version": "v5.3.0" 340 | }, 341 | "symfony/dotenv": { 342 | "version": "v5.3.0" 343 | }, 344 | "symfony/error-handler": { 345 | "version": "v5.3.3" 346 | }, 347 | "symfony/event-dispatcher": { 348 | "version": "v5.3.0" 349 | }, 350 | "symfony/event-dispatcher-contracts": { 351 | "version": "v2.4.0" 352 | }, 353 | "symfony/expression-language": { 354 | "version": "v5.3.0" 355 | }, 356 | "symfony/filesystem": { 357 | "version": "v5.3.3" 358 | }, 359 | "symfony/finder": { 360 | "version": "v5.3.0" 361 | }, 362 | "symfony/flex": { 363 | "version": "1.0", 364 | "recipe": { 365 | "repo": "github.com/symfony/recipes", 366 | "branch": "master", 367 | "version": "1.0", 368 | "ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e" 369 | }, 370 | "files": [ 371 | ".env" 372 | ] 373 | }, 374 | "symfony/form": { 375 | "version": "v5.3.3" 376 | }, 377 | "symfony/framework-bundle": { 378 | "version": "5.3", 379 | "recipe": { 380 | "repo": "github.com/symfony/recipes", 381 | "branch": "master", 382 | "version": "5.3", 383 | "ref": "772b77cfb5017644547ef7f9364c54e4eb9a6c61" 384 | }, 385 | "files": [ 386 | "config/packages/cache.yaml", 387 | "config/packages/framework.yaml", 388 | "config/preload.php", 389 | "config/routes/framework.yaml", 390 | "config/services.yaml", 391 | "public/index.php", 392 | "src/Controller/.gitignore", 393 | "src/Kernel.php" 394 | ] 395 | }, 396 | "symfony/http-client": { 397 | "version": "v5.3.3" 398 | }, 399 | "symfony/http-client-contracts": { 400 | "version": "v2.4.0" 401 | }, 402 | "symfony/http-foundation": { 403 | "version": "v5.3.3" 404 | }, 405 | "symfony/http-kernel": { 406 | "version": "v5.3.3" 407 | }, 408 | "symfony/intl": { 409 | "version": "v5.3.0" 410 | }, 411 | "symfony/mailer": { 412 | "version": "4.3", 413 | "recipe": { 414 | "repo": "github.com/symfony/recipes", 415 | "branch": "master", 416 | "version": "4.3", 417 | "ref": "15658c2a0176cda2e7dba66276a2030b52bd81b2" 418 | }, 419 | "files": [ 420 | "config/packages/mailer.yaml" 421 | ] 422 | }, 423 | "symfony/maker-bundle": { 424 | "version": "1.0", 425 | "recipe": { 426 | "repo": "github.com/symfony/recipes", 427 | "branch": "master", 428 | "version": "1.0", 429 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" 430 | } 431 | }, 432 | "symfony/mime": { 433 | "version": "v5.3.2" 434 | }, 435 | "symfony/monolog-bridge": { 436 | "version": "v5.3.3" 437 | }, 438 | "symfony/monolog-bundle": { 439 | "version": "3.7", 440 | "recipe": { 441 | "repo": "github.com/symfony/recipes", 442 | "branch": "master", 443 | "version": "3.7", 444 | "ref": "329f6a5ef2e7aa033f802be833ef8d1268dd0848" 445 | }, 446 | "files": [ 447 | "config/packages/dev/monolog.yaml", 448 | "config/packages/prod/deprecations.yaml", 449 | "config/packages/prod/monolog.yaml", 450 | "config/packages/test/monolog.yaml" 451 | ] 452 | }, 453 | "symfony/notifier": { 454 | "version": "5.0", 455 | "recipe": { 456 | "repo": "github.com/symfony/recipes", 457 | "branch": "master", 458 | "version": "5.0", 459 | "ref": "c31585e252b32fe0e1f30b1f256af553f4a06eb9" 460 | }, 461 | "files": [ 462 | "config/packages/notifier.yaml" 463 | ] 464 | }, 465 | "symfony/options-resolver": { 466 | "version": "v5.3.0" 467 | }, 468 | "symfony/password-hasher": { 469 | "version": "v5.3.3" 470 | }, 471 | "symfony/phpunit-bridge": { 472 | "version": "5.3", 473 | "recipe": { 474 | "repo": "github.com/symfony/recipes", 475 | "branch": "master", 476 | "version": "5.3", 477 | "ref": "0a3b084605bd384975df457a20e9f41ca09fd4ed" 478 | }, 479 | "files": [ 480 | ".env.test", 481 | "bin/phpunit", 482 | "phpunit.xml.dist", 483 | "tests/bootstrap.php" 484 | ] 485 | }, 486 | "symfony/polyfill-intl-grapheme": { 487 | "version": "v1.23.0" 488 | }, 489 | "symfony/polyfill-intl-icu": { 490 | "version": "v1.23.0" 491 | }, 492 | "symfony/polyfill-intl-idn": { 493 | "version": "v1.23.0" 494 | }, 495 | "symfony/polyfill-intl-normalizer": { 496 | "version": "v1.23.0" 497 | }, 498 | "symfony/polyfill-mbstring": { 499 | "version": "v1.23.0" 500 | }, 501 | "symfony/polyfill-php73": { 502 | "version": "v1.23.0" 503 | }, 504 | "symfony/polyfill-php80": { 505 | "version": "v1.23.0" 506 | }, 507 | "symfony/polyfill-php81": { 508 | "version": "v1.23.0" 509 | }, 510 | "symfony/process": { 511 | "version": "v5.3.2" 512 | }, 513 | "symfony/property-access": { 514 | "version": "v5.3.0" 515 | }, 516 | "symfony/property-info": { 517 | "version": "v5.3.1" 518 | }, 519 | "symfony/proxy-manager-bridge": { 520 | "version": "v5.3.0" 521 | }, 522 | "symfony/routing": { 523 | "version": "5.3", 524 | "recipe": { 525 | "repo": "github.com/symfony/recipes", 526 | "branch": "master", 527 | "version": "5.3", 528 | "ref": "2d706bd8c6a9e6730343bb22092dabba1f42f4f3" 529 | }, 530 | "files": [ 531 | "config/packages/routing.yaml", 532 | "config/routes.yaml" 533 | ] 534 | }, 535 | "symfony/runtime": { 536 | "version": "v5.3.3" 537 | }, 538 | "symfony/security-bundle": { 539 | "version": "5.3", 540 | "recipe": { 541 | "repo": "github.com/symfony/recipes", 542 | "branch": "master", 543 | "version": "5.3", 544 | "ref": "8b35bfc00a7716db4ca5a764a4b338476ca3a704" 545 | }, 546 | "files": [ 547 | "config/packages/security.yaml" 548 | ] 549 | }, 550 | "symfony/security-core": { 551 | "version": "v5.3.3" 552 | }, 553 | "symfony/security-csrf": { 554 | "version": "v5.3.3" 555 | }, 556 | "symfony/security-guard": { 557 | "version": "v5.3.0" 558 | }, 559 | "symfony/security-http": { 560 | "version": "v5.3.3" 561 | }, 562 | "symfony/serializer": { 563 | "version": "v5.3.2" 564 | }, 565 | "symfony/service-contracts": { 566 | "version": "v2.4.0" 567 | }, 568 | "symfony/stopwatch": { 569 | "version": "v5.3.0" 570 | }, 571 | "symfony/string": { 572 | "version": "v5.3.3" 573 | }, 574 | "symfony/translation": { 575 | "version": "5.3", 576 | "recipe": { 577 | "repo": "github.com/symfony/recipes", 578 | "branch": "master", 579 | "version": "5.3", 580 | "ref": "da64f5a2b6d96f5dc24914517c0350a5f91dee43" 581 | }, 582 | "files": [ 583 | "config/packages/translation.yaml", 584 | "translations/.gitignore" 585 | ] 586 | }, 587 | "symfony/translation-contracts": { 588 | "version": "v2.4.0" 589 | }, 590 | "symfony/twig-bridge": { 591 | "version": "v5.3.3" 592 | }, 593 | "symfony/twig-bundle": { 594 | "version": "5.3", 595 | "recipe": { 596 | "repo": "github.com/symfony/recipes", 597 | "branch": "master", 598 | "version": "5.3", 599 | "ref": "b416645602504d22d15912e0918001e6d71bb9fa" 600 | }, 601 | "files": [ 602 | "config/packages/twig.yaml", 603 | "templates/base.html.twig" 604 | ] 605 | }, 606 | "symfony/validator": { 607 | "version": "4.3", 608 | "recipe": { 609 | "repo": "github.com/symfony/recipes", 610 | "branch": "master", 611 | "version": "4.3", 612 | "ref": "d902da3e4952f18d3bf05aab29512eb61cabd869" 613 | }, 614 | "files": [ 615 | "config/packages/test/validator.yaml", 616 | "config/packages/validator.yaml" 617 | ] 618 | }, 619 | "symfony/var-dumper": { 620 | "version": "v5.3.3" 621 | }, 622 | "symfony/var-exporter": { 623 | "version": "v5.3.3" 624 | }, 625 | "symfony/web-link": { 626 | "version": "v5.3.3" 627 | }, 628 | "symfony/web-profiler-bundle": { 629 | "version": "3.3", 630 | "recipe": { 631 | "repo": "github.com/symfony/recipes", 632 | "branch": "master", 633 | "version": "3.3", 634 | "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6" 635 | }, 636 | "files": [ 637 | "config/packages/dev/web_profiler.yaml", 638 | "config/packages/test/web_profiler.yaml", 639 | "config/routes/dev/web_profiler.yaml" 640 | ] 641 | }, 642 | "symfony/yaml": { 643 | "version": "v5.3.3" 644 | }, 645 | "theofidry/alice-data-fixtures": { 646 | "version": "1.0", 647 | "recipe": { 648 | "repo": "github.com/symfony/recipes", 649 | "branch": "master", 650 | "version": "1.0", 651 | "ref": "fe5a50faf580eb58f08ada2abe8afbd2d4941e05" 652 | } 653 | }, 654 | "theseer/tokenizer": { 655 | "version": "1.2.0" 656 | }, 657 | "twig/extra-bundle": { 658 | "version": "v3.3.1" 659 | }, 660 | "twig/twig": { 661 | "version": "v3.3.2" 662 | }, 663 | "webmozart/assert": { 664 | "version": "1.10.0" 665 | }, 666 | "willdurand/negotiation": { 667 | "version": "3.0.0" 668 | } 669 | } 670 | -------------------------------------------------------------------------------- /templates/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | {# Run `composer require symfony/webpack-encore-bundle` 7 | and uncomment the following Encore helpers to start using Symfony UX #} 8 | {% block stylesheets %} 9 | {#{{ encore_entry_link_tags('app') }}#} 10 | {% endblock %} 11 | 12 | {% block javascripts %} 13 | {#{{ encore_entry_script_tags('app') }}#} 14 | {% endblock %} 15 | 16 | 17 | {% block body %}{% endblock %} 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__).'/.env'); 11 | } 12 | -------------------------------------------------------------------------------- /translations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aratinau/api-platform-pagination/1b7f864c4040811b0f6e657133bc724f31e00de2/translations/.gitignore --------------------------------------------------------------------------------