6 Commits

Author SHA1 Message Date
ee1a15ee78 New pipeline
Some checks failed
CI / Tests (push) Failing after 36s
CI / Build and deploy to ${{ startsWith(github.ref, 'refs/tags/') && 'production' || (github.ref == 'refs/heads/main' && 'acceptance' || '') }} (push) Has been skipped
2025-10-21 00:06:23 +02:00
253729abc0 Add completions 2025-10-20 21:41:46 +02:00
b66d2f9e86 Refactor entities and codebase for native property usage
Some checks failed
CI / Tests (push) Failing after 35s
CI / Deploy (push) Has been skipped
- Replaced getters/setters with direct property access across entities and repositories.
- Added and configured `martin-georgiev/postgresql-for-doctrine` for PostgreSQL enhancements.
- Updated Doctrine configuration with types, mappings, and JSONB query functions.
- Removed unused `EliminationService` and related YAML configurations.
2025-10-08 20:50:33 +02:00
ab187a28b9 Add Symfony container support for Rector configuration
- Introduced `tests/symfony-container.php` for bootstrapping Symfony.
  - Updated `rector.php` with new container PHP path and kernel modifications.
  - Registered Symfony routes provider service in Rector.
2025-10-08 20:50:33 +02:00
ca460cca7f - Refactor ORM annotations across entities for consistency.
- Adjusted migrations to align with refactored ORM annotations.
- Added new PHPStan and PHP-CS-Fixer configurations, including stricter rules.
- Introduced `tests/object-manager.php` to improve test environment setup.
2025-10-04 12:40:45 +02:00
3e39550c90 Remove EasyAdmin integration
- Removed EasyAdmin controllers and configurations.
- Uninstalled `easycorp/easyadmin-bundle` and related dependencies.
- Cleaned up all associated routes, bundles, and vendor references.
2025-10-03 22:37:45 +02:00
84 changed files with 1057 additions and 1694 deletions

View File

@@ -13,6 +13,10 @@ concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
packages: write
jobs:
tests:
name: Tests
@@ -57,17 +61,62 @@ jobs:
run: docker compose exec -T php vendor/bin/phpunit
- name: Doctrine Schema Validator
run: docker compose exec -T php bin/console -e test doctrine:schema:validate
deploy:
name: Deploy
build-deploy:
name: Build and deploy to ${{ startsWith(github.ref, 'refs/tags/') && 'production' || (github.ref == 'refs/heads/main' && 'acceptance' || '') }}
environment:
name: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || (github.ref == 'refs/heads/main' && 'acceptance' || '') }}
url: ${{ vars.URL }}
needs: tests
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/')
if: (github.ref == 'refs/heads/main' && false) || startsWith(github.ref, 'refs/tags/')
steps:
- shell: bash
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
run: |
REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
TAG="${GITHUB_REF#refs/tags/}"
{
echo "tag=$TAG"
echo "full_name=ghcr.io/${REPO_LOWER}:$TAG"
} >> "$GITHUB_OUTPUT"
else
SHORT_SHA=$(git rev-parse --short HEAD)
{
echo "tag=$SHORT_SHA"
echo "full_name=ghcr.io/${REPO_LOWER}:$SHORT_SHA"
} >> "$GITHUB_OUTPUT"
fi
- name: Build and Push Docker images
uses: docker/bake-action@v5
with:
pull: true
push: true
files: |
compose.yaml
compose.build.yaml
set: |
*.cache-from=type=gha,scope=${{github.ref}}
*.cache-from=type=gha,scope=refs/heads/main
*.cache-to=type=gha,scope=${{github.ref}},mode=max
*.tags=${{ steps.meta.outputs.full_name }}
- name: Trigger Portainer Deployment
shell: bash
env:
PORTAINER_WEBHOOK: ${{secrets.PORTAINER_WEBHOOK}}
run: |
curl -v -X POST "$PORTAINER_WEBHOOK"
curl -v -X POST "$PORTAINER_WEBHOOK"?IMAGE_TAG=${{steps.meta.outputs.tag}} --fail-with-body

View File

@@ -116,15 +116,12 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/thecodingmachine/phpstan-safe-rule" />
<excludeFolder url="file://$MODULE_DIR$/vendor/thecodingmachine/safe" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/twig" />
<excludeFolder url="file://$MODULE_DIR$/vendor/easycorp/easyadmin-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/intl" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/mime" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-intl-idn" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/translation" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/ux-twig-component" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/validator" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/extra-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/twig/html-extra" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/data-fixtures" />
<excludeFolder url="file://$MODULE_DIR$/vendor/doctrine/doctrine-fixtures-bundle" />
<excludeFolder url="file://$MODULE_DIR$/vendor/vincentlanglet/twig-cs-fixer" />
@@ -167,6 +164,7 @@
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/ux-turbo" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/polyfill-uuid" />
<excludeFolder url="file://$MODULE_DIR$/vendor/symfony/brevo-mailer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/martin-georgiev/postgresql-for-doctrine" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

8
.idea/php.xml generated
View File

@@ -68,7 +68,6 @@
<path value="$PROJECT_DIR$/vendor/symfony/form" />
<path value="$PROJECT_DIR$/vendor/runtime/frankenphp-symfony" />
<path value="$PROJECT_DIR$/vendor/symfony/framework-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/vendor/symfony/cache" />
<path value="$PROJECT_DIR$/vendor/symfony/serializer" />
<path value="$PROJECT_DIR$/vendor/symfony/dotenv" />
@@ -78,7 +77,6 @@
<path value="$PROJECT_DIR$/vendor/symfony/browser-kit" />
<path value="$PROJECT_DIR$/vendor/symfony/config" />
<path value="$PROJECT_DIR$/vendor/symfony/security-http" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/symfony/translation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/event-dispatcher" />
<path value="$PROJECT_DIR$/vendor/symfony/runtime" />
@@ -124,7 +122,6 @@
<path value="$PROJECT_DIR$/vendor/symfony/error-handler" />
<path value="$PROJECT_DIR$/vendor/symfony/cache-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/console" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-twig-component" />
<path value="$PROJECT_DIR$/vendor/symfony/password-hasher" />
<path value="$PROJECT_DIR$/vendor/symfony/process" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-intl-grapheme" />
@@ -142,7 +139,6 @@
<path value="$PROJECT_DIR$/vendor/doctrine/sql-formatter" />
<path value="$PROJECT_DIR$/vendor/doctrine/orm" />
<path value="$PROJECT_DIR$/vendor/doctrine/event-manager" />
<path value="$PROJECT_DIR$/vendor/easycorp/easyadmin-bundle" />
<path value="$PROJECT_DIR$/vendor/evenement/evenement" />
<path value="$PROJECT_DIR$/vendor/doctrine/deprecations" />
<path value="$PROJECT_DIR$/vendor/doctrine/doctrine-bundle" />
@@ -191,13 +187,15 @@
<path value="$PROJECT_DIR$/vendor/clue/ndjson-react" />
<path value="$PROJECT_DIR$/vendor/fidry/cpu-core-counter" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/twig/html-extra" />
<path value="$PROJECT_DIR$/vendor/twig/twig" />
<path value="$PROJECT_DIR$/vendor/symfony/ux-turbo" />
<path value="$PROJECT_DIR$/vendor/symfony/stimulus-bundle" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-uuid" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-php84" />
<path value="$PROJECT_DIR$/vendor/symfony/brevo-mailer" />
<path value="$PROJECT_DIR$/vendor/symfony/translation" />
<path value="$PROJECT_DIR$/vendor/symfony/validator" />
<path value="$PROJECT_DIR$/vendor/martin-georgiev/postgresql-for-doctrine" />
</include_path>
</component>
<component name="PhpInterpreters">

View File

@@ -5,13 +5,13 @@ use PhpCsFixer\Config;
use PhpCsFixer\Finder;
use PhpCsFixer\Runner\Parallel\ParallelConfigFactory;
$finder = (new Finder())
$finder = new Finder()
->in(__DIR__)
->exclude('var')
->exclude('bin')
;
return (new Config())
return new Config()
->setParallelConfig(ParallelConfigFactory::detect())
->setRules([
'@Symfony' => true,
@@ -30,8 +30,9 @@ return (new Config())
'single_line_empty_body' => true,
'strict_comparison' => true,
'strict_param' => true,
'ordered_attributes' => true,
'heredoc_indentation' => ['indentation' => 'start_plus_one'],
'trailing_comma_in_multiline' => ['after_heredoc' => true, 'elements' => ['arguments', 'array_destructuring', 'arrays', 'match', 'parameters']],
])
->setRiskyAllowed(true)
->setFinder($finder)

View File

@@ -22,7 +22,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
file \
gettext \
git \
&& rm -rf /var/lib/apt/lists/*
&& rm -rf /var/lib/apt/lists/*
RUN set -eux; \
install-php-extensions \
@@ -32,7 +32,7 @@ RUN set -eux; \
opcache \
zip \
gd \
excimer-1.2.3 \
excimer \
;
# https://getcomposer.org/doc/03-cli.md#composer-allow-superuser
@@ -60,6 +60,14 @@ FROM frankenphp_base AS frankenphp_dev
ENV APP_ENV=dev XDEBUG_MODE=off
# hadolint ignore=DL3008
RUN apt-get update && apt-get install -y --no-install-recommends \
bash-completion \
&& rm -rf /var/lib/apt/lists/*
COPY --link frankenphp/console-complete.bash /usr/share/bash-completion/completions/console
COPY --link frankenphp/composer-complete.bash /usr/share/bash-completion/completions/composer
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
RUN set -eux; \
@@ -76,6 +84,8 @@ CMD [ "frankenphp", "run", "--config", "/etc/caddy/Caddyfile", "--watch" ]
# Prod FrankenPHP image
FROM frankenphp_base AS frankenphp_prod
RUN rm -rf /var/lib/apt/lists/*
ENV APP_ENV=prod
ENV FRANKENPHP_CONFIG="import worker.Caddyfile"

View File

@@ -1,8 +1,8 @@
up:
docker compose up -d
up *args:
docker compose up -d {{ args }}
down *args:
docker compose down {{ args }} --remove-orphans
docker compose down --remove-orphans {{ args }}
stop:
docker compose stop

5
compose.build.yaml Normal file
View File

@@ -0,0 +1,5 @@
services:
php:
build:
context: .
target: frankenphp_prod

View File

@@ -15,7 +15,6 @@ services:
# See https://xdebug.org/docs/all_settings#mode
XDEBUG_MODE: "${XDEBUG_MODE:-off}"
MAILER_DSN: "smtp://mailer:1025"
PHP_CS_FIXER_IGNORE_ENV: 1
extra_hosts:
# Ensure that host.docker.internal is correctly defined on Linux
- host.docker.internal:host-gateway

View File

@@ -1,9 +1,7 @@
# Production environment override
services:
php:
build:
context: .
target: frankenphp_prod
image: ghcr.io/marijndoeve/tijdvoordetest:${IMAGE_TAG}
environment:
APP_SECRET: ${APP_SECRET}
MERCURE_PUBLISHER_JWT_KEY: ${CADDY_MERCURE_JWT_SECRET}

View File

@@ -13,7 +13,7 @@
"doctrine/doctrine-bundle": "^2.16.2",
"doctrine/doctrine-migrations-bundle": "^3.4.2",
"doctrine/orm": "^3.5.2",
"easycorp/easyadmin-bundle": "^4.25.1",
"martin-georgiev/postgresql-for-doctrine": "^3.5",
"phpdocumentor/reflection-docblock": "^5.6.3",
"phpoffice/phpspreadsheet": "^5.1",
"phpstan/phpdoc-parser": "^2.3",
@@ -34,9 +34,11 @@
"symfony/security-bundle": "7.3.*",
"symfony/security-csrf": "7.3.*",
"symfony/serializer": "7.3.*",
"symfony/translation": "7.3.*",
"symfony/twig-bundle": "7.3.*",
"symfony/uid": "7.3.*",
"symfony/ux-turbo": "^2.30.0",
"symfony/validator": "7.3.*",
"symfony/yaml": "7.3.*",
"symfonycasts/sass-bundle": "^0.8.3",
"symfonycasts/verify-email-bundle": "^1.17.4",
@@ -49,11 +51,11 @@
"doctrine/doctrine-fixtures-bundle": "^4.1",
"friendsofphp/php-cs-fixer": "^3.88.2",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.1.29",
"phpstan/phpstan": "^2.1.30",
"phpstan/phpstan-doctrine": "^2.0.6",
"phpstan/phpstan-phpunit": "^2.0.7",
"phpstan/phpstan-symfony": "^2.0.8",
"phpunit/phpunit": "^12.3.15",
"phpunit/phpunit": "^12.4.0",
"rector/rector": "^2.1.7",
"roave/security-advisories": "dev-latest",
"symfony/browser-kit": "7.3.*",

392
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b0035e3726f72e1549e382ed9b170ae3",
"content-hash": "d9b29aa569964f735b9f65fe5e9a59f9",
"packages": [
{
"name": "composer/pcre",
@@ -1278,108 +1278,6 @@
},
"time": "2025-01-24T11:45:48+00:00"
},
{
"name": "easycorp/easyadmin-bundle",
"version": "v4.25.1",
"source": {
"type": "git",
"url": "https://github.com/EasyCorp/EasyAdminBundle.git",
"reference": "954e88c9cb004c3861b6ec6ae52241642995fb86"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/EasyCorp/EasyAdminBundle/zipball/954e88c9cb004c3861b6ec6ae52241642995fb86",
"reference": "954e88c9cb004c3861b6ec6ae52241642995fb86",
"shasum": ""
},
"require": {
"doctrine/doctrine-bundle": "^2.5",
"doctrine/orm": "^2.12|^3.0",
"ext-json": "*",
"php": ">=8.1",
"symfony/asset": "^5.4|^6.0|^7.0",
"symfony/cache": "^5.4|^6.0|^7.0",
"symfony/config": "^5.4|^6.0|^7.0",
"symfony/dependency-injection": "^5.4|^6.0|^7.0",
"symfony/deprecation-contracts": "^3.0",
"symfony/doctrine-bridge": "^5.4|^6.0|^7.0",
"symfony/event-dispatcher": "^5.4|^6.0|^7.0",
"symfony/filesystem": "^5.4|^6.0|^7.0",
"symfony/form": "^5.4|^6.0|^7.0",
"symfony/framework-bundle": "^5.4|^6.0|^7.0",
"symfony/http-foundation": "^5.4|^6.0|^7.0",
"symfony/http-kernel": "^5.4|^6.0|^7.0",
"symfony/intl": "^5.4|^6.0|^7.0",
"symfony/property-access": "^5.4|^6.0|^7.0",
"symfony/security-bundle": "^5.4|^6.0|^7.0",
"symfony/string": "^5.4|^6.0|^7.0",
"symfony/translation": "^5.4|^6.0|^7.0",
"symfony/twig-bridge": "^5.4.48|^6.4.16|^7.1.9",
"symfony/twig-bundle": "^5.4|^6.0|^7.0",
"symfony/uid": "^5.4|^6.0|^7.0",
"symfony/ux-twig-component": "^2.21",
"symfony/validator": "^5.4|^6.0|^7.0",
"twig/extra-bundle": "^3.17",
"twig/html-extra": "^3.17",
"twig/twig": "^3.20"
},
"require-dev": {
"doctrine/doctrine-fixtures-bundle": "^3.4|3.5.x-dev",
"phpstan/extension-installer": "^1.4",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpstan/phpstan-symfony": "^2.0",
"psr/log": "^1.0",
"symfony/browser-kit": "^5.4|^6.0|^7.0",
"symfony/css-selector": "^5.4|^6.0|^7.0",
"symfony/debug-bundle": "^5.4|^6.0|^7.0",
"symfony/dom-crawler": "^5.4|^6.0|^7.0",
"symfony/expression-language": "^5.4|^6.0|^7.0",
"symfony/phpunit-bridge": "^6.1|^7.0",
"symfony/process": "^5.4|^6.0|^7.0",
"symfony/web-link": "^5.4|^6.0|^7.0"
},
"type": "symfony-bundle",
"extra": {
"branch-alias": {
"dev-master": "4.0.x-dev"
}
},
"autoload": {
"psr-4": {
"EasyCorp\\Bundle\\EasyAdminBundle\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Project Contributors",
"homepage": "https://github.com/EasyCorp/EasyAdminBundle/graphs/contributors"
}
],
"description": "Admin generator for Symfony applications",
"homepage": "https://github.com/EasyCorp/EasyAdminBundle",
"keywords": [
"admin",
"backend",
"generator"
],
"support": {
"issues": "https://github.com/EasyCorp/EasyAdminBundle/issues",
"source": "https://github.com/EasyCorp/EasyAdminBundle/tree/v4.25.1"
},
"funding": [
{
"url": "https://github.com/javiereguiluz",
"type": "github"
}
],
"time": "2025-09-10T05:00:12+00:00"
},
{
"name": "egulias/email-validator",
"version": "4.0.4",
@@ -1808,6 +1706,90 @@
},
"time": "2022-12-02T22:17:43+00:00"
},
{
"name": "martin-georgiev/postgresql-for-doctrine",
"version": "v3.5.1",
"source": {
"type": "git",
"url": "https://github.com/martin-georgiev/postgresql-for-doctrine.git",
"reference": "5d1621e48edd7c7306cf2b9e73e374727867d6af"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/martin-georgiev/postgresql-for-doctrine/zipball/5d1621e48edd7c7306cf2b9e73e374727867d6af",
"reference": "5d1621e48edd7c7306cf2b9e73e374727867d6af",
"shasum": ""
},
"require": {
"doctrine/dbal": "~2.10||~3.0||~4.0",
"ext-ctype": "*",
"ext-json": "*",
"ext-mbstring": "*",
"php": "^8.1"
},
"require-dev": {
"deptrac/deptrac": "^4.0",
"doctrine/orm": "~2.14||~3.0",
"ekino/phpstan-banned-code": "^3.0",
"friendsofphp/php-cs-fixer": "^3.87.1",
"phpstan/phpstan": "^2.1.22",
"phpstan/phpstan-deprecation-rules": "^2.0.3",
"phpstan/phpstan-doctrine": "^2.0.4",
"phpstan/phpstan-phpunit": "^2.0.7",
"phpunit/phpunit": "^10.5.53",
"rector/rector": "^2.1.5",
"symfony/cache": "^6.4||^7.0"
},
"suggest": {
"doctrine/orm": "~2.14||~3.0",
"php": "^8.3"
},
"type": "library",
"autoload": {
"psr-4": {
"MartinGeorgiev\\": "src/MartinGeorgiev/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Martin Georgiev",
"email": "martin.georgiev@gmail.com",
"role": "author"
}
],
"description": "Adds PostgreSQL enhancements to Doctrine. Provides support for JSON, JSONB and some array data types. Provides functions, operators and common expressions used when working with JSON data, arrays and features related to text search.",
"keywords": [
"array data types",
"dbal",
"doctrine",
"json",
"jsonb",
"martin georgiev",
"postgres",
"postgresql",
"text search",
"tsvector"
],
"support": {
"issues": "https://github.com/martin-georgiev/postgresql-for-doctrine/issues",
"source": "https://github.com/martin-georgiev/postgresql-for-doctrine/tree/v3.5.1"
},
"funding": [
{
"url": "https://github.com/sponsors/martin-georgiev",
"type": "custom"
},
{
"url": "https://github.com/martin-georgiev",
"type": "github"
}
],
"time": "2025-09-12T10:54:26+00:00"
},
{
"name": "phpdocumentor/reflection-common",
"version": "2.2.0",
@@ -7701,93 +7683,6 @@
],
"time": "2025-08-27T15:25:48+00:00"
},
{
"name": "symfony/ux-twig-component",
"version": "v2.30.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/ux-twig-component.git",
"reference": "2f445efda4d4400d4d1911ddf9710c366f339614"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/ux-twig-component/zipball/2f445efda4d4400d4d1911ddf9710c366f339614",
"reference": "2f445efda4d4400d4d1911ddf9710c366f339614",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/dependency-injection": "^5.4|^6.0|^7.0|^8.0",
"symfony/deprecation-contracts": "^2.2|^3.0",
"symfony/event-dispatcher": "^5.4|^6.0|^7.0|^8.0",
"symfony/property-access": "^5.4|^6.0|^7.0|^8.0",
"twig/twig": "^3.10.3"
},
"conflict": {
"symfony/config": "<5.4.0"
},
"require-dev": {
"symfony/console": "^5.4|^6.0|^7.0|^8.0",
"symfony/css-selector": "^5.4|^6.0|^7.0|^8.0",
"symfony/dom-crawler": "^5.4|^6.0|^7.0|^8.0",
"symfony/framework-bundle": "^5.4|^6.0|^7.0|^8.0",
"symfony/phpunit-bridge": "^6.0|^7.0|^8.0",
"symfony/stimulus-bundle": "^2.9.1",
"symfony/twig-bundle": "^5.4|^6.0|^7.0|^8.0",
"symfony/webpack-encore-bundle": "^1.15|^2.3.0"
},
"type": "symfony-bundle",
"extra": {
"thanks": {
"url": "https://github.com/symfony/ux",
"name": "symfony/ux"
}
},
"autoload": {
"psr-4": {
"Symfony\\UX\\TwigComponent\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Twig components for Symfony",
"homepage": "https://symfony.com",
"keywords": [
"components",
"symfony-ux",
"twig"
],
"support": {
"source": "https://github.com/symfony/ux-twig-component/tree/v2.30.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2025-08-27T15:25:48+00:00"
},
{
"name": "symfony/validator",
"version": "v7.3.4",
@@ -8447,74 +8342,6 @@
],
"time": "2025-02-19T14:29:33+00:00"
},
{
"name": "twig/html-extra",
"version": "v3.21.0",
"source": {
"type": "git",
"url": "https://github.com/twigphp/html-extra.git",
"reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/html-extra/zipball/5442dd707601c83b8cd4233e37bb10ab8489a90f",
"reference": "5442dd707601c83b8cd4233e37bb10ab8489a90f",
"shasum": ""
},
"require": {
"php": ">=8.1.0",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/mime": "^5.4|^6.4|^7.0",
"twig/twig": "^3.13|^4.0"
},
"require-dev": {
"symfony/phpunit-bridge": "^6.4|^7.0"
},
"type": "library",
"autoload": {
"files": [
"Resources/functions.php"
],
"psr-4": {
"Twig\\Extra\\Html\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
}
],
"description": "A Twig extension for HTML",
"homepage": "https://twig.symfony.com",
"keywords": [
"html",
"twig"
],
"support": {
"source": "https://github.com/twigphp/html-extra/tree/v3.21.0"
},
"funding": [
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/twig/twig",
"type": "tidelift"
}
],
"time": "2025-02-19T14:29:33+00:00"
},
{
"name": "twig/intl-extra",
"version": "v3.21.0",
@@ -9582,16 +9409,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.29",
"source": {
"type": "git",
"url": "https://github.com/phpstan/phpstan-phar-composer-source.git",
"reference": "git"
},
"version": "2.1.30",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
"reference": "d618573eed4a1b6b75e37b2e0b65ac65c885d88e",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
"reference": "a4a7f159927983dd4f7c8020ed227d80b7f39d7d",
"shasum": ""
},
"require": {
@@ -9636,7 +9458,7 @@
"type": "github"
}
],
"time": "2025-09-25T06:58:18+00:00"
"time": "2025-10-02T16:07:52+00:00"
},
{
"name": "phpstan/phpstan-doctrine",
@@ -10170,16 +9992,16 @@
},
{
"name": "phpunit/phpunit",
"version": "12.3.15",
"version": "12.4.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "b035ee2cd8ecad4091885b61017ebb1d80eb0e57"
"reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b035ee2cd8ecad4091885b61017ebb1d80eb0e57",
"reference": "b035ee2cd8ecad4091885b61017ebb1d80eb0e57",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f62aab5794e36ccd26860db2d1bbf89ac19028d9",
"reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9",
"shasum": ""
},
"require": {
@@ -10215,7 +10037,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "12.3-dev"
"dev-main": "12.4-dev"
}
},
"autoload": {
@@ -10247,7 +10069,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.3.15"
"source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.0"
},
"funding": [
{
@@ -10271,7 +10093,7 @@
"type": "tidelift"
}
],
"time": "2025-09-28T12:10:54+00:00"
"time": "2025-10-03T04:28:03+00:00"
},
{
"name": "react/cache",
@@ -10865,12 +10687,12 @@
"source": {
"type": "git",
"url": "https://github.com/Roave/SecurityAdvisories.git",
"reference": "684ef27cdce62b6562f77a92dc76bdfb46542a2d"
"reference": "60ac6a710b7b0527786041ba96200bd49aa8de7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/684ef27cdce62b6562f77a92dc76bdfb46542a2d",
"reference": "684ef27cdce62b6562f77a92dc76bdfb46542a2d",
"url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/60ac6a710b7b0527786041ba96200bd49aa8de7e",
"reference": "60ac6a710b7b0527786041ba96200bd49aa8de7e",
"shasum": ""
},
"conflict": {
@@ -10912,10 +10734,10 @@
"athlon1600/php-proxy-app": "<=3",
"athlon1600/youtube-downloader": "<=4",
"austintoddj/canvas": "<=3.4.2",
"auth0/auth0-php": ">=8.0.0.0-beta1,<8.14",
"auth0/login": "<7.17",
"auth0/symfony": "<5.4",
"auth0/wordpress": "<5.3",
"auth0/auth0-php": ">=3.3,<=8.16",
"auth0/login": "<=7.18",
"auth0/symfony": "<=5.4.1",
"auth0/wordpress": "<=5.3",
"automad/automad": "<2.0.0.0-alpha5",
"automattic/jetpack": "<9.8",
"awesome-support/awesome-support": "<=6.0.7",
@@ -11032,7 +10854,7 @@
"doctrine/mongodb-odm": "<1.0.2",
"doctrine/mongodb-odm-bundle": "<3.0.1",
"doctrine/orm": ">=1,<1.2.4|>=2,<2.4.8|>=2.5,<2.5.1|>=2.8.3,<2.8.4",
"dolibarr/dolibarr": "<=21.0.2",
"dolibarr/dolibarr": "<21.0.3",
"dompdf/dompdf": "<2.0.4",
"doublethreedigital/guest-entries": "<3.1.2",
"drupal/admin_audit_trail": "<1.0.5",
@@ -11221,7 +11043,7 @@
"joomla/archive": "<1.1.12|>=2,<2.0.1",
"joomla/database": ">=1,<2.2|>=3,<3.4",
"joomla/filesystem": "<1.6.2|>=2,<2.0.1",
"joomla/filter": "<1.4.4|>=2,<2.0.1",
"joomla/filter": "<2.0.6|>=3,<3.0.5|==4",
"joomla/framework": "<1.5.7|>=2.5.4,<=3.8.12",
"joomla/input": ">=2,<2.0.2",
"joomla/joomla-cms": "<3.9.12|>=4,<4.4.13|>=5,<5.2.6",
@@ -11639,7 +11461,7 @@
"thelia/thelia": ">=2.1,<2.1.3",
"theonedemon/phpwhois": "<=4.2.5",
"thinkcmf/thinkcmf": "<6.0.8",
"thorsten/phpmyfaq": "<=4.0.1",
"thorsten/phpmyfaq": "<=4.0.1|>=4.0.7,<4.0.13",
"tikiwiki/tiki-manager": "<=17.1",
"timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1",
"tinymce/tinymce": "<7.2",
@@ -11831,7 +11653,7 @@
"type": "tidelift"
}
],
"time": "2025-09-24T21:05:21+00:00"
"time": "2025-10-03T15:05:30+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -5,7 +5,6 @@ declare(strict_types=1);
use Doctrine\Bundle\DoctrineBundle\DoctrineBundle;
use Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle;
use Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle;
use EasyCorp\Bundle\EasyAdminBundle\EasyAdminBundle;
use Sentry\SentryBundle\SentryBundle;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\MakerBundle\MakerBundle;
@@ -14,7 +13,6 @@ use Symfony\Bundle\TwigBundle\TwigBundle;
use Symfony\Bundle\WebProfilerBundle\WebProfilerBundle;
use Symfony\UX\StimulusBundle\StimulusBundle;
use Symfony\UX\Turbo\TurboBundle;
use Symfony\UX\TwigComponent\TwigComponentBundle;
use SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle;
use Symfonycasts\SassBundle\SymfonycastsSassBundle;
use Twig\Extra\TwigExtraBundle\TwigExtraBundle;
@@ -28,8 +26,6 @@ return [
SecurityBundle::class => ['all' => true],
WebProfilerBundle::class => ['dev' => true, 'test' => true],
TwigExtraBundle::class => ['all' => true],
TwigComponentBundle::class => ['all' => true],
EasyAdminBundle::class => ['all' => true],
DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
SymfonyCastsVerifyEmailBundle::class => ['all' => true],
SentryBundle::class => ['prod' => true],

View File

@@ -8,9 +8,22 @@ doctrine:
profiling_collect_backtrace: '%kernel.debug%'
use_savepoints: true
types:
# JSON types
jsonb: MartinGeorgiev\Doctrine\DBAL\Types\Jsonb
'jsonb[]': MartinGeorgiev\Doctrine\DBAL\Types\JsonbArray
tsrange: MartinGeorgiev\Doctrine\DBAL\Types\TsRange
mapping_types:
# JSON type mappings
jsonb: jsonb
'jsonb[]': 'jsonb[]'
_jsonb: 'jsonb[]'
tsrange: tsrange
orm:
auto_generate_proxy_classes: true
enable_lazy_ghost_objects: true
enable_native_lazy_objects: true
report_fields_where_declared: true
validate_xml_mapping: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware

View File

@@ -1,5 +0,0 @@
twig_component:
anonymous_template_directory: 'components/'
defaults:
# Namespace & directory for components
Tvdt\Twig\Components\: 'components/'

View File

@@ -1,4 +0,0 @@
framework:
uid:
default_uuid_version: 7
time_based_uuid_version: 7

View File

@@ -1,3 +0,0 @@
easyadmin:
resource: .
type: easyadmin.routes

View File

@@ -0,0 +1,84 @@
# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html
_sf_composer() {
# Use newline as only separator to allow space in completion values
local IFS=$'\n'
local sf_cmd="${COMP_WORDS[0]}"
# for an alias, get the real script behind it
sf_cmd_type=$(type -t $sf_cmd)
if [[ $sf_cmd_type == "alias" ]]; then
sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/")
elif [[ $sf_cmd_type == "file" ]]; then
sf_cmd=$(type -p $sf_cmd)
fi
if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then
return 1
fi
local cur prev words cword
_get_comp_words_by_ref -n := cur prev words cword
local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-S2.8.11")
for w in ${words[@]}; do
w=$(printf -- '%b' "$w")
# remove quotes from typed values
quote="${w:0:1}"
if [ "$quote" == \' ]; then
w="${w%\'}"
w="${w#\'}"
elif [ "$quote" == \" ]; then
w="${w%\"}"
w="${w#\"}"
fi
# empty values are ignored
if [ ! -z "$w" ]; then
completecmd+=("-i$w")
fi
done
local sfcomplete
if sfcomplete=$(${completecmd[@]} 2>&1); then
local quote suggestions
quote=${cur:0:1}
# Use single quotes by default if suggestions contains backslash (FQCN)
if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then
quote=\'
fi
if [ "$quote" == \' ]; then
# single quotes: no additional escaping (does not accept ' in values)
suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done)
elif [ "$quote" == \" ]; then
# double quotes: double escaping for \ $ ` "
suggestions=$(for s in $sfcomplete; do
s=${s//\\/\\\\}
s=${s//\$/\\\$}
s=${s//\`/\\\`}
s=${s//\"/\\\"}
printf $'%q%q%q\n' "$quote" "$s" "$quote";
done)
else
# no quotes: double escaping
suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done)
fi
COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur")))
__ltrim_colon_completions "$cur"
else
if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then
>&2 echo
>&2 echo $sfcomplete
fi
return 1
fi
}
complete -F _sf_composer composer

View File

@@ -0,0 +1,94 @@
# This file is part of the Symfony package.
#
# (c) Fabien Potencier <fabien@symfony.com>
#
# For the full copyright and license information, please view
# https://symfony.com/doc/current/contributing/code/license.html
_sf_console() {
# Use the default completion for shell redirect operators.
for w in '>' '>>' '&>' '<'; do
if [[ $w = "${COMP_WORDS[COMP_CWORD-1]}" ]]; then
compopt -o filenames
COMPREPLY=($(compgen -f -- "${COMP_WORDS[COMP_CWORD]}"))
return 0
fi
done
# Use newline as only separator to allow space in completion values
local IFS=$'\n'
local sf_cmd="${COMP_WORDS[0]}"
# for an alias, get the real script behind it
sf_cmd_type=$(type -t $sf_cmd)
if [[ $sf_cmd_type == "alias" ]]; then
sf_cmd=$(alias $sf_cmd | sed -E "s/alias $sf_cmd='(.*)'/\1/")
elif [[ $sf_cmd_type == "file" ]]; then
sf_cmd=$(type -p $sf_cmd)
fi
if [[ $sf_cmd_type != "function" && ! -x $sf_cmd ]]; then
return 1
fi
local cur prev words cword
_get_comp_words_by_ref -n := cur prev words cword
local completecmd=("$sf_cmd" "_complete" "--no-interaction" "-sbash" "-c$cword" "-a1")
for w in ${words[@]}; do
w="${w//\\\\/\\}"
# remove quotes from typed values
quote="${w:0:1}"
if [ "$quote" == \' ]; then
w="${w%\'}"
w="${w#\'}"
elif [ "$quote" == \" ]; then
w="${w%\"}"
w="${w#\"}"
fi
# empty values are ignored
if [ ! -z "$w" ]; then
completecmd+=("-i$w")
fi
done
local sfcomplete
if sfcomplete=$(${completecmd[@]} 2>&1); then
local quote suggestions
quote=${cur:0:1}
# Use single quotes by default if suggestions contains backslash (FQCN)
if [ "$quote" == '' ] && [[ "$sfcomplete" =~ \\ ]]; then
quote=\'
fi
if [ "$quote" == \' ]; then
# single quotes: no additional escaping (does not accept ' in values)
suggestions=$(for s in $sfcomplete; do printf $'%q%q%q\n' "$quote" "$s" "$quote"; done)
elif [ "$quote" == \" ]; then
# double quotes: double escaping for \ $ ` "
suggestions=$(for s in $sfcomplete; do
s=${s//\\/\\\\}
s=${s//\$/\\\$}
s=${s//\`/\\\`}
s=${s//\"/\\\"}
printf $'%q%q%q\n' "$quote" "$s" "$quote";
done)
else
# no quotes: double escaping
suggestions=$(for s in $sfcomplete; do printf $'%q\n' $(printf '%q' "$s"); done)
fi
COMPREPLY=($(IFS=$'\n' compgen -W "$suggestions" -- $(printf -- "%q" "$cur")))
__ltrim_colon_completions "$cur"
else
if [[ "$sfcomplete" != *"Command \"_complete\" is not defined."* ]]; then
>&2 echo
>&2 echo $sfcomplete
fi
return 1
fi
}
complete -F _sf_console console

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241229195702 extends AbstractMigration
{
public function getDescription(): string
@@ -19,7 +16,6 @@ final class Version20241229195702 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE answer (id UUID NOT NULL, question_id UUID NOT NULL, text VARCHAR(255) NOT NULL, is_right_answer BOOLEAN NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_DADD4A251E27F6BF ON answer (question_id)');
$this->addSql('COMMENT ON COLUMN answer.id IS \'(DC2Type:uuid)\'');
@@ -67,7 +63,6 @@ final class Version20241229195702 extends AbstractMigration
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE answer DROP CONSTRAINT FK_DADD4A251E27F6BF');
$this->addSql('ALTER TABLE answer_candidate DROP CONSTRAINT FK_F54D5192AA334807');

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241229201314 extends AbstractMigration
{
public function getDescription(): string
@@ -19,7 +16,6 @@ final class Version20241229201314 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE season_user (season_id UUID NOT NULL, user_id UUID NOT NULL, PRIMARY KEY(season_id, user_id))');
$this->addSql('CREATE INDEX IDX_BDA4AD74EC001D1 ON season_user (season_id)');
$this->addSql('CREATE INDEX IDX_BDA4AD7A76ED395 ON season_user (user_id)');
@@ -31,7 +27,6 @@ final class Version20241229201314 extends AbstractMigration
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE season_user DROP CONSTRAINT FK_BDA4AD74EC001D1');
$this->addSql('ALTER TABLE season_user DROP CONSTRAINT FK_BDA4AD7A76ED395');

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241229202103 extends AbstractMigration
{
public function getDescription(): string
@@ -19,7 +16,6 @@ final class Version20241229202103 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE correction (id UUID NOT NULL, candidate_id UUID NOT NULL, quiz_id UUID NOT NULL, amount DOUBLE PRECISION NOT NULL, PRIMARY KEY(id))');
$this->addSql('CREATE INDEX IDX_A29DA1B891BD8781 ON correction (candidate_id)');
$this->addSql('CREATE INDEX IDX_A29DA1B8853CD175 ON correction (quiz_id)');
@@ -32,7 +28,6 @@ final class Version20241229202103 extends AbstractMigration
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE correction DROP CONSTRAINT FK_A29DA1B891BD8781');
$this->addSql('ALTER TABLE correction DROP CONSTRAINT FK_A29DA1B8853CD175');

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241229202155 extends AbstractMigration
{
public function getDescription(): string
@@ -19,13 +16,11 @@ final class Version20241229202155 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE UNIQUE INDEX UNIQ_A29DA1B891BD8781853CD175 ON correction (candidate_id, quiz_id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('DROP INDEX UNIQ_A29DA1B891BD8781853CD175');
}

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20241229204335 extends AbstractMigration
{
public function getDescription(): string
@@ -19,14 +16,12 @@ final class Version20241229204335 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE given_answer ALTER answer_id DROP NOT NULL');
$this->addSql('ALTER TABLE given_answer ALTER created DROP NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE given_answer ALTER answer_id SET NOT NULL');
$this->addSql('ALTER TABLE given_answer ALTER created SET NOT NULL');

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250303221227 extends AbstractMigration
{
public function getDescription(): string
@@ -19,7 +16,6 @@ final class Version20250303221227 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE given_answer ALTER created SET NOT NULL');
$this->addSql('ALTER TABLE season ADD active_quiz_id UUID DEFAULT NULL');
$this->addSql('COMMENT ON COLUMN season.active_quiz_id IS \'(DC2Type:uuid)\'');
@@ -29,7 +25,6 @@ final class Version20250303221227 extends AbstractMigration
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE season DROP CONSTRAINT FK_F0E45BA96706D6B');
$this->addSql('DROP INDEX IDX_F0E45BA96706D6B');

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250311213417 extends AbstractMigration
{
public function getDescription(): string
@@ -19,7 +16,6 @@ final class Version20250311213417 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE answer ALTER id TYPE UUID');
$this->addSql('ALTER TABLE answer ALTER question_id TYPE UUID');
$this->addSql('COMMENT ON COLUMN answer.id IS \'\'');
@@ -69,7 +65,6 @@ final class Version20250311213417 extends AbstractMigration
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE candidate ALTER id TYPE UUID');
$this->addSql('ALTER TABLE candidate ALTER season_id TYPE UUID');
$this->addSql('COMMENT ON COLUMN candidate.id IS \'(DC2Type:uuid)\'');

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250402185128 extends AbstractMigration
{
public function getDescription(): string
@@ -19,13 +16,11 @@ final class Version20250402185128 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE elimination (id UUID NOT NULL, data JSON NOT NULL, PRIMARY KEY(id))');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE elimination');
}
}

View File

@@ -18,13 +18,13 @@ final class Version20250420111904 extends AbstractMigration
{
$this->addSql(<<<'SQL'
ALTER TABLE "user" ADD is_verified BOOLEAN NOT NULL
SQL);
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE "user" DROP is_verified
SQL);
SQL);
}
}

View File

@@ -18,13 +18,13 @@ final class Version20250420125040 extends AbstractMigration
{
$this->addSql(<<<'SQL'
ALTER TABLE season DROP preregister_candidates
SQL);
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE season ADD preregister_candidates BOOLEAN NOT NULL DEFAULT true
SQL);
SQL);
}
}

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250427174822 extends AbstractMigration
{
public function getDescription(): string
@@ -19,23 +16,21 @@ final class Version20250427174822 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE answer ADD ordering SMALLINT DEFAULT 0 NOT NULL
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE question ADD ordering SMALLINT DEFAULT 0 NOT NULL
SQL);
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE answer DROP ordering
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE question DROP ordering
SQL);
SQL);
}
}

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250504101440 extends AbstractMigration
{
public function getDescription(): string
@@ -19,23 +16,21 @@ final class Version20250504101440 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_C8B28E445E237E064EC001D1 ON candidate (name, season_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_A412FA925E237E064EC001D1 ON quiz (name, season_id)
SQL);
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_A412FA925E237E064EC001D1
SQL);
SQL);
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_C8B28E445E237E064EC001D1
SQL);
SQL);
}
}

View File

@@ -18,37 +18,37 @@ final class Version20250521192752 extends AbstractMigration
{
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD quiz_id UUID NOT NULL
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT FK_5947284F853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_5947284F853CD175 ON elimination (quiz_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts SET DEFAULT 1
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts SET NOT NULL
SQL);
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts DROP DEFAULT
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz ALTER dropouts DROP NOT NULL
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT FK_5947284F853CD175
SQL);
SQL);
$this->addSql(<<<'SQL'
DROP INDEX IDX_5947284F853CD175
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP quiz_id
SQL);
SQL);
}
}

View File

@@ -18,7 +18,7 @@ final class Version20250521194035 extends AbstractMigration
{
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD created TIMESTAMP(0) WITHOUT TIME ZONE
SQL);
SQL);
$this->addSql(<<<'SQL'
UPDATE elimination SET created = NOW()
SQL
@@ -33,6 +33,6 @@ final class Version20250521194035 extends AbstractMigration
{
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP created
SQL);
SQL);
}
}

View File

@@ -18,73 +18,73 @@ final class Version20250606192337 extends AbstractMigration
{
$this->addSql(<<<'SQL'
CREATE TABLE quiz_candidate (id UUID NOT NULL, corrections DOUBLE PRECISION NOT NULL, created TIMESTAMP(0) WITH TIME ZONE NOT NULL, quiz_id UUID NOT NULL, candidate_id UUID NOT NULL, PRIMARY KEY(id))
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_CED2FFA2853CD175 ON quiz_candidate (quiz_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX IDX_CED2FFA291BD8781 ON quiz_candidate (candidate_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_CED2FFA291BD8781853CD175 ON quiz_candidate (candidate_id, quiz_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate ADD CONSTRAINT FK_CED2FFA2853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate ADD CONSTRAINT FK_CED2FFA291BD8781 FOREIGN KEY (candidate_id) REFERENCES candidate (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction DROP CONSTRAINT fk_a29da1b891bd8781
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction DROP CONSTRAINT fk_a29da1b8853cd175
SQL);
SQL);
$this->addSql(<<<'SQL'
DROP TABLE correction
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ALTER created TYPE TIMESTAMP(0) WITH TIME ZONE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER created TYPE TIMESTAMP(0) WITH TIME ZONE
SQL);
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
CREATE TABLE correction (id UUID NOT NULL, candidate_id UUID NOT NULL, quiz_id UUID NOT NULL, amount DOUBLE PRECISION NOT NULL, PRIMARY KEY(id))
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX uniq_a29da1b891bd8781853cd175 ON correction (candidate_id, quiz_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX idx_a29da1b8853cd175 ON correction (quiz_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE INDEX idx_a29da1b891bd8781 ON correction (candidate_id)
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction ADD CONSTRAINT fk_a29da1b891bd8781 FOREIGN KEY (candidate_id) REFERENCES candidate (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE correction ADD CONSTRAINT fk_a29da1b8853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate DROP CONSTRAINT FK_CED2FFA2853CD175
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE quiz_candidate DROP CONSTRAINT FK_CED2FFA291BD8781
SQL);
SQL);
$this->addSql(<<<'SQL'
DROP TABLE quiz_candidate
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER created TYPE TIMESTAMP(0) WITHOUT TIME ZONE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ALTER created TYPE TIMESTAMP(0) WITHOUT TIME ZONE
SQL);
SQL);
}
}

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250606195952 extends AbstractMigration
{
public function getDescription(): string
@@ -21,22 +18,22 @@ final class Version20250606195952 extends AbstractMigration
{
$this->addSql(<<<'SQL'
delete from given_answer where answer_id is null
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id TYPE UUID
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id SET NOT NULL
SQL);
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id TYPE UUID
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ALTER answer_id DROP NOT NULL
SQL);
SQL);
}
}

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250607154730 extends AbstractMigration
{
public function getDescription(): string
@@ -19,23 +16,21 @@ final class Version20250607154730 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT FK_F0E45BA96706D6B
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT FK_F0E45BA96706D6B FOREIGN KEY (active_quiz_id) REFERENCES quiz (id) ON DELETE SET NULL NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT fk_f0e45ba96706d6b
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT fk_f0e45ba96706d6b FOREIGN KEY (active_quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
}
}

View File

@@ -7,9 +7,6 @@ namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20250607184525 extends AbstractMigration
{
public function getDescription(): string
@@ -19,35 +16,33 @@ final class Version20250607184525 extends AbstractMigration
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT FK_5947284F853CD175
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT FK_5947284F853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer DROP CONSTRAINT FK_9AC61A30853CD175
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ADD CONSTRAINT FK_9AC61A30853CD175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql(<<<'SQL'
ALTER TABLE given_answer DROP CONSTRAINT fk_9ac61a30853cd175
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE given_answer ADD CONSTRAINT fk_9ac61a30853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination DROP CONSTRAINT fk_5947284f853cd175
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE elimination ADD CONSTRAINT fk_5947284f853cd175 FOREIGN KEY (quiz_id) REFERENCES quiz (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
}
}

View File

@@ -18,31 +18,31 @@ final class Version20250610210417 extends AbstractMigration
{
$this->addSql(<<<'SQL'
CREATE TABLE season_settings (id UUID NOT NULL, show_numbers BOOLEAN DEFAULT false NOT NULL, confirm_answers BOOLEAN DEFAULT false NOT NULL, PRIMARY KEY(id))
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD settings_id UUID DEFAULT NULL
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season ADD CONSTRAINT FK_F0E45BA959949888 FOREIGN KEY (settings_id) REFERENCES season_settings (id) NOT DEFERRABLE INITIALLY IMMEDIATE
SQL);
SQL);
$this->addSql(<<<'SQL'
CREATE UNIQUE INDEX UNIQ_F0E45BA959949888 ON season (settings_id)
SQL);
SQL);
}
public function down(Schema $schema): void
{
$this->addSql(<<<'SQL'
DROP TABLE season_settings
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season DROP CONSTRAINT FK_F0E45BA959949888
SQL);
SQL);
$this->addSql(<<<'SQL'
DROP INDEX UNIQ_F0E45BA959949888
SQL);
SQL);
$this->addSql(<<<'SQL'
ALTER TABLE season DROP settings_id
SQL);
SQL);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20251007194241 extends AbstractMigration
{
public function getDescription(): string
{
return 'Change elimination data type to jsonb';
}
public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE elimination ALTER data TYPE JSONB');
}
public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE elimination ALTER data TYPE JSON');
}
}

View File

@@ -8,3 +8,7 @@ parameters:
- src/
- tests/
treatPhpDocTypesAsCertain: false
symfony:
containerXmlPath: var/cache/dev/Tvdt_KernelDevDebugContainer.xml
doctrine:
objectManagerLoader: tests/object-manager.php

View File

@@ -3,6 +3,8 @@
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Symfony\Bridge\Symfony\Routing\SymfonyRoutesProvider;
use Rector\Symfony\Contract\Bridge\Symfony\Routing\SymfonyRoutesProviderInterface;
return RectorConfig::configure()
->withPaths([
@@ -11,7 +13,9 @@ return RectorConfig::configure()
__DIR__.'/src',
__DIR__.'/tests',
])
->withSymfonyContainerXml('var/cache/dev/App_KernelDevDebugContainer.xml')
->withSymfonyContainerXml(__DIR__.'/var/cache/dev/Tvdt_KernelDevDebugContainer.xml')
->withSymfonyContainerPhp(__DIR__.'/tests/symfony-container.php')
->registerService(SymfonyRoutesProvider::class, SymfonyRoutesProviderInterface::class)
->withParallel()
->withPhpSets()
->withPreparedSets(

View File

@@ -26,12 +26,12 @@ readonly class AddSettingsCommand
$io = new SymfonyStyle($input, $output);
foreach ($this->seasonRepository->findAll() as $season) {
if (null !== $season->getSettings()) {
if (null !== $season->settings) {
continue;
}
$io->text('Adding settings to season : '.$season->getSeasonCode());
$season->setSettings(new SeasonSettings());
$io->text('Adding settings to season : '.$season->seasonCode);
$season->settings = new SeasonSettings();
}
$this->entityManager->flush();

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\Answer;
/** @extends AbstractCrudController<Answer> */
class AnswerCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Answer::class;
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\Candidate;
/** @extends AbstractCrudController<Candidate> */
class CandidateCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Candidate::class;
}
}

View File

@@ -1,67 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Attribute\AdminDashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Component\HttpFoundation\Response;
use Tvdt\Entity\Answer;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\GivenAnswer;
use Tvdt\Entity\Question;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\QuizCandidate;
use Tvdt\Entity\Season;
use Tvdt\Entity\User;
#[AdminDashboard(routePath: '/admin', routeName: 'admin')]
class DashboardController extends AbstractDashboardController
{
#[\Override]
public function index(): Response
{
// Option 1. You can make your dashboard redirect to some common page of your backend
//
$adminUrlGenerator = $this->container->get(AdminUrlGenerator::class);
return $this->redirect($adminUrlGenerator->setController(SeasonCrudController::class)->generateUrl());
// Option 2. You can make your dashboard redirect to different pages depending on the user
//
// if ('jane' === $this->getUser()->getUsername()) {
// return $this->redirect('...');
// }
// Option 3. You can render some custom template to display a proper dashboard with widgets, etc.
// (tip: it's easier if your template extends from @EasyAdmin/page/content.html.twig)
//
// return $this->render('some/path/my-dashboard.html.twig');
}
#[\Override]
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('TijdVoorDeTest');
}
#[\Override]
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::linkToCrud('Season', 'fas fa-list', Season::class);
yield MenuItem::linkToCrud('Quiz', 'fas fa-list', Quiz::class);
yield MenuItem::linkToCrud('Question', 'fas fa-list', Question::class);
yield MenuItem::linkToCrud('Candidate', 'fas fa-list', Candidate::class);
yield MenuItem::linkToCrud('Correction', 'fas fa-list', QuizCandidate::class);
yield MenuItem::linkToCrud('User', 'fas fa-list', User::class);
yield MenuItem::linkToCrud('Given Answer', 'fas fa-list', GivenAnswer::class);
yield MenuItem::linkToCrud('Answer', 'fas fa-list', Answer::class);
yield MenuItem::linkToLogout('Logout', 'fas fa-sign-out');
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\GivenAnswer;
/** @extends AbstractCrudController<GivenAnswer> */
class GivenAnswerCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return GivenAnswer::class;
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\Question;
/** @extends AbstractCrudController<Question> */
class QuestionCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Question::class;
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\QuizCandidate;
/** @extends AbstractCrudController<QuizCandidate> */
class QuizCorrectionCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return QuizCandidate::class;
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\Quiz;
/** @extends AbstractCrudController<Quiz> */
class QuizCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Quiz::class;
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\Season;
/** @extends AbstractCrudController<Season> */
class SeasonCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Season::class;
}
}

View File

@@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use Tvdt\Entity\User;
/** @extends AbstractCrudController<User> */
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
}

View File

@@ -61,7 +61,7 @@ final class BackofficeController extends AbstractController
$em->persist($season);
$em->flush();
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->seasonCode]);
}
return $this->render('backoffice/season_add.html.twig', ['form' => $form]);

View File

@@ -26,7 +26,7 @@ final class PrepareEliminationController extends AbstractController
{
$elimination = $eliminationFactory->createEliminationFromQuiz($quiz);
return $this->redirectToRoute('tvdt_prepare_elimination_view', ['elimination' => $elimination->getId()]);
return $this->redirectToRoute('tvdt_prepare_elimination_view', ['elimination' => $elimination->id]);
}
#[Route(
@@ -41,7 +41,7 @@ final class PrepareEliminationController extends AbstractController
$em->flush();
if ($request->request->getBoolean('start')) {
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->getId()]);
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->id]);
}
$this->addFlash('success', 'Elimination updated');

View File

@@ -33,12 +33,12 @@ class QuizController extends AbstractController
private readonly TranslatorInterface $translator,
) {}
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route(
'/backoffice/season/{seasonCode:season}/quiz/{quiz}',
name: 'tvdt_backoffice_quiz',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function index(Season $season, Quiz $quiz): Response
{
return $this->render('backoffice/quiz.html.twig', [
@@ -48,30 +48,30 @@ class QuizController extends AbstractController
]);
}
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route(
'/backoffice/season/{seasonCode:season}/quiz/{quiz}/enable',
name: 'tvdt_backoffice_enable',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX, 'quiz' => Requirement::UUID.'|null'],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function enableQuiz(Season $season, ?Quiz $quiz, EntityManagerInterface $em): RedirectResponse
{
$season->setActiveQuiz($quiz);
$season->activeQuiz = $quiz;
$em->flush();
if ($quiz instanceof Quiz) {
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $season->getSeasonCode(), 'quiz' => $quiz->getId()]);
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $season->seasonCode, 'quiz' => $quiz->id]);
}
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->seasonCode]);
}
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
#[Route(
'/backoffice/quiz/{quiz}/clear',
name: 'tvdt_backoffice_quiz_clear',
requirements: ['quiz' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
public function clearQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{
try {
@@ -81,30 +81,30 @@ class QuizController extends AbstractController
$this->addFlash('error', $this->translator->trans('Error clearing quiz'));
}
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->getSeason()->getSeasonCode(), 'quiz' => $quiz->getId()]);
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->season->seasonCode, 'quiz' => $quiz->id]);
}
#[IsGranted(SeasonVoter::DELETE, subject: 'quiz')]
#[Route(
'/backoffice/quiz/{quiz}/delete',
name: 'tvdt_backoffice_quiz_delete',
requirements: ['quiz' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::DELETE, subject: 'quiz')]
public function deleteQuiz(Quiz $quiz, QuizRepository $quizRepository): RedirectResponse
{
$quizRepository->deleteQuiz($quiz);
$this->addFlash('success', $this->translator->trans('Quiz deleted'));
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $quiz->getSeason()->getSeasonCode()]);
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $quiz->season->seasonCode]);
}
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
#[Route(
'/backoffice/quiz/{quiz}/candidate/{candidate}/modify_correction',
name: 'tvdt_backoffice_modify_correction',
requirements: ['quiz' => Requirement::UUID, 'candidate' => Requirement::UUID],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'quiz')]
public function modifyCorrection(Quiz $quiz, Candidate $candidate, QuizCandidateRepository $quizCandidateRepository, Request $request): RedirectResponse
{
if (!$request->isMethod('POST')) {
@@ -115,6 +115,6 @@ class QuizController extends AbstractController
$quizCandidateRepository->setCorrectionsForCandidate($quiz, $candidate, $corrections);
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->getSeason()->getSeasonCode(), 'quiz' => $quiz->getId()]);
return $this->redirectToRoute('tvdt_backoffice_quiz', ['seasonCode' => $quiz->season->seasonCode, 'quiz' => $quiz->id]);
}
}

View File

@@ -32,15 +32,15 @@ class SeasonController extends AbstractController
private readonly EntityManagerInterface $em,
) {}
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route(
'/backoffice/season/{seasonCode:season}',
name: 'tvdt_backoffice_season',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function index(Season $season, Request $request): Response
{
$form = $this->createForm(SettingsForm::class, $season->getSettings());
$form = $this->createForm(SettingsForm::class, $season->settings);
$form->handleRequest($request);
@@ -54,13 +54,13 @@ class SeasonController extends AbstractController
]);
}
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route(
'/backoffice/season/{seasonCode:season}/add-candidate',
name: 'tvdt_backoffice_add_candidates',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
priority: 10,
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function addCandidates(Season $season, Request $request): Response
{
$form = $this->createForm(AddCandidatesFormType::class);
@@ -74,19 +74,19 @@ class SeasonController extends AbstractController
$this->em->flush();
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->seasonCode]);
}
return $this->render('backoffice/season_add_candidates.html.twig', ['form' => $form]);
}
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
#[Route(
'/backoffice/season/{seasonCode:season}/add-quiz',
name: 'tvdt_backoffice_quiz_add',
requirements: ['seasonCode' => self::SEASON_CODE_REGEX],
priority: 10,
)]
#[IsGranted(SeasonVoter::EDIT, subject: 'season')]
public function addQuiz(Request $request, Season $season, QuizSpreadsheetService $quizSpreadsheet): Response
{
$quiz = new Quiz();
@@ -100,13 +100,13 @@ class SeasonController extends AbstractController
$quizSpreadsheet->xlsxToQuiz($quiz, $sheet);
$quiz->setSeason($season);
$quiz->season = $season;
$this->em->persist($quiz);
$this->em->flush();
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz Added!'));
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('tvdt_backoffice_season', ['seasonCode' => $season->seasonCode]);
}
return $this->render('/backoffice/quiz_add.html.twig', ['form' => $form, 'season' => $season]);

View File

@@ -28,8 +28,8 @@ final class EliminationController extends AbstractController
{
public function __construct(private readonly TranslatorInterface $translator) {}
#[Route('/elimination/{elimination}', name: 'tvdt_elimination', requirements: ['elimination' => Requirement::UUID])]
#[IsGranted(SeasonVoter::ELIMINATION, 'elimination')]
#[Route('/elimination/{elimination}', name: 'tvdt_elimination', requirements: ['elimination' => Requirement::UUID])]
public function index(#[MapEntity] Elimination $elimination, Request $request): Response
{
$form = $this->createForm(EliminationEnterNameType::class);
@@ -39,7 +39,7 @@ final class EliminationController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$name = $form->get('name')->getData();
return $this->redirectToRoute('tvdt_elimination_candidate', ['elimination' => $elimination->getId(), 'candidateHash' => Base64::base64UrlEncode($name)]);
return $this->redirectToRoute('tvdt_elimination_candidate', ['elimination' => $elimination->id, 'candidateHash' => Base64::base64UrlEncode($name)]);
}
return $this->render('quiz/elimination/index.html.twig', [
@@ -48,25 +48,25 @@ final class EliminationController extends AbstractController
]);
}
#[Route('/elimination/{elimination}/{candidateHash}', name: 'tvdt_elimination_candidate', requirements: ['elimination' => Requirement::UUID, 'candidateHash' => self::CANDIDATE_HASH_REGEX])]
#[IsGranted(SeasonVoter::ELIMINATION, 'elimination')]
#[Route('/elimination/{elimination}/{candidateHash}', name: 'tvdt_elimination_candidate', requirements: ['elimination' => Requirement::UUID, 'candidateHash' => self::CANDIDATE_HASH_REGEX])]
public function candidateScreen(Elimination $elimination, string $candidateHash, CandidateRepository $candidateRepository): Response
{
$candidate = $candidateRepository->getCandidateByHash($elimination->getQuiz()->getSeason(), $candidateHash);
$candidate = $candidateRepository->getCandidateByHash($elimination->quiz->season, $candidateHash);
if (!$candidate instanceof Candidate) {
$this->addFlash(FlashType::Warning,
t('Cound not find candidate with name %name%', ['%name%' => Base64::base64UrlDecode($candidateHash)])->trans($this->translator),
);
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->getId()]);
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->id]);
}
$screenColour = $elimination->getScreenColour($candidate->getName());
$screenColour = $elimination->getScreenColour($candidate->name);
if (null === $screenColour) {
$this->addFlash(FlashType::Warning, $this->translator->trans('Cound not find candidate with name %name% in elimination.', ['%name%' => $candidate->getName()]));
$this->addFlash(FlashType::Warning, $this->translator->trans('Cound not find candidate with name %name% in elimination.', ['%name%' => $candidate->name]));
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->getId()]);
return $this->redirectToRoute('tvdt_elimination', ['elimination' => $elimination->id]);
}
return $this->render('quiz/elimination/candidate.html.twig', [

View File

@@ -65,7 +65,7 @@ final class QuizController extends AbstractController
if ($form->isSubmitted() && $form->isValid()) {
$name = $form->get('name')->getData();
return $this->redirectToRoute('tvdt_quiz_quiz_page', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => Base64::base64UrlEncode($name)]);
return $this->redirectToRoute('tvdt_quiz_quiz_page', ['seasonCode' => $season->seasonCode, 'nameHash' => Base64::base64UrlEncode($name)]);
}
return $this->render('quiz/enter_name.twig', ['season' => $season, 'form' => $form]);
@@ -91,15 +91,15 @@ final class QuizController extends AbstractController
if (!$candidate instanceof Candidate) {
$this->addFlash(FlashType::Danger, $this->translator->trans('Candidate not found'));
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->seasonCode]);
}
$quiz = $season->getActiveQuiz();
$quiz = $season->activeQuiz;
if (!$quiz instanceof Quiz) {
$this->addFlash(FlashType::Warning, $this->translator->trans('There is no active quiz'));
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->seasonCode]);
}
if ('POST' === $request->getMethod()) {
@@ -109,10 +109,10 @@ final class QuizController extends AbstractController
throw new BadRequestHttpException('Invalid Answer ID');
}
$givenAnswer = new GivenAnswer($candidate, $answer->getQuestion()->getQuiz(), $answer);
$givenAnswer = new GivenAnswer($candidate, $answer->question->quiz, $answer);
$givenAnswerRepository->save($givenAnswer);
return $this->redirectToRoute('tvdt_quiz_quiz_page', ['seasonCode' => $season->getSeasonCode(), 'nameHash' => $nameHash]);
return $this->redirectToRoute('tvdt_quiz_quiz_page', ['seasonCode' => $season->seasonCode, 'nameHash' => $nameHash]);
}
$question = $questionRepository->findNextQuestionForCandidate($candidate);
@@ -120,7 +120,7 @@ final class QuizController extends AbstractController
if (!$question instanceof Question) {
$this->addFlash(FlashType::Success, $this->translator->trans('Quiz completed'));
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->getSeasonCode()]);
return $this->redirectToRoute('tvdt_quiz_enter_name', ['seasonCode' => $season->seasonCode]);
}
$quizCandidateRepository->createIfNotExist($quiz, $candidate);

View File

@@ -41,7 +41,7 @@ final class RegistrationController extends AbstractController
/** @var string $plainPassword */
$plainPassword = $form->get('plainPassword')->getData();
$user->setPassword($userPasswordHasher->hashPassword($user, $plainPassword));
$user->password = $userPasswordHasher->hashPassword($user, $plainPassword);
$entityManager->persist($user);
$entityManager->flush();
@@ -50,7 +50,7 @@ final class RegistrationController extends AbstractController
// generate a signed url and email it to the user
$this->emailVerifier->sendEmailConfirmation('tvdt_verify_email', $user,
new TemplatedEmail()
->to((string) $user->getEmail())
->to($user->email)
->subject($this->translator->trans('Please Confirm your Email'))
->htmlTemplate('backoffice/registration/confirmation_email.html.twig'),
);

View File

@@ -19,8 +19,9 @@ class KrtekFixtures extends Fixture
$season = new Season();
$manager->persist($season);
$season->setName('Krtek Weekend')
->setSeasonCode('krtek')
$season->name = 'Krtek Weekend';
$season->seasonCode = 'krtek';
$season
->addCandidate(new Candidate('Claudia'))
->addCandidate(new Candidate('Eelco'))
->addCandidate(new Candidate('Elise'))
@@ -35,338 +36,341 @@ class KrtekFixtures extends Fixture
->addCandidate(new Candidate('Robbert'))
->addCandidate(new Candidate('Tom'));
$quiz1 = $this->createQuiz1($season);
$season->addQuiz($quiz1)
->setActiveQuiz($quiz1)
->addQuiz($this->createQuiz2($season));
$season->addQuiz($quiz1);
$season->activeQuiz = $quiz1;
$season->addQuiz($this->createQuiz2($season));
$manager->flush();
}
private function createQuiz1(Season $season): Quiz
{
return new Quiz()
->setName('Quiz 1')
->setSeason($season)
$quiz = new Quiz();
$quiz->name = 'Quiz 1';
$quiz->season = $season;
->addQuestion(new Question()
->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Vrouw', true))
->addAnswer(new Answer('Man'))
->setOrdering(1),
)
$q = new Question();
$q->question = 'Is de Krtek een man of een vrouw?';
$q->addAnswer(new Answer('Vrouw', true))
->addAnswer(new Answer('Man'));
$q->ordering = 1;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Hoeveel broers heeft de Krtek?')
->addAnswer(new Answer('Geen', true))
->addAnswer(new Answer('1'))
->addAnswer(new Answer('2'))
->setOrdering(2),
)
$q = new Question();
$q->question = 'Hoeveel broers heeft de Krtek?';
$q->addAnswer(new Answer('Geen', true))
->addAnswer(new Answer('1'))
->addAnswer(new Answer('2'));
$q->ordering = 2;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Wat is de lievelingsfeestdag van de Krtek?')
->addAnswer(new Answer('Geen'))
->addAnswer(new Answer('Diens eigen verjaardag'))
->addAnswer(new Answer('Koningsdag'))
->addAnswer(new Answer('Kerst', true))
->addAnswer(new Answer('Oud en Nieuw'))
->setOrdering(3),
)
$q = new Question();
$q->question = 'Wat is de lievelingsfeestdag van de Krtek?';
$q->addAnswer(new Answer('Geen'))
->addAnswer(new Answer('Diens eigen verjaardag'))
->addAnswer(new Answer('Koningsdag'))
->addAnswer(new Answer('Kerst', true))
->addAnswer(new Answer('Oud en Nieuw'));
$q->ordering = 3;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Hoe kwam de Krtek naar Kersteren vandaag?')
->addAnswer(new Answer('Met het OV', true))
->addAnswer(new Answer('Met de auto'))
->setOrdering(4),
)
->addQuestion(new Question()
->setQuestion('Met wie keek de Krtek video bij binnenkomst?')
->addAnswer(new Answer('Claudia'))
->addAnswer(new Answer('Eelco'))
->addAnswer(new Answer('Elise'))
->addAnswer(new Answer('Gert-Jan'))
->addAnswer(new Answer('Iris'))
->addAnswer(new Answer('Jari'))
->addAnswer(new Answer('Lara'))
->addAnswer(new Answer('Lotte'))
->addAnswer(new Answer('Myrthe'))
->addAnswer(new Answer('Philine'))
->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom', true))
->setOrdering(5),
)
$q = new Question();
$q->question = 'Hoe kwam de Krtek naar Kersteren vandaag?';
$q->addAnswer(new Answer('Met het OV', true))
->addAnswer(new Answer('Met de auto'));
$q->ordering = 4;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Welk advies zou de Krtek zichzelf als kind geven?')
->addAnswer(new Answer('Geef je vader een knuffel.'))
->addAnswer(new Answer('Trek je wat minder aan van anderen.'))
->addAnswer(new Answer('Luister meer naar je eigen gevoel in plaats van naar wat anderen vinden.'))
->addAnswer(new Answer('Stel niet alles tot het laatste moment uit.'))
->addAnswer(new Answer('Altijd doorgaan.'))
->addAnswer(new Answer('Probeer ook eens buiten de lijntjes te kleuren', true))
->addAnswer(new Answer('Ga als je groot bent op groepsreis! '))
->addAnswer(new Answer('Trek minder aan van de mening van anderen, het is oké om anders te zijn.'))
->setOrdering(6),
)
$q = new Question();
$q->question = 'Met wie keek de Krtek video bij binnenkomst?';
$q->addAnswer(new Answer('Claudia'))
->addAnswer(new Answer('Eelco'))
->addAnswer(new Answer('Elise'))
->addAnswer(new Answer('Gert-Jan'))
->addAnswer(new Answer('Iris'))
->addAnswer(new Answer('Jari'))
->addAnswer(new Answer('Lara'))
->addAnswer(new Answer('Lotte'))
->addAnswer(new Answer('Myrthe'))
->addAnswer(new Answer('Philine'))
->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom', true));
$q->ordering = 5;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Wat voor soort schoenen droeg de Krtek bij het diner?')
->addAnswer(new Answer('Sneakers'))
->addAnswer(new Answer('Wandel-/bergschoenen', true))
->addAnswer(new Answer('Lederen schoenen'))
->addAnswer(new Answer('Pantoffels'))
->addAnswer(new Answer('Hakken'))
->addAnswer(new Answer('Geen schoenen, alleen sokken'))
->setOrdering(7),
)
$q = new Question();
$q->question = 'Welk advies zou de Krtek zichzelf als kind geven?';
$q->addAnswer(new Answer('Geef je vader een knuffel.'))
->addAnswer(new Answer('Trek je wat minder aan van anderen.'))
->addAnswer(new Answer('Luister meer naar je eigen gevoel in plaats van naar wat anderen vinden.'))
->addAnswer(new Answer('Stel niet alles tot het laatste moment uit.'))
->addAnswer(new Answer('Altijd doorgaan.'))
->addAnswer(new Answer('Probeer ook eens buiten de lijntjes te kleuren', true))
->addAnswer(new Answer('Ga als je groot bent op groepsreis! '))
->addAnswer(new Answer('Trek minder aan van de mening van anderen, het is oké om anders te zijn.'));
$q->ordering = 6;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Met welk vervoersmiddel reist de Krtek het liefste?')
->addAnswer(new Answer('Fiets', true))
->addAnswer(new Answer('Auto'))
->addAnswer(new Answer('Trein'))
->setOrdering(8),
)
$q = new Question();
$q->question = 'Wat voor soort schoenen droeg de Krtek bij het diner?';
$q->addAnswer(new Answer('Sneakers'))
->addAnswer(new Answer('Wandel-/bergschoenen', true))
->addAnswer(new Answer('Lederen schoenen'))
->addAnswer(new Answer('Pantoffels'))
->addAnswer(new Answer('Hakken'))
->addAnswer(new Answer('Geen schoenen, alleen sokken'));
$q->ordering = 7;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Heeft de Krtek een eigen auto?')
->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true))
->setOrdering(9),
)
$q = new Question();
$q->question = 'Met welk vervoersmiddel reist de Krtek het liefste?';
$q->addAnswer(new Answer('Fiets', true))
->addAnswer(new Answer('Auto'))
->addAnswer(new Answer('Trein'));
$q->ordering = 8;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Van wie is de quote die de Krtek gepakt heeft')
->addAnswer(new Answer('Karen'))
->addAnswer(new Answer('Gilles de Coster'))
->addAnswer(new Answer('Kees Tol'))
->addAnswer(new Answer('Harry en John'))
->addAnswer(new Answer('Georgina Verbaan'))
->addAnswer(new Answer('Marc-Marie Huijbregts'))
->addAnswer(new Answer('Fresia Cousiño Arias, Rik van de Westelaken'))
->addAnswer(new Answer('Ellie Lust'))
->addAnswer(new Answer('Bouba'))
->addAnswer(new Answer('Jan Versteegh'))
->addAnswer(new Answer('Dick Jol'))
->addAnswer(new Answer('Karin de Groot'))
->addAnswer(new Answer('Pieter'))
->addAnswer(new Answer('Renée Fokker'))
->addAnswer(new Answer('Sam, Davy', true))
->setOrdering(10),
)
$q = new Question();
$q->question = 'Heeft de Krtek een eigen auto?';
$q->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true));
$q->ordering = 9;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Zou de Krtek molboekjes, jokers, vrijstellingen of topitos uit iemands rugzak stelen om te kunnen winnen?')
->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true))
->setOrdering(11),
)
$q = new Question();
$q->question = 'Van wie is de quote die de Krtek gepakt heeft';
$q->addAnswer(new Answer('Karen'))
->addAnswer(new Answer('Gilles de Coster'))
->addAnswer(new Answer('Kees Tol'))
->addAnswer(new Answer('Harry en John'))
->addAnswer(new Answer('Georgina Verbaan'))
->addAnswer(new Answer('Marc-Marie Huijbregts'))
->addAnswer(new Answer('Fresia Cousiño Arias, Rik van de Westelaken'))
->addAnswer(new Answer('Ellie Lust'))
->addAnswer(new Answer('Bouba'))
->addAnswer(new Answer('Jan Versteegh'))
->addAnswer(new Answer('Dick Jol'))
->addAnswer(new Answer('Karin de Groot'))
->addAnswer(new Answer('Pieter'))
->addAnswer(new Answer('Renée Fokker'))
->addAnswer(new Answer('Sam, Davy', true));
$q->ordering = 10;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('In wat voor bed slaapt de Krtek dit weekend?')
->addAnswer(new Answer('Éénpersoons, losstaand bed'))
->addAnswer(new Answer('Éénpersoonsbed, tegen een ander bed aan', true))
->addAnswer(new Answer('Tweepersoons bed'))
->setOrdering(12),
)
$q = new Question();
$q->question = 'Zou de Krtek molboekjes, jokers, vrijstellingen of topitos uit iemands rugzak stelen om te kunnen winnen?';
$q->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true));
$q->ordering = 11;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Hoeveel jaar heeft de Krtek gedaan over de middelbare school?')
->addAnswer(new Answer('5'))
->addAnswer(new Answer('6', true))
->addAnswer(new Answer('7'))
->addAnswer(new Answer('8'))
->setOrdering(13),
)
$q = new Question();
$q->question = 'In wat voor bed slaapt de Krtek dit weekend?';
$q->addAnswer(new Answer('Éénpersoons, losstaand bed'))
->addAnswer(new Answer('Éénpersoonsbed, tegen een ander bed aan', true))
->addAnswer(new Answer('Tweepersoons bed'));
$q->ordering = 12;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Waar zat de Krtek aan tafel bij het diner?')
->addAnswer(new Answer('Met de rug naar de accommodatie'))
->addAnswer(new Answer('Met de rug naar de buitenmuur', true))
->setOrdering(14),
)
$q = new Question();
$q->question = 'Hoeveel jaar heeft de Krtek gedaan over de middelbare school?';
$q->addAnswer(new Answer('5'))
->addAnswer(new Answer('6', true))
->addAnswer(new Answer('7'))
->addAnswer(new Answer('8'));
$q->ordering = 13;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Wie is de Krtek?')
->addAnswer(new Answer('Claudia', true))
->addAnswer(new Answer('Eelco'))
->addAnswer(new Answer('Elise'))
->addAnswer(new Answer('Gert-Jan'))
->addAnswer(new Answer('Iris'))
->addAnswer(new Answer('Jari'))
->addAnswer(new Answer('Lara'))
->addAnswer(new Answer('Lotte'))
->addAnswer(new Answer('Myrthe'))
->addAnswer(new Answer('Philine'))
->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom'))
->setOrdering(15),
)
;
$q = new Question();
$q->question = 'Waar zat de Krtek aan tafel bij het diner?';
$q->addAnswer(new Answer('Met de rug naar de accommodatie'))
->addAnswer(new Answer('Met de rug naar de buitenmuur', true));
$q->ordering = 14;
$quiz->addQuestion($q);
$q = new Question();
$q->question = 'Wie is de Krtek?';
$q->addAnswer(new Answer('Claudia', true))
->addAnswer(new Answer('Eelco'))
->addAnswer(new Answer('Elise'))
->addAnswer(new Answer('Gert-Jan'))
->addAnswer(new Answer('Iris'))
->addAnswer(new Answer('Jari'))
->addAnswer(new Answer('Lara'))
->addAnswer(new Answer('Lotte'))
->addAnswer(new Answer('Myrthe'))
->addAnswer(new Answer('Philine'))
->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom'));
$q->ordering = 15;
$quiz->addQuestion($q);
return $quiz;
}
private function createQuiz2(Season $season): Quiz
{
return new Quiz()
->setName('Quiz 2')
->setSeason($season)
$quiz = new Quiz();
$quiz->name = 'Quiz 2';
$quiz->season = $season;
->addQuestion(new Question()
->setQuestion('Is de Krtek een man of een vrouw?')
->addAnswer(new Answer('Man'))
->addAnswer(new Answer('Vrouw', true))
->setOrdering(1),
)
$q = new Question();
$q->question = 'Is de Krtek een man of een vrouw?';
$q->addAnswer(new Answer('Man'))
->addAnswer(new Answer('Vrouw', true));
$q->ordering = 1;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Heeft de Krtek dieetwensen of allergieën?')
->addAnswer(new Answer('nee'))
->addAnswer(new Answer('De Krtek is vegetariër', true))
->addAnswer(new Answer('De Krtek is flexitariër'))
->addAnswer(new Answer('De Krtek heeft een allergie'))
->addAnswer(new Answer('De Krtek heeft een intolerantie'))
->addAnswer(new Answer('De Krtek eet geen rundvlees'))
->addAnswer(new Answer('De Krtek eet geen waterdieren'))
->setOrdering(2),
)
$q = new Question();
$q->question = 'Heeft de Krtek dieetwensen of allergieën?';
$q->addAnswer(new Answer('nee'))
->addAnswer(new Answer('De Krtek is vegetariër', true))
->addAnswer(new Answer('De Krtek is flexitariër'))
->addAnswer(new Answer('De Krtek heeft een allergie'))
->addAnswer(new Answer('De Krtek heeft een intolerantie'))
->addAnswer(new Answer('De Krtek eet geen rundvlees'))
->addAnswer(new Answer('De Krtek eet geen waterdieren'));
$q->ordering = 2;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Hoe heet het huisdier/de huisdieren van de Krtek?')
->addAnswer(new Answer('Amy, Karel en Floyd'))
->addAnswer(new Answer('Flip en Majoor'))
->addAnswer(new Answer('Benji'))
->addAnswer(new Answer('Sini'))
->addAnswer(new Answer('Tom'))
->addAnswer(new Answer('De huisdieren van de Krtek hebben geen naam'))
->addAnswer(new Answer('De Krtek heeft geen huisdieren', true))
->setOrdering(3),
)
$q = new Question();
$q->question = 'Hoe heet het huisdier/de huisdieren van de Krtek?';
$q->addAnswer(new Answer('Amy, Karel en Floyd'))
->addAnswer(new Answer('Flip en Majoor'))
->addAnswer(new Answer('Benji'))
->addAnswer(new Answer('Sini'))
->addAnswer(new Answer('Tom'))
->addAnswer(new Answer('De huisdieren van de Krtek hebben geen naam'))
->addAnswer(new Answer('De Krtek heeft geen huisdieren', true));
$q->ordering = 3;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Wat dronk de Krtek deze ochtend bij het ontbijt?')
->addAnswer(new Answer('Koffie'))
->addAnswer(new Answer('Thee'))
->addAnswer(new Answer('Water', true))
->addAnswer(new Answer('Melk'))
->addAnswer(new Answer('Sap'))
->addAnswer(new Answer('Niks'))
->setOrdering(4),
)
$q = new Question();
$q->question = 'Wat dronk de Krtek deze ochtend bij het ontbijt?';
$q->addAnswer(new Answer('Koffie'))
->addAnswer(new Answer('Thee'))
->addAnswer(new Answer('Water', true))
->addAnswer(new Answer('Melk'))
->addAnswer(new Answer('Sap'))
->addAnswer(new Answer('Niks'));
$q->ordering = 4;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Waar ging de eerste vakantie die de Krtek zich nog herinnert heen?')
->addAnswer(new Answer('Denemarken'))
->addAnswer(new Answer('Drenthe'))
->addAnswer(new Answer('Mallorca'))
->addAnswer(new Answer('Marokko'))
->addAnswer(new Answer('Oostenrijk'))
->addAnswer(new Answer('Turkije'))
->addAnswer(new Answer('Zweden', true))
->setOrdering(5),
)
$q = new Question();
$q->question = 'Waar ging de eerste vakantie die de Krtek zich nog herinnert heen?';
$q->addAnswer(new Answer('Denemarken'))
->addAnswer(new Answer('Drenthe'))
->addAnswer(new Answer('Mallorca'))
->addAnswer(new Answer('Marokko'))
->addAnswer(new Answer('Oostenrijk'))
->addAnswer(new Answer('Turkije'))
->addAnswer(new Answer('Zweden', true));
$q->ordering = 5;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Met welk groepje ging de Krtek als eerste het Douanespel in?')
->addAnswer(new Answer('Het eerste groepje', true))
->addAnswer(new Answer('Het tweede groepje'))
->addAnswer(new Answer('Het derde groepje'))
->addAnswer(new Answer('Het vierde groepje'))
->addAnswer(new Answer('Het vijfde groepje'))
->setOrdering(6),
)
$q = new Question();
$q->question = 'Met welk groepje ging de Krtek als eerste het Douanespel in?';
$q->addAnswer(new Answer('Het eerste groepje', true))
->addAnswer(new Answer('Het tweede groepje'))
->addAnswer(new Answer('Het derde groepje'))
->addAnswer(new Answer('Het vierde groepje'))
->addAnswer(new Answer('Het vijfde groepje'));
$q->ordering = 6;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Gelooft de Krtek ergens in?')
->addAnswer(new Answer('Nee'))
->addAnswer(new Answer('Het universum', true))
->addAnswer(new Answer('Toeval'))
->addAnswer(new Answer('De Krtek is hindoeïstisch'))
->setOrdering(7),
)
$q = new Question();
$q->question = 'Gelooft de Krtek ergens in?';
$q->addAnswer(new Answer('Nee'))
->addAnswer(new Answer('Het universum', true))
->addAnswer(new Answer('Toeval'))
->addAnswer(new Answer('De Krtek is hindoeïstisch'));
$q->ordering = 7;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('At de Krtek op vrijdagavond heksenkaas tijdens het diner?')
->addAnswer(new Answer('Ja', true))
->addAnswer(new Answer('Nee'))
->setOrdering(8),
)
$q = new Question();
$q->question = 'At de Krtek op vrijdagavond heksenkaas tijdens het diner?';
$q->addAnswer(new Answer('Ja', true))
->addAnswer(new Answer('Nee'));
$q->ordering = 8;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Hoe laat ging de Krtek gisteravond naar bed?')
->addAnswer(new Answer('Tussen 0:00 en 0:59 uur'))
->addAnswer(new Answer('Tussen 1:00 en 1:59 uur', true))
->addAnswer(new Answer('Tussen 2:00 en 2:59 uur'))
->addAnswer(new Answer('Na 3:00'))
->setOrdering(9),
)
$q = new Question();
$q->question = 'Hoe laat ging de Krtek gisteravond naar bed?';
$q->addAnswer(new Answer('Tussen 0:00 en 0:59 uur'))
->addAnswer(new Answer('Tussen 1:00 en 1:59 uur', true))
->addAnswer(new Answer('Tussen 2:00 en 2:59 uur'))
->addAnswer(new Answer('Na 3:00'));
$q->ordering = 9;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Hoeveel batterijen heeft de Krtek naar het bord gebracht bij het douanespel?')
->addAnswer(new Answer('1'))
->addAnswer(new Answer('2'))
->addAnswer(new Answer('3'))
->addAnswer(new Answer('geen', true))
->setOrdering(10),
)
$q = new Question();
$q->question = 'Hoeveel batterijen heeft de Krtek naar het bord gebracht bij het douanespel?';
$q->addAnswer(new Answer('1'))
->addAnswer(new Answer('2'))
->addAnswer(new Answer('3'))
->addAnswer(new Answer('geen', true));
$q->ordering = 10;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Wat keek de Krtek als kind graag op TV?')
->addAnswer(new Answer('Digimon', true))
->addAnswer(new Answer('Floris'))
->addAnswer(new Answer('Het huis Anubis'))
->addAnswer(new Answer('Sesamstraat'))
->addAnswer(new Answer('Spongebob Squarepants'))
->addAnswer(new Answer('Teletubbies'))
->setOrdering(11),
)
$q = new Question();
$q->question = 'Wat keek de Krtek als kind graag op TV?';
$q->addAnswer(new Answer('Digimon', true))
->addAnswer(new Answer('Floris'))
->addAnswer(new Answer('Het huis Anubis'))
->addAnswer(new Answer('Sesamstraat'))
->addAnswer(new Answer('Spongebob Squarepants'))
->addAnswer(new Answer('Teletubbies'));
$q->ordering = 11;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Waarin zat op de heenreis de bagage van de Krtek (voornamelijk)?')
->addAnswer(new Answer('In koffer(s)', true))
->addAnswer(new Answer('In losse tas(sen)'))
->addAnswer(new Answer('In een rugzak'))
->setOrdering(12),
)
$q = new Question();
$q->question = 'Waarin zat op de heenreis de bagage van de Krtek (voornamelijk)?';
$q->addAnswer(new Answer('In koffer(s)', true))
->addAnswer(new Answer('In losse tas(sen)'))
->addAnswer(new Answer('In een rugzak'));
$q->ordering = 12;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Van welk geluid gaan de haren van de Krtek overeind staan?')
->addAnswer(new Answer('Een vork die door een metalen pan krast '))
->addAnswer(new Answer('Smakkende mensen'))
->addAnswer(new Answer('Een vork die over een bord schraapt'))
->addAnswer(new Answer('Schuren met schuurpapier'))
->addAnswer(new Answer('Nagels op een krijtbord'))
->addAnswer(new Answer('Servies dat tegen elkaar klettert'))
->addAnswer(new Answer('Het geroekoe van een duif', true))
->addAnswer(new Answer('Piepschuim'))
->setOrdering(13),
)
$q = new Question();
$q->question = 'Van welk geluid gaan de haren van de Krtek overeind staan?';
$q->addAnswer(new Answer('Een vork die door een metalen pan krast '))
->addAnswer(new Answer('Smakkende mensen'))
->addAnswer(new Answer('Een vork die over een bord schraapt'))
->addAnswer(new Answer('Schuren met schuurpapier'))
->addAnswer(new Answer('Nagels op een krijtbord'))
->addAnswer(new Answer('Servies dat tegen elkaar klettert'))
->addAnswer(new Answer('Het geroekoe van een duif', true))
->addAnswer(new Answer('Piepschuim'));
$q->ordering = 13;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Wilde de Krtek penningmeester worden?')
->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true))
->setOrdering(14),
)
$q = new Question();
$q->question = 'Wilde de Krtek penningmeester worden?';
$q->addAnswer(new Answer('Ja'))
->addAnswer(new Answer('Nee', true));
$q->ordering = 14;
$quiz->addQuestion($q);
->addQuestion(new Question()
->setQuestion('Wie is de Krtek?')
->addAnswer(new Answer('Claudia', true))
->addAnswer(new Answer('Eelco'))
->addAnswer(new Answer('Elise'))
->addAnswer(new Answer('Gert-Jan'))
->addAnswer(new Answer('Iris'))
->addAnswer(new Answer('Jari'))
->addAnswer(new Answer('Lara'))
->addAnswer(new Answer('Lotte'))
->addAnswer(new Answer('Myrthe'))
->addAnswer(new Answer('Philine'))
->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom'))
->setOrdering(15),
)
;
$q = new Question();
$q->question = 'Wie is de Krtek?';
$q->addAnswer(new Answer('Claudia', true))
->addAnswer(new Answer('Eelco'))
->addAnswer(new Answer('Elise'))
->addAnswer(new Answer('Gert-Jan'))
->addAnswer(new Answer('Iris'))
->addAnswer(new Answer('Jari'))
->addAnswer(new Answer('Lara'))
->addAnswer(new Answer('Lotte'))
->addAnswer(new Answer('Myrthe'))
->addAnswer(new Answer('Philine'))
->addAnswer(new Answer('Remy'))
->addAnswer(new Answer('Robbert'))
->addAnswer(new Answer('Tom'));
$q->ordering = 15;
$quiz->addQuestion($q);
return $quiz;
}
}

19
src/Dto/Result.php Normal file
View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Tvdt\Dto;
use Symfony\Component\Uid\Uuid;
final readonly class Result
{
public function __construct(
public Uuid $id,
public string $name,
public int $correct,
public float $corrections,
public \DateInterval $time,
public float $score,
) {}
}

View File

@@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\AnswerRepository;
@@ -16,115 +15,46 @@ use Tvdt\Repository\AnswerRepository;
#[ORM\Entity(repositoryClass: AnswerRepository::class)]
class Answer
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])]
private int $ordering = 0;
public int $ordering = 0;
#[ORM\ManyToOne(inversedBy: 'answers')]
#[ORM\JoinColumn(nullable: false)]
private Question $question;
#[ORM\ManyToOne(inversedBy: 'answers')]
public Question $question;
/** @var Collection<int, Candidate> */
#[ORM\ManyToMany(targetEntity: Candidate::class, inversedBy: 'answersOnCandidate')]
private Collection $candidates;
public private(set) Collection $candidates;
/** @var Collection<int, GivenAnswer> */
#[ORM\OneToMany(targetEntity: GivenAnswer::class, mappedBy: 'answer', orphanRemoval: true)]
private Collection $givenAnswers;
public private(set) Collection $givenAnswers;
public function __construct(
#[ORM\Column(length: 255)]
private string $text,
public string $text,
#[ORM\Column]
private bool $isRightAnswer = false,
public bool $isRightAnswer = false,
) {
$this->candidates = new ArrayCollection();
$this->givenAnswers = new ArrayCollection();
}
public function getId(): Uuid
{
return $this->id;
}
public function getText(): string
{
return $this->text;
}
public function setText(string $text): static
{
$this->text = $text;
return $this;
}
public function getQuestion(): Question
{
return $this->question;
}
public function setQuestion(Question $question): static
{
$this->question = $question;
return $this;
}
public function isRightAnswer(): bool
{
return $this->isRightAnswer;
}
public function setRightAnswer(bool $isRightAnswer): static
{
$this->isRightAnswer = $isRightAnswer;
return $this;
}
/** @return Collection<int, Candidate> */
public function getCandidates(): Collection
{
return $this->candidates;
}
public function addCandidate(Candidate $candidate): static
public function addCandidate(Candidate $candidate): void
{
if (!$this->candidates->contains($candidate)) {
$this->candidates->add($candidate);
}
return $this;
}
public function removeCandidate(Candidate $candidate): static
public function removeCandidate(Candidate $candidate): void
{
$this->candidates->removeElement($candidate);
return $this;
}
/** @return Collection<int, GivenAnswer> */
public function getGivenAnswers(): Collection
{
return $this->givenAnswers;
}
public function getOrdering(): int
{
return $this->ordering;
}
public function setOrdering(int $ordering): self
{
$this->ordering = $ordering;
return $this;
}
}

View File

@@ -7,7 +7,6 @@ namespace Tvdt\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Helpers\Base64;
@@ -17,105 +16,53 @@ use Tvdt\Repository\CandidateRepository;
#[ORM\UniqueConstraint(fields: ['name', 'season'])]
class Candidate
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\ManyToOne(inversedBy: 'candidates')]
#[ORM\JoinColumn(nullable: false)]
private Season $season;
#[ORM\ManyToOne(inversedBy: 'candidates')]
public Season $season;
/** @var Collection<int, Answer> */
#[ORM\ManyToMany(targetEntity: Answer::class, mappedBy: 'candidates')]
private Collection $answersOnCandidate;
public private(set) Collection $answersOnCandidate;
/** @var Collection<int, GivenAnswer> */
#[ORM\OneToMany(targetEntity: GivenAnswer::class, mappedBy: 'candidate', orphanRemoval: true)]
private Collection $givenAnswers;
public private(set) Collection $givenAnswers;
/** @var Collection<int, QuizCandidate> */
#[ORM\OneToMany(targetEntity: QuizCandidate::class, mappedBy: 'candidate', orphanRemoval: true)]
private Collection $quizData;
public private(set) Collection $quizData;
public string $nameHash {
get => Base64::base64UrlEncode($this->name);
}
public function __construct(
#[ORM\Column(length: 16)]
private string $name,
public string $name,
) {
$this->answersOnCandidate = new ArrayCollection();
$this->givenAnswers = new ArrayCollection();
$this->quizData = new ArrayCollection();
}
public function getId(): Uuid
{
return $this->id;
}
public function getSeason(): Season
{
return $this->season;
}
public function setSeason(Season $season): static
{
$this->season = $season;
return $this;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
/** @return Collection<int, Answer> */
public function getAnswersOnCandidate(): Collection
{
return $this->answersOnCandidate;
}
public function addAnswersOnCandidate(Answer $answersOnCandidate): static
public function addAnswersOnCandidate(Answer $answersOnCandidate): void
{
if (!$this->answersOnCandidate->contains($answersOnCandidate)) {
$this->answersOnCandidate->add($answersOnCandidate);
$answersOnCandidate->addCandidate($this);
}
return $this;
}
public function removeAnswersOnCandidate(Answer $answersOnCandidate): static
public function removeAnswersOnCandidate(Answer $answersOnCandidate): void
{
if ($this->answersOnCandidate->removeElement($answersOnCandidate)) {
$answersOnCandidate->removeCandidate($this);
}
return $this;
}
/** @return Collection<int, GivenAnswer> */
public function getGivenAnswers(): Collection
{
return $this->givenAnswers;
}
/** @return Collection<int, QuizCandidate> */
public function getQuizData(): Collection
{
return $this->quizData;
}
public function getNameHash(): string
{
return Base64::base64UrlEncode($this->name);
}
}

View File

@@ -7,7 +7,6 @@ namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Safe\DateTimeImmutable;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\Uid\Uuid;
@@ -21,49 +20,25 @@ class Elimination
public const string SCREEN_RED = 'red';
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
/** @var array<string, mixed> */
#[ORM\Column(type: Types::JSON)]
private array $data = [];
#[ORM\Column(type: Types::JSONB)]
public array $data = [];
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: false)]
private \DateTimeImmutable $created;
public private(set) \DateTimeImmutable $created;
public function __construct(
#[ORM\ManyToOne(inversedBy: 'eliminations')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Quiz $quiz,
#[ORM\ManyToOne(inversedBy: 'eliminations')]
public Quiz $quiz,
) {}
public function getId(): Uuid
{
return $this->id;
}
/** @return array<string, mixed> */
public function getData(): array
{
return $this->data;
}
/** @param array<string, mixed> $data */
public function setData(array $data): self
{
$this->data = $data;
return $this;
}
public function getQuiz(): Quiz
{
return $this->quiz;
}
/** @param InputBag<bool|float|int|string> $inputBag */
public function updateFromInputBag(InputBag $inputBag): self
{
@@ -87,9 +62,4 @@ class Elimination
{
$this->created = new DateTimeImmutable();
}
public function getCreated(): \DateTimeInterface
{
return $this->created;
}
}

View File

@@ -7,7 +7,6 @@ namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Safe\DateTimeImmutable;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\GivenAnswerRepository;
@@ -16,54 +15,29 @@ use Tvdt\Repository\GivenAnswerRepository;
#[ORM\HasLifecycleCallbacks]
class GivenAnswer
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE, nullable: false)]
private \DateTimeImmutable $created;
public private(set) \DateTimeImmutable $created;
public function __construct(
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
#[ORM\JoinColumn(nullable: false)]
private Candidate $candidate,
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
private(set) Candidate $candidate,
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
private Quiz $quiz,
#[ORM\ManyToOne]
private(set) Quiz $quiz,
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
#[ORM\JoinColumn(nullable: false)]
private Answer $answer,
#[ORM\ManyToOne(inversedBy: 'givenAnswers')]
private(set) Answer $answer,
) {}
public function getId(): Uuid
{
return $this->id;
}
public function getCandidate(): Candidate
{
return $this->candidate;
}
public function getQuiz(): Quiz
{
return $this->quiz;
}
public function getAnswer(): Answer
{
return $this->answer;
}
public function getCreated(): \DateTimeImmutable
{
return $this->created;
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{

View File

@@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\QuestionRepository;
@@ -16,87 +15,40 @@ use Tvdt\Repository\QuestionRepository;
#[ORM\Entity(repositoryClass: QuestionRepository::class)]
class Question
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column(type: Types::SMALLINT, options: ['default' => 0])]
private int $ordering;
public int $ordering;
#[ORM\Column(type: Types::STRING, length: 255)]
private string $question;
public string $question;
#[ORM\ManyToOne(inversedBy: 'questions')]
#[ORM\JoinColumn(nullable: false)]
private Quiz $quiz;
#[ORM\ManyToOne(inversedBy: 'questions')]
public Quiz $quiz;
#[ORM\Column]
private bool $enabled = true;
public bool $enabled = true;
/** @var Collection<int, Answer> */
#[ORM\OneToMany(targetEntity: Answer::class, mappedBy: 'question', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['ordering' => 'ASC'])]
private Collection $answers;
public private(set) Collection $answers;
public function __construct()
{
$this->answers = new ArrayCollection();
}
public function getId(): Uuid
{
return $this->id;
}
public function getQuestion(): string
{
return $this->question;
}
public function setQuestion(string $question): static
{
$this->question = $question;
return $this;
}
public function getQuiz(): Quiz
{
return $this->quiz;
}
public function setQuiz(Quiz $quiz): static
{
$this->quiz = $quiz;
return $this;
}
public function isEnabled(): ?bool
{
return $this->enabled;
}
public function setEnabled(bool $enabled): static
{
$this->enabled = $enabled;
return $this;
}
/** @return Collection<int, Answer> */
public function getAnswers(): Collection
{
return $this->answers;
}
public function addAnswer(Answer $answer): static
{
if (!$this->answers->contains($answer)) {
$this->answers->add($answer);
$answer->setQuestion($this);
$answer->question = $this;
}
return $this;
@@ -108,7 +60,7 @@ class Question
return 'This question has no answers';
}
$correctAnswers = $this->answers->filter(static fn (Answer $answer): bool => $answer->isRightAnswer())->count();
$correctAnswers = $this->answers->filter(static fn (Answer $answer): bool => $answer->isRightAnswer)->count();
if (0 === $correctAnswers) {
return 'This question has no correct answers';
@@ -120,16 +72,4 @@ class Question
return null;
}
public function getOrdering(): int
{
return $this->ordering;
}
public function setOrdering(int $ordering): static
{
$this->ordering = $ordering;
return $this;
}
}

View File

@@ -7,7 +7,6 @@ namespace Tvdt\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\QuizRepository;
@@ -16,35 +15,35 @@ use Tvdt\Repository\QuizRepository;
#[ORM\UniqueConstraint(fields: ['name', 'season'])]
class Quiz
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column(length: 64)]
private string $name;
public string $name;
#[ORM\ManyToOne(inversedBy: 'quizzes')]
#[ORM\JoinColumn(nullable: false)]
private Season $season;
#[ORM\ManyToOne(inversedBy: 'quizzes')]
public Season $season;
/** @var Collection<int, Question> */
#[ORM\OneToMany(targetEntity: Question::class, mappedBy: 'quiz', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['ordering' => 'ASC'])]
private Collection $questions;
public private(set) Collection $questions;
/** @var Collection<int, QuizCandidate> */
#[ORM\OneToMany(targetEntity: QuizCandidate::class, mappedBy: 'quiz', orphanRemoval: true)]
private Collection $candidateData;
public private(set) Collection $candidateData;
#[ORM\Column(nullable: false, options: ['default' => 1])]
private int $dropouts = 1;
public int $dropouts = 1;
/** @var Collection<int, Elimination> */
#[ORM\OneToMany(targetEntity: Elimination::class, mappedBy: 'quiz', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['created' => 'DESC'])]
private Collection $eliminations;
public private(set) Collection $eliminations;
public function __construct()
{
@@ -53,75 +52,16 @@ class Quiz
$this->eliminations = new ArrayCollection();
}
public function getId(): Uuid
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getSeason(): Season
{
return $this->season;
}
public function setSeason(Season $season): static
{
$this->season = $season;
return $this;
}
/** @return Collection<int, Question> */
public function getQuestions(): Collection
{
return $this->questions;
}
public function addQuestion(Question $question): static
{
if (!$this->questions->contains($question)) {
$this->questions->add($question);
$question->setQuiz($this);
$question->quiz = $this;
}
return $this;
}
/** @return Collection<int, QuizCandidate> */
public function getCandidateData(): Collection
{
return $this->candidateData;
}
public function getDropouts(): int
{
return $this->dropouts;
}
public function setDropouts(int $dropouts): static
{
$this->dropouts = $dropouts;
return $this;
}
/** @return Collection<int, Elimination> */
public function getEliminations(): Collection
{
return $this->eliminations;
}
public function addElimination(Elimination $elimination): self
{
$this->eliminations->add($elimination);

View File

@@ -7,70 +7,37 @@ namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Safe\DateTimeImmutable;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\QuizCandidateRepository;
#[ORM\Entity(repositoryClass: QuizCandidateRepository::class)]
#[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])]
#[ORM\HasLifecycleCallbacks]
#[ORM\UniqueConstraint(columns: ['candidate_id', 'quiz_id'])]
class QuizCandidate
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column]
private float $corrections = 0;
public float $corrections = 0;
#[ORM\Column(type: Types::DATETIMETZ_IMMUTABLE)]
private \DateTimeImmutable $created;
public private(set) \DateTimeImmutable $created;
public function __construct(
#[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'candidateData')]
#[ORM\JoinColumn(nullable: false)]
private Quiz $quiz,
public Quiz $quiz,
#[ORM\JoinColumn(nullable: false)]
#[ORM\ManyToOne(inversedBy: 'quizData')]
#[ORM\JoinColumn(nullable: false)]
private Candidate $candidate,
public Candidate $candidate,
) {}
public function getId(): Uuid
{
return $this->id;
}
public function getCandidate(): Candidate
{
return $this->candidate;
}
public function getQuiz(): Quiz
{
return $this->quiz;
}
public function getCorrections(): ?float
{
return $this->corrections;
}
public function setCorrections(float $corrections): static
{
$this->corrections = $corrections;
return $this;
}
public function getCreated(): \DateTimeImmutable
{
return $this->created;
}
#[ORM\PrePersist]
public function setCreatedAtValue(): void
{

View File

@@ -7,7 +7,6 @@ namespace Tvdt\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\SeasonRepository;
@@ -17,38 +16,38 @@ class Season
{
private const string SEASON_CODE_CHARACTERS = 'bcdfghjklmnpqrstvwxz';
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column(length: 64)]
private string $name;
public string $name;
#[ORM\Column(length: 5)]
private string $seasonCode;
public string $seasonCode;
/** @var Collection<int, Quiz> */
#[ORM\OneToMany(targetEntity: Quiz::class, mappedBy: 'season', cascade: ['persist'], orphanRemoval: true)]
private Collection $quizzes;
public private(set) Collection $quizzes;
/** @var Collection<int, Candidate> */
#[ORM\OneToMany(targetEntity: Candidate::class, mappedBy: 'season', cascade: ['persist'], orphanRemoval: true)]
#[ORM\OrderBy(['name' => 'ASC'])]
private Collection $candidates;
public private(set) Collection $candidates;
/** @var Collection<int, User> */
#[ORM\ManyToMany(targetEntity: User::class, inversedBy: 'seasons')]
private Collection $owners;
public private(set) Collection $owners;
#[ORM\ManyToOne]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
private ?Quiz $ActiveQuiz = null;
#[ORM\ManyToOne]
public ?Quiz $activeQuiz = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
#[ORM\JoinColumn(nullable: true)]
private ?SeasonSettings $settings = null;
#[ORM\OneToOne(cascade: ['persist', 'remove'])]
public ?SeasonSettings $settings = null;
public function __construct()
{
@@ -58,73 +57,26 @@ class Season
$this->owners = new ArrayCollection();
}
public function getId(): Uuid
{
return $this->id;
}
public function getName(): string
{
return $this->name;
}
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
public function getSeasonCode(): ?string
{
return $this->seasonCode;
}
public function setSeasonCode(string $seasonCode): static
{
$this->seasonCode = $seasonCode;
return $this;
}
/** @return Collection<int, Quiz> */
public function getQuizzes(): Collection
{
return $this->quizzes;
}
public function addQuiz(Quiz $quiz): static
{
if (!$this->quizzes->contains($quiz)) {
$this->quizzes->add($quiz);
$quiz->setSeason($this);
$quiz->season = $this;
}
return $this;
}
/** @return Collection<int, Candidate> */
public function getCandidates(): Collection
{
return $this->candidates;
}
public function addCandidate(Candidate $candidate): static
{
if (!$this->candidates->contains($candidate)) {
$this->candidates->add($candidate);
$candidate->setSeason($this);
$candidate->season = $this;
}
return $this;
}
/** @return Collection<int, User> */
public function getOwners(): Collection
{
return $this->owners;
}
public function addOwner(User $owner): static
{
if (!$this->owners->contains($owner)) {
@@ -141,18 +93,6 @@ class Season
return $this;
}
public function getActiveQuiz(): ?Quiz
{
return $this->ActiveQuiz;
}
public function setActiveQuiz(?Quiz $ActiveQuiz): static
{
$this->ActiveQuiz = $ActiveQuiz;
return $this;
}
public function isOwner(User $user): bool
{
return $this->owners->contains($user);
@@ -171,16 +111,4 @@ class Season
return $this;
}
public function getSettings(): ?SeasonSettings
{
return $this->settings;
}
public function setSettings(SeasonSettings $settings): static
{
$this->settings = $settings;
return $this;
}
}

View File

@@ -6,7 +6,6 @@ namespace Tvdt\Entity;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;
use Tvdt\Repository\SeasonSettingsRepository;
@@ -14,44 +13,15 @@ use Tvdt\Repository\SeasonSettingsRepository;
#[ORM\Entity(repositoryClass: SeasonSettingsRepository::class)]
class SeasonSettings
{
#[ORM\Id]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Column(type: UuidType::NAME)]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
private bool $showNumbers = false;
public bool $showNumbers = false;
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
private bool $confirmAnswers = false;
public function getId(): Uuid
{
return $this->id;
}
public function isShowNumbers(): bool
{
return $this->showNumbers;
}
public function setShowNumbers(bool $showNumbers): self
{
$this->showNumbers = $showNumbers;
return $this;
}
public function isConfirmAnswers(): bool
{
return $this->confirmAnswers;
}
public function setConfirmAnswers(bool $confirmAnswers): self
{
$this->confirmAnswers = $confirmAnswers;
return $this;
}
public bool $confirmAnswers = false;
}

View File

@@ -8,7 +8,6 @@ use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\IdGenerator\UuidGenerator;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
@@ -22,52 +21,39 @@ use Tvdt\Repository\UserRepository;
#[UniqueEntity(fields: ['email'], message: 'There is already an account with this email')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: UuidGenerator::class)]
private Uuid $id;
#[ORM\Id]
public private(set) Uuid $id;
#[ORM\Column(length: 180)]
private string $email;
public string $email;
/** @var list<string> The user roles */
#[ORM\Column(type: Types::JSON)]
private array $roles = [];
public array $roles = [];
/** @var string The hashed password */
#[ORM\Column]
private string $password;
public string $password;
/** @var Collection<int, Season> */
#[ORM\ManyToMany(targetEntity: Season::class, mappedBy: 'owners')]
private Collection $seasons;
public private(set) Collection $seasons;
#[ORM\Column]
private bool $isVerified = false;
public bool $isVerified = false;
public bool $isAdmin {
get => \in_array('ROLE_ADMIN', $this->getRoles(), true);
}
public function __construct()
{
$this->seasons = new ArrayCollection();
}
public function getId(): Uuid
{
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.
*
@@ -97,27 +83,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
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
{
@@ -125,12 +96,6 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
// $this->plainPassword = null;
}
/** @return Collection<int, Season> */
public function getSeasons(): Collection
{
return $this->seasons;
}
public function addSeason(Season $season): static
{
if (!$this->seasons->contains($season)) {
@@ -149,21 +114,4 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
return $this;
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): static
{
$this->isVerified = $isVerified;
return $this;
}
public function isAdmin(): bool
{
return \in_array('ROLE_ADMIN', $this->getRoles(), true);
}
}

View File

@@ -26,10 +26,10 @@ final readonly class EliminationFactory
$simpleScores = [];
foreach (array_reverse($scores) as $i => $score) {
$simpleScores[$score['name']] = $i < $quiz->getDropouts() ? Elimination::SCREEN_RED : Elimination::SCREEN_GREEN;
$simpleScores[$score->name] = $i < $quiz->dropouts ? Elimination::SCREEN_RED : Elimination::SCREEN_GREEN;
}
$elimination->setData($simpleScores);
$elimination->data = $simpleScores;
$this->em->flush();

View File

@@ -6,8 +6,10 @@ namespace Tvdt\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Safe\DateTimeImmutable;
use Safe\Exceptions\DatetimeException;
use Safe\Exceptions\UrlException;
use Symfony\Component\Uid\Uuid;
use Tvdt\Dto\Result;
use Tvdt\Entity\Candidate;
use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
@@ -15,9 +17,6 @@ use Tvdt\Helpers\Base64;
/**
* @extends ServiceEntityRepository<Candidate>
*
* @phpstan-type Result array{id: Uuid, name: string, correct: int, time: \DateInterval, corrections: float, score: float}
* @phpstan-type ResultList list<Result>
*/
class CandidateRepository extends ServiceEntityRepository
{
@@ -38,7 +37,7 @@ class CandidateRepository extends ServiceEntityRepository
select c from Tvdt\Entity\Candidate c
where c.season = :season
and lower(c.name) = lower(:name)
DQL
DQL
)->setParameter('season', $season)
->setParameter('name', $name)
->getOneOrNullResult();
@@ -53,25 +52,39 @@ class CandidateRepository extends ServiceEntityRepository
}
}
/** @return ResultList */
/**
* @throws DatetimeException
*
* @return list<Result>
*/
public function getScores(Quiz $quiz): array
{
return $this->getEntityManager()->createQuery(<<<DQL
select
c.id,
c.name,
sum(case when a.isRightAnswer = true then 1 else 0 end) as correct,
qc.corrections,
max(ga.created) - qc.created as time,
(sum(case when a.isRightAnswer = true then 1 else 0 end) + qc.corrections) as score
from Tvdt\Entity\Candidate c
join c.givenAnswers ga
join ga.answer a
join c.quizData qc
where qc.quiz = :quiz and ga.quiz = :quiz
group by ga.quiz, c.id, qc.id
order by score desc, time asc
DQL
$result = $this->getEntityManager()->createQuery(<<<DQL
select
c.id,
c.name,
sum(case when a.isRightAnswer = true then 1 else 0 end) as correct,
qc.corrections,
max(ga.created) as end_time,
qc.created as start_time,
(sum(case when a.isRightAnswer = true then 1 else 0 end) + qc.corrections) as score
from Tvdt\Entity\Candidate c
join c.givenAnswers ga
join ga.answer a
join c.quizData qc
where qc.quiz = :quiz and ga.quiz = :quiz
group by ga.quiz, c.id, qc.id
order by score desc, max(ga.created) - qc.created asc
DQL
)->setParameter('quiz', $quiz)->getResult();
return array_map(static fn (array $row): Result => new Result(
id: $row['id'],
name: $row['name'],
correct: (int) $row['correct'],
corrections: $row['corrections'],
time: new DateTimeImmutable($row['end_time'])->diff($row['start_time']),
score: $row['score'],
), $result);
}
}

View File

@@ -32,10 +32,10 @@ class QuestionRepository extends ServiceEntityRepository
and q1.quiz = :quiz
)
and qz = :quiz
DQL)
DQL)
->setMaxResults(1)
->setParameter('candidate', $candidate)
->setParameter('quiz', $candidate->getSeason()->getActiveQuiz())
->setParameter('quiz', $candidate->season->activeQuiz)
->getOneOrNullResult();
}
}

View File

@@ -41,7 +41,7 @@ class QuizCandidateRepository extends ServiceEntityRepository
throw new \InvalidArgumentException('Quiz candidate not found');
}
$quizCandidate->setCorrections($corrections);
$quizCandidate->corrections = $corrections;
$this->getEntityManager()->flush();
}
}

View File

@@ -24,7 +24,7 @@ class SeasonRepository extends ServiceEntityRepository
{
return $this->getEntityManager()->createQuery(<<<DQL
select s from Tvdt\Entity\Season s where :user member of s.owners order by s.name
DQL
DQL
)->setParameter('user', $user)->getResult();
}
}

View File

@@ -23,7 +23,7 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
* */
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
$user->setPassword($newHashedPassword);
$user->password = $newHashedPassword;
$this->getEntityManager()->persist($user);
$this->getEntityManager()->flush();
}
@@ -35,7 +35,7 @@ class UserRepository extends ServiceEntityRepository implements PasswordUpgrader
throw new \InvalidArgumentException('User not found');
}
$user->setRoles(['ROLE_ADMIN']);
$user->roles = ['ROLE_ADMIN'];
$this->getEntityManager()->flush();
}
}

View File

@@ -25,9 +25,9 @@ readonly class EmailVerifier
{
$signatureComponents = $this->verifyEmailHelper->generateSignature(
$verifyEmailRouteName,
(string) $user->getId(),
(string) $user->getEmail(),
['id' => $user->getId()],
$user->id->toRfc4122(),
$user->email,
['id' => $user->id],
);
$context = $email->getContext();
@@ -42,9 +42,9 @@ readonly class EmailVerifier
public function handleEmailConfirmation(Request $request, User $user): void
{
$this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, (string) $user->getId(), (string) $user->getEmail());
$this->verifyEmailHelper->validateEmailConfirmationFromRequest($request, $user->id->toRfc4122(), $user->email);
$user->setIsVerified(true);
$user->isVerified = true;
$this->entityManager->persist($user);
$this->entityManager->flush();

View File

@@ -15,7 +15,7 @@ use Tvdt\Entity\Quiz;
use Tvdt\Entity\Season;
use Tvdt\Entity\User;
/** @extends Voter<string, Season> */
/** @extends Voter<string, Season|Elimination|Quiz|Candidate|Answer|Question> */
final class SeasonVoter extends Voter
{
public const string EDIT = 'SEASON_EDIT';
@@ -28,16 +28,15 @@ final class SeasonVoter extends Voter
{
return \in_array($attribute, [self::EDIT, self::DELETE, self::ELIMINATION], true)
&& (
$subject instanceof Season
|| $subject instanceof Elimination
|| $subject instanceof Quiz
$subject instanceof Answer
|| $subject instanceof Candidate
|| $subject instanceof Answer
|| $subject instanceof Elimination
|| $subject instanceof Season
|| $subject instanceof Question
|| $subject instanceof Quiz
);
}
/** @param Season|Elimination|Quiz|Candidate|Answer|Question $subject */
protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token, ?Vote $vote = null): bool
{
$user = $token->getUser();
@@ -45,21 +44,21 @@ final class SeasonVoter extends Voter
return false;
}
if ($user->isAdmin()) {
if ($user->isAdmin) {
return true;
}
switch (true) {
case $subject instanceof Answer:
$season = $subject->getQuestion()->getQuiz()->getSeason();
$season = $subject->question->quiz->season;
break;
case $subject instanceof Elimination:
case $subject instanceof Question:
$season = $subject->getQuiz()->getSeason();
$season = $subject->quiz->season;
break;
case $subject instanceof Candidate:
case $subject instanceof Quiz:
$season = $subject->getSeason();
$season = $subject->season;
break;
case $subject instanceof Season:
$season = $subject;

View File

@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace Tvdt\Service;
use Tvdt\Repository\CandidateRepository;
/**
* @phpstan-import-type ResultList from CandidateRepository
*/
class EliminationService
{
/** @phpstan-param ResultList $result */
public function createEliminationFromResult(array $result): void {}
}

View File

@@ -84,8 +84,8 @@ class QuizSpreadsheetService
}
$question = new Question();
$question->setQuestion((string) $questionArr[0]);
$question->setOrdering($questionCounter++);
$question->question = (string) $questionArr[0];
$question->ordering = $questionCounter++;
$answerCounter = 1;
$arrCounter = 1;
@@ -100,7 +100,7 @@ class QuizSpreadsheetService
}
$answer = new Answer((string) $questionArr[$arrCounter++], (bool) $questionArr[$arrCounter++]);
$answer->setOrdering($answerCounter++);
$answer->ordering = $answerCounter++;
$question->addAnswer($answer);
}

View File

@@ -47,15 +47,6 @@
"migrations/.gitignore"
]
},
"easycorp/easyadmin-bundle": {
"version": "4.23",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "3.0",
"ref": "b131e6cbfe1b898a508987851963fff485986285"
}
},
"friendsofphp/php-cs-fixer": {
"version": "3.65",
"recipe": {
@@ -276,12 +267,12 @@
]
},
"symfony/translation": {
"version": "7.2",
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.3",
"ref": "e28e27f53663cc34f0be2837aba18e3a1bef8e7b"
"ref": "620a1b84865ceb2ba304c8f8bf2a185fbf32a843"
},
"files": [
"config/packages/translation.yaml",
@@ -319,20 +310,8 @@
"ref": "c85ff94da66841d7ff087c19cbcd97a2df744ef9"
}
},
"symfony/ux-twig-component": {
"version": "2.22",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "2.13",
"ref": "67814b5f9794798b885cec9d3f48631424449a01"
},
"files": [
"config/packages/twig_component.yaml"
]
},
"symfony/validator": {
"version": "7.2",
"version": "7.3",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",

View File

@@ -109,7 +109,7 @@
</form>
</td>
<td>{{ candidate.score|default('x') }}</td>
<td>{{ candidate.time }}</td>
<td>{{ candidate.time.format('%i:%S') }}</td>
</tr>
{% else %}
<tr>

View File

@@ -53,19 +53,19 @@ final class SeasonVoterTest extends TestCase
$season->method('isOwner')->willReturn(true);
$quiz = self::createStub(Quiz::class);
$quiz->method('getSeason')->willReturn($season);
$quiz->season = $season;
$elimination = self::createStub(Elimination::class);
$elimination->method('getQuiz')->willReturn($quiz);
$elimination->quiz = $quiz;
$candidate = self::createStub(Candidate::class);
$candidate->method('getSeason')->willReturn($season);
$candidate->season = $season;
$question = self::createStub(Question::class);
$question->method('getQuiz')->willReturn($quiz);
$question->quiz = $quiz;
$answer = self::createStub(Answer::class);
$answer->method('getQuestion')->willReturn($question);
$answer->question = $question;
yield 'Season' => [$season];
yield 'Elimination' => [$elimination];

15
tests/object-manager.php Normal file
View File

@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
use Symfony\Component\Dotenv\Dotenv;
use Tvdt\Kernel;
require __DIR__.'/../vendor/autoload.php';
new Dotenv()->bootEnv(__DIR__.'/../.env');
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$kernel->boot();
return $kernel->getContainer()->get('doctrine')->getManager();

View File

@@ -0,0 +1,11 @@
<?php
declare(strict_types=1);
use Tvdt\Kernel;
require __DIR__.'/bootstrap.php';
$appKernel = new Kernel('test', false);
$appKernel->boot();
return $appKernel->getContainer();