├── .env.dist ├── .github └── workflows │ └── documentation.yaml ├── .gitignore ├── .gitlab-ci.yml ├── Makefile ├── README.md ├── Vagrantfile.template ├── deployments └── README.md ├── docker-compose.yml ├── docs ├── .gitignore ├── README.md ├── babel.config.js ├── docs │ ├── 01_Get Started │ │ ├── 1_Overview.md │ │ └── 2_Installation.md │ ├── 02_Development Environment │ │ ├── 1_Docker Compose.md │ │ ├── 2_Vagrant.md │ │ └── 3_Housekeeping.md │ ├── 03_API │ │ ├── 1_Overview.md │ │ ├── 2_Configuration.md │ │ ├── 3_Composer.md │ │ └── 4_Housekeeping.md │ ├── 04_Web Application │ │ ├── 1_Overview.md │ │ ├── 2_Configuration.md │ │ ├── 3_Yarn.md │ │ └── 4_Housekeeping.md │ ├── 05_Database │ │ ├── 1_ORM.md │ │ ├── 2_Doctrine Migrations.md │ │ ├── 3_Models.md │ │ ├── 4_DAOs.md │ │ └── 5_Development Data.md │ ├── 06_GraphQL │ │ ├── 1_Overview.md │ │ ├── 2_API.md │ │ ├── 3_Web Application.md │ │ └── 4_Tools.md │ ├── 07_i18n │ │ ├── 1_Default Locale.md │ │ ├── 2_API.md │ │ ├── 3_Web Application.md │ │ ├── 4_API and Web Application Interactions.md │ │ └── 5_Disable i18n.md │ ├── 08_Security │ │ ├── 1_Overview.md │ │ ├── 2_Authentication.md │ │ ├── 3_Access Control.md │ │ └── 4_CORS.md │ ├── 09_Files │ │ ├── 1_Assets.md │ │ ├── 2_Temporary Files.md │ │ └── 3_Uploads.md │ ├── 10_Validation │ │ ├── 1_Overview.md │ │ ├── 2_Models.md │ │ ├── 3_Use Cases.md │ │ ├── 4_GraphQL.md │ │ └── 5_Forms.md │ ├── 11_Lists │ │ ├── 1_Overview.md │ │ ├── 2_Database.md │ │ ├── 3_GraphQL.md │ │ └── 4_Web Application.md │ ├── 12_Emails │ │ ├── 1_Overview.md │ │ ├── 2_Configuration.md │ │ ├── 3_Send Emails.md │ │ └── 4_Templates.md │ ├── 13_Static Analysis │ │ ├── 1_API.md │ │ └── 2_Web Application.md │ ├── 14_Tests │ │ ├── 1_API.md │ │ └── 2_Web Application.md │ ├── 15_Theme │ │ ├── 1_Customization.md │ │ └── 2_Layouts.md │ ├── 16_Deployments │ │ ├── 1_Overview.md │ │ ├── 2_Docker Images.md │ │ ├── 3_CICD.md │ │ ├── 4_Run.md │ │ └── 5_LogRocket.md │ └── 17_CHANGELOG.md ├── docusaurus.config.js ├── excalidraw │ ├── emails_big_picture.excalidraw │ └── uploaded_file_picture.json ├── package.json ├── sidebars.js ├── src │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.js │ │ └── styles.module.css ├── static │ ├── .nojekyll │ └── img │ │ ├── card_layout.png │ │ ├── dashboard_layout.png │ │ ├── default_layout.png │ │ ├── docker.svg │ │ ├── emails_big_picture.png │ │ ├── empty_layout.png │ │ ├── favicon.ico │ │ ├── graphqlite.svg │ │ ├── logo.svg │ │ ├── nuxtjs.svg │ │ ├── uploaded_file_big_picture.png │ │ └── users_page.png └── yarn.lock ├── scripts └── create-vagrantfile-from-template.sh └── src ├── api ├── .dockerignore ├── .env ├── .env.dist ├── .env.test ├── .gitignore ├── Dockerfile ├── README.md ├── assets │ └── css │ │ ├── emails.css │ │ └── foundation-emails.css ├── bin │ └── console ├── 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 │ │ ├── flysystem.yaml │ │ ├── framework.yaml │ │ ├── graphqlite.yaml │ │ ├── mailer.yaml │ │ ├── messenger.yaml │ │ ├── nelmio_cors.yaml │ │ ├── nyholm_psr7.yaml │ │ ├── prod │ │ │ ├── deprecations.yaml │ │ │ ├── doctrine.yaml │ │ │ ├── monolog.yaml │ │ │ └── routing.yaml │ │ ├── routing.yaml │ │ ├── security.yaml │ │ ├── sensio_framework_extra.yaml │ │ ├── tdbm.yaml │ │ ├── test │ │ │ ├── doctrine.yaml │ │ │ ├── framework.yaml │ │ │ ├── messenger.yaml │ │ │ ├── monolog.yaml │ │ │ ├── twig.yaml │ │ │ ├── validator.yaml │ │ │ └── web_profiler.yaml │ │ ├── translation.yaml │ │ ├── twig.yaml │ │ └── validator.yaml │ ├── preload.php │ ├── routes.yaml │ ├── routes │ │ ├── annotations.yaml │ │ ├── dev │ │ │ ├── framework.yaml │ │ │ ├── graphiql.yaml │ │ │ └── web_profiler.yaml │ │ └── graphqlite.yaml │ └── services.yaml ├── depfile.yaml ├── migrations │ ├── .gitignore │ ├── Version20200424093138.php │ └── Version20200424154558.php ├── phpcs.xml.dist ├── phpstan.neon ├── phpunit.xml.dist ├── public │ ├── .htaccess │ └── index.php ├── src │ ├── Domain │ │ ├── Constraint │ │ │ ├── IsResource.php │ │ │ ├── IsResourceValidator.php │ │ │ ├── Unicity.php │ │ │ └── UnicityValidator.php │ │ ├── Dao │ │ │ ├── DoctrineMigrationVersionDao.php │ │ │ ├── Generated │ │ │ │ ├── BaseDoctrineMigrationVersionDao.php │ │ │ │ ├── BaseResetPasswordTokenDao.php │ │ │ │ ├── BaseUserDao.php │ │ │ │ └── DaoFactory.php │ │ │ ├── ResetPasswordTokenDao.php │ │ │ └── UserDao.php │ │ ├── Enum │ │ │ ├── Filter │ │ │ │ ├── SortOrder.php │ │ │ │ └── UsersSortBy.php │ │ │ ├── Locale.php │ │ │ └── Role.php │ │ ├── Model │ │ │ ├── DoctrineMigrationVersion.php │ │ │ ├── Generated │ │ │ │ ├── BaseDoctrineMigrationVersion.php │ │ │ │ ├── BaseResetPasswordToken.php │ │ │ │ └── BaseUser.php │ │ │ ├── Proxy │ │ │ │ └── PasswordProxy.php │ │ │ ├── ResetPasswordToken.php │ │ │ ├── Storable │ │ │ │ ├── ProfilePicture.php │ │ │ │ └── Storable.php │ │ │ └── User.php │ │ ├── ResultIterator │ │ │ ├── DoctrineMigrationVersionResultIterator.php │ │ │ ├── Generated │ │ │ │ ├── AbstractDoctrineMigrationVersionResultIterator.php │ │ │ │ ├── AbstractResetPasswordTokenResultIterator.php │ │ │ │ └── AbstractUserResultIterator.php │ │ │ ├── ResetPasswordTokenResultIterator.php │ │ │ └── UserResultIterator.php │ │ ├── Storage │ │ │ ├── PrivateStorage.php │ │ │ ├── ProfilePictureStorage.php │ │ │ ├── PublicStorage.php │ │ │ └── Storage.php │ │ └── Throwable │ │ │ ├── BusinessRule.php │ │ │ ├── InvalidModel.php │ │ │ └── InvalidStorable.php │ ├── Infrastructure │ │ ├── Command │ │ │ ├── DevFixturesCommand.php │ │ │ └── InitializeS3StorageCommand.php │ │ ├── Controller │ │ │ ├── DownloadController.php │ │ │ ├── DownloadXLSXController.php │ │ │ ├── PingController.php │ │ │ └── User │ │ │ │ ├── UserProfilePictureController.php │ │ │ │ └── UsersXLSXExportController.php │ │ ├── EventListener │ │ │ └── NoBeanFoundExceptionListener.php │ │ ├── EventSubscriber │ │ │ └── LocaleSubscriber.php │ │ ├── Fixtures │ │ │ ├── AppFixtures.php │ │ │ ├── Fixtures.php │ │ │ └── picture.png │ │ ├── S3 │ │ │ ├── CreateBucket.php │ │ │ └── CreatePublicBucket.php │ │ └── Security │ │ │ ├── UserProvider.php │ │ │ └── Voter │ │ │ ├── AppVoter.php │ │ │ └── UserVoter.php │ ├── Kernel.php │ └── UseCase │ │ ├── AsyncTask.php │ │ ├── CreateEmail.php │ │ ├── CreateXLSXExport.php │ │ └── User │ │ ├── CreateUser.php │ │ ├── CreateUsersXLSXExport.php │ │ ├── DeleteUser.php │ │ ├── FailIfNotAuthenticated.php │ │ ├── GetUser.php │ │ ├── GetUsers.php │ │ ├── ResetPassword │ │ ├── CreateResetPasswordEmail.php │ │ └── ResetPassword.php │ │ ├── UpdateLocale.php │ │ ├── UpdatePassword.php │ │ ├── UpdateProfilePicture.php │ │ ├── UpdateUser.php │ │ └── VerifyResetPasswordToken │ │ ├── InvalidResetPasswordTokenId.php │ │ ├── ResetPasswordTokenExpired.php │ │ ├── VerifyResetPasswordToken.php │ │ └── WrongResetPasswordToken.php ├── symfony.lock ├── tdbm.lock.yml ├── templates │ ├── base.html.twig │ └── emails │ │ ├── email.html.twig │ │ ├── reset_password.html.twig │ │ └── welcome_new_user.html.twig ├── tests │ ├── FrontEnd │ │ └── LoginTest.php │ ├── Infrastructure │ │ └── GraphQlTest.php │ ├── Pest.php │ ├── UseCase │ │ ├── AsyncTransport.php │ │ ├── DummyValues.php │ │ ├── UseCaseTestCase.php │ │ └── User │ │ │ ├── CreateUserTest.php │ │ │ ├── DeleteUserTest.php │ │ │ ├── GetUserTest.php │ │ │ ├── GetUsersTest.php │ │ │ ├── ResetPasswordTest.php │ │ │ ├── UpdateLocaleTest.php │ │ │ ├── UpdatePasswordTest.php │ │ │ ├── UpdateProfilePictureTest.php │ │ │ ├── UpdateUserTest.php │ │ │ ├── VerifyResetPasswordTokenTest.php │ │ │ ├── foo.jpg │ │ │ ├── foo.png │ │ │ └── foo.txt │ └── bootstrap.php └── translations │ ├── .gitignore │ ├── emails.en.yaml │ ├── emails.fr.yaml │ ├── spreadsheets.en.yaml │ ├── spreadsheets.fr.yaml │ ├── validators.en.yaml │ └── validators.fr.yaml └── webapp ├── .dockerignore ├── .editorconfig ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── Dockerfile ├── README.md ├── assets ├── README.md ├── css │ ├── _variables.scss │ ├── layouts │ │ ├── dashboard.scss │ │ └── index.scss │ ├── main.scss │ └── overrides.scss └── images │ ├── default-profile-picture-white.svg │ ├── default-profile-picture.svg │ └── logo.svg ├── components ├── README.md ├── forms │ ├── ConfirmDelete.vue │ ├── ErrorsList.vue │ └── FilesList.vue └── layouts │ ├── Header.vue │ └── LeftMenu.vue ├── cypress.config.js ├── cypress └── e2e │ ├── createUser.cy.js │ ├── login.cy.js │ ├── logout.cy.js │ └── passwordReset.cy.js ├── enums ├── filters │ ├── sort-order.js │ └── users-sort-by.js ├── locales.js └── roles.js ├── graphql ├── auth │ ├── fail_if_not_authenticated.query.js │ ├── login.mutation.js │ ├── logout.mutation.js │ ├── me.fragment.js │ ├── me.query.js │ ├── reset_password.mutation.js │ ├── update_locale.mutation.js │ ├── update_password.mutation.js │ └── verify_reset_password_token.mutation.js └── users │ ├── create_user.mutation.js │ ├── delete_user.mutation.js │ ├── update_profile_picture.mutation.js │ ├── update_user.mutation.js │ ├── user.query.js │ └── users.query.js ├── jsconfig.json ├── layouts ├── README.md ├── card.vue ├── dashboard.vue ├── default.vue ├── empty.vue └── error.vue ├── locales ├── en.js └── fr.js ├── middleware ├── README.md ├── redirect-if-authenticated.js └── redirect-if-not-authenticated.js ├── mixins ├── auth.js ├── form.js ├── generic-toast.js ├── global-overlay.js ├── images.js ├── list.js ├── locales.js └── roles.js ├── nuxt.config.js ├── package-lock.json ├── package.json ├── pages ├── README.md ├── dashboard │ ├── admin │ │ └── users │ │ │ ├── _id.vue │ │ │ ├── create.vue │ │ │ └── index.vue │ ├── index.vue │ └── my-profile.vue ├── index.vue ├── login.vue ├── reset-password.vue └── update-password │ └── _id │ └── _token │ └── index.vue ├── plugins ├── README.md ├── i18n.js └── scroll-to-top.client.js ├── static ├── README.md └── favicon.ico ├── store ├── README.md ├── actions.js ├── auth │ ├── actions.js │ ├── getters.js │ ├── mutations.js │ └── state.js └── global-overlay │ ├── getters.js │ ├── mutations.js │ └── state.js ├── stylelint.config.js └── yarn.lock /.env.dist: -------------------------------------------------------------------------------- 1 | # Vagrant 2 | VAGRANT_BOX=bento/ubuntu-20.04 3 | VAGRANT_PROJECT_NAME=symfony-boilerplate 4 | VAGRANT_MEMORY=4096 5 | VAGRANT_CPUS=2 6 | VAGRANT_DOCKER_COMPOSE_VERSION=1.28.2 7 | 8 | # The next variables help templating the docker-compose.yml file. 9 | # 10 | # Consider putting here your secrets with dummy values and variables you want to 11 | # reuse in two or more services. 12 | 13 | APP_NAME="Symfony Boilerplate" 14 | 15 | # Domain 16 | DOMAIN=symfony-boilerplate.localhost 17 | API_SUBDOMAIN=api 18 | STORAGE_SUBDOMAIN=minio 19 | 20 | # API 21 | APP_SECRET=$ecretf0rt3st 22 | 23 | # MySQL 24 | MYSQL_ROOT_PASSWORD=super-secret 25 | MYSQL_DATABASE=symfony-boilerplate 26 | MYSQL_USER=api-agent 27 | MYSQL_PASSWORD=secret 28 | 29 | # Redis 30 | REDIS_PASSWORD=secret 31 | 32 | # Storage 33 | STORAGE_PUBLIC_BUCKET=public 34 | STORAGE_PRIVATE_BUCKET=private 35 | STORAGE_ACCESS_KEY=accesskey 36 | STORAGE_SECRET_KEY=secretkey 37 | 38 | # i18n 39 | DEFAULT_LOCALE=en 40 | 41 | # XDebug 42 | XDEBUG_MODE=debug,coverage -------------------------------------------------------------------------------- /.github/workflows/documentation.yaml: -------------------------------------------------------------------------------- 1 | name: documentation 2 | 3 | on: 4 | pull_request: 5 | branches: [master] 6 | push: 7 | branches: [master] 8 | 9 | jobs: 10 | checks: 11 | if: github.event_name != 'push' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: '12.x' 18 | - name: Test Build 19 | run: | 20 | cd docs 21 | if [ -e yarn.lock ]; then 22 | yarn install --frozen-lockfile 23 | elif [ -e package-lock.json ]; then 24 | npm ci 25 | else 26 | npm i 27 | fi 28 | npm run build 29 | gh-release: 30 | if: github.event_name != 'pull_request' 31 | runs-on: ubuntu-latest 32 | steps: 33 | - uses: actions/checkout@v1 34 | - uses: actions/setup-node@v1 35 | with: 36 | node-version: '12.x' 37 | - name: Add key to allow access to repository 38 | env: 39 | SSH_AUTH_SOCK: /tmp/ssh_agent.sock 40 | run: | 41 | mkdir -p ~/.ssh 42 | ssh-keyscan github.com >> ~/.ssh/known_hosts 43 | echo "${{ secrets.GH_PAGES_DEPLOY }}" > ~/.ssh/id_rsa 44 | chmod 600 ~/.ssh/id_rsa 45 | cat <> ~/.ssh/config 46 | Host github.com 47 | HostName github.com 48 | IdentityFile ~/.ssh/id_rsa 49 | EOT 50 | - name: Release to GitHub Pages 51 | env: 52 | USE_SSH: true 53 | GIT_USER: git 54 | run: | 55 | cd docs 56 | git config --global user.email "actions@gihub.com" 57 | git config --global user.name "gh-actions" 58 | if [ -e yarn.lock ]; then 59 | yarn install --frozen-lockfile 60 | elif [ -e package-lock.json ]; then 61 | npm ci 62 | else 63 | npm i 64 | fi 65 | npx docusaurus deploy -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Vagrantfile 2 | /.env -------------------------------------------------------------------------------- /deployments/README.md: -------------------------------------------------------------------------------- 1 | > Put here your deployments' guidelines. -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://v2.docusaurus.io/), a modern static website generator. 4 | 5 | ## Installation 6 | 7 | ```console 8 | yarn install 9 | ``` 10 | 11 | ## Local Development 12 | 13 | ```console 14 | yarn start 15 | ``` 16 | 17 | This command starts a local development server and open up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ## Build 20 | 21 | ```console 22 | yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ## Deployment 28 | 29 | ```console 30 | GIT_USER= USE_SSH=true yarn deploy 31 | ``` 32 | 33 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 34 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/01_Get Started/1_Overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | slug: / 4 | --- 5 | 6 | The Symfony Boilerplate provides a dummy application with core concepts and functionalities to help you build 7 | a modern web application. 8 | 9 | ![Users page](/img/users_page.png) 10 | 11 | Many services compose this boilerplate. 12 | 13 | ## Application Layer 14 | 15 | This layer has two services: 16 | 17 | 1. The GraphQL API, built with [Symfony 5](https://symfony.com/), [TDBM](https://github.com/thecodingmachine/tdbm), and 18 | [GraphQLite](https://graphqlite.thecodingmachine.io/). 19 | 2. The Web Application, built with [Nuxt.js](https://nuxtjs.org). 20 | 21 | ## Data Layer 22 | 23 | This layer has 3 services: 24 | 25 | 1. MySQL for storing sessions and business data. 26 | 2. Redis for queueing asynchronous tasks (e.g., emails). 27 | 3. [MinIO](https://min.io/) for storing files (e.g., uploads). 28 | 29 | :::note 30 | 31 | 📣  In production, you may externalize them to the equivalent services from your provider. 32 | 33 | ::: 34 | 35 | ## Additional Services 36 | 37 | These services are mostly useful in development: 38 | 39 | 1. [Traefik](https://doc.traefik.io/traefik/), a reverse proxy. 40 | 2. [MailHog](https://github.com/mailhog/MailHog), an email trapper with a web UI. 41 | 3. phpMyAdmin, a web UI for displaying your database's data. 42 | 43 | :::note 44 | 45 | 📣  In production, you may externalize them to the equivalent services from your provider. 46 | 47 | ::: -------------------------------------------------------------------------------- /docs/docs/01_Get Started/2_Installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Installation 3 | slug: /installation 4 | --- 5 | 6 | Create your Git repository on the platform of your choice, then add a new remote to it: 7 | 8 | ```bash title="console" 9 | git remote add boilerplate https://github.com/thecodingmachine/symfony-boilerplate.git 10 | ``` 11 | 12 | Finally, pull the source code from a [release](https://github.com/thecodingmachine/symfony-boilerplate/tags) to 13 | your current branch: 14 | 15 | ```bash title="console" 16 | git pull boilerplate [TAG] 17 | ``` 18 | 19 | You may now follow the instructions of the *README*! 😋 20 | 21 | :::note 22 | 23 | 📣  There are comments on the *README* to help you customize its content. 24 | 25 | ::: 26 | 27 | :::note 28 | 29 | 📣  If you don't need it, you should remove the *.github* folder. 30 | 31 | ::: 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /docs/docs/02_Development Environment/2_Vagrant.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vagrant 3 | slug: /development-environment/vagrant 4 | --- 5 | 6 | On macOS and Windows, Docker currently has substantial performance issues. 7 | 8 | [Vagrant](https://www.vagrantup.com/) will help you to have an almost Linux-like experience regarding performances. 9 | 10 | ## Usage 11 | 12 | * 📦  `make vagrant` - creates the *Vagrantfile*. 13 | * 🚀  `vagrant up` - installs and starts the virtual machine. 14 | * 🚇  `vagrant ssh` - connects to the virtual machine. 15 | * 🚦  `vagrant halt` - stops the virtual machine. 16 | * 💣  `vagrant destroy` - destroys the virtual machine. 17 | 18 | :::note 19 | 20 | 📣  On Windows, consider using a Linux-like terminal to run the `make vagrant` command. 21 | 22 | ::: 23 | 24 | :::note 25 | 26 | 📣  With `vagrant ssh`, you may run the *Makefile*'s instructions like a Linux user. 27 | 28 | ::: 29 | 30 | ## Configuration 31 | 32 | In the *.env* file contains the following variables for Vagrant: 33 | 34 | * `VAGRANT_BOX` - the VM to use. 35 | * `VAGRANT_PROJECT_NAME` - the project name: only use alphanumeric characters (no spaces, distinguish words with `_` or `-`). 36 | * `VAGRANT_MEMORY` - the memory to allocate to the VM. 37 | * `VAGRANT_CPUS` - the CPUs to allocate to the VM. 38 | * `VAGRANT_DOCKER_COMPOSE_VERSION` - the version of Docker Compose to use. 39 | 40 | The command `make vagrant` reads these variables and uses them as arguments 41 | when calling the *scripts/create-vagrantfile-from-template.sh* script. 42 | 43 | This script replaces placeholders from the *Vagrantfile.template* by the variables' values and creates a new *Vagrantfile*. 44 | 45 | :::note 46 | 47 | 📣  You should never commit the *Vagrantfile*. 48 | 49 | ::: -------------------------------------------------------------------------------- /docs/docs/03_API/3_Composer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Composer 3 | slug: /api/composer 4 | --- 5 | 6 | :::note 7 | 8 | 📣  All commands have to be run in the `api` service (`make api`). 9 | 10 | ::: 11 | 12 | When installing a PHP dependency, ask yourself if it is a `dev` dependency or not: 13 | 14 | ``` 15 | composer require [--dev] [package] 16 | COMPOSER_MEMORY_LIMIT=-1 composer normalize 17 | ``` 18 | 19 | As we're using Symfony, make sure to choose the package with Symfony support (aka bundle) if available. 20 | 21 | :::note 22 | 23 | 📣  Vagrant users might encounter some issues with Composer. 24 | A workaround solution is to add the flag `--prefer-source` to your Composer command. 25 | 26 | ::: -------------------------------------------------------------------------------- /docs/docs/03_API/4_Housekeeping.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Housekeeping 3 | slug: /api/housekeeping 4 | --- 5 | 6 | 👉  The sooner, the better! 7 | 8 | ## Symfony 9 | 10 | You should update to the latest minor/majors version whenever a new version is available. 11 | 12 | See [Symfony releases](https://symfony.com/releases). 13 | 14 | ### Minor Versions 15 | 16 | See https://symfony.com/doc/current/setup/upgrade_minor.html. 17 | 18 | ### Major Versions 19 | 20 | See https://symfony.com/doc/current/setup/upgrade_major.html. 21 | 22 | :::note 23 | 24 | 📣  Make sure your Symfony bundles are ready before updating. 25 | 26 | ::: 27 | 28 | ## Composer 29 | 30 | To show the list of direct outdated dependencies, run: 31 | 32 | ```bash title="console" 33 | composer outdated --direct 34 | ``` -------------------------------------------------------------------------------- /docs/docs/04_Web Application/1_Overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | slug: /webapp 4 | --- 5 | 6 | We built the web application with [Nuxt.js](https://nuxtjs.org). 7 | 8 | The web application is the frontend of the boilerplate. 9 | 10 | While you may find most of the information in the [official documentation](https://nuxtjs.org/docs/2.x/get-started/installation), 11 | it is essential to note that: 12 | 13 | * Most of the logic (use cases, validations, access control) occurs in the API. 14 | * The web application's primary goal is literally to be a UI for the API. -------------------------------------------------------------------------------- /docs/docs/04_Web Application/2_Configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Configuration 3 | slug: /webapp/configuration 4 | --- 5 | 6 | General documentation on how to configure Nuxt.js. More details are available 7 | in the [official documentation](https://nuxtjs.org/docs/2.x/configuration-glossary/configuration-build). 8 | 9 | ### nuxt.config.js 10 | 11 | The *src/webapp/nuxt.config.js* file contains the configuration of Nuxt.js. 12 | 13 | You may use environment variables in this file. They are available through 14 | the instruction `process.env.YOUR_ENVIRONMENT_VARIABLE_NAME`. 15 | 16 | :::note 17 | 18 | 📣  Put them under the `environment` property of your `webapp` service in your *docker-compose.yml* file. 19 | 20 | ::: 21 | 22 | If you need the value of an environment variable in your code, put it under the `publicRuntimeConfig` or 23 | `privateRuntimeConfig` section of the *nuxt.config.js* file. 24 | 25 | For instance: 26 | 27 | ```js title="src/webapp/nuxt.config.js" 28 | publicRuntimeConfig: { 29 | apiURL: process.env.API_URL, 30 | } 31 | ``` 32 | 33 | The value is available in your code thanks to `this.$config.apiURL` (or `$config.apiURL` in your `template` blocks). 34 | 35 | :::note 36 | 37 | 📣  `privateRuntimeConfig` should contain your secrets. Values from 38 | this section **are only available when Nuxt.js executes your code on the server.** 39 | 40 | ::: -------------------------------------------------------------------------------- /docs/docs/04_Web Application/3_Yarn.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Yarn 3 | slug: /webapp/yarn 4 | --- 5 | 6 | :::note 7 | 8 | 📣  All commands have to be run in the `webapp` service (`make webapp`). 9 | 10 | ::: 11 | 12 | We recommend using [Yarn](https://yarnpkg.com/) as package manager. 13 | 14 | When installing a JavaScript dependency, ask yourself if it is a `dev` dependency or not: 15 | 16 | ``` 17 | yarn add [package] [--dev] 18 | ``` 19 | 20 | As we're using Nuxt.js, make sure to choose the package with Nuxt.js support (aka module) if available. 21 | 22 | ## Hot Reloading 23 | 24 | In your development environment, the `webapp` service run the command `yarn dev`. 25 | 26 | This command watch for file changes, recompile those files and automatically refresh your browser. 27 | 28 | This command may also show ESLint errors and warnings; you can fix them using `yarn lint:fix`. 29 | 30 | :::note 31 | 32 | 📣  You may also configure your IDE to fix the ESLint errors automatically. 33 | 34 | ::: -------------------------------------------------------------------------------- /docs/docs/04_Web Application/4_Housekeeping.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Housekeeping 3 | slug: /webapp/housekeeping 4 | --- 5 | 6 | 👉  The sooner, the better! 7 | 8 | ## Nuxt.js 9 | 10 | From time to time: 11 | 12 | 1. Remove the file *src/webapp/yarn.lock* and the folder *src/webapp/node_modules*. 13 | 2. Recreate the `webapp` service with `docker-compose up -d --force webapp`. 14 | 3. (optional) Run `yarn outdated` for additional packages to update. 15 | 4. (optional) Update their versions in the *src/webapp/package.json* file. 16 | 5. Repeat! 😄 -------------------------------------------------------------------------------- /docs/docs/05_Database/1_ORM.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ORM 3 | slug: /database/ORM 4 | --- 5 | 6 | The boilerplate uses [TDBM](https://github.com/thecodingmachine/tdbm) as ORM. 7 | It is an alternative to Doctrine or Eloquent, yet it is pretty close. 8 | 9 | The main difference is that with [TDBM](https://github.com/thecodingmachine/tdbm), 10 | you write your database's tables first before generating your models. 11 | -------------------------------------------------------------------------------- /docs/docs/05_Database/3_Models.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Models 3 | slug: /database/models 4 | --- 5 | 6 | Models are the PHP representation of your database's tables. 7 | 8 | :::note 9 | 10 | 📣  All commands have to be run in the `api` service (`make api`). 11 | 12 | ::: 13 | 14 | ## Generate 15 | 16 | ```bash title="console" 17 | php bin/console tdbm:generate 18 | ``` 19 | 20 | This command will regenerate the [TDBM](https://github.com/thecodingmachine/tdbm) 21 | models (and DAOs - more on that in the next chapter). 22 | 23 | Models come in two kinds of classes. For instance: 24 | 25 | * `BaseUser`. 26 | * `User` that extends `BaseUser`. 27 | 28 | [TDBM](https://github.com/thecodingmachine/tdbm) generates the first class, and it contains the default getters and setters. 29 | 30 | You cannot modify it, but instead, edit the second class as [TDBM](https://github.com/thecodingmachine/tdbm) 31 | does not override it. 32 | 33 | :::note 34 | 35 | 📣  There are other kinds of classes in the *src/api/src/Domain/Model* folder, but 36 | they are not related to [TDBM](https://github.com/thecodingmachine/tdbm). 37 | 38 | ::: 39 | 40 | ## Create an instance 41 | 42 | Let's say you have a model `Foo` with the following properties: 43 | 44 | * `bar`, non-nullable. 45 | * `baz`, nullable. 46 | 47 | ```php 48 | $foo = new Foo($bar); 49 | $foo->setBaz($baz); 50 | ``` 51 | 52 | :::note 53 | 54 | 📣  A constructor of a model requires non-nullable values. 55 | 56 | ::: 57 | -------------------------------------------------------------------------------- /docs/docs/05_Database/5_Development Data.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Development Data 3 | slug: /database/development-data 4 | --- 5 | 6 | :::note 7 | 8 | 📣  All commands have to be run in the `api` service (`make api`). 9 | 10 | ::: 11 | 12 | The `DevFixturesCommand` class provides a Symfony command for initializing your 13 | development database with dummy data: 14 | 15 | ```bash title="console" 16 | php bin/console app:fixtures:dev 17 | ``` 18 | 19 | It uses the class `AppFixtures` for that task. You should edit it according to your needs. -------------------------------------------------------------------------------- /docs/docs/06_GraphQL/1_Overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | slug: /graphql 4 | --- 5 | 6 | > GraphQL is a query language for APIs and a runtime for fulfilling those queries with your existing data. 7 | > GraphQL provides a complete and understandable description of the data in your API, gives clients the power to ask 8 | > for exactly what they need and nothing more, makes it easier to evolve APIs over time, and enables powerful 9 | > developer tools. 10 | > 11 | > -- [graphql.org](https://graphql.org/) 12 | 13 | 14 | If you're new to GraphQL, consider reading the [official documentation](https://graphql.org/) for a better understanding 15 | of the concepts. -------------------------------------------------------------------------------- /docs/docs/06_GraphQL/3_Web Application.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Web Application 3 | slug: /graphql/web-application 4 | --- 5 | 6 | The Symfony Boilerplate uses [graphql-request](https://github.com/prisma-labs/graphql-request) client. 7 | 8 | It is available in a Vue component thanks to `this.$graphql`. 9 | 10 | Queries and mutations are JavaScript files. For instance: 11 | 12 | ```js title="src/webapp/graphql/auth/me.query.js" 13 | import { gql } from 'graphql-request' 14 | import { MeFragment } from '@/graphql/auth/me.fragment' 15 | 16 | export const MeQuery = gql` 17 | query me { 18 | me { 19 | ... on User { 20 | ...MeFragment 21 | } 22 | } 23 | } 24 | ${MeFragment} 25 | ` 26 | ``` 27 | 28 | :::note 29 | 30 | 📣  A fragment is useful is you want to fetch the same data in many queries and mutations. 31 | 32 | ::: -------------------------------------------------------------------------------- /docs/docs/06_GraphQL/4_Tools.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tools 3 | slug: /graphql/tools 4 | --- 5 | 6 | The API provides the endpoint `/graphiql` (in development) so that you may quickly test a query or a mutation. 7 | 8 | You may also use the [Altair GraphQL Client](https://altair.sirmuel.design/). -------------------------------------------------------------------------------- /docs/docs/07_i18n/1_Default Locale.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Default Locale 3 | slug: /i18n/default-locale 4 | --- 5 | 6 | Both the `webapp` and `api` services read the `DEFAULT_LOCALE` environment variable. 7 | 8 | Its value comes from the root *.env* file. 9 | 10 | If you update this value, you will have to restart these services (locally by doing `make down up`). 11 | 12 | :::note 13 | 14 | 📣  Don't forget to update the file *.env.dist* if this change is definitive. 15 | 16 | ::: -------------------------------------------------------------------------------- /docs/docs/07_i18n/4_API and Web Application Interactions.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API and Web Application Interactions 3 | slug: /i18n/api-and-web-application-interactions 4 | --- 5 | 6 | There are three ways for communicating the user locale from the web application to the API: 7 | 8 | 1. Requests with `Accept-Language` HTTP header for validation error messages. 9 | 2. Query parameters when you cannot set the previous HTTP header (links, for instance). 10 | 3. Update the user's locale via a GraphQL request if authenticated. 11 | 4. Update the user's locale on login if the web application locale is not the same as the locale from the API 12 | 13 | ## HTTP Header 14 | 15 | Use case: translating the validation error messages. 16 | 17 | Each time the user changes its locale on the web application, the *src/webapp/plugins/i18n.js* plugin will 18 | update the HTTP header `Accept-Language` for the next GraphQL requests with the new value. 19 | 20 | The `LocaleSubscriber` class from the API reads the value of this HTTP header to set the locale on its side. 21 | 22 | ## Query Parameters 23 | 24 | In some use cases, you cannot set an HTTP header. For instance, when the user clicks on a link, you will have to use 25 | query parameters: 26 | 27 | ``` 28 | http://foo.bar/?locale=fr 29 | ``` 30 | 31 | In the Symfony Boilerplate, we use this solution for XLSX exports. 32 | 33 | ## Authenticated user's locale 34 | 35 | As explained before, whenever an authenticated user changes the locale on the web application, we run the 36 | `updateLocale` GraphQL mutation. 37 | 38 | In the API, the `UpdateLocale` use case updates the `locale` property of this user. 39 | 40 | :::note 41 | 42 | 📣  This property helps to know in which locale the API has to translate emails for this user. 43 | 44 | ::: 45 | 46 | We also call the `updateLocale` GraphQL mutation on page *src/webapp/pages/login.vue* 47 | in the specific scenario where the web application locale is not the same as the user's locale from the API. -------------------------------------------------------------------------------- /docs/docs/07_i18n/5_Disable i18n.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Disable i18n 3 | slug: /i18n/disable-i18n 4 | --- 5 | 6 | Consider not disabling i18n as you may need it in the future. 7 | 8 | Moreover, it's often a good practice to centralize your application texts. 9 | 10 | If you only need one locale, you may remove the web application's locale selection (*src/webapp/components/layouts/Header.vue*) 11 | and set the `DEFAULT_LOCALE` environment variable with your unique locale 12 | (see [default locale](/docs/i18n/default-locale) chapter). -------------------------------------------------------------------------------- /docs/docs/08_Security/1_Overview.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Overview 3 | slug: /security 4 | --- 5 | 6 | The Symfony Boilerplate centralizes the security in the API. 7 | This approach has many benefits: 8 | 9 | * No need to rewrite the security logic for all your front-ends. 10 | * The web application focus on UI logic, not business logic. 11 | 12 | In the next chapters, we explain how it works and how you should implement it. -------------------------------------------------------------------------------- /docs/docs/08_Security/4_CORS.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CORS 3 | slug: /security/CORS 4 | --- 5 | 6 | CORS is the mechanism defining what can interact with the API via HTTP requests. 7 | 8 | The application uses the [nelmio/cors-bundle](https://github.com/nelmio/NelmioCorsBundle) package for that task. 9 | We configured this bundle in the configuration file *src/api/config/packages/nelmio_cors.yaml*. 10 | 11 | The current configuration only authorizes HTTP requests from the main domain (and the API subdomain). 12 | 13 | :::note 14 | 15 | 📣  Never use `*` as `CORS_ALLOW_ORIGIN` because it opens your API to the world. As there is no CSRF protection, a 16 | malicious hacker will be able to hijack the connexion of one of your authenticated users to do bad things. Also, make sure 17 | you don't have XSS vulnerabilities. 18 | 19 | ::: -------------------------------------------------------------------------------- /docs/docs/09_Files/1_Assets.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Assets 3 | slug: /files/assets 4 | --- 5 | 6 | The application bundles assets, i.e., files like images, CSS, PDFs, etc. 7 | 8 | ## Web Application 9 | 10 | The *src/webapp/assets* folder contains these static files. The Nuxt.js server serves them to the users. 11 | 12 | For instance, if you want to display an image to your users: 13 | 14 | ```html title="Vue component