├── .env ├── .env.test ├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── bin ├── console └── phpunit ├── composer.json ├── composer.lock ├── config ├── bootstrap.php ├── bundles.php ├── packages │ ├── cache.yaml │ ├── dev │ │ ├── debug.yaml │ │ ├── monolog.yaml │ │ └── web_profiler.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── fos_rest.yaml │ ├── framework.yaml │ ├── mailer.yaml │ ├── notifier.yaml │ ├── prod │ │ ├── doctrine.yaml │ │ ├── monolog.yaml │ │ └── routing.yaml │ ├── routing.yaml │ ├── security.yaml │ ├── sensio_framework_extra.yaml │ ├── test │ │ ├── framework.yaml │ │ ├── monolog.yaml │ │ ├── twig.yaml │ │ ├── validator.yaml │ │ └── web_profiler.yaml │ ├── translation.yaml │ ├── twig.yaml │ └── validator.yaml ├── routes.yaml ├── routes │ ├── annotations.yaml │ └── dev │ │ ├── framework.yaml │ │ └── web_profiler.yaml └── services.yaml ├── phpunit.xml.dist ├── public └── index.php ├── src ├── Controller │ ├── .gitignore │ ├── AbstractApiController.php │ ├── CartController.php │ ├── CustomerController.php │ └── ProductController.php ├── Entity │ ├── .gitignore │ ├── Cart.php │ ├── Customer.php │ └── Product.php ├── Form │ └── Type │ │ ├── CartType.php │ │ ├── CustomerType.php │ │ └── ProductType.php ├── Kernel.php ├── Migrations │ └── Version20200515201045.php └── Repository │ └── .gitignore ├── symfony.lock ├── templates └── base.html.twig ├── tests └── bootstrap.php └── translations └── .gitignore /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration 15 | 16 | ###> symfony/framework-bundle ### 17 | APP_ENV=dev 18 | APP_SECRET=50373636bdd097439dc76657d212924c 19 | #TRUSTED_PROXIES=127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16 20 | #TRUSTED_HOSTS='^(localhost|example\.com)$' 21 | ###< symfony/framework-bundle ### 22 | 23 | ###> symfony/mailer ### 24 | # MAILER_DSN=smtp://localhost 25 | ###< symfony/mailer ### 26 | 27 | ###> doctrine/doctrine-bundle ### 28 | # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url 29 | # For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db" 30 | # For a PostgreSQL database, use: "postgresql://db_user:db_password@127.0.0.1:5432/db_name?serverVersion=11&charset=utf8" 31 | # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml 32 | DATABASE_URL='postgres://sf_user:nopassword@postgres/test' 33 | ###< doctrine/doctrine-bundle ### 34 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='$ecretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | PANTHER_APP_ENV=panther 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: capcoding 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /public/bundles/ 2 | /public/assets/ 3 | /var/ 4 | /vendor/ 5 | .idea 6 | 7 | ###> phpunit/phpunit ### 8 | /phpunit.xml 9 | .phpunit.result.cache 10 | ###< phpunit/phpunit ### 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cap Coding 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 | # How to build simple CRUD API service with Symfony 5 (for beginners) 2 | 3 | Please watch the whole video tutorial [here](https://youtu.be/tbXpX4dAqjg) 4 | 5 | Create a customer: 6 | 7 | ```sh 8 | curl --location --request POST 'http://localhost:8080/api/v1/customers' \ 9 | --header 'Content-Type: application/json' \ 10 | --data-raw '{ 11 | "email": "test.email@gmail.com", 12 | "phoneNumber": "+49116012345678" 13 | }' 14 | ``` 15 | 16 | Create a product: 17 | 18 | ```sh 19 | curl --location --request POST 'http://localhost:8080/api/v1/products' \ 20 | --header 'Content-Type: application/json' \ 21 | --data-raw '{ 22 | "code": "test_code", 23 | "title": "Test title", 24 | "price": 1234 25 | }' 26 | ``` 27 | 28 | Create a cart: 29 | 30 | ```sh 31 | curl --location --request POST 'http://localhost:8080/api/v1/customers/cart' \ 32 | --header 'Content-Type: application/json' \ 33 | --data-raw '{ 34 | "customer": 1, 35 | "products": [ 36 | 1 37 | ], 38 | "dateTime": "2020-08-05 12:15:00" 39 | }' 40 | ``` 41 | 42 | 43 | # Other video tutorials 44 | 45 | ## Code faster with Github Copilot 46 | 47 | There is a [video](https://youtu.be/qyxJXNNvd70) 48 | 49 | ## Create a classic website using Symfony 5 50 | 51 | There is a [video](https://youtu.be/svAxl6U8akQ) 52 | 53 | ## Delay "heavy" tasks in Symfony with component Messenger 54 | 55 | There is a [video](https://youtu.be/UHlA5nHdCmw) 56 | 57 | ## Delay heavy tasks in Symfony with kernel.terminate event to decrease response time 58 | 59 | There is a [video](https://youtu.be/HrQme9KUlUg) 60 | 61 | ## Create Symfony 5 project with Docker and Postgres 62 | 63 | There is a [video](https://youtu.be/4UrPI6Y3BWA) 64 | 65 | ## Design pattern "Chain of responsibility" (Symfony implementation) 66 | 67 | There is a [video](https://youtu.be/3KQlubIv684) 68 | 69 | ## How to use data transfer objects (DTO) in Symfony API application 70 | 71 | There is a [branch](https://github.com/Cap-Coding/symfony_api/tree/data_transfer_objects) and here is a [video](https://youtu.be/XxIhzgGv214) 72 | 73 | ## How to build simple CRUD API service with Symfony 5 (for beginners) 74 | 75 | There is a [branch](https://github.com/Cap-Coding/symfony_api/tree/crud_api) and here is a [video](https://youtu.be/tbXpX4dAqjg) 76 | 77 | ## How to use Symfony Form Events in API service 78 | 79 | There is a [video](https://youtu.be/lLwx96DA_Ww) 80 | 81 | ## How to use object factories with Symfony Forms 82 | 83 | There is a [video](https://youtu.be/chgvsi6TWM8) 84 | 85 | ## Dockerize WordPress 86 | 87 | There is a [video](https://youtu.be/coqucs1UhMY) 88 | 89 | ## Get trusted SSL certificate (https) for free with Let's Encrypt and Certbot 90 | 91 | There is a [video](https://youtu.be/nFDk43tAKFQ) 92 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 23 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 24 | } 25 | 26 | if ($input->hasParameterOption('--no-debug', true)) { 27 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 28 | } 29 | 30 | require dirname(__DIR__).'/config/bootstrap.php'; 31 | 32 | if ($_SERVER['APP_DEBUG']) { 33 | umask(0000); 34 | 35 | if (class_exists(Debug::class)) { 36 | Debug::enable(); 37 | } 38 | } 39 | 40 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 41 | $application = new Application($kernel); 42 | $application->run($input); 43 | -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | =1.2) 13 | if (is_array($env = @include dirname(__DIR__).'/.env.local.php') && (!isset($env['APP_ENV']) || ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? $env['APP_ENV']) === $env['APP_ENV'])) { 14 | (new Dotenv(false))->populate($env); 15 | } else { 16 | // load all the .env files 17 | (new Dotenv(false))->loadEnv(dirname(__DIR__).'/.env'); 18 | } 19 | 20 | $_SERVER += $_ENV; 21 | $_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = ($_SERVER['APP_ENV'] ?? $_ENV['APP_ENV'] ?? null) ?: 'dev'; 22 | $_SERVER['APP_DEBUG'] = $_SERVER['APP_DEBUG'] ?? $_ENV['APP_DEBUG'] ?? 'prod' !== $_SERVER['APP_ENV']; 23 | $_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = (int) $_SERVER['APP_DEBUG'] || filter_var($_SERVER['APP_DEBUG'], FILTER_VALIDATE_BOOLEAN) ? '1' : '0'; 24 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 6 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], 7 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true], 8 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 9 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 10 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 11 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 12 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 13 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true], 14 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 15 | FOS\RestBundle\FOSRestBundle::class => ['all' => true], 16 | ]; 17 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | # uncomment to get logging in your browser 9 | # you may have to allow bigger header sizes in your Web server configuration 10 | #firephp: 11 | # type: firephp 12 | # level: info 13 | #chromephp: 14 | # type: chromephp 15 | # level: info 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine", "!console"] 20 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { only_exceptions: false } 7 | -------------------------------------------------------------------------------- /config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | driver: 'pdo_pgsql' 4 | server_version: '12.2' 5 | charset: utf8 6 | url: '%env(resolve:DATABASE_URL)%' 7 | orm: 8 | auto_generate_proxy_classes: true 9 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware 10 | auto_mapping: true 11 | mappings: 12 | App: 13 | is_bundle: false 14 | type: annotation 15 | dir: '%kernel.project_dir%/src/Entity' 16 | prefix: 'App\Entity' 17 | alias: App 18 | -------------------------------------------------------------------------------- /config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/src/Migrations' 6 | -------------------------------------------------------------------------------- /config/packages/fos_rest.yaml: -------------------------------------------------------------------------------- 1 | # Read the documentation: https://symfony.com/doc/master/bundles/FOSRestBundle/index.html 2 | fos_rest: 3 | body_listener: 4 | enabled: true 5 | # param_fetcher_listener: true 6 | # allowed_methods_listener: true 7 | # routing_loader: true 8 | # view: 9 | # view_response_listener: true 10 | # exception: 11 | # codes: 12 | # App\Exception\MyException: 403 13 | # messages: 14 | # App\Exception\MyException: Forbidden area. 15 | format_listener: 16 | rules: 17 | - { path: ^/api, prefer_extension: true, fallback_format: json, priorities: [ json ] } 18 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | secret: '%env(APP_SECRET)%' 3 | #csrf_protection: true 4 | #http_method_override: true 5 | 6 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 7 | # Remove or comment this section to explicitly disable session support. 8 | session: 9 | handler_id: null 10 | cookie_secure: auto 11 | cookie_samesite: lax 12 | 13 | #esi: true 14 | #fragments: true 15 | php_errors: 16 | log: true 17 | -------------------------------------------------------------------------------- /config/packages/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | dsn: '%env(MAILER_DSN)%' 4 | -------------------------------------------------------------------------------- /config/packages/notifier.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | notifier: 3 | #chatter_transports: 4 | # slack: '%env(SLACK_DSN)%' 5 | # telegram: '%env(TELEGRAM_DSN)%' 6 | #texter_transports: 7 | # twilio: '%env(TWILIO_DSN)%' 8 | # nexmo: '%env(NEXMO_DSN)%' 9 | channel_policy: 10 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo 11 | urgent: ['email'] 12 | high: ['email'] 13 | medium: ['email'] 14 | low: ['email'] 15 | admin_recipients: 16 | - { email: admin@example.com } 17 | -------------------------------------------------------------------------------- /config/packages/prod/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | orm: 3 | auto_generate_proxy_classes: false 4 | metadata_cache_driver: 5 | type: pool 6 | pool: doctrine.system_cache_pool 7 | query_cache_driver: 8 | type: pool 9 | pool: doctrine.system_cache_pool 10 | result_cache_driver: 11 | type: pool 12 | pool: doctrine.result_cache_pool 13 | 14 | framework: 15 | cache: 16 | pools: 17 | doctrine.result_cache_pool: 18 | adapter: cache.app 19 | doctrine.system_cache_pool: 20 | adapter: cache.system 21 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | console: 14 | type: console 15 | process_psr_3_messages: false 16 | channels: ["!event", "!doctrine"] 17 | 18 | # Uncomment to log deprecations 19 | #deprecation: 20 | # type: stream 21 | # path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log" 22 | #deprecation_filter: 23 | # type: filter 24 | # handler: deprecation 25 | # max_level: info 26 | # channels: ["php"] 27 | -------------------------------------------------------------------------------- /config/packages/prod/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null 4 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 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 | users_in_memory: { memory: null } 5 | firewalls: 6 | dev: 7 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 8 | security: false 9 | main: 10 | anonymous: lazy 11 | provider: users_in_memory 12 | 13 | # activate different ways to authenticate 14 | # https://symfony.com/doc/current/security.html#firewalls-authentication 15 | 16 | # https://symfony.com/doc/current/security/impersonating_user.html 17 | # switch_user: true 18 | 19 | # Easy way to control access for large sections of your site 20 | # Note: Only the *first* access control that matches will be used 21 | access_control: 22 | # - { path: ^/admin, roles: ROLE_ADMIN } 23 | # - { path: ^/profile, roles: ROLE_USER } 24 | -------------------------------------------------------------------------------- /config/packages/sensio_framework_extra.yaml: -------------------------------------------------------------------------------- 1 | sensio_framework_extra: 2 | router: 3 | annotations: false 4 | -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_id: session.storage.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | channels: ["!event"] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: en 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - en 7 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | -------------------------------------------------------------------------------- /config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /config/routes.yaml: -------------------------------------------------------------------------------- 1 | customer_list: 2 | path: /api/v1/customers 3 | controller: App\Controller\CustomerController:indexAction 4 | methods: [GET] 5 | 6 | customer_create: 7 | path: /api/v1/customers 8 | controller: App\Controller\CustomerController:createAction 9 | methods: [POST] 10 | 11 | product_list: 12 | path: /api/v1/products 13 | controller: App\Controller\ProductController:indexAction 14 | methods: [GET] 15 | 16 | product_create: 17 | path: /api/v1/products 18 | controller: App\Controller\ProductController:createAction 19 | methods: [POST] 20 | 21 | cart_show: 22 | path: /api/v1/customers/{id}/cart 23 | controller: App\Controller\CartController:showAction 24 | methods: [GET] 25 | requirements: 26 | id: '\d+' 27 | 28 | cart_create: 29 | path: /api/v1/customers/cart 30 | controller: App\Controller\CartController:createAction 31 | methods: [POST] 32 | 33 | cart_update: 34 | path: /api/v1/customers/{customerId}/cart 35 | controller: App\Controller\CartController:updateAction 36 | methods: [PATCH] 37 | requirements: 38 | customerId: '\d+' 39 | 40 | cart_delete: 41 | path: /api/v1/customers/{customerId}/cart/{cartId} 42 | controller: App\Controller\CartController:deleteAction 43 | methods: [DELETE] 44 | requirements: 45 | customerId: '\d+' 46 | cartId: '\d+' 47 | 48 | -------------------------------------------------------------------------------- /config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: annotation 4 | 5 | kernel: 6 | resource: ../../src/Kernel.php 7 | type: annotation 8 | -------------------------------------------------------------------------------- /config/routes/dev/framework.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@FrameworkBundle/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/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | 8 | services: 9 | # default configuration for services in *this* file 10 | _defaults: 11 | autowire: true # Automatically injects dependencies in your services. 12 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 13 | 14 | # makes classes in src/ available to be used as services 15 | # this creates a service per class whose id is the fully-qualified class name 16 | App\: 17 | resource: '../src/*' 18 | exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}' 19 | 20 | # controllers are imported separately to make sure services can be injected 21 | # as action arguments even if you don't extend any base controller class 22 | App\Controller\: 23 | resource: '../src/Controller' 24 | tags: ['controller.service_arguments'] 25 | 26 | # add more service definitions when explicit configuration is needed 27 | # please note that last definitions always *replace* previous ones 28 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | tests 21 | 22 | 23 | 24 | 25 | 26 | src 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | handle($request); 26 | $response->send(); 27 | $kernel->terminate($request, $response); 28 | -------------------------------------------------------------------------------- /src/Controller/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cap-Coding/symfony_api/f60c1e0d35c210ce24d2d1b84ce80235b3b0480a/src/Controller/.gitignore -------------------------------------------------------------------------------- /src/Controller/AbstractApiController.php: -------------------------------------------------------------------------------- 1 | false, 17 | ]); 18 | 19 | return $this->container->get('form.factory')->createNamed('', $type, $data, $options); 20 | } 21 | 22 | protected function respond($data, int $statusCode = Response::HTTP_OK): Response 23 | { 24 | return $this->handleView($this->view($data, $statusCode)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Controller/CartController.php: -------------------------------------------------------------------------------- 1 | get('id'); 19 | 20 | $customer = $this->getDoctrine()->getRepository(Customer::class)->findOneBy(['id' => $customerId]); 21 | 22 | if (!$customer) { 23 | throw new NotFoundHttpException('Customer not found'); 24 | } 25 | 26 | $cart = $this->getDoctrine()->getRepository(Cart::class)->findOneBy([ 27 | 'customer' => $customer, 28 | ]); 29 | 30 | if (!$cart) { 31 | throw new NotFoundHttpException('Cart does not exist for this customer'); 32 | } 33 | 34 | return $this->respond($cart); 35 | } 36 | 37 | public function createAction(Request $request): Response 38 | { 39 | $form = $this->buildForm(CartType::class); 40 | 41 | $form->handleRequest($request); 42 | 43 | if (!$form->isSubmitted() || !$form->isValid()) { 44 | return $this->respond($form, Response::HTTP_BAD_REQUEST); 45 | } 46 | 47 | /** @var Cart $cart */ 48 | $cart = $form->getData(); 49 | 50 | $this->getDoctrine()->getManager()->persist($cart); 51 | $this->getDoctrine()->getManager()->flush(); 52 | 53 | return $this->respond($cart); 54 | } 55 | 56 | public function updateAction(Request $request): Response 57 | { 58 | $customerId = $request->get('customerId'); 59 | 60 | $customer = $this->getDoctrine()->getRepository(Customer::class)->findOneBy(['id' => $customerId]); 61 | 62 | if (!$customer) { 63 | throw new NotFoundHttpException('Customer not found'); 64 | } 65 | 66 | $cart = $this->getDoctrine()->getRepository(Cart::class)->findOneBy([ 67 | 'customer' => $customer, 68 | ]); 69 | 70 | $form = $this->buildForm(CartType::class, $cart, [ 71 | 'method' => $request->getMethod(), 72 | ]); 73 | 74 | $form->handleRequest($request); 75 | 76 | if (!$form->isSubmitted() || !$form->isValid()) { 77 | return $this->respond($form, Response::HTTP_BAD_REQUEST); 78 | } 79 | 80 | /** @var Cart $cart */ 81 | $cart = $form->getData(); 82 | 83 | $this->getDoctrine()->getManager()->persist($cart); 84 | $this->getDoctrine()->getManager()->flush(); 85 | 86 | return $this->respond($cart); 87 | } 88 | 89 | public function deleteAction(Request $request): Response 90 | { 91 | $cartId = $request->get('cartId'); 92 | $customerId = $request->get('customerId'); 93 | 94 | $cart = $this->getDoctrine()->getRepository(Cart::class)->findOneBy([ 95 | 'customer' => $customerId, 96 | 'id' => $cartId, 97 | ]); 98 | 99 | if (!$cart) { 100 | throw new NotFoundHttpException('Cart not found'); 101 | } 102 | 103 | $this->getDoctrine()->getManager()->remove($cart); 104 | $this->getDoctrine()->getManager()->flush(); 105 | 106 | return $this->respond(null); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Controller/CustomerController.php: -------------------------------------------------------------------------------- 1 | getDoctrine()->getRepository(Customer::class)->findAll(); 17 | 18 | return $this->respond($customers); 19 | } 20 | 21 | public function createAction(Request $request): Response 22 | { 23 | $form = $this->buildForm(CustomerType::class); 24 | 25 | $form->handleRequest($request); 26 | 27 | if (!$form->isSubmitted() || !$form->isValid()) { 28 | return $this->respond($form, Response::HTTP_BAD_REQUEST); 29 | } 30 | 31 | /** @var Customer $customer */ 32 | $customer = $form->getData(); 33 | 34 | $this->getDoctrine()->getManager()->persist($customer); 35 | $this->getDoctrine()->getManager()->flush(); 36 | 37 | return $this->respond($customer); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Controller/ProductController.php: -------------------------------------------------------------------------------- 1 | getDoctrine()->getRepository(Product::class)->findAll(); 17 | 18 | return $this->respond($products); 19 | } 20 | 21 | public function createAction(Request $request): Response 22 | { 23 | $form = $this->buildForm(ProductType::class); 24 | 25 | $form->handleRequest($request); 26 | 27 | if (!$form->isSubmitted() || !$form->isValid()) { 28 | return $this->respond($form, Response::HTTP_BAD_REQUEST); 29 | } 30 | 31 | /** @var Product $product */ 32 | $product = $form->getData(); 33 | 34 | $this->getDoctrine()->getManager()->persist($product); 35 | $this->getDoctrine()->getManager()->flush(); 36 | 37 | return $this->respond($product); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cap-Coding/symfony_api/f60c1e0d35c210ce24d2d1b84ce80235b3b0480a/src/Entity/.gitignore -------------------------------------------------------------------------------- /src/Entity/Cart.php: -------------------------------------------------------------------------------- 1 | products = new ArrayCollection(); 50 | } 51 | 52 | /** 53 | * @return int|null 54 | */ 55 | public function getId(): ?int 56 | { 57 | return $this->id; 58 | } 59 | 60 | /** 61 | * @return \DateTime|null 62 | */ 63 | public function getDateTime(): ?\DateTime 64 | { 65 | return $this->dateTime; 66 | } 67 | 68 | /** 69 | * @param \DateTime|null $dateTime 70 | */ 71 | public function setDateTime(?\DateTime $dateTime): void 72 | { 73 | $this->dateTime = $dateTime; 74 | } 75 | 76 | /** 77 | * @return Customer|null 78 | */ 79 | public function getCustomer(): ?Customer 80 | { 81 | return $this->customer; 82 | } 83 | 84 | /** 85 | * @param Customer|null $customer 86 | */ 87 | public function setCustomer(?Customer $customer): void 88 | { 89 | $this->customer = $customer; 90 | } 91 | 92 | /** 93 | * @return Product[]|Collection 94 | */ 95 | public function getProducts() 96 | { 97 | return $this->products; 98 | } 99 | 100 | /** 101 | * @param Product[]|Collection $products 102 | */ 103 | public function setProducts($products): void 104 | { 105 | $this->products = $products; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Entity/Customer.php: -------------------------------------------------------------------------------- 1 | id; 44 | } 45 | 46 | /** 47 | * @return string|null 48 | */ 49 | public function getEmail(): ?string 50 | { 51 | return $this->email; 52 | } 53 | 54 | /** 55 | * @param string|null $email 56 | */ 57 | public function setEmail(?string $email): void 58 | { 59 | $this->email = $email; 60 | } 61 | 62 | /** 63 | * @return string|null 64 | */ 65 | public function getPhoneNumber(): ?string 66 | { 67 | return $this->phoneNumber; 68 | } 69 | 70 | /** 71 | * @param string|null $phoneNumber 72 | */ 73 | public function setPhoneNumber(?string $phoneNumber): void 74 | { 75 | $this->phoneNumber = $phoneNumber; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Entity/Product.php: -------------------------------------------------------------------------------- 1 | id; 51 | } 52 | 53 | /** 54 | * @return string|null 55 | */ 56 | public function getCode(): ?string 57 | { 58 | return $this->code; 59 | } 60 | 61 | /** 62 | * @param string|null $code 63 | */ 64 | public function setCode(?string $code): void 65 | { 66 | $this->code = $code; 67 | } 68 | 69 | /** 70 | * @return string|null 71 | */ 72 | public function getTitle(): ?string 73 | { 74 | return $this->title; 75 | } 76 | 77 | /** 78 | * @param string|null $title 79 | */ 80 | public function setTitle(?string $title): void 81 | { 82 | $this->title = $title; 83 | } 84 | 85 | /** 86 | * @return int|null 87 | */ 88 | public function getPrice(): ?int 89 | { 90 | return $this->price; 91 | } 92 | 93 | /** 94 | * @param int|null $price 95 | */ 96 | public function setPrice(?int $price): void 97 | { 98 | $this->price = $price; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Form/Type/CartType.php: -------------------------------------------------------------------------------- 1 | add('customer', EntityType::class, [ 23 | 'class' => Customer::class, 24 | 'constraints' => [ 25 | new NotNull(), 26 | ], 27 | ]) 28 | ->add('products', EntityType::class, [ 29 | 'class' => Product::class, 30 | 'multiple' => true, 31 | 'constraints' => [ 32 | new NotNull(), 33 | ], 34 | ]) 35 | ->add('dateTime', DateTimeType::class, [ 36 | 'widget' => 'single_text', 37 | 'constraints' => [ 38 | new NotNull(), 39 | ], 40 | ]) 41 | ; 42 | } 43 | 44 | public function configureOptions(OptionsResolver $resolver): void 45 | { 46 | $resolver->setDefaults([ 47 | 'data_class' => Cart::class, 48 | ]); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Form/Type/CustomerType.php: -------------------------------------------------------------------------------- 1 | add('email', EmailType::class, [ 22 | 'constraints' => [ 23 | new NotNull([ 24 | 'message' => 'Email can not be blank', 25 | ]), 26 | new Email(), 27 | ] 28 | ]) 29 | ->add('phoneNumber', TextType::class, [ 30 | 'constraints' => [ 31 | new NotNull(), 32 | ] 33 | ]) 34 | ; 35 | } 36 | 37 | public function configureOptions(OptionsResolver $resolver): void 38 | { 39 | $resolver->setDefaults([ 40 | 'data_class' => Customer::class, 41 | ]); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Form/Type/ProductType.php: -------------------------------------------------------------------------------- 1 | add('code', TextType::class, [ 23 | 'constraints' => [ 24 | new NotNull(), 25 | new Length([ 26 | 'max' => 100, 27 | ]) 28 | ] 29 | ]) 30 | ->add('title', TextType::class, [ 31 | 'constraints' => [ 32 | new NotNull(), 33 | ] 34 | ]) 35 | ->add('price', IntegerType::class, [ 36 | 'constraints' => [ 37 | new NotNull(), 38 | new GreaterThan([ 39 | 'value' => 0 40 | ]), 41 | ] 42 | ]) 43 | ; 44 | } 45 | 46 | public function configureOptions(OptionsResolver $resolver): void 47 | { 48 | $resolver->setDefaults([ 49 | 'data_class' => Product::class, 50 | ]); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | getProjectDir().'/config/bundles.php'; 21 | foreach ($contents as $class => $envs) { 22 | if ($envs[$this->environment] ?? $envs['all'] ?? false) { 23 | yield new $class(); 24 | } 25 | } 26 | } 27 | 28 | public function getProjectDir(): string 29 | { 30 | return \dirname(__DIR__); 31 | } 32 | 33 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void 34 | { 35 | $container->addResource(new FileResource($this->getProjectDir().'/config/bundles.php')); 36 | $container->setParameter('container.dumper.inline_class_loader', \PHP_VERSION_ID < 70400 || $this->debug); 37 | $container->setParameter('container.dumper.inline_factories', true); 38 | $confDir = $this->getProjectDir().'/config'; 39 | 40 | $loader->load($confDir.'/{packages}/*'.self::CONFIG_EXTS, 'glob'); 41 | $loader->load($confDir.'/{packages}/'.$this->environment.'/*'.self::CONFIG_EXTS, 'glob'); 42 | $loader->load($confDir.'/{services}'.self::CONFIG_EXTS, 'glob'); 43 | $loader->load($confDir.'/{services}_'.$this->environment.self::CONFIG_EXTS, 'glob'); 44 | } 45 | 46 | protected function configureRoutes(RouteCollectionBuilder $routes): void 47 | { 48 | $confDir = $this->getProjectDir().'/config'; 49 | 50 | $routes->import($confDir.'/{routes}/'.$this->environment.'/*'.self::CONFIG_EXTS, '/', 'glob'); 51 | $routes->import($confDir.'/{routes}/*'.self::CONFIG_EXTS, '/', 'glob'); 52 | $routes->import($confDir.'/{routes}'.self::CONFIG_EXTS, '/', 'glob'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Migrations/Version20200515201045.php: -------------------------------------------------------------------------------- 1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); 15 | 16 | $this->addSql('CREATE SEQUENCE app_cart_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 17 | $this->addSql('CREATE SEQUENCE app_product_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 18 | $this->addSql('CREATE SEQUENCE app_customer_id_seq INCREMENT BY 1 MINVALUE 1 START 1'); 19 | $this->addSql('CREATE TABLE app_cart (id INT NOT NULL, customer_id INT DEFAULT NULL, date_time TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); 20 | $this->addSql('CREATE UNIQUE INDEX UNIQ_E8DAD179395C3F3 ON app_cart (customer_id)'); 21 | $this->addSql('CREATE TABLE cart_product (cart_id INT NOT NULL, product_id INT NOT NULL, PRIMARY KEY(cart_id, product_id))'); 22 | $this->addSql('CREATE INDEX IDX_2890CCAA1AD5CDBF ON cart_product (cart_id)'); 23 | $this->addSql('CREATE INDEX IDX_2890CCAA4584665A ON cart_product (product_id)'); 24 | $this->addSql('CREATE TABLE app_product (id INT NOT NULL, code VARCHAR(100) NOT NULL, title VARCHAR(200) NOT NULL, price INT NOT NULL, PRIMARY KEY(id))'); 25 | $this->addSql('CREATE TABLE app_customer (id INT NOT NULL, email VARCHAR(100) NOT NULL, phone_number VARCHAR(50) NOT NULL, PRIMARY KEY(id))'); 26 | $this->addSql('ALTER TABLE app_cart ADD CONSTRAINT FK_E8DAD179395C3F3 FOREIGN KEY (customer_id) REFERENCES app_customer (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); 27 | $this->addSql('ALTER TABLE cart_product ADD CONSTRAINT FK_2890CCAA1AD5CDBF FOREIGN KEY (cart_id) REFERENCES app_cart (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); 28 | $this->addSql('ALTER TABLE cart_product ADD CONSTRAINT FK_2890CCAA4584665A FOREIGN KEY (product_id) REFERENCES app_product (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE'); 29 | } 30 | 31 | public function down(Schema $schema) : void 32 | { 33 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.'); 34 | 35 | $this->addSql('ALTER TABLE cart_product DROP CONSTRAINT FK_2890CCAA1AD5CDBF'); 36 | $this->addSql('ALTER TABLE cart_product DROP CONSTRAINT FK_2890CCAA4584665A'); 37 | $this->addSql('ALTER TABLE app_cart DROP CONSTRAINT FK_E8DAD179395C3F3'); 38 | $this->addSql('DROP SEQUENCE app_cart_id_seq CASCADE'); 39 | $this->addSql('DROP SEQUENCE app_product_id_seq CASCADE'); 40 | $this->addSql('DROP SEQUENCE app_customer_id_seq CASCADE'); 41 | $this->addSql('DROP TABLE app_cart'); 42 | $this->addSql('DROP TABLE cart_product'); 43 | $this->addSql('DROP TABLE app_product'); 44 | $this->addSql('DROP TABLE app_customer'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Repository/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cap-Coding/symfony_api/f60c1e0d35c210ce24d2d1b84ce80235b3b0480a/src/Repository/.gitignore -------------------------------------------------------------------------------- /symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "doctrine/annotations": { 3 | "version": "1.0", 4 | "recipe": { 5 | "repo": "github.com/symfony/recipes", 6 | "branch": "master", 7 | "version": "1.0", 8 | "ref": "a2759dd6123694c8d901d0ec80006e044c2e6457" 9 | }, 10 | "files": [ 11 | "config/routes/annotations.yaml" 12 | ] 13 | }, 14 | "doctrine/cache": { 15 | "version": "1.10.0" 16 | }, 17 | "doctrine/collections": { 18 | "version": "1.6.4" 19 | }, 20 | "doctrine/common": { 21 | "version": "2.12.0" 22 | }, 23 | "doctrine/dbal": { 24 | "version": "2.10.2" 25 | }, 26 | "doctrine/deprecations": { 27 | "version": "v0.5.3" 28 | }, 29 | "doctrine/doctrine-bundle": { 30 | "version": "2.0", 31 | "recipe": { 32 | "repo": "github.com/symfony/recipes", 33 | "branch": "master", 34 | "version": "2.0", 35 | "ref": "a9f2463b9f73efe74482f831f03a204a41328555" 36 | }, 37 | "files": [ 38 | "config/packages/doctrine.yaml", 39 | "config/packages/prod/doctrine.yaml", 40 | "src/Entity/.gitignore", 41 | "src/Repository/.gitignore" 42 | ] 43 | }, 44 | "doctrine/doctrine-migrations-bundle": { 45 | "version": "1.2", 46 | "recipe": { 47 | "repo": "github.com/symfony/recipes", 48 | "branch": "master", 49 | "version": "1.2", 50 | "ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1" 51 | }, 52 | "files": [ 53 | "config/packages/doctrine_migrations.yaml", 54 | "src/Migrations/.gitignore" 55 | ] 56 | }, 57 | "doctrine/event-manager": { 58 | "version": "1.1.0" 59 | }, 60 | "doctrine/inflector": { 61 | "version": "1.4.0" 62 | }, 63 | "doctrine/instantiator": { 64 | "version": "1.3.0" 65 | }, 66 | "doctrine/lexer": { 67 | "version": "1.2.0" 68 | }, 69 | "doctrine/migrations": { 70 | "version": "2.2.1" 71 | }, 72 | "doctrine/orm": { 73 | "version": "v2.7.2" 74 | }, 75 | "doctrine/persistence": { 76 | "version": "1.3.7" 77 | }, 78 | "doctrine/reflection": { 79 | "version": "1.2.1" 80 | }, 81 | "doctrine/sql-formatter": { 82 | "version": "1.1.2" 83 | }, 84 | "egulias/email-validator": { 85 | "version": "2.1.17" 86 | }, 87 | "friendsofphp/proxy-manager-lts": { 88 | "version": "v1.0.5" 89 | }, 90 | "friendsofsymfony/rest-bundle": { 91 | "version": "2.2", 92 | "recipe": { 93 | "repo": "github.com/symfony/recipes-contrib", 94 | "branch": "master", 95 | "version": "2.2", 96 | "ref": "cad41ef93d6150067ae2bb3c7fd729492dff6f0a" 97 | }, 98 | "files": [ 99 | "config/packages/fos_rest.yaml" 100 | ] 101 | }, 102 | "jdorn/sql-formatter": { 103 | "version": "v1.2.17" 104 | }, 105 | "laminas/laminas-code": { 106 | "version": "3.4.1" 107 | }, 108 | "laminas/laminas-eventmanager": { 109 | "version": "3.2.1" 110 | }, 111 | "laminas/laminas-zendframework-bridge": { 112 | "version": "1.0.3" 113 | }, 114 | "monolog/monolog": { 115 | "version": "2.0.2" 116 | }, 117 | "myclabs/deep-copy": { 118 | "version": "1.10.2" 119 | }, 120 | "nikic/php-parser": { 121 | "version": "v4.4.0" 122 | }, 123 | "ocramius/package-versions": { 124 | "version": "1.8.0" 125 | }, 126 | "ocramius/proxy-manager": { 127 | "version": "2.8.0" 128 | }, 129 | "phar-io/manifest": { 130 | "version": "2.0.3" 131 | }, 132 | "phar-io/version": { 133 | "version": "3.1.0" 134 | }, 135 | "php": { 136 | "version": "7.4" 137 | }, 138 | "phpdocumentor/reflection-common": { 139 | "version": "2.1.0" 140 | }, 141 | "phpdocumentor/reflection-docblock": { 142 | "version": "5.1.0" 143 | }, 144 | "phpdocumentor/type-resolver": { 145 | "version": "1.1.0" 146 | }, 147 | "phpspec/prophecy": { 148 | "version": "1.14.0" 149 | }, 150 | "phpunit/php-code-coverage": { 151 | "version": "9.2.8" 152 | }, 153 | "phpunit/php-file-iterator": { 154 | "version": "3.0.5" 155 | }, 156 | "phpunit/php-invoker": { 157 | "version": "3.1.1" 158 | }, 159 | "phpunit/php-text-template": { 160 | "version": "2.0.4" 161 | }, 162 | "phpunit/php-timer": { 163 | "version": "5.0.3" 164 | }, 165 | "phpunit/phpunit": { 166 | "version": "9.5", 167 | "recipe": { 168 | "repo": "github.com/symfony/recipes", 169 | "branch": "master", 170 | "version": "9.3", 171 | "ref": "a6249a6c4392e9169b87abf93225f7f9f59025e6" 172 | }, 173 | "files": [ 174 | ".env.test", 175 | "phpunit.xml.dist", 176 | "tests/bootstrap.php" 177 | ] 178 | }, 179 | "psr/cache": { 180 | "version": "1.0.1" 181 | }, 182 | "psr/container": { 183 | "version": "1.0.0" 184 | }, 185 | "psr/event-dispatcher": { 186 | "version": "1.0.0" 187 | }, 188 | "psr/link": { 189 | "version": "1.0.0" 190 | }, 191 | "psr/log": { 192 | "version": "1.1.3" 193 | }, 194 | "roave/security-advisories": { 195 | "version": "dev-latest" 196 | }, 197 | "sebastian/cli-parser": { 198 | "version": "1.0.1" 199 | }, 200 | "sebastian/code-unit": { 201 | "version": "1.0.8" 202 | }, 203 | "sebastian/code-unit-reverse-lookup": { 204 | "version": "2.0.3" 205 | }, 206 | "sebastian/comparator": { 207 | "version": "4.0.6" 208 | }, 209 | "sebastian/complexity": { 210 | "version": "2.0.2" 211 | }, 212 | "sebastian/diff": { 213 | "version": "4.0.4" 214 | }, 215 | "sebastian/environment": { 216 | "version": "5.1.3" 217 | }, 218 | "sebastian/exporter": { 219 | "version": "4.0.4" 220 | }, 221 | "sebastian/global-state": { 222 | "version": "5.0.3" 223 | }, 224 | "sebastian/lines-of-code": { 225 | "version": "1.0.3" 226 | }, 227 | "sebastian/object-enumerator": { 228 | "version": "4.0.4" 229 | }, 230 | "sebastian/object-reflector": { 231 | "version": "2.0.4" 232 | }, 233 | "sebastian/recursion-context": { 234 | "version": "4.0.4" 235 | }, 236 | "sebastian/resource-operations": { 237 | "version": "3.0.3" 238 | }, 239 | "sebastian/type": { 240 | "version": "2.3.4" 241 | }, 242 | "sebastian/version": { 243 | "version": "3.0.2" 244 | }, 245 | "sensio/framework-extra-bundle": { 246 | "version": "5.2", 247 | "recipe": { 248 | "repo": "github.com/symfony/recipes", 249 | "branch": "master", 250 | "version": "5.2", 251 | "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b" 252 | }, 253 | "files": [ 254 | "config/packages/sensio_framework_extra.yaml" 255 | ] 256 | }, 257 | "symfony/asset": { 258 | "version": "v5.0.8" 259 | }, 260 | "symfony/browser-kit": { 261 | "version": "v5.0.8" 262 | }, 263 | "symfony/cache": { 264 | "version": "v5.0.8" 265 | }, 266 | "symfony/cache-contracts": { 267 | "version": "v2.0.1" 268 | }, 269 | "symfony/config": { 270 | "version": "v5.0.8" 271 | }, 272 | "symfony/console": { 273 | "version": "4.4", 274 | "recipe": { 275 | "repo": "github.com/symfony/recipes", 276 | "branch": "master", 277 | "version": "4.4", 278 | "ref": "ea8c0eda34fda57e7d5cd8cbd889e2a387e3472c" 279 | }, 280 | "files": [ 281 | "bin/console", 282 | "config/bootstrap.php" 283 | ] 284 | }, 285 | "symfony/css-selector": { 286 | "version": "v5.0.8" 287 | }, 288 | "symfony/debug-bundle": { 289 | "version": "4.1", 290 | "recipe": { 291 | "repo": "github.com/symfony/recipes", 292 | "branch": "master", 293 | "version": "4.1", 294 | "ref": "f8863cbad2f2e58c4b65fa1eac892ab189971bea" 295 | }, 296 | "files": [ 297 | "config/packages/dev/debug.yaml" 298 | ] 299 | }, 300 | "symfony/dependency-injection": { 301 | "version": "v5.0.8" 302 | }, 303 | "symfony/deprecation-contracts": { 304 | "version": "v2.4.0" 305 | }, 306 | "symfony/doctrine-bridge": { 307 | "version": "v5.0.8" 308 | }, 309 | "symfony/dom-crawler": { 310 | "version": "v5.0.8" 311 | }, 312 | "symfony/dotenv": { 313 | "version": "v5.0.8" 314 | }, 315 | "symfony/error-handler": { 316 | "version": "v5.0.8" 317 | }, 318 | "symfony/event-dispatcher": { 319 | "version": "v5.0.8" 320 | }, 321 | "symfony/event-dispatcher-contracts": { 322 | "version": "v2.0.1" 323 | }, 324 | "symfony/expression-language": { 325 | "version": "v5.0.8" 326 | }, 327 | "symfony/filesystem": { 328 | "version": "v5.0.8" 329 | }, 330 | "symfony/finder": { 331 | "version": "v5.0.8" 332 | }, 333 | "symfony/flex": { 334 | "version": "1.0", 335 | "recipe": { 336 | "repo": "github.com/symfony/recipes", 337 | "branch": "master", 338 | "version": "1.0", 339 | "ref": "c0eeb50665f0f77226616b6038a9b06c03752d8e" 340 | }, 341 | "files": [ 342 | ".env" 343 | ] 344 | }, 345 | "symfony/form": { 346 | "version": "v5.0.8" 347 | }, 348 | "symfony/framework-bundle": { 349 | "version": "4.4", 350 | "recipe": { 351 | "repo": "github.com/symfony/recipes", 352 | "branch": "master", 353 | "version": "4.4", 354 | "ref": "36d3075b2b8e0c4de0e82356a86e4c4a4eb6681b" 355 | }, 356 | "files": [ 357 | "config/bootstrap.php", 358 | "config/packages/cache.yaml", 359 | "config/packages/framework.yaml", 360 | "config/packages/test/framework.yaml", 361 | "config/routes/dev/framework.yaml", 362 | "config/services.yaml", 363 | "public/index.php", 364 | "src/Controller/.gitignore", 365 | "src/Kernel.php" 366 | ] 367 | }, 368 | "symfony/http-client": { 369 | "version": "v5.0.8" 370 | }, 371 | "symfony/http-client-contracts": { 372 | "version": "v2.0.1" 373 | }, 374 | "symfony/http-foundation": { 375 | "version": "v5.0.8" 376 | }, 377 | "symfony/http-kernel": { 378 | "version": "v5.0.8" 379 | }, 380 | "symfony/intl": { 381 | "version": "v5.0.8" 382 | }, 383 | "symfony/mailer": { 384 | "version": "4.3", 385 | "recipe": { 386 | "repo": "github.com/symfony/recipes", 387 | "branch": "master", 388 | "version": "4.3", 389 | "ref": "15658c2a0176cda2e7dba66276a2030b52bd81b2" 390 | }, 391 | "files": [ 392 | "config/packages/mailer.yaml" 393 | ] 394 | }, 395 | "symfony/maker-bundle": { 396 | "version": "1.0", 397 | "recipe": { 398 | "repo": "github.com/symfony/recipes", 399 | "branch": "master", 400 | "version": "1.0", 401 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" 402 | } 403 | }, 404 | "symfony/mime": { 405 | "version": "v5.0.8" 406 | }, 407 | "symfony/monolog-bridge": { 408 | "version": "v5.0.8" 409 | }, 410 | "symfony/monolog-bundle": { 411 | "version": "3.3", 412 | "recipe": { 413 | "repo": "github.com/symfony/recipes", 414 | "branch": "master", 415 | "version": "3.3", 416 | "ref": "a89f4cd8a232563707418eea6c2da36acd36a917" 417 | }, 418 | "files": [ 419 | "config/packages/dev/monolog.yaml", 420 | "config/packages/prod/monolog.yaml", 421 | "config/packages/test/monolog.yaml" 422 | ] 423 | }, 424 | "symfony/notifier": { 425 | "version": "5.0", 426 | "recipe": { 427 | "repo": "github.com/symfony/recipes", 428 | "branch": "master", 429 | "version": "5.0", 430 | "ref": "c31585e252b32fe0e1f30b1f256af553f4a06eb9" 431 | }, 432 | "files": [ 433 | "config/packages/notifier.yaml" 434 | ] 435 | }, 436 | "symfony/options-resolver": { 437 | "version": "v5.0.8" 438 | }, 439 | "symfony/password-hasher": { 440 | "version": "v5.3.8" 441 | }, 442 | "symfony/phpunit-bridge": { 443 | "version": "4.3", 444 | "recipe": { 445 | "repo": "github.com/symfony/recipes", 446 | "branch": "master", 447 | "version": "4.3", 448 | "ref": "6d0e35f749d5f4bfe1f011762875275cd3f9874f" 449 | }, 450 | "files": [ 451 | ".env.test", 452 | "bin/phpunit", 453 | "phpunit.xml.dist", 454 | "tests/bootstrap.php" 455 | ] 456 | }, 457 | "symfony/polyfill-intl-grapheme": { 458 | "version": "v1.17.0" 459 | }, 460 | "symfony/polyfill-intl-icu": { 461 | "version": "v1.16.0" 462 | }, 463 | "symfony/polyfill-intl-idn": { 464 | "version": "v1.16.0" 465 | }, 466 | "symfony/polyfill-intl-normalizer": { 467 | "version": "v1.17.0" 468 | }, 469 | "symfony/polyfill-mbstring": { 470 | "version": "v1.16.0" 471 | }, 472 | "symfony/polyfill-php73": { 473 | "version": "v1.16.0" 474 | }, 475 | "symfony/polyfill-php80": { 476 | "version": "v1.23.1" 477 | }, 478 | "symfony/polyfill-php81": { 479 | "version": "v1.23.0" 480 | }, 481 | "symfony/process": { 482 | "version": "v5.0.8" 483 | }, 484 | "symfony/property-access": { 485 | "version": "v5.0.8" 486 | }, 487 | "symfony/property-info": { 488 | "version": "v5.0.8" 489 | }, 490 | "symfony/proxy-manager-bridge": { 491 | "version": "v5.3.4" 492 | }, 493 | "symfony/routing": { 494 | "version": "4.2", 495 | "recipe": { 496 | "repo": "github.com/symfony/recipes", 497 | "branch": "master", 498 | "version": "4.2", 499 | "ref": "683dcb08707ba8d41b7e34adb0344bfd68d248a7" 500 | }, 501 | "files": [ 502 | "config/packages/prod/routing.yaml", 503 | "config/packages/routing.yaml", 504 | "config/routes.yaml" 505 | ] 506 | }, 507 | "symfony/security-bundle": { 508 | "version": "4.4", 509 | "recipe": { 510 | "repo": "github.com/symfony/recipes", 511 | "branch": "master", 512 | "version": "4.4", 513 | "ref": "7b4408dc203049666fe23fabed23cbadc6d8440f" 514 | }, 515 | "files": [ 516 | "config/packages/security.yaml" 517 | ] 518 | }, 519 | "symfony/security-core": { 520 | "version": "v5.0.8" 521 | }, 522 | "symfony/security-csrf": { 523 | "version": "v5.0.8" 524 | }, 525 | "symfony/security-guard": { 526 | "version": "v5.0.8" 527 | }, 528 | "symfony/security-http": { 529 | "version": "v5.0.8" 530 | }, 531 | "symfony/serializer": { 532 | "version": "v5.0.8" 533 | }, 534 | "symfony/service-contracts": { 535 | "version": "v2.0.1" 536 | }, 537 | "symfony/stopwatch": { 538 | "version": "v5.0.8" 539 | }, 540 | "symfony/string": { 541 | "version": "v5.0.8" 542 | }, 543 | "symfony/translation": { 544 | "version": "3.3", 545 | "recipe": { 546 | "repo": "github.com/symfony/recipes", 547 | "branch": "master", 548 | "version": "3.3", 549 | "ref": "2ad9d2545bce8ca1a863e50e92141f0b9d87ffcd" 550 | }, 551 | "files": [ 552 | "config/packages/translation.yaml", 553 | "translations/.gitignore" 554 | ] 555 | }, 556 | "symfony/translation-contracts": { 557 | "version": "v2.0.1" 558 | }, 559 | "symfony/twig-bridge": { 560 | "version": "v5.0.8" 561 | }, 562 | "symfony/twig-bundle": { 563 | "version": "5.0", 564 | "recipe": { 565 | "repo": "github.com/symfony/recipes", 566 | "branch": "master", 567 | "version": "5.0", 568 | "ref": "fab9149bbaa4d5eca054ed93f9e1b66cc500895d" 569 | }, 570 | "files": [ 571 | "config/packages/test/twig.yaml", 572 | "config/packages/twig.yaml", 573 | "templates/base.html.twig" 574 | ] 575 | }, 576 | "symfony/validator": { 577 | "version": "4.3", 578 | "recipe": { 579 | "repo": "github.com/symfony/recipes", 580 | "branch": "master", 581 | "version": "4.3", 582 | "ref": "d902da3e4952f18d3bf05aab29512eb61cabd869" 583 | }, 584 | "files": [ 585 | "config/packages/test/validator.yaml", 586 | "config/packages/validator.yaml" 587 | ] 588 | }, 589 | "symfony/var-dumper": { 590 | "version": "v5.0.8" 591 | }, 592 | "symfony/var-exporter": { 593 | "version": "v5.0.8" 594 | }, 595 | "symfony/web-link": { 596 | "version": "v5.0.8" 597 | }, 598 | "symfony/web-profiler-bundle": { 599 | "version": "3.3", 600 | "recipe": { 601 | "repo": "github.com/symfony/recipes", 602 | "branch": "master", 603 | "version": "3.3", 604 | "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6" 605 | }, 606 | "files": [ 607 | "config/packages/dev/web_profiler.yaml", 608 | "config/packages/test/web_profiler.yaml", 609 | "config/routes/dev/web_profiler.yaml" 610 | ] 611 | }, 612 | "symfony/yaml": { 613 | "version": "v5.0.8" 614 | }, 615 | "theseer/tokenizer": { 616 | "version": "1.2.1" 617 | }, 618 | "twig/extra-bundle": { 619 | "version": "v3.0.3" 620 | }, 621 | "twig/twig": { 622 | "version": "v3.0.3" 623 | }, 624 | "webimpress/safe-writer": { 625 | "version": "2.0.1" 626 | }, 627 | "webmozart/assert": { 628 | "version": "1.8.0" 629 | }, 630 | "willdurand/jsonp-callback-validator": { 631 | "version": "v1.1.0" 632 | }, 633 | "willdurand/negotiation": { 634 | "version": "v2.3.1" 635 | } 636 | } 637 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__).'/.env'); 11 | } 12 | -------------------------------------------------------------------------------- /translations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cap-Coding/symfony_api/f60c1e0d35c210ce24d2d1b84ce80235b3b0480a/translations/.gitignore --------------------------------------------------------------------------------