├── .gitignore
├── sources
└── app
│ ├── tests
│ ├── .gitignore
│ ├── bootstrap.php
│ └── Controller
│ │ ├── AbstractControllerWebTestCase.php
│ │ └── PostControllerTest.php
│ ├── src
│ ├── Entity
│ │ ├── .gitignore
│ │ ├── Post.php
│ │ └── User.php
│ ├── Controller
│ │ ├── .gitignore
│ │ ├── IndexController.php
│ │ ├── SecurityController.php
│ │ └── PostController.php
│ ├── Migrations
│ │ ├── .gitignore
│ │ ├── Version20190618154659.php
│ │ ├── Version20190618080327.php
│ │ └── Version20190618152017.php
│ ├── Repository
│ │ └── .gitignore
│ ├── DataFixtures
│ │ ├── AppFixtures.php
│ │ └── UserFixtures.php
│ ├── Exception
│ │ └── HTTPExceptionListener.php
│ ├── Security
│ │ └── HashPasswordListener.php
│ └── Kernel.php
│ ├── translations
│ └── .gitignore
│ ├── config
│ ├── packages
│ │ ├── test
│ │ │ ├── swiftmailer.yaml
│ │ │ ├── routing.yaml
│ │ │ ├── validator.yaml
│ │ │ ├── framework.yaml
│ │ │ ├── web_profiler.yaml
│ │ │ └── monolog.yaml
│ │ ├── dev
│ │ │ ├── routing.yaml
│ │ │ ├── web_profiler.yaml
│ │ │ ├── swiftmailer.yaml
│ │ │ ├── debug.yaml
│ │ │ ├── easy_log_handler.yaml
│ │ │ └── monolog.yaml
│ │ ├── swiftmailer.yaml
│ │ ├── routing.yaml
│ │ ├── sensio_framework_extra.yaml
│ │ ├── assets.yaml
│ │ ├── ramsey_uuid_doctrine.yaml
│ │ ├── twig.yaml
│ │ ├── prod
│ │ │ ├── webpack_encore.yaml
│ │ │ ├── monolog.yaml
│ │ │ └── doctrine.yaml
│ │ ├── translation.yaml
│ │ ├── doctrine_migrations.yaml
│ │ ├── validator.yaml
│ │ ├── fos_rest.yaml
│ │ ├── framework.yaml
│ │ ├── cache.yaml
│ │ ├── doctrine.yaml
│ │ ├── webpack_encore.yaml
│ │ └── security.yaml
│ ├── routes.yaml
│ ├── routes
│ │ ├── annotations.yaml
│ │ └── dev
│ │ │ ├── twig.yaml
│ │ │ └── web_profiler.yaml
│ ├── bootstrap.php
│ ├── bundles.php
│ └── services.yaml
│ ├── .env.test
│ ├── assets
│ └── vue
│ │ ├── api
│ │ ├── security.js
│ │ └── post.js
│ │ ├── index.js
│ │ ├── store
│ │ ├── index.js
│ │ ├── security.js
│ │ └── post.js
│ │ ├── views
│ │ ├── Home.vue
│ │ ├── Posts.vue
│ │ └── Login.vue
│ │ ├── components
│ │ ├── Post.vue
│ │ └── ErrorMessage.vue
│ │ ├── router
│ │ └── index.js
│ │ └── App.vue
│ ├── phpstan.neon
│ ├── .eslintrc.json
│ ├── bin
│ ├── phpunit
│ └── console
│ ├── .gitignore
│ ├── public
│ ├── index.php
│ └── .htaccess
│ ├── package.json
│ ├── templates
│ └── base.html.twig
│ ├── phpunit.xml.dist
│ ├── phpcs.xml.dist
│ ├── webpack.config.js
│ ├── composer.json
│ └── symfony.lock
├── .env.template
├── services
└── mysql
│ └── utf8mb4.cnf
├── README.md
├── LICENSE
└── docker-compose.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
--------------------------------------------------------------------------------
/sources/app/tests/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sources/app/src/Entity/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sources/app/src/Controller/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sources/app/src/Migrations/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sources/app/src/Repository/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sources/app/translations/.gitignore:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sources/app/config/packages/test/swiftmailer.yaml:
--------------------------------------------------------------------------------
1 | swiftmailer:
2 | disable_delivery: true
3 |
--------------------------------------------------------------------------------
/.env.template:
--------------------------------------------------------------------------------
1 | MYSQL_ROOT_PASSWORD=admin
2 | MYSQL_DATABASE=tutorial
3 | MYSQL_USER=foo
4 | MYSQL_PASSWORD=bar
--------------------------------------------------------------------------------
/sources/app/config/packages/dev/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: true
4 |
--------------------------------------------------------------------------------
/sources/app/config/packages/test/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: true
4 |
--------------------------------------------------------------------------------
/sources/app/config/routes.yaml:
--------------------------------------------------------------------------------
1 | #index:
2 | # path: /
3 | # controller: App\Controller\DefaultController::index
4 |
--------------------------------------------------------------------------------
/sources/app/config/packages/swiftmailer.yaml:
--------------------------------------------------------------------------------
1 | swiftmailer:
2 | url: '%env(MAILER_URL)%'
3 | spool: { type: 'memory' }
4 |
--------------------------------------------------------------------------------
/sources/app/config/packages/test/validator.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | validation:
3 | not_compromised_password: false
4 |
--------------------------------------------------------------------------------
/sources/app/config/routes/annotations.yaml:
--------------------------------------------------------------------------------
1 | controllers:
2 | resource: ../../src/Controller/
3 | type: annotation
4 |
--------------------------------------------------------------------------------
/sources/app/config/packages/routing.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | router:
3 | strict_requirements: ~
4 | utf8: true
5 |
--------------------------------------------------------------------------------
/sources/app/config/packages/sensio_framework_extra.yaml:
--------------------------------------------------------------------------------
1 | sensio_framework_extra:
2 | router:
3 | annotations: false
4 |
--------------------------------------------------------------------------------
/sources/app/config/packages/test/framework.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | test: true
3 | session:
4 | storage_id: session.storage.mock_file
5 |
--------------------------------------------------------------------------------
/sources/app/config/routes/dev/twig.yaml:
--------------------------------------------------------------------------------
1 | _errors:
2 | resource: '@TwigBundle/Resources/config/routing/errors.xml'
3 | prefix: /_error
4 |
--------------------------------------------------------------------------------
/sources/app/config/packages/assets.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | assets:
3 | json_manifest_path: '%kernel.project_dir%/public/build/manifest.json'
4 |
--------------------------------------------------------------------------------
/sources/app/config/packages/ramsey_uuid_doctrine.yaml:
--------------------------------------------------------------------------------
1 | doctrine:
2 | dbal:
3 | types:
4 | uuid: 'Ramsey\Uuid\Doctrine\UuidType'
5 |
--------------------------------------------------------------------------------
/sources/app/.env.test:
--------------------------------------------------------------------------------
1 | # define your env variables for the test env here
2 | KERNEL_CLASS='App\Kernel'
3 | APP_SECRET='s$cretf0rt3st'
4 | SYMFONY_DEPRECATIONS_HELPER=999999
5 |
--------------------------------------------------------------------------------
/sources/app/config/packages/twig.yaml:
--------------------------------------------------------------------------------
1 | twig:
2 | default_path: '%kernel.project_dir%/templates'
3 | debug: '%kernel.debug%'
4 | strict_variables: '%kernel.debug%'
5 |
--------------------------------------------------------------------------------
/sources/app/config/packages/test/web_profiler.yaml:
--------------------------------------------------------------------------------
1 | web_profiler:
2 | toolbar: false
3 | intercept_redirects: false
4 |
5 | framework:
6 | profiler: { collect: false }
7 |
--------------------------------------------------------------------------------
/sources/app/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 |
--------------------------------------------------------------------------------
/sources/app/config/packages/prod/webpack_encore.yaml:
--------------------------------------------------------------------------------
1 | #webpack_encore:
2 | # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes)
3 | # Available in version 1.2
4 | #cache: true
5 |
--------------------------------------------------------------------------------
/sources/app/config/packages/translation.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | default_locale: '%locale%'
3 | translator:
4 | default_path: '%kernel.project_dir%/translations'
5 | fallbacks:
6 | - '%locale%'
7 |
--------------------------------------------------------------------------------
/sources/app/config/packages/dev/swiftmailer.yaml:
--------------------------------------------------------------------------------
1 | # See https://symfony.com/doc/current/email/dev_environment.html
2 | swiftmailer:
3 | # send all emails to a specific address
4 | #delivery_addresses: ['me@example.com']
5 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/api/security.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export default {
4 | login(login, password) {
5 | return axios.post("/api/security/login", {
6 | username: login,
7 | password: password
8 | });
9 | }
10 | }
--------------------------------------------------------------------------------
/services/mysql/utf8mb4.cnf:
--------------------------------------------------------------------------------
1 | [client]
2 | default-character-set = utf8mb4
3 |
4 | [mysql]
5 | default-character-set = utf8mb4
6 |
7 | [mysqld]
8 | character-set-client-handshake = FALSE
9 | character-set-server = utf8mb4
10 | collation-server = utf8mb4_unicode_ci
--------------------------------------------------------------------------------
/sources/app/assets/vue/api/post.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 |
3 | export default {
4 | create(message) {
5 | return axios.post("/api/posts", {
6 | message: message
7 | });
8 | },
9 | findAll() {
10 | return axios.get("/api/posts");
11 | }
12 | };
--------------------------------------------------------------------------------
/sources/app/assets/vue/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import App from "./App";
3 | import router from "./router";
4 | import store from "./store";
5 |
6 | new Vue({
7 | components: { App },
8 | template: "",
9 | router,
10 | store
11 | }).$mount("#app");
12 |
--------------------------------------------------------------------------------
/sources/app/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 |
--------------------------------------------------------------------------------
/sources/app/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 |
--------------------------------------------------------------------------------
/sources/app/config/packages/doctrine_migrations.yaml:
--------------------------------------------------------------------------------
1 | doctrine_migrations:
2 | dir_name: '%kernel.project_dir%/src/Migrations'
3 | # namespace is arbitrary but should be different from App\Migrations
4 | # as migrations classes should NOT be autoloaded
5 | namespace: DoctrineMigrations
6 |
--------------------------------------------------------------------------------
/sources/app/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 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/store/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import Vuex from "vuex";
3 | import SecurityModule from "./security";
4 | import PostModule from "./post";
5 |
6 | Vue.use(Vuex);
7 |
8 | export default new Vuex.Store({
9 | modules: {
10 | security: SecurityModule,
11 | post: PostModule
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/sources/app/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon
3 | - vendor/thecodingmachine/phpstan-safe-rule/phpstan-safe-rule.neon
4 | parameters:
5 | excludes_analyse:
6 | - %currentWorkingDirectory%/src/Migrations/*.php
7 | - %currentWorkingDirectory%/src/Kernel.php
--------------------------------------------------------------------------------
/sources/app/config/packages/test/monolog.yaml:
--------------------------------------------------------------------------------
1 | monolog:
2 | handlers:
3 | main:
4 | type: stream
5 | # Output logs to Docker stderr by default.
6 | path: "php://stderr"
7 | #path: "%kernel.logs_dir%/%kernel.environment%.log"
8 | level: debug
9 | channels: ["!event"]
10 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/views/Home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Homepage
5 |
6 |
7 |
8 |
This is the homepage of our Vue.js application.
9 |
10 |
11 |
12 |
13 |
18 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/components/Post.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{ message }}
5 |
6 |
7 |
8 |
9 |
20 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/components/ErrorMessage.vue:
--------------------------------------------------------------------------------
1 |
2 |
6 | {{ error.response.data.error }}
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/sources/app/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "parserOptions": {
4 | "ecmaVersion": 2018,
5 | "sourceType": "module",
6 | "parser": "babel-eslint"
7 | },
8 | "env": {
9 | "browser": true,
10 | "es6": true
11 | },
12 | "extends": [
13 | "eslint:recommended",
14 | "plugin:vue/recommended"
15 | ],
16 | "rules": {
17 | "indent": ["error", 2]
18 | }
19 | }
--------------------------------------------------------------------------------
/sources/app/src/DataFixtures/AppFixtures.php:
--------------------------------------------------------------------------------
1 | flush();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/sources/app/bin/phpunit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | symfony/framework-bundle ###
3 | /.env.local
4 | /.env.local.php
5 | /.env.*.local
6 | /public/bundles/
7 | /var/
8 | /vendor/
9 | ###< symfony/framework-bundle ###
10 |
11 | ###> symfony/phpunit-bridge ###
12 | .phpunit
13 | /phpunit.xml
14 | ###< symfony/phpunit-bridge ###
15 |
16 | ###> symfony/web-server-bundle ###
17 | /.web-server-pid
18 | ###< symfony/web-server-bundle ###
19 |
20 | ###> squizlabs/php_codesniffer ###
21 | /.phpcs-cache
22 | /phpcs.xml
23 | ###< squizlabs/php_codesniffer ###
24 |
25 | ###> symfony/webpack-encore-bundle ###
26 | /node_modules/
27 | /public/build/
28 | npm-debug.log
29 | yarn-error.log
30 | ###< symfony/webpack-encore-bundle ###
31 |
--------------------------------------------------------------------------------
/sources/app/config/packages/cache.yaml:
--------------------------------------------------------------------------------
1 | framework:
2 | cache:
3 | # Put the unique name of your app here: the prefix seed
4 | # is used to compute stable namespaces for cache keys.
5 | #prefix_seed: your_vendor_name/app_name
6 |
7 | # The app cache caches to the filesystem by default.
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: ~
20 |
--------------------------------------------------------------------------------
/sources/app/config/packages/dev/monolog.yaml:
--------------------------------------------------------------------------------
1 | monolog:
2 | handlers:
3 | main:
4 | type: stream
5 | # Output logs to Docker stderr by default.
6 | path: "php://stderr"
7 | #path: "%kernel.logs_dir%/%kernel.environment%.log"
8 | level: debug
9 | channels: ["!event"]
10 | # uncomment to get logging in your browser
11 | # you may have to allow bigger header sizes in your Web server configuration
12 | #firephp:
13 | # type: firephp
14 | # level: info
15 | #chromephp:
16 | # type: chromephp
17 | # level: info
18 | console:
19 | type: console
20 | process_psr_3_messages: false
21 | channels: ["!event", "!doctrine", "!console"]
22 |
--------------------------------------------------------------------------------
/sources/app/config/packages/doctrine.yaml:
--------------------------------------------------------------------------------
1 | doctrine:
2 | dbal:
3 | # configure these for your database server
4 | driver: 'pdo_mysql'
5 | server_version: '5.7'
6 | charset: utf8mb4
7 | default_table_options:
8 | charset: utf8mb4
9 | collate: utf8mb4_unicode_ci
10 |
11 | url: '%env(resolve:DATABASE_URL)%'
12 | schema_filter: ~^(?!sessions)~
13 | orm:
14 | auto_generate_proxy_classes: true
15 | naming_strategy: doctrine.orm.naming_strategy.underscore
16 | auto_mapping: true
17 | mappings:
18 | App:
19 | is_bundle: false
20 | type: annotation
21 | dir: '%kernel.project_dir%/src/Entity'
22 | prefix: 'App\Entity'
23 | alias: App
24 |
--------------------------------------------------------------------------------
/sources/app/config/packages/webpack_encore.yaml:
--------------------------------------------------------------------------------
1 | webpack_encore:
2 | # The path where Encore is building the assets.
3 | # This should match Encore.setOutputPath() in webpack.config.js.
4 | output_path: '%kernel.project_dir%/public/build'
5 | # If multiple builds are defined (as shown below), you can disable the default build:
6 | # output_path: false
7 |
8 | # if using Encore.enableIntegrityHashes() specify the crossorigin attribute value (default: false, or use 'anonymous' or 'use-credentials')
9 | # crossorigin: 'anonymous'
10 |
11 | # Cache the entrypoints.json (rebuild Symfony's cache when entrypoints.json changes).
12 | # To enable caching for the production environment, creating a webpack_encore.yaml in the config/packages/prod directory with this value set to true
13 | # Available in version 1.2
14 | #cache: 'false'
15 |
--------------------------------------------------------------------------------
/sources/app/src/Exception/HTTPExceptionListener.php:
--------------------------------------------------------------------------------
1 | getException();
17 | if (! ($exception instanceof HttpException) || strpos($event->getRequest()->getRequestUri(), '/api/') === false) {
18 | return;
19 | }
20 |
21 | $response = new JsonResponse(['error' => $exception->getMessage()]);
22 | $response->setStatusCode($exception->getStatusCode());
23 | $event->setResponse($response);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/sources/app/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 | nested:
9 | type: stream
10 | # Output logs to Docker stderr by default.
11 | path: "php://stderr"
12 | #path: "%kernel.logs_dir%/%kernel.environment%.log"
13 | level: debug
14 | console:
15 | type: console
16 | process_psr_3_messages: false
17 | channels: ["!event", "!doctrine"]
18 | deprecation:
19 | type: stream
20 | path: "%kernel.logs_dir%/%kernel.environment%.deprecations.log"
21 | deprecation_filter:
22 | type: filter
23 | handler: deprecation
24 | max_level: info
25 | channels: ["php"]
26 |
--------------------------------------------------------------------------------
/sources/app/public/index.php:
--------------------------------------------------------------------------------
1 | handle($request);
26 | $response->send();
27 | $kernel->terminate($request, $response);
28 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | **A new version with Symfony + GraphQL + Nuxt.js is available here:** https://github.com/thecodingmachine/symfony-boilerplate
2 |
3 | ---
4 |
5 | Source code of the tutorial [thecodingmachine.io/building-a-single-page-application-with-symfony-4-and-vuejs](https://thecodingmachine.io/building-a-single-page-application-with-symfony-4-and-vuejs).
6 |
7 | # Quick start
8 |
9 | If you want to try out the project just follow those steps:
10 |
11 | ```bash
12 | $ cp .env.template .env
13 | $ docker-compose up -d
14 | $ docker-compose exec app bash # executing bash inside app service
15 | $ composer install
16 | $ yarn install
17 | $ yarn dev
18 | $ php bin/console doctrine:migration:migrate
19 | $ php bin/console doctrine:fixtures:load
20 | ```
21 |
22 | On MacOS, also update your `/etc/hosts` file with:
23 |
24 | ```
25 | 127.0.0.1 app.localhost
26 | 127.0.0.1 phpmyadmin.app.localhost
27 | ```
28 |
29 | You may now go to [http://app.localhost/](http://app.localhost/) and
30 | login using the following credentials:
31 |
32 | Login: `foo`
33 | Password: `bar`
34 |
--------------------------------------------------------------------------------
/sources/app/tests/bootstrap.php:
--------------------------------------------------------------------------------
1 | run();
17 | if (! $process->isSuccessful()) {
18 | throw new ProcessFailedException($process);
19 | }
20 |
21 | $process = new Process(['php', 'bin/console', 'doctrine:fixtures:load', '--no-interaction']);
22 | $process->run();
23 | if (! $process->isSuccessful()) {
24 | throw new ProcessFailedException($process);
25 | }
26 |
--------------------------------------------------------------------------------
/sources/app/config/packages/prod/doctrine.yaml:
--------------------------------------------------------------------------------
1 | doctrine:
2 | orm:
3 | auto_generate_proxy_classes: false
4 | metadata_cache_driver:
5 | type: service
6 | id: doctrine.system_cache_provider
7 | query_cache_driver:
8 | type: service
9 | id: doctrine.system_cache_provider
10 | result_cache_driver:
11 | type: service
12 | id: doctrine.result_cache_provider
13 |
14 | services:
15 | doctrine.result_cache_provider:
16 | class: Symfony\Component\Cache\DoctrineProvider
17 | public: false
18 | arguments:
19 | - '@doctrine.result_cache_pool'
20 | doctrine.system_cache_provider:
21 | class: Symfony\Component\Cache\DoctrineProvider
22 | public: false
23 | arguments:
24 | - '@doctrine.system_cache_pool'
25 |
26 | framework:
27 | cache:
28 | pools:
29 | doctrine.result_cache_pool:
30 | adapter: cache.app
31 | doctrine.system_cache_pool:
32 | adapter: cache.system
33 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/router/index.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue";
2 | import VueRouter from "vue-router";
3 | import store from "../store";
4 | import Home from "../views/Home";
5 | import Login from "../views/Login";
6 | import Posts from "../views/Posts";
7 |
8 | Vue.use(VueRouter);
9 |
10 | let router = new VueRouter({
11 | mode: "history",
12 | routes: [
13 | { path: "/home", component: Home },
14 | { path: "/login", component: Login },
15 | { path: "/posts", component: Posts, meta: { requiresAuth: true } },
16 | { path: "*", redirect: "/home" }
17 | ],
18 | });
19 |
20 | router.beforeEach((to, from, next) => {
21 | if (to.matched.some(record => record.meta.requiresAuth)) {
22 | // this route requires auth, check if logged in
23 | // if not, redirect to login page.
24 | if (store.getters["security/isAuthenticated"]) {
25 | next();
26 | } else {
27 | next({
28 | path: "/login",
29 | query: { redirect: to.fullPath }
30 | });
31 | }
32 | } else {
33 | next(); // make sure to always call next()!
34 | }
35 | });
36 |
37 | export default router;
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 TheCodingMachine
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 |
--------------------------------------------------------------------------------
/sources/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "@babel/plugin-transform-runtime": "^7.4.4",
4 | "@babel/runtime": "^7.4.5",
5 | "@symfony/webpack-encore": "^0.27.0",
6 | "babel-eslint": "^10.0.2",
7 | "core-js": "^3.0.0",
8 | "eslint": "^5.16.0",
9 | "eslint-loader": "^2.1.2",
10 | "eslint-plugin-vue": "^5.2.2",
11 | "vue-loader": "^15.7.0",
12 | "vue-template-compiler": "^2.6.10",
13 | "webpack-notifier": "^1.6.0"
14 | },
15 | "license": "UNLICENSED",
16 | "private": true,
17 | "scripts": {
18 | "dev-server": "encore dev-server",
19 | "dev": "encore dev",
20 | "watch": "encore dev --watch",
21 | "build": "encore production --progress",
22 | "csfix": "eslint assets/vue --ext .js,.vue, --fix"
23 | },
24 | "dependencies": {
25 | "axios": "^0.19.0",
26 | "vue": "^2.6.10",
27 | "vue-router": "^3.0.6",
28 | "vuex": "^3.1.1"
29 | },
30 | "browserslist": [
31 | "> 0.5%",
32 | "last 2 versions",
33 | "Firefox ESR",
34 | "not dead"
35 | ]
36 | }
37 |
--------------------------------------------------------------------------------
/sources/app/config/bootstrap.php:
--------------------------------------------------------------------------------
1 | =1.2)
9 | /*if (is_array($env = @include dirname(__DIR__).'/.env.local.php')) {
10 | foreach ($env as $k => $v) {
11 | $_ENV[$k] = $_ENV[$k] ?? (isset($_SERVER[$k]) && 0 !== strpos($k, 'HTTP_') ? $_SERVER[$k] : $v);
12 | }
13 | } elseif (!class_exists(Dotenv::class)) {
14 | throw new RuntimeException('Please run "composer require symfony/dotenv" to load the ".env" files configuring the application.');
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 |
--------------------------------------------------------------------------------
/sources/app/templates/base.html.twig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Symfony 4 with a Vue.js SPA
6 |
7 | {{ encore_entry_link_tags('app') }}
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{ encore_entry_script_tags('app') }}
15 |
16 |
--------------------------------------------------------------------------------
/sources/app/src/Migrations/Version20190618154659.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
23 |
24 | $this->addSql('CREATE TABLE sessions (sess_id VARCHAR(128) NOT NULL PRIMARY KEY, sess_data BLOB NOT NULL, sess_time INTEGER UNSIGNED NOT NULL, sess_lifetime MEDIUMINT NOT NULL) COLLATE utf8_bin, ENGINE = InnoDB');
25 | }
26 |
27 | public function down(Schema $schema) : void
28 | {
29 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
30 |
31 | $this->addSql('DROP TABLE sessions');
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/sources/app/src/DataFixtures/UserFixtures.php:
--------------------------------------------------------------------------------
1 | createUser($manager, self::DEFAULT_USER_LOGIN, self::DEFAULT_USER_PASSWORD, ['ROLE_FOO']);
24 | $this->createUser($manager, self::USER_LOGIN_ROLE_BAR, self::USER_PASSWORD_ROLE_BAR, ['ROLE_BAR']);
25 | }
26 |
27 | /**
28 | * @param string[] $roles
29 | */
30 | private function createUser(ObjectManager $manager, string $login, string $password, array $roles): void
31 | {
32 | $userEntity = new User();
33 | $userEntity->setLogin($login);
34 | $userEntity->setPlainPassword($password);
35 | $userEntity->setRoles($roles);
36 | $manager->persist($userEntity);
37 | $manager->flush();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/sources/app/phpunit.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | tests
23 |
24 |
25 |
26 |
27 |
28 | src
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/sources/app/config/packages/security.yaml:
--------------------------------------------------------------------------------
1 | security:
2 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers
3 | encoders:
4 | App\Entity\User:
5 | algorithm: auto
6 | providers:
7 | in_memory: { memory: ~ }
8 | pdo:
9 | entity:
10 | class: App\Entity\User
11 | property: login
12 | firewalls:
13 | dev:
14 | pattern: ^/(_(profiler|wdt)|css|images|js)/
15 | security: false
16 | main:
17 | anonymous: true
18 |
19 | provider: pdo
20 |
21 | json_login:
22 | check_path: /api/security/login
23 |
24 | logout:
25 | path: /api/security/logout
26 |
27 | # activate different ways to authenticate
28 |
29 | # http_basic: true
30 | # https://symfony.com/doc/current/security.html#a-configuring-how-your-users-will-authenticate
31 |
32 | # form_login: true
33 | # https://symfony.com/doc/current/security/form_login_setup.html
34 |
35 | # Easy way to control access for large sections of your site
36 | # Note: Only the *first* access control that matches will be used
37 | access_control:
38 | # - { path: ^/admin, roles: ROLE_ADMIN }
39 | # - { path: ^/profile, roles: ROLE_USER }
--------------------------------------------------------------------------------
/sources/app/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 |
--------------------------------------------------------------------------------
/sources/app/config/bundles.php:
--------------------------------------------------------------------------------
1 | ['all' => true],
5 | Doctrine\Bundle\DoctrineCacheBundle\DoctrineCacheBundle::class => ['all' => true],
6 | Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle::class => ['all' => true],
7 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
8 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
9 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
10 | Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true],
11 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
12 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
13 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
14 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true, 'test' => true],
15 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
16 | Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['dev' => true],
17 | Symfony\WebpackEncoreBundle\WebpackEncoreBundle::class => ['all' => true],
18 | FOS\RestBundle\FOSRestBundle::class => ['all' => true],
19 | Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
20 | ];
21 |
--------------------------------------------------------------------------------
/sources/app/src/Migrations/Version20190618080327.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
24 |
25 | $this->addSql('CREATE TABLE posts (id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', message VARCHAR(255) NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
26 | }
27 |
28 | public function down(Schema $schema) : void
29 | {
30 | // this down() migration is auto-generated, please modify it to your needs
31 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
32 |
33 | $this->addSql('DROP TABLE posts');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/sources/app/src/Migrations/Version20190618152017.php:
--------------------------------------------------------------------------------
1 | abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
24 |
25 | $this->addSql('CREATE TABLE users (id CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', login VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:simple_array)\', created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, UNIQUE INDEX UNIQ_1483A5E9AA08CB10 (login), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
26 | }
27 |
28 | public function down(Schema $schema) : void
29 | {
30 | // this down() migration is auto-generated, please modify it to your needs
31 | $this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'mysql', 'Migration can only be executed safely on \'mysql\'.');
32 |
33 | $this->addSql('DROP TABLE users');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/sources/app/src/Controller/IndexController.php:
--------------------------------------------------------------------------------
1 | serializer = $serializer;
24 | }
25 |
26 | /**
27 | * @throws JsonException
28 | *
29 | * @Route("/{vueRouting}", requirements={"vueRouting"="^(?!api|_(profiler|wdt)).*"}, name="index")
30 | */
31 | public function indexAction(): Response
32 | {
33 | /** @var User|null $user */
34 | $user = $this->getUser();
35 | $data = null;
36 | if (! empty($user)) {
37 | $userClone = clone $user;
38 | $userClone->setPassword('');
39 | $data = $this->serializer->serialize($userClone, JsonEncoder::FORMAT);
40 | }
41 |
42 | return $this->render('base.html.twig', [
43 | 'isAuthenticated' => json_encode(! empty($user)),
44 | 'user' => $data ?? json_encode($data),
45 | ]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/sources/app/src/Controller/SecurityController.php:
--------------------------------------------------------------------------------
1 | serializer = $serializer;
27 | }
28 |
29 | /**
30 | * @Route("/security/login", name="login")
31 | */
32 | public function loginAction(): JsonResponse
33 | {
34 | /** @var User $user */
35 | $user = $this->getUser();
36 | $userClone = clone $user;
37 | $userClone->setPassword('');
38 | $data = $this->serializer->serialize($userClone, JsonEncoder::FORMAT);
39 |
40 | return new JsonResponse($data, Response::HTTP_OK, [], true);
41 | }
42 |
43 | /**
44 | * @throws RuntimeException
45 | *
46 | * @Route("/security/logout", name="logout")
47 | */
48 | public function logoutAction(): void
49 | {
50 | throw new RuntimeException('This should not be reached!');
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/sources/app/phpcs.xml.dist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | src
13 | tests
14 |
15 | tests/dependencies/*
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | 0
29 |
30 |
31 |
32 |
33 | 0
34 |
35 |
36 | 0
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.7'
2 |
3 | services:
4 |
5 | traefik:
6 | image: traefik:1.7
7 | command: --docker --docker.exposedbydefault=false
8 | ports:
9 | - "80:80"
10 | volumes:
11 | - /var/run/docker.sock:/var/run/docker.sock
12 |
13 | app:
14 | image: thecodingmachine/php:7.3-v2-apache-node10
15 | labels:
16 | - traefik.enable=true
17 | - traefik.backend=app
18 | - traefik.frontend.rule=Host:app.localhost
19 | environment:
20 | APACHE_DOCUMENT_ROOT: "public/"
21 | PHP_EXTENSION_XDEBUG: "1"
22 | PHP_INI_MEMORY_LIMIT: "1G"
23 | # Symfony
24 | APP_ENV: "dev"
25 | APP_SECRET: "8d2a5c935d8ef1c0e2b751147382bc75"
26 | DATABASE_URL: "mysql://$MYSQL_USER:$MYSQL_PASSWORD@mysql:3306/$MYSQL_DATABASE"
27 | volumes:
28 | - ./sources/app:/var/www/html:rw
29 |
30 | mysql:
31 | image: mysql:5.7
32 | environment:
33 | MYSQL_ROOT_PASSWORD: "$MYSQL_ROOT_PASSWORD"
34 | MYSQL_DATABASE: "$MYSQL_DATABASE"
35 | MYSQL_USER: "$MYSQL_USER"
36 | MYSQL_PASSWORD: "$MYSQL_PASSWORD"
37 | volumes:
38 | - mysql_data:/var/lib/mysql
39 | - ./services/mysql/utf8mb4.cnf:/etc/mysql/conf.d/utf8mb4.cnf:ro
40 |
41 | mysql_tests:
42 | image: mysql:5.7
43 | environment:
44 | MYSQL_ROOT_PASSWORD: "admin"
45 | MYSQL_DATABASE: "tests"
46 | MYSQL_USER: "foo"
47 | MYSQL_PASSWORD: "bar"
48 | volumes:
49 | - ./services/mysql/utf8mb4.cnf:/etc/mysql/conf.d/utf8mb4.cnf:ro
50 |
51 | phpmyadmin:
52 | image: phpmyadmin/phpmyadmin:4.7
53 | labels:
54 | - traefik.enable=true
55 | - traefik.backend=phpmyadmin
56 | - traefik.frontend.rule=Host:phpmyadmin.app.localhost
57 | environment:
58 | PMA_HOST: "mysql"
59 | PMA_USER: "$MYSQL_USER"
60 | PMA_PASSWORD: "$MYSQL_PASSWORD"
61 |
62 | volumes:
63 |
64 | mysql_data:
65 | driver: local
--------------------------------------------------------------------------------
/sources/app/src/Entity/Post.php:
--------------------------------------------------------------------------------
1 | id = Uuid::uuid4();
57 | $this->created = new DateTime('NOW');
58 | }
59 |
60 | /**
61 | * @ORM\PreUpdate
62 | */
63 | public function onPreUpdate(): void
64 | {
65 | $this->updated = new DateTime('NOW');
66 | }
67 |
68 | public function getId(): UuidInterface
69 | {
70 | return $this->id;
71 | }
72 |
73 | public function getMessage(): string
74 | {
75 | return $this->message;
76 | }
77 |
78 | public function setMessage(string $message): void
79 | {
80 | $this->message = $message;
81 | }
82 |
83 | public function getCreated(): DateTime
84 | {
85 | return $this->created;
86 | }
87 |
88 | public function getUpdated(): ?DateTime
89 | {
90 | return $this->updated;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/sources/app/src/Security/HashPasswordListener.php:
--------------------------------------------------------------------------------
1 | passwordEncoder = $passwordEncoder;
21 | }
22 |
23 | public function prePersist(LifecycleEventArgs $args): void
24 | {
25 | $entity = $args->getEntity();
26 | if (! $entity instanceof User) {
27 | return;
28 | }
29 |
30 | $this->encodePassword($entity);
31 | }
32 |
33 | public function preUpdate(LifecycleEventArgs $args): void
34 | {
35 | $entity = $args->getEntity();
36 | if (! $entity instanceof User) {
37 | return;
38 | }
39 |
40 | $this->encodePassword($entity);
41 | // necessary to force the update to see the change
42 | $em = $args->getEntityManager();
43 | $meta = $em->getClassMetadata(get_class($entity));
44 | $em->getUnitOfWork()->recomputeSingleEntityChangeSet($meta, $entity);
45 | }
46 |
47 | /**
48 | * {@inheritdoc}
49 | */
50 | public function getSubscribedEvents()
51 | {
52 | return ['prePersist', 'preUpdate'];
53 | }
54 |
55 | private function encodePassword(User $entity): void
56 | {
57 | $plainPassword = $entity->getPlainPassword();
58 | if ($plainPassword === null) {
59 | return;
60 | }
61 |
62 | $encoded = $this->passwordEncoder->encodePassword(
63 | $entity,
64 | $plainPassword
65 | );
66 |
67 | $entity->setPassword($encoded);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/sources/app/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 | locale: 'en'
8 |
9 | services:
10 | # default configuration for services in *this* file
11 | _defaults:
12 | autowire: true # Automatically injects dependencies in your services.
13 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
14 |
15 | # makes classes in src/ available to be used as services
16 | # this creates a service per class whose id is the fully-qualified class name
17 | App\:
18 | resource: '../src/*'
19 | exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'
20 |
21 | # controllers are imported separately to make sure services can be injected
22 | # as action arguments even if you don't extend any base controller class
23 | App\Controller\:
24 | resource: '../src/Controller'
25 | tags: ['controller.service_arguments']
26 |
27 | # add more service definitions when explicit configuration is needed
28 | # please note that last definitions always *replace* previous ones
29 | App\Exception\HTTPExceptionListener:
30 | tags:
31 | - { name: kernel.event_listener, event: kernel.exception }
32 |
33 | app.security.hash.password.listener:
34 | class: App\Security\HashPasswordListener
35 | tags:
36 | - { name: doctrine.event_subscriber }
37 |
38 | Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler:
39 | arguments:
40 | - !service { class: PDO, factory: 'database_connection:getWrappedConnection' }
41 | # If you get transaction issues (e.g. after login) uncomment the line below
42 | - { lock_mode: 1 }
--------------------------------------------------------------------------------
/sources/app/tests/Controller/AbstractControllerWebTestCase.php:
--------------------------------------------------------------------------------
1 | client = static::createClient();
25 | }
26 |
27 | /**
28 | * @param mixed[] $data
29 | *
30 | * @throws JsonException
31 | */
32 | protected function JSONRequest(string $method, string $uri, array $data = []): void
33 | {
34 | $this->client->request($method, $uri, [], [], ['CONTENT_TYPE' => 'application/json'], json_encode($data));
35 | }
36 |
37 | /**
38 | * @return mixed
39 | *
40 | * @throws JsonException
41 | */
42 | protected function assertJSONResponse(Response $response, int $expectedStatusCode)
43 | {
44 | $this->assertEquals($expectedStatusCode, $response->getStatusCode());
45 | $this->assertTrue($response->headers->contains('Content-Type', 'application/json'));
46 | $this->assertJson($response->getContent());
47 |
48 | return json_decode($response->getContent(), true);
49 | }
50 |
51 | /**
52 | * @throws JsonException
53 | */
54 | protected function login(string $username = UserFixtures::DEFAULT_USER_LOGIN, string $password = UserFixtures::DEFAULT_USER_PASSWORD): void
55 | {
56 | $this->client->request(Request::METHOD_POST, '/api/security/login', [], [], ['CONTENT_TYPE' => 'application/json'], json_encode(['username' => $username, 'password' => $password]));
57 | $this->assertEquals(Response::HTTP_OK, $this->client->getResponse()->getStatusCode());
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/sources/app/src/Kernel.php:
--------------------------------------------------------------------------------
1 | getProjectDir() . '/config/bundles.php';
24 | foreach ($contents as $class => $envs) {
25 | if ($envs[$this->environment] ?? $envs['all'] ?? false) {
26 | yield new $class();
27 | }
28 | }
29 | }
30 |
31 | public function getProjectDir(): string
32 | {
33 | return dirname(__DIR__);
34 | }
35 |
36 | protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
37 | {
38 | $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php'));
39 | $container->setParameter('container.dumper.inline_class_loader', true);
40 | $confDir = $this->getProjectDir() . '/config';
41 |
42 | $loader->load($confDir . '/{packages}/*' . self::CONFIG_EXTS, 'glob');
43 | $loader->load($confDir . '/{packages}/' . $this->environment . '/**/*' . self::CONFIG_EXTS, 'glob');
44 | $loader->load($confDir . '/{services}' . self::CONFIG_EXTS, 'glob');
45 | $loader->load($confDir . '/{services}_' . $this->environment . self::CONFIG_EXTS, 'glob');
46 | }
47 |
48 | protected function configureRoutes(RouteCollectionBuilder $routes): void
49 | {
50 | $confDir = $this->getProjectDir() . '/config';
51 |
52 | $routes->import($confDir . '/{routes}/' . $this->environment . '/**/*' . self::CONFIG_EXTS, '/', 'glob');
53 | $routes->import($confDir . '/{routes}/*' . self::CONFIG_EXTS, '/', 'glob');
54 | $routes->import($confDir . '/{routes}' . self::CONFIG_EXTS, '/', 'glob');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/store/security.js:
--------------------------------------------------------------------------------
1 | import SecurityAPI from "../api/security";
2 |
3 | const AUTHENTICATING = "AUTHENTICATING",
4 | AUTHENTICATING_SUCCESS = "AUTHENTICATING_SUCCESS",
5 | AUTHENTICATING_ERROR = "AUTHENTICATING_ERROR",
6 | PROVIDING_DATA_ON_REFRESH_SUCCESS = "PROVIDING_DATA_ON_REFRESH_SUCCESS";
7 |
8 | export default {
9 | namespaced: true,
10 | state: {
11 | isLoading: false,
12 | error: null,
13 | isAuthenticated: false,
14 | user: null
15 | },
16 | getters: {
17 | isLoading(state) {
18 | return state.isLoading;
19 | },
20 | hasError(state) {
21 | return state.error !== null;
22 | },
23 | error(state) {
24 | return state.error;
25 | },
26 | isAuthenticated(state) {
27 | return state.isAuthenticated;
28 | },
29 | hasRole(state) {
30 | return role => {
31 | return state.user.roles.indexOf(role) !== -1;
32 | }
33 | }
34 | },
35 | mutations: {
36 | [AUTHENTICATING](state) {
37 | state.isLoading = true;
38 | state.error = null;
39 | state.isAuthenticated = false;
40 | state.user = null;
41 | },
42 | [AUTHENTICATING_SUCCESS](state, user) {
43 | state.isLoading = false;
44 | state.error = null;
45 | state.isAuthenticated = true;
46 | state.user = user;
47 | },
48 | [AUTHENTICATING_ERROR](state, error) {
49 | state.isLoading = false;
50 | state.error = error;
51 | state.isAuthenticated = false;
52 | state.user = null;
53 | },
54 | [PROVIDING_DATA_ON_REFRESH_SUCCESS](state, payload) {
55 | state.isLoading = false;
56 | state.error = null;
57 | state.isAuthenticated = payload.isAuthenticated;
58 | state.user = payload.user;
59 | }
60 | },
61 | actions: {
62 | async login({commit}, payload) {
63 | commit(AUTHENTICATING);
64 | try {
65 | let response = await SecurityAPI.login(payload.login, payload.password);
66 | commit(AUTHENTICATING_SUCCESS, response.data);
67 | return response.data;
68 | } catch (error) {
69 | commit(AUTHENTICATING_ERROR, error);
70 | return null;
71 | }
72 | },
73 | onRefresh({commit}, payload) {
74 | commit(PROVIDING_DATA_ON_REFRESH_SUCCESS, payload);
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/sources/app/src/Controller/PostController.php:
--------------------------------------------------------------------------------
1 | em = $em;
34 | $this->serializer = $serializer;
35 | }
36 |
37 | /**
38 | * @throws BadRequestHttpException
39 | *
40 | * @Rest\Post("/posts", name="createPost")
41 | * @IsGranted("ROLE_FOO")
42 | */
43 | public function createAction(Request $request): JsonResponse
44 | {
45 | $message = $request->request->get('message');
46 | if (empty($message)) {
47 | throw new BadRequestHttpException('message cannot be empty');
48 | }
49 | $post = new Post();
50 | $post->setMessage($message);
51 | $this->em->persist($post);
52 | $this->em->flush();
53 | $data = $this->serializer->serialize($post, JsonEncoder::FORMAT);
54 |
55 | return new JsonResponse($data, Response::HTTP_CREATED, [], true);
56 | }
57 |
58 | /**
59 | * @Rest\Get("/posts", name="findAllPosts")
60 | */
61 | public function findAllAction(): JsonResponse
62 | {
63 | $posts = $this->em->getRepository(Post::class)->findBy([], ['id' => 'DESC']);
64 | $data = $this->serializer->serialize($posts, JsonEncoder::FORMAT);
65 |
66 | return new JsonResponse($data, Response::HTTP_OK, [], true);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/store/post.js:
--------------------------------------------------------------------------------
1 | import PostAPI from "../api/post";
2 |
3 | const CREATING_POST = "CREATING_POST",
4 | CREATING_POST_SUCCESS = "CREATING_POST_SUCCESS",
5 | CREATING_POST_ERROR = "CREATING_POST_ERROR",
6 | FETCHING_POSTS = "FETCHING_POSTS",
7 | FETCHING_POSTS_SUCCESS = "FETCHING_POSTS_SUCCESS",
8 | FETCHING_POSTS_ERROR = "FETCHING_POSTS_ERROR";
9 |
10 | export default {
11 | namespaced: true,
12 | state: {
13 | isLoading: false,
14 | error: null,
15 | posts: []
16 | },
17 | getters: {
18 | isLoading(state) {
19 | return state.isLoading;
20 | },
21 | hasError(state) {
22 | return state.error !== null;
23 | },
24 | error(state) {
25 | return state.error;
26 | },
27 | hasPosts(state) {
28 | return state.posts.length > 0;
29 | },
30 | posts(state) {
31 | return state.posts;
32 | }
33 | },
34 | mutations: {
35 | [CREATING_POST](state) {
36 | state.isLoading = true;
37 | state.error = null;
38 | },
39 | [CREATING_POST_SUCCESS](state, post) {
40 | state.isLoading = false;
41 | state.error = null;
42 | state.posts.unshift(post);
43 | },
44 | [CREATING_POST_ERROR](state, error) {
45 | state.isLoading = false;
46 | state.error = error;
47 | state.posts = [];
48 | },
49 | [FETCHING_POSTS](state) {
50 | state.isLoading = true;
51 | state.error = null;
52 | state.posts = [];
53 | },
54 | [FETCHING_POSTS_SUCCESS](state, posts) {
55 | state.isLoading = false;
56 | state.error = null;
57 | state.posts = posts;
58 | },
59 | [FETCHING_POSTS_ERROR](state, error) {
60 | state.isLoading = false;
61 | state.error = error;
62 | state.posts = [];
63 | }
64 | },
65 | actions: {
66 | async create({ commit }, message) {
67 | commit(CREATING_POST);
68 | try {
69 | let response = await PostAPI.create(message);
70 | commit(CREATING_POST_SUCCESS, response.data);
71 | return response.data;
72 | } catch (error) {
73 | commit(CREATING_POST_ERROR, error);
74 | return null;
75 | }
76 | },
77 | async findAll({ commit }) {
78 | commit(FETCHING_POSTS);
79 | try {
80 | let response = await PostAPI.findAll();
81 | commit(FETCHING_POSTS_SUCCESS, response.data);
82 | return response.data;
83 | } catch (error) {
84 | commit(FETCHING_POSTS_ERROR, error);
85 | return null;
86 | }
87 | }
88 | }
89 | };
90 |
--------------------------------------------------------------------------------
/sources/app/tests/Controller/PostControllerTest.php:
--------------------------------------------------------------------------------
1 | JSONRequest(Request::METHOD_POST, '/api/posts');
22 | $this->assertJSONResponse($this->client->getResponse(), Response::HTTP_UNAUTHORIZED);
23 | // test that sending a request while not having the role "ROLE_FOO" will result to a forbidden HTTP code.
24 | $this->login(UserFixtures::USER_LOGIN_ROLE_BAR, UserFixtures::USER_PASSWORD_ROLE_BAR);
25 | $this->JSONRequest(Request::METHOD_POST, '/api/posts');
26 | $this->assertJSONResponse($this->client->getResponse(), Response::HTTP_FORBIDDEN);
27 | // test that sending a request while begin authenticated will result to a created HTTP code.
28 | $this->login();
29 | $this->JSONRequest(Request::METHOD_POST, '/api/posts', ['message' => 'Hello world!']);
30 | $this->assertJSONResponse($this->client->getResponse(), Response::HTTP_CREATED);
31 | // test that sending no message will result to a bad request HTTP code.
32 | $this->JSONRequest(Request::METHOD_POST, '/api/posts');
33 | $this->assertJSONResponse($this->client->getResponse(), Response::HTTP_BAD_REQUEST);
34 | }
35 |
36 | /**
37 | * @throws JsonException
38 | */
39 | public function testFindAllPosts(): void
40 | {
41 | // test that sending a request without being authenticated will result to a unauthorized HTTP code.
42 | $this->client->request(Request::METHOD_GET, '/api/posts');
43 | $this->assertJSONResponse($this->client->getResponse(), Response::HTTP_UNAUTHORIZED);
44 | // test that sending a request while begin authenticated will result to a OK HTTP code.
45 | $this->login();
46 | $this->client->request(Request::METHOD_GET, '/api/posts');
47 | $response = $this->client->getResponse();
48 | $content = $this->assertJSONResponse($response, Response::HTTP_OK);
49 | $this->assertEquals(1, count($content));
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/views/Posts.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Posts
5 |
6 |
7 |
33 |
34 |
40 |
41 |
45 |
46 |
47 |
48 |
52 | No posts!
53 |
54 |
55 |
63 |
64 |
65 |
66 |
114 |
--------------------------------------------------------------------------------
/sources/app/assets/vue/views/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Login
5 |
6 |
7 |
37 |
38 |
44 |
45 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/sources/app/webpack.config.js:
--------------------------------------------------------------------------------
1 | let Encore = require('@symfony/webpack-encore');
2 |
3 | // Manually configure the runtime environment if not already configured yet by the "encore" command.
4 | // It's useful when you use tools that rely on webpack.config.js file.
5 | if (!Encore.isRuntimeEnvironmentConfigured()) {
6 | Encore.configureRuntimeEnvironment(process.env.NODE_ENV || 'dev');
7 | }
8 |
9 | Encore
10 | // directory where compiled assets will be stored
11 | .setOutputPath('public/build/')
12 | // public path used by the web server to access the output path
13 | .setPublicPath('/build')
14 | // only needed for CDN's or sub-directory deploy
15 | //.setManifestKeyPrefix('build/')
16 |
17 | /*
18 | * ENTRY CONFIG
19 | *
20 | * Add 1 entry for each "page" of your app
21 | * (including one that's included on every page - e.g. "app")
22 | *
23 | * Each entry will result in one JavaScript file (e.g. app.js)
24 | * and one CSS file (e.g. app.css) if you JavaScript imports CSS.
25 | */
26 | .addEntry('app', './assets/vue/index.js')
27 | //.addEntry('page1', './assets/js/page1.js')
28 | //.addEntry('page2', './assets/js/page2.js')
29 |
30 | // When enabled, Webpack "splits" your files into smaller pieces for greater optimization.
31 | .splitEntryChunks()
32 |
33 | // will require an extra script tag for runtime.js
34 | // but, you probably want this, unless you're building a single-page app
35 | //.enableSingleRuntimeChunk()
36 | .disableSingleRuntimeChunk()
37 |
38 | /*
39 | * FEATURE CONFIG
40 | *
41 | * Enable & configure other features below. For a full
42 | * list of features, see:
43 | * https://symfony.com/doc/current/frontend.html#adding-more-features
44 | */
45 | .cleanupOutputBeforeBuild()
46 | .enableBuildNotifications()
47 | .enableSourceMaps(!Encore.isProduction())
48 | // enables hashed filenames (e.g. app.abc123.css)
49 | .enableVersioning(Encore.isProduction())
50 |
51 | // enables @babel/preset-env polyfills
52 | .configureBabel((babelConfig) => {
53 | babelConfig.plugins.push('@babel/plugin-transform-runtime');
54 | }, {
55 | useBuiltIns: 'usage',
56 | corejs: 3
57 | })
58 |
59 | // enables Vue.js support
60 | .enableVueLoader()
61 |
62 | // enables Sass/SCSS support
63 | //.enableSassLoader()
64 |
65 | // uncomment if you use TypeScript
66 | //.enableTypeScriptLoader()
67 |
68 | // uncomment to get integrity="..." attributes on your script & link tags
69 | // requires WebpackEncoreBundle 1.4 or higher
70 | .enableIntegrityHashes()
71 |
72 | // enable ESLint
73 | .addLoader({
74 | enforce: 'pre',
75 | test: /\.(js|vue)$/,
76 | loader: 'eslint-loader',
77 | exclude: /node_modules/,
78 | options: {
79 | fix: true,
80 | emitError: true,
81 | emitWarning: true,
82 | },
83 | })
84 |
85 | // uncomment if you're having problems with a jQuery plugin
86 | //.autoProvidejQuery()
87 |
88 | // uncomment if you use API Platform Admin (composer req api-admin)
89 | //.enableReactPreset()
90 | //.addEntry('admin', './assets/js/admin.js')
91 | ;
92 |
93 | module.exports = Encore.getWebpackConfig();
--------------------------------------------------------------------------------
/sources/app/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "type": "project",
3 | "license": "proprietary",
4 | "require": {
5 | "php": "^7.1.3",
6 | "ext-ctype": "*",
7 | "ext-iconv": "*",
8 | "doctrine/doctrine-migrations-bundle": "^2.0",
9 | "friendsofsymfony/rest-bundle": "^2.5",
10 | "ramsey/uuid-doctrine": "^1.5",
11 | "sensio/framework-extra-bundle": "^5.1",
12 | "symfony/apache-pack": "^1.0",
13 | "symfony/asset": "4.3.*",
14 | "symfony/console": "4.3.*",
15 | "symfony/dotenv": "4.3.*",
16 | "symfony/expression-language": "4.3.*",
17 | "symfony/flex": "^1.1",
18 | "symfony/form": "4.3.*",
19 | "symfony/framework-bundle": "4.3.*",
20 | "symfony/http-client": "4.3.*",
21 | "symfony/intl": "4.3.*",
22 | "symfony/monolog-bundle": "^3.1",
23 | "symfony/orm-pack": "*",
24 | "symfony/process": "4.3.*",
25 | "symfony/security-bundle": "4.3.*",
26 | "symfony/serializer-pack": "*",
27 | "symfony/swiftmailer-bundle": "^3.1",
28 | "symfony/translation": "4.3.*",
29 | "symfony/twig-bundle": "4.3.*",
30 | "symfony/validator": "4.3.*",
31 | "symfony/web-link": "4.3.*",
32 | "symfony/webpack-encore-bundle": "^1.6",
33 | "symfony/yaml": "4.3.*",
34 | "thecodingmachine/safe": "^0.1.15"
35 | },
36 | "require-dev": {
37 | "doctrine/coding-standard": "^6.0",
38 | "doctrine/doctrine-fixtures-bundle": "^3.2",
39 | "phpstan/phpstan": "^0.11.8",
40 | "squizlabs/php_codesniffer": "^3.4",
41 | "symfony/debug-pack": "*",
42 | "symfony/maker-bundle": "^1.0",
43 | "symfony/phpunit-bridge": "^4.3",
44 | "symfony/profiler-pack": "*",
45 | "symfony/test-pack": "*",
46 | "symfony/web-server-bundle": "4.3.*",
47 | "thecodingmachine/phpstan-safe-rule": "^0.1.3",
48 | "thecodingmachine/phpstan-strict-rules": "^0.11.2"
49 | },
50 | "config": {
51 | "preferred-install": {
52 | "*": "dist"
53 | },
54 | "sort-packages": true
55 | },
56 | "autoload": {
57 | "psr-4": {
58 | "App\\": "src/"
59 | }
60 | },
61 | "autoload-dev": {
62 | "psr-4": {
63 | "App\\Tests\\": "tests/"
64 | }
65 | },
66 | "replace": {
67 | "paragonie/random_compat": "2.*",
68 | "symfony/polyfill-ctype": "*",
69 | "symfony/polyfill-iconv": "*",
70 | "symfony/polyfill-php71": "*",
71 | "symfony/polyfill-php70": "*",
72 | "symfony/polyfill-php56": "*"
73 | },
74 | "scripts": {
75 | "csfix": "phpcbf --ignore=src/Migrations/**,src/Kernel.php",
76 | "cscheck": "phpcs --ignore=src/Migrations/**,src/Kernel.php",
77 | "phpstan": "phpstan analyse src/ -c phpstan.neon --level=7 --no-progress -vvv --memory-limit=1024M",
78 | "auto-scripts": {
79 | "cache:clear": "symfony-cmd",
80 | "assets:install %PUBLIC_DIR%": "symfony-cmd"
81 | },
82 | "post-install-cmd": [
83 | "@auto-scripts"
84 | ],
85 | "post-update-cmd": [
86 | "@auto-scripts"
87 | ]
88 | },
89 | "conflict": {
90 | "symfony/symfony": "*"
91 | },
92 | "extra": {
93 | "symfony": {
94 | "allow-contrib": false,
95 | "require": "4.3.*"
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/sources/app/public/.htaccess:
--------------------------------------------------------------------------------
1 | # Use the front controller as index file. It serves as a fallback solution when
2 | # every other rewrite/redirect fails (e.g. in an aliased environment without
3 | # mod_rewrite). Additionally, this reduces the matching process for the
4 | # start page (path "/") because otherwise Apache will apply the rewriting rules
5 | # to each configured DirectoryIndex file (e.g. index.php, index.html, index.pl).
6 | DirectoryIndex index.php
7 |
8 | # By default, Apache does not evaluate symbolic links if you did not enable this
9 | # feature in your server configuration. Uncomment the following line if you
10 | # install assets as symlinks or if you experience problems related to symlinks
11 | # when compiling LESS/Sass/CoffeScript assets.
12 | # Options FollowSymlinks
13 |
14 | # Disabling MultiViews prevents unwanted negotiation, e.g. "/index" should not resolve
15 | # to the front controller "/index.php" but be rewritten to "/index.php/index".
16 |
17 | Options -MultiViews
18 |
19 |
20 |
21 | RewriteEngine On
22 |
23 | # Determine the RewriteBase automatically and set it as environment variable.
24 | # If you are using Apache aliases to do mass virtual hosting or installed the
25 | # project in a subdirectory, the base path will be prepended to allow proper
26 | # resolution of the index.php file and to redirect to the correct URI. It will
27 | # work in environments without path prefix as well, providing a safe, one-size
28 | # fits all solution. But as you do not need it in this case, you can comment
29 | # the following 2 lines to eliminate the overhead.
30 | RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
31 | RewriteRule ^(.*) - [E=BASE:%1]
32 |
33 | # Sets the HTTP_AUTHORIZATION header removed by Apache
34 | RewriteCond %{HTTP:Authorization} .
35 | RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
36 |
37 | # Redirect to URI without front controller to prevent duplicate content
38 | # (with and without `/index.php`). Only do this redirect on the initial
39 | # rewrite by Apache and not on subsequent cycles. Otherwise we would get an
40 | # endless redirect loop (request -> rewrite to front controller ->
41 | # redirect -> request -> ...).
42 | # So in case you get a "too many redirects" error or you always get redirected
43 | # to the start page because your Apache does not expose the REDIRECT_STATUS
44 | # environment variable, you have 2 choices:
45 | # - disable this feature by commenting the following 2 lines or
46 | # - use Apache >= 2.3.9 and replace all L flags by END flags and remove the
47 | # following RewriteCond (best solution)
48 | RewriteCond %{ENV:REDIRECT_STATUS} ^$
49 | RewriteRule ^index\.php(?:/(.*)|$) %{ENV:BASE}/$1 [R=301,L]
50 |
51 | # If the requested filename exists, simply serve it.
52 | # We only want to let Apache serve files and not directories.
53 | RewriteCond %{REQUEST_FILENAME} -f
54 | RewriteRule ^ - [L]
55 |
56 | # Rewrite all other queries to the front controller.
57 | RewriteRule ^ %{ENV:BASE}/index.php [L]
58 |
59 |
60 |
61 |
62 | # When mod_rewrite is not available, we instruct a temporary redirect of
63 | # the start page to the front controller explicitly so that the website
64 | # and the generated links can still be used.
65 | RedirectMatch 307 ^/$ /index.php/
66 | # RedirectTemp cannot be used instead
67 |
68 |
69 |
--------------------------------------------------------------------------------
/sources/app/src/Entity/User.php:
--------------------------------------------------------------------------------
1 | roles = [];
76 | }
77 |
78 | /**
79 | * @ORM\PrePersist
80 | *
81 | * @throws Exception
82 | */
83 | public function onPrePersist(): void
84 | {
85 | $this->id = Uuid::uuid4();
86 | $this->created = new DateTime('NOW');
87 | }
88 |
89 | /**
90 | * @ORM\PreUpdate
91 | */
92 | public function onPreUpdate(): void
93 | {
94 | $this->updated = new DateTime('NOW');
95 | }
96 |
97 | public function getId(): UuidInterface
98 | {
99 | return $this->id;
100 | }
101 |
102 | public function getLogin(): string
103 | {
104 | return $this->login;
105 | }
106 |
107 | public function setLogin(string $login): void
108 | {
109 | $this->login = $login;
110 | }
111 |
112 | public function getUsername(): string
113 | {
114 | return $this->login;
115 | }
116 |
117 | public function getPlainPassword(): ?string
118 | {
119 | return $this->plainPassword;
120 | }
121 |
122 | public function setPlainPassword(string $password): void
123 | {
124 | $this->plainPassword = $password;
125 |
126 | // forces the object to look "dirty" to Doctrine. Avoids
127 | // Doctrine *not* saving this entity, if only plainPassword changes
128 | $this->password = null;
129 | }
130 |
131 | public function getPassword(): ?string
132 | {
133 | return $this->password;
134 | }
135 |
136 | public function setPassword(string $password): void
137 | {
138 | $this->password = $password;
139 | }
140 |
141 | /**
142 | * @return null
143 | */
144 | public function getSalt()
145 | {
146 | // The bcrypt algorithm doesn't require a separate salt.
147 | return null;
148 | }
149 |
150 | /**
151 | * @return string[]
152 | */
153 | public function getRoles(): array
154 | {
155 | return $this->roles;
156 | }
157 |
158 | /**
159 | * @param string[] $roles
160 | */
161 | public function setRoles(array $roles): void
162 | {
163 | $this->roles = $roles;
164 | }
165 |
166 | public function eraseCredentials(): void
167 | {
168 | $this->plainPassword = null;
169 | }
170 |
171 | public function getCreated(): DateTime
172 | {
173 | return $this->created;
174 | }
175 |
176 | public function getUpdated(): ?DateTime
177 | {
178 | return $this->updated;
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/sources/app/symfony.lock:
--------------------------------------------------------------------------------
1 | {
2 | "composer/xdebug-handler": {
3 | "version": "1.3.3"
4 | },
5 | "dealerdirect/phpcodesniffer-composer-installer": {
6 | "version": "v0.5.0"
7 | },
8 | "doctrine/annotations": {
9 | "version": "1.0",
10 | "recipe": {
11 | "repo": "github.com/symfony/recipes",
12 | "branch": "master",
13 | "version": "1.0",
14 | "ref": "cb4152ebcadbe620ea2261da1a1c5a9b8cea7672"
15 | },
16 | "files": [
17 | "config/routes/annotations.yaml"
18 | ]
19 | },
20 | "doctrine/cache": {
21 | "version": "v1.8.0"
22 | },
23 | "doctrine/coding-standard": {
24 | "version": "6.0.0"
25 | },
26 | "doctrine/collections": {
27 | "version": "v1.6.2"
28 | },
29 | "doctrine/common": {
30 | "version": "v2.10.0"
31 | },
32 | "doctrine/data-fixtures": {
33 | "version": "v1.3.1"
34 | },
35 | "doctrine/dbal": {
36 | "version": "v2.9.2"
37 | },
38 | "doctrine/doctrine-bundle": {
39 | "version": "1.6",
40 | "recipe": {
41 | "repo": "github.com/symfony/recipes",
42 | "branch": "master",
43 | "version": "1.6",
44 | "ref": "02bc9e7994b70f4fda004131a0c78b7b1bf09789"
45 | },
46 | "files": [
47 | "config/packages/doctrine.yaml",
48 | "config/packages/prod/doctrine.yaml",
49 | "src/Entity/.gitignore",
50 | "src/Repository/.gitignore"
51 | ]
52 | },
53 | "doctrine/doctrine-cache-bundle": {
54 | "version": "1.3.5"
55 | },
56 | "doctrine/doctrine-fixtures-bundle": {
57 | "version": "3.0",
58 | "recipe": {
59 | "repo": "github.com/symfony/recipes",
60 | "branch": "master",
61 | "version": "3.0",
62 | "ref": "fc52d86631a6dfd9fdf3381d0b7e3df2069e51b3"
63 | },
64 | "files": [
65 | "src/DataFixtures/AppFixtures.php"
66 | ]
67 | },
68 | "doctrine/doctrine-migrations-bundle": {
69 | "version": "1.2",
70 | "recipe": {
71 | "repo": "github.com/symfony/recipes",
72 | "branch": "master",
73 | "version": "1.2",
74 | "ref": "c1431086fec31f17fbcfe6d6d7e92059458facc1"
75 | },
76 | "files": [
77 | "config/packages/doctrine_migrations.yaml",
78 | "src/Migrations/.gitignore"
79 | ]
80 | },
81 | "doctrine/event-manager": {
82 | "version": "v1.0.0"
83 | },
84 | "doctrine/inflector": {
85 | "version": "v1.3.0"
86 | },
87 | "doctrine/instantiator": {
88 | "version": "1.2.0"
89 | },
90 | "doctrine/lexer": {
91 | "version": "1.0.2"
92 | },
93 | "doctrine/migrations": {
94 | "version": "v2.1.0"
95 | },
96 | "doctrine/orm": {
97 | "version": "v2.6.3"
98 | },
99 | "doctrine/persistence": {
100 | "version": "1.1.1"
101 | },
102 | "doctrine/reflection": {
103 | "version": "v1.0.0"
104 | },
105 | "easycorp/easy-log-handler": {
106 | "version": "1.0",
107 | "recipe": {
108 | "repo": "github.com/symfony/recipes",
109 | "branch": "master",
110 | "version": "1.0",
111 | "ref": "70062abc2cd58794d2a90274502f81b55cd9951b"
112 | },
113 | "files": [
114 | "config/packages/dev/easy_log_handler.yaml"
115 | ]
116 | },
117 | "egulias/email-validator": {
118 | "version": "2.1.8"
119 | },
120 | "fig/link-util": {
121 | "version": "1.0.0"
122 | },
123 | "friendsofsymfony/rest-bundle": {
124 | "version": "2.2",
125 | "recipe": {
126 | "repo": "github.com/symfony/recipes-contrib",
127 | "branch": "master",
128 | "version": "2.2",
129 | "ref": "258300d52be6ad59b32a888d5ddafbf9638540ff"
130 | },
131 | "files": [
132 | "config/packages/fos_rest.yaml"
133 | ]
134 | },
135 | "jdorn/sql-formatter": {
136 | "version": "v1.2.17"
137 | },
138 | "jean85/pretty-package-versions": {
139 | "version": "1.2"
140 | },
141 | "monolog/monolog": {
142 | "version": "1.24.0"
143 | },
144 | "nette/bootstrap": {
145 | "version": "v3.0.0"
146 | },
147 | "nette/di": {
148 | "version": "v3.0.0"
149 | },
150 | "nette/finder": {
151 | "version": "v2.5.0"
152 | },
153 | "nette/neon": {
154 | "version": "v3.0.0"
155 | },
156 | "nette/php-generator": {
157 | "version": "v3.2.2"
158 | },
159 | "nette/robot-loader": {
160 | "version": "v3.2.0"
161 | },
162 | "nette/schema": {
163 | "version": "v1.0.0"
164 | },
165 | "nette/utils": {
166 | "version": "v3.0.1"
167 | },
168 | "nikic/php-parser": {
169 | "version": "v4.2.2"
170 | },
171 | "ocramius/proxy-manager": {
172 | "version": "2.1.1"
173 | },
174 | "phpdocumentor/reflection-common": {
175 | "version": "1.0.1"
176 | },
177 | "phpdocumentor/reflection-docblock": {
178 | "version": "4.3.1"
179 | },
180 | "phpdocumentor/type-resolver": {
181 | "version": "0.4.0"
182 | },
183 | "phpstan/phpdoc-parser": {
184 | "version": "0.3.5"
185 | },
186 | "phpstan/phpstan": {
187 | "version": "0.11.8"
188 | },
189 | "psr/cache": {
190 | "version": "1.0.1"
191 | },
192 | "psr/container": {
193 | "version": "1.0.0"
194 | },
195 | "psr/link": {
196 | "version": "1.0.0"
197 | },
198 | "psr/log": {
199 | "version": "1.1.0"
200 | },
201 | "ramsey/uuid": {
202 | "version": "3.8.0"
203 | },
204 | "ramsey/uuid-doctrine": {
205 | "version": "1.3",
206 | "recipe": {
207 | "repo": "github.com/symfony/recipes-contrib",
208 | "branch": "master",
209 | "version": "1.3",
210 | "ref": "471aed0fbf5620b8d7f92b7a5ebbbf6c0945c27a"
211 | },
212 | "files": [
213 | "config/packages/ramsey_uuid_doctrine.yaml"
214 | ]
215 | },
216 | "sensio/framework-extra-bundle": {
217 | "version": "5.2",
218 | "recipe": {
219 | "repo": "github.com/symfony/recipes",
220 | "branch": "master",
221 | "version": "5.2",
222 | "ref": "fb7e19da7f013d0d422fa9bce16f5c510e27609b"
223 | },
224 | "files": [
225 | "config/packages/sensio_framework_extra.yaml"
226 | ]
227 | },
228 | "slevomat/coding-standard": {
229 | "version": "5.0.4"
230 | },
231 | "squizlabs/php_codesniffer": {
232 | "version": "3.0",
233 | "recipe": {
234 | "repo": "github.com/symfony/recipes-contrib",
235 | "branch": "master",
236 | "version": "3.0",
237 | "ref": "0dc9cceda799fd3a08b96987e176a261028a3709"
238 | },
239 | "files": [
240 | "phpcs.xml.dist"
241 | ]
242 | },
243 | "swiftmailer/swiftmailer": {
244 | "version": "v6.2.1"
245 | },
246 | "symfony/apache-pack": {
247 | "version": "1.0",
248 | "recipe": {
249 | "repo": "github.com/symfony/recipes-contrib",
250 | "branch": "master",
251 | "version": "1.0",
252 | "ref": "c82bead70f9a4f656354a193df7bf0ca2114efa0"
253 | },
254 | "files": [
255 | "public/.htaccess"
256 | ]
257 | },
258 | "symfony/asset": {
259 | "version": "v4.3.1"
260 | },
261 | "symfony/browser-kit": {
262 | "version": "v4.3.1"
263 | },
264 | "symfony/cache": {
265 | "version": "v4.3.1"
266 | },
267 | "symfony/cache-contracts": {
268 | "version": "v1.1.1"
269 | },
270 | "symfony/config": {
271 | "version": "v4.3.1"
272 | },
273 | "symfony/console": {
274 | "version": "3.3",
275 | "recipe": {
276 | "repo": "github.com/symfony/recipes",
277 | "branch": "master",
278 | "version": "3.3",
279 | "ref": "482d233eb8de91ebd042992077bbd5838858890c"
280 | },
281 | "files": [
282 | "bin/console",
283 | "config/bootstrap.php"
284 | ]
285 | },
286 | "symfony/css-selector": {
287 | "version": "v4.3.1"
288 | },
289 | "symfony/debug": {
290 | "version": "v4.3.1"
291 | },
292 | "symfony/debug-bundle": {
293 | "version": "4.1",
294 | "recipe": {
295 | "repo": "github.com/symfony/recipes",
296 | "branch": "master",
297 | "version": "4.1",
298 | "ref": "f8863cbad2f2e58c4b65fa1eac892ab189971bea"
299 | },
300 | "files": [
301 | "config/packages/dev/debug.yaml"
302 | ]
303 | },
304 | "symfony/debug-pack": {
305 | "version": "v1.0.7"
306 | },
307 | "symfony/dependency-injection": {
308 | "version": "v4.3.1"
309 | },
310 | "symfony/doctrine-bridge": {
311 | "version": "v4.3.1"
312 | },
313 | "symfony/dom-crawler": {
314 | "version": "v4.3.1"
315 | },
316 | "symfony/dotenv": {
317 | "version": "v4.3.1"
318 | },
319 | "symfony/event-dispatcher": {
320 | "version": "v4.3.1"
321 | },
322 | "symfony/event-dispatcher-contracts": {
323 | "version": "v1.1.1"
324 | },
325 | "symfony/expression-language": {
326 | "version": "v4.3.1"
327 | },
328 | "symfony/filesystem": {
329 | "version": "v4.3.1"
330 | },
331 | "symfony/finder": {
332 | "version": "v4.3.1"
333 | },
334 | "symfony/flex": {
335 | "version": "1.0",
336 | "recipe": {
337 | "repo": "github.com/symfony/recipes",
338 | "branch": "master",
339 | "version": "1.0",
340 | "ref": "dc3fc2e0334a4137c47cfd5a3ececc601fa61a0b"
341 | },
342 | "files": [
343 | ".env"
344 | ]
345 | },
346 | "symfony/form": {
347 | "version": "v4.3.1"
348 | },
349 | "symfony/framework-bundle": {
350 | "version": "4.2",
351 | "recipe": {
352 | "repo": "github.com/symfony/recipes",
353 | "branch": "master",
354 | "version": "4.2",
355 | "ref": "f5cb896a182fa2b845c41d287d03cd61279b7ba5"
356 | },
357 | "files": [
358 | "config/bootstrap.php",
359 | "config/packages/cache.yaml",
360 | "config/packages/framework.yaml",
361 | "config/packages/test/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": "v4.3.1"
370 | },
371 | "symfony/http-client-contracts": {
372 | "version": "v1.1.3"
373 | },
374 | "symfony/http-foundation": {
375 | "version": "v4.3.1"
376 | },
377 | "symfony/http-kernel": {
378 | "version": "v4.3.1"
379 | },
380 | "symfony/inflector": {
381 | "version": "v4.3.1"
382 | },
383 | "symfony/intl": {
384 | "version": "v4.3.1"
385 | },
386 | "symfony/maker-bundle": {
387 | "version": "1.0",
388 | "recipe": {
389 | "repo": "github.com/symfony/recipes",
390 | "branch": "master",
391 | "version": "1.0",
392 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
393 | }
394 | },
395 | "symfony/mime": {
396 | "version": "v4.3.1"
397 | },
398 | "symfony/monolog-bridge": {
399 | "version": "v4.3.1"
400 | },
401 | "symfony/monolog-bundle": {
402 | "version": "3.3",
403 | "recipe": {
404 | "repo": "github.com/symfony/recipes",
405 | "branch": "master",
406 | "version": "3.3",
407 | "ref": "6240c6d43e8237a32452f057f81816820fd56ab6"
408 | },
409 | "files": [
410 | "config/packages/dev/monolog.yaml",
411 | "config/packages/prod/monolog.yaml",
412 | "config/packages/test/monolog.yaml"
413 | ]
414 | },
415 | "symfony/options-resolver": {
416 | "version": "v4.3.1"
417 | },
418 | "symfony/orm-pack": {
419 | "version": "v1.0.6"
420 | },
421 | "symfony/phpunit-bridge": {
422 | "version": "4.3",
423 | "recipe": {
424 | "repo": "github.com/symfony/recipes",
425 | "branch": "master",
426 | "version": "4.3",
427 | "ref": "b0582341f1df39aaf3a9a866cdbe49937da35984"
428 | },
429 | "files": [
430 | ".env.test",
431 | "bin/phpunit",
432 | "config/bootstrap.php",
433 | "phpunit.xml.dist",
434 | "tests/.gitignore"
435 | ]
436 | },
437 | "symfony/polyfill-intl-icu": {
438 | "version": "v1.11.0"
439 | },
440 | "symfony/polyfill-intl-idn": {
441 | "version": "v1.11.0"
442 | },
443 | "symfony/polyfill-mbstring": {
444 | "version": "v1.11.0"
445 | },
446 | "symfony/polyfill-php72": {
447 | "version": "v1.11.0"
448 | },
449 | "symfony/polyfill-php73": {
450 | "version": "v1.11.0"
451 | },
452 | "symfony/process": {
453 | "version": "v4.3.1"
454 | },
455 | "symfony/profiler-pack": {
456 | "version": "v1.0.4"
457 | },
458 | "symfony/property-access": {
459 | "version": "v4.3.1"
460 | },
461 | "symfony/property-info": {
462 | "version": "v4.3.1"
463 | },
464 | "symfony/routing": {
465 | "version": "4.2",
466 | "recipe": {
467 | "repo": "github.com/symfony/recipes",
468 | "branch": "master",
469 | "version": "4.2",
470 | "ref": "5374e24d508ba8fd6ba9eb15170255fdb778316a"
471 | },
472 | "files": [
473 | "config/packages/dev/routing.yaml",
474 | "config/packages/routing.yaml",
475 | "config/packages/test/routing.yaml",
476 | "config/routes.yaml"
477 | ]
478 | },
479 | "symfony/security-bundle": {
480 | "version": "3.3",
481 | "recipe": {
482 | "repo": "github.com/symfony/recipes",
483 | "branch": "master",
484 | "version": "3.3",
485 | "ref": "9a2034eca6d83d9cda632014e06995b8d9d9fd09"
486 | },
487 | "files": [
488 | "config/packages/security.yaml"
489 | ]
490 | },
491 | "symfony/security-core": {
492 | "version": "v4.3.1"
493 | },
494 | "symfony/security-csrf": {
495 | "version": "v4.3.1"
496 | },
497 | "symfony/security-guard": {
498 | "version": "v4.3.1"
499 | },
500 | "symfony/security-http": {
501 | "version": "v4.3.1"
502 | },
503 | "symfony/serializer": {
504 | "version": "v4.3.1"
505 | },
506 | "symfony/serializer-pack": {
507 | "version": "v1.0.2"
508 | },
509 | "symfony/service-contracts": {
510 | "version": "v1.1.2"
511 | },
512 | "symfony/stopwatch": {
513 | "version": "v4.3.1"
514 | },
515 | "symfony/swiftmailer-bundle": {
516 | "version": "2.5",
517 | "recipe": {
518 | "repo": "github.com/symfony/recipes",
519 | "branch": "master",
520 | "version": "2.5",
521 | "ref": "3db029c03e452b4a23f7fc45cec7c922c2247eb8"
522 | },
523 | "files": [
524 | "config/packages/dev/swiftmailer.yaml",
525 | "config/packages/swiftmailer.yaml",
526 | "config/packages/test/swiftmailer.yaml"
527 | ]
528 | },
529 | "symfony/templating": {
530 | "version": "v4.3.1"
531 | },
532 | "symfony/test-pack": {
533 | "version": "v1.0.5"
534 | },
535 | "symfony/translation": {
536 | "version": "3.3",
537 | "recipe": {
538 | "repo": "github.com/symfony/recipes",
539 | "branch": "master",
540 | "version": "3.3",
541 | "ref": "1fb02a6e1c8f3d4232cce485c9afa868d63b115a"
542 | },
543 | "files": [
544 | "config/packages/translation.yaml",
545 | "translations/.gitignore"
546 | ]
547 | },
548 | "symfony/translation-contracts": {
549 | "version": "v1.1.2"
550 | },
551 | "symfony/twig-bridge": {
552 | "version": "v4.3.1"
553 | },
554 | "symfony/twig-bundle": {
555 | "version": "3.3",
556 | "recipe": {
557 | "repo": "github.com/symfony/recipes",
558 | "branch": "master",
559 | "version": "3.3",
560 | "ref": "369b5b29dc52b2c190002825ae7ec24ab6f962dd"
561 | },
562 | "files": [
563 | "config/packages/twig.yaml",
564 | "config/routes/dev/twig.yaml",
565 | "templates/base.html.twig"
566 | ]
567 | },
568 | "symfony/validator": {
569 | "version": "4.3",
570 | "recipe": {
571 | "repo": "github.com/symfony/recipes",
572 | "branch": "master",
573 | "version": "4.3",
574 | "ref": "48a8e5ef925e82aa7d665c887cd1fa60f8b7921d"
575 | },
576 | "files": [
577 | "config/packages/test/validator.yaml",
578 | "config/packages/validator.yaml"
579 | ]
580 | },
581 | "symfony/var-dumper": {
582 | "version": "v4.3.1"
583 | },
584 | "symfony/var-exporter": {
585 | "version": "v4.3.1"
586 | },
587 | "symfony/web-link": {
588 | "version": "v4.3.1"
589 | },
590 | "symfony/web-profiler-bundle": {
591 | "version": "3.3",
592 | "recipe": {
593 | "repo": "github.com/symfony/recipes",
594 | "branch": "master",
595 | "version": "3.3",
596 | "ref": "6bdfa1a95f6b2e677ab985cd1af2eae35d62e0f6"
597 | },
598 | "files": [
599 | "config/packages/dev/web_profiler.yaml",
600 | "config/packages/test/web_profiler.yaml",
601 | "config/routes/dev/web_profiler.yaml"
602 | ]
603 | },
604 | "symfony/web-server-bundle": {
605 | "version": "3.3",
606 | "recipe": {
607 | "repo": "github.com/symfony/recipes",
608 | "branch": "master",
609 | "version": "3.3",
610 | "ref": "dae9b39fd6717970be7601101ce5aa960bf53d9a"
611 | }
612 | },
613 | "symfony/webpack-encore-bundle": {
614 | "version": "1.0",
615 | "recipe": {
616 | "repo": "github.com/symfony/recipes",
617 | "branch": "master",
618 | "version": "1.0",
619 | "ref": "e33393e32759195f8e10c9d4faceb0d232a7e1de"
620 | },
621 | "files": [
622 | "assets/css/app.css",
623 | "assets/js/app.js",
624 | "config/packages/assets.yaml",
625 | "config/packages/prod/webpack_encore.yaml",
626 | "config/packages/webpack_encore.yaml",
627 | "package.json",
628 | "webpack.config.js"
629 | ]
630 | },
631 | "symfony/yaml": {
632 | "version": "v4.3.1"
633 | },
634 | "thecodingmachine/phpstan-safe-rule": {
635 | "version": "v0.1.3"
636 | },
637 | "thecodingmachine/phpstan-strict-rules": {
638 | "version": "v0.11.2"
639 | },
640 | "thecodingmachine/safe": {
641 | "version": "v0.1.15"
642 | },
643 | "twig/twig": {
644 | "version": "v2.11.2"
645 | },
646 | "webmozart/assert": {
647 | "version": "1.4.0"
648 | },
649 | "willdurand/jsonp-callback-validator": {
650 | "version": "v1.1.0"
651 | },
652 | "willdurand/negotiation": {
653 | "version": "v2.3.1"
654 | },
655 | "zendframework/zend-code": {
656 | "version": "3.3.1"
657 | },
658 | "zendframework/zend-eventmanager": {
659 | "version": "3.2.1"
660 | }
661 | }
662 |
--------------------------------------------------------------------------------