Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
  • br1
2 results

Target

Select target project
No results found
Select Git revision
  • main
  • br1
2 results
Show changes

Commits on Source 2

76 files
+ 12441
139
Compare changes
  • Side-by-side
  • Inline

Files

.github/workflows/audit.yaml

deleted100644 → 0
+0 −50
Original line number Diff line number Diff line
name: Security Audit
on: [push, pull_request]
jobs:
  audit:
    name: Security Audit
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          tools: composer:v2

      - name: Setup Cache
        run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
        
      - name: Caching deps
        uses: actions/cache@v4
        with:
          path: ${{ env.COMPOSER_CACHE_DIR }}
          key: php8.3-composer-${{ hashFiles('**/composer.json') }}
          restore-keys: |
            php8.3-composer-latest-

      - name: Update composer
        run: composer self-update

      - name: install deps
        run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi

      - name: security audit
        run: |
          composer audit \
            --no-dev \
            --abandoned="report" \
            --ignore-severity="low" \
            --ignore-severity="medium" \
            --format="json" \
            --no-ansi \
            > /tmp/security-audit.json

      - name: upload security audit report
        uses: actions/upload-artifact@v4
        if: always()
        with:
          name: security-audit
          path: /tmp/security-audit.json                        

.github/workflows/quality.yaml

deleted100644 → 0
+0 −36
Original line number Diff line number Diff line
name: Quality Analysis
on: [push, pull_request]
jobs:
  quality:
    name: Quality Analysis
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          tools: composer:v2

      - name: Setup Cache
        run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
        
      - name: Caching deps
        uses: actions/cache@v4
        with:
          path: ${{ env.COMPOSER_CACHE_DIR }}
          key: php8.3-composer-${{ hashFiles('**/composer.json') }}
          restore-keys: |
            php8.3-composer-latest-

      - name: Update composer
        run: composer self-update

      - name: install deps
        run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi

      - name: Quality analysis
        run: composer app:code-quality
                        

.github/workflows/test.yaml

deleted100644 → 0
+0 −53
Original line number Diff line number Diff line
name: Tests
on: [push, pull_request]
jobs:
  test:
    name: Tests
    runs-on: ubuntu-latest
    services:
      database:
        image: mariadb:10.7.3
        env:
          MARIADB_USER: root
          MARIADB_ROOT_PASSWORD: root
          MARIADB_DATABASE: app_test
          MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 'no'
        ports:
          - 3306/tcp
        options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Setup PHP
        uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          tools: composer:v2

      - name: Setup Cache
        run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV
        
      - name: Caching deps
        uses: actions/cache@v4
        with:
          path: ${{ env.COMPOSER_CACHE_DIR }}
          key: php8.3-composer-${{ hashFiles('**/composer.json') }}
          restore-keys: |
            php8.3-composer-latest-

      - name: Update composer
        run: composer self-update

      - name: install deps
        run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi

      - name: Prepare the database
        run: sudo systemctl start mysql

      - name: Tests
        run: composer app:tests
        env:
          APP_ENV: test
          DATABASE_URL: mysql://root:root@127.0.0.1:${{ job.services.database.ports['3306'] }}/app_test 

.gitlab-ci.yml

0 → 100644
+40 −0
Original line number Diff line number Diff line
stages:
  - install
  - test
  - deploy  # Optionnel, à activer si nécessaire

variables:
  PHP_VERSION: "8.2"
  COMPOSER_CACHE_DIR: "$CI_PROJECT_DIR/var/cache/composer"

cache:
  key: composer
  paths:
    - vendor/
    - var/cache/composer/

install_dependencies:
  stage: install
  image: php:$PHP_VERSION
  script:
    - apt-get update && apt-get install -y unzip git
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - composer install --no-interaction --prefer-dist
  artifacts:
    paths:
      - vendor/

run_tests:
  stage: test
  image: php:$PHP_VERSION
  script:
    - ./bin/phpunit

# Déploiement (à adapter selon ton serveur)
deploy:
  stage: deploy
  only:
    - main  # Modifier selon la branche utilisée
  script:
    - echo "Déploiement en cours..."
    # Ajouter ici les commandes pour le déploiement

assets/app.js

0 → 100644
+10 −0
Original line number Diff line number Diff line
import './bootstrap.js';
/*
 * Welcome to your app's main JavaScript file!
 *
 * This file will be included onto the page via the importmap() Twig function,
 * which should already be in your base.html.twig.
 */
import './styles/app.css';

console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');

assets/bootstrap.js

0 → 100644
+5 −0
Original line number Diff line number Diff line
import { startStimulusApp } from '@symfony/stimulus-bundle';

const app = startStimulusApp();
// register any custom, 3rd party controllers here
// app.register('some_controller_name', SomeImportedController);
+15 −0
Original line number Diff line number Diff line
{
    "controllers": {
        "@symfony/ux-turbo": {
            "turbo-core": {
                "enabled": true,
                "fetch": "eager"
            },
            "mercure-turbo-stream": {
                "enabled": false,
                "fetch": "eager"
            }
        }
    },
    "entrypoints": []
}
Original line number Diff line number Diff line
var nameCheck = /^[-_a-zA-Z0-9]{4,22}$/;
var tokenCheck = /^[-_/+a-zA-Z0-9]{24,}$/;

// Generate and double-submit a CSRF token in a form field and a cookie, as defined by Symfony's SameOriginCsrfTokenManager
document.addEventListener('submit', function (event) {
    var csrfField = event.target.querySelector('input[data-controller="csrf-protection"]');

    if (!csrfField) {
        return;
    }

    var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');
    var csrfToken = csrfField.value;

    if (!csrfCookie && nameCheck.test(csrfToken)) {
        csrfField.setAttribute('data-csrf-protection-cookie-value', csrfCookie = csrfToken);
        csrfField.value = csrfToken = btoa(String.fromCharCode.apply(null, (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(18))));
    }

    if (csrfCookie && tokenCheck.test(csrfToken)) {
        var cookie = csrfCookie + '_' + csrfToken + '=' + csrfCookie + '; path=/; samesite=strict';
        document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
    }
});

// When @hotwired/turbo handles form submissions, send the CSRF token in a header in addition to a cookie
// The `framework.csrf_protection.check_header` config option needs to be enabled for the header to be checked
document.addEventListener('turbo:submit-start', function (event) {
    var csrfField = event.detail.formSubmission.formElement.querySelector('input[data-controller="csrf-protection"]');

    if (!csrfField) {
        return;
    }

    var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');

    if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
        event.detail.formSubmission.fetchRequest.headers[csrfCookie] = csrfField.value;
    }
});

// When @hotwired/turbo handles form submissions, remove the CSRF cookie once a form has been submitted
document.addEventListener('turbo:submit-end', function (event) {
    var csrfField = event.detail.formSubmission.formElement.querySelector('input[data-controller="csrf-protection"]');

    if (!csrfField) {
        return;
    }

    var csrfCookie = csrfField.getAttribute('data-csrf-protection-cookie-value');

    if (tokenCheck.test(csrfField.value) && nameCheck.test(csrfCookie)) {
        var cookie = csrfCookie + '_' + csrfField.value + '=0; path=/; samesite=strict; max-age=0';

        document.cookie = window.location.protocol === 'https:' ? '__Host-' + cookie + '; secure' : cookie;
    }
});

/* stimulusFetch: 'lazy' */
export default 'csrf-protection-controller';
+16 −0
Original line number Diff line number Diff line
import { Controller } from '@hotwired/stimulus';

/*
 * This is an example Stimulus controller!
 *
 * Any element with a data-controller="hello" attribute will cause
 * this controller to be executed. The name "hello" comes from the filename:
 * hello_controller.js -> "hello"
 *
 * Delete this file or adapt it for your use!
 */
export default class extends Controller {
    connect() {
        this.element.textContent = 'Hello Stimulus! Edit me in assets/controllers/hello_controller.js';
    }
}

assets/styles/app.css

0 → 100644
+3 −0
Original line number Diff line number Diff line
body {
    background-color: skyblue;
}

bin/console

0 → 100644
+21 −0
Original line number Diff line number Diff line
#!/usr/bin/env php
<?php

use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;

if (!is_dir(dirname(__DIR__).'/vendor')) {
    throw new LogicException('Dependencies are missing. Try running "composer install".');
}

if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
    throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    $kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);

    return new Application($kernel);
};

bin/phpunit

0 → 100644
+23 −0
Original line number Diff line number Diff line
#!/usr/bin/env php
<?php

if (!ini_get('date.timezone')) {
    ini_set('date.timezone', 'UTC');
}

if (is_file(dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit')) {
    if (PHP_VERSION_ID >= 80000) {
        require dirname(__DIR__).'/vendor/phpunit/phpunit/phpunit';
    } else {
        define('PHPUNIT_COMPOSER_INSTALL', dirname(__DIR__).'/vendor/autoload.php');
        require PHPUNIT_COMPOSER_INSTALL;
        PHPUnit\TextUI\Command::main();
    }
} else {
    if (!is_file(dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php')) {
        echo "Unable to find the `simple-phpunit.php` script in `vendor/symfony/phpunit-bridge/bin/`.\n";
        exit(1);
    }

    require dirname(__DIR__).'/vendor/symfony/phpunit-bridge/bin/simple-phpunit.php';
}

compose.yaml

0 → 100644
+69 −0
Original line number Diff line number Diff line
services:
  php:
    # pour eviter le erreur de permission
    user: '${USER_ID:-1000}:${GROUP_ID:-1000}'
    build: ./docker/php
    volumes:
      - .:/var/www:delegated # le dossier courant sera monté dans le dossier /var/www du container
    depends_on: # ce service depend de la base de donnée, sinon je ne peux pas engistrer mes utilisateurs 
      - database
    networks:
      - app_network

  # un serveur web qui va intercepeté les requêtes et les rediriger vers PHP qui va les traiter et retourner le résultat
  nginx:
    build: ./docker/nginx
    volumes:
      - ./public:/var/www/public:delegated
      - ./docker/nginx/nginx.conf:/etc/nginx/conf.d/default.conf
    ports:
      - "8000:80"
    depends_on: #sinon on sera pas interprété les requêtes
      - php
    networks:
      - app_network

  database:
    image: mariadb:10.7.3
    environment:
      MARIADB_USER: root
      MARIADB_ROOT_PASSWORD: root
      MARIADB_DATABASE: app
      MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 'no'
    volumes: 
      - database_data:/var/lib/mysql:rw #ou les données de la base de données seront stocké 
      - .var/mysql:/var/www/var
    networks: 
      - app_network # ce n'est pas trés important car parce que tous les services qui sont définis seront dans le même réseau

  adminer: # permet de voir tous ce qu'il y'a dans la base de donnée et permet de débugger si les requêtes fonctionnent comme il faut   
    image: adminer:latest
    depends_on: # ce service depend de la base de donnée, ça sert à rien de le lancer si database n'est pas UP ! 
      - database
    environment:
      APP_ENV: dev #environement de dev par default
      ADMINER_DESIGN: pepa-linha  # le design utilisé par default   
      ADMINER_DEFAULT_SERVER:     # serveur utilisé par default
    ports: # c'est comment accéder au "adminer" une fois le contenair sera lancé (port forwading)
      - "8082:8080" # le adminer va tourner dans le port 8080, mais ce port la c'est dans le contenair et on a pas accés à ce port via la machine hots
    networks:       # on va rediriger le port 8082 vers le port interne 8080 du container
      - app_network   

  mailer: # Pour envoyer les emails nous avons besoin d'un mailer 
    image: axllent/mailpit
    ports:
      - "1025:1025" # ca pour le port SMTP qui va permettre d'envoyer les mails  
      - "8025:8025" # ca pour le port du serveur web qui va permettre d'afficher les emails qui ont étés envoyer  
    environment: # définir si on accepter les connexion non sécurisé
      MP_SMTP_AUTH_ACCEPT_ANY: 1  
      MP_SMTP_AUTH_ALLOW_INSECURE: 1 
    networks:
      - app_network
    
networks:
  app_network:

volumes:
  database_data: #cette clé il faut la crée 

  
 No newline at end of file

composer.json

0 → 100644
+133 −0
Original line number Diff line number Diff line
{
    "type": "project",
    "license": "proprietary",
    "minimum-stability": "stable",
    "prefer-stable": true,
    "require": {
        "php": ">=8.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "doctrine/dbal": "^3",
        "doctrine/doctrine-bundle": "^2.13",
        "doctrine/doctrine-migrations-bundle": "^3.3",
        "doctrine/orm": "^3.3",
        "phpdocumentor/reflection-docblock": "^5.6",
        "phpstan/phpdoc-parser": "^2.0",
        "symfony/asset": "7.2.*",
        "symfony/asset-mapper": "7.2.*",
        "symfony/console": "7.2.*",
        "symfony/doctrine-messenger": "7.2.*",
        "symfony/dotenv": "7.2.*",
        "symfony/expression-language": "7.2.*",
        "symfony/flex": "^2",
        "symfony/form": "7.2.*",
        "symfony/framework-bundle": "7.2.*",
        "symfony/http-client": "7.2.*",
        "symfony/intl": "7.2.*",
        "symfony/mailer": "7.2.*",
        "symfony/mime": "7.2.*",
        "symfony/monolog-bundle": "^3.0",
        "symfony/notifier": "7.2.*",
        "symfony/process": "7.2.*",
        "symfony/property-access": "7.2.*",
        "symfony/property-info": "7.2.*",
        "symfony/runtime": "7.2.*",
        "symfony/security-bundle": "7.2.*",
        "symfony/serializer": "7.2.*",
        "symfony/stimulus-bundle": "^2.22",
        "symfony/string": "7.2.*",
        "symfony/translation": "7.2.*",
        "symfony/twig-bundle": "7.2.*",
        "symfony/ux-turbo": "^2.22",
        "symfony/validator": "7.2.*",
        "symfony/web-link": "7.2.*",
        "symfony/yaml": "7.2.*",
        "symfonycasts/verify-email-bundle": "^1.17",
        "twig/extra-bundle": "^2.12|^3.0",
        "twig/twig": "^2.12|^3.0"
    },
    "config": {
        "allow-plugins": {
            "php-http/discovery": true,
            "symfony/flex": true,
            "symfony/runtime": true
        },
        "bump-after-update": true,
        "sort-packages": true
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "App\\Tests\\": "tests/"
        }
    },
    "replace": {
        "symfony/polyfill-ctype": "*",
        "symfony/polyfill-iconv": "*",
        "symfony/polyfill-php72": "*",
        "symfony/polyfill-php73": "*",
        "symfony/polyfill-php74": "*",
        "symfony/polyfill-php80": "*",
        "symfony/polyfill-php81": "*",
        "symfony/polyfill-php82": "*"
    },
    "scripts": {
        "auto-scripts": {
            "cache:clear": "symfony-cmd",
            "assets:install %PUBLIC_DIR%": "symfony-cmd",
            "importmap:install": "symfony-cmd"
        },
        "post-install-cmd": [
            "@auto-scripts"
        ],
        "post-update-cmd": [
            "@auto-scripts"
        ],
        "app:code-quality": [
            "./vendor/bin/ecs check",
            "bin/console lint:yaml config --parse-tags",
            "bin/console lint:twig templates",
            "bin/console lint:container",
            "./vendor/bin/phpstan analyse --memory-limit=-1",
            "./vendor/bin/rector --dry-run"
        ],
        "app:migration": [
            "bin/console doctrine:migrations:migrate"
        ],
        "app:tests": [
            "bin/console doctrine:database:drop --force --if-exists --env=test",
            "bin/console doctrine:database:create --env=test",
            "bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration --env=test",
            "bin/console cache:clear --env=test",
            "bin/phpunit"
        ]
    },
    "conflict": {
        "symfony/symfony": "*"
    },
    "extra": {
        "symfony": {
            "allow-contrib": false,
            "require": "7.2.*"
        }
    },
    "require-dev": {
        "phpstan/phpstan": "^2.1",
        "phpstan/phpstan-doctrine": "^2.0",
        "phpstan/phpstan-symfony": "^2.0",
        "phpunit/phpunit": "^9.5",
        "rector/rector": "^2.0",
        "symfony/browser-kit": "7.2.*",
        "symfony/css-selector": "7.2.*",
        "symfony/debug-bundle": "7.2.*",
        "symfony/maker-bundle": "^1.0",
        "symfony/phpunit-bridge": "^7.2",
        "symfony/stopwatch": "7.2.*",
        "symfony/web-profiler-bundle": "7.2.*",
        "symplify/easy-coding-standard": "^12.5"
    }
}

composer.lock

0 → 100644
+10219 −0

File added.

Preview size limit exceeded, changes collapsed.

config/bundles.php

0 → 100644
+17 −0
Original line number Diff line number Diff line
<?php

return [
    Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
    Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
    Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
    Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
    Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
    Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
    Symfony\UX\StimulusBundle\StimulusBundle::class => ['all' => true],
    Symfony\UX\Turbo\TurboBundle::class => ['all' => true],
    Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
    Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
    Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
    Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
    SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
];
+11 −0
Original line number Diff line number Diff line
framework:
    asset_mapper:
        # The paths to make available to the asset mapper.
        paths:
            - assets/
        missing_import_mode: strict

when@prod:
    framework:
        asset_mapper:
            missing_import_mode: warn
+19 −0
Original line number Diff line number Diff line
framework:
    cache:
        # Unique name of your app: used to compute stable namespaces for cache keys.
        #prefix_seed: your_vendor_name/app_name

        # The "app" cache stores to the filesystem by default.
        # The data in this cache should persist between deploys.
        # Other options include:

        # Redis
        #app: cache.adapter.redis
        #default_redis_provider: redis://localhost

        # APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
        #app: cache.adapter.apcu

        # Namespaced pools use the above "app" backend by default
        #pools:
            #my.dedicated.cache: null
+11 −0
Original line number Diff line number Diff line
# Enable stateless CSRF protection for forms and logins/logouts
framework:
    form:
        csrf_protection:
            token_id: submit

    csrf_protection:
        stateless_token_ids:
            - submit
            - authenticate
            - logout
+5 −0
Original line number Diff line number Diff line
when@dev:
    debug:
        # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
        # See the "server:dump" command to start a new server.
        dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"
+54 −0
Original line number Diff line number Diff line
doctrine:
    dbal:
        url: '%env(resolve:DATABASE_URL)%'

        # IMPORTANT: You MUST configure your server version,
        # either here or in the DATABASE_URL env var (see .env file)
        #server_version: '16'

        profiling_collect_backtrace: '%kernel.debug%'
        use_savepoints: true
    orm:
        auto_generate_proxy_classes: true
        enable_lazy_ghost_objects: true
        report_fields_where_declared: true
        validate_xml_mapping: true
        naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
        identity_generation_preferences:
            Doctrine\DBAL\Platforms\PostgreSQLPlatform: identity
        auto_mapping: true
        mappings:
            App:
                type: attribute
                is_bundle: false
                dir: '%kernel.project_dir%/src/Entity'
                prefix: 'App\Entity'
                alias: App
        controller_resolver:
            auto_mapping: false

when@test:
    doctrine:
        dbal:
            # "TEST_TOKEN" is typically set by ParaTest
            dbname_suffix: '_test%env(default::TEST_TOKEN)%'

when@prod:
    doctrine:
        orm:
            auto_generate_proxy_classes: false
            proxy_dir: '%kernel.build_dir%/doctrine/orm/Proxies'
            query_cache_driver:
                type: pool
                pool: doctrine.system_cache_pool
            result_cache_driver:
                type: pool
                pool: doctrine.result_cache_pool

    framework:
        cache:
            pools:
                doctrine.result_cache_pool:
                    adapter: cache.app
                doctrine.system_cache_pool:
                    adapter: cache.system
Original line number Diff line number Diff line
doctrine_migrations:
    migrations_paths:
        # namespace is arbitrary but should be different from App\Migrations
        # as migrations classes should NOT be autoloaded
        'DoctrineMigrations': '%kernel.project_dir%/migrations'
    enable_profiler: false
+15 −0
Original line number Diff line number Diff line
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
    secret: '%env(APP_SECRET)%'

    # Note that the session will be started ONLY if you read or write from it.
    session: true

    #esi: true
    #fragments: true

when@test:
    framework:
        test: true
        session:
            storage_factory_id: session.storage.factory.mock_file
+3 −0
Original line number Diff line number Diff line
framework:
    mailer:
        dsn: '%env(MAILER_DSN)%'
+29 −0
Original line number Diff line number Diff line
framework:
    messenger:
        failure_transport: failed

        transports:
            # https://symfony.com/doc/current/messenger.html#transport-configuration
            async:
                dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
                options:
                    use_notify: true
                    check_delayed_interval: 60000
                retry_strategy:
                    max_retries: 3
                    multiplier: 2
            failed: 'doctrine://default?queue_name=failed'
            sync: 'sync://'

        default_bus: messenger.bus.default

        buses:
            messenger.bus.default: []

        routing:
            Symfony\Component\Mailer\Messenger\SendEmailMessage: sync
            Symfony\Component\Notifier\Message\ChatMessage: async
            Symfony\Component\Notifier\Message\SmsMessage: async

            # Route your messages to the transports
            # 'App\Message\YourMessage': async
+62 −0
Original line number Diff line number Diff line
monolog:
    channels:
        - deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists

when@dev:
    monolog:
        handlers:
            main:
                type: stream
                path: "%kernel.logs_dir%/%kernel.environment%.log"
                level: debug
                channels: ["!event"]
            # uncomment to get logging in your browser
            # you may have to allow bigger header sizes in your Web server configuration
            #firephp:
            #    type: firephp
            #    level: info
            #chromephp:
            #    type: chromephp
            #    level: info
            console:
                type: console
                process_psr_3_messages: false
                channels: ["!event", "!doctrine", "!console"]

when@test:
    monolog:
        handlers:
            main:
                type: fingers_crossed
                action_level: error
                handler: nested
                excluded_http_codes: [404, 405]
                channels: ["!event"]
            nested:
                type: stream
                path: "%kernel.logs_dir%/%kernel.environment%.log"
                level: debug

when@prod:
    monolog:
        handlers:
            main:
                type: fingers_crossed
                action_level: error
                handler: nested
                excluded_http_codes: [404, 405]
                buffer_size: 50 # How many messages should be saved? Prevent memory leaks
            nested:
                type: stream
                path: php://stderr
                level: debug
                formatter: monolog.formatter.json
            console:
                type: console
                process_psr_3_messages: false
                channels: ["!event", "!doctrine"]
            deprecation:
                type: stream
                channels: [deprecation]
                path: php://stderr
                formatter: monolog.formatter.json
+12 −0
Original line number Diff line number Diff line
framework:
    notifier:
        chatter_transports:
        texter_transports:
        channel_policy:
            # use chat/slack, chat/telegram, sms/twilio or sms/nexmo
            urgent: ['email']
            high: ['email']
            medium: ['email']
            low: ['email']
        admin_recipients:
            - { email: admin@example.com }
+10 −0
Original line number Diff line number Diff line
framework:
    router:
        # Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
        # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
        #default_uri: http://localhost

when@prod:
    framework:
        router:
            strict_requirements: null
+51 −0
Original line number Diff line number Diff line
security:
    # https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
    password_hashers:
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
    # https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
    providers:
        # used to reload user from session & other features (e.g. switch_user)
        app_user_provider:
            entity:
                class: App\Entity\User
                property: email
    firewalls:
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        main:
            lazy: true
            provider: app_user_provider
            form_login:
                login_path: app_login
                check_path: app_login
                enable_csrf: true
            logout:
                path: app_logout
                # where to redirect after logout
                # target: app_any_route

            # activate different ways to authenticate
            # https://symfony.com/doc/current/security.html#the-firewall

            # https://symfony.com/doc/current/security/impersonating_user.html
            # switch_user: true

    # Easy way to control access for large sections of your site
    # Note: Only the *first* access control that matches will be used
    access_control:
        # - { path: ^/admin, roles: ROLE_ADMIN }
        # - { path: ^/profile, roles: ROLE_USER }

when@test:
    security:
        password_hashers:
            # By default, password hashers are resource intensive and take time. This is
            # important to generate secure password hashes. In tests however, secure hashes
            # are not important, waste resources and increase test times. The following
            # reduces the work factor to the lowest possible values.
            Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
                algorithm: auto
                cost: 4 # Lowest possible value for bcrypt
                time_cost: 3 # Lowest possible value for argon
                memory_cost: 10 # Lowest possible value for argon
+7 −0
Original line number Diff line number Diff line
framework:
    default_locale: en
    translator:
        default_path: '%kernel.project_dir%/translations'
        fallbacks:
            - en
        providers:
+6 −0
Original line number Diff line number Diff line
twig:
    file_name_pattern: '*.twig'

when@test:
    twig:
        strict_variables: true
+11 −0
Original line number Diff line number Diff line
framework:
    validation:
        # Enables validator auto-mapping support.
        # For instance, basic validation constraints will be inferred from Doctrine's metadata.
        #auto_mapping:
        #    App\Entity\: []

when@test:
    framework:
        validation:
            not_compromised_password: false
+17 −0
Original line number Diff line number Diff line
when@dev:
    web_profiler:
        toolbar: true
        intercept_redirects: false

    framework:
        profiler:
            only_exceptions: false
            collect_serializer_data: true

when@test:
    web_profiler:
        toolbar: false
        intercept_redirects: false

    framework:
        profiler: { collect: false }

config/preload.php

0 → 100644
+5 −0
Original line number Diff line number Diff line
<?php

if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
    require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

config/routes.yaml

0 → 100644
+5 −0
Original line number Diff line number Diff line
controllers:
    resource:
        path: ../src/Controller/
        namespace: App\Controller
    type: attribute
+4 −0
Original line number Diff line number Diff line
when@dev:
    _errors:
        resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
        prefix: /_error
+3 −0
Original line number Diff line number Diff line
_security_logout:
    resource: security.route_loader.logout
    type: service
+8 −0
Original line number Diff line number Diff line
when@dev:
    web_profiler_wdt:
        resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
        prefix: /_wdt

    web_profiler_profiler:
        resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
        prefix: /_profiler

config/services.yaml

0 → 100644
+24 −0
Original line number Diff line number Diff line
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/'
        exclude:
            - '../src/DependencyInjection/'
            - '../src/Entity/'
            - '../src/Kernel.php'

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones
+4 −0
Original line number Diff line number Diff line
FROM nginx:1.27.1-alpine

# copier la configuration de nginx vers le container
COPY default.conf /etc/nginx/conf.d/default.conf
+36 −0
Original line number Diff line number Diff line
server {
    listen 80;
    server_name localhost;
    root /var/www/public;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    location / {
        root /var/www/;
        try_files /public/$uri /public/$uri /assets/$uri /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 421 422 423 424 425 426 428 429 431 451 500 501 502 503 504 505 506 507 508 510 511 /error.html;

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
 No newline at end of file

docker/php/Dockerfile

0 → 100644
+26 −0
Original line number Diff line number Diff line
# l'image php c'est une image minimaliste qui ne va inclure toutes les extenctions importantes pour fonctionner avec Symphony
# par exemple Ctype, iconv, simpleXML, Session ..

FROM PHP:8.3-fpm-alpine

# install dependencies ' permet d installer des differentes dependances '
RUN apk --no-cache add curl git wget bash dpkg

# aprés pour installer les extentions on serait tenté de taper apt get install ... sauf que ça marchera pas vraiment,
# donc il y'a un outil qu'on va utiliser qui se trouve sur github et permet d'installer plus facilement les extentions
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
# aprés une fois que ce outil sera télechargé on va le rendre excutable en tapant la commande suivante
RUN chmod +x /usr/local/bin/install-php-extensions     

# ensuite on va installer les extentions dont on a besoin
RUN install-php-extensions opcache iconv soap
RUN install-php-extensions zip intl fileinfo
RUN install-php-extensions pdo redis mysqli pdo_mysql
RUN install-php-extensions ctype gd 

# on install composer dans notre container depuis l'instalateur de composeur en ligne 
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer      

# notre répertoir de travail par default ( c'est à l'intérieur que les commandes vots être executées )
WORKDIR /var/www/

ecs.php

0 → 100644
+18 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

use PhpCsFixer\Fixer\Operator\ConcatSpaceFixer;
use Symplify\EasyCodingStandard\Config\ECSConfig;
use PhpCsFixer\Fixer\Import\GlobalNamespaceImportFixer;
use Symplify\EasyCodingStandard\ValueObject\Set\SetList;

return ECSConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests'
    ])
    ->withPreparedSets(true)
    ->withSkip([
        ConcatSpaceFixer::class
    ]);

importmap.php

0 → 100644
+28 −0
Original line number Diff line number Diff line
<?php

/**
 * Returns the importmap for this application.
 *
 * - "path" is a path inside the asset mapper system. Use the
 *     "debug:asset-map" command to see the full list of paths.
 *
 * - "entrypoint" (JavaScript only) set to true for any module that will
 *     be used as an "entrypoint" (and passed to the importmap() Twig function).
 *
 * The "importmap:require" command can be used to add new entries to this file.
 */
return [
    'app' => [
        'path' => './assets/app.js',
        'entrypoint' => true,
    ],
    '@hotwired/stimulus' => [
        'version' => '3.2.2',
    ],
    '@symfony/stimulus-bundle' => [
        'path' => './vendor/symfony/stimulus-bundle/assets/dist/loader.js',
    ],
    '@hotwired/turbo' => [
        'version' => '7.3.0',
    ],
];
+33 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

/**
 * Auto-generated Migration: Please modify to your needs!
 */
final class Version20250309153641 extends AbstractMigration
{
    public function getDescription(): string
    {
        return '';
    }

    public function up(Schema $schema): void
    {
        // this up() migration is auto-generated, please modify it to your needs
        $this->addSql('CREATE TABLE `user` (id INT AUTO_INCREMENT NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL COMMENT \'(DC2Type:json)\', password VARCHAR(255) NOT NULL, is_verified TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL (email), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
        $this->addSql('CREATE TABLE messenger_messages (id BIGINT AUTO_INCREMENT NOT NULL, body LONGTEXT NOT NULL, headers LONGTEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', available_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', delivered_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', INDEX IDX_75EA56E0FB7336F0 (queue_name), INDEX IDX_75EA56E0E3BD61CE (available_at), INDEX IDX_75EA56E016BA31DB (delivered_at), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
    }

    public function down(Schema $schema): void
    {
        // this down() migration is auto-generated, please modify it to your needs
        $this->addSql('DROP TABLE `user`');
        $this->addSql('DROP TABLE messenger_messages');
    }
}

phpstan.dist.neon

0 → 100644
+15 −0
Original line number Diff line number Diff line
includes:
    - vendor/phpstan/phpstan-symfony/extension.neon
    - vendor/phpstan/phpstan-symfony/rules.neon
    - vendor/phpstan/phpstan-doctrine/extension.neon
    - vendor/phpstan/phpstan-doctrine/rules.neon

parameters:
    level: 6
    paths:
        - src/
        - tests/
    doctrine:
        allowNullablePropertyForRequiredField: true
    ignoreErrors:
        - identifier: missingType.generics
 No newline at end of file

phpunit.xml.dist

0 → 100644
+38 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>

<!-- https://phpunit.readthedocs.io/en/latest/configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         backupGlobals="false"
         colors="true"
         bootstrap="tests/bootstrap.php"
         convertDeprecationsToExceptions="false"
>
    <php>
        <ini name="display_errors" value="1" />
        <ini name="error_reporting" value="-1" />
        <server name="APP_ENV" value="test" force="true" />
        <server name="SHELL_VERBOSITY" value="-1" />
        <server name="SYMFONY_PHPUNIT_REMOVE" value="" />
        <server name="SYMFONY_PHPUNIT_VERSION" value="9.5" />
    </php>

    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <coverage processUncoveredFiles="true">
        <include>
            <directory suffix=".php">src</directory>
        </include>
    </coverage>

    <listeners>
        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
    </listeners>

    <extensions>
    </extensions>
</phpunit>

public/index.php

0 → 100644
+9 −0
Original line number Diff line number Diff line
<?php

use App\Kernel;

require_once dirname(__DIR__).'/vendor/autoload_runtime.php';

return function (array $context) {
    return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

rector.php

0 → 100644
+13 −0
Original line number Diff line number Diff line
<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;

return RectorConfig::configure()
    ->withPaths([
        __DIR__ . '/src',
        __DIR__ . '/tests',
    ])
    ->withPhpSets(php83: true)
    ->withTypeCoverageLevel(0);
 No newline at end of file
Original line number Diff line number Diff line
<?php

namespace App\Controller;

use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Repository\UserRepository;
use App\Security\EmailVerifier;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mime\Address;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;

class RegistrationController extends AbstractController
{
    public function __construct(private readonly EmailVerifier $emailVerifier)
    {
    }

    #[Route('/register', name: 'app_register')]
    public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, Security $security, EntityManagerInterface $entityManager): Response
    {
        $user = new User();
        $form = $this->createForm(RegistrationFormType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            /** @var string $plainPassword */
            $plainPassword = $form->get('plainPassword')->getData();

            // encode the plain password
            $user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword));

            $entityManager->persist($user);
            $entityManager->flush();

            // generate a signed url and email it to the user
            $this->emailVerifier->sendEmailConfirmation(
                'app_verify_email',
                $user,
                (new TemplatedEmail())
                    ->from(new Address('app@test.com', 'Acme Mail'))
                    ->to((string) $user->getEmail())
                    ->subject('Please Confirm your Email')
                    ->htmlTemplate('registration/confirmation_email.html.twig')
            );

            // do anything else you need here, like send an email

            return $this->redirectToRoute('app_login');
        }

        return $this->render('registration/register.html.twig', [
            'registrationForm' => $form,
        ]);
    }

    #[Route('/verify/email', name: 'app_verify_email')]
    public function verifyUserEmail(Request $request, TranslatorInterface $translator, UserRepository $userRepository): Response
    {
        $id = $request->query->get('id');

        if (null === $id) {
            return $this->redirectToRoute('app_register');
        }

        $user = $userRepository->find($id);

        if (null === $user) {
            return $this->redirectToRoute('app_register');
        }

        // validate email confirmation link, sets User::isVerified=true and persists
        try {
            $this->emailVerifier->handleEmailConfirmation($request, $user);
        } catch (VerifyEmailExceptionInterface $exception) {
            $this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle'));

            return $this->redirectToRoute('app_register');
        }

        // @TODO Change the redirect on success and handle or remove the flash message in your templates
        $this->addFlash('success', 'Your email address has been verified.');

        return $this->redirectToRoute('app_register');
    }
}
+32 −0
Original line number Diff line number Diff line
<?php

namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;

class SecurityController extends AbstractController
{
    #[Route(path: '/login', name: 'app_login')]
    public function login(AuthenticationUtils $authenticationUtils): Response
    {
        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();

        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render('security/login.html.twig', [
            'last_username' => $lastUsername,
            'error' => $error,
        ]);
    }

    #[Route(path: '/logout', name: 'app_logout')]
    public function logout(): void
    {
        throw new \LogicException('This method can be blank - it will be intercepted by the logout key on your firewall.');
    }
}
+81 −0
Original line number Diff line number Diff line
<?php

namespace App\Controller;

use App\Entity\User;
use App\Form\UserType;
use App\Repository\UserRepository;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route;

#[Route('/user')]
final class UserController extends AbstractController
{
    #[Route(name: 'app_user_index', methods: ['GET'])]
    public function index(UserRepository $userRepository): Response
    {
        return $this->render('user/index.html.twig', [
            'users' => $userRepository->findAll(),
        ]);
    }

    #[Route('/new', name: 'app_user_new', methods: ['GET', 'POST'])]
    public function new(Request $request, EntityManagerInterface $entityManager): Response
    {
        $user = new User();
        $form = $this->createForm(UserType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->persist($user);
            $entityManager->flush();

            return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
        }

        return $this->render('user/new.html.twig', [
            'user' => $user,
            'form' => $form,
        ]);
    }

    #[Route('/{id}', name: 'app_user_show', methods: ['GET'])]
    public function show(User $user): Response
    {
        return $this->render('user/show.html.twig', [
            'user' => $user,
        ]);
    }

    #[Route('/{id}/edit', name: 'app_user_edit', methods: ['GET', 'POST'])]
    public function edit(Request $request, User $user, EntityManagerInterface $entityManager): Response
    {
        $form = $this->createForm(UserType::class, $user);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $entityManager->flush();

            return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
        }

        return $this->render('user/edit.html.twig', [
            'user' => $user,
            'form' => $form,
        ]);
    }

    #[Route('/{id}', name: 'app_user_delete', methods: ['POST'])]
    public function delete(Request $request, User $user, EntityManagerInterface $entityManager): Response
    {
        if ($this->isCsrfTokenValid('delete'.$user->getId(), $request->getPayload()->getString('_token'))) {
            $entityManager->remove($user);
            $entityManager->flush();
        }

        return $this->redirectToRoute('app_user_index', [], Response::HTTP_SEE_OTHER);
    }
}

src/Entity/User.php

0 → 100644
+126 −0
Original line number Diff line number Diff line
<?php

namespace App\Entity;

use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;

#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[ORM\UniqueConstraint(name: 'UNIQ_IDENTIFIER_EMAIL', fields: ['email'])]
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
    #[ORM\Id]
    #[ORM\GeneratedValue]
    #[ORM\Column]
    private ?int $id = null;

    #[ORM\Column(length: 180)]
    private ?string $email = null;

    /**
     * @var list<string> The user roles
     */
    #[ORM\Column]
    private array $roles = [];

    /**
     * @var string The hashed password
     */
    #[ORM\Column]
    private ?string $password = null;

    #[ORM\Column]
    private bool $isVerified = false;

    public function getId(): ?int
    {
        return $this->id;
    }

    public function getEmail(): ?string
    {
        return $this->email;
    }

    public function setEmail(string $email): static
    {
        $this->email = $email;

        return $this;
    }

    /**
     * A visual identifier that represents this user.
     *
     * @see UserInterface
     */
    public function getUserIdentifier(): string
    {
        return (string) $this->email;
    }

    /**
     * @see UserInterface
     *
     * @return list<string>
     */
    public function getRoles(): array
    {
        $roles = $this->roles;
        // guarantee every user at least has ROLE_USER
        $roles[] = 'ROLE_USER';

        return array_unique($roles);
    }

    /**
     * @param list<string> $roles
     */
    public function setRoles(array $roles): static
    {
        $this->roles = $roles;

        return $this;
    }

    /**
     * @see PasswordAuthenticatedUserInterface
     */
    public function getPassword(): ?string
    {
        return $this->password;
    }

    public function setPassword(string $password): static
    {
        $this->password = $password;

        return $this;
    }

    /**
     * @see UserInterface
     */
    public function eraseCredentials(): void
    {
        // If you store any temporary, sensitive data on the user, clear it here
        // $this->plainPassword = null;
    }

    public function isVerified(): bool
    {
        return $this->isVerified;
    }

    public function setVerified(bool $isVerified): static
    {
        $this->isVerified = $isVerified;

        return $this;
    }
}
+55 −0
Original line number Diff line number Diff line
<?php

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;

class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('email')
            ->add('agreeTerms', CheckboxType::class, [
                'mapped' => false,
                'constraints' => [
                    new IsTrue([
                        'message' => 'You should agree to our terms.',
                    ]),
                ],
            ])
            ->add('plainPassword', PasswordType::class, [
                // instead of being set onto the object directly,
                // this is read and encoded in the controller
                'mapped' => false,
                'attr' => ['autocomplete' => 'new-password'],
                'constraints' => [
                    new NotBlank([
                        'message' => 'Please enter a password',
                    ]),
                    new Length([
                        'min' => 6,
                        'minMessage' => 'Your password should be at least {{ limit }} characters',
                        // max length allowed by Symfony for security reasons
                        'max' => 4096,
                    ]),
                ],
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
}

src/Form/UserType.php

0 → 100644
+42 −0
Original line number Diff line number Diff line
<?php

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('email', EmailType::class)
            ->add('roles', ChoiceType::class, [
                'choices' => [
                    'User' => 'ROLE_USER',
                    'Admin' => 'ROLE_ADMIN',
                ],
                'multiple' => true,
                'expanded' => true,
            ])
            ->add('password', PasswordType::class)
            ->add('verified', CheckboxType::class, [
                'label' => 'Is Verified?',
                'required' => false,
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'data_class' => User::class,
        ]);
    }
}

src/Kernel.php

0 → 100644
+11 −0
Original line number Diff line number Diff line
<?php

namespace App;

use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;
}
+60 −0
Original line number Diff line number Diff line
<?php

namespace App\Repository;

use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;

/**
 * @extends ServiceEntityRepository<User>
 */
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
    public function __construct(ManagerRegistry $registry)
    {
        parent::__construct($registry, User::class);
    }

    /**
     * Used to upgrade (rehash) the user's password automatically over time.
     */
    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
    {
        if (!$user instanceof User) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', $user::class));
        }

        $user->setPassword($newHashedPassword);
        $this->getEntityManager()->persist($user);
        $this->getEntityManager()->flush();
    }

    //    /**
    //     * @return User[] Returns an array of User objects
    //     */
    //    public function findByExampleField($value): array
    //    {
    //        return $this->createQueryBuilder('u')
    //            ->andWhere('u.exampleField = :val')
    //            ->setParameter('val', $value)
    //            ->orderBy('u.id', 'ASC')
    //            ->setMaxResults(10)
    //            ->getQuery()
    //            ->getResult()
    //        ;
    //    }

    //    public function findOneBySomeField($value): ?User
    //    {
    //        return $this->createQueryBuilder('u')
    //            ->andWhere('u.exampleField = :val')
    //            ->setParameter('val', $value)
    //            ->getQuery()
    //            ->getOneOrNullResult()
    //        ;
    //    }
}
+53 −0
Original line number Diff line number Diff line
<?php

namespace App\Security;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;

class EmailVerifier
{
    public function __construct(
        private readonly VerifyEmailHelperInterface $verifyEmailHelper,
        private readonly MailerInterface $mailer,
        private readonly EntityManagerInterface $entityManager
    ) {
    }

    public function sendEmailConfirmation(string $verifyEmailRouteName, User $user, TemplatedEmail $email): void
    {
        $signatureComponents = $this->verifyEmailHelper->generateSignature(
            $verifyEmailRouteName,
            (string) $user->getId(),
            (string) $user->getEmail(),
            ['id' => $user->getId()]
        );

        $context = $email->getContext();
        $context['signedUrl'] = $signatureComponents->getSignedUrl();
        $context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
        $context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();

        $email->context($context);

        $this->mailer->send($email);
    }

    /**
     * @throws VerifyEmailExceptionInterface
     */
    public function handleEmailConfirmation(Request $request, User $user): void
    {
        $this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $user->getId(), (string) $user->getEmail());

        $user->setVerified(true);

        $this->entityManager->persist($user);
        $this->entityManager->flush();
    }
}

symfony.lock

0 → 100644
+317 −0
Original line number Diff line number Diff line
{
    "doctrine/doctrine-bundle": {
        "version": "2.13",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "2.13",
            "ref": "8d96c0b51591ffc26794d865ba3ee7d193438a83"
        },
        "files": [
            "config/packages/doctrine.yaml",
            "src/Entity/.gitignore",
            "src/Repository/.gitignore"
        ]
    },
    "doctrine/doctrine-migrations-bundle": {
        "version": "3.3",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "3.1",
            "ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33"
        },
        "files": [
            "config/packages/doctrine_migrations.yaml",
            "migrations/.gitignore"
        ]
    },
    "phpstan/phpstan": {
        "version": "2.1",
        "recipe": {
            "repo": "github.com/symfony/recipes-contrib",
            "branch": "main",
            "version": "1.0",
            "ref": "5e490cc197fb6bb1ae22e5abbc531ddc633b6767"
        },
        "files": [
            "phpstan.dist.neon"
        ]
    },
    "phpunit/phpunit": {
        "version": "9.6",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "9.6",
            "ref": "7364a21d87e658eb363c5020c072ecfdc12e2326"
        },
        "files": [
            ".env.test",
            "phpunit.xml.dist",
            "tests/bootstrap.php"
        ]
    },
    "symfony/asset-mapper": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "6.4",
            "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a"
        },
        "files": [
            "assets/app.js",
            "assets/styles/app.css",
            "config/packages/asset_mapper.yaml",
            "importmap.php"
        ]
    },
    "symfony/console": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "5.3",
            "ref": "1781ff40d8a17d87cf53f8d4cf0c8346ed2bb461"
        },
        "files": [
            "bin/console"
        ]
    },
    "symfony/debug-bundle": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "5.3",
            "ref": "5aa8aa48234c8eb6dbdd7b3cd5d791485d2cec4b"
        },
        "files": [
            "config/packages/debug.yaml"
        ]
    },
    "symfony/flex": {
        "version": "2.4",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "2.4",
            "ref": "52e9754527a15e2b79d9a610f98185a1fe46622a"
        },
        "files": [
            ".env",
            ".env.dev"
        ]
    },
    "symfony/form": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "7.2",
            "ref": "7d86a6723f4a623f59e2bf966b6aad2fc461d36b"
        },
        "files": [
            "config/packages/csrf.yaml"
        ]
    },
    "symfony/framework-bundle": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "7.2",
            "ref": "87bcf6f7c55201f345d8895deda46d2adbdbaa89"
        },
        "files": [
            "config/packages/cache.yaml",
            "config/packages/framework.yaml",
            "config/preload.php",
            "config/routes/framework.yaml",
            "config/services.yaml",
            "public/index.php",
            "src/Controller/.gitignore",
            "src/Kernel.php"
        ]
    },
    "symfony/mailer": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "4.3",
            "ref": "09051cfde49476e3c12cd3a0e44289ace1c75a4f"
        },
        "files": [
            "config/packages/mailer.yaml"
        ]
    },
    "symfony/maker-bundle": {
        "version": "1.61",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "1.0",
            "ref": "fadbfe33303a76e25cb63401050439aa9b1a9c7f"
        }
    },
    "symfony/messenger": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "6.0",
            "ref": "ba1ac4e919baba5644d31b57a3284d6ba12d52ee"
        },
        "files": [
            "config/packages/messenger.yaml"
        ]
    },
    "symfony/monolog-bundle": {
        "version": "3.10",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "3.7",
            "ref": "aff23899c4440dd995907613c1dd709b6f59503f"
        },
        "files": [
            "config/packages/monolog.yaml"
        ]
    },
    "symfony/notifier": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "5.0",
            "ref": "178877daf79d2dbd62129dd03612cb1a2cb407cc"
        },
        "files": [
            "config/packages/notifier.yaml"
        ]
    },
    "symfony/phpunit-bridge": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "6.3",
            "ref": "a411a0480041243d97382cac7984f7dce7813c08"
        },
        "files": [
            ".env.test",
            "bin/phpunit",
            "phpunit.xml.dist",
            "tests/bootstrap.php"
        ]
    },
    "symfony/routing": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "7.0",
            "ref": "21b72649d5622d8f7da329ffb5afb232a023619d"
        },
        "files": [
            "config/packages/routing.yaml",
            "config/routes.yaml"
        ]
    },
    "symfony/security-bundle": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "6.4",
            "ref": "2ae08430db28c8eb4476605894296c82a642028f"
        },
        "files": [
            "config/packages/security.yaml",
            "config/routes/security.yaml"
        ]
    },
    "symfony/stimulus-bundle": {
        "version": "2.22",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "2.20",
            "ref": "41c4285a926752ec2852a5cafa39e7b527c33a38"
        },
        "files": [
            "assets/bootstrap.js",
            "assets/controllers.json",
            "assets/controllers/csrf_protection_controller.js",
            "assets/controllers/hello_controller.js"
        ]
    },
    "symfony/translation": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "6.3",
            "ref": "e28e27f53663cc34f0be2837aba18e3a1bef8e7b"
        },
        "files": [
            "config/packages/translation.yaml",
            "translations/.gitignore"
        ]
    },
    "symfony/twig-bundle": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "6.4",
            "ref": "cab5fd2a13a45c266d45a7d9337e28dee6272877"
        },
        "files": [
            "config/packages/twig.yaml",
            "templates/base.html.twig"
        ]
    },
    "symfony/ux-turbo": {
        "version": "2.22",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "2.20",
            "ref": "c85ff94da66841d7ff087c19cbcd97a2df744ef9"
        }
    },
    "symfony/validator": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "7.0",
            "ref": "8c1c4e28d26a124b0bb273f537ca8ce443472bfd"
        },
        "files": [
            "config/packages/validator.yaml"
        ]
    },
    "symfony/web-profiler-bundle": {
        "version": "7.2",
        "recipe": {
            "repo": "github.com/symfony/recipes",
            "branch": "main",
            "version": "6.1",
            "ref": "e42b3f0177df239add25373083a564e5ead4e13a"
        },
        "files": [
            "config/packages/web_profiler.yaml",
            "config/routes/web_profiler.yaml"
        ]
    },
    "symfonycasts/verify-email-bundle": {
        "version": "v1.17.3"
    },
    "twig/extra-bundle": {
        "version": "v3.18.0"
    }
}
+17 −0
Original line number Diff line number Diff line
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>{% block title %}Welcome!{% endblock %}</title>
        <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>">
        {% block stylesheets %}
        {% endblock %}

        {% block javascripts %}
            {% block importmap %}{{ importmap('app') }}{% endblock %}
        {% endblock %}
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>
Original line number Diff line number Diff line
<h1>Hi! Please confirm your email!</h1>

<p>
    Please confirm your email address by clicking the following link: <br><br>
    <a href="{{ signedUrl|raw }}">Confirm my Email</a>.
    This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
</p>

<p>
    Cheers!
</p>
Original line number Diff line number Diff line
{% extends 'base.html.twig' %}

{% block title %}Register{% endblock %}

{% block body %}
    {% for flash_error in app.flashes('verify_email_error') %}
        <div class="alert alert-danger" role="alert">{{ flash_error }}</div>
    {% endfor %}

    <h1>Register</h1>

    {{ form_errors(registrationForm) }}

    {{ form_start(registrationForm) }}
        {{ form_row(registrationForm.email) }}
        {{ form_row(registrationForm.plainPassword, {
            label: 'Password'
        }) }}
        {{ form_row(registrationForm.agreeTerms) }}

        <button type="submit" class="btn">Register</button>
    {{ form_end(registrationForm) }}
{% endblock %}
+41 −0
Original line number Diff line number Diff line
{% extends 'base.html.twig' %}

{% block title %}Log in!{% endblock %}

{% block body %}
    <form method="post">
        {% if error %}
            <div class="alert alert-danger">{{ error.messageKey|trans(error.messageData, 'security') }}</div>
        {% endif %}

        {% if app.user %}
            <div class="mb-3">
                You are logged in as {{ app.user.userIdentifier }}, <a href="{{ path('app_logout') }}">Logout</a>
            </div>
        {% endif %}

        <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
        <label for="username">Email</label>
        <input type="email" value="{{ last_username }}" name="_username" id="username" class="form-control" autocomplete="email" required autofocus>
        <label for="password">Password</label>
        <input type="password" name="_password" id="password" class="form-control" autocomplete="current-password" required>

        <input type="hidden" name="_csrf_token"
               value="{{ csrf_token('authenticate') }}"
        >

        {#
            Uncomment this section and add a remember_me option below your firewall to activate remember me functionality.
            See https://symfony.com/doc/current/security/remember_me.html

            <div class="checkbox mb-3">
                <input type="checkbox" name="_remember_me" id="_remember_me">
                <label for="_remember_me">Remember me</label>
            </div>
        #}

        <button class="btn btn-lg btn-primary" type="submit">
            Sign in
        </button>
    </form>
{% endblock %}
+4 −0
Original line number Diff line number Diff line
<form method="post" action="{{ path('app_user_delete', {'id': user.id}) }}" onsubmit="return confirm('Are you sure you want to delete this item?');">
    <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ user.id) }}">
    <button class="btn">Delete</button>
</form>
+4 −0
Original line number Diff line number Diff line
{{ form_start(form) }}
    {{ form_widget(form) }}
    <button class="btn">{{ button_label|default('Save') }}</button>
{{ form_end(form) }}
+13 −0
Original line number Diff line number Diff line
{% extends 'base.html.twig' %}

{% block title %}Edit User{% endblock %}

{% block body %}
    <h1>Edit User</h1>

    {{ include('user/_form.html.twig', {'button_label': 'Update'}) }}

    <a href="{{ path('app_user_index') }}">back to list</a>

    {{ include('user/_delete_form.html.twig') }}
{% endblock %}
+41 −0
Original line number Diff line number Diff line
{% extends 'base.html.twig' %}

{% block title %}User index{% endblock %}

{% block body %}
    <h1>User index</h1>

    <table class="table">
        <thead>
            <tr>
                <th>Id</th>
                <th>Email</th>
                <th>Roles</th>
                <th>Password</th>
                <th>IsVerified</th>
                <th>actions</th>
            </tr>
        </thead>
        <tbody>
        {% for user in users %}
            <tr>
                <td>{{ user.id }}</td>
                <td>{{ user.email }}</td>
                <td>{{ user.roles ? user.roles|json_encode : '' }}</td>
                <td>{{ user.password }}</td>
                <td>{{ user.isVerified ? 'Yes' : 'No' }}</td>
                <td>
                    <a href="{{ path('app_user_show', {'id': user.id}) }}">show</a>
                    <a href="{{ path('app_user_edit', {'id': user.id}) }}">edit</a>
                </td>
            </tr>
        {% else %}
            <tr>
                <td colspan="6">no records found</td>
            </tr>
        {% endfor %}
        </tbody>
    </table>

    <a href="{{ path('app_user_new') }}">Create new</a>
{% endblock %}
+11 −0
Original line number Diff line number Diff line
{% extends 'base.html.twig' %}

{% block title %}New User{% endblock %}

{% block body %}
    <h1>Create new User</h1>

    {{ include('user/_form.html.twig') }}

    <a href="{{ path('app_user_index') }}">back to list</a>
{% endblock %}
+38 −0
Original line number Diff line number Diff line
{% extends 'base.html.twig' %}

{% block title %}User{% endblock %}

{% block body %}
    <h1>User</h1>

    <table class="table">
        <tbody>
            <tr>
                <th>Id</th>
                <td>{{ user.id }}</td>
            </tr>
            <tr>
                <th>Email</th>
                <td>{{ user.email }}</td>
            </tr>
            <tr>
                <th>Roles</th>
                <td>{{ user.roles ? user.roles|json_encode : '' }}</td>
            </tr>
            <tr>
                <th>Password</th>
                <td>{{ user.password }}</td>
            </tr>
            <tr>
                <th>IsVerified</th>
                <td>{{ user.isVerified ? 'Yes' : 'No' }}</td>
            </tr>
        </tbody>
    </table>

    <a href="{{ path('app_user_index') }}">back to list</a>

    <a href="{{ path('app_user_edit', {'id': user.id}) }}">edit</a>

    {{ include('user/_delete_form.html.twig') }}
{% endblock %}
+127 −0
Original line number Diff line number Diff line
<?php

namespace App\Tests\Controller;

use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

final class UserControllerTest extends WebTestCase
{
    private KernelBrowser $client;
    private EntityManagerInterface $manager;
    private EntityRepository $repository;
    private string $path = '/user';

    protected function setUp(): void
    {
        $this->client = static::createClient();
        $this->manager = static::getContainer()->get('doctrine')->getManager();
        $this->repository = $this->manager->getRepository(User::class);

        foreach ($this->repository->findAll() as $object) {
            $this->manager->remove($object);
        }

        $this->manager->flush();
    }

    public function testIndex(): void
    {
        $this->client->followRedirects();
        $crawler = $this->client->request('GET', $this->path);

        self::assertResponseStatusCodeSame(200);
        self::assertPageTitleContains('User index');

        // Use the $crawler to perform additional assertions e.g.
        // self::assertSame('Some text on the page', $crawler->filter('.p')->first());
    }

    public function testNew(): void
    {
        $this->client->request('GET', sprintf('%s/new', $this->path));

        self::assertResponseStatusCodeSame(200);

        $this->client->submitForm('Save', [
            'user[email]' => 'test@test.com',
            'user[roles]' => ['ROLE_USER'],
            'user[password]' => 'Testing',
            'user[verified]' => true,
        ]);

        self::assertResponseRedirects($this->path);

        self::assertSame(1, $this->repository->count([]));
    }

    public function testShow(): void
    {
        $fixture = new User();
        $fixture->setEmail('test@test.com');
        $fixture->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
        $fixture->setPassword('testing');
        $fixture->setVerified(true);

        $this->manager->persist($fixture);
        $this->manager->flush();

        $this->client->request('GET', sprintf('%s/%s', $this->path, $fixture->getId()));

        self::assertResponseStatusCodeSame(200);
        self::assertPageTitleContains('User');

        // Use assertions to check that the properties are properly displayed.
    }

    public function testEdit(): void
    {
        $fixture = new User();
        $fixture->setEmail('test@test.com');
        $fixture->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
        $fixture->setPassword('testing');
        $fixture->setVerified(true);

        $this->manager->persist($fixture);
        $this->manager->flush();

        $this->client->request('GET', sprintf('%s/%s/edit', $this->path, $fixture->getId()));

        $this->client->submitForm('Update', [
            'user[email]' => 'test+1@test.com',
            'user[roles]' => ['ROLE_USER', 'ROLE_ADMIN'],
            'user[password]' => 'Something New',
            'user[verified]' => true,
        ]);

        self::assertResponseRedirects('/user');

        $fixture = $this->repository->findAll();

        self::assertSame('test+1@test.com', $fixture[0]->getEmail());
        self::assertSame(['ROLE_USER', 'ROLE_ADMIN'], $fixture[0]->getRoles());
        self::assertSame('Something New', $fixture[0]->getPassword());
        self::assertSame(true, $fixture[0]->isVerified());
    }

    public function testRemove(): void
    {
        $fixture = new User();
        $fixture->setEmail('test+1@test.com');
        $fixture->setRoles(['ROLE_USER', 'ROLE_ADMIN']);
        $fixture->setPassword('Value');
        $fixture->setVerified(true);

        $this->manager->persist($fixture);
        $this->manager->flush();

        $this->client->request('GET', sprintf('%s/%s', $this->path, $fixture->getId()));
        $this->client->submitForm('Delete');

        self::assertResponseRedirects('/user');
        self::assertSame(0, $this->repository->count([]));
    }
}

tests/bootstrap.php

0 → 100644
+11 −0
Original line number Diff line number Diff line
<?php

use Symfony\Component\Dotenv\Dotenv;

require dirname(__DIR__).'/vendor/autoload.php';

if (file_exists(dirname(__DIR__).'/config/bootstrap.php')) {
    require dirname(__DIR__).'/config/bootstrap.php';
} elseif (method_exists(Dotenv::class, 'bootEnv')) {
    (new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
}