├── .env.dist
├── .env.test
├── .gitignore
├── .php_cs.dist
├── .travis.yml
├── EXAMPLES.md
├── LICENSE
├── README.md
├── bin
├── console
└── phpunit
├── composer.json
├── config
├── bundles.php
├── jwt
│ ├── private.pem
│ ├── private.pem-back
│ └── public.pem
├── packages
│ ├── dev
│ │ ├── framework.yaml
│ │ ├── hautelook_alice.yaml
│ │ ├── jms_serializer.yaml
│ │ ├── nelmio_alice.yaml
│ │ ├── routing.yaml
│ │ └── web_profiler.yaml
│ ├── doctrine.yaml
│ ├── doctrine_migrations.yaml
│ ├── framework.yaml
│ ├── jms_serializer.yaml
│ ├── lexik_jwt_authentication.yaml
│ ├── nelmio_api_doc.yaml
│ ├── prod
│ │ ├── doctrine.yaml
│ │ └── jms_serializer.yaml
│ ├── routing.yaml
│ ├── security.yaml
│ ├── security_checker.yaml
│ ├── sensio_framework_extra.yaml
│ ├── stof_doctrine_extensions.yaml
│ ├── test
│ │ ├── framework.yaml
│ │ ├── hautelook_alice.yaml
│ │ ├── nelmio_alice.yaml
│ │ ├── routing.yaml
│ │ └── web_profiler.yaml
│ ├── translation.yaml
│ ├── twig.yaml
│ └── validator.yaml
├── routes.yaml
├── routes
│ ├── annotations.yaml
│ ├── dev
│ │ ├── twig.yaml
│ │ └── web_profiler.yaml
│ └── nelmio_api_doc.yaml
└── services.yaml
├── css
└── style.css
├── fixtures
├── .gitignore
├── data.yml
└── data
│ ├── books.yml
│ ├── movies.yml
│ ├── reviews.yml
│ └── users.yml
├── phpcs.xml.dist
├── phpmd.dist.xml
├── phpunit.xml.dist
├── public
├── css
│ ├── roboto.css
│ └── style.css
├── favicon.ico
├── favicons
│ ├── android-icon-144x144.png
│ ├── android-icon-192x192.png
│ ├── android-icon-36x36.png
│ ├── android-icon-48x48.png
│ ├── android-icon-72x72.png
│ ├── android-icon-96x96.png
│ ├── apple-icon-114x114.png
│ ├── apple-icon-120x120.png
│ ├── apple-icon-144x144.png
│ ├── apple-icon-152x152.png
│ ├── apple-icon-180x180.png
│ ├── apple-icon-57x57.png
│ ├── apple-icon-60x60.png
│ ├── apple-icon-72x72.png
│ ├── apple-icon-76x76.png
│ ├── apple-icon-precomposed.png
│ ├── apple-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon.ico
│ ├── manifest.json
│ ├── ms-icon-144x144.png
│ ├── ms-icon-150x150.png
│ ├── ms-icon-310x310.png
│ └── ms-icon-70x70.png
├── images
│ └── github.png
├── index.php
├── js
│ ├── clusterize.min.js
│ └── functions.js
└── robots.txt
├── src
├── Controller
│ ├── AbstractController.php
│ ├── BookController.php
│ ├── MovieController.php
│ ├── README.md
│ ├── ReviewController.php
│ └── UserController.php
├── Entity
│ ├── AbstractUser.php
│ ├── Book.php
│ ├── Movie.php
│ ├── README.md
│ ├── Review.php
│ └── User.php
├── EventSubscriber
│ ├── README.md
│ └── UserSubscriber.php
├── Exception
│ ├── ApiException.php
│ ├── Enum
│ │ └── ApiErrorEnumType.php
│ └── FormInvalidException.php
├── Form
│ ├── BookType.php
│ ├── Filter
│ │ ├── BookFilter.php
│ │ ├── MovieFilter.php
│ │ ├── ReviewFilter.php
│ │ └── UserFilter.php
│ ├── Handler
│ │ ├── AbstractFormHandler.php
│ │ └── DefaultFormHandler.php
│ ├── MovieType.php
│ ├── README.md
│ ├── ReviewType.php
│ └── UserType.php
├── Interfaces
│ ├── ControllerInterface.php
│ └── RepositoryInterface.php
├── Kernel.php
├── Migrations
│ └── .gitignore
├── Repository
│ ├── AbstractRepository.php
│ ├── BookRepository.php
│ ├── MovieRepository.php
│ ├── README.md
│ ├── ReviewRepository.php
│ └── UserRepository.php
├── Resource
│ ├── PaginationResource.php
│ └── README.md
├── Security
│ ├── README.md
│ ├── UserProvider.php
│ └── Voter
│ │ ├── Book
│ │ ├── CreateBookVoter.php
│ │ ├── DeleteBookVoter.php
│ │ └── UpdateBookVoter.php
│ │ ├── Movie
│ │ ├── CreateMovieVoter.php
│ │ ├── DeleteMovieVoter.php
│ │ └── UpdateMovieVoter.php
│ │ ├── Review
│ │ ├── CreateReviewVoter.php
│ │ ├── DeleteReviewVoter.php
│ │ └── UpdateReviewVoter.php
│ │ └── User
│ │ ├── DeleteUserVoter.php
│ │ └── UpdateUserVoter.php
├── Service
│ ├── Form
│ │ └── FormErrorsSerializer.php
│ ├── Generic
│ │ ├── ResponseCreator.php
│ │ └── SerializationService.php
│ ├── Manager
│ │ └── UserManager.php
│ └── README.md
└── Traits
│ ├── ControllerTrait.php
│ ├── IdColumnTrait.php
│ ├── README.md
│ └── TimeAwareTrait.php
├── templates
├── base.html.twig
└── bundles
│ ├── NelmioApiDocBundle
│ └── SwaggerUi
│ │ └── index.html.twig
│ └── TwigBundle
│ └── Exception
│ ├── error404.html.twig
│ └── error404.json.twig
├── tests
└── Controller
│ ├── AbstractWebTestCase.php
│ ├── BookControllerTest.php
│ ├── MovieControllerTest.php
│ ├── ReviewControllerTest.php
│ └── UserControllerTest.php
├── translations
└── .gitignore
└── var
└── data.db
/.env.dist:
--------------------------------------------------------------------------------
1 | # This file is a "template" of which env vars need to be defined for your application
2 | # Copy this file to .env file for development, create environment variables when deploying to production
3 | # https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration
4 |
5 | ###> symfony/framework-bundle ###
6 | APP_ENV=prod
7 | APP_SECRET=a8fa11779665bac09ad226d42e67c3e9
8 | #TRUSTED_PROXIES=127.0.0.1,127.0.0.2
9 | #TRUSTED_HOSTS=localhost,example.com
10 | ###< symfony/framework-bundle ###
11 |
12 | ###> doctrine/doctrine-bundle ###
13 | # Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
14 | # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
15 | # Configure your db driver and server_version in config/packages/doctrine.yaml
16 | #DATABASE_URL=postgres://symfony:symfony@postgres/symfony
17 | DATABASE_URL=sqlite:///%kernel.project_dir%/var/data.db
18 | ###< doctrine/doctrine-bundle ###
19 |
20 | ###> lexik/jwt-authentication-bundle ###
21 | JWT_SECRET_KEY=%kernel.project_dir%/config/jwt/private.pem
22 | JWT_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
23 | JWT_PASSPHRASE=75cc06031b56099860d4213dadb18289
24 | ###< lexik/jwt-authentication-bundle ###
25 |
--------------------------------------------------------------------------------
/.env.test:
--------------------------------------------------------------------------------
1 | # define your env variables for the test env here
2 | KERNEL_CLASS='App\Kernel'
3 | APP_SECRET='s$cretf0rt3st'
4 | SYMFONY_DEPRECATIONS_HELPER=999999
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ###> symfony/framework-bundle ###
3 | /.env
4 | /.php_cs.cache/
5 | /public/bundles/
6 | /var/
7 | /vendor/
8 | composer.lock
9 | symfony.lock
10 | ###< symfony/framework-bundle ###
11 |
12 | ###> lexik/jwt-authentication-bundle ###
13 | /config/jwt/*.pem
14 | ###< lexik/jwt-authentication-bundle ###
15 |
16 | ###> friendsofphp/php-cs-fixer ###
17 | /.php_cs
18 | /.php_cs.cache
19 | ###< friendsofphp/php-cs-fixer ###
20 |
21 | ###> symfony/framework-bundle ###
22 | /public/bundles/
23 | /var/
24 | /vendor/
25 | ###< symfony/framework-bundle ###
26 |
27 | ###> symfony/phpunit-bridge ###
28 | .phpunit
29 | /phpunit.xml
30 | ###< symfony/phpunit-bridge ###
31 |
32 | ###> friendsofphp/php-cs-fixer ###
33 | /.php_cs
34 | /.php_cs.cache
35 | ###< friendsofphp/php-cs-fixer ###
36 |
37 | ###> squizlabs/php_codesniffer ###
38 | /.phpcs-cache
39 | /phpcs.xml
40 | ###< squizlabs/php_codesniffer ###
41 |
42 | ###> symfony/web-server-bundle ###
43 | /.web-server-pid
44 | ###< symfony/web-server-bundle ###
45 |
--------------------------------------------------------------------------------
/.php_cs.dist:
--------------------------------------------------------------------------------
1 | setRiskyAllowed(true)
11 | ->setRules(
12 | [
13 | '@Symfony' => true,
14 | '@Symfony:risky' => true,
15 | '@PHP71Migration' => true,
16 | '@PHPUnit48Migration:risky' => true,
17 | 'array_syntax' => ['syntax' => 'short'],
18 | 'dir_constant' => true,
19 | 'heredoc_to_nowdoc' => true,
20 | 'linebreak_after_opening_tag' => true,
21 | 'modernize_types_casting' => true,
22 | 'no_multiline_whitespace_before_semicolons' => true,
23 | 'no_unreachable_default_argument_value' => true,
24 | 'no_useless_else' => true,
25 | 'no_useless_return' => true,
26 | 'ordered_class_elements' => true,
27 | 'ordered_imports' => true,
28 | 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false],
29 | 'phpdoc_order' => true,
30 | 'declare_strict_types' => true,
31 | 'doctrine_annotation_braces' => true,
32 | 'doctrine_annotation_indentation' => true,
33 | 'doctrine_annotation_spaces' => true,
34 | 'psr4' => true,
35 | 'no_php4_constructor' => true,
36 | 'no_short_echo_tag' => true,
37 | 'semicolon_after_instruction' => true,
38 | 'align_multiline_comment' => true,
39 | 'doctrine_annotation_array_assignment' => true,
40 | 'general_phpdoc_annotation_remove' => ['annotations' => ['author', 'package']],
41 | 'list_syntax' => ['syntax' => 'short'],
42 | 'phpdoc_types_order' => ['null_adjustment' => 'always_last'],
43 | 'single_line_comment_style' => true,
44 | 'fully_qualified_strict_types' => true,
45 | 'phpdoc_align' => ['align' => 'left'],
46 | 'no_short_bool_cast' => false,
47 | ]
48 | )
49 | ->setFinder(
50 | PhpCsFixer\Finder::create()
51 | ->in(__DIR__.'/src')
52 | );
53 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 | sudo: false
3 |
4 | cache:
5 | directories:
6 | - $HOME/.composer/cache/files
7 | - ./bin/.phpunit
8 |
9 | env:
10 | global:
11 | - SYMFONY_PHPUNIT_DIR=./bin/.phpunit
12 |
13 | matrix:
14 | fast_finish: true
15 | include:
16 | - php: 7.2
17 |
18 | before_install:
19 | - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || phpenv config-rm xdebug.ini'
20 | - composer self-update
21 |
22 | install:
23 | - COMPOSER_MEMORY_LIMIT=-1 composer install
24 | - ./bin/phpunit install
25 |
26 | script:
27 | - ./bin/phpunit
28 | # this checks that the source code follows the Symfony Code Syntax rules
29 | - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || ./vendor/bin/php-cs-fixer fix --dry-run -v phpcs.xml.dist'
30 | - ./bin/console lint:yaml config
31 | # this checks that the application doesn't use dependencies with known security vulnerabilities
32 | - ./bin/console security:check
33 | # this checks that the composer.json and composer.lock files are valid
34 | - composer validate
35 | # this checks that Doctrine's mapping configurations are valid
36 | - ./bin/console doctrine:schema:validate --skip-sync -vvv --no-interaction
37 | - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || ./vendor/bin/phpmd src/ text phpmd.dist.xml'
38 | - '[[ "$TRAVIS_PHP_VERSION" == "7.2" ]] || ./vendor/bin/phpstan analyse src'
39 |
--------------------------------------------------------------------------------
/EXAMPLES.md:
--------------------------------------------------------------------------------
1 | # Examples of Usage
2 |
3 | **Notice:** Don't forget to add `Content-Type: application/json` to your requests.
4 |
5 |
6 | **Get JWT token:**
7 |
8 | ```
9 | {
10 | "username": "developer@symfony.local",
11 | "password": "developer"
12 | }
13 | ```
14 |
15 | **Get list of all books**
16 |
17 | ```
18 | [GET] http://[host]/books
19 | ```
20 |
21 | **Get second page the list**
22 |
23 | ```
24 | [GET] http://[host]/books?page=2
25 | ```
26 |
27 | By default if Request don't have`limit` parameter Response will return 10 results.
28 |
29 | **Get 20 results per page**
30 |
31 | ```
32 | [GET] http://[host]/books?limit=20
33 | ```
34 |
35 | You can combine freely combine all available parameters.
36 |
37 | ```
38 | [GET] http://[host]/books?limit=20&page=2
39 | ```
40 |
41 | **Get books with its reviews**
42 | You can also expand book listing of it's reviews.
43 |
44 | ```
45 | [GET] http://[host]/books?expand=reviews
46 | [GET] http://[host]/books?expand=reviews&limit=20&page=2
47 | ```
48 |
49 | *Add a new book*
50 |
51 | ```
52 | [POST] http://[host]/books
53 |
54 | { "isbn": "9799325573620",
55 | "title": "Accusamus nihil repellat vero omnis.",
56 | "description": "Amet et et suscipit qui recusandae totam. Quam ipsam voluptatem cupiditate sed natus debitis voluptas. Laudantium sit repudiandae esse perspiciatis dignissimos error et itaque. Tempora velit porro ut velit soluta explicabo eligendi.",
57 | "author": "Serena Streich",
58 | "publicationDate": "2008-05-10T01:29:03+00:00"
59 | }
60 |
61 | ```
62 |
63 | ## Filtering
64 | **Get Book of given ISBN**
65 |
66 | ```
67 | [GET] http://[host]/books?book_filter[isbn]=9799325573620
68 | ```
69 |
70 | ISBN is Book unique property - if book will not be find it return 404 error.
71 |
72 | **Get books published between certain date:**
73 |
74 | ```
75 | [GET] http://[host]/books?book_filter[publicationDate][left_datetime]=2017-06-24&[publicationDate][right_datetime]=2018-06-24
76 | ```
77 |
78 | **Get Users who watched movie of given title**
79 |
80 | ```
81 | [GET] http://[host]/users?user_filter[movies]=Et aut esse.
82 | [GET] http://[host]/users?expand=movies&user_filter[movies]=Et aut esse.
83 | ```
84 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Lukasz D. Tulikowski
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | # Symfony 4 REST API
16 |
17 | Written WITHOUT FOSUserBundle and FOSRestBundle
18 | 2000 lines under your control.
19 |
20 |
21 |
22 | Requirements: PHP min. version 7.2.0
23 |
24 |
25 | See demo: http://rest-api.tulik.io
26 |
27 |
28 | ## Quick start
29 |
30 | **Clone repository**
31 |
32 | ```
33 | git clone git@github.com:tulik/symfony-4-rest-api.git
34 | ```
35 |
36 | **Install dependencies**
37 |
38 | ```
39 | composer install
40 | ```
41 |
42 | **Start local server**
43 |
44 | ```
45 | bin/console server:start
46 | ```
47 |
48 | ## Listing with filters and pagination
49 | It is possible filtering data using **LexikFormFilterBundle** and to paginate results using **whiteoctober/Pagerfanta**
50 |
51 | ## Flexibility
52 | The whole API including contains **only ~2000 lines of code**, gives you full control possibility to adapt it to an existing project with ease.
53 |
54 | ## Extensibility
55 | Extending its functionality of additional **ElasticSearch**, **Redis** or **RabbitMQ** solution is straightforward. In case you need to change something it's always under your
56 |
57 |
60 |
61 |
62 |
63 |
64 | # Documentation
65 | 1. [Controllers](../../tree/master/src/Controller)
66 | 2. [Entities](../../tree/master/src/Entity)
67 | 3. [EventSubscriber](../../tree/master/src/EventSubscriber)
68 | 4. [Form](../../tree/master/src/Form)
69 | 5. [Resource](../../tree/master/src/Resource)
70 | 6. [Security](../../tree/master/src/Security)
71 | 7. [Service](../../tree/master/src/Service)
72 | 8. [Traits](../../tree/master/src/Traits)
73 |
--------------------------------------------------------------------------------
/bin/console:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | load(__DIR__.'/../.env');
23 | }
24 |
25 | $input = new ArgvInput();
26 | $env = $input->getParameterOption(['--env', '-e'], $_SERVER['APP_ENV'] ?? 'dev');
27 | $debug = ($_SERVER['APP_DEBUG'] ?? ('prod' !== $env)) && !$input->hasParameterOption(['--no-debug', '']);
28 |
29 | if ($debug) {
30 | umask(0000);
31 |
32 | if (class_exists(Debug::class)) {
33 | Debug::enable();
34 | }
35 | }
36 |
37 | $kernel = new Kernel($env, $debug);
38 | $application = new Application($kernel);
39 | $application->run($input);
40 |
--------------------------------------------------------------------------------
/bin/phpunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | ['all' => true],
5 | Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
6 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
7 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
8 | Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle::class => ['all' => true],
9 | Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle::class => ['all' => true],
10 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
11 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
12 | JMS\SerializerBundle\JMSSerializerBundle::class => ['all' => true],
13 | Nelmio\ApiDocBundle\NelmioApiDocBundle::class => ['all' => true],
14 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
15 | Lexik\Bundle\FormFilterBundle\LexikFormFilterBundle::class => ['all' => true],
16 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => 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 | WhiteOctober\PagerfantaBundle\WhiteOctoberPagerfantaBundle::class => ['all' => true],
21 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
22 | Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
23 | ];
24 |
--------------------------------------------------------------------------------
/config/jwt/private.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | MIIJKQIBAAKCAgEApwU9utb3UgNg3eygg8rTxNHVWJpYLjghT9saV7IjZV6Mow6p
3 | gZCvmgdC+oqEbouhDymX3g9HLyTvEFhb68bZS27jvtlTtmIHLIDkiBOTZMaNjMcu
4 | RCKD10sG/cShIc13LOdZvyyS8JvAVKmT6swP11mrPlQdwY8kaESXsj/1aMY+yj1r
5 | ENLwRnOI7pMBsWxwlGuMNxu59mFWNzoIdIRNLShu98IQ6rJTNQWhq4hLJfp0ZDhq
6 | p5X4qnie2a9TIMsABfBxQOFjTvBT5nDx+FClI8itQm9bvxYI/D8gR6zUo3DiP3GW
7 | NeSM3TEyqCugPv52p7hrVI+7Pu7QrP1HFZ9+hBgiHiMBrjMcq/UKjqb3gmGMT/I0
8 | bMX3yAk7ErRtS5SGUuOjY/2l2EZ/mKdcd3m7pwzb1sv3HDuXquXnwbq+NNCpKcLO
9 | TNJsReTWrwSzfNj3eJapxzcn1S+XuJ9aEdsBj/uHAR3li8kubv9TAj5bUDeeoZJN
10 | n1F3ajC0uimb1aIlno8h7pDBLY2cpz6YUeR9QbWpP1wrleo1PndueuVIsue+txU2
11 | Md5pKOLFMAFvy4s2yQeGmhPZ/5APbWdnhfkslnYcBi36r4ODgje6s3IzkDe4H5Td
12 | gFTmPcLqOrn297sny6NM+C3gMBiNkApd7pcctQtUMxAKMMEmJLST85IPjs0CAwEA
13 | AQKCAgBQdVrmbfYYoR4B6qLsukHH99mR5FCEe2+4u3D2PA+HHsQbLM4FZ4Dgb40Z
14 | iq7/Xe5JkpzhUXTWRjGQKzCk5Vb6WsIFx0Xnf1O7YaA12VBQ5MF9xtoH4qSmizMj
15 | /pws34EAtbZrPPrQRAekAUkLfCBJep3e2cC35NACFsEJEnyTa6UF4g54vVUYa0HU
16 | xCa1pOqa5TBXv9iW0w8obaFzF+Th0y+Z1Pg3R46D5WGbc33YHs4BFZhzgPCYlqDX
17 | dvlRu9kYA1tbiPhBJ88THOfD2n2jPmIQtfp5lBDiCzrurFiHH7MuIvbcoWwmhjPE
18 | BbgdgJICe/ngc5kdWQhXvW+IABx+mKzDCw0GQHhrdDHedIxq4sGY+UzEwxfoY2xW
19 | WNT1sro+Ksw7FZZbebepgpNqevGRuWYzu2EoyaUK8CYihCShun/2YQK71r3b5aVm
20 | U35DL5nxJIdT0ahJ0jqXXcWx6byArMY1WK9L90gDQmT74eMVk6tmNfo+TtGRsCjy
21 | mOl02x6bQksdUtTI2s3rc1WPbnNdplfrJBjwJDcUEIbu11WxoPrHw/3PeodykVsj
22 | 8Ni5ebNuX+7t9iE+QurMpugOzP2jNUDuxcYrVF9brpktOpgv8IlDNLvzsi3myuhR
23 | m9V2uvU0C+gpDQzuph3/WzVdqi+8X3sbvNmeDWjjtftb3IKm4QKCAQEA0lA/p1qn
24 | JzmmdrE3VtGu72D8Cf/cyG6jwvlwX39kM4/hZV2n7lQsxB5gnhc5YiyXws+7Dn3p
25 | iMn1tw0/o5mNwNTpiirck3/BvW6czTvzelq9/ZcxJo79zuwehjSsuMKPbLvXn7Ba
26 | rI76HggE7SWHWA6nySsGLY80F0HkpOgLkCuIOx2oW1Zb5nfFZcWeuDndUvv2Thez
27 | 2j9UPtOUhoWDaw7cY44OclI7J+s/4uMzoUZXBxRk7CNq7xpYMnLmKvwTFqf58t9P
28 | Pbupktd6yYXBqBHuzmdVl6GagLD7mR2WbYJ8AjKhuEcRKMVfkydtYd3i9c+3hLW8
29 | JTvoOirgmtSNRQKCAQEAy01seeJbQmunXuzVS3AIiEL1bu7/UvScUCSjpNzKR3YV
30 | VmP7JaWWIlbQRnetoerUIvCJpiEcnrpOlxDphdeQa9dTNkk2ztb1Rj/osV5USHEl
31 | jEgHIf1FmmrM6ZzB/b7VJk5FUqzOZEeaObdmk9C2xtKrumwZ8D9cDveLCoDu87pI
32 | 61J4OT5iwvVxt7s4Du12nb6FQxNWfGWFWhw7FGSleK2LnJHJdiMIvdiGTxUgDjn5
33 | dn1+7XugigRvpUmv3DrwefWkE7DkRRPlu122fd6kK8nvkKajJw69R/SgZBon7kef
34 | fbb9fSnnfSkA4dIBpTtDtakbnEhG6o/Su8pW3Gs/6QKCAQAeHF0wsbry078wiSja
35 | JkU8go8zQ02x6J1Lofjjw1JuS3BC2gjcB3MtVQgSOlL96lKEEse+SGqyKfAjGCN/
36 | YdG4xQL2xDI2b/kmDPsoKygt4WYIM6hW0+wkvwuTvWDpRvnP4Ij7lP02bXYD7LP/
37 | 2/qnsdl15NIKndEgb0+0CID3UDQ9+n4LLa8UrRs2+fdCew5j/i0Ce0RFwAFoyVQf
38 | emgZYNRO8JzC42ES0wyfiFXxBigZnGLiqCN8PjJYbrjjeJmnCb+wdSZcOU0K+Azd
39 | Y2gZjw+4v3Sys/Fx8WTkRCcwYJkum18qCgq74p5PbDqt413GQcoNlxNr5UrXYSIt
40 | KLddAoIBAQC3AveHsRDd9fMxLJnF0xCbOUuflV4a20BrlNALdQZS1iXXIyHOfgVs
41 | 3CGZjdqsS6yz1zzSZDRTXvuoWf0eEzNbIPczgyznffJGTvm10Wil3dUjNyPUoR6r
42 | J0FXe1nWhpdyaDtXdWBGPX7EPikFH3mp+bPFmdKvxxmkD4sG5ZI1rZg+3nqDbXmS
43 | b0jzUIHiTjndPsjP3PSZ/vnQaGF2tjOPMwre4w4sXtVbsTMWtbmplN7Qn6BHQGcA
44 | V4X5kR/SbOxVnZ8aar7SwFqqFG5XWLkJAju6R4fPfSE/SSOpeTJA+hDFJpmCttpA
45 | fUzh/B6nE3acbaMBSL8uIFJf4oHW4mUhAoIBAQCrmCt2uZKWwmVcYnfhxmE97VuO
46 | WPJVUSBkdEQvi1AraoAMTcBweagpieW77EQYjwwHbcFmgZL4lfEF6ZBh1XvxM3J+
47 | D0E3StvNtxaEHWHTZTVI6JVv6IYBKf+NFEklF51bbkpfYyaA9LHz0EiRPGIFe3FC
48 | wXPtCOpcZQLrY0MY4Z5FCWgu2zzt87xqzjZEJQnuntTvtwoZpck3J4ZfXsuPmRk4
49 | 4WwazE9J8NTyubtHzKDtxYB9z04iVT+LrUFhoekAXNc9ufqFHu56yTkBkZEfeWeh
50 | AHbGubrp9enuF+rH9E+4TqX1yO/q8h84m5KnaFMh6cyiVQM56SYps3Tc2pMV
51 | -----END RSA PRIVATE KEY-----
52 |
--------------------------------------------------------------------------------
/config/jwt/private.pem-back:
--------------------------------------------------------------------------------
1 | -----BEGIN RSA PRIVATE KEY-----
2 | Proc-Type: 4,ENCRYPTED
3 | DEK-Info: AES-256-CBC,E16106A85FA583DB7A5F16D4C1305A9C
4 |
5 | X1uf2ZDFheaMqMuwwQE80uDHVz1peLNT6xFu2W77DH5Y1kGgBFLLqaeP+4jDDWBY
6 | cuFzcAIpNZaOEuG5FrogyIZ0skCWuGyudIouHEW2/4x/MYgFvTSYtTUENPg+RUKX
7 | 6lbCNVTZ7MJWuEaBd7L+xxPEF/X90N6vy4NiZ/sWZW69N/uYxnjc8UWRpxs4plU8
8 | 4xBmDlRogdFgUbmjLG2WU9IehvJc+s9j+3P+LFM2IWPbcwaFCVh89f0enG/H7lP+
9 | 17yzGImKRrMcP4NN5CptSvkp1O/ByE98M51c64HzwHBsgjP3GMcfqZy/OjLNv0SU
10 | nrEOBFY+wPbE/tAPbtJ+q0GNzOHDp7sPad+qkqDUJD9nKJPDUmHtDEXFb/bDlNqy
11 | CAd8hmEXnLWBQDc4OTPTY7bqPhv4r3iYhBa92iP07T7YfFolf5xI2scBj/RoY6e1
12 | ZAe/4l+exezrzsiBAHxgGAdElKGD8uJFHyM9Wsg10fvTHeAMkubXCyMuCv1D4cOu
13 | Mgb3ZfungG0+BWHbX8UCOCwIX+u5WSce+iJJjkVYPimFcZaYyXdqIbATwmdoKx6+
14 | ZVu5fHpYwjMMOjeLOJ/LACPiae63uq2zIBA686AshZndZcAU+jk10XJZ+1JdP7n3
15 | HEao0em9CAtj5jhMMeMxSDIhobnr3p25WDJapuTkpvuuyXbC1geHYUXwrVNgmMNm
16 | hNHkP3+V1mADPsidyojSGQ6oWrRpUhQLdkvrsrKM6YSItGXjZEvflKwGqFxs05LI
17 | QFJClpQQUYnvrl0uBdhcCJF3TRFdBTNDX8hFeDs4jXbn+l/3Mk+dTDtwvzJzSJN1
18 | 21Y1I3c0l3ODCQfzciUdMBleNpQDuRz2X1dX9ptxA29COcohxtrnIfNy55iMrvVS
19 | Bjbr8IUgnnckT4NkB3IRAlVYLd6ZWfzbgnc+rQWllpW86DeNmG3mQCX0bU5I7beP
20 | Oj+4Tf9gT9nTHGhqR4zM8/th7FinnFo6sPw07TVs2qlVdZyKrJGZKNOFA5R+flYb
21 | dHxqJpE9ZvqHmLUEjzdTOiImlR4ARNr31P3Y+1ybABctZmZ3z3QmMhBgdu/8RmfM
22 | kAmouhy3/c/j+VduRwYtXQ71hoxeuGTagGiXiQHPM3rDx4qGOeKutWX3JIZUY4a7
23 | jlzZzetc+Ow0I1JIa9phWVYqfcYFCfSComRGG1oTifHkgXurqTW1Qfy2vdVHCGSS
24 | Xfdv1dNoSFyfNRtzUD9Ei9D5sxPaosL3BQekUgCG+TeVkcOoiTERylcFsLlOU0gZ
25 | B3Hz8f1Tb5Su3gCA/onBkitHh5r4IUvmYzIMLpc3Nm88Xkpzkhml3iNUVnodS1lX
26 | 9CiWTErtTufJwgJcZFqHXms5/fzNa4PqO3myh/1cK0ZbjR6qLt2w/ycn9fS5eQFT
27 | 0MBkFHzBHmlyB+IvYDEo6vL8CGtwBqBM3LC2+0ZSiIFBWoYCH9uVz8kv/YWKHEZg
28 | EnUjjqkp0voQYp0/5chmDxOHt0jdYem4KJC+qTDNzmhpmacrDx5e6Ch5XopVf15c
29 | vrkIoYJf9DMYsZE/3FD1Dr0ULB36ZYQliCrZXZpoN2NyvR6WIV2ZiE1HJFysYfS5
30 | 0b2U9ivrhLbwhnw6vakG49JSSNxpYzKjmIFq7gpyu2/VyyLU7CqAWE+3kwR4G/96
31 | dmTwloI+v6EVc464rYgofOuGFhNriz6Zn5J/SR4H8rlPaTmq2QdHVYWR1uJ9joD0
32 | CTgbnY8yMkm9txWnzI+4WOLsPjIj3pj0N9jf5qNGD8e95yY0eU9G71bbczXiome7
33 | qVhViFniW9NNp35e/QrXGfvsCobQYfFCnXSW7q/tfi67l8mFpSL9hnscPdH69qAs
34 | 6D86EnRNrs1LutKFfrlTFnlLUSQ1oLfmZZdg6AWosXC3YC2MiU2L0T9JK27kmf1V
35 | 2Edo/IJ3lnjEDiSUUdRXR81rPZr4QrWBvaIMB3K74lqZ5JkG8s9nm2bKbUfk1KRX
36 | 8FkmVw+pm7NYkh+/e2G65WYZHSOYjL+Q5DLacAJZrJvG4t+rMizEmLnnQNm4p/Fx
37 | 0fcmkDYddMVNU4IFsrD4gi37WpzDbMYAulc6yBbnMeXj6/e565i/puuInkFmYQiZ
38 | VrN5nWZa8vCzUKmXALb1aRBjDsPeXoLbCbcK+WgmqoFCweUoBWrvJyQpTZCg8pdL
39 | L2eG8cMoGJN0Yqpg86r15ZhcpqDmZYXNpNJK5GV8i/bAKFIVb4RkX4kEB0uJVSnm
40 | X/Odc8M/WzAZUF27XYF/rA6OIbUfQygenkCDKbtA38cletncacKu2eOBgESYDphX
41 | WhCkW5+8t19aTFV5sNwV1CZiKMImKL17liKCHjVQ22ZB2ywFf01jojHDW53v9w0Z
42 | TEpmVmYIlAcRwRRHCdIdO12DUZeFTnP6p+w8YIQ/tzIyWUKWaodTWioZ4G023YN3
43 | bLZmJP/x6pei3fhZIfY+1NyfbJYTcVWP/GIp9qMHzDTLJMAP1qd7BdmydPovIj4J
44 | WC8YbDeLZoId1vDH70NDnB2sjdTaseuXZLsPHt+xPYFBO9fUnWM+pW/WID5KeTjU
45 | W0dcwxm6A4Zn3/l9oPmyrefdD+dOlR93XwhbdXEOnxQjN4f9AbqBVqoabmi1Oxfp
46 | 6g7JUFooGo0YTswrpz9A7ldiSXG5f9kUggntbkwezuM25ddJ+FgnXTQYmWP6STBM
47 | jtLZcq6KH1t57KvC7QBJP+nAL4J41NnNnlL+GBst2quFhHhC8acTwASJ1Ck4cH0U
48 | puoFMoLcoGsuURbvqfcgP0oVDOtLDhDDZGxzJfe0RY24m5zQU1Js660SHqlyVQ26
49 | CtP9YWPMokWMqP12so2Rd/IR2phJ1v4xE7AQ3sg0VHs7FEoTNc682vO/m+HQik8d
50 | wTnOA9tc4beKzMXKMY+ztdAiK40gyb4vPPkmHKo+QqeZOQEUmTlloEGNAa629i17
51 | j5KZPoVYk3UVHEkAwGe4P6FsA3/SQaYI0CEBtd+aTmfKBJ3/Z/Y0u91su++3SWba
52 | YZJbqohHx7DTK727yq2q4PEHhfC89MB3mj5fKjKt7VFQNJEl14x1GIrbfAV29jF9
53 | g/nqWR+bB5CEOYg7KzccnvLj/hvW3UudwhX4/PoUJ2zWR4Z/R3NS38gNeFN+4S/P
54 | -----END RSA PRIVATE KEY-----
55 |
--------------------------------------------------------------------------------
/config/jwt/public.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApwU9utb3UgNg3eygg8rT
3 | xNHVWJpYLjghT9saV7IjZV6Mow6pgZCvmgdC+oqEbouhDymX3g9HLyTvEFhb68bZ
4 | S27jvtlTtmIHLIDkiBOTZMaNjMcuRCKD10sG/cShIc13LOdZvyyS8JvAVKmT6swP
5 | 11mrPlQdwY8kaESXsj/1aMY+yj1rENLwRnOI7pMBsWxwlGuMNxu59mFWNzoIdIRN
6 | LShu98IQ6rJTNQWhq4hLJfp0ZDhqp5X4qnie2a9TIMsABfBxQOFjTvBT5nDx+FCl
7 | I8itQm9bvxYI/D8gR6zUo3DiP3GWNeSM3TEyqCugPv52p7hrVI+7Pu7QrP1HFZ9+
8 | hBgiHiMBrjMcq/UKjqb3gmGMT/I0bMX3yAk7ErRtS5SGUuOjY/2l2EZ/mKdcd3m7
9 | pwzb1sv3HDuXquXnwbq+NNCpKcLOTNJsReTWrwSzfNj3eJapxzcn1S+XuJ9aEdsB
10 | j/uHAR3li8kubv9TAj5bUDeeoZJNn1F3ajC0uimb1aIlno8h7pDBLY2cpz6YUeR9
11 | QbWpP1wrleo1PndueuVIsue+txU2Md5pKOLFMAFvy4s2yQeGmhPZ/5APbWdnhfks
12 | lnYcBi36r4ODgje6s3IzkDe4H5TdgFTmPcLqOrn297sny6NM+C3gMBiNkApd7pcc
13 | tQtUMxAKMMEmJLST85IPjs0CAwEAAQ==
14 | -----END PUBLIC KEY-----
15 |
--------------------------------------------------------------------------------
/config/packages/dev/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: ~
3 | session:
4 | storage_id: session.storage.mock_file
5 |
--------------------------------------------------------------------------------
/config/packages/dev/hautelook_alice.yaml:
--------------------------------------------------------------------------------
1 | hautelook_alice:
2 | fixtures_path: fixtures
3 |
--------------------------------------------------------------------------------
/config/packages/dev/jms_serializer.yaml:
--------------------------------------------------------------------------------
1 | jms_serializer:
2 | visitors:
3 | json:
4 | options:
5 | - JSON_PRETTY_PRINT
6 | - JSON_UNESCAPED_SLASHES
7 | - JSON_PRESERVE_ZERO_FRACTION
8 |
--------------------------------------------------------------------------------
/config/packages/dev/nelmio_alice.yaml:
--------------------------------------------------------------------------------
1 | nelmio_alice:
2 | functions_blacklist:
3 | - 'current'
4 |
--------------------------------------------------------------------------------
/config/packages/dev/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: true
4 |
--------------------------------------------------------------------------------
/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 | parameters:
2 | # Adds a fallback DATABASE_URL if the env var is not set.
3 | # This allows you to run cache:warmup even if your
4 | # environment variables are not available yet.
5 | # You should not need to change this value.
6 | env(DATABASE_URL): ''
7 |
8 | doctrine:
9 | dbal:
10 | # configure these for your database server
11 | driver: 'pdo_sqlite'
12 | charset: utf8
13 | default_table_options:
14 | charset: utf8
15 | collate: utf8_bin
16 |
17 | url: '%env(resolve:DATABASE_URL)%'
18 | orm:
19 | auto_generate_proxy_classes: '%kernel.debug%'
20 | naming_strategy: doctrine.orm.naming_strategy.underscore
21 | auto_mapping: true
22 | mappings:
23 | App:
24 | is_bundle: false
25 | type: annotation
26 | dir: '%kernel.project_dir%/src/Entity'
27 | prefix: 'App\Entity'
28 | alias: App
29 |
--------------------------------------------------------------------------------
/config/packages/doctrine_migrations.yaml:
--------------------------------------------------------------------------------
1 | doctrine_migrations:
2 | dir_name: '%kernel.project_dir%/src/Migrations'
3 | # namespace is arbitrary but should be different from App\Migrations
4 | # as migrations classes should NOT be autoloaded
5 | namespace: DoctrineMigrations
6 |
--------------------------------------------------------------------------------
/config/packages/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | secret: '%env(APP_SECRET)%'
3 | default_locale: en
4 | csrf_protection: true
5 | http_method_override: true
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: ~
11 |
12 | #esi: true
13 | #fragments: true
14 | php_errors:
15 | log: true
16 |
17 | cache:
18 | # Put the unique name of your app here: the prefix seed
19 | # is used to compute stable namespaces for cache keys.
20 | #prefix_seed: your_vendor_name/app_name
21 |
22 | # The app cache caches to the filesystem by default.
23 | # Other options include:
24 |
25 | # Redis
26 | #app: cache.adapter.redis
27 | #default_redis_provider: redis://localhost
28 |
29 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
30 | #app: cache.adapter.apcu
31 |
--------------------------------------------------------------------------------
/config/packages/jms_serializer.yaml:
--------------------------------------------------------------------------------
1 | jms_serializer:
2 | property_naming:
3 | id: 'jms_serializer.identical_property_naming_strategy'
4 |
5 | # metadata:
6 | # auto_detection: false
7 | # directories:
8 | # any-name:
9 | # namespace_prefix: "My\\FooBundle"
10 | # path: "@MyFooBundle/Resources/config/serializer"
11 | # another-name:
12 | # namespace_prefix: "My\\BarBundle"
13 | # path: "@MyBarBundle/Resources/config/serializer"
14 |
--------------------------------------------------------------------------------
/config/packages/lexik_jwt_authentication.yaml:
--------------------------------------------------------------------------------
1 | lexik_jwt_authentication:
2 | secret_key: '%env(resolve:JWT_SECRET_KEY)%'
3 | public_key: '%env(resolve:JWT_PUBLIC_KEY)%'
4 | pass_phrase: '%env(JWT_PASSPHRASE)%'
5 |
--------------------------------------------------------------------------------
/config/packages/nelmio_api_doc.yaml:
--------------------------------------------------------------------------------
1 | nelmio_api_doc:
2 | documentation:
3 | models: { use_jms: false }
4 | routes:
5 | path_patterns:
6 | - ^/(?!/$)
7 | schemes: [http, https]
8 | info:
9 | title: Symfony Rest API Demo
10 | version: beta
11 | securityDefinitions:
12 | Bearer:
13 | type: apiKey
14 | description: 'Value: Bearer {jwt}'
15 | name: Authorization
16 | in: header
17 | security:
18 | - Bearer: []
19 | areas: # to filter documented areas
20 | path_patterns:
21 | - ^/(?!/$)
22 |
--------------------------------------------------------------------------------
/config/packages/prod/doctrine.yaml:
--------------------------------------------------------------------------------
1 | doctrine:
2 | dbal:
3 | url: '%env(DATABASE_URL)%'
4 | orm:
5 | auto_generate_proxy_classes: '%kernel.debug%'
6 | naming_strategy: doctrine.orm.naming_strategy.underscore
7 | auto_mapping: true
8 | mappings:
9 | App:
10 | is_bundle: false
11 | type: annotation
12 | dir: '%kernel.project_dir%/src/Entity'
13 | prefix: 'App\Entity\'
14 | alias: App
15 | metadata_cache_driver:
16 | type: service
17 | id: doctrine.system_cache_provider
18 | query_cache_driver:
19 | type: service
20 | id: doctrine.system_cache_provider
21 | result_cache_driver:
22 | type: service
23 | id: doctrine.result_cache_provider
24 |
25 | services:
26 | doctrine.result_cache_provider:
27 | class: Symfony\Component\Cache\DoctrineProvider
28 | public: false
29 | arguments:
30 | - '@doctrine.result_cache_pool'
31 | doctrine.system_cache_provider:
32 | class: Symfony\Component\Cache\DoctrineProvider
33 | public: false
34 | arguments:
35 | - '@doctrine.system_cache_pool'
36 |
37 | framework:
38 | cache:
39 | pools:
40 | doctrine.result_cache_pool:
41 | adapter: cache.app
42 | doctrine.system_cache_pool:
43 | adapter: cache.system
44 |
--------------------------------------------------------------------------------
/config/packages/prod/jms_serializer.yaml:
--------------------------------------------------------------------------------
1 | jms_serializer:
2 | visitors:
3 | json:
4 | options:
5 | - JSON_UNESCAPED_SLASHES
6 | - JSON_PRESERVE_ZERO_FRACTION
7 |
--------------------------------------------------------------------------------
/config/packages/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: ~
4 |
--------------------------------------------------------------------------------
/config/packages/security.yaml:
--------------------------------------------------------------------------------
1 | security:
2 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3 | providers:
4 | webservice:
5 | id: App\Security\UserProvider
6 |
7 | encoders:
8 | App\Entity\User:
9 | algorithm: bcrypt
10 | cost: 12
11 |
12 | firewalls:
13 | dev:
14 | pattern: ^/(_(profiler|wdt)|css|images|js)/
15 | security: false
16 |
17 | login:
18 | pattern: ^/login
19 | stateless: true
20 | anonymous: true
21 | json_login:
22 | check_path: /login_check
23 | success_handler: lexik_jwt_authentication.handler.authentication_success
24 | failure_handler: lexik_jwt_authentication.handler.authentication_failure
25 |
26 | api:
27 | pattern: ^/
28 | anonymous: true
29 | stateless: true
30 | guard:
31 | authenticators:
32 | - lexik_jwt_authentication.jwt_token_authenticator
33 |
34 | # Easy way to control access for large sections of your site
35 | # Note: Only the *first* access control that matches will be used
36 | access_control:
37 | - { path: ^/(.+), roles: IS_AUTHENTICATED_ANONYMOUSLY }
38 | - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
39 | - { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
40 |
--------------------------------------------------------------------------------
/config/packages/security_checker.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | SensioLabs\Security\SecurityChecker:
3 | public: false
4 |
5 | SensioLabs\Security\Command\SecurityCheckerCommand:
6 | arguments: ['@SensioLabs\Security\SecurityChecker']
7 | public: false
8 | tags:
9 | - { name: console.command, command: 'security:check' }
10 |
--------------------------------------------------------------------------------
/config/packages/sensio_framework_extra.yaml:
--------------------------------------------------------------------------------
1 | sensio_framework_extra:
2 | router:
3 | annotations: false
4 |
--------------------------------------------------------------------------------
/config/packages/stof_doctrine_extensions.yaml:
--------------------------------------------------------------------------------
1 | # Read the documentation: https://symfony.com/doc/current/bundles/StofDoctrineExtensionsBundle/index.html
2 | # See the official DoctrineExtensions documentation for more details: https://github.com/Atlantic18/DoctrineExtensions/tree/master/doc/
3 | stof_doctrine_extensions:
4 | default_locale: en_US
5 | orm:
6 | default:
7 | timestampable: true
8 |
--------------------------------------------------------------------------------
/config/packages/test/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: ~
3 | session:
4 | storage_id: session.storage.mock_file
5 |
--------------------------------------------------------------------------------
/config/packages/test/hautelook_alice.yaml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: ../dev/hautelook_alice.yaml }
3 |
--------------------------------------------------------------------------------
/config/packages/test/nelmio_alice.yaml:
--------------------------------------------------------------------------------
1 | imports:
2 | - { resource: ../dev/nelmio_alice.yaml }
3 |
--------------------------------------------------------------------------------
/config/packages/test/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: true
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: '%locale%'
3 | translator:
4 | default_path: '%kernel.project_dir%/translations'
5 | fallbacks:
6 | - '%locale%'
7 |
--------------------------------------------------------------------------------
/config/packages/twig.yaml:
--------------------------------------------------------------------------------
1 | twig:
2 | default_path: '%kernel.project_dir%/templates'
3 | debug: '%kernel.debug%'
4 | strict_variables: '%kernel.debug%'
5 |
--------------------------------------------------------------------------------
/config/packages/validator.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | validation:
3 | email_validation_mode: html5
4 |
--------------------------------------------------------------------------------
/config/routes.yaml:
--------------------------------------------------------------------------------
1 | app.swagger_ui:
2 | path: /
3 | methods: GET
4 | defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
5 |
6 | api.login_check:
7 | path: /login_check
8 |
9 | _errors:
10 | resource: '@TwigBundle/Resources/config/routing/errors.xml'
11 | prefix: /_error
12 |
13 |
14 |
--------------------------------------------------------------------------------
/config/routes/annotations.yaml:
--------------------------------------------------------------------------------
1 | controllers:
2 | resource: ../../src/Controller/
3 | type: annotation
4 |
--------------------------------------------------------------------------------
/config/routes/dev/twig.yaml:
--------------------------------------------------------------------------------
1 | _errors:
2 | resource: '@TwigBundle/Resources/config/routing/errors.xml'
3 | prefix: /_error
4 |
--------------------------------------------------------------------------------
/config/routes/dev/web_profiler.yaml:
--------------------------------------------------------------------------------
1 | web_profiler_wdt:
2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
3 | prefix: /_wdt
4 |
5 | web_profiler_profiler:
6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
7 | prefix: /_profiler
8 |
--------------------------------------------------------------------------------
/config/routes/nelmio_api_doc.yaml:
--------------------------------------------------------------------------------
1 | # Expose your documentation as JSON swagger compliant
2 | app.swagger:
3 | path: /api/doc.json
4 | methods: GET
5 | defaults: { _controller: nelmio_api_doc.controller.swagger }
6 |
7 | ## Requires the Asset component and the Twig bundle
8 | # $ composer require twig asset
9 | #app.swagger_ui:
10 | # path: /
11 | # methods: GET
12 | # defaults: { _controller: nelmio_api_doc.controller.swagger_ui }
13 |
--------------------------------------------------------------------------------
/config/services.yaml:
--------------------------------------------------------------------------------
1 | # This file is the entry point to configure your own services.
2 | # Files in the packages/ subdirectory configure your dependencies.
3 | framework:
4 | session:
5 | handler_id: session.handler.native_file
6 | save_path: '%kernel.project_dir%/var/sessions/%kernel.environment%'
7 |
8 | # Put parameters here that don't need to change on each machine where the app is deployed
9 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
10 | parameters:
11 | locale: 'en'
12 | symfony:
13 | container_xml_path: srcDevDebugProjectContainer.xml
14 |
15 | services:
16 | # default configuration for services in *this* file
17 | _defaults:
18 | autowire: true # Automatically injects dependencies in your services.
19 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
20 | public: false # Allows optimizing the container by removing unused services; this also means
21 | # fetching services directly from the container via $container->get() won't work.
22 | # The best practice is to be explicit about your dependencies anyway.
23 |
24 | # makes classes in src/ available to be used as services
25 | # this creates a service per class whose id is the fully-qualified class name
26 | App\:
27 | resource: '../src/*'
28 | exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
29 |
30 | # controllers are imported separately to make sure services can be injected
31 | # as action arguments even if you don't extend any base controller class
32 | App\Controller\:
33 | resource: '../src/Controller'
34 | tags: ['controller.service_arguments']
35 | calls:
36 | - [setContainer, ['@service_container']]
37 |
38 | # make doctrine Inflector available to be used as service
39 | Doctrine\Common\Inflector\Inflector:
40 |
41 | # add more service definitions when explicit configuration is needed
42 | # please note that last definitions always *replace* previous ones
43 |
44 | # App\EventListener\ExceptionListener:
45 | # tags:
46 | # - { name: kernel.event_listener, event: kernel.exception }
47 |
48 | App\EventSubscriber\UserSubscriber:
49 | tags:
50 | - { name: doctrine.event_listener, event: prePersist }
51 | - { name: doctrine.event_listener, event: postUpdate }
52 | - { name: doctrine.event_listener, event: onFlush }
53 |
--------------------------------------------------------------------------------
/css/style.css:
--------------------------------------------------------------------------------
1 | /** HEADER **/
2 |
3 | header:before {
4 | content:"";
5 | background-color: #265a8e;
6 | height:70px;
7 | width:100%;
8 | text-align:center;
9 | position:fixed;
10 | top:0;
11 | z-index:100;
12 | box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
13 | }
14 |
15 | header #logo {
16 | position:fixed;
17 | top : 35px;
18 | right:40px;
19 | z-index:102;
20 | transform:translateY(-50%);
21 | }
22 |
23 | header #logo img {
24 | height:48px;
25 | background-color: #265a8e !important;
26 | }
27 |
28 | #swagger-ui.api-platform .info .title {
29 | color:#265a8e;
30 | }
31 |
32 | #swagger-ui.api-platform .info {
33 | width: 100%;
34 | max-width: 1460px;
35 | padding: 0px 50px;
36 | margin: 0px auto;
37 | }
38 |
39 | /** METHODS BLOCS **/
40 |
41 | #swagger-ui.api-platform .opblock.opblock-get .opblock-summary-method {
42 | background-color:#265a8e;
43 | }
44 |
45 | #swagger-ui.api-platform .opblock.opblock-get .opblock-summary {
46 | border-color:#265a8e;
47 | }
48 |
49 | /** BUTTONS **/
50 |
51 | #swagger-ui.api-platform .btn.execute {
52 | background-color:#265a8e;
53 | border-color:#265a8e;
54 | animation:none;
55 | transition:all ease 0.3s;
56 | }
57 |
58 | #swagger-ui.api-platform .btn.execute:hover {
59 | background-color:#265a8e;
60 | border-color:#265a8e;
61 | }
--------------------------------------------------------------------------------
/fixtures/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/fixtures/.gitignore
--------------------------------------------------------------------------------
/fixtures/data.yml:
--------------------------------------------------------------------------------
1 | include:
2 | - data/books.yml
3 | - data/movies.yml
4 | - data/reviews.yml
5 | - data/users.yml
6 |
--------------------------------------------------------------------------------
/fixtures/data/books.yml:
--------------------------------------------------------------------------------
1 | App\Entity\Book:
2 | book_{1..100}:
3 | isbn (unique):
4 | title:
5 | description:
6 | author:
7 | publicationDate:
8 |
--------------------------------------------------------------------------------
/fixtures/data/movies.yml:
--------------------------------------------------------------------------------
1 | App\Entity\Movie:
2 | movie_{1..100}:
3 | duration:
4 | title:
5 | description:
6 | director:
7 | publicationDate:
8 |
--------------------------------------------------------------------------------
/fixtures/data/reviews.yml:
--------------------------------------------------------------------------------
1 | App\Entity\Review:
2 | review_{1..1000}:
3 | body:
4 | rating:
5 | author: '@user*'
6 | publicationDate:
7 | book: '@book*'
8 | movie: '@movie*'
9 |
--------------------------------------------------------------------------------
/fixtures/data/users.yml:
--------------------------------------------------------------------------------
1 | App\Entity\User:
2 | user_developer:
3 | fullName: 'Symfony Developer'
4 | email (unique): 'developer@symfony.local'
5 | plainPassword: 'developer'
6 | books (unique): 'x @book*'
7 | movies (unique): 'x @movie*'
8 | roles: ["ROLE_ADMIN"]
9 |
10 | user_{1..10}:
11 | fullName:
12 | email (unique):
13 | plainPassword: "changeMe"
14 | books (unique): 'x @book*'
15 | movies (unique): 'x @movie*'
16 |
--------------------------------------------------------------------------------
/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The Symfony coding standard.
5 |
6 |
7 | */Resources/*
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | 0
56 |
57 |
58 |
59 |
60 | 0
61 |
62 |
63 |
64 |
65 | 0
66 |
67 |
68 | 0
69 |
70 |
71 | 0
72 |
73 |
74 |
75 | 0
76 |
77 |
78 |
79 | error
80 |
81 |
82 |
83 | error
84 |
85 |
86 |
87 | error
88 |
89 |
90 |
91 | error
92 |
93 |
94 |
95 | 0
96 |
97 |
98 |
99 | There should always be a description, followed by a blank line, before the tags of a class comment.
100 |
101 |
102 | bin/
103 | config/
104 | public/
105 | src/
106 | tests/
107 |
108 |
109 |
--------------------------------------------------------------------------------
/phpmd.dist.xml:
--------------------------------------------------------------------------------
1 |
7 | mess detection
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | tests/
35 |
36 |
37 |
38 |
39 | ./src
40 |
41 | ./src/Kernel.php
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/public/css/roboto.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Roboto';
3 | font-style: normal;
4 | font-weight: 300;
5 | src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/roboto-light.ttf) format('truetype');
6 | }
7 | @font-face {
8 | font-family: 'Roboto';
9 | font-style: normal;
10 | font-weight: 700;
11 | src: local('Roboto Bold'), local('Roboto-Bold'), url(../fonts/roboto-bold.ttf) format('truetype');
12 | }
13 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/android-icon-144x144.png
--------------------------------------------------------------------------------
/public/favicons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/android-icon-192x192.png
--------------------------------------------------------------------------------
/public/favicons/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/android-icon-36x36.png
--------------------------------------------------------------------------------
/public/favicons/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/android-icon-48x48.png
--------------------------------------------------------------------------------
/public/favicons/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/android-icon-72x72.png
--------------------------------------------------------------------------------
/public/favicons/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/android-icon-96x96.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-114x114.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-144x144.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-57x57.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-60x60.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-72x72.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-76x76.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/public/favicons/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/apple-icon.png
--------------------------------------------------------------------------------
/public/favicons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 | #ffffff
3 |
--------------------------------------------------------------------------------
/public/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/public/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/public/favicons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/favicon-96x96.png
--------------------------------------------------------------------------------
/public/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/favicon.ico
--------------------------------------------------------------------------------
/public/favicons/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "App",
3 | "icons": [
4 | {
5 | "src": "\/android-icon-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": "0.75"
9 | },
10 | {
11 | "src": "\/android-icon-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": "1.0"
15 | },
16 | {
17 | "src": "\/android-icon-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": "1.5"
21 | },
22 | {
23 | "src": "\/android-icon-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": "2.0"
27 | },
28 | {
29 | "src": "\/android-icon-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": "3.0"
33 | },
34 | {
35 | "src": "\/android-icon-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": "4.0"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/public/favicons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/public/favicons/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/ms-icon-150x150.png
--------------------------------------------------------------------------------
/public/favicons/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/ms-icon-310x310.png
--------------------------------------------------------------------------------
/public/favicons/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/favicons/ms-icon-70x70.png
--------------------------------------------------------------------------------
/public/images/github.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/public/images/github.png
--------------------------------------------------------------------------------
/public/index.php:
--------------------------------------------------------------------------------
1 | load(__DIR__.'/../.env');
18 | }
19 |
20 | $env = $_SERVER['APP_ENV'] ?? 'dev';
21 | $debug = $_SERVER['APP_DEBUG'] ?? ('prod' !== $env);
22 |
23 | if ($debug) {
24 | umask(0000);
25 |
26 | Debug::enable();
27 | }
28 |
29 | if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? false) {
30 | Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
31 | }
32 |
33 | if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? false) {
34 | Request::setTrustedHosts(explode(',', $trustedHosts));
35 | }
36 |
37 | $kernel = new Kernel($env, $debug);
38 | $request = Request::createFromGlobals();
39 | $response = $kernel->handle($request);
40 | $response->send();
41 | $kernel->terminate($request, $response);
42 |
--------------------------------------------------------------------------------
/public/js/clusterize.min.js:
--------------------------------------------------------------------------------
1 | /*! Clusterize.js - v0.17.6 - 2017-03-05
2 | * http://NeXTs.github.com/Clusterize.js/
3 | * Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
4 |
5 | ;(function(q,n){"undefined"!=typeof module?module.exports=n():"function"==typeof define&&"object"==typeof define.amd?define(n):this[q]=n()})("Clusterize",function(){function q(b,a,c){return a.addEventListener?a.addEventListener(b,c,!1):a.attachEvent("on"+b,c)}function n(b,a,c){return a.removeEventListener?a.removeEventListener(b,c,!1):a.detachEvent("on"+b,c)}function r(b){return"[object Array]"===Object.prototype.toString.call(b)}function m(b,a){return window.getComputedStyle?window.getComputedStyle(a)[b]:
6 | a.currentStyle[b]}var l=function(){for(var b=3,a=document.createElement("b"),c=a.all||[];a.innerHTML="\x3c!--[if gt IE "+ ++b+"]>=l&&!c.tag&&(c.tag=b[0].match(/<([^>\s/]*)/)[1].toLowerCase()),1>=this.content_elem.children.length&&(a.data=this.html(b[0]+b[0]+b[0])),c.tag||(c.tag=this.content_elem.children[0].tagName.toLowerCase()),
11 | this.getRowsHeight(b))},getRowsHeight:function(b){var a=this.options,c=a.item_height;a.cluster_height=0;if(b.length){b=this.content_elem.children;var d=b[Math.floor(b.length/2)];a.item_height=d.offsetHeight;"tr"==a.tag&&"collapse"!=m("borderCollapse",this.content_elem)&&(a.item_height+=parseInt(m("borderSpacing",this.content_elem),10)||0);"tr"!=a.tag&&(b=parseInt(m("marginTop",d),10)||0,d=parseInt(m("marginBottom",d),10)||0,a.item_height+=Math.max(b,d));a.block_height=a.item_height*a.rows_in_block;
12 | a.rows_in_cluster=a.blocks_in_cluster*a.rows_in_block;a.cluster_height=a.blocks_in_cluster*a.block_height;return c!=a.item_height}},getClusterNum:function(){this.options.scroll_top=this.scroll_elem.scrollTop;return Math.floor(this.options.scroll_top/(this.options.cluster_height-this.options.block_height))||0},generateEmptyRow:function(){var b=this.options;if(!b.tag||!b.show_no_data_row)return[];var a=document.createElement(b.tag),c=document.createTextNode(b.no_data_text),d;a.className=b.no_data_class;
13 | "tr"==b.tag&&(d=document.createElement("td"),d.colSpan=100,d.appendChild(c));a.appendChild(d||c);return[a.outerHTML]},generate:function(b,a){var c=this.options,d=b.length;if(de&&g++;f=l&&"tr"==this.options.tag){var c=document.createElement("div");for(c.innerHTML="";b=a.lastChild;)a.removeChild(b);for(c=this.getChildNodes(c.firstChild.firstChild);c.length;)a.appendChild(c.shift())}else a.innerHTML=b},getChildNodes:function(b){b=b.children;for(var a=[],c=0,d=b.length;c
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Controller;
13 |
14 | use App\Entity\Book;
15 | use App\Exception\ApiException;
16 | use App\Form\BookType;
17 | use App\Form\Filter\BookFilter;
18 | use App\Interfaces\ControllerInterface;
19 | use Nelmio\ApiDocBundle\Annotation\Model;
20 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
21 | use Swagger\Annotations as SWG;
22 | use Symfony\Component\HttpFoundation\JsonResponse;
23 | use Symfony\Component\HttpFoundation\Request;
24 | use Symfony\Component\HttpFoundation\Response;
25 | use Symfony\Component\Routing\Annotation\Route;
26 |
27 | /**
28 | * @Route(path="/books")
29 | */
30 | class BookController extends AbstractController implements ControllerInterface
31 | {
32 | /**
33 | * BookController constructor.
34 | */
35 | public function __construct()
36 | {
37 | parent::__construct(Book::class);
38 | }
39 |
40 | /**
41 | * Get all Books.
42 | *
43 | * @Route(name="api_book_list", methods={Request::METHOD_GET})
44 | *
45 | * @SWG\Tag(name="Book")
46 | * @SWG\Response(
47 | * response=200,
48 | * description="Returns the list of books.yml",
49 | * @SWG\Schema(
50 | * type="array",
51 | * @SWG\Items(ref=@Model(type=Book::class))
52 | * )
53 | * )
54 | *
55 | * @param Request $request
56 | *
57 | * @return JsonResponse
58 | */
59 | public function listAction(Request $request): JsonResponse
60 | {
61 | return $this->createCollectionResponse(
62 | $this->handleFilterForm(
63 | $request,
64 | BookFilter::class
65 | )
66 | );
67 | }
68 |
69 | /**
70 | * Show single Books.
71 | *
72 | * @Route(path="/{book}", name="api_book_show", methods={Request::METHOD_GET})
73 | *
74 | * @SWG\Tag(name="Book")
75 | * @SWG\Response(
76 | * response=200,
77 | * description="Returns book of given identifier.",
78 | * @SWG\Schema(
79 | * type="object",
80 | * @SWG\Items(ref=@Model(type=Book::class))
81 | * )
82 | * )
83 | *
84 | * @param Book|null $book
85 | *
86 | * @return JsonResponse
87 | */
88 | public function showAction(Book $book = null): JsonResponse
89 | {
90 | if (false === !!$book) {
91 | return $this->createNotFoundResponse();
92 | }
93 |
94 | return $this->createResourceResponse($book);
95 | }
96 |
97 | /**
98 | * Add new Book.
99 | *
100 | * @Route(name="api_book_create", methods={Request::METHOD_POST})
101 | *
102 | * @SWG\Tag(name="Book")
103 | * @SWG\Response(
104 | * response=201,
105 | * description="Creates new Book and returns the created object.",
106 | * @SWG\Schema(
107 | * type="object",
108 | * @SWG\Items(ref=@Model(type=Book::class))
109 | * )
110 | * )
111 | *
112 | * @param Request $request
113 | * @param Book|null $book
114 | *
115 | * @return JsonResponse
116 | *
117 | * @Security("is_granted('CAN_CREATE_BOOK', book)")
118 | */
119 | public function createAction(Request $request, Book $book = null): JsonResponse
120 | {
121 | if (false === !!$book) {
122 | $book = new Book();
123 | }
124 |
125 | $form = $this->getForm(
126 | BookType::class,
127 | $book,
128 | [
129 | 'method' => $request->getMethod(),
130 | ]
131 | );
132 |
133 | try {
134 | $this->formHandler->process($request, $form);
135 | } catch (ApiException $e) {
136 | return new JsonResponse($e->getData(), Response::HTTP_BAD_REQUEST);
137 | }
138 |
139 | return $this->createResourceResponse($book, Response::HTTP_CREATED);
140 | }
141 |
142 | /**
143 | * Edit existing Book.
144 | *
145 | * @Route(path="/{book}", name="api_book_edit", methods={Request::METHOD_PATCH})
146 | *
147 | * @SWG\Tag(name="Book")
148 | * @SWG\Response(
149 | * response=200,
150 | * description="Updates Book of given identifier and returns the updated object.",
151 | * @SWG\Schema(
152 | * type="object",
153 | * @SWG\Items(ref=@Model(type=Book::class))
154 | * )
155 | * )
156 | *
157 | * @param Request $request
158 | * @param Book|null $book
159 | *
160 | * @return JsonResponse
161 | *
162 | * @Security("is_granted('CAN_UPDATE_BOOK', book)")
163 | */
164 | public function updateAction(Request $request, Book $book = null): JsonResponse
165 | {
166 | if (false === !!$book) {
167 | return $this->createNotFoundResponse();
168 | }
169 |
170 | $form = $this->getForm(
171 | BookType::class,
172 | $book,
173 | [
174 | 'method' => $request->getMethod(),
175 | ]
176 | );
177 |
178 | try {
179 | $this->formHandler->process($request, $form);
180 | } catch (ApiException $e) {
181 | return new JsonResponse($e->getMessage(), Response::HTTP_BAD_REQUEST);
182 | }
183 |
184 | return $this->createResourceResponse($book);
185 | }
186 |
187 | /**
188 | * Delete Book.
189 | *
190 | * @Route(path="/{book}", name="api_book_delete", methods={Request::METHOD_DELETE})
191 | *
192 | * @SWG\Tag(name="Book")
193 | * @SWG\Response(
194 | * response=200,
195 | * description="Delete Book of given identifier and returns the empty object.",
196 | * @SWG\Schema(
197 | * type="object",
198 | * @SWG\Items(ref=@Model(type=Book::class))
199 | * )
200 | * )
201 | *
202 | * @param Book|null $book
203 | *
204 | * @return JsonResponse
205 | *
206 | * @Security("is_granted('CAN_DELETE_BOOK', book)")
207 | */
208 | public function deleteAction(Book $book = null): JsonResponse
209 | {
210 | if (false === !!$book) {
211 | return $this->createNotFoundResponse();
212 | }
213 |
214 | try {
215 | $this->entityManager->remove($book);
216 | $this->entityManager->flush();
217 | } catch (\Exception $exception) {
218 | return $this->createGenericErrorResponse($exception);
219 | }
220 |
221 | return $this->createSuccessfulApiResponse(self::DELETED);
222 | }
223 | }
224 |
--------------------------------------------------------------------------------
/src/Controller/MovieController.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Controller;
13 |
14 | use App\Entity\Movie;
15 | use App\Exception\ApiException;
16 | use App\Form\Filter\MovieFilter;
17 | use App\Form\MovieType;
18 | use App\Interfaces\ControllerInterface;
19 | use Nelmio\ApiDocBundle\Annotation\Model;
20 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
21 | use Swagger\Annotations as SWG;
22 | use Symfony\Component\HttpFoundation\JsonResponse;
23 | use Symfony\Component\HttpFoundation\Request;
24 | use Symfony\Component\HttpFoundation\Response;
25 | use Symfony\Component\Routing\Annotation\Route;
26 |
27 | /**
28 | * @Route(path="/movies")
29 | */
30 | class MovieController extends AbstractController implements ControllerInterface
31 | {
32 | /**
33 | * MovieController constructor.
34 | */
35 | public function __construct()
36 | {
37 | parent::__construct(Movie::class);
38 | }
39 |
40 | /**
41 | * Get all Movies.
42 | *
43 | * @Route(name="api_movie_list", methods={Request::METHOD_GET})
44 | *
45 | * @SWG\Tag(name="Movie")
46 | * @SWG\Response(
47 | * response=200,
48 | * description="Returns the list of movies",
49 | * @SWG\Schema(
50 | * type="array",
51 | * @SWG\Items(ref=@Model(type=Movie::class))
52 | * )
53 | * )
54 | *
55 | * @param Request $request
56 | *
57 | * @return JsonResponse
58 | */
59 | public function listAction(Request $request): JsonResponse
60 | {
61 | return $this->createCollectionResponse(
62 | $this->handleFilterForm(
63 | $request,
64 | MovieFilter::class
65 | )
66 | );
67 | }
68 |
69 | /**
70 | * Show single Movies.
71 | *
72 | * @Route(path="/{movie}", name="api_movie_show", methods={Request::METHOD_GET})
73 | *
74 | * @SWG\Tag(name="Movie")
75 | * @SWG\Response(
76 | * response=200,
77 | * description="Returns movie of given identifier.",
78 | * @SWG\Schema(
79 | * type="object",
80 | * title="movie",
81 | * @SWG\Items(ref=@Model(type=Movie::class))
82 | * )
83 | * )
84 | *
85 | * @param Movie|null $movie
86 | *
87 | * @return JsonResponse
88 | */
89 | public function showAction(Movie $movie = null): JsonResponse
90 | {
91 | if (false === !!$movie) {
92 | return $this->createNotFoundResponse();
93 | }
94 |
95 | return $this->createResourceResponse($movie);
96 | }
97 |
98 | /**
99 | * Add new Movie.
100 | *
101 | * @Route(name="api_movie_create", methods={Request::METHOD_POST})
102 | *
103 | * @SWG\Tag(name="Movie")
104 | * @SWG\Response(
105 | * response=200,
106 | * description="Updates Movie of given identifier and returns the updated object.",
107 | * @SWG\Schema(
108 | * type="object",
109 | * @SWG\Items(ref=@Model(type=Movie::class))
110 | * )
111 | * )
112 | *
113 | * @param Request $request
114 | * @param Movie|null $movie
115 | *
116 | * @return JsonResponse
117 | *
118 | * @Security("is_granted('CAN_CREATE_MOVIE', movie)")
119 | */
120 | public function createAction(Request $request, Movie $movie = null): JsonResponse
121 | {
122 | if (false === !!$movie) {
123 | $movie = new Movie();
124 | }
125 |
126 | $form = $this->getForm(
127 | MovieType::class,
128 | $movie,
129 | [
130 | 'method' => $request->getMethod(),
131 | ]
132 | );
133 |
134 | try {
135 | $this->formHandler->process($request, $form);
136 | } catch (ApiException $e) {
137 | return new JsonResponse($e->getMessage(), Response::HTTP_BAD_REQUEST);
138 | }
139 |
140 | return $this->createResourceResponse($movie, Response::HTTP_CREATED);
141 | }
142 |
143 | /**
144 | * Edit existing Movie.
145 | *
146 | * @Route(path="/{movie}", name="api_movie_edit", methods={Request::METHOD_PATCH})
147 | *
148 | * @SWG\Tag(name="Movie")
149 | * @SWG\Response(
150 | * response=200,
151 | * description="Updates Movie of given identifier and returns the updated object.",
152 | * @SWG\Schema(
153 | * type="object",
154 | * @SWG\Items(ref=@Model(type=Movie::class))
155 | * )
156 | * )
157 | *
158 | * @param Request $request
159 | * @param Movie|null $movie
160 | *
161 | * @return JsonResponse
162 | *
163 | * @Security("is_granted('CAN_UPDATE_MOVIE', movie)")
164 | */
165 | public function updateAction(Request $request, Movie $movie = null): JsonResponse
166 | {
167 | if (false === !!$movie) {
168 | return $this->createNotFoundResponse();
169 | }
170 |
171 | $form = $this->getForm(
172 | MovieType::class,
173 | $movie,
174 | [
175 | 'method' => $request->getMethod(),
176 | ]
177 | );
178 |
179 | try {
180 | $this->formHandler->process($request, $form);
181 | } catch (ApiException $e) {
182 | return new JsonResponse($e->getMessage(), Response::HTTP_BAD_REQUEST);
183 | }
184 |
185 | return $this->createResourceResponse($movie);
186 | }
187 |
188 | /**
189 | * Delete Movie.
190 | *
191 | * @Route(path="/{movie}", name="api_movie_delete", methods={Request::METHOD_DELETE})
192 | *
193 | * @SWG\Tag(name="Movie")
194 | * @SWG\Response(
195 | * response=200,
196 | * description="Delete Movie of given identifier and returns the empty object.",
197 | * @SWG\Schema(
198 | * type="object",
199 | * @SWG\Items(ref=@Model(type=Movie::class))
200 | * )
201 | * )
202 | *
203 | * @param Movie|null $movie
204 | *
205 | * @return JsonResponse
206 | *
207 | * @Security("is_granted('CAN_DELETE_MOVIE', movie)")
208 | */
209 | public function deleteAction(Movie $movie = null): JsonResponse
210 | {
211 | if (false === !!$movie) {
212 | return $this->createNotFoundResponse();
213 | }
214 |
215 | try {
216 | $this->entityManager->remove($movie);
217 | $this->entityManager->flush();
218 | } catch (\Exception $exception) {
219 | return $this->createGenericErrorResponse($exception);
220 | }
221 |
222 | return $this->createSuccessfulApiResponse(self::DELETED);
223 | }
224 | }
225 |
--------------------------------------------------------------------------------
/src/Controller/README.md:
--------------------------------------------------------------------------------
1 | # Controllers
2 |
3 | ### Abstract controller
4 |
5 | Creating `$pagitor` with `DoctrineORMAdapter` is probably the most common use case.
6 |
7 | You can also implement `createMongoPaginagor(Request $request, Query $query)` using `DoctrineORMAdapter` in a very similar way.
8 |
9 | If you need method `elasticSearchPagination()` you can build according to [Pagerfanta Documentation]((https://github.com/whiteoctober/Pagerfanta#elasticaadapter))
10 |
11 | ```php
12 | setMaxPerPage($request->query->get('limit', 10));
23 | $paginator->setCurrentPage($request->query->get('page', 1));
24 |
25 | return $paginator;
26 | }
27 | }
28 | ```
29 |
30 | This method allows you to forget about handling `LexikFormFilter` and simplify listing your data with filters controllers.
31 |
32 | ```php
33 | getRepository();
40 | $queryBuilder = $repository->getQueryBuilder();
41 |
42 | $form = $this->getForm($filterForm);
43 |
44 | if ($request->query->has($form->getName())) {
45 | $form->submit($request->query->get($form->getName()));
46 |
47 | $queryBuilder = $this->get('lexik_form_filter.query_builder_updater')
48 | ->addFilterConditions($form, $queryBuilder);
49 | }
50 |
51 | $paginagor = $this->createPaginator($request, $queryBuilder->getQuery());
52 |
53 | return $paginagor;
54 | }
55 | }
56 | ```
57 |
58 | # Controllers
59 |
60 | Controllers follow implements `list`, `show`, `create`, `update` and `delete` actions.
61 |
62 | I implemented **GET**, **POST**, **PATCH** and **DELETE** request methods. I haven't included **PUT** in favor of **PATCH**.
63 |
64 | It's up to you what methods do you want to implement and what additional endpoint you need to create.
65 |
--------------------------------------------------------------------------------
/src/Controller/ReviewController.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Controller;
13 |
14 | use App\Entity\Review;
15 | use App\Exception\ApiException;
16 | use App\Form\Filter\ReviewFilter;
17 | use App\Form\ReviewType;
18 | use App\Interfaces\ControllerInterface;
19 | use Nelmio\ApiDocBundle\Annotation\Model;
20 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
21 | use Swagger\Annotations as SWG;
22 | use Symfony\Component\HttpFoundation\JsonResponse;
23 | use Symfony\Component\HttpFoundation\Request;
24 | use Symfony\Component\HttpFoundation\Response;
25 | use Symfony\Component\Routing\Annotation\Route;
26 |
27 | /**
28 | * @Route(path="/reviews")
29 | */
30 | class ReviewController extends AbstractController implements ControllerInterface
31 | {
32 | /**
33 | * ReviewController constructor.
34 | */
35 | public function __construct()
36 | {
37 | parent::__construct(Review::class);
38 | }
39 |
40 | /**
41 | * Get all Reviews.
42 | *
43 | * @Route(name="api_review_list", methods={Request::METHOD_GET})
44 | *
45 | * @SWG\Tag(name="Review")
46 | * @SWG\Response(
47 | * response=200,
48 | * description="Returns the list of reviews",
49 | * @SWG\Schema(
50 | * type="array",
51 | * @SWG\Items(ref=@Model(type=Review::class))
52 | * )
53 | * )
54 | *
55 | * @param Request $request
56 | *
57 | * @return JsonResponse
58 | */
59 | public function listAction(Request $request): JsonResponse
60 | {
61 | return $this->createCollectionResponse(
62 | $this->handleFilterForm(
63 | $request,
64 | ReviewFilter::class
65 | )
66 | );
67 | }
68 |
69 | /**
70 | * Show single Reviews.
71 | *
72 | * @Route(path="/{review}", name="api_review_show", methods={Request::METHOD_GET})
73 | *
74 | * @SWG\Tag(name="Review")
75 | * @SWG\Response(
76 | * response=200,
77 | * description="Returns review of given identifier.",
78 | * @SWG\Schema(
79 | * type="object",
80 | * title="review",
81 | * @SWG\Items(ref=@Model(type=Review::class))
82 | * )
83 | * )
84 | *
85 | * @param Review|null $review
86 | *
87 | * @return JsonResponse
88 | */
89 | public function showAction(Review $review = null): JsonResponse
90 | {
91 | if (false === !!$review) {
92 | return $this->createNotFoundResponse();
93 | }
94 |
95 | return $this->createResourceResponse($review);
96 | }
97 |
98 | /**
99 | * Add new Review.
100 | *
101 | * @Route(name="api_review_create", methods={Request::METHOD_POST})
102 | *
103 | * @SWG\Tag(name="Review")
104 | * @SWG\Response(
105 | * response=200,
106 | * description="Updates Review of given identifier and returns the updated object.",
107 | * @SWG\Schema(
108 | * type="object",
109 | * @SWG\Items(ref=@Model(type=Review::class))
110 | * )
111 | * )
112 | *
113 | * @param Request $request
114 | * @param Review $review
115 | *
116 | * @return JsonResponse
117 | *
118 | * @Security("is_granted('CAN_CREATE_REVIEW', review)")
119 | */
120 | public function createAction(Request $request, Review $review = null): JsonResponse
121 | {
122 | if (false === !!$review) {
123 | $review = new Review();
124 | $review->setAuthor($this->getUser());
125 | }
126 |
127 | $form = $this->getForm(
128 | ReviewType::class,
129 | $review,
130 | [
131 | 'method' => $request->getMethod(),
132 | ]
133 | );
134 |
135 | try {
136 | $this->formHandler->process($request, $form);
137 | } catch (ApiException $e) {
138 | return new JsonResponse($e->getData(), Response::HTTP_BAD_REQUEST);
139 | }
140 |
141 | return $this->createResourceResponse($review, Response::HTTP_CREATED);
142 | }
143 |
144 | /**
145 | * Edit existing Review.
146 | *
147 | * @Route(path="/{review}", name="api_review_edit", methods={Request::METHOD_PATCH})
148 | *
149 | * @SWG\Tag(name="Review")
150 | * @SWG\Response(
151 | * response=200,
152 | * description="Updates Review of given identifier and returns the updated object.",
153 | * @SWG\Schema(
154 | * type="object",
155 | * @SWG\Items(ref=@Model(type=Review::class))
156 | * )
157 | * )
158 | *
159 | * @param Request $request
160 | * @param Review|null $review
161 | *
162 | * @return JsonResponse
163 | *
164 | * @Security("is_granted('CAN_UPDATE_REVIEW', review)")
165 | */
166 | public function updateAction(Request $request, Review $review = null): JsonResponse
167 | {
168 | if (false === !!$review) {
169 | return $this->createNotFoundResponse();
170 | }
171 |
172 | $form = $this->getForm(
173 | ReviewType::class,
174 | $review,
175 | [
176 | 'method' => $request->getMethod(),
177 | ]
178 | );
179 |
180 | try {
181 | $this->formHandler->process($request, $form);
182 | } catch (ApiException $e) {
183 | return new JsonResponse($e->getMessage(), Response::HTTP_BAD_REQUEST);
184 | }
185 |
186 | return $this->createResourceResponse($review);
187 | }
188 |
189 | /**
190 | * Delete Review.
191 | *
192 | * @Route(path="/{review}", name="api_review_delete", methods={Request::METHOD_DELETE})
193 | *
194 | * @SWG\Tag(name="Review")
195 | * @SWG\Response(
196 | * response=200,
197 | * description="Delete Review of given identifier and returns the empty object.",
198 | * @SWG\Schema(
199 | * type="object",
200 | * @SWG\Items(ref=@Model(type=Review::class))
201 | * )
202 | * )
203 | *
204 | * @param Review|null $review
205 | *
206 | * @return JsonResponse
207 | *
208 | * @Security("is_granted('CAN_DELETE_REVIEW', review)")
209 | */
210 | public function deleteAction(Review $review = null): JsonResponse
211 | {
212 | if (false === !!$review) {
213 | return $this->createNotFoundResponse();
214 | }
215 |
216 | try {
217 | $this->entityManager->remove($review);
218 | $this->entityManager->flush();
219 | } catch (\Exception $exception) {
220 | return $this->createGenericErrorResponse($exception);
221 | }
222 |
223 | return $this->createSuccessfulApiResponse(self::DELETED);
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/src/Controller/UserController.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Controller;
13 |
14 | use App\Entity\User;
15 | use App\Exception\ApiException;
16 | use App\Form\Filter\UserFilter;
17 | use App\Form\UserType;
18 | use App\Interfaces\ControllerInterface;
19 | use Nelmio\ApiDocBundle\Annotation\Model;
20 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
21 | use Swagger\Annotations as SWG;
22 | use Symfony\Component\HttpFoundation\JsonResponse;
23 | use Symfony\Component\HttpFoundation\Request;
24 | use Symfony\Component\HttpFoundation\Response;
25 | use Symfony\Component\Routing\Annotation\Route;
26 |
27 | /**
28 | * @Route(path="/users")
29 | */
30 | class UserController extends AbstractController implements ControllerInterface
31 | {
32 | /**
33 | * UserController constructor.
34 | */
35 | public function __construct()
36 | {
37 | parent::__construct(User::class);
38 | }
39 |
40 | /**
41 | * Get all Users.
42 | *
43 | * @Route(name="api_user_list", methods={Request::METHOD_GET})
44 | *
45 | * @SWG\Tag(name="User")
46 | * @SWG\Response(
47 | * response=200,
48 | * description="Returns the list of users",
49 | * @SWG\Schema(
50 | * type="array",
51 | * @SWG\Items(ref=@Model(type=User::class))
52 | * )
53 | * )
54 | *
55 | * @param Request $request
56 | *
57 | * @return JsonResponse
58 | */
59 | public function listAction(Request $request): JsonResponse
60 | {
61 | return $this->createCollectionResponse(
62 | $this->handleFilterForm(
63 | $request,
64 | UserFilter::class
65 | )
66 | );
67 | }
68 |
69 | /**
70 | * Show single Users.
71 | *
72 | * @Route(path="/{user}", name="api_user_show", methods={Request::METHOD_GET})
73 | *
74 | * @SWG\Tag(name="User")
75 | * @SWG\Response(
76 | * response=200,
77 | * description="Returns user of given identifier.",
78 | * @SWG\Schema(
79 | * type="object",
80 | * title="user",
81 | * @SWG\Items(ref=@Model(type=User::class))
82 | * )
83 | * )
84 | *
85 | * @param User|null $user
86 | *
87 | * @return JsonResponse
88 | */
89 | public function showAction(User $user = null): JsonResponse
90 | {
91 | if (false === !!$user) {
92 | return $this->createNotFoundResponse();
93 | }
94 |
95 | return $this->createResourceResponse($user);
96 | }
97 |
98 | /**
99 | * Add new User.
100 | *
101 | * @Route(name="api_user_create", methods={Request::METHOD_POST})
102 | *
103 | * @SWG\Tag(name="User")
104 | * @SWG\Response(
105 | * response=200,
106 | * description="Updates User of given identifier and returns the updated object.",
107 | * @SWG\Schema(
108 | * type="object",
109 | * @SWG\Items(ref=@Model(type=User::class))
110 | * )
111 | * )
112 | *
113 | * @param Request $request
114 | * @param User|null $user
115 | *
116 | * @return JsonResponse
117 | */
118 | public function createAction(Request $request, User $user = null): JsonResponse
119 | {
120 | if (false === !!$user) {
121 | $user = new User();
122 | }
123 |
124 | $form = $this->getForm(
125 | UserType::class,
126 | $user,
127 | [
128 | 'method' => $request->getMethod(),
129 | ]
130 | );
131 |
132 | try {
133 | $this->formHandler->process($request, $form);
134 | } catch (ApiException $e) {
135 | return new JsonResponse($e->getData(), Response::HTTP_BAD_REQUEST);
136 | }
137 |
138 | return $this->createResourceResponse($user, Response::HTTP_CREATED);
139 | }
140 |
141 | /**
142 | * Edit existing User.
143 | *
144 | * @Route(path="/{user}", name="api_user_edit", methods={Request::METHOD_PATCH})
145 | *
146 | * @SWG\Tag(name="User")
147 | * @SWG\Response(
148 | * response=200,
149 | * description="Updates User of given identifier and returns the updated object.",
150 | * @SWG\Schema(
151 | * type="object",
152 | * @SWG\Items(ref=@Model(type=User::class))
153 | * )
154 | * )*
155 | *
156 | * @param Request $request
157 | * @param User|null $user
158 | *
159 | * @return JsonResponse
160 | *
161 | * @Security("is_granted('CAN_UPDATE_USER', user)")
162 | */
163 | public function updateAction(Request $request, User $user = null): JsonResponse
164 | {
165 | if (false === !!$user) {
166 | return $this->createNotFoundResponse();
167 | }
168 |
169 | $form = $this->getForm(
170 | UserType::class,
171 | $user,
172 | [
173 | 'method' => $request->getMethod(),
174 | ]
175 | );
176 |
177 | try {
178 | $this->formHandler->process($request, $form);
179 | } catch (ApiException $e) {
180 | return new JsonResponse($e->getMessage(), Response::HTTP_BAD_REQUEST);
181 | }
182 |
183 | return $this->createResourceResponse($user);
184 | }
185 |
186 | /**
187 | * Delete User.
188 | *
189 | * @Route(path="/{user}", name="api_user_delete", methods={Request::METHOD_DELETE})
190 | *
191 | * @SWG\Tag(name="User")
192 | * @SWG\Response(
193 | * response=200,
194 | * description="Delete User of given identifier and returns the empty object.",
195 | * @SWG\Schema(
196 | * type="object",
197 | * @SWG\Items(ref=@Model(type=User::class))
198 | * )
199 | * )
200 | *
201 | * @param User $user
202 | *
203 | * @return JsonResponse
204 | *
205 | * @Security("is_granted('CAN_DELETE_USER', user)")
206 | */
207 | public function deleteAction(User $user = null): JsonResponse
208 | {
209 | if (false === !!$user) {
210 | return $this->createNotFoundResponse();
211 | }
212 |
213 | try {
214 | $this->entityManager->remove($user);
215 | $this->entityManager->flush();
216 | } catch (\Exception $exception) {
217 | return $this->createGenericErrorResponse($exception);
218 | }
219 |
220 | return $this->createSuccessfulApiResponse(self::DELETED);
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/Entity/AbstractUser.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Entity;
13 |
14 | use App\EventSubscriber\UserSubscriber;
15 | use App\Traits\IdColumnTrait;
16 | use App\Traits\TimeAwareTrait;
17 | use Doctrine\ORM\Mapping as ORM;
18 | use JMS\Serializer\Annotation as JMS;
19 | use Symfony\Component\Security\Core\User\UserInterface;
20 | use Symfony\Component\Validator\Constraints as Assert;
21 |
22 | /**
23 | * @JMS\ExclusionPolicy("ALL")
24 | */
25 | abstract class AbstractUser implements UserInterface, \Serializable
26 | {
27 | use IdColumnTrait;
28 | use TimeAwareTrait;
29 |
30 | /**
31 | * @var string
32 | *
33 | * @ORM\Column(type="string")
34 | * @Assert\NotBlank
35 | *
36 | * @JMS\Expose
37 | */
38 | protected $fullName;
39 |
40 | /**
41 | * @var string
42 | *
43 | * @see UserSubscriber::onFlush
44 | *
45 | * @ORM\Column(type="string", unique=true)
46 | */
47 | protected $username;
48 |
49 | /**
50 | * @var string
51 | *
52 | * @ORM\Column(type="string", unique=true)
53 | *
54 | * @Assert\Email
55 | * @Assert\NotBlank
56 | *
57 | * @JMS\Expose
58 | */
59 | protected $email;
60 |
61 | /**
62 | * @var string
63 | */
64 | protected $plainPassword;
65 |
66 | /**
67 | * @var string
68 | *
69 | * @ORM\Column(type="string")
70 | */
71 | protected $password;
72 |
73 | /**
74 | * @var array
75 | *
76 | * @ORM\Column(type="json")
77 | */
78 | protected $roles = [];
79 |
80 | /**
81 | * @return string
82 | */
83 | public function __toString(): string
84 | {
85 | return $this->username;
86 | }
87 |
88 | /**
89 | * @return int
90 | */
91 | public function getId(): int
92 | {
93 | return $this->id;
94 | }
95 |
96 | /**
97 | * @param string $fullName
98 | */
99 | public function setFullName(string $fullName): void
100 | {
101 | $this->fullName = $fullName;
102 | }
103 |
104 | /**
105 | * @return string
106 | */
107 | public function getFullName(): ?string
108 | {
109 | return $this->fullName;
110 | }
111 |
112 | /**
113 | * @return string
114 | */
115 | public function getUsername(): ?string
116 | {
117 | return $this->email;
118 | }
119 |
120 | /**
121 | * @param string $username
122 | */
123 | public function setUsername(string $username): void
124 | {
125 | $this->username = $username;
126 | }
127 |
128 | /**
129 | * @return string
130 | */
131 | public function getEmail(): ?string
132 | {
133 | return $this->email;
134 | }
135 |
136 | /**
137 | * @param string $email
138 | */
139 | public function setEmail(string $email): void
140 | {
141 | $this->email = $email;
142 | }
143 |
144 | /**
145 | * @param string $plainPassword
146 | */
147 | public function setPlainPassword(string $plainPassword): void
148 | {
149 | $this->plainPassword = $plainPassword;
150 | }
151 |
152 | /**
153 | * @return string|null
154 | */
155 | public function getPlainPassword(): ?string
156 | {
157 | return $this->plainPassword;
158 | }
159 |
160 | /**
161 | * @return string|null
162 | */
163 | public function getPassword(): ?string
164 | {
165 | return $this->password;
166 | }
167 |
168 | public function setPassword(string $password): void
169 | {
170 | $this->password = $password;
171 | }
172 |
173 | /**
174 | * Returns the roles or permissions granted to the user for security.
175 | */
176 | public function getRoles(): array
177 | {
178 | $roles = $this->roles;
179 |
180 | // guarantees that a user always has at least one role for security
181 | if (empty($roles)) {
182 | $roles[] = 'ROLE_USER';
183 | }
184 |
185 | return array_unique($roles);
186 | }
187 |
188 | public function setRoles(array $roles): void
189 | {
190 | $this->roles = $roles;
191 | }
192 |
193 | /**
194 | * Returns the salt that was originally used to encode the password.
195 | *
196 | * {@inheritdoc}
197 | */
198 | public function getSalt(): ?string
199 | {
200 | // See "Do you need to use a Salt?" at https://symfony.com/doc/current/cookbook/security/entity_provider.html
201 | // we're using bcrypt in security.yml to encode the password, so
202 | // the salt value is built-in and you don't have to generate one
203 |
204 | return null;
205 | }
206 |
207 | /**
208 | * Removes sensitive data from the user.
209 | *
210 | * {@inheritdoc}
211 | */
212 | public function eraseCredentials(): void
213 | {
214 | $this->plainPassword = null;
215 | }
216 |
217 | /**
218 | * {@inheritdoc}
219 | */
220 | public function serialize(): ?string
221 | {
222 | return serialize([$this->id, $this->username, $this->password]);
223 | }
224 |
225 | /**
226 | * {@inheritdoc}
227 | */
228 | public function unserialize($serialized): void
229 | {
230 | [$this->id, $this->username, $this->password] = unserialize($serialized, ['allowed_classes' => false]);
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
/src/Entity/Book.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Entity;
13 |
14 | use App\Traits\IdColumnTrait;
15 | use App\Traits\TimeAwareTrait;
16 | use Doctrine\Common\Collections\ArrayCollection;
17 | use Doctrine\Common\Collections\Collection;
18 | use Doctrine\ORM\Mapping as ORM;
19 | use JMS\Serializer\Annotation as JMS;
20 | use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
21 | use Symfony\Component\Validator\Constraints as Assert;
22 |
23 | /**
24 | * @ORM\Entity(repositoryClass="App\Repository\BookRepository")
25 | *
26 | * @UniqueEntity({"isbn"}, message="Book with this ISBN already exists,")
27 | *
28 | * @JMS\ExclusionPolicy("ALL")
29 | */
30 | class Book
31 | {
32 | use IdColumnTrait;
33 | use TimeAwareTrait;
34 |
35 | /**
36 | * @var string
37 | *
38 | * @ORM\Column(type="string")
39 | *
40 | * @Assert\NotBlank
41 | *
42 | * @JMS\Expose
43 | */
44 | protected $isbn;
45 |
46 | /**
47 | * @var string
48 | *
49 | * @ORM\Column(type="string")
50 | *
51 | * @Assert\NotBlank
52 | *
53 | * @JMS\Expose
54 | */
55 | protected $title;
56 |
57 | /**
58 | * @var string
59 | *
60 | * @ORM\Column(type="text")
61 | *
62 | * @Assert\NotBlank
63 | *
64 | * @JMS\Expose
65 | */
66 | protected $description;
67 |
68 | /**
69 | * @var string
70 | *
71 | * @ORM\Column(type="text")
72 | *
73 | * @Assert\NotBlank
74 | *
75 | * @JMS\Expose
76 | */
77 | protected $author;
78 |
79 | /**
80 | * @var \DateTimeInterface
81 | *
82 | * @ORM\Column(type="datetime")
83 | *
84 | * @Assert\NotBlank
85 | *
86 | * @JMS\Expose
87 | */
88 | protected $publicationDate;
89 |
90 | /**
91 | * @var ArrayCollection|Review[]
92 | *
93 | * @ORM\OneToMany(targetEntity="App\Entity\Review", mappedBy="book", cascade={"all"})
94 | *
95 | * @JMS\Expose
96 | * @JMS\Groups("reviews")
97 | */
98 | protected $reviews;
99 |
100 | /**
101 | * @var ArrayCollection|User[]
102 | *
103 | * @ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="books", cascade={"all"})
104 | *
105 | * @JMS\Expose
106 | * @JMS\Groups("readers")
107 | */
108 | protected $readers;
109 |
110 | /**
111 | * Book constructor.
112 | */
113 | public function __construct()
114 | {
115 | $this->reviews = new ArrayCollection();
116 | $this->readers = new ArrayCollection();
117 | }
118 |
119 | /**
120 | * @return string|null
121 | */
122 | public function getIsbn(): ?string
123 | {
124 | return $this->isbn;
125 | }
126 |
127 | /**
128 | * @param string $isbn
129 | *
130 | * @return Book
131 | */
132 | public function setIsbn(string $isbn): self
133 | {
134 | $this->isbn = $isbn;
135 |
136 | return $this;
137 | }
138 |
139 | /**
140 | * @return string|null
141 | */
142 | public function getTitle(): ?string
143 | {
144 | return $this->title;
145 | }
146 |
147 | /**
148 | * @param string $title
149 | *
150 | * @return Book
151 | */
152 | public function setTitle(string $title): self
153 | {
154 | $this->title = $title;
155 |
156 | return $this;
157 | }
158 |
159 | /**
160 | * @return string|null
161 | */
162 | public function getDescription(): ?string
163 | {
164 | return $this->description;
165 | }
166 |
167 | /**
168 | * @param string $description
169 | *
170 | * @return Book
171 | */
172 | public function setDescription(string $description): self
173 | {
174 | $this->description = $description;
175 |
176 | return $this;
177 | }
178 |
179 | /**
180 | * @return string|null
181 | */
182 | public function getAuthor(): ?string
183 | {
184 | return $this->author;
185 | }
186 |
187 | /**
188 | * @param string $author
189 | *
190 | * @return Book
191 | */
192 | public function setAuthor(string $author): self
193 | {
194 | $this->author = $author;
195 |
196 | return $this;
197 | }
198 |
199 | /**
200 | * @return \DateTimeInterface|null
201 | */
202 | public function getPublicationDate(): ?\DateTimeInterface
203 | {
204 | return $this->publicationDate;
205 | }
206 |
207 | /**
208 | * @param \DateTimeInterface $publicationDate
209 | *
210 | * @return Book
211 | */
212 | public function setPublicationDate(?\DateTimeInterface $publicationDate): self
213 | {
214 | $this->publicationDate = $publicationDate;
215 |
216 | return $this;
217 | }
218 |
219 | /**
220 | * @return Collection|Review[]
221 | */
222 | public function getReviews(): Collection
223 | {
224 | return $this->reviews;
225 | }
226 |
227 | /**
228 | * @param Review $review
229 | *
230 | * @return Book
231 | */
232 | public function addReview(Review $review): self
233 | {
234 | if (!$this->reviews->contains($review)) {
235 | $this->reviews[] = $review;
236 | $review->setBook($this);
237 | }
238 |
239 | return $this;
240 | }
241 |
242 | /**
243 | * @param Review $review
244 | *
245 | * @return Book
246 | */
247 | public function removeReview(Review $review): self
248 | {
249 | if ($this->reviews->contains($review)) {
250 | $this->reviews->removeElement($review);
251 | // set the owning side to null (unless already changed)
252 | if ($review->getBook() === $this) {
253 | $review->setBook(null);
254 | }
255 | }
256 |
257 | return $this;
258 | }
259 |
260 | /**
261 | * @return Collection
262 | */
263 | public function getReaders(): Collection
264 | {
265 | return $this->readers;
266 | }
267 |
268 | /**
269 | * @param User $reader
270 | *
271 | * @return Book
272 | */
273 | public function addReader(User $reader): self
274 | {
275 | if (!$this->readers->contains($reader)) {
276 | $this->readers[] = $reader;
277 | $reader->addBook($this);
278 | }
279 |
280 | return $this;
281 | }
282 |
283 | /**
284 | * @param User $reader
285 | *
286 | * @return Book
287 | */
288 | public function removeReader(User $reader): self
289 | {
290 | if ($this->readers->contains($reader)) {
291 | $this->readers->removeElement($reader);
292 | $reader->removeBook($this);
293 | }
294 |
295 | return $this;
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/src/Entity/Movie.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Entity;
13 |
14 | use App\Traits\IdColumnTrait;
15 | use App\Traits\TimeAwareTrait;
16 | use Doctrine\Common\Collections\ArrayCollection;
17 | use Doctrine\Common\Collections\Collection;
18 | use Doctrine\ORM\Mapping as ORM;
19 | use JMS\Serializer\Annotation as JMS;
20 | use Symfony\Component\Validator\Constraints as Assert;
21 |
22 | /**
23 | * @ORM\Entity(repositoryClass="App\Repository\MovieRepository")
24 | *
25 | * @JMS\ExclusionPolicy("ALL")
26 | */
27 | class Movie
28 | {
29 | use IdColumnTrait;
30 | use TimeAwareTrait;
31 |
32 | /**
33 | * @var int
34 | *
35 | * @ORM\Column(type="integer")
36 | *
37 | * @Assert\NotBlank
38 | *
39 | * @JMS\Expose
40 | */
41 | protected $duration;
42 |
43 | /**
44 | * @var string
45 | *
46 | * @ORM\Column(type="string")
47 | *
48 | * @Assert\NotBlank
49 | *
50 | * @JMS\Expose
51 | */
52 | protected $title;
53 |
54 | /**
55 | * @var string
56 | *
57 | * @ORM\Column(type="text")
58 | *
59 | * @Assert\NotBlank
60 | */
61 | protected $description;
62 |
63 | /**
64 | * @var string
65 | *
66 | * @ORM\Column(type="text")
67 | *
68 | * @Assert\NotBlank
69 | *
70 | * @JMS\Expose
71 | */
72 | protected $director;
73 |
74 | /**
75 | * @var \DateTimeInterface
76 | *
77 | * @ORM\Column(type="datetime")
78 | *
79 | * @Assert\NotBlank
80 | *
81 | * @JMS\Expose
82 | */
83 | protected $publicationDate;
84 |
85 | /**
86 | * @var ArrayCollection|Review[]
87 | *
88 | * @ORM\OneToMany(targetEntity="App\Entity\Review", mappedBy="movie")
89 | *
90 | * @JMS\Expose
91 | * @JMS\Groups("reviews")
92 | */
93 | protected $reviews;
94 |
95 | /**
96 | * @var ArrayCollection|User[]
97 | *
98 | * @ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="movies")
99 | *
100 | * @JMS\Expose
101 | * @JMS\Groups("audience")
102 | */
103 | protected $audience;
104 |
105 | /**
106 | * Movie constructor.
107 | */
108 | public function __construct()
109 | {
110 | $this->reviews = new ArrayCollection();
111 | $this->audience = new ArrayCollection();
112 | }
113 |
114 | /**
115 | * @return int|null
116 | */
117 | public function getDuration(): ?int
118 | {
119 | return $this->duration;
120 | }
121 |
122 | /**
123 | * @param int $duration
124 | *
125 | * @return Movie
126 | */
127 | public function setDuration(int $duration): self
128 | {
129 | $this->duration = $duration;
130 |
131 | return $this;
132 | }
133 |
134 | /**
135 | * @return string|null
136 | */
137 | public function getTitle(): ?string
138 | {
139 | return $this->title;
140 | }
141 |
142 | /**
143 | * @param string|null $title
144 | *
145 | * @return Movie
146 | */
147 | public function setTitle(string $title): self
148 | {
149 | $this->title = $title;
150 |
151 | return $this;
152 | }
153 |
154 | /**
155 | * @return string|null
156 | */
157 | public function getDescription(): ?string
158 | {
159 | return $this->description;
160 | }
161 |
162 | /**
163 | * @param string $description
164 | *
165 | * @return Movie
166 | */
167 | public function setDescription(string $description): self
168 | {
169 | $this->description = $description;
170 |
171 | return $this;
172 | }
173 |
174 | /**
175 | * @return string|null
176 | */
177 | public function getDirector(): ?string
178 | {
179 | return $this->director;
180 | }
181 |
182 | /**
183 | * @param string $director
184 | *
185 | * @return Movie
186 | */
187 | public function setDirector(string $director): self
188 | {
189 | $this->director = $director;
190 |
191 | return $this;
192 | }
193 |
194 | /**
195 | * @return \DateTimeInterface|null
196 | */
197 | public function getPublicationDate(): ?\DateTimeInterface
198 | {
199 | return $this->publicationDate;
200 | }
201 |
202 | /**
203 | * @param \DateTimeInterface $publicationDate
204 | *
205 | * @return Movie
206 | */
207 | public function setPublicationDate(?\DateTimeInterface $publicationDate): self
208 | {
209 | $this->publicationDate = $publicationDate;
210 |
211 | return $this;
212 | }
213 |
214 | /**
215 | * @return Collection|Review[]
216 | */
217 | public function getReviews(): Collection
218 | {
219 | return $this->reviews;
220 | }
221 |
222 | /**
223 | * @param Review $review
224 | *
225 | * @return Movie
226 | */
227 | public function addReview(Review $review): self
228 | {
229 | if (!$this->reviews->contains($review)) {
230 | $this->reviews[] = $review;
231 | $review->setMovie($this);
232 | }
233 |
234 | return $this;
235 | }
236 |
237 | /**
238 | * @param Review $review
239 | *
240 | * @return Movie
241 | */
242 | public function removeReview(Review $review): self
243 | {
244 | if ($this->reviews->contains($review)) {
245 | $this->reviews->removeElement($review);
246 | // set the owning side to null (unless already changed)
247 | if ($review->getMovie() === $this) {
248 | $review->setMovie(null);
249 | }
250 | }
251 |
252 | return $this;
253 | }
254 |
255 | /**
256 | * @return Collection|User[]
257 | */
258 | public function getAudience(): Collection
259 | {
260 | return $this->audience;
261 | }
262 |
263 | /**
264 | * @param User $audience
265 | *
266 | * @return Movie
267 | */
268 | public function addAudience(User $audience): self
269 | {
270 | if (!$this->audience->contains($audience)) {
271 | $this->audience[] = $audience;
272 | $audience->addMovie($this);
273 | }
274 |
275 | return $this;
276 | }
277 |
278 | /**
279 | * @param User $audience
280 | *
281 | * @return Movie
282 | */
283 | public function removeAudience(User $audience): self
284 | {
285 | if ($this->audience->contains($audience)) {
286 | $this->audience->removeElement($audience);
287 | $audience->removeMovie($this);
288 | }
289 |
290 | return $this;
291 | }
292 | }
293 |
--------------------------------------------------------------------------------
/src/Entity/README.md:
--------------------------------------------------------------------------------
1 | # Entities
2 |
3 | ## Exclusion Policy
4 | By default all entities uses `ExclusionPolicy("ALL")`.
5 | The property will not appear in response until you add `@JMS\Expose` in annotations.
6 |
7 | ```php
8 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Entity;
13 |
14 | use App\Traits\IdColumnTrait;
15 | use App\Traits\TimeAwareTrait;
16 | use Doctrine\ORM\Mapping as ORM;
17 | use JMS\Serializer\Annotation as JMS;
18 | use Symfony\Component\Validator\Constraints as Assert;
19 |
20 | /**
21 | * @ORM\Entity(repositoryClass="App\Repository\ReviewRepository")
22 | *
23 | * @JMS\ExclusionPolicy("ALL")
24 | */
25 | class Review
26 | {
27 | use IdColumnTrait;
28 | use TimeAwareTrait;
29 |
30 | /**
31 | * @var string
32 | *
33 | * @ORM\Column(type="text")
34 | *
35 | * @Assert\NotBlank
36 | *
37 | * @JMS\Expose
38 | */
39 | protected $body;
40 |
41 | /**
42 | * @var int
43 | *
44 | * @ORM\Column(type="integer")
45 | *
46 | * @Assert\NotBlank
47 | *
48 | * @JMS\Expose
49 | */
50 | protected $rating;
51 |
52 | /**
53 | * @var User
54 | *
55 | * @ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="reviews", cascade={"persist", "remove"})
56 | *
57 | * @JMS\Expose
58 | * @JMS\Groups("author")
59 | */
60 | protected $author;
61 |
62 | /**
63 | * @var \DateTimeInterface
64 | *
65 | * @ORM\Column(type="date")
66 | *
67 | * @Assert\NotBlank
68 | *
69 | * @JMS\Expose
70 | */
71 | protected $publicationDate;
72 |
73 | /**
74 | * @var Book
75 | *
76 | * @ORM\ManyToOne(targetEntity="App\Entity\Book", inversedBy="reviews", cascade={"persist"})
77 | *
78 | * @JMS\Expose
79 | * @JMS\Groups("books")
80 | */
81 | protected $book;
82 |
83 | /**
84 | * @var Movie
85 | *
86 | * @ORM\ManyToOne(targetEntity="App\Entity\Movie", inversedBy="reviews", cascade={"persist"})
87 | *
88 | * @JMS\Expose
89 | * @JMS\Groups("movies")
90 | */
91 | protected $movie;
92 |
93 | /**
94 | * @return string|null
95 | */
96 | public function getBody(): ?string
97 | {
98 | return $this->body;
99 | }
100 |
101 | /**
102 | * @param string $body
103 | *
104 | * @return Review
105 | */
106 | public function setBody(string $body): self
107 | {
108 | $this->body = $body;
109 |
110 | return $this;
111 | }
112 |
113 | /**
114 | * @return int|null
115 | */
116 | public function getRating(): ?int
117 | {
118 | return $this->rating;
119 | }
120 |
121 | /**
122 | * @param int $rating
123 | *
124 | * @return Review
125 | */
126 | public function setRating(int $rating): self
127 | {
128 | $this->rating = $rating;
129 |
130 | return $this;
131 | }
132 |
133 | /**
134 | * @return \DateTimeInterface|null
135 | */
136 | public function getPublicationDate(): ?\DateTimeInterface
137 | {
138 | return $this->publicationDate;
139 | }
140 |
141 | /**
142 | * @param \DateTimeInterface $publicationDate
143 | *
144 | * @return Review
145 | */
146 | public function setPublicationDate(?\DateTimeInterface $publicationDate): self
147 | {
148 | $this->publicationDate = $publicationDate;
149 |
150 | return $this;
151 | }
152 |
153 | /**
154 | * @return User|null
155 | */
156 | public function getAuthor(): ?User
157 | {
158 | return $this->author;
159 | }
160 |
161 | /**
162 | * @param User|null $author
163 | *
164 | * @return Review
165 | */
166 | public function setAuthor(?User $author): self
167 | {
168 | $this->author = $author;
169 |
170 | return $this;
171 | }
172 |
173 | /**
174 | * @return Book|null
175 | */
176 | public function getBook(): ?Book
177 | {
178 | return $this->book;
179 | }
180 |
181 | /**
182 | * @param Book|null $book
183 | *
184 | * @return Review
185 | */
186 | public function setBook(?Book $book): self
187 | {
188 | $this->book = $book;
189 |
190 | return $this;
191 | }
192 |
193 | /**
194 | * @return Movie|null
195 | */
196 | public function getMovie(): ?Movie
197 | {
198 | return $this->movie;
199 | }
200 |
201 | /**
202 | * @param Movie|null $movie
203 | *
204 | * @return Review
205 | */
206 | public function setMovie(?Movie $movie): self
207 | {
208 | $this->movie = $movie;
209 |
210 | return $this;
211 | }
212 | }
213 |
--------------------------------------------------------------------------------
/src/Entity/User.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Entity;
13 |
14 | use Doctrine\Common\Collections\ArrayCollection;
15 | use Doctrine\Common\Collections\Collection;
16 | use Doctrine\ORM\Mapping as ORM;
17 | use JMS\Serializer\Annotation as JMS;
18 | use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
19 | use Symfony\Component\Security\Core\User\UserInterface;
20 |
21 | /**
22 | * @ORM\HasLifecycleCallbacks
23 | * @ORM\Entity(repositoryClass="App\Repository\UserRepository")
24 | * @ORM\Table(name="app_user")
25 | *
26 | * @UniqueEntity({"email"}, message="Email already exists.")
27 | *
28 | * @JMS\ExclusionPolicy("ALL")
29 | */
30 | class User extends AbstractUser implements UserInterface
31 | {
32 | /**
33 | * @var ArrayCollection|Book[]
34 | *
35 | * @ORM\ManyToMany(targetEntity="App\Entity\Book", inversedBy="readers", cascade={"persist"})
36 | *
37 | * @JMS\Expose
38 | * @JMS\Groups("books")
39 | */
40 | protected $books;
41 |
42 | /**
43 | * @var ArrayCollection|Movie[]
44 | *
45 | * @ORM\ManyToMany(targetEntity="App\Entity\Movie", inversedBy="audience", cascade={"persist"})
46 | *
47 | * @JMS\Expose
48 | * @JMS\Groups("movies")
49 | */
50 | protected $movies;
51 |
52 | /**
53 | * @var ArrayCollection|Review[]
54 | *
55 | * @ORM\OneToMany(targetEntity="App\Entity\Review", mappedBy="author", cascade={"persist"})
56 | *
57 | * @JMS\Expose
58 | * @JMS\Groups("reviews")
59 | */
60 | protected $reviews;
61 |
62 | /**
63 | * User constructor.
64 | */
65 | public function __construct()
66 | {
67 | $this->books = new ArrayCollection();
68 | $this->movies = new ArrayCollection();
69 | $this->reviews = new ArrayCollection();
70 | }
71 |
72 | /**
73 | * @return Book[]|Collection
74 | */
75 | public function getBooks(): Collection
76 | {
77 | return $this->books;
78 | }
79 |
80 | /**
81 | * @param Book $book
82 | *
83 | * @return User
84 | */
85 | public function addBook(Book $book): self
86 | {
87 | if (!$this->books->contains($book)) {
88 | $this->books[] = $book;
89 | }
90 |
91 | return $this;
92 | }
93 |
94 | /**
95 | * @param Book $book
96 | *
97 | * @return User
98 | */
99 | public function removeBook(Book $book): self
100 | {
101 | if ($this->books->contains($book)) {
102 | $this->books->removeElement($book);
103 | }
104 |
105 | return $this;
106 | }
107 |
108 | /**
109 | * @return Collection|Movie[]
110 | */
111 | public function getMovies(): Collection
112 | {
113 | return $this->movies;
114 | }
115 |
116 | /**
117 | * @param Movie $movie
118 | *
119 | * @return User
120 | */
121 | public function addMovie(Movie $movie): self
122 | {
123 | if (!$this->movies->contains($movie)) {
124 | $this->movies[] = $movie;
125 | }
126 |
127 | return $this;
128 | }
129 |
130 | /**
131 | * @param Movie $movie
132 | *
133 | * @return User
134 | */
135 | public function removeMovie(Movie $movie): self
136 | {
137 | if ($this->movies->contains($movie)) {
138 | $this->movies->removeElement($movie);
139 | }
140 |
141 | return $this;
142 | }
143 |
144 | /**
145 | * @return Collection|Review[]
146 | */
147 | public function getReviews(): Collection
148 | {
149 | return $this->reviews;
150 | }
151 |
152 | /**
153 | * @param Review $review
154 | *
155 | * @return User
156 | */
157 | public function addReview(Review $review): self
158 | {
159 | if (!$this->reviews->contains($review)) {
160 | $this->reviews[] = $review;
161 | $review->setAuthor($this);
162 | }
163 |
164 | return $this;
165 | }
166 |
167 | /**
168 | * @param Review $review
169 | *
170 | * @return User
171 | */
172 | public function removeReview(Review $review): self
173 | {
174 | if ($this->reviews->contains($review)) {
175 | $this->reviews->removeElement($review);
176 | // set the owning side to null (unless already changed)
177 | if ($review->getAuthor() === $this) {
178 | $review->setAuthor(null);
179 | }
180 | }
181 |
182 | return $this;
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/src/EventSubscriber/README.md:
--------------------------------------------------------------------------------
1 | # Event Subscriber
2 |
3 | `UserEventSubscriber` is responsible for encoding User `$plainPassword` to `$password` when user is _created_ or _updated_.
4 |
--------------------------------------------------------------------------------
/src/EventSubscriber/UserSubscriber.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\EventSubscriber;
13 |
14 | use App\Entity\User;
15 | use Doctrine\Common\EventSubscriber;
16 | use Doctrine\ORM\Event\LifecycleEventArgs;
17 | use Doctrine\ORM\Event\OnFlushEventArgs;
18 | use Doctrine\ORM\Events;
19 | use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
20 |
21 | class UserSubscriber implements EventSubscriber
22 | {
23 | /**
24 | * @var UserPasswordEncoderInterface
25 | */
26 | protected $encoder;
27 |
28 | /**
29 | * UserSubscriber constructor.
30 | *
31 | * @param UserPasswordEncoderInterface $encoder
32 | */
33 | public function __construct(UserPasswordEncoderInterface $encoder)
34 | {
35 | $this->encoder = $encoder;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function getSubscribedEvents(): array
42 | {
43 | return [
44 | Events::prePersist,
45 | Events::postUpdate,
46 | Events::onFlush,
47 | ];
48 | }
49 |
50 | /**
51 | * @param LifecycleEventArgs $args
52 | */
53 | public function prePersist(LifecycleEventArgs $args): void
54 | {
55 | $user = $args->getEntity();
56 |
57 | if ($user instanceof User) {
58 | $this->encodePassword($user);
59 | }
60 | }
61 |
62 | /**
63 | * @param LifecycleEventArgs $args
64 | */
65 | public function postUpdate(LifecycleEventArgs $args): void
66 | {
67 | $user = $args->getEntity();
68 |
69 | if ($user instanceof User) {
70 | $this->encodePassword($user);
71 | }
72 | }
73 |
74 | /**
75 | * @param OnFlushEventArgs $eventArgs
76 | */
77 | public function onFlush(OnFlushEventArgs $eventArgs)
78 | {
79 | $em = $eventArgs->getEntityManager();
80 | $uow = $em->getUnitOfWork();
81 | $classMetadata = $em->getClassMetadata(User::class);
82 |
83 | foreach ($uow->getScheduledEntityInsertions() as $entity) {
84 | if ($entity instanceof User) {
85 | $entity->setUsername($entity->getEmail());
86 | $uow->recomputeSingleEntityChangeSet($classMetadata, $entity);
87 | }
88 | }
89 | }
90 |
91 | /**
92 | * @param User $user
93 | */
94 | protected function encodePassword(User $user): void
95 | {
96 | $encoded = $this->encoder->encodePassword($user, $user->getPlainPassword());
97 |
98 | $user->setPassword($encoded);
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/src/Exception/ApiException.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Exception;
13 |
14 | use App\Exception\Enum\ApiErrorEnumType;
15 | use Exception;
16 |
17 | class ApiException extends Exception
18 | {
19 | public const DEFAULT_MESSAGE = 'General error.';
20 |
21 | /**
22 | * @var string
23 | */
24 | protected $type;
25 |
26 | /**
27 | * @var array
28 | */
29 | protected $data;
30 |
31 | /**
32 | * {@inheritdoc}
33 | *
34 | * @param string $message
35 | * @param array $data
36 | */
37 | public function __construct(string $message, int $code, array $data = [])
38 | {
39 | parent::__construct($message, $code);
40 |
41 | $this->type = ApiErrorEnumType::GENERAL_ERROR;
42 | $this->data = $data;
43 | }
44 |
45 | /**
46 | * @param array $data
47 | *
48 | * @return ApiException
49 | */
50 | public static function createWithData(array $data = []): self
51 | {
52 | return new static(static::DEFAULT_MESSAGE, 0, $data);
53 | }
54 |
55 | /**
56 | * @return array
57 | */
58 | public function getData(): array
59 | {
60 | return $this->data;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/Exception/Enum/ApiErrorEnumType.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Exception\Enum;
13 |
14 | class ApiErrorEnumType
15 | {
16 | public const FORM_INVALID = 'form-invalid';
17 | public const GENERAL_ERROR = 'general-error';
18 | }
19 |
--------------------------------------------------------------------------------
/src/Exception/FormInvalidException.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Exception;
13 |
14 | class FormInvalidException extends ApiException
15 | {
16 | public const DEFAULT_MESSAGE = 'Submitted form did not pass validation.';
17 |
18 | /**
19 | * @param string $message
20 | * @param int $code
21 | * @param array $data
22 | */
23 | public function __construct(string $message, int $code, array $data = [])
24 | {
25 | parent::__construct($message, $code, $data);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Form/BookType.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form;
13 |
14 | use App\Entity\Book;
15 | use Symfony\Component\Form\AbstractType;
16 | use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
17 | use Symfony\Component\Form\FormBuilderInterface;
18 | use Symfony\Component\OptionsResolver\OptionsResolver;
19 |
20 | class BookType extends AbstractType
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function buildForm(FormBuilderInterface $builder, array $options)
26 | {
27 | $builder
28 | ->add('isbn')
29 | ->add('title')
30 | ->add('description')
31 | ->add('author')
32 | ->add('publicationDate', DateTimeType::class, [
33 | 'widget' => 'single_text',
34 | 'input' => 'datetime',
35 | ]);
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function configureOptions(OptionsResolver $resolver)
42 | {
43 | $resolver->setDefaults([
44 | 'data_class' => Book::class,
45 | ]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Form/Filter/BookFilter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form\Filter;
13 |
14 | use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
15 | use Symfony\Component\Form\AbstractType;
16 | use Symfony\Component\Form\FormBuilderInterface;
17 | use Symfony\Component\OptionsResolver\OptionsResolver;
18 |
19 | class BookFilter extends AbstractType
20 | {
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function buildForm(FormBuilderInterface $builder, array $options)
25 | {
26 | $dateTimeFieldOptions = [
27 | 'widget' => 'single_text',
28 | 'format' => 'yyyy-MM-dd',
29 | ];
30 |
31 | $builder->add('isbn', Filters\TextFilterType::class)
32 | ->add('title', Filters\TextFilterType::class)
33 | ->add('description', Filters\TextFilterType::class)
34 | ->add('author', Filters\TextFilterType::class)
35 | ->add(
36 | 'publicationDate',
37 | Filters\DateTimeRangeFilterType::class,
38 | [
39 | 'left_datetime_options' => $dateTimeFieldOptions,
40 | 'right_datetime_options' => $dateTimeFieldOptions,
41 | ]
42 | );
43 | }
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function configureOptions(OptionsResolver $resolver)
49 | {
50 | $resolver->setDefaults([
51 | 'validation_groups' => ['filtering'],
52 | ]);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Form/Filter/MovieFilter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form\Filter;
13 |
14 | use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
15 | use Symfony\Component\Form\AbstractType;
16 | use Symfony\Component\Form\FormBuilderInterface;
17 | use Symfony\Component\OptionsResolver\OptionsResolver;
18 |
19 | class MovieFilter extends AbstractType
20 | {
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function buildForm(FormBuilderInterface $builder, array $options)
25 | {
26 | $dateTimeFieldOptions = [
27 | 'widget' => 'single_text',
28 | 'format' => 'yyyy-MM-dd',
29 | ];
30 |
31 | $builder
32 | ->add('duration', Filters\TextFilterType::class)
33 | ->add('title', Filters\TextFilterType::class)
34 | ->add('description', Filters\TextFilterType::class)
35 | ->add('author', Filters\TextFilterType::class)
36 | ->add(
37 | 'publicationDate',
38 | Filters\DateTimeRangeFilterType::class,
39 | [
40 | 'left_datetime_options' => $dateTimeFieldOptions,
41 | 'right_datetime_options' => $dateTimeFieldOptions,
42 | ]
43 | );
44 | }
45 |
46 | /**
47 | * {@inheritdoc}
48 | */
49 | public function configureOptions(OptionsResolver $resolver)
50 | {
51 | $resolver->setDefaults([
52 | 'validation_groups' => ['filtering'],
53 | ]);
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Form/Filter/ReviewFilter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form\Filter;
13 |
14 | use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
15 | use Symfony\Component\Form\AbstractType;
16 | use Symfony\Component\Form\FormBuilderInterface;
17 | use Symfony\Component\OptionsResolver\OptionsResolver;
18 |
19 | class ReviewFilter extends AbstractType
20 | {
21 | /**
22 | * {@inheritdoc}
23 | */
24 | public function buildForm(FormBuilderInterface $builder, array $options)
25 | {
26 | $dateTimeFieldOptions = [
27 | 'widget' => 'single_text',
28 | 'format' => 'yyyy-MM-dd',
29 | ];
30 |
31 | $builder
32 | ->add('body', Filters\TextFilterType::class)
33 | ->add('rating', Filters\TextFilterType::class)
34 | ->add(
35 | 'publicationDate',
36 | Filters\DateTimeRangeFilterType::class,
37 | [
38 | 'left_datetime_options' => $dateTimeFieldOptions,
39 | 'right_datetime_options' => $dateTimeFieldOptions,
40 | ]
41 | );
42 | }
43 |
44 | /**
45 | * {@inheritdoc}
46 | */
47 | public function configureOptions(OptionsResolver $resolver)
48 | {
49 | $resolver->setDefaults([
50 | 'validation_groups' => ['filtering'],
51 | ]);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/Form/Filter/UserFilter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form\Filter;
13 |
14 | use Lexik\Bundle\FormFilterBundle\Filter\Form\Type as Filters;
15 | use Lexik\Bundle\FormFilterBundle\Filter\Query\QueryInterface;
16 | use Symfony\Component\Form\AbstractType;
17 | use Symfony\Component\Form\FormBuilderInterface;
18 | use Symfony\Component\OptionsResolver\OptionsResolver;
19 |
20 | class UserFilter extends AbstractType
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function buildForm(FormBuilderInterface $builder, array $options)
26 | {
27 | // Allows filter Users movie title they watched.
28 | $builder
29 | ->add('email', Filters\TextFilterType::class)
30 | ->add('movies', Filters\TextFilterType::class, [
31 | 'apply_filter' => function (QueryInterface $filterQuery, $field, $values) {
32 | if (empty($values['value'])) {
33 | return null;
34 | }
35 |
36 | $query = $filterQuery->getQueryBuilder();
37 | $query->innerJoin($field, 't');
38 |
39 | $paramName = sprintf('p_%s', str_replace('.', '_', $field));
40 | $expression = $filterQuery->getExpr()->eq('t.title', ':'.$paramName);
41 | $parameters = [$paramName => $values['value']];
42 |
43 | return $filterQuery->createCondition($expression, $parameters);
44 | },
45 | ]);
46 | }
47 |
48 | /**
49 | * {@inheritdoc}
50 | */
51 | public function configureOptions(OptionsResolver $resolver)
52 | {
53 | $resolver->setDefaults([
54 | 'validation_groups' => ['filtering'],
55 | ]);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Form/Handler/AbstractFormHandler.php:
--------------------------------------------------------------------------------
1 | responseCreator = $responseCreator;
45 | $this->formErrorsSerializer = $formErrorsSerializer;
46 | $this->entityManager = $entityManager;
47 | }
48 |
49 | /**
50 | * @param Request $request
51 | * @param FormInterface $form
52 | *
53 | * @throws FormInvalidException
54 | *
55 | * @return mixed
56 | */
57 | public function process(Request $request, FormInterface $form)
58 | {
59 | $data = json_decode($request->getContent(), true);
60 |
61 | $clearMissing = Request::METHOD_PATCH !== $request->getMethod();
62 |
63 | $form->submit($data, $clearMissing);
64 |
65 | if ($form->isValid()) {
66 | return $this->onSuccess($form->getData());
67 | }
68 |
69 | throw new FormInvalidException(
70 | ApiErrorEnumType::FORM_INVALID,
71 | 0,
72 | $this->formErrorsSerializer->serialize($form)
73 | );
74 | }
75 |
76 | /**
77 | * @param mixed $object
78 | *
79 | * @return mixed
80 | */
81 | abstract protected function onSuccess($object);
82 | }
83 |
--------------------------------------------------------------------------------
/src/Form/Handler/DefaultFormHandler.php:
--------------------------------------------------------------------------------
1 | entityManager->persist($object);
17 | $this->entityManager->flush();
18 |
19 | return $object;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Form/MovieType.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form;
13 |
14 | use App\Entity\Movie;
15 | use Symfony\Component\Form\AbstractType;
16 | use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
17 | use Symfony\Component\Form\FormBuilderInterface;
18 | use Symfony\Component\OptionsResolver\OptionsResolver;
19 |
20 | class MovieType extends AbstractType
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function buildForm(FormBuilderInterface $builder, array $options)
26 | {
27 | $builder
28 | ->add('duration')
29 | ->add('title')
30 | ->add('description')
31 | ->add('director')
32 | ->add('publicationDate', DateTimeType::class, [
33 | 'widget' => 'single_text',
34 | 'input' => 'datetime',
35 | ]);
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function configureOptions(OptionsResolver $resolver)
42 | {
43 | $resolver->setDefaults([
44 | 'data_class' => Movie::class,
45 | ]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Form/README.md:
--------------------------------------------------------------------------------
1 | # Event Forms and Filters
2 |
3 | `AbsctractFormHandler` is responsible for process your JSON payload, validation and returning possible errors.
4 |
5 |
6 | ## Forms
7 |
8 | Changes on Entity properties are possible because of Symfony Forms.
9 |
10 | You need to include all **required properties** otherwise **POST** will not pass validation.
11 |
12 | There is no need to include in **PATCH** payload all properties if you don't want to change them.
13 |
14 |
15 | ```php
16 | add('isbn')
21 | ->add('title')
22 | ->add('description');
23 | ```
24 |
25 | ## Filter
26 |
27 | Filter forms allow filtering results. It's a great tool - to get familiar with `FilterForms` check out [LexikFormFilterBundle Documentation](https://github.com/lexik/LexikFormFilterBundle/blob/master/Resources/doc/index.md)
28 |
--------------------------------------------------------------------------------
/src/Form/ReviewType.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form;
13 |
14 | use App\Entity\Review;
15 | use Symfony\Component\Form\AbstractType;
16 | use Symfony\Component\Form\Extension\Core\Type\DateTimeType;
17 | use Symfony\Component\Form\FormBuilderInterface;
18 | use Symfony\Component\OptionsResolver\OptionsResolver;
19 |
20 | class ReviewType extends AbstractType
21 | {
22 | /**
23 | * {@inheritdoc}
24 | */
25 | public function buildForm(FormBuilderInterface $builder, array $options)
26 | {
27 | $builder
28 | ->add('body')
29 | ->add('rating')
30 | ->add('author')
31 | ->add('publicationDate', DateTimeType::class, [
32 | 'widget' => 'single_text',
33 | 'input' => 'datetime',
34 | ]);
35 | }
36 |
37 | /**
38 | * {@inheritdoc}
39 | */
40 | public function configureOptions(OptionsResolver $resolver)
41 | {
42 | $resolver->setDefaults([
43 | 'data_class' => Review::class,
44 | ]);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Form/UserType.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Form;
13 |
14 | use App\Entity\Book;
15 | use App\Entity\Movie;
16 | use App\Entity\Review;
17 | use App\Entity\User;
18 | use Doctrine\ORM\EntityRepository;
19 | use Symfony\Bridge\Doctrine\Form\Type\EntityType;
20 | use Symfony\Component\Form\AbstractType;
21 | use Symfony\Component\Form\FormBuilderInterface;
22 | use Symfony\Component\OptionsResolver\OptionsResolver;
23 |
24 | class UserType extends AbstractType
25 | {
26 | /**
27 | * {@inheritdoc}
28 | */
29 | public function buildForm(FormBuilderInterface $builder, array $options)
30 | {
31 | $builder
32 | ->add('fullName')
33 | ->add('email')
34 | ->add('plainPassword')
35 | ->add('books', EntityType::class, [
36 | 'class' => Book::class,
37 | 'multiple' => 'true',
38 | 'query_builder' => function (EntityRepository $entityRepository) {
39 | return $entityRepository->createQueryBuilder('this');
40 | },
41 | ])
42 | ->add('movies', EntityType::class, [
43 | 'class' => Movie::class,
44 | 'multiple' => 'true',
45 | 'query_builder' => function (EntityRepository $entityRepository) {
46 | return $entityRepository->createQueryBuilder('this');
47 | },
48 | ])
49 | ->add('reviews', EntityType::class, [
50 | 'class' => Review::class,
51 | 'multiple' => 'true',
52 | 'query_builder' => function (EntityRepository $entityRepository) {
53 | return $entityRepository->createQueryBuilder('this');
54 | },
55 | ]);
56 | }
57 |
58 | /**
59 | * {@inheritdoc}
60 | */
61 | public function configureOptions(OptionsResolver $resolver)
62 | {
63 | $resolver->setDefaults([
64 | 'data_class' => User::class,
65 | ]);
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Interfaces/ControllerInterface.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Interfaces;
13 |
14 | use JMS\Serializer\SerializationContext;
15 | use Pagerfanta\Pagerfanta;
16 | use Symfony\Component\Form\FormInterface;
17 | use Symfony\Component\HttpFoundation\JsonResponse;
18 | use Symfony\Component\HttpFoundation\Request;
19 | use Symfony\Component\HttpFoundation\Response;
20 |
21 | interface ControllerInterface
22 | {
23 | /**
24 | * @param Pagerfanta $paginator
25 | *
26 | * @return JsonResponse
27 | */
28 | public function createCollectionResponse(PagerFanta $paginator): JsonResponse;
29 |
30 | /**
31 | * @param $resource
32 | * @param int $status
33 | * @param SerializationContext|null $context
34 | *
35 | * @return JsonResponse
36 | */
37 | public function createResourceResponse($resource, $status = Response::HTTP_OK, SerializationContext $context = null): JsonResponse;
38 |
39 | /**
40 | * @param array $data
41 | * @param int $status
42 | *
43 | * @return mixed
44 | */
45 | public function createSuccessfulApiResponse(array $data, $status = Response::HTTP_OK);
46 |
47 | /**
48 | * @return JsonResponse
49 | */
50 | public function createNotFoundResponse(): JsonResponse;
51 |
52 | /**
53 | * @param \Exception $exception
54 | *
55 | * @return JsonResponse
56 | */
57 | public function createGenericErrorResponse(\Exception $exception): JsonResponse;
58 |
59 | /**
60 | * @param Request $request
61 | * @param string $filterForm
62 | *
63 | * @return Pagerfanta
64 | */
65 | public function handleFilterForm(Request $request, string $filterForm): Pagerfanta;
66 |
67 | /**
68 | * @param string $type
69 | * @param array|null $data
70 | * @param array $options
71 | *
72 | * @return FormInterface
73 | */
74 | public function getForm(string $type, $data = null, array $options = []): FormInterface;
75 | }
76 |
--------------------------------------------------------------------------------
/src/Interfaces/RepositoryInterface.php:
--------------------------------------------------------------------------------
1 | getEnvironment();
22 | }
23 |
24 | public function getLogDir()
25 | {
26 | return $this->getProjectDir().'/var/log';
27 | }
28 |
29 | public function registerBundles()
30 | {
31 | $contents = include $this->getProjectDir().'/config/bundles.php';
32 | foreach ($contents as $class => $envs) {
33 | if (isset($envs['all']) || isset($envs[$this->environment])) {
34 | yield new $class();
35 | }
36 | }
37 | }
38 |
39 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader)
40 | {
41 | $container->setParameter('container.autowiring.strict_mode', true);
42 | $container->setParameter('container.dumper.inline_class_loader', true);
43 | $confDir = $this->getProjectDir().'/config';
44 | $loader->load($confDir.'/packages/*'.self::CONFIG_EXTS, 'glob');
45 | if (is_dir($confDir.'/packages/'.$this->environment)) {
46 | $loader->load($confDir.'/packages/'.$this->environment.'/**/*'.self::CONFIG_EXTS, 'glob');
47 | }
48 | $loader->load($confDir.'/services'.self::CONFIG_EXTS, 'glob');
49 | $loader->load($confDir.'/services_'.$this->environment.self::CONFIG_EXTS, 'glob');
50 | }
51 |
52 | protected function configureRoutes(RouteCollectionBuilder $routes)
53 | {
54 | $confDir = $this->getProjectDir().'/config';
55 | if (is_dir($confDir.'/routes/')) {
56 | $routes->import($confDir.'/routes/*'.self::CONFIG_EXTS, '/', 'glob');
57 | }
58 | if (is_dir($confDir.'/routes/'.$this->environment)) {
59 | $routes->import($confDir.'/routes/'.$this->environment.'/**/*'.self::CONFIG_EXTS, '/', 'glob');
60 | }
61 | $routes->import($confDir.'/routes'.self::CONFIG_EXTS, '/', 'glob');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Migrations/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/src/Migrations/.gitignore
--------------------------------------------------------------------------------
/src/Repository/AbstractRepository.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Repository;
13 |
14 | use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
15 | use Doctrine\ORM\QueryBuilder;
16 |
17 | abstract class AbstractRepository extends ServiceEntityRepository
18 | {
19 | /**
20 | * @return QueryBuilder
21 | */
22 | public function getQueryBuilder(): QueryBuilder
23 | {
24 | $qb = $this->createQueryBuilder('this')->select('this');
25 |
26 | return $qb;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Repository/BookRepository.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Repository;
13 |
14 | use App\Entity\Book;
15 | use App\Interfaces\RepositoryInterface;
16 | use Symfony\Bridge\Doctrine\RegistryInterface;
17 |
18 | /**
19 | * {@inheritdoc}
20 | *
21 | * @method Book find($id, $lockMode = null, $lockVersion = null)
22 | * @method Book findOneBy(array $criteria, array $orderBy = null)
23 | * @method Book[] findAll()
24 | */
25 | class BookRepository extends AbstractRepository implements RepositoryInterface
26 | {
27 | /**
28 | * BookRepository constructor.
29 | *
30 | * @param RegistryInterface $registry
31 | */
32 | public function __construct(RegistryInterface $registry)
33 | {
34 | parent::__construct($registry, Book::class);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Repository/MovieRepository.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Repository;
13 |
14 | use App\Entity\Movie;
15 | use App\Interfaces\RepositoryInterface;
16 | use Symfony\Bridge\Doctrine\RegistryInterface;
17 |
18 | /**
19 | * {@inheritdoc}
20 | *
21 | * @method Movie find($id, $lockMode = null, $lockVersion = null)
22 | * @method Movie findOneBy(array $criteria, array $orderBy = null)
23 | * @method Movie[] findAll()
24 | */
25 | class MovieRepository extends AbstractRepository implements RepositoryInterface
26 | {
27 | /**
28 | * BookRepository constructor.
29 | *
30 | * @param RegistryInterface $registry
31 | */
32 | public function __construct(RegistryInterface $registry)
33 | {
34 | parent::__construct($registry, Movie::class);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Repository/README.md:
--------------------------------------------------------------------------------
1 | # Repositories
2 |
--------------------------------------------------------------------------------
/src/Repository/ReviewRepository.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Repository;
13 |
14 | use App\Entity\Review;
15 | use App\Interfaces\RepositoryInterface;
16 | use Symfony\Bridge\Doctrine\RegistryInterface;
17 |
18 | /**
19 | * {@inheritdoc}
20 | *
21 | * @method Review find($id, $lockMode = null, $lockVersion = null)
22 | * @method Review findOneBy(array $criteria, array $orderBy = null)
23 | * @method Review[] findAll()
24 | */
25 | class ReviewRepository extends AbstractRepository implements RepositoryInterface
26 | {
27 | /**
28 | * ReviewRepository constructor.
29 | *
30 | * @param RegistryInterface $registry
31 | */
32 | public function __construct(RegistryInterface $registry)
33 | {
34 | parent::__construct($registry, Review::class);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Repository/UserRepository.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Repository;
13 |
14 | use App\Entity\User;
15 | use App\Interfaces\RepositoryInterface;
16 | use Symfony\Bridge\Doctrine\RegistryInterface;
17 |
18 | /**
19 | * {@inheritdoc}
20 | *
21 | * @method User find($id, $lockMode = null, $lockVersion = null)
22 | * @method User findOneBy(array $criteria, array $orderBy = null)
23 | * @method User[] findAll()
24 | */
25 | class UserRepository extends AbstractRepository implements RepositoryInterface
26 | {
27 | /**
28 | * UserRepository constructor.
29 | *
30 | * @param RegistryInterface $registry
31 | */
32 | public function __construct(RegistryInterface $registry)
33 | {
34 | parent::__construct($registry, User::class);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Resource/PaginationResource.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Resource;
13 |
14 | use Pagerfanta\Pagerfanta;
15 |
16 | final class PaginationResource
17 | {
18 | /**
19 | * @var int
20 | */
21 | private $totalNumberOfResults;
22 |
23 | /**
24 | * @var int
25 | */
26 | private $resultsPerPageCount;
27 |
28 | /**
29 | * @var int
30 | */
31 | private $currentPageNumber;
32 |
33 | /**
34 | * PaginationResource constructor.
35 | *
36 | * @param int $totalNumberOfResults
37 | * @param int $resultsPerPageCount
38 | * @param int $currentPageNumber
39 | */
40 | public function __construct(int $totalNumberOfResults = 0, int $resultsPerPageCount = 0, int $currentPageNumber = 0)
41 | {
42 | $this->totalNumberOfResults = $totalNumberOfResults;
43 | $this->resultsPerPageCount = $resultsPerPageCount;
44 | $this->currentPageNumber = $currentPageNumber;
45 | }
46 |
47 | /**
48 | * @param Pagerfanta $paginator
49 | *
50 | * @return self
51 | */
52 | public static function createFromPagerfanta(Pagerfanta $paginator): self
53 | {
54 | return new self(
55 | $paginator->getNbResults(),
56 | $paginator->getMaxPerPage(),
57 | $paginator->getCurrentPage()
58 | );
59 | }
60 |
61 | /**
62 | * @return array
63 | */
64 | public function toJsArray(): array
65 | {
66 | return [
67 | 'total' => $this->totalNumberOfResults,
68 | 'limit' => $this->resultsPerPageCount,
69 | 'page' => $this->currentPageNumber,
70 | ];
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Resource/README.md:
--------------------------------------------------------------------------------
1 | # Resource
2 |
3 | `PaginationResource` creates a pagination resource allowing to create Response.
4 |
5 | Check out [Pagerfanta documentation](https://github.com/whiteoctober/Pagerfanta#usage) to get more familiar with the solution.
6 |
--------------------------------------------------------------------------------
/src/Security/README.md:
--------------------------------------------------------------------------------
1 | # Security and Voters
2 |
3 | ## UserProvider
4 | `UserProvider` is an implementation of `UserProviderInterface` build based on Symfony [All about User Providers](https://symfony.com/doc/current/security/user_provider.html) documentation.
5 |
6 | ## Voters
7 | Symfony voters are the most granular and flexible way of checking permissions to perform an action by User.
8 |
9 | Check out []How to Use Voters to [Check User Permissions](https://symfony.com/doc/current/security/voters.html) to get more familiar with Voters.
10 |
11 | In Controllers, they check for permissions to perform a specific action.
12 |
13 | Take a look on `@Security("is_granted('CAN_CREATE_BOOK', book)")` annotation.
14 |
15 | ```php
16 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security;
13 |
14 | use App\Entity\User;
15 | use App\Service\Manager\UserManager;
16 | use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
17 | use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
18 | use Symfony\Component\Security\Core\User\UserInterface;
19 | use Symfony\Component\Security\Core\User\UserProviderInterface;
20 |
21 | class UserProvider implements UserProviderInterface
22 | {
23 | /**
24 | * @var UserManager
25 | */
26 | protected $userManager;
27 |
28 | /**
29 | * UserProvider constructor.
30 | *
31 | * @param UserManager $userManager
32 | */
33 | public function __construct(UserManager $userManager)
34 | {
35 | $this->userManager = $userManager;
36 | }
37 |
38 | /**
39 | * {@inheritdoc}
40 | */
41 | public function loadUserByUsername($username)
42 | {
43 | $user = $this->findUser($username);
44 |
45 | if (false === !!$user) {
46 | throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
47 | }
48 |
49 | return $user;
50 | }
51 |
52 | /**
53 | * {@inheritdoc}
54 | */
55 | public function refreshUser(UserInterface $user): ?UserInterface
56 | {
57 | /** @var User $user */
58 | if (!$this->supportsClass(\get_class($user))) {
59 | throw new UnsupportedUserException(sprintf('Expected an instance of %s, but got "%s".', $this->userManager->getClass(), \get_class($user)));
60 | }
61 |
62 | if (null === $reloadedUser = $this->userManager->findUserBy(['id' => $user->getId()])) {
63 | throw new UsernameNotFoundException(sprintf('User with ID "%s" could not be reloaded.', $user->getId()));
64 | }
65 |
66 | if ($reloadedUser instanceof UserInterface) {
67 | return $reloadedUser;
68 | }
69 |
70 | return null;
71 | }
72 |
73 | /**
74 | * {@inheritdoc}
75 | */
76 | public function supportsClass($class)
77 | {
78 | $userClass = $this->userManager->getClass();
79 |
80 | return $userClass === $class || is_subclass_of($class, $userClass);
81 | }
82 |
83 | /**
84 | * Finds a user by username.
85 | *
86 | * This method is meant to be an extension point for child classes.
87 | *
88 | * @param string $email
89 | *
90 | * @return UserInterface|null
91 | */
92 | protected function findUser($email): ?UserInterface
93 | {
94 | return $this->userManager->findUserByEmail($email);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/src/Security/Voter/Book/CreateBookVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Book;
13 |
14 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
16 |
17 | class CreateBookVoter extends Voter
18 | {
19 | public const CAN_CREATE_BOOK = 'CAN_CREATE_BOOK';
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | protected function supports($attribute, $subject)
25 | {
26 | // you only want to vote if the attribute and subject are what you expect
27 | return self::CAN_CREATE_BOOK === $attribute && null === $subject;
28 | }
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
34 | {
35 | // our previous business logic indicates that mods and admins can do it regardless
36 | foreach ($token->getRoles() as $role) {
37 | if (\in_array($role->getRole(), ['ROLE_MODERATOR', 'ROLE_ADMIN'])) {
38 | return true;
39 | }
40 | }
41 |
42 | return false;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Security/Voter/Book/DeleteBookVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Book;
13 |
14 | use App\Entity\Book;
15 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17 |
18 | class DeleteBookVoter extends Voter
19 | {
20 | public const CAN_DELETE_BOOK = 'CAN_DELETE_BOOK';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function supports($attribute, $subject)
26 | {
27 | // you only want to vote if the attribute and subject are what you expect
28 | return self::CAN_DELETE_BOOK === $attribute && ($subject instanceof Book || null === $subject);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
35 | {
36 | // our previous business logic indicates that admins can do it regardless
37 | foreach ($token->getRoles() as $role) {
38 | if (\in_array($role->getRole(), ['ROLE_ADMIN'])) {
39 | return true;
40 | }
41 | }
42 |
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Security/Voter/Book/UpdateBookVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Book;
13 |
14 | use App\Entity\Book;
15 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17 |
18 | class UpdateBookVoter extends Voter
19 | {
20 | public const CAN_UPDATE_BOOK = 'CAN_UPDATE_BOOK';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function supports($attribute, $subject)
26 | {
27 | // you only want to vote if the attribute and subject are what you expect
28 | return self::CAN_UPDATE_BOOK === $attribute && ($subject instanceof Book || null === $subject);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
35 | {
36 | // our previous business logic indicates that mods and admins can do it regardless
37 | foreach ($token->getRoles() as $role) {
38 | if (\in_array($role->getRole(), ['ROLE_MODERATOR', 'ROLE_ADMIN'])) {
39 | return true;
40 | }
41 | }
42 |
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Security/Voter/Movie/CreateMovieVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Movie;
13 |
14 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
15 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
16 |
17 | class CreateMovieVoter extends Voter
18 | {
19 | public const CAN_CREATE_MOVIE = 'CAN_CREATE_MOVIE';
20 |
21 | /**
22 | * {@inheritdoc}
23 | */
24 | protected function supports($attribute, $subject)
25 | {
26 | // you only want to vote if the attribute and subject are what you expect
27 | return self::CAN_CREATE_MOVIE === $attribute && null === $subject;
28 | }
29 |
30 | /**
31 | * {@inheritdoc}
32 | */
33 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
34 | {
35 | // our previous business logic indicates that mods and admins can do it regardless
36 | foreach ($token->getRoles() as $role) {
37 | if (\in_array($role->getRole(), ['ROLE_MODERATOR', 'ROLE_ADMIN'])) {
38 | return true;
39 | }
40 | }
41 |
42 | return false;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Security/Voter/Movie/DeleteMovieVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Movie;
13 |
14 | use App\Entity\Movie;
15 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17 |
18 | class DeleteMovieVoter extends Voter
19 | {
20 | public const CAN_DELETE_MOVIE = 'CAN_DELETE_MOVIE';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function supports($attribute, $subject)
26 | {
27 | // you only want to vote if the attribute and subject are what you expect
28 | return self::CAN_DELETE_MOVIE === $attribute && ($subject instanceof Movie || null === $subject);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
35 | {
36 | // our previous business logic indicates that admins can do it regardless
37 | foreach ($token->getRoles() as $role) {
38 | if (\in_array($role->getRole(), ['ROLE_ADMIN'])) {
39 | return true;
40 | }
41 | }
42 |
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Security/Voter/Movie/UpdateMovieVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Movie;
13 |
14 | use App\Entity\Movie;
15 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17 |
18 | class UpdateMovieVoter extends Voter
19 | {
20 | public const CAN_UPDATE_MOVIE = 'CAN_UPDATE_MOVIE';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function supports($attribute, $subject)
26 | {
27 | // you only want to vote if the attribute and subject are what you expect
28 | return self::CAN_UPDATE_MOVIE === $attribute && ($subject instanceof Movie || null === $subject);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
35 | {
36 | // our previous business logic indicates that mods and admins can do it regardless
37 | foreach ($token->getRoles() as $role) {
38 | if (\in_array($role->getRole(), ['ROLE_MODERATOR', 'ROLE_ADMIN'])) {
39 | return true;
40 | }
41 | }
42 |
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Security/Voter/Review/CreateReviewVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Review;
13 |
14 | use App\Entity\User;
15 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17 |
18 | class CreateReviewVoter extends Voter
19 | {
20 | public const CAN_CREATE_REVIEW = 'CAN_CREATE_REVIEW';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function supports($attribute, $subject)
26 | {
27 | // you only want to vote if the attribute and subject are what you expect
28 | return self::CAN_CREATE_REVIEW === $attribute && null === $subject;
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
35 | {
36 | $user = $token->getUser();
37 |
38 | // allow user to delete account
39 | if ($user instanceof User) {
40 | return true;
41 | }
42 |
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Security/Voter/Review/DeleteReviewVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Review;
13 |
14 | use App\Entity\Review;
15 | use App\Entity\User;
16 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
17 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
18 |
19 | class DeleteReviewVoter extends Voter
20 | {
21 | public const CAN_DELETE_REVIEW = 'CAN_DELETE_REVIEW';
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | protected function supports($attribute, $subject)
27 | {
28 | // you only want to vote if the attribute and subject are what you expect
29 | return self::CAN_DELETE_REVIEW === $attribute && ($subject instanceof Review || null === $subject);
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
36 | {
37 | // our previous business logic indicates that admins can do it regardless
38 | foreach ($token->getRoles() as $role) {
39 | if (\in_array($role->getRole(), ['ROLE_ADMIN'])) {
40 | return true;
41 | }
42 | }
43 |
44 | // allow controller handle not found subject
45 | if (null === $subject) {
46 | return true;
47 | }
48 |
49 | $user = $token->getUser();
50 |
51 | // allow user to delete account
52 | if ($user instanceof User) {
53 | return $subject->getAuthor()->getId() === $user->getId();
54 | }
55 |
56 | return false;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Security/Voter/Review/UpdateReviewVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\Review;
13 |
14 | use App\Entity\Review;
15 | use App\Entity\User;
16 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
17 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
18 |
19 | class UpdateReviewVoter extends Voter
20 | {
21 | public const CAN_UPDATE_REVIEW = 'CAN_UPDATE_REVIEW';
22 |
23 | /**
24 | * {@inheritdoc}
25 | */
26 | protected function supports($attribute, $subject)
27 | {
28 | // you only want to vote if the attribute and subject are what you expect
29 | return self::CAN_UPDATE_REVIEW === $attribute && ($subject instanceof Review || null === $subject);
30 | }
31 |
32 | /**
33 | * {@inheritdoc}
34 | */
35 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
36 | {
37 | // our previous business logic indicates that mods and admins can do it regardless
38 | foreach ($token->getRoles() as $role) {
39 | if (\in_array($role->getRole(), ['ROLE_MODERATOR', 'ROLE_ADMIN'])) {
40 | return true;
41 | }
42 | }
43 |
44 | // allow controller handle not found subject
45 | if (null === $subject) {
46 | return true;
47 | }
48 |
49 | $user = $token->getUser();
50 |
51 | // allow user to update account
52 | if ($user instanceof User) {
53 | return $subject->getAuthor()->getId() === $user->getId();
54 | }
55 |
56 | return false;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Security/Voter/User/DeleteUserVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\User;
13 |
14 | use App\Entity\User;
15 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17 |
18 | class DeleteUserVoter extends Voter
19 | {
20 | public const CAN_DELETE_USER = 'CAN_DELETE_USER';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function supports($attribute, $subject)
26 | {
27 | // you only want to vote if the attribute and subject are what you expect
28 | return self::CAN_DELETE_USER === $attribute && ($subject instanceof User || null === $subject);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
35 | {
36 | // our previous business logic indicates that admins can do it regardless
37 | foreach ($token->getRoles() as $role) {
38 | if (\in_array($role->getRole(), ['ROLE_ADMIN'])) {
39 | return true;
40 | }
41 | }
42 |
43 | // allow controller handle not found subject
44 | if (null === $subject) {
45 | return true;
46 | }
47 |
48 | $user = $token->getUser();
49 |
50 | // allow user to delete account
51 | if ($user instanceof User) {
52 | return $subject->getId() === $user->getId();
53 | }
54 |
55 | return false;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Security/Voter/User/UpdateUserVoter.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Security\Voter\User;
13 |
14 | use App\Entity\User;
15 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
16 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
17 |
18 | class UpdateUserVoter extends Voter
19 | {
20 | public const CAN_UPDATE_USER = 'CAN_UPDATE_USER';
21 |
22 | /**
23 | * {@inheritdoc}
24 | */
25 | protected function supports($attribute, $subject)
26 | {
27 | // you only want to vote if the attribute and subject are what you expect
28 | return self::CAN_UPDATE_USER === $attribute && ($subject instanceof User || null === $subject);
29 | }
30 |
31 | /**
32 | * {@inheritdoc}
33 | */
34 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token)
35 | {
36 | // our previous business logic indicates that mods and admins can do it regardless
37 | foreach ($token->getRoles() as $role) {
38 | if (\in_array($role->getRole(), ['ROLE_MODERATOR', 'ROLE_ADMIN'])) {
39 | return true;
40 | }
41 | }
42 |
43 | // allow controller handle not found subject
44 | if (null === $subject) {
45 | return true;
46 | }
47 |
48 | $user = $token->getUser();
49 |
50 | // allow user to update account
51 | if ($user instanceof User) {
52 | return $subject->getId() === $user->getId();
53 | }
54 |
55 | return false;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Service/Form/FormErrorsSerializer.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Service\Form;
13 |
14 | use Symfony\Component\Form\FormInterface;
15 |
16 | class FormErrorsSerializer
17 | {
18 | /**
19 | * @var array
20 | */
21 | protected $errors;
22 |
23 | /**
24 | * @param FormInterface $form
25 | *
26 | * @return array
27 | */
28 | public function serialize(FormInterface $form): array
29 | {
30 | $this->errors = $this->serializeErrors($form);
31 |
32 | return $this->errors;
33 | }
34 |
35 | /**
36 | * @param FormInterface $form
37 | *
38 | * @return array
39 | */
40 | protected function serializeErrors(FormInterface $form): array
41 | {
42 | $errors = [];
43 | foreach ($form->getErrors() as $error) {
44 | $errors[] = $error->getMessage();
45 | }
46 |
47 | foreach ($form->all() as $childForm) {
48 | if ($childForm instanceof FormInterface) {
49 | if ($childErrors = $this->serializeErrors($childForm)) {
50 | $errors[$childForm->getName()] = $childErrors;
51 | }
52 | }
53 | }
54 |
55 | return $errors;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Service/Generic/ResponseCreator.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Service\Generic;
13 |
14 | use App\Resource\PaginationResource;
15 | use Doctrine\Common\Persistence\ObjectManager;
16 | use JMS\Serializer\SerializerInterface;
17 | use Symfony\Component\HttpFoundation\JsonResponse;
18 |
19 | class ResponseCreator
20 | {
21 | /**
22 | * @var ObjectManager
23 | */
24 | protected $objectManager;
25 |
26 | /**
27 | * @var SerializerInterface
28 | */
29 | protected $serializer;
30 |
31 | /**
32 | * @var SerializationService
33 | */
34 | protected $serializationService;
35 |
36 | /**
37 | * @var array
38 | */
39 | protected $data;
40 |
41 | /**
42 | * @var PaginationResource
43 | */
44 | protected $pagination;
45 |
46 | /**
47 | * ResponseCreator constructor.
48 | *
49 | * @param ObjectManager $objectManager
50 | * @param SerializationService $serializationService
51 | * @param SerializerInterface $serializer
52 | */
53 | public function __construct(
54 | ObjectManager $objectManager,
55 | SerializationService $serializationService,
56 | SerializerInterface $serializer
57 | ) {
58 | $this->objectManager = $objectManager;
59 | $this->serializationService = $serializationService;
60 | $this->serializer = $serializer;
61 | $this->data = [];
62 | $this->pagination = null;
63 | }
64 |
65 | /**
66 | * @param array $data
67 | */
68 | public function setData(array $data): void
69 | {
70 | $this->data = $data;
71 | }
72 |
73 | /**
74 | * @param array $data
75 | * @param paginationResource $pagination
76 | */
77 | public function setCollectionData(array $data, PaginationResource $pagination): void
78 | {
79 | $this->data = $data;
80 | $this->pagination = $pagination;
81 | }
82 |
83 | /**
84 | * @param int $code
85 | * @param string $className
86 | *
87 | * @return JsonResponse
88 | */
89 | public function getResponse(int $code, string $className = null): JsonResponse
90 | {
91 | $context = $this->serializationService->createBaseOnRequest();
92 |
93 | $response = new JsonResponse(null, $code);
94 | $response->setContent($this->serializer->serialize($this->buildResponse($className), 'json', $context));
95 |
96 | return $response;
97 | }
98 |
99 | /**
100 | * @param string $className
101 | *
102 | * @return array
103 | */
104 | protected function buildResponse(string $className = null): ?array
105 | {
106 | if (null === $className) {
107 | return $this->data;
108 | }
109 |
110 | $responseArray = [];
111 | $responseArray[$className] = $this->data ?: new \stdClass();
112 |
113 | if (null !== $this->pagination) {
114 | $responseArray['pagination'] = $this->pagination->toJsArray();
115 | }
116 |
117 | return $responseArray;
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/src/Service/Generic/SerializationService.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Service\Generic;
13 |
14 | use JMS\Serializer\SerializationContext;
15 | use Symfony\Component\HttpFoundation\RequestStack;
16 |
17 | class SerializationService
18 | {
19 | /**
20 | * @var RequestStack
21 | */
22 | private $requestStack;
23 |
24 | /**
25 | * SerializationService constructor.
26 | *
27 | * @param RequestStack $requestStack
28 | */
29 | public function __construct(RequestStack $requestStack)
30 | {
31 | $this->requestStack = $requestStack;
32 | }
33 |
34 | /**
35 | * @return SerializationContext|null
36 | */
37 | public function createBaseOnRequest(): SerializationContext
38 | {
39 | $currentRequest = $this->requestStack->getCurrentRequest();
40 |
41 | if (false === !!$currentRequest) {
42 | throw new \RuntimeException(sprintf('Current request is is required!'));
43 | }
44 |
45 | $expand = $currentRequest
46 | ->query
47 | ->get('expand', []);
48 |
49 | if (true === \is_string($expand)) {
50 | $expand = explode(',', $expand);
51 | }
52 |
53 | return $this->createWithGroups($expand);
54 | }
55 |
56 | /**
57 | * @param array $groups
58 | *
59 | * @return SerializationContext
60 | */
61 | public function createWithGroups(array $groups): SerializationContext
62 | {
63 | $serializationContext = (new SerializationContext())->create();
64 | $serializationContext->setGroups(array_merge(['Default'], $groups));
65 |
66 | return $serializationContext;
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Service/Manager/UserManager.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Service\Manager;
13 |
14 | use App\Entity\User;
15 | use App\Repository\UserRepository;
16 | use Doctrine\ORM\EntityManagerInterface;
17 | use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
18 | use Symfony\Component\Security\Core\User\UserInterface;
19 |
20 | class UserManager
21 | {
22 | /**
23 | * @var EncoderFactoryInterface
24 | */
25 | protected $encoderFactory;
26 | /**
27 | * @var EntityManagerInterface
28 | */
29 | protected $entityManager;
30 |
31 | /**
32 | * @var UserRepository
33 | */
34 | protected $repository;
35 |
36 | /**
37 | * UserManager constructor.
38 | *
39 | * @param EncoderFactoryInterface $encoderFactory
40 | * @param EntityManagerInterface $entityManager
41 | * @param UserRepository $userRepository
42 | */
43 | public function __construct(
44 | EncoderFactoryInterface $encoderFactory,
45 | EntityManagerInterface $entityManager,
46 | UserRepository $userRepository
47 | ) {
48 | $this->encoderFactory = $encoderFactory;
49 | $this->entityManager = $entityManager;
50 | $this->repository = $userRepository;
51 | }
52 |
53 | /**
54 | * @param UserInterface $user
55 | */
56 | public function deleteUser(UserInterface $user)
57 | {
58 | $this->entityManager->remove($user);
59 | $this->entityManager->flush();
60 | }
61 |
62 | /**
63 | * @return string
64 | */
65 | public function getClass()
66 | {
67 | return User::class;
68 | }
69 |
70 | /**
71 | * @param array $criteria
72 | *
73 | * @return object
74 | */
75 | public function findUserBy(array $criteria)
76 | {
77 | return $this->repository->findOneBy($criteria);
78 | }
79 |
80 | /**
81 | * @return array
82 | */
83 | public function findUsers()
84 | {
85 | return $this->repository->findAll();
86 | }
87 |
88 | /**
89 | * @param UserInterface $user
90 | */
91 | public function reloadUser(UserInterface $user)
92 | {
93 | $this->entityManager->refresh($user);
94 | }
95 |
96 | /**
97 | * Finds a user by email.
98 | *
99 | * @param string $email
100 | *
101 | * @return object|User|UserInterface
102 | */
103 | public function findUserByEmail($email)
104 | {
105 | return $this->findUserBy(['email' => strtolower($email)]);
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Service/README.md:
--------------------------------------------------------------------------------
1 | # Services
2 |
3 | ## Form
4 |
5 | `FormErrorSerializer` is responsible for serialization error form submitted form.
6 |
7 | ## Generic
8 |
9 | `SerializationService` provides serialization context.
10 |
11 | ## Manager
12 |
13 | `UserManager` is a simple service allowing to manage users. It also provide `UserProvider` with necessary methods.
14 |
--------------------------------------------------------------------------------
/src/Traits/ControllerTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Traits;
13 |
14 | use App\Form\Handler\DefaultFormHandler;
15 | use App\Service\Generic\ResponseCreator;
16 | use App\Service\Generic\SerializationService;
17 | use Doctrine\Common\Inflector\Inflector;
18 | use JMS\Serializer\Serializer;
19 | use JMS\Serializer\SerializerInterface;
20 | use Symfony\Component\Form\FormFactoryInterface;
21 |
22 | trait ControllerTrait
23 | {
24 | /**
25 | * @var Inflector
26 | */
27 | protected $inflector;
28 |
29 | /**
30 | * @var FormFactoryInterface
31 | */
32 | protected $formFactory;
33 |
34 | /**
35 | * @var DefaultFormHandler
36 | */
37 | protected $formHandler;
38 |
39 | /**
40 | * @var ResponseCreator
41 | */
42 | protected $responseCreator;
43 |
44 | /**
45 | * @var Serializer
46 | */
47 | protected $serializer;
48 |
49 | /**
50 | * @var SerializationService
51 | */
52 | protected $serializationService;
53 |
54 | /**
55 | * @required
56 | *
57 | * @param Inflector $inflector
58 | */
59 | public function setInflector(Inflector $inflector): void
60 | {
61 | $this->inflector = $inflector;
62 | }
63 |
64 | /**
65 | * @required
66 | *
67 | * @param FormFactoryInterface $formFactory
68 | */
69 | public function setFormFactory(FormFactoryInterface $formFactory): void
70 | {
71 | $this->formFactory = $formFactory;
72 | }
73 |
74 | /**
75 | * @required
76 | *
77 | * @param DefaultFormHandler $formHandler
78 | */
79 | public function setFormHandler(DefaultFormHandler $formHandler): void
80 | {
81 | $this->formHandler = $formHandler;
82 | }
83 |
84 | /**
85 | * @required
86 | *
87 | * @param ResponseCreator $responseCreator
88 | */
89 | public function setResponseCreator(ResponseCreator $responseCreator): void
90 | {
91 | $this->responseCreator = $responseCreator;
92 | }
93 |
94 | /**
95 | * @required
96 | *
97 | * @param SerializerInterface $serializer
98 | */
99 | public function setSerializer(SerializerInterface $serializer): void
100 | {
101 | if (!$serializer instanceof Serializer) {
102 | throw new \InvalidArgumentException(
103 | sprintf(
104 | 'Serializer must be instance of %s but %s given',
105 | Serializer::class,
106 | \get_class($this->serializer)
107 | )
108 | );
109 | }
110 |
111 | $this->serializer = $serializer;
112 | }
113 |
114 | /**
115 | * @required
116 | *
117 | * @param SerializationService $serializationService
118 | */
119 | public function setSerializationService(SerializationService $serializationService): void
120 | {
121 | $this->serializationService = $serializationService;
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Traits/IdColumnTrait.php:
--------------------------------------------------------------------------------
1 | id;
29 | }
30 |
31 | /**
32 | * @return int
33 | */
34 | public function getIdOrThrow(): int
35 | {
36 | if (!$this->id) {
37 | throw new \RuntimeException(sprintf(
38 | 'Entity `%s` is not flushed, therefore its id is not known yet',
39 | __CLASS__
40 | ));
41 | }
42 |
43 | return $this->id;
44 | }
45 |
46 | /**
47 | * @param int $id
48 | */
49 | public function setId(int $id)
50 | {
51 | $this->id = $id;
52 | }
53 |
54 | public function resetId()
55 | {
56 | $this->id = null;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/Traits/README.md:
--------------------------------------------------------------------------------
1 | # Traits
2 |
3 | `ControllerTrait` is the list of properties and setters.
4 |
5 | ```php
6 | inflector = $inflector;
25 | }
26 | ```
27 | Setters have `@required` parameter when you use this trait all setters are executed and set traits properties.
28 |
29 | This trait is used to avoid passing a lot of parameters to `AbstractController.php`
30 |
31 | `IdCollumnTrait` and `TimeAwareTrait` allow to reduce repetitive code.
32 |
--------------------------------------------------------------------------------
/src/Traits/TimeAwareTrait.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Traits;
13 |
14 | use DateTimeInterface;
15 | use Doctrine\ORM\Mapping as ORM;
16 | use Gedmo\Mapping\Annotation as Gedmo;
17 | use JMS\Serializer\Annotation as JMS;
18 |
19 | trait TimeAwareTrait
20 | {
21 | /**
22 | * @var DateTimeInterface
23 | *
24 | * @JMS\Expose
25 | *
26 | * @Gedmo\Timestampable(on="create")
27 | * @ORM\Column(type="datetime")
28 | */
29 | protected $created;
30 |
31 | /**
32 | * @var DateTimeInterface
33 | *
34 | * @JMS\Expose
35 | *
36 | * @Gedmo\Timestampable(on="update")
37 | * @ORM\Column(type="datetime")
38 | */
39 | protected $updated;
40 |
41 | /**
42 | * Set created.
43 | *
44 | * @param DateTimeInterface|null $created
45 | *
46 | * @return mixed
47 | */
48 | public function setCreated(DateTimeInterface $created = null)
49 | {
50 | $this->created = $created;
51 |
52 | return $this;
53 | }
54 |
55 | /**
56 | * Get created.
57 | *
58 | * @return DateTimeInterface
59 | */
60 | public function getCreated(): DateTimeInterface
61 | {
62 | return $this->created;
63 | }
64 |
65 | /**
66 | * Set updated.
67 | *
68 | * @param DateTimeInterface|null $updated
69 | *
70 | * @return mixed
71 | */
72 | public function setUpdated(DateTimeInterface $updated = null)
73 | {
74 | $this->updated = $updated;
75 |
76 | return $this;
77 | }
78 |
79 | /**
80 | * Get updated.
81 | *
82 | * @return DateTimeInterface $updated
83 | */
84 | public function getUpdated(): DateTimeInterface
85 | {
86 | return $this->updated;
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/templates/base.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {% block title %}Welcome!{% endblock %}
6 | {% block stylesheets %}{% endblock %}
7 |
8 |
9 | {% block body %}{% endblock %}
10 | {% block javascripts %}{% endblock %}
11 |
12 |
13 |
--------------------------------------------------------------------------------
/templates/bundles/NelmioApiDocBundle/SwaggerUi/index.html.twig:
--------------------------------------------------------------------------------
1 | {# templates/bundles/NelmioApiDocBundle/SwaggerUi/index.html.twig #}
2 |
3 | {#
4 | To avoid a "reached nested level" error an exclamation mark `!` has to be added
5 | See https://symfony.com/blog/new-in-symfony-3-4-improved-the-overriding-of-templates
6 | #}
7 | {% extends '@!NelmioApiDoc/SwaggerUi/index.html.twig' %}
8 |
9 | {% block stylesheets %}
10 | {{ parent() }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | {% endblock stylesheets %}
31 |
32 | {% block javascripts %}
33 | {{ parent() }}
34 |
35 | {% endblock javascripts %}
36 |
37 | {% block header %}
38 |
39 | {% endblock header %}
40 |
--------------------------------------------------------------------------------
/templates/bundles/TwigBundle/Exception/error404.html.twig:
--------------------------------------------------------------------------------
1 | {# templates/bundles/TwigBundle/Exception/error404.html.twig #}
2 | {% extends 'base.html.twig' %}
3 |
4 | {% block body %}
5 | Page not found
6 |
7 |
8 | The requested page couldn't be located. Checkout for any URL
9 | misspelling or return to the homepage .
10 |
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/templates/bundles/TwigBundle/Exception/error404.json.twig:
--------------------------------------------------------------------------------
1 | {
2 | "error": "404 Not found."
3 | }
4 |
--------------------------------------------------------------------------------
/tests/Controller/AbstractWebTestCase.php:
--------------------------------------------------------------------------------
1 |
5 | *
6 | * For the full copyright and license information, please view the LICENSE
7 | * file that was distributed with this source code.
8 | */
9 |
10 | declare(strict_types=1);
11 |
12 | namespace App\Tests\Controller;
13 |
14 | use Faker\Factory;
15 | use Faker\Generator;
16 | use Lexik\Bundle\JWTAuthenticationBundle\Exception\JWTEncodeFailureException;
17 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
18 | use Symfony\Component\BrowserKit\Client;
19 |
20 | abstract class AbstractWebTestCase extends WebTestCase
21 | {
22 | /**
23 | * @var Client
24 | */
25 | protected $client;
26 |
27 | /**
28 | * @var string|null
29 | */
30 | protected $token;
31 |
32 | /**
33 | * @var Generator
34 | */
35 | protected $faker;
36 |
37 | /**
38 | * AbstractWebTestCase constructor.
39 | *
40 | * @param string|null $name
41 | * @param array $data
42 | * @param string $dataName
43 | */
44 | public function __construct(?string $name = null, array $data = [], string $dataName = '')
45 | {
46 | parent::__construct($name, $data, $dataName);
47 |
48 | $this->client = static::createClient();
49 | $this->token = self::getToken();
50 | $this->faker = Factory::create();
51 | }
52 |
53 | /**
54 | * @return string|null
55 | */
56 | private static function getToken(): ?string
57 | {
58 | self::bootKernel();
59 |
60 | // returns the real and unchanged service container
61 | $container = self::$kernel->getContainer();
62 |
63 | $data = ['username' => 'developer@symfony.local', 'roles' => ['ROLE_ADMIN']];
64 |
65 | try {
66 | $token = $container
67 | ->get('lexik_jwt_authentication.encoder')
68 | ->encode($data);
69 | } catch (JWTEncodeFailureException $e) {
70 | echo $e->getMessage().PHP_EOL;
71 | }
72 |
73 | return $token;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/translations/.gitignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/translations/.gitignore
--------------------------------------------------------------------------------
/var/data.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tulik/symfony-4-rest-api/ad4b8aec093a3d188e9d47b9c586e0798d45d148/var/data.db
--------------------------------------------------------------------------------