From 65ef2ed89c2459e4adf559023913d500d561313d Mon Sep 17 00:00:00 2001 From: Tim Lappe Date: Fri, 30 May 2025 07:04:14 +0200 Subject: [PATCH] Use postgresql and improved visuals --- .cursor/rules/project.mdc | 28 + .gitignore | 5 + Dockerfile.dev | 15 +- README.md | 117 +- assets/app.js | 17 + bin/docker | 3 + composer.json | 10 +- composer.lock | 1634 ++++++++++++++--- config/bundles.php | 2 + config/packages/asset_mapper.yaml | 11 + config/packages/doctrine.yaml | 24 + config/packages/doctrine_migrations.yaml | 6 + config/services.yaml | 9 +- docker-compose.yml | 67 +- importmap.php | 38 + migrations/Version20250529155930.php | 57 + src/Application/Commands/LoadFixtures.php | 513 ++++++ src/Domain/Model/Brand.php | 12 +- src/Domain/Model/CarModelCollection.php | 16 + src/Domain/Model/CarRevisionCollection.php | 16 + .../Model/Persistence/PersistedBrand.php | 13 + .../Model/Persistence/PersistedCarModel.php | 13 + .../Persistence/PersistedCarRevision.php | 13 + src/Domain/Model/Value/Acceleration.php | 2 +- src/Domain/Model/Value/Consumption.php | 13 +- src/Domain/Model/Value/Currency.php | 15 - src/Domain/Model/Value/Price.php | 7 +- src/Domain/Repository/BrandRepository.php | 8 +- src/Domain/Repository/CarModelRepository.php | 104 +- .../Repository/CarRevisionRepository.php | 22 + src/Domain/Search/Engine.php | 24 +- src/Domain/Search/Tiles/AvailabilityTile.php | 4 +- src/Infrastructure/MongoDB/MongoDBClient.php | 28 - .../BrandRepository/ModelMapper.php | 27 - .../MongoDBBrandRepository.php | 30 - .../BrandRepository/ModelMapper.php | 22 + .../PostgreSQLBrandRepository.php | 88 + .../CarModelRepository/ModelMapper.php | 24 + .../PostgreSQLCarModelRepository.php | 119 ++ .../CarRevisionRepository/ModelMapper.php | 118 ++ .../PostgreSQLCarRevisionRepository.php | 167 ++ symfony.lock | 51 + templates/_components/search.html.twig | 6 +- templates/base.html.twig | 9 +- templates/home/index.html.twig | 12 +- templates/result/tiles/acceleration.html.twig | 6 +- templates/result/tiles/availability.html.twig | 13 +- templates/result/tiles/battery.html.twig | 5 +- .../result/tiles/batterydetails.html.twig | 24 +- templates/result/tiles/brand.html.twig | 5 +- templates/result/tiles/car.html.twig | 2 +- templates/result/tiles/chargecurve.html.twig | 5 +- templates/result/tiles/chargetime.html.twig | 25 +- templates/result/tiles/charging.html.twig | 7 +- .../tiles/chargingconnectivity.html.twig | 44 +- templates/result/tiles/consumption.html.twig | 8 +- templates/result/tiles/drivetrain.html.twig | 5 +- .../tiles/performanceoverview.html.twig | 5 +- templates/result/tiles/power.html.twig | 5 +- templates/result/tiles/price.html.twig | 6 +- .../result/tiles/productionperiod.html.twig | 5 +- templates/result/tiles/range.html.twig | 5 +- .../result/tiles/rangecomparison.html.twig | 5 +- templates/result/tiles/realrange.html.twig | 26 +- templates/result/tiles/section.html.twig | 2 +- templates/result/tiles/subsection.html.twig | 2 +- templates/result/tiles/topspeed.html.twig | 5 +- 67 files changed, 3055 insertions(+), 699 deletions(-) create mode 100644 .cursor/rules/project.mdc create mode 100644 assets/app.js create mode 100755 bin/docker create mode 100644 config/packages/asset_mapper.yaml create mode 100644 config/packages/doctrine.yaml create mode 100644 config/packages/doctrine_migrations.yaml create mode 100644 importmap.php create mode 100644 migrations/Version20250529155930.php create mode 100644 src/Application/Commands/LoadFixtures.php create mode 100644 src/Domain/Model/CarModelCollection.php create mode 100644 src/Domain/Model/CarRevisionCollection.php create mode 100644 src/Domain/Model/Persistence/PersistedBrand.php create mode 100644 src/Domain/Model/Persistence/PersistedCarModel.php create mode 100644 src/Domain/Model/Persistence/PersistedCarRevision.php create mode 100644 src/Domain/Repository/CarRevisionRepository.php delete mode 100644 src/Infrastructure/MongoDB/MongoDBClient.php delete mode 100644 src/Infrastructure/MongoDB/Repository/BrandRepository/ModelMapper.php delete mode 100644 src/Infrastructure/MongoDB/Repository/BrandRepository/MongoDBBrandRepository.php create mode 100644 src/Infrastructure/PostgreSQL/Repository/BrandRepository/ModelMapper.php create mode 100644 src/Infrastructure/PostgreSQL/Repository/BrandRepository/PostgreSQLBrandRepository.php create mode 100644 src/Infrastructure/PostgreSQL/Repository/CarModelRepository/ModelMapper.php create mode 100644 src/Infrastructure/PostgreSQL/Repository/CarModelRepository/PostgreSQLCarModelRepository.php create mode 100644 src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/ModelMapper.php create mode 100644 src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/PostgreSQLCarRevisionRepository.php diff --git a/.cursor/rules/project.mdc b/.cursor/rules/project.mdc new file mode 100644 index 0000000..ec677e2 --- /dev/null +++ b/.cursor/rules/project.mdc @@ -0,0 +1,28 @@ +--- +description: +globs: +alwaysApply: true +--- +# EV Wiki +The EV Wiki project is a website for searching, finding and comparison of ev vehicles. +It works like a search engine and aggregates the data from the database to show the user the requested information in a tile based view. + +## Structue +The project is structured in domain driven design manner. Inside the src-Folder, you find: +- Application (Controllers, Commands and other symfony related application stuff) +- Domain: The core business logic of the project. This is 100% framework agnostic code +- Infrastructure: Implementations of Interfaces provided by the Domain layer. + +The dependency is going from Application -> Domain -> Infrastrcture, but not in the opposite direction. +The Domain does not know about the existence of the application folder, and also the infrastructure doesn't know anything about the Domain and Application folder. + +## Concepts +### Tile based results +One core concept of the search results in the ev wiki project is the tile based search result. +The Tile Classes e.g. are holding view data for specific tile types. These tiles are displayed by custom twig template, one template for one tile type. +The Tiles were aggregated and built by the Engine class that is responsible for building the tile view. + +### Project dependencies +We will barely use external dependencies. Some depencies are explicitly avoided: +- No ORM: The project follows the philosophy, that orms destroy the needed abstraction between model (Domain) and database layer (Infrastrcture). + Since SQL is already a human readable language, there is no need to introduce heavy coupling between the model and the database layer. \ No newline at end of file diff --git a/.gitignore b/.gitignore index cb38ff4..49908d7 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,8 @@ next-env.d.ts /var/ /vendor/ ###< symfony/framework-bundle ### + +###> symfony/asset-mapper ### +/public/assets/ +/assets/vendor/ +###< symfony/asset-mapper ### diff --git a/Dockerfile.dev b/Dockerfile.dev index 449b453..62ac4a4 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -2,7 +2,7 @@ FROM php:8.4-fpm-alpine WORKDIR /app -# Install system dependencies including zsh +# Install system dependencies RUN apk add --no-cache \ git \ curl \ @@ -14,14 +14,15 @@ RUN apk add --no-cache \ autoconf \ g++ \ make \ - wget + wget \ + postgresql-dev \ + zsh + +# Install Oh My Zsh +RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended # Install PHP extensions -RUN docker-php-ext-install pdo pdo_mysql - -# Install MongoDB PHP extension -RUN pecl install mongodb \ - && docker-php-ext-enable mongodb +RUN docker-php-ext-install pdo pdo_pgsql pgsql # Install Composer COPY --from=composer:latest /usr/bin/composer /usr/bin/composer diff --git a/README.md b/README.md index 1835e7f..bb55f04 100644 --- a/README.md +++ b/README.md @@ -8,22 +8,22 @@ A modern Symfony application for browsing and searching electric vehicle informa - **Brand Directory**: Browse popular electric vehicle manufacturers - **Vehicle Database**: Comprehensive information about electric car models and revisions - **RESTful API**: JSON API endpoints for integration -- **MongoDB Integration**: NoSQL database for flexible data storage +- **PostgreSQL Integration**: Relational database for structured data storage - **Responsive Design**: Mobile-friendly interface without external CSS frameworks ## Technology Stack -- **Backend**: Symfony 7.2 (PHP 8.2+) -- **Database**: MongoDB with Doctrine ODM +- **Backend**: Symfony 7.2 (PHP 8.3+) +- **Database**: PostgreSQL with native PDO - **Frontend**: Vanilla JavaScript with modern CSS - **Architecture**: Clean Architecture with SOLID principles ## Requirements -- PHP 8.2 or higher -- MongoDB 4.4 or higher +- PHP 8.3 or higher +- PostgreSQL 13 or higher - Composer -- MongoDB PHP Extension +- PostgreSQL PHP Extension (pdo_pgsql) ## Installation @@ -43,19 +43,21 @@ A modern Symfony application for browsing and searching electric vehicle informa cp .env.local.example .env.local ``` - Edit `.env.local` and set your MongoDB connection: + Edit `.env.local` and set your PostgreSQL connection: ``` APP_ENV=dev APP_SECRET=your-secret-key-here - MONGODB_URI=mongodb://localhost:27017 + DATABASE_DSN="pgsql:host=localhost;port=5432;dbname=evwiki" + DATABASE_USER=postgres + DATABASE_PASSWORD=postgres ``` -4. **Start MongoDB** - Make sure MongoDB is running on your system. +4. **Start PostgreSQL** + Make sure PostgreSQL is running on your system. -5. **Seed the database** +5. **Initialize the database** ```bash - php bin/console app:seed-data + php bin/console app:database:init ``` 6. **Start the development server** @@ -68,26 +70,48 @@ A modern Symfony application for browsing and searching electric vehicle informa php -S localhost:8000 -t public/ ``` +## Docker Setup + +You can also run the application using Docker: + +1. **Start the services** + ```bash + docker-compose up -d + ``` + +2. **Initialize the database in the container** + ```bash + docker-compose exec app php bin/console app:database:init + ``` + ## Project Structure ``` src/ -├── Command/ # Console commands -├── Controller/ # HTTP controllers -├── Document/ # MongoDB document models -├── Repository/ # Data access layer -├── Service/ # Business logic layer -└── Kernel.php # Application kernel +├── Command/ # Console commands +├── Application/ +│ └── Controller/ # HTTP controllers +├── Domain/ +│ ├── Model/ # Domain models +│ └── Repository/ # Repository interfaces +├── Infrastructure/ +│ └── PostgreSQL/ # PostgreSQL implementation +│ ├── PostgreSQLClient.php +│ └── Repository/ # Concrete repositories +└── Kernel.php # Application kernel templates/ -├── base.html.twig # Base template -└── home/ # Home page templates +├── base.html.twig # Base template +└── home/ # Home page templates + +database/ +└── schema.sql # PostgreSQL schema config/ -├── bundles.php # Bundle configuration -├── packages/ # Package configurations -├── routes.yaml # Route definitions -└── services.yaml # Service container +├── bundles.php # Bundle configuration +├── packages/ # Package configurations +├── routes.yaml # Route definitions +└── services.yaml # Service container ``` ## API Endpoints @@ -107,50 +131,63 @@ config/ This application follows clean architecture principles: ### Domain Layer -- **Documents**: MongoDB document models (`Brand`, `CarModel`, `CarRevision`) +- **Models**: Core domain models (`Brand`, `CarModel`, `CarRevision`) - **Repositories**: Data access interfaces ### Application Layer -- **Services**: Business logic (`CarSearchService`) +- **Controllers**: HTTP request handlers - **Commands**: Console commands for data management ### Infrastructure Layer -- **Controllers**: HTTP request handlers +- **PostgreSQL**: Database implementation with native PDO - **Templates**: Twig templates for rendering ### Key Design Principles 1. **SOLID Principles**: Each class has a single responsibility 2. **Dependency Injection**: All dependencies are injected via constructor -3. **No Else Statements**: Code uses early returns for better readability -4. **Readable Names**: Self-documenting code with descriptive names +3. **Clean Architecture**: Domain logic is independent of infrastructure +4. **Readable Code**: Self-documenting code with descriptive names -## Development +## Database Commands -### Adding New Data - -Use the seed command to populate the database: +### Initialize Database +Initialize the database with schema and sample data: ```bash -php bin/console app:seed-data +php bin/console app:database:init ``` ### Console Commands - List all available commands: ```bash php bin/console list ``` -### Database Operations +## Database Schema -The application uses MongoDB with Doctrine ODM. All database operations are handled through repositories following the Repository pattern. +The application uses PostgreSQL with the following main tables: + +- **brands**: Electric vehicle manufacturers +- **car_models**: Vehicle models belonging to brands +- **car_revisions**: Specific revisions of car models with detailed specifications + +All database operations are handled through repositories following the Repository pattern, using native PDO for optimal performance. ## Styling Guidelines - **No External CSS Frameworks**: Pure CSS following modern standards -- **Responsive Design**: Mobile-first approach -- **Modern UI**: Clean, minimalist design inspired by search engines -- **Accessibility**: Semantic HTML and proper contrast ratios +- **Mobile-First**: Responsive design approach +- **Clean Design**: Minimalist interface focusing on content + +## Environment Variables + +Required environment variables: + +- `APP_ENV`: Application environment (dev/prod) +- `APP_SECRET`: Secret key for Symfony +- `DATABASE_DSN`: PostgreSQL DSN connection string +- `DATABASE_USER`: PostgreSQL username +- `DATABASE_PASSWORD`: PostgreSQL password ## Contributing diff --git a/assets/app.js b/assets/app.js new file mode 100644 index 0000000..a1f199e --- /dev/null +++ b/assets/app.js @@ -0,0 +1,17 @@ +/* + * Welcome to your app's main JavaScript file! + * + * This file will be included onto the page via the importmap() Twig function, + * which should already be in your base.html.twig. + */ + +// Import Font Awesome CSS +import '@fortawesome/fontawesome-free/css/fontawesome.min.css'; +import '@fortawesome/fontawesome-free/css/solid.min.css'; +import '@fortawesome/fontawesome-free/css/brands.min.css'; +import '@fortawesome/fontawesome-free/css/regular.min.css'; + +// Import Font Awesome JavaScript (optional - for advanced features) +import '@fortawesome/fontawesome-free'; + +console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉'); diff --git a/bin/docker b/bin/docker new file mode 100755 index 0000000..91f62ec --- /dev/null +++ b/bin/docker @@ -0,0 +1,3 @@ +#!/bin/bash + +docker compose exec app /bin/zsh \ No newline at end of file diff --git a/composer.json b/composer.json index d798245..868c1be 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,10 @@ "php": "^8.3", "ext-ctype": "*", "ext-iconv": "*", - "mongodb/mongodb": "*", + "ext-pdo": "*", + "ext-pgsql": "*", + "doctrine/doctrine-migrations-bundle": "^3.4", + "symfony/asset-mapper": "^7.3", "symfony/console": "7.2.*", "symfony/dotenv": "7.2.*", "symfony/flex": "^2", @@ -46,7 +49,8 @@ "scripts": { "auto-scripts": { "cache:clear": "symfony-cmd", - "assets:install %PUBLIC_DIR%": "symfony-cmd" + "assets:install %PUBLIC_DIR%": "symfony-cmd", + "importmap:install": "symfony-cmd" }, "post-install-cmd": [ "@auto-scripts" @@ -55,4 +59,4 @@ "@auto-scripts" ] } -} +} \ No newline at end of file diff --git a/composer.lock b/composer.lock index dbf6ffd..dccb2ee 100644 --- a/composer.lock +++ b/composer.lock @@ -4,84 +4,793 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "bc7b96ae2944ee48c0d513cabd0ec0c8", + "content-hash": "af2c42f4eb216435ec2aed6bb8b1dd11", "packages": [ { - "name": "mongodb/mongodb", - "version": "2.1.0", + "name": "composer/semver", + "version": "3.4.3", "source": { "type": "git", - "url": "https://github.com/mongodb/mongo-php-library.git", - "reference": "3bbe7ba9578724c7e1f47fcd17c881c0995baaad" + "url": "https://github.com/composer/semver.git", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/3bbe7ba9578724c7e1f47fcd17c881c0995baaad", - "reference": "3bbe7ba9578724c7e1f47fcd17c881c0995baaad", + "url": "https://api.github.com/repos/composer/semver/zipball/4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", + "reference": "4313d26ada5e0c4edfbd1dc481a92ff7bff91f12", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0", - "ext-mongodb": "^2.1", - "php": "^8.1", - "psr/log": "^1.1.4|^2|^3", - "symfony/polyfill-php85": "^1.32" - }, - "replace": { - "mongodb/builder": "*" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^12.0", - "phpunit/phpunit": "^10.5.35", - "rector/rector": "^1.2", - "squizlabs/php_codesniffer": "^3.7", - "vimeo/psalm": "6.5.*" + "phpstan/phpstan": "^1.11", + "symfony/phpunit-bridge": "^3 || ^7" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-main": "3.x-dev" } }, "autoload": { - "files": [ - "src/functions.php" - ], "psr-4": { - "MongoDB\\": "src/" + "Composer\\Semver\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { - "name": "Andreas Braun", - "email": "andreas.braun@mongodb.com" + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" }, { - "name": "Jeremy Mikola", - "email": "jmikola@gmail.com" + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" }, { - "name": "Jérôme Tamarelle", - "email": "jerome.tamarelle@mongodb.com" + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" } ], - "description": "MongoDB driver library", - "homepage": "https://jira.mongodb.org/browse/PHPLIB", + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "support": { + "irc": "ircs://irc.libera.chat:6697/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.4.3" + }, + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-09-19T14:15:21+00:00" + }, + { + "name": "doctrine/dbal", + "version": "4.2.3", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "33d2d7fe1269b2301640c44cf2896ea607b30e3e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/33d2d7fe1269b2301640c44cf2896ea607b30e3e", + "reference": "33d2d7fe1269b2301640c44cf2896ea607b30e3e", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^0.5.3|^1", + "php": "^8.1", + "psr/cache": "^1|^2|^3", + "psr/log": "^1|^2|^3" + }, + "require-dev": { + "doctrine/coding-standard": "12.0.0", + "fig/log-test": "^1", + "jetbrains/phpstorm-stubs": "2023.2", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "2.0.3", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "10.5.39", + "slevomat/coding-standard": "8.13.1", + "squizlabs/php_codesniffer": "3.10.2", + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\DBAL\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", + "homepage": "https://www.doctrine-project.org/projects/dbal.html", + "keywords": [ + "abstraction", + "database", + "db2", + "dbal", + "mariadb", + "mssql", + "mysql", + "oci8", + "oracle", + "pdo", + "pgsql", + "postgresql", + "queryobject", + "sasql", + "sql", + "sqlite", + "sqlserver", + "sqlsrv" + ], + "support": { + "issues": "https://github.com/doctrine/dbal/issues", + "source": "https://github.com/doctrine/dbal/tree/4.2.3" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", + "type": "tidelift" + } + ], + "time": "2025-03-07T18:29:05+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "1.1.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38", + "shasum": "" + }, + "require": { + "php": "^7.1 || ^8.0" + }, + "conflict": { + "phpunit/phpunit": "<=7.5 || >=13" + }, + "require-dev": { + "doctrine/coding-standard": "^9 || ^12 || ^13", + "phpstan/phpstan": "1.4.10 || 2.1.11", + "phpstan/phpstan-phpunit": "^1.0 || ^2", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12", + "psr/log": "^1 || ^2 || ^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/1.1.5" + }, + "time": "2025-04-07T20:06:18+00:00" + }, + { + "name": "doctrine/doctrine-bundle", + "version": "2.14.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineBundle.git", + "reference": "ca6a7350b421baf7fbdefbf9f4993292ed18effb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineBundle/zipball/ca6a7350b421baf7fbdefbf9f4993292ed18effb", + "reference": "ca6a7350b421baf7fbdefbf9f4993292ed18effb", + "shasum": "" + }, + "require": { + "doctrine/dbal": "^3.7.0 || ^4.0", + "doctrine/persistence": "^3.1 || ^4", + "doctrine/sql-formatter": "^1.0.1", + "php": "^8.1", + "symfony/cache": "^6.4 || ^7.0", + "symfony/config": "^6.4 || ^7.0", + "symfony/console": "^6.4 || ^7.0", + "symfony/dependency-injection": "^6.4 || ^7.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/doctrine-bridge": "^6.4.3 || ^7.0.3", + "symfony/framework-bundle": "^6.4 || ^7.0", + "symfony/service-contracts": "^2.5 || ^3" + }, + "conflict": { + "doctrine/annotations": ">=3.0", + "doctrine/cache": "< 1.11", + "doctrine/orm": "<2.17 || >=4.0", + "symfony/var-exporter": "< 6.4.1 || 7.0.0", + "twig/twig": "<2.13 || >=3.0 <3.0.4" + }, + "require-dev": { + "doctrine/annotations": "^1 || ^2", + "doctrine/cache": "^1.11 || ^2.0", + "doctrine/coding-standard": "^12", + "doctrine/deprecations": "^1.0", + "doctrine/orm": "^2.17 || ^3.0", + "friendsofphp/proxy-manager-lts": "^1.0", + "phpstan/phpstan": "2.1.1", + "phpstan/phpstan-phpunit": "2.0.3", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^9.6.22", + "psr/log": "^1.1.4 || ^2.0 || ^3.0", + "symfony/doctrine-messenger": "^6.4 || ^7.0", + "symfony/messenger": "^6.4 || ^7.0", + "symfony/phpunit-bridge": "^7.2", + "symfony/property-info": "^6.4 || ^7.0", + "symfony/security-bundle": "^6.4 || ^7.0", + "symfony/stopwatch": "^6.4 || ^7.0", + "symfony/string": "^6.4 || ^7.0", + "symfony/twig-bridge": "^6.4 || ^7.0", + "symfony/validator": "^6.4 || ^7.0", + "symfony/var-exporter": "^6.4.1 || ^7.0.1", + "symfony/web-profiler-bundle": "^6.4 || ^7.0", + "symfony/yaml": "^6.4 || ^7.0", + "twig/twig": "^2.13 || ^3.0.4" + }, + "suggest": { + "doctrine/orm": "The Doctrine ORM integration is optional in the bundle.", + "ext-pdo": "*", + "symfony/web-profiler-bundle": "To use the data collector." + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\DoctrineBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org/" + } + ], + "description": "Symfony DoctrineBundle", + "homepage": "https://www.doctrine-project.org", "keywords": [ "database", - "driver", - "mongodb", + "dbal", + "orm", "persistence" ], "support": { - "issues": "https://github.com/mongodb/mongo-php-library/issues", - "source": "https://github.com/mongodb/mongo-php-library/tree/2.1.0" + "issues": "https://github.com/doctrine/DoctrineBundle/issues", + "source": "https://github.com/doctrine/DoctrineBundle/tree/2.14.0" }, - "time": "2025-05-23T10:48:05+00:00" + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-bundle", + "type": "tidelift" + } + ], + "time": "2025-03-22T17:28:21+00:00" + }, + { + "name": "doctrine/doctrine-migrations-bundle", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/DoctrineMigrationsBundle.git", + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/DoctrineMigrationsBundle/zipball/5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "reference": "5a6ac7120c2924c4c070a869d08b11ccf9e277b9", + "shasum": "" + }, + "require": { + "doctrine/doctrine-bundle": "^2.4", + "doctrine/migrations": "^3.2", + "php": "^7.2 || ^8.0", + "symfony/deprecation-contracts": "^2.1 || ^3", + "symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "composer/semver": "^3.0", + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.6 || ^3", + "phpstan/phpstan": "^1.4 || ^2", + "phpstan/phpstan-deprecation-rules": "^1 || ^2", + "phpstan/phpstan-phpunit": "^1 || ^2", + "phpstan/phpstan-strict-rules": "^1.1 || ^2", + "phpstan/phpstan-symfony": "^1.3 || ^2", + "phpunit/phpunit": "^8.5 || ^9.5", + "symfony/phpunit-bridge": "^6.3 || ^7", + "symfony/var-exporter": "^5.4 || ^6 || ^7" + }, + "type": "symfony-bundle", + "autoload": { + "psr-4": { + "Doctrine\\Bundle\\MigrationsBundle\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Doctrine Project", + "homepage": "https://www.doctrine-project.org" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony DoctrineMigrationsBundle", + "homepage": "https://www.doctrine-project.org", + "keywords": [ + "dbal", + "migrations", + "schema" + ], + "support": { + "issues": "https://github.com/doctrine/DoctrineMigrationsBundle/issues", + "source": "https://github.com/doctrine/DoctrineMigrationsBundle/tree/3.4.2" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdoctrine-migrations-bundle", + "type": "tidelift" + } + ], + "time": "2025-03-11T17:36:26+00:00" + }, + { + "name": "doctrine/event-manager", + "version": "2.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/event-manager.git", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", + "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "conflict": { + "doctrine/common": "<2.9" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.8.8", + "phpunit/phpunit": "^10.5", + "vimeo/psalm": "^5.24" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", + "homepage": "https://www.doctrine-project.org/projects/event-manager.html", + "keywords": [ + "event", + "event dispatcher", + "event manager", + "event system", + "events" + ], + "support": { + "issues": "https://github.com/doctrine/event-manager/issues", + "source": "https://github.com/doctrine/event-manager/tree/2.0.1" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", + "type": "tidelift" + } + ], + "time": "2024-05-22T20:47:39+00:00" + }, + { + "name": "doctrine/migrations", + "version": "3.9.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/migrations.git", + "reference": "325b61e41d032f5f7d7e2d11cbefff656eadc9ab" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/migrations/zipball/325b61e41d032f5f7d7e2d11cbefff656eadc9ab", + "reference": "325b61e41d032f5f7d7e2d11cbefff656eadc9ab", + "shasum": "" + }, + "require": { + "composer-runtime-api": "^2", + "doctrine/dbal": "^3.6 || ^4", + "doctrine/deprecations": "^0.5.3 || ^1", + "doctrine/event-manager": "^1.2 || ^2.0", + "php": "^8.1", + "psr/log": "^1.1.3 || ^2 || ^3", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0", + "symfony/var-exporter": "^6.2 || ^7.0" + }, + "conflict": { + "doctrine/orm": "<2.12 || >=4" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "doctrine/orm": "^2.13 || ^3", + "doctrine/persistence": "^2 || ^3 || ^4", + "doctrine/sql-formatter": "^1.0", + "ext-pdo_sqlite": "*", + "fig/log-test": "^1", + "phpstan/phpstan": "^1.10", + "phpstan/phpstan-deprecation-rules": "^1.1", + "phpstan/phpstan-phpunit": "^1.3", + "phpstan/phpstan-strict-rules": "^1.4", + "phpstan/phpstan-symfony": "^1.3", + "phpunit/phpunit": "^10.3", + "symfony/cache": "^5.4 || ^6.0 || ^7.0", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "doctrine/sql-formatter": "Allows to generate formatted SQL with the diff command.", + "symfony/yaml": "Allows the use of yaml for migration configuration files." + }, + "bin": [ + "bin/doctrine-migrations" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Migrations\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Michael Simonson", + "email": "contact@mikesimonson.com" + } + ], + "description": "PHP Doctrine Migrations project offer additional functionality on top of the database abstraction layer (DBAL) for versioning your database schema and easily deploying changes to it. It is a very easy to use and a powerful tool.", + "homepage": "https://www.doctrine-project.org/projects/migrations.html", + "keywords": [ + "database", + "dbal", + "migrations" + ], + "support": { + "issues": "https://github.com/doctrine/migrations/issues", + "source": "https://github.com/doctrine/migrations/tree/3.9.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fmigrations", + "type": "tidelift" + } + ], + "time": "2025-03-26T06:48:45+00:00" + }, + { + "name": "doctrine/persistence", + "version": "4.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/persistence.git", + "reference": "45004aca79189474f113cbe3a53847c2115a55fa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/persistence/zipball/45004aca79189474f113cbe3a53847c2115a55fa", + "reference": "45004aca79189474f113cbe3a53847c2115a55fa", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^1 || ^2", + "php": "^8.1", + "psr/cache": "^1.0 || ^2.0 || ^3.0" + }, + "conflict": { + "doctrine/common": "<2.10" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "1.12.7", + "phpstan/phpstan-phpunit": "^1", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^9.6", + "symfony/cache": "^4.4 || ^5.4 || ^6.0 || ^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Persistence\\": "src/Persistence" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + }, + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com" + } + ], + "description": "The Doctrine Persistence project is a set of shared interfaces and functionality that the different Doctrine object mappers share.", + "homepage": "https://www.doctrine-project.org/projects/persistence.html", + "keywords": [ + "mapper", + "object", + "odm", + "orm", + "persistence" + ], + "support": { + "issues": "https://github.com/doctrine/persistence/issues", + "source": "https://github.com/doctrine/persistence/tree/4.0.0" + }, + "funding": [ + { + "url": "https://www.doctrine-project.org/sponsorship.html", + "type": "custom" + }, + { + "url": "https://www.patreon.com/phpdoctrine", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fpersistence", + "type": "tidelift" + } + ], + "time": "2024-11-01T21:49:07+00:00" + }, + { + "name": "doctrine/sql-formatter", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/sql-formatter.git", + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/sql-formatter/zipball/d6d00aba6fd2957fe5216fe2b7673e9985db20c8", + "reference": "d6d00aba6fd2957fe5216fe2b7673e9985db20c8", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "doctrine/coding-standard": "^12", + "ergebnis/phpunit-slow-test-detector": "^2.14", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5" + }, + "bin": [ + "bin/sql-formatter" + ], + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\SqlFormatter\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Dorn", + "email": "jeremy@jeremydorn.com", + "homepage": "https://jeremydorn.com/" + } + ], + "description": "a PHP SQL highlighting library", + "homepage": "https://github.com/doctrine/sql-formatter/", + "keywords": [ + "highlight", + "sql" + ], + "support": { + "issues": "https://github.com/doctrine/sql-formatter/issues", + "source": "https://github.com/doctrine/sql-formatter/tree/1.5.2" + }, + "time": "2025-01-24T11:45:48+00:00" }, { "name": "psr/cache", @@ -286,24 +995,104 @@ "time": "2024-09-11T13:17:53+00:00" }, { - "name": "symfony/cache", - "version": "v7.2.6", + "name": "symfony/asset-mapper", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/cache.git", - "reference": "8b49dde3f5a5e9867595a3a269977f78418d75ee" + "url": "https://github.com/symfony/asset-mapper.git", + "reference": "6516f38868b75c4902ea72a9fa44967628375ae7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/8b49dde3f5a5e9867595a3a269977f78418d75ee", - "reference": "8b49dde3f5a5e9867595a3a269977f78418d75ee", + "url": "https://api.github.com/repos/symfony/asset-mapper/zipball/6516f38868b75c4902ea72a9fa44967628375ae7", + "reference": "6516f38868b75c4902ea72a9fa44967628375ae7", + "shasum": "" + }, + "require": { + "composer/semver": "^3.0", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/filesystem": "^7.1", + "symfony/http-client": "^6.4|^7.0" + }, + "conflict": { + "symfony/framework-bundle": "<6.4" + }, + "require-dev": { + "symfony/asset": "^6.4|^7.0", + "symfony/browser-kit": "^6.4|^7.0", + "symfony/console": "^6.4|^7.0", + "symfony/event-dispatcher-contracts": "^3.0", + "symfony/finder": "^6.4|^7.0", + "symfony/framework-bundle": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/web-link": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\AssetMapper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Maps directories of assets & makes them available in a public directory with versioned filenames.", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/asset-mapper/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-24T14:05:12+00:00" + }, + { + "name": "symfony/cache", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache.git", + "reference": "c4b217b578c11ec764867aa0c73e602c602965de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache/zipball/c4b217b578c11ec764867aa0c73e602c602965de", + "reference": "c4b217b578c11ec764867aa0c73e602c602965de", "shasum": "" }, "require": { "php": ">=8.2", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", - "symfony/cache-contracts": "^2.5|^3", + "symfony/cache-contracts": "^3.6", "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/service-contracts": "^2.5|^3", "symfony/var-exporter": "^6.4|^7.0" @@ -365,7 +1154,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.2.6" + "source": "https://github.com/symfony/cache/tree/v7.3.0" }, "funding": [ { @@ -381,20 +1170,20 @@ "type": "tidelift" } ], - "time": "2025-04-08T09:06:23+00:00" + "time": "2025-05-06T19:00:13+00:00" }, { "name": "symfony/cache-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/cache-contracts.git", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b" + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", - "reference": "15a4f8e5cd3bce9aeafc882b1acab39ec8de2c1b", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/5d68a57d66910405e5c0b63d6f0af941e66fc868", + "reference": "5d68a57d66910405e5c0b63d6f0af941e66fc868", "shasum": "" }, "require": { @@ -408,7 +1197,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -441,7 +1230,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/cache-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/cache-contracts/tree/v3.6.0" }, "funding": [ { @@ -457,20 +1246,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-03-13T15:25:07+00:00" }, { "name": "symfony/config", - "version": "v7.2.6", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "e0b050b83ba999aa77a3736cb6d5b206d65b9d0d" + "reference": "ba62ae565f1327c2f6366726312ed828c85853bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/e0b050b83ba999aa77a3736cb6d5b206d65b9d0d", - "reference": "e0b050b83ba999aa77a3736cb6d5b206d65b9d0d", + "url": "https://api.github.com/repos/symfony/config/zipball/ba62ae565f1327c2f6366726312ed828c85853bc", + "reference": "ba62ae565f1327c2f6366726312ed828c85853bc", "shasum": "" }, "require": { @@ -516,7 +1305,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.2.6" + "source": "https://github.com/symfony/config/tree/v7.3.0" }, "funding": [ { @@ -532,20 +1321,20 @@ "type": "tidelift" } ], - "time": "2025-04-03T21:14:15+00:00" + "time": "2025-05-15T09:04:05+00:00" }, { "name": "symfony/console", - "version": "v7.2.6", + "version": "v7.2.7", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218" + "reference": "56d7d02c6a6a549a3cd09d5233429100c44270c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0e2e3f38c192e93e622e41ec37f4ca70cfedf218", - "reference": "0e2e3f38c192e93e622e41ec37f4ca70cfedf218", + "url": "https://api.github.com/repos/symfony/console/zipball/56d7d02c6a6a549a3cd09d5233429100c44270c0", + "reference": "56d7d02c6a6a549a3cd09d5233429100c44270c0", "shasum": "" }, "require": { @@ -609,7 +1398,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.2.6" + "source": "https://github.com/symfony/console/tree/v7.2.7" }, "funding": [ { @@ -625,20 +1414,20 @@ "type": "tidelift" } ], - "time": "2025-04-07T19:09:28+00:00" + "time": "2025-05-07T07:45:33+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.2.6", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "2ca85496cde37f825bd14f7e3548e2793ca90712" + "reference": "f64a8f3fa7d4ad5e85de1b128a0e03faed02b732" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/2ca85496cde37f825bd14f7e3548e2793ca90712", - "reference": "2ca85496cde37f825bd14f7e3548e2793ca90712", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/f64a8f3fa7d4ad5e85de1b128a0e03faed02b732", + "reference": "f64a8f3fa7d4ad5e85de1b128a0e03faed02b732", "shasum": "" }, "require": { @@ -689,7 +1478,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.2.6" + "source": "https://github.com/symfony/dependency-injection/tree/v7.3.0" }, "funding": [ { @@ -705,20 +1494,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:37:55+00:00" + "time": "2025-05-19T13:28:56+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", - "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", "shasum": "" }, "require": { @@ -731,7 +1520,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -756,7 +1545,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" }, "funding": [ { @@ -772,7 +1561,116 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/doctrine-bridge", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/doctrine-bridge.git", + "reference": "1df0cb5ce77ddfa0bdbca410009e3822567a6a19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/1df0cb5ce77ddfa0bdbca410009e3822567a6a19", + "reference": "1df0cb5ce77ddfa0bdbca410009e3822567a6a19", + "shasum": "" + }, + "require": { + "doctrine/event-manager": "^2", + "doctrine/persistence": "^3.1|^4", + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "doctrine/collections": "<1.8", + "doctrine/dbal": "<3.6", + "doctrine/lexer": "<1.1", + "doctrine/orm": "<2.15", + "symfony/cache": "<6.4", + "symfony/dependency-injection": "<6.4", + "symfony/form": "<6.4.6|>=7,<7.0.6", + "symfony/http-foundation": "<6.4", + "symfony/http-kernel": "<6.4", + "symfony/lock": "<6.4", + "symfony/messenger": "<6.4", + "symfony/property-info": "<6.4", + "symfony/security-bundle": "<6.4", + "symfony/security-core": "<6.4", + "symfony/validator": "<6.4" + }, + "require-dev": { + "doctrine/collections": "^1.8|^2.0", + "doctrine/data-fixtures": "^1.1|^2", + "doctrine/dbal": "^3.6|^4", + "doctrine/orm": "^2.15|^3", + "psr/log": "^1|^2|^3", + "symfony/cache": "^6.4|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/doctrine-messenger": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/form": "^6.4.6|^7.0.6", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/property-access": "^6.4|^7.0", + "symfony/property-info": "^6.4|^7.0", + "symfony/security-core": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/translation": "^6.4|^7.0", + "symfony/type-info": "^7.1", + "symfony/uid": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "symfony-bridge", + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Doctrine\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides integration for Doctrine with various Symfony components", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/doctrine-bridge/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-25T10:32:38+00:00" }, { "name": "symfony/dotenv", @@ -850,16 +1748,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b" + "reference": "cf68d225bc43629de4ff54778029aee6dc191b83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", - "reference": "102be5e6a8e4f4f3eb3149bcbfa33a80d1ee374b", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/cf68d225bc43629de4ff54778029aee6dc191b83", + "reference": "cf68d225bc43629de4ff54778029aee6dc191b83", "shasum": "" }, "require": { @@ -872,9 +1770,11 @@ "symfony/http-kernel": "<6.4" }, "require-dev": { + "symfony/console": "^6.4|^7.0", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^6.4|^7.0", + "symfony/webpack-encore-bundle": "^1.0|^2.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -905,7 +1805,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.2.5" + "source": "https://github.com/symfony/error-handler/tree/v7.3.0" }, "funding": [ { @@ -921,20 +1821,20 @@ "type": "tidelift" } ], - "time": "2025-03-03T07:12:39+00:00" + "time": "2025-05-29T07:19:49+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", - "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/497f73ac996a598c92409b44ac43b6690c4f666d", + "reference": "497f73ac996a598c92409b44ac43b6690c4f666d", "shasum": "" }, "require": { @@ -985,7 +1885,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.3.0" }, "funding": [ { @@ -1001,20 +1901,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2025-04-22T09:11:45+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" + "reference": "59eb412e93815df44f05f342958efa9f46b1e586" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", - "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/59eb412e93815df44f05f342958efa9f46b1e586", + "reference": "59eb412e93815df44f05f342958efa9f46b1e586", "shasum": "" }, "require": { @@ -1028,7 +1928,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -1061,7 +1961,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.6.0" }, "funding": [ { @@ -1077,11 +1977,11 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/filesystem", - "version": "v7.2.0", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -1127,7 +2027,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.2.0" + "source": "https://github.com/symfony/filesystem/tree/v7.3.0" }, "funding": [ { @@ -1147,16 +2047,16 @@ }, { "name": "symfony/finder", - "version": "v7.2.2", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb" + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/87a71856f2f56e4100373e92529eed3171695cfb", - "reference": "87a71856f2f56e4100373e92529eed3171695cfb", + "url": "https://api.github.com/repos/symfony/finder/zipball/ec2344cf77a48253bbca6939aa3d2477773ea63d", + "reference": "ec2344cf77a48253bbca6939aa3d2477773ea63d", "shasum": "" }, "require": { @@ -1191,7 +2091,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.2.2" + "source": "https://github.com/symfony/finder/tree/v7.3.0" }, "funding": [ { @@ -1207,20 +2107,20 @@ "type": "tidelift" } ], - "time": "2024-12-30T19:00:17+00:00" + "time": "2024-12-30T19:00:26+00:00" }, { "name": "symfony/flex", - "version": "v2.7.0", + "version": "v2.7.1", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "5d743b3b78fabe9f3146586d77b0a1f9292851fc" + "reference": "4ae50d368415a06820739e54d38a4a29d6df9155" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/5d743b3b78fabe9f3146586d77b0a1f9292851fc", - "reference": "5d743b3b78fabe9f3146586d77b0a1f9292851fc", + "url": "https://api.github.com/repos/symfony/flex/zipball/4ae50d368415a06820739e54d38a4a29d6df9155", + "reference": "4ae50d368415a06820739e54d38a4a29d6df9155", "shasum": "" }, "require": { @@ -1259,7 +2159,7 @@ "description": "Composer plugin for Symfony", "support": { "issues": "https://github.com/symfony/flex/issues", - "source": "https://github.com/symfony/flex/tree/v2.7.0" + "source": "https://github.com/symfony/flex/tree/v2.7.1" }, "funding": [ { @@ -1275,20 +2175,20 @@ "type": "tidelift" } ], - "time": "2025-05-23T11:41:40+00:00" + "time": "2025-05-28T14:22:54+00:00" }, { "name": "symfony/framework-bundle", - "version": "v7.2.5", + "version": "v7.2.7", "source": { "type": "git", "url": "https://github.com/symfony/framework-bundle.git", - "reference": "c1c6ee8946491b698b067df2258e07918c25da02" + "reference": "7627f005de900e19fde7199247fba325690b0407" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/c1c6ee8946491b698b067df2258e07918c25da02", - "reference": "c1c6ee8946491b698b067df2258e07918c25da02", + "url": "https://api.github.com/repos/symfony/framework-bundle/zipball/7627f005de900e19fde7199247fba325690b0407", + "reference": "7627f005de900e19fde7199247fba325690b0407", "shasum": "" }, "require": { @@ -1409,7 +2309,7 @@ "description": "Provides a tight integration between Symfony components and the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/framework-bundle/tree/v7.2.5" + "source": "https://github.com/symfony/framework-bundle/tree/v7.2.7" }, "funding": [ { @@ -1425,20 +2325,193 @@ "type": "tidelift" } ], - "time": "2025-03-24T12:37:32+00:00" + "time": "2025-05-16T15:49:05+00:00" }, { - "name": "symfony/http-foundation", - "version": "v7.2.6", + "name": "symfony/http-client", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/http-foundation.git", - "reference": "6023ec7607254c87c5e69fb3558255aca440d72b" + "url": "https://github.com/symfony/http-client.git", + "reference": "57e4fb86314015a695a750ace358d07a7e37b8a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/6023ec7607254c87c5e69fb3558255aca440d72b", - "reference": "6023ec7607254c87c5e69fb3558255aca440d72b", + "url": "https://api.github.com/repos/symfony/http-client/zipball/57e4fb86314015a695a750ace358d07a7e37b8a9", + "reference": "57e4fb86314015a695a750ace358d07a7e37b8a9", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/http-client-contracts": "~3.4.4|^3.5.2", + "symfony/service-contracts": "^2.5|^3" + }, + "conflict": { + "amphp/amp": "<2.5", + "php-http/discovery": "<1.15", + "symfony/http-foundation": "<6.4" + }, + "provide": { + "php-http/async-client-implementation": "*", + "php-http/client-implementation": "*", + "psr/http-client-implementation": "1.0", + "symfony/http-client-implementation": "3.0" + }, + "require-dev": { + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", + "amphp/socket": "^1.1", + "guzzlehttp/promises": "^1.4|^2.0", + "nyholm/psr7": "^1.0", + "php-http/httplug": "^1.0|^2.0", + "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously", + "homepage": "https://symfony.com", + "keywords": [ + "http" + ], + "support": { + "source": "https://github.com/symfony/http-client/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-05-02T08:23:16+00:00" + }, + { + "name": "symfony/http-client-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-client-contracts.git", + "reference": "75d7043853a42837e68111812f4d964b01e5101c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", + "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\HttpClient\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to HTTP clients", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-04-29T11:18:49+00:00" + }, + { + "name": "symfony/http-foundation", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "4236baf01609667d53b20371486228231eb135fd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/4236baf01609667d53b20371486228231eb135fd", + "reference": "4236baf01609667d53b20371486228231eb135fd", "shasum": "" }, "require": { @@ -1455,6 +2528,7 @@ "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", "symfony/cache": "^6.4.12|^7.1.5", + "symfony/clock": "^6.4|^7.0", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -1487,7 +2561,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.2.6" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.0" }, "funding": [ { @@ -1503,20 +2577,20 @@ "type": "tidelift" } ], - "time": "2025-04-09T08:14:01+00:00" + "time": "2025-05-12T14:48:23+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.2.6", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec" + "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f9dec01e6094a063e738f8945ef69c0cfcf792ec", - "reference": "f9dec01e6094a063e738f8945ef69c0cfcf792ec", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/ac7b8e163e8c83dce3abcc055a502d4486051a9f", + "reference": "ac7b8e163e8c83dce3abcc055a502d4486051a9f", "shasum": "" }, "require": { @@ -1524,8 +2598,8 @@ "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/event-dispatcher": "^7.3", + "symfony/http-foundation": "^7.3", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -1601,7 +2675,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.2.6" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.0" }, "funding": [ { @@ -1617,7 +2691,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T09:04:03+00:00" + "time": "2025-05-29T07:47:32+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2014,94 +3088,18 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php85", - "version": "v1.32.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php85.git", - "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php85/zipball/6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", - "reference": "6fedf31ce4e3648f4ff5ca58bfd53127d38f05fd", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php85\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.5+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php85/tree/v1.32.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-05-02T08:40:52+00:00" - }, { "name": "symfony/routing", - "version": "v7.2.3", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996" + "reference": "8e213820c5fea844ecea29203d2a308019007c15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ee9a67edc6baa33e5fae662f94f91fd262930996", - "reference": "ee9a67edc6baa33e5fae662f94f91fd262930996", + "url": "https://api.github.com/repos/symfony/routing/zipball/8e213820c5fea844ecea29203d2a308019007c15", + "reference": "8e213820c5fea844ecea29203d2a308019007c15", "shasum": "" }, "require": { @@ -2153,7 +3151,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.2.3" + "source": "https://github.com/symfony/routing/tree/v7.3.0" }, "funding": [ { @@ -2169,20 +3167,20 @@ "type": "tidelift" } ], - "time": "2025-01-17T10:56:55+00:00" + "time": "2025-05-24T20:43:28+00:00" }, { "name": "symfony/runtime", - "version": "v7.2.3", + "version": "v7.2.7", "source": { "type": "git", "url": "https://github.com/symfony/runtime.git", - "reference": "8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad" + "reference": "91a630dc8ccd123f833a2f0508c52744af0407c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/runtime/zipball/8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad", - "reference": "8e8d09bd69b7f6c0260dd3d58f37bd4fbdeab5ad", + "url": "https://api.github.com/repos/symfony/runtime/zipball/91a630dc8ccd123f833a2f0508c52744af0407c5", + "reference": "91a630dc8ccd123f833a2f0508c52744af0407c5", "shasum": "" }, "require": { @@ -2232,7 +3230,7 @@ "runtime" ], "support": { - "source": "https://github.com/symfony/runtime/tree/v7.2.3" + "source": "https://github.com/symfony/runtime/tree/v7.2.7" }, "funding": [ { @@ -2248,20 +3246,20 @@ "type": "tidelift" } ], - "time": "2024-12-29T21:39:47+00:00" + "time": "2025-05-08T06:52:38+00:00" }, { "name": "symfony/serializer", - "version": "v7.2.6", + "version": "v7.2.7", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "be549655b034edc1a16ed23d8164aa04318c5ec1" + "reference": "33734cd7b431cb426b784305eafc8bf507e8e19d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/be549655b034edc1a16ed23d8164aa04318c5ec1", - "reference": "be549655b034edc1a16ed23d8164aa04318c5ec1", + "url": "https://api.github.com/repos/symfony/serializer/zipball/33734cd7b431cb426b784305eafc8bf507e8e19d", + "reference": "33734cd7b431cb426b784305eafc8bf507e8e19d", "shasum": "" }, "require": { @@ -2330,7 +3328,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.2.6" + "source": "https://github.com/symfony/serializer/tree/v7.2.7" }, "funding": [ { @@ -2346,20 +3344,20 @@ "type": "tidelift" } ], - "time": "2025-04-27T13:34:41+00:00" + "time": "2025-05-12T14:48:02+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", - "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", "shasum": "" }, "require": { @@ -2377,7 +3375,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -2413,7 +3411,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" }, "funding": [ { @@ -2429,20 +3427,82 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2025-04-25T09:37:31+00:00" }, { - "name": "symfony/string", - "version": "v7.2.6", + "name": "symfony/stopwatch", + "version": "v7.3.0", "source": { "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931" + "url": "https://github.com/symfony/stopwatch.git", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/a214fe7d62bd4df2a76447c67c6b26e1d5e74931", - "reference": "a214fe7d62bd4df2a76447c67c6b26e1d5e74931", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "reference": "5a49289e2b308214c8b9c2fda4ea454d8b8ad7cd", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v7.3.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-02-24T10:49:57+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/f3570b8c61ca887a9e2938e85cb6458515d2b125", + "reference": "f3570b8c61ca887a9e2938e85cb6458515d2b125", "shasum": "" }, "require": { @@ -2500,7 +3560,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.2.6" + "source": "https://github.com/symfony/string/tree/v7.3.0" }, "funding": [ { @@ -2516,20 +3576,20 @@ "type": "tidelift" } ], - "time": "2025-04-20T20:18:16+00:00" + "time": "2025-04-20T20:19:01+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.1", + "version": "v3.6.0", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", - "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/df210c7a2573f1913b2d17cc95f90f53a73d8f7d", + "reference": "df210c7a2573f1913b2d17cc95f90f53a73d8f7d", "shasum": "" }, "require": { @@ -2542,7 +3602,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.5-dev" + "dev-main": "3.6-dev" } }, "autoload": { @@ -2578,7 +3638,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" + "source": "https://github.com/symfony/translation-contracts/tree/v3.6.0" }, "funding": [ { @@ -2594,27 +3654,27 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-27T08:32:26+00:00" }, { "name": "symfony/twig-bridge", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "b1942d5515b7f0a18e16fd668a04ea952db2b0f2" + "reference": "082eb15d8a4f9afee0acc4709fbe3aaf26d48891" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/b1942d5515b7f0a18e16fd668a04ea952db2b0f2", - "reference": "b1942d5515b7f0a18e16fd668a04ea952db2b0f2", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/082eb15d8a4f9afee0acc4709fbe3aaf26d48891", + "reference": "082eb15d8a4f9afee0acc4709fbe3aaf26d48891", "shasum": "" }, "require": { "php": ">=8.2", "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.12" + "twig/twig": "^3.21" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", @@ -2641,7 +3701,7 @@ "symfony/finder": "^6.4|^7.0", "symfony/form": "^6.4.20|^7.2.5", "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-foundation": "^7.3", "symfony/http-kernel": "^6.4|^7.0", "symfony/intl": "^6.4|^7.0", "symfony/mime": "^6.4|^7.0", @@ -2655,12 +3715,13 @@ "symfony/serializer": "^6.4.3|^7.0.3", "symfony/stopwatch": "^6.4|^7.0", "symfony/translation": "^6.4|^7.0", + "symfony/validator": "^6.4|^7.0", "symfony/web-link": "^6.4|^7.0", "symfony/workflow": "^6.4|^7.0", "symfony/yaml": "^6.4|^7.0", - "twig/cssinliner-extra": "^2.12|^3", - "twig/inky-extra": "^2.12|^3", - "twig/markdown-extra": "^2.12|^3" + "twig/cssinliner-extra": "^3", + "twig/inky-extra": "^3", + "twig/markdown-extra": "^3" }, "type": "symfony-bridge", "autoload": { @@ -2688,7 +3749,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.2.5" + "source": "https://github.com/symfony/twig-bridge/tree/v7.3.0" }, "funding": [ { @@ -2704,7 +3765,7 @@ "type": "tidelift" } ], - "time": "2025-03-28T13:15:09+00:00" + "time": "2025-05-19T13:28:56+00:00" }, { "name": "symfony/twig-bundle", @@ -2792,16 +3853,16 @@ }, { "name": "symfony/validator", - "version": "v7.2.6", + "version": "v7.2.7", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "f7c32e309885a97fc9572335e22c2c2d31f328c4" + "reference": "39d26103ee9a32ad2ee2b6931a833384fb021e4e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/f7c32e309885a97fc9572335e22c2c2d31f328c4", - "reference": "f7c32e309885a97fc9572335e22c2c2d31f328c4", + "url": "https://api.github.com/repos/symfony/validator/zipball/39d26103ee9a32ad2ee2b6931a833384fb021e4e", + "reference": "39d26103ee9a32ad2ee2b6931a833384fb021e4e", "shasum": "" }, "require": { @@ -2869,7 +3930,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.2.6" + "source": "https://github.com/symfony/validator/tree/v7.2.7" }, "funding": [ { @@ -2885,7 +3946,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T08:36:00+00:00" + "time": "2025-05-29T07:19:28+00:00" }, { "name": "symfony/var-dumper", @@ -2972,20 +4033,21 @@ }, { "name": "symfony/var-exporter", - "version": "v7.2.6", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "422b8de94c738830a1e071f59ad14d67417d7007" + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/422b8de94c738830a1e071f59ad14d67417d7007", - "reference": "422b8de94c738830a1e071f59ad14d67417d7007", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/c9a1168891b5aaadfd6332ef44393330b3498c4c", + "reference": "c9a1168891b5aaadfd6332ef44393330b3498c4c", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { "symfony/property-access": "^6.4|^7.0", @@ -3028,7 +4090,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.2.6" + "source": "https://github.com/symfony/var-exporter/tree/v7.3.0" }, "funding": [ { @@ -3044,7 +4106,7 @@ "type": "tidelift" } ], - "time": "2025-05-02T08:36:00+00:00" + "time": "2025-05-15T09:04:05+00:00" }, { "name": "symfony/yaml", @@ -3516,16 +4578,16 @@ }, { "name": "symfony/process", - "version": "v7.2.5", + "version": "v7.3.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d" + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/87b7c93e57df9d8e39a093d32587702380ff045d", - "reference": "87b7c93e57df9d8e39a093d32587702380ff045d", + "url": "https://api.github.com/repos/symfony/process/zipball/40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", + "reference": "40c295f2deb408d5e9d2d32b8ba1dd61e36f05af", "shasum": "" }, "require": { @@ -3557,7 +4619,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.2.5" + "source": "https://github.com/symfony/process/tree/v7.3.0" }, "funding": [ { @@ -3573,19 +4635,21 @@ "type": "tidelift" } ], - "time": "2025-03-13T12:21:46+00:00" + "time": "2025-04-17T09:11:12+00:00" } ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.3", "ext-ctype": "*", - "ext-iconv": "*" + "ext-iconv": "*", + "ext-pdo": "*", + "ext-pgsql": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/config/bundles.php b/config/bundles.php index 44fcc95..7d41ca9 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -5,4 +5,6 @@ return [ Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], + Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], + Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true], ]; diff --git a/config/packages/asset_mapper.yaml b/config/packages/asset_mapper.yaml new file mode 100644 index 0000000..f7653e9 --- /dev/null +++ b/config/packages/asset_mapper.yaml @@ -0,0 +1,11 @@ +framework: + asset_mapper: + # The paths to make available to the asset mapper. + paths: + - assets/ + missing_import_mode: strict + +when@prod: + framework: + asset_mapper: + missing_import_mode: warn diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml new file mode 100644 index 0000000..e5a4d68 --- /dev/null +++ b/config/packages/doctrine.yaml @@ -0,0 +1,24 @@ +doctrine: + dbal: + url: '%env(resolve:DATABASE_URL)%' + + # IMPORTANT: You MUST configure your server version, + # either here or in the DATABASE_URL env var (see .env file) + #server_version: '16' + + profiling_collect_backtrace: '%kernel.debug%' + use_savepoints: true + +when@test: + doctrine: + dbal: + # "TEST_TOKEN" is typically set by ParaTest + dbname_suffix: '_test%env(default::TEST_TOKEN)%' + + framework: + cache: + pools: + doctrine.result_cache_pool: + adapter: cache.app + doctrine.system_cache_pool: + adapter: cache.system diff --git a/config/packages/doctrine_migrations.yaml b/config/packages/doctrine_migrations.yaml new file mode 100644 index 0000000..29231d9 --- /dev/null +++ b/config/packages/doctrine_migrations.yaml @@ -0,0 +1,6 @@ +doctrine_migrations: + migrations_paths: + # namespace is arbitrary but should be different from App\Migrations + # as migrations classes should NOT be autoloaded + 'DoctrineMigrations': '%kernel.project_dir%/migrations' + enable_profiler: false diff --git a/config/services.yaml b/config/services.yaml index 189864f..515d34b 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -9,10 +9,5 @@ services: resource: '../src/' exclude: - '../src/DependencyInjection/' - - '../src/Document/' - - '../src/Kernel.php' - - App\Infrastructure\MongoDB\MongoDBClient: - arguments: - $dsl: '%env(MONGODB_DSL)%' - $databaseName: '%env(MONGODB_DATABASE)%' \ No newline at end of file + - '../src/Entity/' + - '../src/Kernel.php' \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 2fbdfb1..d26ad55 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,3 @@ -version: '3' - services: app: build: @@ -9,9 +7,7 @@ services: volumes: - .:/app depends_on: - - mongodb - environment: - - MONGODB_URI=mongodb://mongodb:27017/evwiki + - database labels: - "traefik.enable=true" - "traefik.http.routers.app.entrypoints=web" @@ -20,41 +16,44 @@ services: networks: - proxy - mongodb: - image: mongo:latest - hostname: mongodb.evwiki.test - volumes: - - mongodb_data:/data/db - networks: - - proxy - labels: - - "traefik.enable=true" - - "traefik.http.routers.mongodb.entrypoints=mongodb" - - "traefik.http.routers.mongodb.rule=Host(`mongodb.evwiki.test`)" - - "traefik.http.services.mongodb.loadbalancer.server.port=27017" - - mongo-express: - image: mongo-express:latest - hostname: mongo-express.evwiki.test - depends_on: - - mongodb + database: + image: postgres:17-alpine + hostname: database.evwiki.test environment: - - ME_CONFIG_MONGODB_SERVER=mongodb - - ME_CONFIG_MONGODB_PORT=27017 - - ME_CONFIG_MONGODB_ENABLE_ADMIN=true - - ME_CONFIG_BASICAUTH_USERNAME=admin - - ME_CONFIG_BASICAUTH_PASSWORD=pass - networks: - - proxy + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres + - POSTGRES_DB=evwiki + volumes: + - postgres_evwiki:/var/lib/postgresql/data labels: - "traefik.enable=true" - - "traefik.http.routers.mongo-express.entrypoints=web" - - "traefik.http.routers.mongo-express.rule=Host(`mongo-express.evwiki.test`)" - - "traefik.http.services.mongo-express.loadbalancer.server.port=8081" + - "traefik.tcp.routers.database.entrypoints=postgres" + - "traefik.tcp.routers.database.rule=HostSNI(`database.evwiki.test`)" + - "traefik.tcp.routers.database.tls=true" + - "traefik.tcp.routers.database.tls.passthrough=true" + - "traefik.tcp.services.database.loadbalancer.server.port=5432" + networks: + - proxy + + adminer: + image: adminer + hostname: adminer.evwiki.test + environment: + - ADMINER_DEFAULT_SERVER=database.evwiki.test + - ADMINER_DEFAULT_DB=evwiki + - ADMINER_DESIGN=darkly + - ADMINER_USER=postgres_evwiki + - ADMINER_PASSWORD=postgres_evwiki + labels: + - "traefik.enable=true" + - "traefik.http.routers.adminer.rule=Host(`adminer.evwiki.test`)" + - "traefik.http.services.adminer.loadbalancer.server.port=8080" + networks: + - proxy networks: proxy: external: true volumes: - mongodb_data: \ No newline at end of file + postgres_evwiki: \ No newline at end of file diff --git a/importmap.php b/importmap.php new file mode 100644 index 0000000..e6c879c --- /dev/null +++ b/importmap.php @@ -0,0 +1,38 @@ + [ + 'path' => './assets/app.js', + 'entrypoint' => true, + ], + '@fortawesome/fontawesome-free' => [ + 'version' => '6.7.2', + ], + '@fortawesome/fontawesome-free/css/fontawesome.min.css' => [ + 'version' => '6.7.2', + 'type' => 'css', + ], + '@fortawesome/fontawesome-free/css/solid.min.css' => [ + 'version' => '6.7.2', + 'type' => 'css', + ], + '@fortawesome/fontawesome-free/css/brands.min.css' => [ + 'version' => '6.7.2', + 'type' => 'css', + ], + '@fortawesome/fontawesome-free/css/regular.min.css' => [ + 'version' => '6.7.2', + 'type' => 'css', + ], +]; diff --git a/migrations/Version20250529155930.php b/migrations/Version20250529155930.php new file mode 100644 index 0000000..63438df --- /dev/null +++ b/migrations/Version20250529155930.php @@ -0,0 +1,57 @@ +addSql(<<addSql(<<addSql(<<addSql('DROP TABLE car_revisions'); + $this->addSql('DROP TABLE car_models'); + $this->addSql('DROP TABLE brands'); + } +} diff --git a/src/Application/Commands/LoadFixtures.php b/src/Application/Commands/LoadFixtures.php new file mode 100644 index 0000000..bf533bc --- /dev/null +++ b/src/Application/Commands/LoadFixtures.php @@ -0,0 +1,513 @@ +title('Loading EV Wiki Fixtures'); + + // Load brands + $brands = $this->getFixtureBrands(); + $io->section('Loading Brands'); + $io->progressStart(count($brands)); + + foreach ($brands as $brand) { + $persistedBrand = $this->brandRepository->create($brand); + $this->brandIds[$brand->name] = $persistedBrand->id; + $io->progressAdvance(); + } + + $io->progressFinish(); + $io->success(sprintf('Successfully loaded %d brands', count($brands))); + + // Load car models + $carModels = $this->getFixtureCarModels(); + $io->section('Loading Car Models'); + $io->progressStart(count($carModels)); + + foreach ($carModels as $carModelData) { + $persistedCarModel = $this->carModelRepository->create( + $carModelData['model'], + $this->brandIds[$carModelData['brand']] + ); + $this->carModelIds[$carModelData['model']->name] = $persistedCarModel->id; + $io->progressAdvance(); + } + + $io->progressFinish(); + $io->success(sprintf('Successfully loaded %d car models', count($carModels))); + + // Load car revisions + $carRevisions = $this->getFixtureCarRevisions(); + $io->section('Loading Car Revisions'); + $io->progressStart(count($carRevisions)); + + foreach ($carRevisions as $carRevisionData) { + $this->carRevisionRepository->create( + $carRevisionData['revision'], + $this->carModelIds[$carRevisionData['model']] + ); + $io->progressAdvance(); + } + + $io->progressFinish(); + $io->success(sprintf('Successfully loaded %d car revisions', count($carRevisions))); + + $io->success('All fixtures loaded successfully!'); + + return Command::SUCCESS; + } + + /** + * @return Brand[] + */ + private function getFixtureBrands(): array + { + return [ + new Brand( + name: 'Tesla', + logo: 'https://logo.clearbit.com/tesla.com', + description: 'American electric vehicle and clean energy company founded by Elon Musk', + foundedYear: 2003, + headquarters: 'Austin, Texas, USA', + website: 'https://tesla.com', + ), + new Brand( + name: 'BMW', + logo: 'https://logo.clearbit.com/bmw.com', + description: 'German multinational corporation producing luxury vehicles with strong EV lineup', + foundedYear: 1916, + headquarters: 'Munich, Germany', + website: 'https://bmw.com', + ), + new Brand( + name: 'Audi', + logo: 'https://logo.clearbit.com/audi.com', + description: 'German automotive manufacturer of luxury vehicles with e-tron electric series', + foundedYear: 1909, + headquarters: 'Ingolstadt, Germany', + website: 'https://audi.com', + ), + new Brand( + name: 'Mercedes-Benz', + logo: 'https://logo.clearbit.com/mercedes-benz.com', + description: 'German luxury automotive brand with EQS, EQC and other electric models', + foundedYear: 1926, + headquarters: 'Stuttgart, Germany', + website: 'https://mercedes-benz.com', + ), + new Brand( + name: 'Volkswagen', + logo: 'https://logo.clearbit.com/vw.com', + description: 'German motor vehicle manufacturer with ID series electric vehicles', + foundedYear: 1937, + headquarters: 'Wolfsburg, Germany', + website: 'https://vw.com', + ), + new Brand( + name: 'Porsche', + logo: 'https://logo.clearbit.com/porsche.com', + description: 'German sports car manufacturer with Taycan electric sports cars', + foundedYear: 1931, + headquarters: 'Stuttgart, Germany', + website: 'https://porsche.com', + ), + new Brand( + name: 'Lucid Motors', + logo: 'https://logo.clearbit.com/lucidmotors.com', + description: 'American electric vehicle manufacturer focused on luxury sedans', + foundedYear: 2007, + headquarters: 'Newark, California, USA', + website: 'https://lucidmotors.com', + ), + new Brand( + name: 'Rivian', + logo: 'https://logo.clearbit.com/rivian.com', + description: 'American electric vehicle manufacturer focusing on electric trucks and vans', + foundedYear: 2009, + headquarters: 'Irvine, California, USA', + website: 'https://rivian.com', + ), + new Brand( + name: 'NIO', + logo: 'https://logo.clearbit.com/nio.com', + description: 'Chinese electric vehicle manufacturer with innovative battery swapping technology', + foundedYear: 2014, + headquarters: 'Shanghai, China', + website: 'https://nio.com', + ), + new Brand( + name: 'BYD', + logo: 'https://logo.clearbit.com/byd.com', + description: 'Chinese electric vehicle and battery manufacturer, world leader in EV sales', + foundedYear: 1995, + headquarters: 'Shenzhen, China', + website: 'https://byd.com', + ) + ]; + } + + /** + * @return array + */ + private function getFixtureCarModels(): array + { + return [ + // Tesla Models + [ + 'brand' => 'Tesla', + 'model' => new CarModel('Model S'), + ], + [ + 'brand' => 'Tesla', + 'model' => new CarModel('Model 3'), + ], + [ + 'brand' => 'Tesla', + 'model' => new CarModel('Model X'), + ], + [ + 'brand' => 'Tesla', + 'model' => new CarModel('Model Y'), + ], + + // BMW Models + [ + 'brand' => 'BMW', + 'model' => new CarModel('iX'), + ], + [ + 'brand' => 'BMW', + 'model' => new CarModel('i4'), + ], + [ + 'brand' => 'BMW', + 'model' => new CarModel('iX3'), + ], + + // Audi Models + [ + 'brand' => 'Audi', + 'model' => new CarModel('e-tron GT'), + ], + [ + 'brand' => 'Audi', + 'model' => new CarModel('Q4 e-tron'), + ], + [ + 'brand' => 'Audi', + 'model' => new CarModel('e-tron'), + ], + + // Mercedes-Benz Models + [ + 'brand' => 'Mercedes-Benz', + 'model' => new CarModel('EQS'), + ], + [ + 'brand' => 'Mercedes-Benz', + 'model' => new CarModel('EQC'), + ], + [ + 'brand' => 'Mercedes-Benz', + 'model' => new CarModel('EQA'), + ], + + // Volkswagen Models + [ + 'brand' => 'Volkswagen', + 'model' => new CarModel('ID.4'), + ], + [ + 'brand' => 'Volkswagen', + 'model' => new CarModel('ID.3'), + ], + [ + 'brand' => 'Volkswagen', + 'model' => new CarModel('ID.Buzz'), + ], + + // Porsche Models + [ + 'brand' => 'Porsche', + 'model' => new CarModel('Taycan'), + ], + [ + 'brand' => 'Porsche', + 'model' => new CarModel('Macan Electric'), + ], + ]; + } + + /** + * @return array + */ + private function getFixtureCarRevisions(): array + { + return [ + // Tesla Model S Plaid + [ + 'model' => 'Model S', + 'revision' => new CarRevision( + name: 'Plaid', + productionBegin: new Date(1, 1, 2021), + drivingCharacteristics: new DrivingCharacteristics( + power: new Power(750), + acceleration: new Acceleration(2.1), + topSpeed: new Speed(322), + consumption: new Consumption(new Energy(19.3)) + ), + battery: new BatteryProperties( + usableCapacity: new Energy(95.0), + totalCapacity: new Energy(100.0), + cellChemistry: CellChemistry::LithiumNickelManganeseOxide, + model: '4680', + manufacturer: 'Tesla' + ), + chargingProperties: new ChargingProperties( + topChargingSpeed: new Power(250) + ), + rangeProperties: new RangeProperties( + wltp: new WltpRange(new Range(628)), + nefz: new NefzRange(new Range(652)) + ), + catalogPrice: new Price(129990, Currency::euro()), + image: new Image('https://digitalassets.tesla.com/tesla-contents/image/upload/f_auto,q_auto/Model-S-Main-Hero-Desktop-LHD.jpg') + ), + ], + + // Tesla Model 3 Long Range + [ + 'model' => 'Model 3', + 'revision' => new CarRevision( + name: 'Long Range', + productionBegin: new Date(1, 1, 2020), + drivingCharacteristics: new DrivingCharacteristics( + power: new Power(366), + acceleration: new Acceleration(4.4), + topSpeed: new Speed(233), + consumption: new Consumption(new Energy(14.9)) + ), + battery: new BatteryProperties( + usableCapacity: new Energy(75.0), + totalCapacity: new Energy(82.0), + cellChemistry: CellChemistry::LithiumNickelManganeseOxide, + model: '2170', + manufacturer: 'Tesla' + ), + chargingProperties: new ChargingProperties( + topChargingSpeed: new Power(250) + ), + rangeProperties: new RangeProperties( + wltp: new WltpRange(new Range(602)), + nefz: new NefzRange(new Range(614)) + ), + catalogPrice: new Price(49990, Currency::euro()), + image: new Image('https://digitalassets.tesla.com/tesla-contents/image/upload/f_auto,q_auto/Model-3-Main-Hero-Desktop-LHD.jpg') + ), + ], + + // BMW iX xDrive50 + [ + 'model' => 'iX', + 'revision' => new CarRevision( + name: 'xDrive50', + productionBegin: new Date(1, 1, 2021), + drivingCharacteristics: new DrivingCharacteristics( + power: new Power(385), + acceleration: new Acceleration(4.6), + topSpeed: new Speed(200), + consumption: new Consumption(new Energy(19.8)) + ), + battery: new BatteryProperties( + usableCapacity: new Energy(71.2), + totalCapacity: new Energy(76.6), + cellChemistry: CellChemistry::LithiumNickelManganeseOxide, + model: 'BMW Gen5', + manufacturer: 'CATL' + ), + chargingProperties: new ChargingProperties( + topChargingSpeed: new Power(195) + ), + rangeProperties: new RangeProperties( + wltp: new WltpRange(new Range(630)), + nefz: new NefzRange(new Range(680)) + ), + catalogPrice: new Price(77300, Currency::euro()), + image: new Image('https://www.bmw.de/content/dam/bmw/common/all-models/x-series/ix/2021/highlights/bmw-ix-sp-desktop.jpg') + ), + ], + + // Audi e-tron GT RS + [ + 'model' => 'e-tron GT', + 'revision' => new CarRevision( + name: 'RS', + productionBegin: new Date(1, 1, 2021), + drivingCharacteristics: new DrivingCharacteristics( + power: new Power(475), + acceleration: new Acceleration(3.3), + topSpeed: new Speed(250), + consumption: new Consumption(new Energy(19.6)) + ), + battery: new BatteryProperties( + usableCapacity: new Energy(83.7), + totalCapacity: new Energy(93.4), + cellChemistry: CellChemistry::LithiumNickelManganeseOxide, + model: 'PPE Platform', + manufacturer: 'LG Energy Solution' + ), + chargingProperties: new ChargingProperties( + topChargingSpeed: new Power(270) + ), + rangeProperties: new RangeProperties( + wltp: new WltpRange(new Range(472)), + nefz: new NefzRange(new Range(487)) + ), + catalogPrice: new Price(142900, Currency::euro()), + image: new Image('https://www.audi.de/content/dam/nemo/models/e-tron-gt/my-2021/1920x1080-gallery/1920x1080_AudiRS_e-tron_GT_19.jpg') + ), + ], + + // Mercedes EQS 450+ + [ + 'model' => 'EQS', + 'revision' => new CarRevision( + name: '450+', + productionBegin: new Date(1, 1, 2021), + drivingCharacteristics: new DrivingCharacteristics( + power: new Power(245), + acceleration: new Acceleration(6.2), + topSpeed: new Speed(210), + consumption: new Consumption(new Energy(15.7)) + ), + battery: new BatteryProperties( + usableCapacity: new Energy(90.0), + totalCapacity: new Energy(107.8), + cellChemistry: CellChemistry::LithiumNickelManganeseOxide, + model: 'EVA Platform', + manufacturer: 'CATL' + ), + chargingProperties: new ChargingProperties( + topChargingSpeed: new Power(200) + ), + rangeProperties: new RangeProperties( + wltp: new WltpRange(new Range(756)), + nefz: new NefzRange(new Range(770)) + ), + catalogPrice: new Price(106374, Currency::euro()), + image: new Image('https://www.mercedes-benz.de/content/germany/de/mercedes-benz/vehicles/passenger-cars/eqs/sedan/overview/_jcr_content/root/responsivegrid/simple_stage.component.damq2.3318859008536.jpg') + ), + ], + + // Volkswagen ID.4 Pro + [ + 'model' => 'ID.4', + 'revision' => new CarRevision( + name: 'Pro', + productionBegin: new Date(1, 1, 2020), + drivingCharacteristics: new DrivingCharacteristics( + power: new Power(150), + acceleration: new Acceleration(8.5), + topSpeed: new Speed(160), + consumption: new Consumption(new Energy(16.3)) + ), + battery: new BatteryProperties( + usableCapacity: new Energy(77.0), + totalCapacity: new Energy(82.0), + cellChemistry: CellChemistry::LithiumNickelManganeseOxide, + model: 'MEB Platform', + manufacturer: 'LG Energy Solution' + ), + chargingProperties: new ChargingProperties( + topChargingSpeed: new Power(125) + ), + rangeProperties: new RangeProperties( + wltp: new WltpRange(new Range(520)), + nefz: new NefzRange(new Range(549)) + ), + catalogPrice: new Price(51515, Currency::euro()), + image: new Image('https://www.volkswagen.de/content/dam/vw-ngw/vw_pkw/importers/de/models/id-4/gallery/id4-gallery-exterior-01-16x9.jpg') + ), + ], + + // Porsche Taycan Turbo S + [ + 'model' => 'Taycan', + 'revision' => new CarRevision( + name: 'Turbo S', + productionBegin: new Date(1, 1, 2019), + drivingCharacteristics: new DrivingCharacteristics( + power: new Power(560), + acceleration: new Acceleration(2.8), + topSpeed: new Speed(260), + consumption: new Consumption(new Energy(23.7)) + ), + battery: new BatteryProperties( + usableCapacity: new Energy(83.7), + totalCapacity: new Energy(93.4), + cellChemistry: CellChemistry::LithiumNickelManganeseOxide, + model: 'J1 Platform', + manufacturer: 'LG Energy Solution' + ), + chargingProperties: new ChargingProperties( + topChargingSpeed: new Power(270) + ), + rangeProperties: new RangeProperties( + wltp: new WltpRange(new Range(440)), + nefz: new NefzRange(new Range(452)) + ), + catalogPrice: new Price(185456, Currency::euro()), + image: new Image('https://www.porsche.com/germany/models/taycan/taycan-models/turbo-s/_jcr_content/par/twocolumnlayout/par_left/image.transform.porsche-model-desktop-xl.jpg') + ), + ], + ]; + } +} \ No newline at end of file diff --git a/src/Domain/Model/Brand.php b/src/Domain/Model/Brand.php index 3a381f5..89455b8 100644 --- a/src/Domain/Model/Brand.php +++ b/src/Domain/Model/Brand.php @@ -5,13 +5,11 @@ namespace App\Domain\Model; final readonly class Brand { public function __construct( - public readonly string $id, public readonly string $name, - public readonly string $logo, - public readonly string $description, - public readonly int $foundedYear, - public readonly string $headquarters, - public readonly string $website, - public readonly array $carModels, + public readonly ?string $logo = null, + public readonly ?string $description = null, + public readonly ?int $foundedYear = null, + public readonly ?string $headquarters = null, + public readonly ?string $website = null, ) {} } \ No newline at end of file diff --git a/src/Domain/Model/CarModelCollection.php b/src/Domain/Model/CarModelCollection.php new file mode 100644 index 0000000..77f93f4 --- /dev/null +++ b/src/Domain/Model/CarModelCollection.php @@ -0,0 +1,16 @@ +carModels; + } +} \ No newline at end of file diff --git a/src/Domain/Model/CarRevisionCollection.php b/src/Domain/Model/CarRevisionCollection.php new file mode 100644 index 0000000..d5ce8ff --- /dev/null +++ b/src/Domain/Model/CarRevisionCollection.php @@ -0,0 +1,16 @@ +carRevisions; + } +} \ No newline at end of file diff --git a/src/Domain/Model/Persistence/PersistedBrand.php b/src/Domain/Model/Persistence/PersistedBrand.php new file mode 100644 index 0000000..bb83cff --- /dev/null +++ b/src/Domain/Model/Persistence/PersistedBrand.php @@ -0,0 +1,13 @@ +secondsFrom0To100 . ' sec (0-100 km/h)'; } - public function getSeconds(): float + public function seconds(): float { return $this->secondsFrom0To100; } diff --git a/src/Domain/Model/Value/Consumption.php b/src/Domain/Model/Value/Consumption.php index 995b13b..b6db4cc 100644 --- a/src/Domain/Model/Value/Consumption.php +++ b/src/Domain/Model/Value/Consumption.php @@ -5,17 +5,22 @@ namespace App\Domain\Model\Value; class Consumption { public function __construct( - public readonly Energy $energyPerKm, + public readonly Energy $energyPer100Km, ) { } public function __toString(): string { - return $this->energyPerKm->kwh() . ' kWh/100km'; + return $this->energyPer100Km->kwh() . ' ' . $this->unit(); } - public function energyPerKm(): Energy + public function energyPer100Km(): Energy { - return $this->energyPerKm; + return $this->energyPer100Km; + } + + public function unit(): string + { + return 'kWh/100km'; } } \ No newline at end of file diff --git a/src/Domain/Model/Value/Currency.php b/src/Domain/Model/Value/Currency.php index 6dcefaf..0bde8e7 100644 --- a/src/Domain/Model/Value/Currency.php +++ b/src/Domain/Model/Value/Currency.php @@ -15,21 +15,6 @@ class Currency return $this->symbol; } - public function symbol(): string - { - return $this->symbol; - } - - public function currency(): string - { - return $this->currency; - } - - public function name(): string - { - return $this->name; - } - public static function euro(): self { return new self('€', 'EUR', 'Euro'); diff --git a/src/Domain/Model/Value/Price.php b/src/Domain/Model/Value/Price.php index 94031f5..8fff371 100644 --- a/src/Domain/Model/Value/Price.php +++ b/src/Domain/Model/Value/Price.php @@ -12,6 +12,11 @@ class Price public function __toString(): string { - return number_format($this->price, 0, ',', '.') . ' ' . $this->currency->symbol(); + return $this->formattedPrice() . ' ' . $this->currency->symbol; + } + + public function formattedPrice(): string + { + return number_format($this->price, 0, ',', '.'); } } \ No newline at end of file diff --git a/src/Domain/Repository/BrandRepository.php b/src/Domain/Repository/BrandRepository.php index 65df297..769735c 100644 --- a/src/Domain/Repository/BrandRepository.php +++ b/src/Domain/Repository/BrandRepository.php @@ -2,9 +2,15 @@ namespace App\Domain\Repository; +use App\Domain\Model\Brand; use App\Domain\Model\BrandCollection; +use App\Domain\Model\Persistence\PersistedBrand; interface BrandRepository { public function findAll(): BrandCollection; -} \ No newline at end of file + + public function create(Brand $brand): PersistedBrand; + + public function update(PersistedBrand $persistedBrand): void; +} \ No newline at end of file diff --git a/src/Domain/Repository/CarModelRepository.php b/src/Domain/Repository/CarModelRepository.php index 2728262..a04de13 100644 --- a/src/Domain/Repository/CarModelRepository.php +++ b/src/Domain/Repository/CarModelRepository.php @@ -1,98 +1,22 @@ getUnitOfWork(), $dm->getClassMetadata(CarModel::class)); - } + public function findAll(): CarModelCollection; + + public function findById(string $id): ?PersistedCarModel; + + public function findByBrandId(string $brandId): CarModelCollection; + + public function create(CarModel $carModel, string $brandId): PersistedCarModel; - public function findAllCarModels(): array - { - return $this->createQueryBuilder() - ->sort('name', 'asc') - ->getQuery() - ->execute() - ->toArray(); - } - - public function findCarModelById(string $id): ?CarModel - { - return $this->find($id); - } - - public function findCarModelsByBrandId(string $brandId): array - { - return $this->createQueryBuilder() - ->field('brand.$id')->equals(new \MongoDB\BSON\ObjectId($brandId)) - ->sort('name', 'asc') - ->getQuery() - ->execute() - ->toArray(); - } - - public function findCarModelsByName(string $name): array - { - return $this->createQueryBuilder() - ->field('name')->equals(new \MongoDB\BSON\Regex($name, 'i')) - ->sort('name', 'asc') - ->getQuery() - ->execute() - ->toArray(); - } - - public function findCarModelsByCategory(string $category): array - { - return $this->createQueryBuilder() - ->field('category')->equals($category) - ->sort('name', 'asc') - ->getQuery() - ->execute() - ->toArray(); - } - - public function findCarModelsByYearRange(int $startYear, int $endYear): array - { - return $this->createQueryBuilder() - ->field('productionStartYear')->gte($startYear) - ->field('productionStartYear')->lte($endYear) - ->sort('productionStartYear', 'asc') - ->getQuery() - ->execute() - ->toArray(); - } - - public function searchCarModels(string $query): array - { - $regex = new \MongoDB\BSON\Regex($query, 'i'); - - return $this->createQueryBuilder() - ->addOr( - $this->createQueryBuilder()->field('name')->equals($regex)->getQueryArray(), - $this->createQueryBuilder()->field('description')->equals($regex)->getQueryArray(), - $this->createQueryBuilder()->field('category')->equals($regex)->getQueryArray() - ) - ->sort('name', 'asc') - ->getQuery() - ->execute() - ->toArray(); - } - - public function saveCarModel(CarModel $carModel): void - { - $this->getDocumentManager()->persist($carModel); - $this->getDocumentManager()->flush(); - } - - public function deleteCarModel(CarModel $carModel): void - { - $this->getDocumentManager()->remove($carModel); - $this->getDocumentManager()->flush(); - } + public function update(PersistedCarModel $persistedCarModel): void; + + public function delete(PersistedCarModel $persistedCarModel): void; } \ No newline at end of file diff --git a/src/Domain/Repository/CarRevisionRepository.php b/src/Domain/Repository/CarRevisionRepository.php new file mode 100644 index 0000000..341cac9 --- /dev/null +++ b/src/Domain/Repository/CarRevisionRepository.php @@ -0,0 +1,22 @@ +image, [ new BrandTile('Skoda'), new PriceTile($skodaElroq85->catalogPrice), - new ProductionPeriodTile($skodaElroq85->productionBegin, $skodaElroq85->productionEnd), + new AvailabilityTile('Verfügbar', new Date(1, 1, 2020)), + new RangeTile($wltpRange->range), + new ConsumptionTile($drivingCharacteristics->consumption), + new AccelerationTile($drivingCharacteristics->acceleration), ]), new SubSectionTile('Performance', [ new PowerTile($drivingCharacteristics->power), - new AccelerationTile($drivingCharacteristics->acceleration), new TopSpeedTile($drivingCharacteristics->topSpeed), new DrivetrainTile(new Drivetrain('rear')), - new ConsumptionTile($drivingCharacteristics->consumption), ], 'Individual performance metrics'), new SubSectionTile('Reichweite', [ new RangeTile($wltpRange->range), - new RangeComparisonTile($skodaElroq85->rangeProperties), new RealRangeTile($realRangeTests), ], 'Range data from different sources'), @@ -160,9 +160,9 @@ class Engine ], 'Battery capacity and technology'), new SubSectionTile('Laden', [ + new ChargingTile($chargingSpeed), new ChargeTimeTile($chargingProperties->chargeTimeProperties), new ChargingConnectivityTile($chargingProperties->chargingConnectivity), - new ChargeCurveTile($chargingProperties->chargeCurve), ], 'Charging capabilities and compatibility'), ]), ]); diff --git a/src/Domain/Search/Tiles/AvailabilityTile.php b/src/Domain/Search/Tiles/AvailabilityTile.php index 54e5abc..e93ded5 100644 --- a/src/Domain/Search/Tiles/AvailabilityTile.php +++ b/src/Domain/Search/Tiles/AvailabilityTile.php @@ -2,10 +2,12 @@ namespace App\Domain\Search\Tiles; +use App\Domain\Model\Value\Date; + class AvailabilityTile { public function __construct( public readonly string $status, - public readonly ?string $availableSince = null, + public readonly ?Date $availableSince = null, ) {} } \ No newline at end of file diff --git a/src/Infrastructure/MongoDB/MongoDBClient.php b/src/Infrastructure/MongoDB/MongoDBClient.php deleted file mode 100644 index 6584aed..0000000 --- a/src/Infrastructure/MongoDB/MongoDBClient.php +++ /dev/null @@ -1,28 +0,0 @@ -client = new Client($this->dsl); - } - - public function getClient(): Client - { - return $this->client; - } - - public function getDatabase(): Database - { - return $this->client->selectDatabase($this->databaseName); - } -} \ No newline at end of file diff --git a/src/Infrastructure/MongoDB/Repository/BrandRepository/ModelMapper.php b/src/Infrastructure/MongoDB/Repository/BrandRepository/ModelMapper.php deleted file mode 100644 index bc6db55..0000000 --- a/src/Infrastructure/MongoDB/Repository/BrandRepository/ModelMapper.php +++ /dev/null @@ -1,27 +0,0 @@ -client->getDatabase()->selectCollection('brands')->find(); - $brands = []; - - $mapper = new ModelMapper(); - - /** @var \MongoDB\Model\BSONDocument $brand */ - foreach ($result as $brand) { - $brands[] = $mapper->map($brand->getArrayCopy()); - } - - return new BrandCollection($brands); - } -} \ No newline at end of file diff --git a/src/Infrastructure/PostgreSQL/Repository/BrandRepository/ModelMapper.php b/src/Infrastructure/PostgreSQL/Repository/BrandRepository/ModelMapper.php new file mode 100644 index 0000000..ea9db26 --- /dev/null +++ b/src/Infrastructure/PostgreSQL/Repository/BrandRepository/ModelMapper.php @@ -0,0 +1,22 @@ +connection->executeQuery($sql); + + $brands = []; + $mapper = new ModelMapper(); + + foreach ($result as $brand) { + $brands[] = $mapper->map($brand); + } + + return new BrandCollection($brands); + } + + public function create(Brand $brand): PersistedBrand + { + // Generate an ID for the brand since Brand model doesn't have one + $brandId = uniqid('brand_', true); + + $sql = <<<'SQL' + INSERT INTO brands (id, name, content) + VALUES (?, ?, ?) + ON CONFLICT (id) DO UPDATE SET + name = EXCLUDED.name, + content = EXCLUDED.content + SQL; + + $content = json_encode([ + 'logo' => $brand->logo, + 'description' => $brand->description, + 'founded_year' => $brand->foundedYear, + 'headquarters' => $brand->headquarters, + 'website' => $brand->website, + ]); + + $this->connection->executeStatement($sql, [ + $brandId, + $brand->name, + $content, + ]); + + return new PersistedBrand($brandId, $brand); + } + + public function update(PersistedBrand $persistedBrand): void + { + $brand = $persistedBrand->brand; + + $sql = <<<'SQL' + UPDATE brands SET + name = ?, + content = ? + WHERE id = ? + SQL; + + $content = json_encode([ + 'logo' => $brand->logo, + 'description' => $brand->description, + 'founded_year' => $brand->foundedYear, + 'headquarters' => $brand->headquarters, + 'website' => $brand->website, + ]); + + $this->connection->executeStatement($sql, [ + $brand->name, + $content, + $persistedBrand->id, + ]); + } +} \ No newline at end of file diff --git a/src/Infrastructure/PostgreSQL/Repository/CarModelRepository/ModelMapper.php b/src/Infrastructure/PostgreSQL/Repository/CarModelRepository/ModelMapper.php new file mode 100644 index 0000000..aeeb05b --- /dev/null +++ b/src/Infrastructure/PostgreSQL/Repository/CarModelRepository/ModelMapper.php @@ -0,0 +1,24 @@ +connection->executeQuery($sql); + + $carModels = []; + $mapper = new ModelMapper(); + + foreach ($result as $carModel) { + $carModels[] = $mapper->map($carModel); + } + + return new CarModelCollection($carModels); + } + + public function findById(string $id): ?PersistedCarModel + { + $sql = 'SELECT * FROM car_models WHERE id = ?'; + $result = $this->connection->executeQuery($sql, [$id]); + $data = $result->fetchAssociative(); + + if (!$data) { + return null; + } + + $mapper = new ModelMapper(); + $carModel = $mapper->map($data); + + return new PersistedCarModel($data['id'], $carModel); + } + + public function findByBrandId(string $brandId): CarModelCollection + { + $sql = 'SELECT * FROM car_models WHERE brand_id = ? ORDER BY name ASC'; + $result = $this->connection->executeQuery($sql, [$brandId]); + + $carModels = []; + $mapper = new ModelMapper(); + + foreach ($result as $carModel) { + $carModels[] = $mapper->map($carModel); + } + + return new CarModelCollection($carModels); + } + + public function create(CarModel $carModel, string $brandId): PersistedCarModel + { + // Generate an ID for the car model + $carModelId = uniqid('carmodel_', true); + + $sql = <<<'SQL' + INSERT INTO car_models (id, brand_id, name, content) + VALUES (?, ?, ?, ?) + ON CONFLICT (id) DO UPDATE SET + brand_id = EXCLUDED.brand_id, + name = EXCLUDED.name, + content = EXCLUDED.content + SQL; + + $content = json_encode([ + 'brand' => $carModel->brand?->name ?? null, + ]); + + $this->connection->executeStatement($sql, [ + $carModelId, + $brandId, + $carModel->name, + $content, + ]); + + return new PersistedCarModel($carModelId, $carModel); + } + + public function update(PersistedCarModel $persistedCarModel): void + { + $carModel = $persistedCarModel->carModel; + + $sql = <<<'SQL' + UPDATE car_models SET + name = ?, + content = ? + WHERE id = ? + SQL; + + $content = json_encode([ + 'brand' => $carModel->brand?->name ?? null, + ]); + + $this->connection->executeStatement($sql, [ + $carModel->name, + $content, + $persistedCarModel->id, + ]); + } + + public function delete(PersistedCarModel $persistedCarModel): void + { + $sql = 'DELETE FROM car_models WHERE id = ?'; + $this->connection->executeStatement($sql, [$persistedCarModel->id]); + } +} \ No newline at end of file diff --git a/src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/ModelMapper.php b/src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/ModelMapper.php new file mode 100644 index 0000000..2433521 --- /dev/null +++ b/src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/ModelMapper.php @@ -0,0 +1,118 @@ + Currency::euro(), + 'USD' => Currency::usd(), + default => Currency::euro(), // fallback to euro + }; + + $catalogPrice = new Price( + $content['catalog_price'], + $currency + ); + } + + $image = null; + if (!empty($content['image_url'])) { + $image = new Image($content['image_url']); + } + + $drivingCharacteristics = null; + if (!empty($content['driving_characteristics'])) { + $dc = $content['driving_characteristics']; + $drivingCharacteristics = new DrivingCharacteristics( + power: !empty($dc['power_kw']) ? new Power($dc['power_kw']) : null, + acceleration: !empty($dc['acceleration_0_100']) ? new Acceleration($dc['acceleration_0_100']) : null, + topSpeed: !empty($dc['top_speed_kmh']) ? new Speed($dc['top_speed_kmh']) : null, + consumption: !empty($dc['consumption_kwh_100km']) ? new Consumption(new Energy($dc['consumption_kwh_100km'])) : null, + ); + } + + $battery = null; + if (!empty($content['battery'])) { + $b = $content['battery']; + if (!empty($b['usable_capacity_kwh']) && !empty($b['total_capacity_kwh'])) { + $battery = new BatteryProperties( + usableCapacity: new Energy($b['usable_capacity_kwh']), + totalCapacity: new Energy($b['total_capacity_kwh']), + cellChemistry: !empty($b['cell_chemistry']) ? CellChemistry::from($b['cell_chemistry']) : CellChemistry::LithiumIronPhosphate, + model: $b['model'] ?? '', + manufacturer: $b['manufacturer'] ?? '', + ); + } + } + + $chargingProperties = null; + if (!empty($content['charging']['top_charging_speed_kw'])) { + $chargingProperties = new ChargingProperties( + topChargingSpeed: new Power($content['charging']['top_charging_speed_kw']) + ); + } + + $rangeProperties = null; + if (!empty($content['range'])) { + $r = $content['range']; + $wltp = !empty($r['wltp_km']) ? new WltpRange(new Range($r['wltp_km'])) : null; + $nefz = !empty($r['nefz_km']) ? new NefzRange(new Range($r['nefz_km'])) : null; + + if ($wltp || $nefz) { + $rangeProperties = new RangeProperties( + wltp: $wltp, + nefz: $nefz, + ); + } + } + + return new CarRevision( + name: $data['name'] ?? '', + productionBegin: $productionBegin, + productionEnd: $productionEnd, + drivingCharacteristics: $drivingCharacteristics, + battery: $battery, + chargingProperties: $chargingProperties, + rangeProperties: $rangeProperties, + catalogPrice: $catalogPrice, + carModel: null, // CarModel would need to be loaded separately if needed + image: $image, + ); + } +} \ No newline at end of file diff --git a/src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/PostgreSQLCarRevisionRepository.php b/src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/PostgreSQLCarRevisionRepository.php new file mode 100644 index 0000000..7c3f588 --- /dev/null +++ b/src/Infrastructure/PostgreSQL/Repository/CarRevisionRepository/PostgreSQLCarRevisionRepository.php @@ -0,0 +1,167 @@ +connection->executeQuery($sql); + + $carRevisions = []; + $mapper = new ModelMapper(); + + foreach ($result as $carRevision) { + $carRevisions[] = $mapper->map($carRevision); + } + + return new CarRevisionCollection($carRevisions); + } + + public function findById(string $id): ?PersistedCarRevision + { + $sql = 'SELECT * FROM car_revisions WHERE id = ?'; + $result = $this->connection->executeQuery($sql, [$id]); + $data = $result->fetchAssociative(); + + if (!$data) { + return null; + } + + $mapper = new ModelMapper(); + $carRevision = $mapper->map($data); + + return new PersistedCarRevision($data['id'], $carRevision); + } + + public function findByCarModelId(string $carModelId): CarRevisionCollection + { + $sql = 'SELECT * FROM car_revisions WHERE car_model_id = ? ORDER BY name ASC'; + $result = $this->connection->executeQuery($sql, [$carModelId]); + + $carRevisions = []; + $mapper = new ModelMapper(); + + foreach ($result as $carRevision) { + $carRevisions[] = $mapper->map($carRevision); + } + + return new CarRevisionCollection($carRevisions); + } + + public function create(CarRevision $carRevision, string $carModelId): PersistedCarRevision + { + // Generate an ID for the car revision + $carRevisionId = uniqid('carrevision_', true); + + $sql = <<<'SQL' + INSERT INTO car_revisions (id, car_model_id, name, content) + VALUES (?, ?, ?, ?) + ON CONFLICT (id) DO UPDATE SET + car_model_id = EXCLUDED.car_model_id, + name = EXCLUDED.name, + content = EXCLUDED.content + SQL; + + $content = json_encode([ + 'production_begin' => $carRevision->productionBegin?->year ?? null, + 'production_end' => $carRevision->productionEnd?->year ?? null, + 'catalog_price' => $carRevision->catalogPrice?->price ?? null, + 'catalog_price_currency' => $carRevision->catalogPrice?->currency->currency ?? null, + 'image_url' => $carRevision->image?->url ?? null, + 'driving_characteristics' => [ + 'power_kw' => $carRevision->drivingCharacteristics?->power?->kilowatts ?? null, + 'acceleration_0_100' => $carRevision->drivingCharacteristics?->acceleration?->secondsFrom0To100 ?? null, + 'top_speed_kmh' => $carRevision->drivingCharacteristics?->topSpeed?->kmh ?? null, + 'consumption_kwh_100km' => $carRevision->drivingCharacteristics?->consumption?->energyPer100Km->kwh() ?? null, + ], + 'battery' => [ + 'usable_capacity_kwh' => $carRevision->battery?->usableCapacity->kwh() ?? null, + 'total_capacity_kwh' => $carRevision->battery?->totalCapacity->kwh() ?? null, + 'cell_chemistry' => $carRevision->battery?->cellChemistry->value ?? null, + 'model' => $carRevision->battery?->model ?? null, + 'manufacturer' => $carRevision->battery?->manufacturer ?? null, + ], + 'charging' => [ + 'top_charging_speed_kw' => $carRevision->chargingProperties?->topChargingSpeed?->kilowatts ?? null, + ], + 'range' => [ + 'wltp_km' => $carRevision->rangeProperties?->wltp?->range->kilometers ?? null, + 'nefz_km' => $carRevision->rangeProperties?->nefz?->range->kilometers ?? null, + ], + ]); + + $this->connection->executeStatement($sql, [ + $carRevisionId, + $carModelId, + $carRevision->name, + $content, + ]); + + return new PersistedCarRevision($carRevisionId, $carRevision); + } + + public function update(PersistedCarRevision $persistedCarRevision): void + { + $carRevision = $persistedCarRevision->carRevision; + + $sql = <<<'SQL' + UPDATE car_revisions SET + name = ?, + content = ? + WHERE id = ? + SQL; + + $content = json_encode([ + 'production_begin' => $carRevision->productionBegin?->year ?? null, + 'production_end' => $carRevision->productionEnd?->year ?? null, + 'catalog_price' => $carRevision->catalogPrice?->price ?? null, + 'catalog_price_currency' => $carRevision->catalogPrice?->currency->currency ?? null, + 'image_url' => $carRevision->image?->url ?? null, + 'driving_characteristics' => [ + 'power_kw' => $carRevision->drivingCharacteristics?->power?->kilowatts ?? null, + 'acceleration_0_100' => $carRevision->drivingCharacteristics?->acceleration?->secondsFrom0To100 ?? null, + 'top_speed_kmh' => $carRevision->drivingCharacteristics?->topSpeed?->kmh ?? null, + 'consumption_kwh_100km' => $carRevision->drivingCharacteristics?->consumption?->energyPer100Km->kwh() ?? null, + ], + 'battery' => [ + 'usable_capacity_kwh' => $carRevision->battery?->usableCapacity->kwh() ?? null, + 'total_capacity_kwh' => $carRevision->battery?->totalCapacity->kwh() ?? null, + 'cell_chemistry' => $carRevision->battery?->cellChemistry->value ?? null, + 'model' => $carRevision->battery?->model ?? null, + 'manufacturer' => $carRevision->battery?->manufacturer ?? null, + ], + 'charging' => [ + 'top_charging_speed_kw' => $carRevision->chargingProperties?->topChargingSpeed?->kilowatts ?? null, + ], + 'range' => [ + 'wltp_km' => $carRevision->rangeProperties?->wltp?->range->kilometers ?? null, + 'nefz_km' => $carRevision->rangeProperties?->nefz?->range->kilometers ?? null, + ], + ]); + + $this->connection->executeStatement($sql, [ + $carRevision->name, + $content, + $persistedCarRevision->id, + ]); + } + + public function delete(PersistedCarRevision $persistedCarRevision): void + { + $sql = 'DELETE FROM car_revisions WHERE id = ?'; + $this->connection->executeStatement($sql, [$persistedCarRevision->id]); + } +} \ No newline at end of file diff --git a/symfony.lock b/symfony.lock index 5c33e13..503d463 100644 --- a/symfony.lock +++ b/symfony.lock @@ -1,4 +1,55 @@ { + "doctrine/deprecations": { + "version": "1.1", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "1.0", + "ref": "87424683adc81d7dc305eefec1fced883084aab9" + } + }, + "doctrine/doctrine-bundle": { + "version": "2.14", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "2.13", + "ref": "620b57f496f2e599a6015a9fa222c2ee0a32adcb" + }, + "files": [ + "config/packages/doctrine.yaml", + "src/Entity/.gitignore", + "src/Repository/.gitignore" + ] + }, + "doctrine/doctrine-migrations-bundle": { + "version": "3.4", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "3.1", + "ref": "1d01ec03c6ecbd67c3375c5478c9a423ae5d6a33" + }, + "files": [ + "config/packages/doctrine_migrations.yaml", + "migrations/.gitignore" + ] + }, + "symfony/asset-mapper": { + "version": "7.3", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "main", + "version": "6.4", + "ref": "5ad1308aa756d58f999ffbe1540d1189f5d7d14a" + }, + "files": [ + "assets/app.js", + "assets/styles/app.css", + "config/packages/asset_mapper.yaml", + "importmap.php" + ] + }, "symfony/console": { "version": "7.2", "recipe": { diff --git a/templates/_components/search.html.twig b/templates/_components/search.html.twig index 5082f08..1088a0f 100644 --- a/templates/_components/search.html.twig +++ b/templates/_components/search.html.twig @@ -1,6 +1,8 @@
- - + +