├── .env ├── .env.test ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .php-cs-fixer.php ├── README.md ├── assets ├── app.js ├── bootstrap.js ├── controllers.json ├── controllers │ └── hello_controller.js └── styles │ └── app.css ├── bin ├── console └── phpunit ├── compose.override.yaml ├── compose.yaml ├── composer.json ├── composer.lock ├── config ├── bundles.php ├── packages │ ├── asset_mapper.yaml │ ├── cache.yaml │ ├── debug.yaml │ ├── doctrine.yaml │ ├── doctrine_migrations.yaml │ ├── framework.yaml │ ├── mailer.yaml │ ├── messenger.yaml │ ├── monolog.yaml │ ├── notifier.yaml │ ├── reset_password.yaml │ ├── routing.yaml │ ├── security.yaml │ ├── test │ │ └── dama_doctrine_test_bundle.yaml │ ├── translation.yaml │ ├── twig.yaml │ ├── twig_component.yaml │ ├── validator.yaml │ └── web_profiler.yaml ├── preload.php ├── routes.yaml ├── routes │ ├── framework.yaml │ └── web_profiler.yaml └── services.yaml ├── importmap.php ├── migrations ├── .gitignore └── Version20231128211533.php ├── phpstan.dist.neon ├── phpunit.xml.dist ├── public └── index.php ├── src ├── Controller │ ├── .gitignore │ ├── DashBoardController.php │ ├── HomeController.php │ ├── ResetPasswordController.php │ └── SecurityController.php ├── DataFixtures │ └── AppFixtures.php ├── Entity │ ├── .gitignore │ ├── ResetPasswordRequest.php │ └── User.php ├── Form │ ├── ChangePasswordFormType.php │ ├── RegisterFormType.php │ └── ResetPasswordRequestFormType.php ├── Kernel.php ├── Repository │ ├── .gitignore │ ├── ResetPasswordRequestRepository.php │ └── UserRepository.php └── Security │ └── RegisterUser.php ├── symfony.lock ├── tailwind.config.js ├── templates ├── base.html.twig ├── components │ ├── Footer.html.twig │ └── NavBar.html.twig ├── dashboard.html.twig ├── home.html.twig ├── layout.html.twig ├── reset_password │ ├── check_email.html.twig │ ├── email.html.twig │ ├── request.html.twig │ └── reset.html.twig ├── security │ ├── login.html.twig │ └── signup.html.twig └── security_layout.html.twig ├── tests ├── Controller │ ├── HomeControllerTest.php │ └── SecurityControllerTest.php └── bootstrap.php └── translations └── .gitignore /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # https://symfony.com/doc/current/configuration/secrets.html 13 | # 14 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 15 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration 16 | 17 | ###> symfony/framework-bundle ### 18 | APP_ENV=dev 19 | APP_SECRET=06238bce12e10d0eb70c87012f9cefae 20 | ###< symfony/framework-bundle ### 21 | 22 | ###> doctrine/doctrine-bundle ### 23 | # Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url 24 | # IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml 25 | # 26 | # DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" 27 | # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4" 28 | # DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4" 29 | DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db" 30 | ###< doctrine/doctrine-bundle ### 31 | 32 | ###> symfony/messenger ### 33 | # Choose one of the transports below 34 | # MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages 35 | # MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages 36 | MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0 37 | ###< symfony/messenger ### 38 | 39 | ###> symfony/mailer ### 40 | MAILER_DSN=null://null 41 | ###< symfony/mailer ### 42 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='$ecretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | PANTHER_APP_ENV=panther 6 | PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots 7 | DATABASE_URL="sqlite:///%kernel.project_dir%/var/data_test.db" 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI process for Symfony StarterKit 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | symfony: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | - name: Set up PHP 14 | uses: shivammathur/setup-php@v2 15 | with: 16 | php-version: '8.2' 17 | extensions: mbstring, xml, ctype, iconv, intl, pdo, dom, filter, gd, iconv, json, mbstring 18 | tools: php-cs-fixer 19 | - name: Install Composer dependencies 20 | run: composer install --no-progress --prefer-dist --optimize-autoloader 21 | - name: Run PHPStan 22 | run: vendor/bin/phpstan analyse 23 | - name: Run PHPCsFixer 24 | run: php-cs-fixer fix src tests --config=.php-cs-fixer.php --dry-run --diff --allow-risky=yes 25 | - name: Install test DB 26 | run: php bin/console doctrine:database:create --env=test 27 | - name: Run migrations 28 | run: php bin/console doctrine:migrations:migrate --env=test --no-interaction 29 | - name: Build assets 30 | run: php bin/console tailwind:build 31 | - name: Run tests 32 | run: php bin/phpunit 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | ###> symfony/framework-bundle ### 3 | /.env.local 4 | /.env.local.php 5 | /.env.*.local 6 | /config/secrets/prod/prod.decrypt.private.php 7 | /public/bundles/ 8 | /var/ 9 | /vendor/ 10 | ###< symfony/framework-bundle ### 11 | 12 | ###> phpunit/phpunit ### 13 | /phpunit.xml 14 | .phpunit.result.cache 15 | ###< phpunit/phpunit ### 16 | 17 | ###> symfony/phpunit-bridge ### 18 | .phpunit.result.cache 19 | /phpunit.xml 20 | ###< symfony/phpunit-bridge ### 21 | 22 | ###> symfony/asset-mapper ### 23 | /public/assets/ 24 | ###< symfony/asset-mapper ### 25 | 26 | ###> phpstan/phpstan ### 27 | phpstan.neon 28 | ###< phpstan/phpstan ### 29 | 30 | ###> friendsofphp/php-cs-fixer ### 31 | /.php-cs-fixer.php 32 | /.php-cs-fixer.cache 33 | .php-cs-fixer.cache 34 | .php-cs-fixer.dist.php 35 | ###< friendsofphp/php-cs-fixer ### 36 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | in(__DIR__) 5 | ; 6 | 7 | return (new PhpCsFixer\Config()) 8 | ->setRules([ 9 | '@PhpCsFixer' => true, 10 | '@PHP80Migration:risky' => true 11 | ]) 12 | ->setFinder($finder) 13 | ; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SymfonyStarterKit 2 | A simple StarterKit to help you getting starded blazzing fast on a new Symfony project. 3 | 4 | Capture d’écran 2023-11-29 à 15 56 37 5 | 6 | ## Features 7 | - Basic HomePage 8 | - Login/SignUp/ResetPassword pages already fonctionnal 9 | - Simple Dashboard 10 | - AssetMapper integration with Tailwind 11 | - Tests 12 | - Phpstan already set up at the max level 13 | - CI 14 | - SQLite 15 | 16 | ## Requirements 17 | 18 | The same as the one mentions here: https://symfony.com/doc/current/setup.html#technical-requirements 19 | 20 | ## How to install it locally ? 21 | 22 | 1. use this repo as a template for your repo (click the green button above) 23 | 2. git clone your new repo locally 24 | 3. at the root of your project run: `composer install` 25 | 4. install the db run: `bin/console doctrine:database:create` 26 | 5. run the migration: `bin/console doctrine:migrations:migrate` 27 | 6. build your assets: `bin/console tailwind:build` 28 | 29 | And Voilà ✨ you are ready to go! Enjoy 30 | 31 | Capture d’écran 2023-11-29 à 15 56 51 32 | -------------------------------------------------------------------------------- /assets/app.js: -------------------------------------------------------------------------------- 1 | import './bootstrap.js'; 2 | /* 3 | * Welcome to your app's main JavaScript file! 4 | * 5 | * This file will be included onto the page via the importmap() Twig function, 6 | * which should already be in your base.html.twig. 7 | */ 8 | console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉') 9 | -------------------------------------------------------------------------------- /assets/bootstrap.js: -------------------------------------------------------------------------------- 1 | import { startStimulusApp } from '@symfony/stimulus-bundle'; 2 | 3 | const app = startStimulusApp(); 4 | // register any custom, 3rd party controllers here 5 | // app.register('some_controller_name', SomeImportedController); 6 | -------------------------------------------------------------------------------- /assets/controllers.json: -------------------------------------------------------------------------------- 1 | { 2 | "controllers": [], 3 | "entrypoints": [] 4 | } 5 | -------------------------------------------------------------------------------- /assets/controllers/hello_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from '@hotwired/stimulus'; 2 | 3 | /* 4 | * This is an example Stimulus controller! 5 | * 6 | * Any element with a data-controller="hello" attribute will cause 7 | * this controller to be executed. The name "hello" comes from the filename: 8 | * hello_controller.js -> "hello" 9 | * 10 | * Delete this file or adapt it for your use! 11 | */ 12 | export default class extends Controller { 13 | connect() { 14 | this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js'; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /assets/styles/app.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | = 80000) { 10 | require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit'; 11 | } else { 12 | define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php'); 13 | require PHPUNIT_COMPOSER_INSTALL; 14 | PHPUnit\TextUI\Command::main(); 15 | } 16 | } else { 17 | if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) { 18 | echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n"; 19 | exit(1); 20 | } 21 | 22 | require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php'; 23 | } 24 | -------------------------------------------------------------------------------- /compose.override.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | ###> doctrine/doctrine-bundle ### 5 | database: 6 | ports: 7 | - "5432" 8 | ###< doctrine/doctrine-bundle ### 9 | 10 | ###> symfony/mailer ### 11 | mailer: 12 | image: schickling/mailcatcher 13 | ports: ["1025", "1080"] 14 | ###< symfony/mailer ### 15 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | ###> doctrine/doctrine-bundle ### 5 | database: 6 | image: postgres:${POSTGRES_VERSION:-15}-alpine 7 | environment: 8 | POSTGRES_DB: ${POSTGRES_DB:-app} 9 | # You should definitely change the password in production 10 | POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!} 11 | POSTGRES_USER: ${POSTGRES_USER:-app} 12 | volumes: 13 | - database_data:/var/lib/postgresql/data:rw 14 | # You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data! 15 | # - ./docker/db/data:/var/lib/postgresql/data:rw 16 | ###< doctrine/doctrine-bundle ### 17 | 18 | volumes: 19 | ###> doctrine/doctrine-bundle ### 20 | database_data: 21 | ###< doctrine/doctrine-bundle ### 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "project", 3 | "license": "proprietary", 4 | "minimum-stability": "stable", 5 | "prefer-stable": true, 6 | "require": { 7 | "php": ">=8.1", 8 | "ext-ctype": "*", 9 | "ext-iconv": "*", 10 | "doctrine/doctrine-bundle": "^2.11", 11 | "doctrine/doctrine-migrations-bundle": "^3.3", 12 | "doctrine/orm": "^2.17", 13 | "friendsofphp/php-cs-fixer": "^3.40", 14 | "phpdocumentor/reflection-docblock": "^5.3", 15 | "phpstan/phpdoc-parser": "^1.24", 16 | "symfony/asset": "6.3.*", 17 | "symfony/asset-mapper": "6.3.*", 18 | "symfony/console": "6.3.*", 19 | "symfony/doctrine-messenger": "6.3.*", 20 | "symfony/dotenv": "6.3.*", 21 | "symfony/expression-language": "6.3.*", 22 | "symfony/flex": "^2", 23 | "symfony/form": "6.3.*", 24 | "symfony/framework-bundle": "6.3.*", 25 | "symfony/http-client": "6.3.*", 26 | "symfony/intl": "6.3.*", 27 | "symfony/mailer": "6.3.*", 28 | "symfony/mime": "6.3.*", 29 | "symfony/monolog-bundle": "^3.0", 30 | "symfony/notifier": "6.3.*", 31 | "symfony/process": "6.3.*", 32 | "symfony/property-access": "6.3.*", 33 | "symfony/property-info": "6.3.*", 34 | "symfony/runtime": "6.3.*", 35 | "symfony/security-bundle": "6.3.*", 36 | "symfony/serializer": "6.3.*", 37 | "symfony/stimulus-bundle": "^2.13", 38 | "symfony/string": "6.3.*", 39 | "symfony/translation": "6.3.*", 40 | "symfony/twig-bundle": "6.3.*", 41 | "symfony/ux-twig-component": "^2.13", 42 | "symfony/validator": "6.3.*", 43 | "symfony/web-link": "6.3.*", 44 | "symfony/yaml": "6.3.*", 45 | "symfonycasts/reset-password-bundle": "^1.18", 46 | "symfonycasts/tailwind-bundle": "^0.2.1", 47 | "twig/extra-bundle": "^3.0", 48 | "twig/twig": "^3.0" 49 | }, 50 | "config": { 51 | "allow-plugins": { 52 | "php-http/discovery": true, 53 | "symfony/flex": true, 54 | "symfony/runtime": true 55 | }, 56 | "sort-packages": true 57 | }, 58 | "autoload": { 59 | "psr-4": { 60 | "App\\": "src/" 61 | } 62 | }, 63 | "autoload-dev": { 64 | "psr-4": { 65 | "App\\Tests\\": "tests/" 66 | } 67 | }, 68 | "replace": { 69 | "symfony/polyfill-ctype": "*", 70 | "symfony/polyfill-iconv": "*", 71 | "symfony/polyfill-php72": "*", 72 | "symfony/polyfill-php73": "*", 73 | "symfony/polyfill-php74": "*", 74 | "symfony/polyfill-php80": "*", 75 | "symfony/polyfill-php81": "*" 76 | }, 77 | "scripts": { 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": "6.3.*" 96 | } 97 | }, 98 | "require-dev": { 99 | "dama/doctrine-test-bundle": "^7", 100 | "doctrine/doctrine-fixtures-bundle": "^3.5", 101 | "phpstan/phpstan": "^1.10", 102 | "phpunit/phpunit": "^9.5", 103 | "symfony/browser-kit": "6.3.*", 104 | "symfony/css-selector": "6.3.*", 105 | "symfony/debug-bundle": "6.3.*", 106 | "symfony/maker-bundle": "^1.0", 107 | "symfony/phpunit-bridge": "^6.3", 108 | "symfony/stopwatch": "6.3.*", 109 | "symfony/web-profiler-bundle": "6.3.*" 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], 6 | Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], 7 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], 8 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 9 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 10 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], 11 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 12 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 13 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 14 | SymfonyCasts\Bundle\ResetPassword\SymfonyCastsResetPasswordBundle::class => ['all' => true], 15 | Symfonycasts\TailwindBundle\SymfonycastsTailwindBundle::class => ['all' => true], 16 | Symfony\UX\TwigComponent\TwigComponentBundle::class => ['all' => true], 17 | Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true], 18 | Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true], 19 | DAMA\DoctrineTestBundle\DAMADoctrineTestBundle::class => ['test' => true], 20 | ]; 21 | -------------------------------------------------------------------------------- /config/packages/asset_mapper.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | asset_mapper: 3 | # The paths to make available to the asset mapper. 4 | paths: 5 | - assets/ 6 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | # Unique name of your app: used to compute stable namespaces for cache keys. 4 | #prefix_seed: your_vendor_name/app_name 5 | 6 | # The "app" cache stores to the filesystem by default. 7 | # The data in this cache should persist between deploys. 8 | # Other options include: 9 | 10 | # Redis 11 | #app: cache.adapter.redis 12 | #default_redis_provider: redis://localhost 13 | 14 | # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues) 15 | #app: cache.adapter.apcu 16 | 17 | # Namespaced pools use the above "app" backend by default 18 | #pools: 19 | #my.dedicated.cache: null 20 | -------------------------------------------------------------------------------- /config/packages/debug.yaml: -------------------------------------------------------------------------------- 1 | when@dev: 2 | debug: 3 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 4 | # See the "server:dump" command to start a new server. 5 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 6 | -------------------------------------------------------------------------------- /config/packages/doctrine.yaml: -------------------------------------------------------------------------------- 1 | doctrine: 2 | dbal: 3 | driver: 'pdo_sqlite' 4 | url: '%env(resolve:DATABASE_URL)%' 5 | 6 | # IMPORTANT: You MUST configure your server version, 7 | # either here or in the DATABASE_URL env var (see .env file) 8 | #server_version: '15' 9 | 10 | profiling_collect_backtrace: '%kernel.debug%' 11 | orm: 12 | auto_generate_proxy_classes: true 13 | enable_lazy_ghost_objects: true 14 | report_fields_where_declared: true 15 | validate_xml_mapping: true 16 | naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware 17 | auto_mapping: true 18 | mappings: 19 | App: 20 | type: attribute 21 | is_bundle: false 22 | dir: '%kernel.project_dir%/src/Entity' 23 | prefix: 'App\Entity' 24 | alias: App 25 | 26 | when@test: 27 | doctrine: 28 | dbal: 29 | # "TEST_TOKEN" is typically set by ParaTest 30 | dbname_suffix: '_test%env(default::TEST_TOKEN)%' 31 | 32 | when@prod: 33 | doctrine: 34 | orm: 35 | auto_generate_proxy_classes: false 36 | proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies' 37 | query_cache_driver: 38 | type: pool 39 | pool: doctrine.system_cache_pool 40 | result_cache_driver: 41 | type: pool 42 | pool: doctrine.result_cache_pool 43 | 44 | framework: 45 | cache: 46 | pools: 47 | doctrine.result_cache_pool: 48 | adapter: cache.app 49 | doctrine.system_cache_pool: 50 | adapter: cache.system 51 | -------------------------------------------------------------------------------- /config/packages/doctrine_migrations.yaml: -------------------------------------------------------------------------------- 1 | doctrine_migrations: 2 | migrations_paths: 3 | # namespace is arbitrary but should be different from App\Migrations 4 | # as migrations classes should NOT be autoloaded 5 | 'DoctrineMigrations': '%kernel.project_dir%/migrations' 6 | enable_profiler: false 7 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | #csrf_protection: true 5 | http_method_override: false 6 | handle_all_throwables: true 7 | 8 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 9 | # Remove or comment this section to explicitly disable session support. 10 | session: 11 | handler_id: null 12 | cookie_secure: auto 13 | cookie_samesite: lax 14 | storage_factory_id: session.storage.factory.native 15 | 16 | #esi: true 17 | #fragments: true 18 | php_errors: 19 | log: true 20 | 21 | when@test: 22 | framework: 23 | test: true 24 | session: 25 | storage_factory_id: session.storage.factory.mock_file 26 | -------------------------------------------------------------------------------- /config/packages/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | dsn: '%env(MAILER_DSN)%' 4 | -------------------------------------------------------------------------------- /config/packages/messenger.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | messenger: 3 | failure_transport: failed 4 | 5 | transports: 6 | # https://symfony.com/doc/current/messenger.html#transport-configuration 7 | async: 8 | dsn: '%env(MESSENGER_TRANSPORT_DSN)%' 9 | options: 10 | use_notify: true 11 | check_delayed_interval: 60000 12 | retry_strategy: 13 | max_retries: 3 14 | multiplier: 2 15 | failed: 'doctrine://default?queue_name=failed' 16 | # sync: 'sync://' 17 | 18 | routing: 19 | Symfony\Component\Mailer\Messenger\SendEmailMessage: async 20 | Symfony\Component\Notifier\Message\ChatMessage: async 21 | Symfony\Component\Notifier\Message\SmsMessage: async 22 | 23 | # Route your messages to the transports 24 | # 'App\Message\YourMessage': async 25 | -------------------------------------------------------------------------------- /config/packages/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | channels: 3 | - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists 4 | 5 | when@dev: 6 | monolog: 7 | handlers: 8 | main: 9 | type: stream 10 | path: "%kernel.logs_dir%/%kernel.environment%.log" 11 | level: debug 12 | channels: ["!event"] 13 | # uncomment to get logging in your browser 14 | # you may have to allow bigger header sizes in your Web server configuration 15 | #firephp: 16 | # type: firephp 17 | # level: info 18 | #chromephp: 19 | # type: chromephp 20 | # level: info 21 | console: 22 | type: console 23 | process_psr_3_messages: false 24 | channels: ["!event", "!doctrine", "!console"] 25 | 26 | when@test: 27 | monolog: 28 | handlers: 29 | main: 30 | type: fingers_crossed 31 | action_level: error 32 | handler: nested 33 | excluded_http_codes: [404, 405] 34 | channels: ["!event"] 35 | nested: 36 | type: stream 37 | path: "%kernel.logs_dir%/%kernel.environment%.log" 38 | level: debug 39 | 40 | when@prod: 41 | monolog: 42 | handlers: 43 | main: 44 | type: fingers_crossed 45 | action_level: error 46 | handler: nested 47 | excluded_http_codes: [404, 405] 48 | buffer_size: 50 # How many messages should be saved? Prevent memory leaks 49 | nested: 50 | type: stream 51 | path: php://stderr 52 | level: debug 53 | formatter: monolog.formatter.json 54 | console: 55 | type: console 56 | process_psr_3_messages: false 57 | channels: ["!event", "!doctrine"] 58 | deprecation: 59 | type: stream 60 | channels: [deprecation] 61 | path: php://stderr 62 | -------------------------------------------------------------------------------- /config/packages/notifier.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | notifier: 3 | chatter_transports: 4 | texter_transports: 5 | channel_policy: 6 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo 7 | urgent: ['email'] 8 | high: ['email'] 9 | medium: ['email'] 10 | low: ['email'] 11 | admin_recipients: 12 | - { email: admin@example.com } 13 | -------------------------------------------------------------------------------- /config/packages/reset_password.yaml: -------------------------------------------------------------------------------- 1 | symfonycasts_reset_password: 2 | # Replace symfonycasts.reset_password.fake_request_repository with the full 3 | # namespace of the password reset request repository after it has been created. 4 | # i.e. App\Repository\ResetPasswordRequestRepository 5 | request_password_repository: symfonycasts.reset_password.fake_request_repository 6 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | utf8: true 4 | 5 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 6 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 7 | #default_uri: http://localhost 8 | 9 | when@prod: 10 | framework: 11 | router: 12 | strict_requirements: null 13 | -------------------------------------------------------------------------------- /config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords 3 | password_hashers: 4 | Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto' 5 | # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider 6 | providers: 7 | app_user_provider: 8 | entity: 9 | class: App\Entity\User 10 | property: email 11 | firewalls: 12 | dev: 13 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 14 | security: false 15 | main: 16 | lazy: true 17 | provider: app_user_provider 18 | form_login: 19 | login_path: app_security_login 20 | check_path: app_security_login 21 | enable_csrf: true 22 | default_target_path: app_dashboard_index 23 | logout: 24 | path: app_security_logout 25 | 26 | # activate different ways to authenticate 27 | # https://symfony.com/doc/current/security.html#the-firewall 28 | 29 | # https://symfony.com/doc/current/security/impersonating_user.html 30 | # switch_user: true 31 | 32 | # Easy way to control access for large sections of your site 33 | # Note: Only the *first* access control that matches will be used 34 | access_control: 35 | # - { path: ^/admin, roles: ROLE_ADMIN } 36 | # - { path: ^/profile, roles: ROLE_USER } 37 | 38 | when@test: 39 | security: 40 | password_hashers: 41 | # By default, password hashers are resource intensive and take time. This is 42 | # important to generate secure password hashes. In tests however, secure hashes 43 | # are not important, waste resources and increase test times. The following 44 | # reduces the work factor to the lowest possible values. 45 | Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 46 | algorithm: auto 47 | cost: 4 # Lowest possible value for bcrypt 48 | time_cost: 3 # Lowest possible value for argon 49 | memory_cost: 10 # Lowest possible value for argon 50 | -------------------------------------------------------------------------------- /config/packages/test/dama_doctrine_test_bundle.yaml: -------------------------------------------------------------------------------- 1 | dama_doctrine_test: 2 | enable_static_connection: true 3 | enable_static_meta_data_cache: true 4 | enable_static_query_cache: true 5 | -------------------------------------------------------------------------------- /config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: en 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - en 7 | # providers: 8 | # crowdin: 9 | # dsn: '%env(CROWDIN_DSN)%' 10 | # loco: 11 | # dsn: '%env(LOCO_DSN)%' 12 | # lokalise: 13 | # dsn: '%env(LOKALISE_DSN)%' 14 | # phrase: 15 | # dsn: '%env(PHRASE_DSN)%' 16 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | 4 | when@test: 5 | twig: 6 | strict_variables: true 7 | -------------------------------------------------------------------------------- /config/packages/twig_component.yaml: -------------------------------------------------------------------------------- 1 | twig_component: 2 | anonymous_template_directory: 'components/' 3 | defaults: 4 | # Namespace & directory for components 5 | App\Twig\Components\: 'components/' 6 | -------------------------------------------------------------------------------- /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 | 10 | when@test: 11 | framework: 12 | validation: 13 | not_compromised_password: false 14 | -------------------------------------------------------------------------------- /config/packages/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | when@dev: 2 | web_profiler: 3 | toolbar: true 4 | intercept_redirects: false 5 | 6 | framework: 7 | profiler: 8 | only_exceptions: false 9 | collect_serializer_data: true 10 | 11 | when@test: 12 | web_profiler: 13 | toolbar: false 14 | intercept_redirects: false 15 | 16 | framework: 17 | profiler: { collect: false } 18 | -------------------------------------------------------------------------------- /config/preload.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'path' => 'app.js', 19 | 'preload' => true, 20 | ], 21 | '@hotwired/stimulus' => [ 22 | 'url' => 'https://cdn.jsdelivr.net/npm/@hotwired/stimulus@3.2.2/+esm', 23 | ], 24 | '@symfony/stimulus-bundle' => [ 25 | 'path' => '@symfony/stimulus-bundle/loader.js', 26 | ], 27 | ]; 28 | -------------------------------------------------------------------------------- /migrations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebMamba/SymfonyStarterKit/390a4ef98a6d266cbda4812867ebb22e4c092be8/migrations/.gitignore -------------------------------------------------------------------------------- /migrations/Version20231128211533.php: -------------------------------------------------------------------------------- 1 | addSql('CREATE TABLE reset_password_request (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER NOT NULL, selector VARCHAR(20) NOT NULL, hashed_token VARCHAR(100) NOT NULL, requested_at DATETIME NOT NULL --(DC2Type:datetime_immutable) 24 | , expires_at DATETIME NOT NULL --(DC2Type:datetime_immutable) 25 | , CONSTRAINT FK_7CE748AA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) NOT DEFERRABLE INITIALLY IMMEDIATE)'); 26 | $this->addSql('CREATE INDEX IDX_7CE748AA76ED395 ON reset_password_request (user_id)'); 27 | $this->addSql('CREATE TABLE "user" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json) 28 | , password VARCHAR(255) NOT NULL)'); 29 | $this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)'); 30 | $this->addSql('CREATE TABLE messenger_messages (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, body CLOB NOT NULL, headers CLOB NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL --(DC2Type:datetime_immutable) 31 | , available_at DATETIME NOT NULL --(DC2Type:datetime_immutable) 32 | , delivered_at DATETIME DEFAULT NULL --(DC2Type:datetime_immutable) 33 | )'); 34 | $this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0 ON messenger_messages (queue_name)'); 35 | $this->addSql('CREATE INDEX IDX_75EA56E0E3BD61CE ON messenger_messages (available_at)'); 36 | $this->addSql('CREATE INDEX IDX_75EA56E016BA31DB ON messenger_messages (delivered_at)'); 37 | } 38 | 39 | public function down(Schema $schema): void 40 | { 41 | // this down() migration is auto-generated, please modify it to your needs 42 | $this->addSql('DROP TABLE reset_password_request'); 43 | $this->addSql('DROP TABLE "user"'); 44 | $this->addSql('DROP TABLE messenger_messages'); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /phpstan.dist.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: max 3 | paths: 4 | - bin/ 5 | - config/ 6 | - public/ 7 | - src/ 8 | - tests/ 9 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | render('dashboard.html.twig'); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Controller/HomeController.php: -------------------------------------------------------------------------------- 1 | render('home.html.twig'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Controller/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | createForm(ResetPasswordRequestFormType::class); 42 | $form->handleRequest($request); 43 | 44 | /** @var string $email */ 45 | $email = $form->get('email')->getData(); 46 | 47 | if ($form->isSubmitted() && $form->isValid()) { 48 | return $this->processSendingPasswordResetEmail( 49 | $email, 50 | $mailer, 51 | $translator 52 | ); 53 | } 54 | 55 | return $this->render('reset_password/request.html.twig', [ 56 | 'requestForm' => $form->createView(), 57 | ]); 58 | } 59 | 60 | /** 61 | * Confirmation page after a user has requested a password reset. 62 | */ 63 | #[Route('/check-email', name: 'app_check_email')] 64 | public function checkEmail(): Response 65 | { 66 | // Generate a fake token if the user does not exist or someone hit this page directly. 67 | // This prevents exposing whether or not a user was found with the given email address or not 68 | if (null === ($resetToken = $this->getTokenObjectFromSession())) { 69 | $resetToken = $this->resetPasswordHelper->generateFakeResetToken(); 70 | } 71 | 72 | return $this->render('reset_password/check_email.html.twig', [ 73 | 'resetToken' => $resetToken, 74 | ]); 75 | } 76 | 77 | /** 78 | * Validates and process the reset URL that the user clicked in their email. 79 | */ 80 | #[Route('/reset/{token}', name: 'app_reset_password')] 81 | public function reset(Request $request, UserPasswordHasherInterface $passwordHasher, TranslatorInterface $translator, string $token = null): Response 82 | { 83 | if ($token) { 84 | // We store the token in session and remove it from the URL, to avoid the URL being 85 | // loaded in a browser and potentially leaking the token to 3rd party JavaScript. 86 | $this->storeTokenInSession($token); 87 | 88 | return $this->redirectToRoute('app_reset_password'); 89 | } 90 | 91 | $token = $this->getTokenFromSession(); 92 | if (null === $token) { 93 | throw $this->createNotFoundException('No reset password token found in the URL or in the session.'); 94 | } 95 | 96 | try { 97 | /** @var User $user */ 98 | $user = $this->resetPasswordHelper->validateTokenAndFetchUser($token); 99 | } catch (ResetPasswordExceptionInterface $e) { 100 | $this->addFlash('reset_password_error', sprintf( 101 | '%s - %s', 102 | $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_VALIDATE, [], 'ResetPasswordBundle'), 103 | $translator->trans($e->getReason(), [], 'ResetPasswordBundle') 104 | )); 105 | 106 | return $this->redirectToRoute('app_forgot_password_request'); 107 | } 108 | 109 | // The token is valid; allow the user to change their password. 110 | $form = $this->createForm(ChangePasswordFormType::class); 111 | $form->handleRequest($request); 112 | 113 | if ($form->isSubmitted() && $form->isValid()) { 114 | // A password reset token should be used only once, remove it. 115 | $this->resetPasswordHelper->removeResetRequest($token); 116 | 117 | /** @var string $plainPassword */ 118 | $plainPassword = $form->get('plainPassword')->getData(); 119 | 120 | // Encode(hash) the plain password, and set it. 121 | $encodedPassword = $passwordHasher->hashPassword( 122 | $user, 123 | $plainPassword 124 | ); 125 | 126 | $user->setPassword($encodedPassword); 127 | $this->entityManager->flush(); 128 | 129 | // The session is cleaned up after the password has been changed. 130 | $this->cleanSessionAfterReset(); 131 | 132 | return $this->redirectToRoute('app_home'); 133 | } 134 | 135 | return $this->render('reset_password/reset.html.twig', [ 136 | 'resetForm' => $form->createView(), 137 | ]); 138 | } 139 | 140 | private function processSendingPasswordResetEmail(string $emailFormData, MailerInterface $mailer, TranslatorInterface $translator): RedirectResponse 141 | { 142 | $user = $this->entityManager->getRepository(User::class)->findOneBy([ 143 | 'email' => $emailFormData, 144 | ]); 145 | 146 | // Do not reveal whether a user account was found or not. 147 | if (!$user) { 148 | return $this->redirectToRoute('app_check_email'); 149 | } 150 | 151 | try { 152 | $resetToken = $this->resetPasswordHelper->generateResetToken($user); 153 | } catch (ResetPasswordExceptionInterface $e) { 154 | // If you want to tell the user why a reset email was not sent, uncomment 155 | // the lines below and change the redirect to 'app_forgot_password_request'. 156 | // Caution: This may reveal if a user is registered or not. 157 | // 158 | // $this->addFlash('reset_password_error', sprintf( 159 | // '%s - %s', 160 | // $translator->trans(ResetPasswordExceptionInterface::MESSAGE_PROBLEM_HANDLE, [], 'ResetPasswordBundle'), 161 | // $translator->trans($e->getReason(), [], 'ResetPasswordBundle') 162 | // )); 163 | 164 | return $this->redirectToRoute('app_check_email'); 165 | } 166 | 167 | $email = (new TemplatedEmail()) 168 | ->from(new Address('starter-kit@test.com', 'Starter Kit')) 169 | ->to($user->getEmail()) 170 | ->subject('Your password reset request') 171 | ->htmlTemplate('reset_password/email.html.twig') 172 | ->context([ 173 | 'resetToken' => $resetToken, 174 | ]) 175 | ; 176 | 177 | $mailer->send($email); 178 | 179 | // Store the token object in session for retrieval in check-email route. 180 | $this->setTokenObjectInSession($resetToken); 181 | 182 | return $this->redirectToRoute('app_check_email'); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/Controller/SecurityController.php: -------------------------------------------------------------------------------- 1 | createForm(RegisterFormType::class); 22 | 23 | $form->handleRequest($request); 24 | 25 | if ($form->isSubmitted() && $form->isValid()) { 26 | /** @var User $user */ 27 | $user = $form->getData(); 28 | 29 | $registerUser->register($user); 30 | 31 | return $this->redirectToRoute('app_dashboard_index'); 32 | } 33 | 34 | return $this->render('security/signup.html.twig', ['form' => $form]); 35 | } 36 | 37 | #[Route('/login')] 38 | public function login(AuthenticationUtils $authenticationUtils): Response 39 | { 40 | $error = $authenticationUtils->getLastAuthenticationError(); 41 | 42 | $lastUsername = $authenticationUtils->getLastUsername(); 43 | 44 | return $this->render('security/login.html.twig', [ 45 | 'last_username' => $lastUsername, 46 | 'error' => $error, 47 | ]); 48 | } 49 | 50 | #[Route('/logout')] 51 | public function logout(): void 52 | { 53 | throw new \Exception('This should never be reached!'); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/DataFixtures/AppFixtures.php: -------------------------------------------------------------------------------- 1 | persist($product); 16 | 17 | $manager->flush(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Entity/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebMamba/SymfonyStarterKit/390a4ef98a6d266cbda4812867ebb22e4c092be8/src/Entity/.gitignore -------------------------------------------------------------------------------- /src/Entity/ResetPasswordRequest.php: -------------------------------------------------------------------------------- 1 | user = $user; 29 | $this->initialize($expiresAt, $selector, $hashedToken); 30 | } 31 | 32 | public function getId(): ?int 33 | { 34 | return $this->id; 35 | } 36 | 37 | public function getUser(): User 38 | { 39 | return $this->user; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Entity/User.php: -------------------------------------------------------------------------------- 1 | 26 | */ 27 | #[ORM\Column] 28 | private array $roles = []; 29 | 30 | /** 31 | * @var string The hashed password 32 | */ 33 | #[ORM\Column] 34 | private string $password; 35 | 36 | public function getId(): ?int 37 | { 38 | return $this->id; 39 | } 40 | 41 | public function getEmail(): string 42 | { 43 | return $this->email; 44 | } 45 | 46 | public function setEmail(string $email): static 47 | { 48 | $this->email = $email; 49 | 50 | return $this; 51 | } 52 | 53 | /** 54 | * A visual identifier that represents this user. 55 | * 56 | * @see UserInterface 57 | */ 58 | public function getUserIdentifier(): string 59 | { 60 | return (string) $this->email; 61 | } 62 | 63 | /** 64 | * @see UserInterface 65 | */ 66 | public function getRoles(): array 67 | { 68 | $roles = $this->roles; 69 | // guarantee every user at least has ROLE_USER 70 | $roles[] = 'ROLE_USER'; 71 | 72 | return array_unique($roles); 73 | } 74 | 75 | /** 76 | * @param array $roles 77 | */ 78 | public function setRoles(array $roles): static 79 | { 80 | $this->roles = $roles; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * @see PasswordAuthenticatedUserInterface 87 | */ 88 | public function getPassword(): string 89 | { 90 | return $this->password; 91 | } 92 | 93 | public function setPassword(string $password): static 94 | { 95 | $this->password = $password; 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * @see UserInterface 102 | */ 103 | public function eraseCredentials(): void 104 | { 105 | // If you store any temporary, sensitive data on the user, clear it here 106 | // $this->plainPassword = null; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Form/ChangePasswordFormType.php: -------------------------------------------------------------------------------- 1 | add('plainPassword', RepeatedType::class, [ 21 | 'type' => PasswordType::class, 22 | 'options' => [ 23 | 'attr' => [ 24 | 'autocomplete' => 'new-password', 25 | ], 26 | ], 27 | 'first_options' => [ 28 | 'constraints' => [ 29 | new NotBlank([ 30 | 'message' => 'Please enter a password', 31 | ]), 32 | new Length([ 33 | 'min' => 6, 34 | 'minMessage' => 'Your password should be at least {{ limit }} characters', 35 | // max length allowed by Symfony for security reasons 36 | 'max' => 4096, 37 | ]), 38 | ], 39 | 'label' => 'New password', 40 | ], 41 | 'second_options' => [ 42 | 'label' => 'Repeat Password', 43 | ], 44 | 'invalid_message' => 'The password fields must match.', 45 | // Instead of being set onto the object directly, 46 | // this is read and encoded in the controller 47 | 'mapped' => false, 48 | ]) 49 | ; 50 | } 51 | 52 | public function configureOptions(OptionsResolver $resolver): void 53 | { 54 | $resolver->setDefaults([]); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Form/RegisterFormType.php: -------------------------------------------------------------------------------- 1 | add('email', TextType::class) 19 | ->add('password', TextType::class) 20 | ; 21 | } 22 | 23 | public function configureOptions(OptionsResolver $resolver): void 24 | { 25 | $resolver->setDefaults([ 26 | 'data_class' => User::class, 27 | ]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Form/ResetPasswordRequestFormType.php: -------------------------------------------------------------------------------- 1 | add('email', EmailType::class, [ 19 | 'attr' => ['autocomplete' => 'email'], 20 | 'constraints' => [ 21 | new NotBlank([ 22 | 'message' => 'Please enter your email', 23 | ]), 24 | ], 25 | ]) 26 | ; 27 | } 28 | 29 | public function configureOptions(OptionsResolver $resolver): void 30 | { 31 | $resolver->setDefaults([]); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | 17 | * 18 | * @method null|ResetPasswordRequest find($id, $lockMode = null, $lockVersion = null) 19 | * @method null|ResetPasswordRequest findOneBy(array $criteria, array $orderBy = null) 20 | * @method ResetPasswordRequest[] findAll() 21 | * @method ResetPasswordRequest[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 22 | */ 23 | class ResetPasswordRequestRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface 24 | { 25 | use ResetPasswordRequestRepositoryTrait; 26 | 27 | public function __construct(ManagerRegistry $registry) 28 | { 29 | parent::__construct($registry, ResetPasswordRequest::class); 30 | } 31 | 32 | /** 33 | * @param User $user 34 | */ 35 | public function createResetPasswordRequest(object $user, \DateTimeInterface $expiresAt, string $selector, string $hashedToken): ResetPasswordRequestInterface 36 | { 37 | return new ResetPasswordRequest($user, $expiresAt, $selector, $hashedToken); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Repository/UserRepository.php: -------------------------------------------------------------------------------- 1 | 16 | * 17 | * @implements PasswordUpgraderInterface 18 | * 19 | * @method null|User find($id, $lockMode = null, $lockVersion = null) 20 | * @method null|User findOneBy(array $criteria, array $orderBy = null) 21 | * @method User[] findAll() 22 | * @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 23 | */ 24 | class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface 25 | { 26 | public function __construct(ManagerRegistry $registry) 27 | { 28 | parent::__construct($registry, User::class); 29 | } 30 | 31 | /** 32 | * Used to upgrade (rehash) the user's password automatically over time. 33 | */ 34 | public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void 35 | { 36 | if (!$user instanceof User) { 37 | throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class)); 38 | } 39 | 40 | $user->setPassword($newHashedPassword); 41 | $this->getEntityManager()->persist($user); 42 | $this->getEntityManager()->flush(); 43 | } 44 | 45 | public function save(User $user): void 46 | { 47 | $this->getEntityManager()->persist($user); 48 | $this->getEntityManager()->flush(); 49 | } 50 | 51 | // /** 52 | // * @return User[] Returns an array of User objects 53 | // */ 54 | // public function findByExampleField($value): array 55 | // { 56 | // return $this->createQueryBuilder('u') 57 | // ->andWhere('u.exampleField = :val') 58 | // ->setParameter('val', $value) 59 | // ->orderBy('u.id', 'ASC') 60 | // ->setMaxResults(10) 61 | // ->getQuery() 62 | // ->getResult() 63 | // ; 64 | // } 65 | 66 | // public function findOneBySomeField($value): ?User 67 | // { 68 | // return $this->createQueryBuilder('u') 69 | // ->andWhere('u.exampleField = :val') 70 | // ->setParameter('val', $value) 71 | // ->getQuery() 72 | // ->getOneOrNullResult() 73 | // ; 74 | // } 75 | } 76 | -------------------------------------------------------------------------------- /src/Security/RegisterUser.php: -------------------------------------------------------------------------------- 1 | passwordHasher->hashPassword( 23 | $user, 24 | $user->getPassword() 25 | ); 26 | 27 | $user->setPassword($hashedPassword); 28 | 29 | $this->userRepository->save($user); 30 | 31 | $this->security->login($user); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /symfony.lock: -------------------------------------------------------------------------------- 1 | { 2 | "dama/doctrine-test-bundle": { 3 | "version": "7.3", 4 | "recipe": { 5 | "repo": "github.com/symfony/recipes-contrib", 6 | "branch": "main", 7 | "version": "4.0", 8 | "ref": "2c920f73a217f30bd4a37833c91071f4d3dc1ecd" 9 | }, 10 | "files": [ 11 | "config/packages/test/dama_doctrine_test_bundle.yaml" 12 | ] 13 | }, 14 | "doctrine/doctrine-bundle": { 15 | "version": "2.11", 16 | "recipe": { 17 | "repo": "github.com/symfony/recipes", 18 | "branch": "main", 19 | "version": "2.10", 20 | "ref": "0db4b12b5df45f5122213b4ecd18733ab7fa7d53" 21 | }, 22 | "files": [ 23 | "config/packages/doctrine.yaml", 24 | "src/Entity/.gitignore", 25 | "src/Repository/.gitignore" 26 | ] 27 | }, 28 | "doctrine/doctrine-fixtures-bundle": { 29 | "version": "3.5", 30 | "recipe": { 31 | "repo": "github.com/symfony/recipes", 32 | "branch": "main", 33 | "version": "3.0", 34 | "ref": "1f5514cfa15b947298df4d771e694e578d4c204d" 35 | }, 36 | "files": [ 37 | "src/DataFixtures/AppFixtures.php" 38 | ] 39 | }, 40 | "doctrine/doctrine-migrations-bundle": { 41 | "version": "3.3", 42 | "recipe": { 43 | "repo": "github.com/symfony/recipes", 44 | "branch": "main", 45 | "version": "3.1", 46 | "ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33" 47 | }, 48 | "files": [ 49 | "config/packages/doctrine_migrations.yaml", 50 | "migrations/.gitignore" 51 | ] 52 | }, 53 | "friendsofphp/php-cs-fixer": { 54 | "version": "3.40", 55 | "recipe": { 56 | "repo": "github.com/symfony/recipes", 57 | "branch": "main", 58 | "version": "3.0", 59 | "ref": "be2103eb4a20942e28a6dd87736669b757132435" 60 | }, 61 | "files": [ 62 | ".php-cs-fixer.dist.php" 63 | ] 64 | }, 65 | "phpstan/phpstan": { 66 | "version": "1.10", 67 | "recipe": { 68 | "repo": "github.com/symfony/recipes-contrib", 69 | "branch": "main", 70 | "version": "1.0", 71 | "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767" 72 | }, 73 | "files": [ 74 | "phpstan.dist.neon" 75 | ] 76 | }, 77 | "phpunit/phpunit": { 78 | "version": "9.6", 79 | "recipe": { 80 | "repo": "github.com/symfony/recipes", 81 | "branch": "main", 82 | "version": "9.6", 83 | "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326" 84 | }, 85 | "files": [ 86 | ".env.test", 87 | "phpunit.xml.dist", 88 | "tests/bootstrap.php" 89 | ] 90 | }, 91 | "symfony/asset-mapper": { 92 | "version": "6.3", 93 | "recipe": { 94 | "repo": "github.com/symfony/recipes", 95 | "branch": "main", 96 | "version": "6.3", 97 | "ref": "e8c46ba5e488fc2d7462ab6832559ab93867370a" 98 | }, 99 | "files": [ 100 | "assets/app.js", 101 | "assets/styles/app.css", 102 | "config/packages/asset_mapper.yaml", 103 | "importmap.php" 104 | ] 105 | }, 106 | "symfony/console": { 107 | "version": "6.3", 108 | "recipe": { 109 | "repo": "github.com/symfony/recipes", 110 | "branch": "main", 111 | "version": "5.3", 112 | "ref": "da0c8be8157600ad34f10ff0c9cc91232522e047" 113 | }, 114 | "files": [ 115 | "bin/console" 116 | ] 117 | }, 118 | "symfony/debug-bundle": { 119 | "version": "6.3", 120 | "recipe": { 121 | "repo": "github.com/symfony/recipes", 122 | "branch": "main", 123 | "version": "5.3", 124 | "ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b" 125 | }, 126 | "files": [ 127 | "config/packages/debug.yaml" 128 | ] 129 | }, 130 | "symfony/flex": { 131 | "version": "2.4", 132 | "recipe": { 133 | "repo": "github.com/symfony/recipes", 134 | "branch": "main", 135 | "version": "1.0", 136 | "ref": "146251ae39e06a95be0fe3d13c807bcf3938b172" 137 | }, 138 | "files": [ 139 | ".env" 140 | ] 141 | }, 142 | "symfony/framework-bundle": { 143 | "version": "6.3", 144 | "recipe": { 145 | "repo": "github.com/symfony/recipes", 146 | "branch": "main", 147 | "version": "6.2", 148 | "ref": "af47254c5e4cd543e6af3e4508298ffebbdaddd3" 149 | }, 150 | "files": [ 151 | "config/packages/cache.yaml", 152 | "config/packages/framework.yaml", 153 | "config/preload.php", 154 | "config/routes/framework.yaml", 155 | "config/services.yaml", 156 | "public/index.php", 157 | "src/Controller/.gitignore", 158 | "src/Kernel.php" 159 | ] 160 | }, 161 | "symfony/mailer": { 162 | "version": "6.3", 163 | "recipe": { 164 | "repo": "github.com/symfony/recipes", 165 | "branch": "main", 166 | "version": "4.3", 167 | "ref": "2bf89438209656b85b9a49238c4467bff1b1f939" 168 | }, 169 | "files": [ 170 | "config/packages/mailer.yaml" 171 | ] 172 | }, 173 | "symfony/maker-bundle": { 174 | "version": "1.51", 175 | "recipe": { 176 | "repo": "github.com/symfony/recipes", 177 | "branch": "main", 178 | "version": "1.0", 179 | "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f" 180 | } 181 | }, 182 | "symfony/messenger": { 183 | "version": "6.3", 184 | "recipe": { 185 | "repo": "github.com/symfony/recipes", 186 | "branch": "main", 187 | "version": "6.0", 188 | "ref": "ba1ac4e919baba5644d31b57a3284d6ba12d52ee" 189 | }, 190 | "files": [ 191 | "config/packages/messenger.yaml" 192 | ] 193 | }, 194 | "symfony/monolog-bundle": { 195 | "version": "3.10", 196 | "recipe": { 197 | "repo": "github.com/symfony/recipes", 198 | "branch": "main", 199 | "version": "3.7", 200 | "ref": "213676c4ec929f046dfde5ea8e97625b81bc0578" 201 | }, 202 | "files": [ 203 | "config/packages/monolog.yaml" 204 | ] 205 | }, 206 | "symfony/notifier": { 207 | "version": "6.3", 208 | "recipe": { 209 | "repo": "github.com/symfony/recipes", 210 | "branch": "main", 211 | "version": "5.0", 212 | "ref": "178877daf79d2dbd62129dd03612cb1a2cb407cc" 213 | }, 214 | "files": [ 215 | "config/packages/notifier.yaml" 216 | ] 217 | }, 218 | "symfony/phpunit-bridge": { 219 | "version": "6.3", 220 | "recipe": { 221 | "repo": "github.com/symfony/recipes", 222 | "branch": "main", 223 | "version": "6.3", 224 | "ref": "1f5830c331065b6e4c9d5fa2105e322d29fcd573" 225 | }, 226 | "files": [ 227 | ".env.test", 228 | "bin/phpunit", 229 | "phpunit.xml.dist", 230 | "tests/bootstrap.php" 231 | ] 232 | }, 233 | "symfony/routing": { 234 | "version": "6.3", 235 | "recipe": { 236 | "repo": "github.com/symfony/recipes", 237 | "branch": "main", 238 | "version": "6.2", 239 | "ref": "e0a11b4ccb8c9e70b574ff5ad3dfdcd41dec5aa6" 240 | }, 241 | "files": [ 242 | "config/packages/routing.yaml", 243 | "config/routes.yaml" 244 | ] 245 | }, 246 | "symfony/security-bundle": { 247 | "version": "6.3", 248 | "recipe": { 249 | "repo": "github.com/symfony/recipes", 250 | "branch": "main", 251 | "version": "6.0", 252 | "ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48" 253 | }, 254 | "files": [ 255 | "config/packages/security.yaml" 256 | ] 257 | }, 258 | "symfony/stimulus-bundle": { 259 | "version": "2.13", 260 | "recipe": { 261 | "repo": "github.com/symfony/recipes", 262 | "branch": "main", 263 | "version": "2.9", 264 | "ref": "05c45071c7ecacc1e48f94bc43c1f8d4405fb2b2" 265 | }, 266 | "files": [ 267 | "assets/bootstrap.js", 268 | "assets/controllers.json", 269 | "assets/controllers/hello_controller.js" 270 | ] 271 | }, 272 | "symfony/translation": { 273 | "version": "6.3", 274 | "recipe": { 275 | "repo": "github.com/symfony/recipes", 276 | "branch": "main", 277 | "version": "6.3", 278 | "ref": "64fe617084223633e1dedf9112935d8c95410d3e" 279 | }, 280 | "files": [ 281 | "config/packages/translation.yaml", 282 | "translations/.gitignore" 283 | ] 284 | }, 285 | "symfony/twig-bundle": { 286 | "version": "6.3", 287 | "recipe": { 288 | "repo": "github.com/symfony/recipes", 289 | "branch": "main", 290 | "version": "6.3", 291 | "ref": "b7772eb20e92f3fb4d4fe756e7505b4ba2ca1a2c" 292 | }, 293 | "files": [ 294 | "config/packages/twig.yaml", 295 | "templates/base.html.twig" 296 | ] 297 | }, 298 | "symfony/ux-twig-component": { 299 | "version": "2.13", 300 | "recipe": { 301 | "repo": "github.com/symfony/recipes", 302 | "branch": "main", 303 | "version": "2.13", 304 | "ref": "67814b5f9794798b885cec9d3f48631424449a01" 305 | }, 306 | "files": [ 307 | "config/packages/twig_component.yaml" 308 | ] 309 | }, 310 | "symfony/validator": { 311 | "version": "6.3", 312 | "recipe": { 313 | "repo": "github.com/symfony/recipes", 314 | "branch": "main", 315 | "version": "5.3", 316 | "ref": "c32cfd98f714894c4f128bb99aa2530c1227603c" 317 | }, 318 | "files": [ 319 | "config/packages/validator.yaml" 320 | ] 321 | }, 322 | "symfony/web-profiler-bundle": { 323 | "version": "6.3", 324 | "recipe": { 325 | "repo": "github.com/symfony/recipes", 326 | "branch": "main", 327 | "version": "6.1", 328 | "ref": "e42b3f0177df239add25373083a564e5ead4e13a" 329 | }, 330 | "files": [ 331 | "config/packages/web_profiler.yaml", 332 | "config/routes/web_profiler.yaml" 333 | ] 334 | }, 335 | "symfony/webapp-pack": { 336 | "version": "1.2", 337 | "recipe": { 338 | "repo": "github.com/symfony/recipes", 339 | "branch": "main", 340 | "version": "1.0", 341 | "ref": "aece95c8a188f6e6d04f01ccb8678d1764fd2642" 342 | }, 343 | "files": [ 344 | "config/packages/messenger.yaml" 345 | ] 346 | }, 347 | "symfonycasts/reset-password-bundle": { 348 | "version": "1.18", 349 | "recipe": { 350 | "repo": "github.com/symfony/recipes", 351 | "branch": "main", 352 | "version": "1.0", 353 | "ref": "97c1627c0384534997ae1047b93be517ca16de43" 354 | }, 355 | "files": [ 356 | "config/packages/reset_password.yaml" 357 | ] 358 | }, 359 | "symfonycasts/tailwind-bundle": { 360 | "version": "v0.2.1" 361 | }, 362 | "twig/extra-bundle": { 363 | "version": "v3.8.0" 364 | } 365 | } 366 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./assets/**/*.js", 5 | "./templates/**/*.html.twig", 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [], 11 | } 12 | -------------------------------------------------------------------------------- /templates/base.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {% block title %}Welcome!{% endblock %} 6 | 7 | 8 | {% block stylesheets %} 9 | {{ ux_controller_link_tags() }} 10 | 11 | {% endblock %} 12 | 13 | {% block javascripts %} 14 | {{ importmap() }} 15 | {% endblock %} 16 | 17 | 18 | {% block body %}{% endblock %} 19 | 20 | 21 | -------------------------------------------------------------------------------- /templates/components/Footer.html.twig: -------------------------------------------------------------------------------- 1 | 27 | -------------------------------------------------------------------------------- /templates/components/NavBar.html.twig: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /templates/dashboard.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | 3 | {% block title %} 4 | Dashboard 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |
10 |
11 |

Welcome in your dashboard

12 |

This is great, but the Symfony is the best PHP framework in the world you can do so much more! You can do it! You are amazing and clever, so change this word! You can do it!

13 |
14 |
15 |
16 |
17 | 18 |
19 |

Marketing

20 |

Plan it, create it, launch it. Collaborate seamlessly with all the organization and hit your marketing goals every month with our marketing plan.

21 |
22 |
23 |
24 | 25 |
26 |

Legal

27 |

Protect your organization, devices and stay compliant with our structured workflows and custom permissions made for you.

28 |
29 |
30 |
31 | 32 |
33 |

Business Automation

34 |

Auto-assign tasks, send Slack messages, and much more. Now power up with hundreds of new templates to help you get started.

35 |
36 |
37 |
38 | 39 |
40 |

Finance

41 |

Audit-proof software built for critical financial operations like month-end close and quarterly budgeting.

42 |
43 |
44 |
45 | 46 |
47 |

Enterprise Design

48 |

Craft beautiful, delightful experiences for both marketing and product with real cross-company collaboration.

49 |
50 |
51 |
52 | 53 |
54 |

Operations

55 |

Keep your company’s lights on with customizable, iterative, and structured workflows built for all efficient teams and individual.

56 |
57 |
58 |
59 |
60 | {% endblock %} -------------------------------------------------------------------------------- /templates/home.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout.html.twig' %} 2 | 3 | {% block title %} 4 | Symfony starter-kit home page 5 | {% endblock %} 6 | 7 | {% block content %} 8 |
9 |

Symfony starter-kit

10 |

11 | All your need set up in few secondes. Feel whats greatness is with Symfony, 12 | the best PHP framework ever created. 13 | Go beyond the limits of PHP with Symfony. 14 |

15 | 16 | Learn more 17 | 20 | 21 |
22 | {% endblock %} -------------------------------------------------------------------------------- /templates/layout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block body %} 4 | 5 | {% block content %}{% endblock %} 6 | 7 | {% endblock %} -------------------------------------------------------------------------------- /templates/reset_password/check_email.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'security_layout.html.twig' %} 2 | 3 | {% block title %}Password Reset Email Sent{% endblock %} 4 | 5 | {% block card_title %}Change Password{% endblock %} 6 | 7 | {% block content %} 8 |

9 | If an account matching your email exists, then an email was just sent that contains a link that you can use to reset your password. 10 | This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}. 11 |

12 |

If you don't receive an email please check your spam folder or try again.

13 | {% endblock %} 14 | -------------------------------------------------------------------------------- /templates/reset_password/email.html.twig: -------------------------------------------------------------------------------- 1 |

Hi!

2 | 3 |

To reset your password, please visit the following link

4 | 5 | {{ url('app_reset_password', {token: resetToken.token}) }} 6 | 7 |

This link will expire in {{ resetToken.expirationMessageKey|trans(resetToken.expirationMessageData, 'ResetPasswordBundle') }}.

8 | 9 |

Cheers!

10 | -------------------------------------------------------------------------------- /templates/reset_password/request.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'security_layout.html.twig' %} 2 | 3 | {% block title %}Reset your password{% endblock %} 4 | 5 | {% block card_title %}Reset your password{% endblock %} 6 | 7 | {% block content %} 8 |

9 | Enter your email address, and we will send you a 10 | link to reset your password. 11 |

12 | {% for flash_error in app.flashes('reset_password_error') %} 13 | 16 | {% endfor %} 17 | {{ form_start(requestForm, {attr: {class: 'mt-4 space-y-4 lg:mt-5 md:space-y-5'}}) }} 18 | {{ form_label(requestForm.email, null, {label_attr: {class: 'block mb-2 text-sm font-medium text-gray-900 dark:text-white'}}) }} 19 | {{ form_widget(requestForm.email, {attr: {class: 'bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'}}) }} 20 | {{ form_errors(requestForm.email, {attr: {class: 'mt-2 text-sm text-red-600 dark:text-red-500'}}) }} 21 | 22 | {{ form_end(requestForm) }} 23 | {% endblock %} 24 | -------------------------------------------------------------------------------- /templates/reset_password/reset.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'security_layout.html.twig' %} 2 | 3 | {% block title %}Reset your password{% endblock %} 4 | 5 | {% block card_title %}Reset your password{% endblock %} 6 | 7 | {% block content %} 8 | {% for flash_error in app.flashes('reset_password_error') %} 9 | 12 | {% endfor %} 13 | {{ form_start(resetForm, {attr: {class: 'mt-4 space-y-4 lg:mt-5 md:space-y-5'}}) }} 14 | {{ form_label(resetForm.plainPassword, null, {label_attr: {class: 'block mb-2 text-sm font-medium text-gray-900 dark:text-white'}}) }} 15 | {{ form_widget(resetForm.plainPassword.first, {attr: {placeholder: 'password', class: 'bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'}}) }} 16 | {{ form_widget(resetForm.plainPassword.second, {attr: {placeholder: 'confirm password', class: 'bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'}}) }} 17 | {{ form_errors(resetForm.plainPassword, {attr: {class: 'mt-2 text-sm text-red-600 dark:text-red-500'}}) }} 18 | 19 | {{ form_end(resetForm) }} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /templates/security/login.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'security_layout.html.twig' %} 2 | 3 | {% block title %} 4 | Sign in to your account 5 | {% endblock %} 6 | 7 | {% block card_title %} 8 | Sign in to your account 9 | {% endblock %} 10 | 11 | {% block content %} 12 | {% if error %} 13 |

{{ error.messageKey|trans(error.messageData, 'security') }}

14 | {% endif %} 15 |
16 |
17 | 18 | 19 |
20 |
21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 |
30 | 31 |
32 |
33 | 34 | Forgot password? 35 |
36 | 37 |

38 | Don’t have an account yet? Sign up 39 |

40 |
41 | {% endblock %} 42 | 43 | -------------------------------------------------------------------------------- /templates/security/signup.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'security_layout.html.twig' %} 2 | 3 | {% block title %} 4 | Create an account 5 | {% endblock %} 6 | 7 | {% block card_title %} 8 | Create an account 9 | {% endblock %} 10 | 11 | {% block content %} 12 | {{ form_start(form, {'attr': {'class': 'space-y-4 md:space-y-6'}}) }} 13 |
14 | {{ form_label(form.email, null, {'label_attr': {'class': 'block mb-2 text-sm font-medium text-gray-900 dark:text-white'}}) }} 15 | {{ form_widget(form.email, {'attr': {'class': 'bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'}}) }} 16 | {{ form_errors(form.email, {'attr': {'class': 'mt-2 text-sm text-red-600 dark:text-red-500'}}) }} 17 |
18 |
19 | {{ form_label(form.password, null, {'label_attr': {'class': 'block mb-2 text-sm font-medium text-gray-900 dark:text-white'}}) }} 20 | {{ form_widget(form.password, {'attr': {'class': 'bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500'}}) }} 21 | {{ form_errors(form.password, {'attr': {'class': 'mt-2 text-sm text-red-600 dark:text-red-500'}}) }} 22 |
23 | 24 |

25 | Already have an account? Login here 26 |

27 | {{ form_end(form) }} 28 | {% endblock %} -------------------------------------------------------------------------------- /templates/security_layout.html.twig: -------------------------------------------------------------------------------- 1 | {% extends 'base.html.twig' %} 2 | 3 | {% block body %} 4 |
5 |
6 | 7 | logo 8 | Symfony StarterKit 9 | 10 |
11 |
12 |

13 | {% block card_title %}{% endblock %} 14 |

15 | {% block content %} 16 | {% endblock %} 17 |
18 |
19 |
20 |
21 | {% endblock %} -------------------------------------------------------------------------------- /tests/Controller/HomeControllerTest.php: -------------------------------------------------------------------------------- 1 | request('GET', '/'); 20 | 21 | $this->assertResponseIsSuccessful(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Controller/SecurityControllerTest.php: -------------------------------------------------------------------------------- 1 | request('GET', '/sign-up'); 20 | 21 | // assert user can sign up 22 | $client->submitForm('Create an account', [ 23 | 'register_form[email]' => 'test@test.com', 24 | 'register_form[password]' => 'test', 25 | ]); 26 | $this->assertResponseRedirects('/dashboard'); 27 | 28 | // assert user can logout 29 | $client->request('GET', '/logout'); 30 | 31 | // assert user can login 32 | $client->request('GET', '/login'); 33 | $client->submitForm('Sign in', [ 34 | '_username' => 'test@test.com', 35 | '_password' => 'test', 36 | ]); 37 | $client->followRedirect(); 38 | $this->assertResponseIsSuccessful(); 39 | 40 | self::assertNotFalse($client->getResponse()->getContent()); 41 | $this->assertStringContainsString('Welcome in your dashboard', $client->getResponse()->getContent()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__).'/.env'); 13 | } 14 | -------------------------------------------------------------------------------- /translations/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebMamba/SymfonyStarterKit/390a4ef98a6d266cbda4812867ebb22e4c092be8/translations/.gitignore --------------------------------------------------------------------------------