|
| 1 | +# Installing Doctrine |
| 2 | + |
| 3 | +## Composer Requirements |
| 4 | + |
| 5 | +The first step is to add alongside your current packages the required entries for our Doctrine installation. We would add the following to our `composer.json` file located in our root folder: |
| 6 | + |
| 7 | +```json |
| 8 | +{ |
| 9 | + "require": { |
| 10 | + "dotkernel/dot-cache": "^4.0", |
| 11 | + "ramsey/uuid": "^4.5.0", |
| 12 | + "ramsey/uuid-doctrine": "^2.1.0", |
| 13 | + "roave/psr-container-doctrine": "^5.2.2" |
| 14 | + }, |
| 15 | + "require-dev": { |
| 16 | + "phpstan/phpstan-doctrine": "^2.0.3" |
| 17 | + } |
| 18 | +} |
| 19 | +``` |
| 20 | + |
| 21 | +`dotkernel/dot-cache` |
| 22 | + |
| 23 | +Provides caching support for DotKernel applications. |
| 24 | +It offers a PSR-6 and PSR-16 compatible caching system that integrates smoothly with DotKernel's service container. |
| 25 | + |
| 26 | +`ramsey/uuid` |
| 27 | + |
| 28 | +A widely used PHP library for generating and working with UUIDs (Universally Unique Identifiers). |
| 29 | +It supports multiple UUID versions. |
| 30 | + |
| 31 | +`ramsey/uuid-doctrine` |
| 32 | + |
| 33 | +Adds UUID support to Doctrine ORM using ramsey/uuid. |
| 34 | +It allows Doctrine to store and retrieve UUIDs as proper value objects instead of plain strings, improving type safety. |
| 35 | + |
| 36 | +`roave/psr-container-doctrine` |
| 37 | + |
| 38 | +Provides a set of factory classes that integrate Doctrine ORM with any PSR-11 container. |
| 39 | +It simplifies wiring Doctrine EntityManager, DBAL, configuration, and related services in frameworks like DotKernel. |
| 40 | + |
| 41 | +`phpstan/phpstan-doctrine (dev requirement)` |
| 42 | + |
| 43 | +An extension for PHPStan that improves static analysis for Doctrine. |
| 44 | +It understands entity metadata, repositories, and common Doctrine patterns, helping catch errors during development. |
| 45 | + |
| 46 | +## Setting Up Doctrine |
| 47 | + |
| 48 | +After successfully installing our dependencies we now need to configure our Doctrine instance. |
| 49 | + |
| 50 | +### Declare your database |
| 51 | + |
| 52 | +In the file `config/autoload/local.php`: |
| 53 | + |
| 54 | +```php |
| 55 | +$databases = [ |
| 56 | + 'default' => [ |
| 57 | + 'host' => 'localhost', |
| 58 | + 'dbname' => 'light', |
| 59 | + 'user' => 'root', |
| 60 | + 'password' => '123', |
| 61 | + 'port' => 3306, |
| 62 | + 'driver' => 'pdo_mysql', |
| 63 | + 'charset' => 'utf8mb4', |
| 64 | + 'collate' => 'utf8mb4_general_ci', |
| 65 | + ], |
| 66 | + // you can add more database connections into this array |
| 67 | +]; |
| 68 | + |
| 69 | +return [ |
| 70 | + 'databases' => $databases, |
| 71 | + //the rest of your configuration variables |
| 72 | +]; |
| 73 | +``` |
| 74 | + |
| 75 | +### Declare the Doctrine Drivers and Migrations Location |
| 76 | + |
| 77 | +With the very nice utility of the package `laminas/laminas-config-aggregator` we can declare our doctrine settings in the `src/App/src/ConfigProvider.php` file. |
| 78 | +This package takes all the provided configs from the `config/config.php` file and merges them into one. |
| 79 | + |
| 80 | +Our new `src/App/src/ConfigProvider.php` class would look like this now: |
| 81 | + |
| 82 | +```php |
| 83 | +<?php |
| 84 | + |
| 85 | +declare(strict_types=1); |
| 86 | + |
| 87 | +namespace Light\App; |
| 88 | + |
| 89 | +use Doctrine\ORM\EntityManager; |
| 90 | +use Doctrine\ORM\EntityManagerInterface; |
| 91 | +use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; |
| 92 | +use Dot\Cache\Adapter\ArrayAdapter; |
| 93 | +use Dot\Cache\Adapter\FilesystemAdapter; |
| 94 | +use Light\App\Factory\GetIndexViewHandlerFactory; |
| 95 | +use Light\App\Handler\GetIndexViewHandler; |
| 96 | +use Mezzio\Application; |
| 97 | +use Ramsey\Uuid\Doctrine\UuidBinaryOrderedTimeType; |
| 98 | +use Ramsey\Uuid\Doctrine\UuidBinaryType; |
| 99 | +use Ramsey\Uuid\Doctrine\UuidType; |
| 100 | +use Roave\PsrContainerDoctrine\EntityManagerFactory; |
| 101 | + |
| 102 | +use function getcwd; |
| 103 | + |
| 104 | +class ConfigProvider |
| 105 | +{ |
| 106 | + /** |
| 107 | + @return array{ |
| 108 | + * dependencies: array<mixed>, |
| 109 | + * templates: array<mixed>, |
| 110 | + * } |
| 111 | + */ |
| 112 | + public function __invoke(): array |
| 113 | + { |
| 114 | + return [ |
| 115 | + 'dependencies' => $this->getDependencies(), |
| 116 | + 'doctrine' => $this->getDoctrineConfig(), |
| 117 | + 'templates' => $this->getTemplates(), |
| 118 | + ]; |
| 119 | + } |
| 120 | + |
| 121 | + /** |
| 122 | + * @return array{ |
| 123 | + * delegators: array<class-string, array<class-string>>, |
| 124 | + * factories: array<class-string, class-string>, |
| 125 | + * } |
| 126 | + */ |
| 127 | + public function getDependencies(): array |
| 128 | + { |
| 129 | + return [ |
| 130 | + 'delegators' => [ |
| 131 | + Application::class => [ |
| 132 | + RoutesDelegator::class, |
| 133 | + ], |
| 134 | + ], |
| 135 | + 'factories' => [ |
| 136 | + 'doctrine.entity_manager.orm_default' => EntityManagerFactory::class, |
| 137 | + GetIndexViewHandler::class => GetIndexViewHandlerFactory::class, |
| 138 | + ], |
| 139 | + 'aliases' => [ |
| 140 | + EntityManager::class => 'doctrine.entity_manager.orm_default', |
| 141 | + EntityManagerInterface::class => 'doctrine.entity_manager.orm_default', |
| 142 | + ], |
| 143 | + ]; |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * @return array{ |
| 148 | + * paths: array{ |
| 149 | + * app: array{literal-string&non-falsy-string}, |
| 150 | + * error: array{literal-string&non-falsy-string}, |
| 151 | + * layout: array{literal-string&non-falsy-string}, |
| 152 | + * partial: array{literal-string&non-falsy-string}, |
| 153 | + * } |
| 154 | + * } |
| 155 | + */ |
| 156 | + public function getTemplates(): array |
| 157 | + { |
| 158 | + return [ |
| 159 | + 'paths' => [ |
| 160 | + 'app' => [__DIR__ . '/../templates/app'], |
| 161 | + 'error' => [__DIR__ . '/../templates/error'], |
| 162 | + 'layout' => [__DIR__ . '/../templates/layout'], |
| 163 | + 'partial' => [__DIR__ . '/../templates/partial'], |
| 164 | + ], |
| 165 | + ]; |
| 166 | + } |
| 167 | + |
| 168 | + private function getDoctrineConfig(): array |
| 169 | + { |
| 170 | + return [ |
| 171 | + 'cache' => [ |
| 172 | + 'array' => [ |
| 173 | + 'class' => ArrayAdapter::class, |
| 174 | + ], |
| 175 | + 'filesystem' => [ |
| 176 | + 'class' => FilesystemAdapter::class, |
| 177 | + 'directory' => getcwd() . '/data/cache', |
| 178 | + 'namespace' => 'doctrine', |
| 179 | + ], |
| 180 | + ], |
| 181 | + 'configuration' => [ |
| 182 | + 'orm_default' => [ |
| 183 | + 'result_cache' => 'filesystem', |
| 184 | + 'metadata_cache' => 'filesystem', |
| 185 | + 'query_cache' => 'filesystem', |
| 186 | + 'hydration_cache' => 'array', |
| 187 | + 'typed_field_mapper' => null, |
| 188 | + 'second_level_cache' => [ |
| 189 | + 'enabled' => true, |
| 190 | + 'default_lifetime' => 3600, |
| 191 | + 'default_lock_lifetime' => 60, |
| 192 | + 'file_lock_region_directory' => '', |
| 193 | + 'regions' => [], |
| 194 | + ], |
| 195 | + ], |
| 196 | + ], |
| 197 | + 'connection' => [ |
| 198 | + 'orm_default' => [ |
| 199 | + 'doctrine_mapping_types' => [ |
| 200 | + UuidBinaryType::NAME => 'binary', |
| 201 | + UuidBinaryOrderedTimeType::NAME => 'binary', |
| 202 | + ], |
| 203 | + ], |
| 204 | + ], |
| 205 | + 'driver' => [ |
| 206 | + // The default metadata driver aggregates all other drivers into a single one. |
| 207 | + // Override `orm_default` only if you know what you're doing. |
| 208 | + 'orm_default' => [ |
| 209 | + 'class' => MappingDriverChain::class, |
| 210 | + ], |
| 211 | + ], |
| 212 | + 'migrations' => [ |
| 213 | + // Modify this line based on where you would like to have you migrations |
| 214 | + 'migrations_paths' => [ |
| 215 | + 'Migrations' => 'src/Migrations', |
| 216 | + ], |
| 217 | + 'all_or_nothing' => true, |
| 218 | + 'check_database_platform' => true, |
| 219 | + ], |
| 220 | + 'types' => [ |
| 221 | + UuidType::NAME => UuidType::class, |
| 222 | + UuidBinaryType::NAME => UuidBinaryType::class, |
| 223 | + UuidBinaryOrderedTimeType::NAME => UuidBinaryOrderedTimeType::class, |
| 224 | + ], |
| 225 | + ]; |
| 226 | + } |
| 227 | +} |
| 228 | + |
| 229 | +``` |
| 230 | + |
| 231 | +We also require a new file `config/cli-config.php`. |
| 232 | +It initializes and returns a `DependencyFactory` that Doctrine Migrations uses to run migrations. |
| 233 | + |
| 234 | +```php |
| 235 | +<?php |
| 236 | + |
| 237 | +declare(strict_types=1); |
| 238 | + |
| 239 | +use Doctrine\Migrations\Configuration\EntityManager\ExistingEntityManager; |
| 240 | +use Doctrine\Migrations\Configuration\Migration\ConfigurationArray; |
| 241 | +use Doctrine\Migrations\DependencyFactory; |
| 242 | +use Doctrine\ORM\EntityManager; |
| 243 | + |
| 244 | +$container = require 'config/container.php'; |
| 245 | + |
| 246 | +$entityManager = $container->get(EntityManager::class); |
| 247 | +$entityManager->getEventManager(); |
| 248 | + |
| 249 | +return DependencyFactory::fromEntityManager( |
| 250 | + new ConfigurationArray($container->get('config')['doctrine']['migrations']), |
| 251 | + new ExistingEntityManager($entityManager) |
| 252 | +); |
| 253 | +``` |
| 254 | + |
| 255 | +## Running doctrine |
| 256 | + |
| 257 | +Now that everything has been configured we only need to do one last thing, to create an executable for the Doctrine CLI. |
| 258 | +In our case we will create it as `/bin/doctrine` |
| 259 | + |
| 260 | +```php |
| 261 | +#!/usr/bin/env php |
| 262 | +<?php |
| 263 | + |
| 264 | +declare(strict_types=1); |
| 265 | + |
| 266 | +use Doctrine\ORM\EntityManager; |
| 267 | +use Doctrine\ORM\Tools\Console\ConsoleRunner; |
| 268 | +use Doctrine\ORM\Tools\Console\EntityManagerProvider\SingleManagerProvider; |
| 269 | + |
| 270 | +require_once 'vendor/autoload.php'; |
| 271 | + |
| 272 | +$container = require 'config/container.php'; |
| 273 | + |
| 274 | +$entityManager = $container->get(EntityManager::class); |
| 275 | +$entityManager->getEventManager(); |
| 276 | + |
| 277 | +ConsoleRunner::run(new SingleManagerProvider($entityManager)); |
| 278 | +``` |
| 279 | + |
| 280 | +(Optional) To keep things tidy we recommend to make an executable for the migrations of Doctrine as well for example `/bin/doctrine-migrations`: |
| 281 | + |
| 282 | +```php |
| 283 | +#!/usr/bin/env php |
| 284 | +<?php |
| 285 | + |
| 286 | +declare(strict_types=1); |
| 287 | + |
| 288 | +namespace Doctrine\Migrations; |
| 289 | + |
| 290 | +require __DIR__ . '/../vendor/doctrine/migrations/bin/doctrine-migrations.php'; |
| 291 | +``` |
| 292 | + |
| 293 | +Now by running the command bellow we should see the Doctrine CLI version alongside its available commands: |
| 294 | + |
| 295 | +```shell |
| 296 | +php bin/doctrine |
| 297 | +``` |
| 298 | + |
| 299 | +Example (truncated) output: |
| 300 | + |
| 301 | +```terminaloutput |
| 302 | +Doctrine Command Line Interface 3.5.7.0 |
| 303 | +
|
| 304 | +Options: |
| 305 | + -h, --help Display help for the given command. When no command is given display help for the list command |
| 306 | + --silent Do not output any message |
| 307 | + -q, --quiet Only errors are displayed. All other output is suppressed |
| 308 | + -V, --version Display this application version |
| 309 | + --ansi|--no-ansi Force (or disable --no-ansi) ANSI output |
| 310 | + -n, --no-interaction Do not ask any interactive question |
| 311 | + -v|vv|vvv, --verbose Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug |
| 312 | +``` |
0 commit comments