From c198d8a9843872f13c71948ea7cf8d1025b4e0a4 Mon Sep 17 00:00:00 2001 From: Howriq Date: Thu, 20 Nov 2025 09:38:08 +0200 Subject: [PATCH 01/12] Changes for chapter 1 Signed-off-by: Howriq --- src/App/src/ConfigProvider.php | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index f52d871..7d0fdaf 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -112,14 +112,6 @@ private function getDoctrineConfig(): array ], ], ], - 'connection' => [ - 'orm_default' => [ - 'doctrine_mapping_types' => [ - UuidBinaryType::NAME => 'binary', - UuidBinaryOrderedTimeType::NAME => 'binary', - ], - ], - ], 'driver' => [ // The default metadata driver aggregates all other drivers into a single one. // Override `orm_default` only if you know what you're doing. @@ -136,9 +128,7 @@ private function getDoctrineConfig(): array 'check_database_platform' => true, ], 'types' => [ - UuidType::NAME => UuidType::class, - UuidBinaryType::NAME => UuidBinaryType::class, - UuidBinaryOrderedTimeType::NAME => UuidBinaryOrderedTimeType::class, + UuidType::NAME => UuidType::class, ], ]; } From 715eef121bc78904ad4fabb4598e861da11f6e74 Mon Sep 17 00:00:00 2001 From: Howriq Date: Tue, 25 Nov 2025 20:14:04 +0200 Subject: [PATCH 02/12] Code chapter 2 Signed-off-by: Howriq --- bin/doctrine-migrations.php | 8 +++ composer.json | 3 +- config/cli-config.php | 18 ++++++ config/config.php | 1 + src/App/src/ConfigProvider.php | 34 ++++++++--- src/App/src/Entity/AbstractEntity.php | 57 ++++++++++++++++++ src/App/src/Entity/TimestampsTrait.php | 53 ++++++++++++++++ src/App/src/Entity/UuidIdentifierTrait.php | 33 ++++++++++ .../Factory/EntityListenerResolverFactory.php | 16 +++++ .../src/Resolver/EntityListenerResolver.php | 27 +++++++++ src/App/src/Types/UuidType.php | 17 ++++++ src/Book/src/ConfigProvider.php | 47 +++++++++++++++ src/Book/src/Entity/Book.php | 60 +++++++++++++++++++ src/Migrations/Version20251125173634.php | 31 ++++++++++ 14 files changed, 397 insertions(+), 8 deletions(-) create mode 100755 bin/doctrine-migrations.php create mode 100644 config/cli-config.php create mode 100644 src/App/src/Entity/AbstractEntity.php create mode 100644 src/App/src/Entity/TimestampsTrait.php create mode 100644 src/App/src/Entity/UuidIdentifierTrait.php create mode 100644 src/App/src/Factory/EntityListenerResolverFactory.php create mode 100644 src/App/src/Resolver/EntityListenerResolver.php create mode 100644 src/App/src/Types/UuidType.php create mode 100644 src/Book/src/ConfigProvider.php create mode 100644 src/Book/src/Entity/Book.php create mode 100644 src/Migrations/Version20251125173634.php diff --git a/bin/doctrine-migrations.php b/bin/doctrine-migrations.php new file mode 100755 index 0000000..d40cc55 --- /dev/null +++ b/bin/doctrine-migrations.php @@ -0,0 +1,8 @@ +#!/usr/bin/env php +get(EntityManager::class); +$entityManager->getEventManager(); + +return DependencyFactory::fromEntityManager( + new ConfigurationArray($container->get('config')['doctrine']['migrations']), + new ExistingEntityManager($entityManager) +); diff --git a/config/config.php b/config/config.php index 1c58178..d45c47a 100644 --- a/config/config.php +++ b/config/config.php @@ -31,6 +31,7 @@ // Default App module config \Light\App\ConfigProvider::class, \Light\Page\ConfigProvider::class, + \Light\Book\ConfigProvider::class, // Load application config in a pre-defined order in such a way that local settings // overwrite global settings. (Loaded as first to last): diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index f41f864..d2aae50 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -6,13 +6,15 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\ORM\Mapping\EntityListenerResolver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Dot\Cache\Adapter\ArrayAdapter; use Dot\Cache\Adapter\FilesystemAdapter; +use Light\App\Factory\EntityListenerResolverFactory; use Light\App\Factory\GetIndexViewHandlerFactory; use Light\App\Handler\GetIndexViewHandler; +use Light\App\Types\UuidType; use Mezzio\Application; -use Ramsey\Uuid\Doctrine\UuidType; use Roave\PsrContainerDoctrine\EntityManagerFactory; use function getcwd; @@ -51,6 +53,8 @@ public function getDependencies(): array 'factories' => [ 'doctrine.entity_manager.orm_default' => EntityManagerFactory::class, GetIndexViewHandler::class => GetIndexViewHandlerFactory::class, + EntityListenerResolver::class => EntityListenerResolverFactory::class, + ], 'aliases' => [ EntityManager::class => 'doctrine.entity_manager.orm_default', @@ -96,12 +100,13 @@ private function getDoctrineConfig(): array ], 'configuration' => [ 'orm_default' => [ - 'result_cache' => 'filesystem', - 'metadata_cache' => 'filesystem', - 'query_cache' => 'filesystem', - 'hydration_cache' => 'array', - 'typed_field_mapper' => null, - 'second_level_cache' => [ + 'entity_listener_resolver' => EntityListenerResolver::class, + 'result_cache' => 'filesystem', + 'metadata_cache' => 'filesystem', + 'query_cache' => 'filesystem', + 'hydration_cache' => 'array', + 'typed_field_mapper' => null, + 'second_level_cache' => [ 'enabled' => true, 'default_lifetime' => 3600, 'default_lock_lifetime' => 60, @@ -117,6 +122,21 @@ private function getDoctrineConfig(): array 'class' => MappingDriverChain::class, ], ], + 'migrations' => [ + 'table_storage' => [ + 'table_name' => 'doctrine_migration_versions', + 'version_column_name' => 'version', + 'version_column_length' => 191, + 'executed_at_column_name' => 'executed_at', + 'execution_time_column_name' => 'execution_time', + ], + // Modify this line based on where you would like to have you migrations + 'migrations_paths' => [ + 'Migrations' => 'src/Migrations', + ], + 'all_or_nothing' => true, + 'check_database_platform' => true, + ], 'types' => [ UuidType::NAME => UuidType::class, ], diff --git a/src/App/src/Entity/AbstractEntity.php b/src/App/src/Entity/AbstractEntity.php new file mode 100644 index 0000000..c88a233 --- /dev/null +++ b/src/App/src/Entity/AbstractEntity.php @@ -0,0 +1,57 @@ +initId(); + } + + protected function initId(): void + { + } + + /** + * Override this method in soft-deletable entities + */ + public function isDeleted(): bool + { + return false; + } + + /** + * @param array $array + */ + public function exchangeArray(array $array): void + { + foreach ($array as $property => $values) { + if (is_array($values)) { + $method = 'add' . ucfirst($property); + if (! method_exists($this, $method)) { + continue; + } + foreach ($values as $value) { + $this->$method($value); + } + } else { + $method = 'set' . ucfirst($property); + if (! method_exists($this, $method)) { + continue; + } + $this->$method($values); + } + } + } +} diff --git a/src/App/src/Entity/TimestampsTrait.php b/src/App/src/Entity/TimestampsTrait.php new file mode 100644 index 0000000..d004f47 --- /dev/null +++ b/src/App/src/Entity/TimestampsTrait.php @@ -0,0 +1,53 @@ +created; + } + + public function getCreatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): string + { + return $this->created->format($dateFormat); + } + + public function getUpdated(): ?DateTimeImmutable + { + return $this->updated; + } + + public function getUpdatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): ?string + { + if ($this->updated instanceof DateTimeImmutable) { + return $this->updated->format($dateFormat); + } + + return null; + } + + #[ORM\PrePersist] + public function created(): void + { + $this->created = new DateTimeImmutable(); + } + + #[ORM\PreUpdate] + public function touch(): void + { + $this->updated = new DateTimeImmutable(); + } +} diff --git a/src/App/src/Entity/UuidIdentifierTrait.php b/src/App/src/Entity/UuidIdentifierTrait.php new file mode 100644 index 0000000..4dc9c98 --- /dev/null +++ b/src/App/src/Entity/UuidIdentifierTrait.php @@ -0,0 +1,33 @@ +uuid; + } + + public function setId(UuidInterface $id): static + { + $this->uuid = $id; + + return $this; + } + + protected function initId(): void + { + $this->uuid = Uuid::uuid7(); + } +} diff --git a/src/App/src/Factory/EntityListenerResolverFactory.php b/src/App/src/Factory/EntityListenerResolverFactory.php new file mode 100644 index 0000000..71b97ad --- /dev/null +++ b/src/App/src/Factory/EntityListenerResolverFactory.php @@ -0,0 +1,16 @@ +container->get($className); + } +} diff --git a/src/App/src/Types/UuidType.php b/src/App/src/Types/UuidType.php new file mode 100644 index 0000000..1132170 --- /dev/null +++ b/src/App/src/Types/UuidType.php @@ -0,0 +1,17 @@ + $this->getDependencies(), + 'doctrine' => $this->getDoctrineConfig(), + ]; + } + + private function getDependencies(): array + { + return [ + 'delegators' => [ + ], + 'factories' => [ + ], + ]; + } + + private function getDoctrineConfig(): array + { + return [ + 'driver' => [ + 'orm_default' => [ + 'drivers' => [ + 'Light\Book\Entity' => 'BookEntities', + ], + ], + 'BookEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => [__DIR__ . '/Entity'], + ], + ], + ]; + } +} diff --git a/src/Book/src/Entity/Book.php b/src/Book/src/Entity/Book.php new file mode 100644 index 0000000..c5c20d4 --- /dev/null +++ b/src/Book/src/Entity/Book.php @@ -0,0 +1,60 @@ +created(); + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): void + { + $this->title = $title; + } + + public function getAuthor(): ?string + { + return $this->author; + } + + public function setAuthor(string $author): void + { + $this->author = $author; + } + + public function getArrayCopy(): array + { + return [ + 'title' => $this->title, + 'author' => $this->author, + ]; + } +} diff --git a/src/Migrations/Version20251125173634.php b/src/Migrations/Version20251125173634.php new file mode 100644 index 0000000..b15e48a --- /dev/null +++ b/src/Migrations/Version20251125173634.php @@ -0,0 +1,31 @@ +addSql('CREATE TABLE books (title VARCHAR(500) DEFAULT NULL, author VARCHAR(500) DEFAULT NULL, id UUID NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('DROP TABLE books'); + } +} From fe04d0f1fcc48b215332c9d042b162a7ceb69782 Mon Sep 17 00:00:00 2001 From: Howriq Date: Tue, 25 Nov 2025 20:16:16 +0200 Subject: [PATCH 03/12] Code chapter 2 Signed-off-by: Howriq --- src/App/src/ConfigProvider.php | 3 +-- src/App/src/Types/UuidType.php | 2 +- src/Book/src/ConfigProvider.php | 7 ++----- src/Book/src/Entity/Book.php | 6 +++--- 4 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index d2aae50..4bbb641 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -54,7 +54,6 @@ public function getDependencies(): array 'doctrine.entity_manager.orm_default' => EntityManagerFactory::class, GetIndexViewHandler::class => GetIndexViewHandlerFactory::class, EntityListenerResolver::class => EntityListenerResolverFactory::class, - ], 'aliases' => [ EntityManager::class => 'doctrine.entity_manager.orm_default', @@ -123,7 +122,7 @@ private function getDoctrineConfig(): array ], ], 'migrations' => [ - 'table_storage' => [ + 'table_storage' => [ 'table_name' => 'doctrine_migration_versions', 'version_column_name' => 'version', 'version_column_length' => 191, diff --git a/src/App/src/Types/UuidType.php b/src/App/src/Types/UuidType.php index 1132170..05bb095 100644 --- a/src/App/src/Types/UuidType.php +++ b/src/App/src/Types/UuidType.php @@ -14,4 +14,4 @@ public function getSQLDeclaration(array $column, AbstractPlatform $platform): st { return 'UUID'; } -} \ No newline at end of file +} diff --git a/src/Book/src/ConfigProvider.php b/src/Book/src/ConfigProvider.php index b3ee4e6..878cc83 100644 --- a/src/Book/src/ConfigProvider.php +++ b/src/Book/src/ConfigProvider.php @@ -5,7 +5,6 @@ namespace Light\Book; use Doctrine\ORM\Mapping\Driver\AttributeDriver; -use Light\App\Types\UuidType; class ConfigProvider { @@ -20,10 +19,8 @@ public function __invoke(): array private function getDependencies(): array { return [ - 'delegators' => [ - ], - 'factories' => [ - ], + 'delegators' => [], + 'factories' => [], ]; } diff --git a/src/Book/src/Entity/Book.php b/src/Book/src/Entity/Book.php index c5c20d4..0e93ad0 100644 --- a/src/Book/src/Entity/Book.php +++ b/src/Book/src/Entity/Book.php @@ -4,18 +4,18 @@ namespace Light\Book\Entity; -use Light\App\Entity\UuidIdentifierTrait; use Doctrine\ORM\Mapping as ORM; use Light\App\Entity\AbstractEntity; use Light\App\Entity\TimestampsTrait; +use Light\App\Entity\UuidIdentifierTrait; #[ORM\Entity] #[ORM\Table(name: 'books')] #[ORM\HasLifecycleCallbacks] class Book extends AbstractEntity { - use UuidIdentifierTrait; use TimestampsTrait; + use UuidIdentifierTrait; #[ORM\Column(name: 'title', type: 'string', length: 500, nullable: true)] private ?string $title = null; @@ -53,7 +53,7 @@ public function setAuthor(string $author): void public function getArrayCopy(): array { return [ - 'title' => $this->title, + 'title' => $this->title, 'author' => $this->author, ]; } From b887f5ff16dd6b036202e7d4bde7664a82dce290 Mon Sep 17 00:00:00 2001 From: Howriq Date: Tue, 25 Nov 2025 23:09:40 +0200 Subject: [PATCH 04/12] Code chapter 2 Signed-off-by: Howriq --- README.md | 16 -------- data/cache/.gitignore | 2 - mkdocs.yml | 16 -------- phpcs.xml | 4 +- src/App/src/ConfigProvider.php | 68 ++++++++++++++++++++++++++++----- src/Book/src/ConfigProvider.php | 31 +++++++++++++++ src/Book/src/Entity/Book.php | 6 +++ 7 files changed, 99 insertions(+), 44 deletions(-) delete mode 100644 README.md delete mode 100755 data/cache/.gitignore delete mode 100644 mkdocs.yml diff --git a/README.md b/README.md deleted file mode 100644 index 4e4f9d2..0000000 --- a/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# tutorial-101 -Book tutorial starting from Dotkernel Light: Level 101 beginner - -### BRANCH : - main unde e documentatia , se compileaza documentatia. aplicatie din light, deploy functional !!!! --> 101.dotkernel.net - 1. instalezi light la tine pe WSL2 - 2. iei chapter 1, citesti documentation, implementezi una cite una - - CHAPTER-1-doctrine # protected - including Light, and add visible DIFF between branches - - CHAPTER-2- forms # protected - - CHAPTER-3- inputfilter # protected - - CHAPTER-4 -list books # protected - - main: incepi de la light , clone - - adaigi folderul docs/book cu structura asta https://github.com/dotkernel/dot-session/tree/5.0/docs/book - - cu fisier symlink si etc. - - diff --git a/data/cache/.gitignore b/data/cache/.gitignore deleted file mode 100755 index d6b7ef3..0000000 --- a/data/cache/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/mkdocs.yml b/mkdocs.yml deleted file mode 100644 index aa23067..0000000 --- a/mkdocs.yml +++ /dev/null @@ -1,16 +0,0 @@ -docs_dir: docs/book -site_dir: docs/html -extra: - project: Tutorial-101 - current_version: v1 - versions: - - v1 -nav: - - Home: index.md - - book: - - Chapter 1: book/chapter-1.md -site_name: 101.dotkernel -site_description: "Beginner tutorial for using Dotkernel" -repo_url: "https://github.com/dotkernel/tutorial-101#" -plugins: - - search \ No newline at end of file diff --git a/phpcs.xml b/phpcs.xml index c6b0c09..7110499 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -19,5 +19,7 @@ test - + + src/Migrations/* + diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index 4bbb641..d41c4bb 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -6,26 +6,76 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\EntityListenerResolver; +use Doctrine\ORM\Mapping\EntityListenerResolver as EntityListenerResolverInterface; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Dot\Cache\Adapter\ArrayAdapter; use Dot\Cache\Adapter\FilesystemAdapter; use Light\App\Factory\EntityListenerResolverFactory; use Light\App\Factory\GetIndexViewHandlerFactory; use Light\App\Handler\GetIndexViewHandler; +use Light\App\Resolver\EntityListenerResolver; use Light\App\Types\UuidType; use Mezzio\Application; use Roave\PsrContainerDoctrine\EntityManagerFactory; +use Symfony\Component\Cache\Adapter\AdapterInterface; use function getcwd; +/** + * @phpstan-type ConfigType array{ + * dependencies: DependenciesType, + * doctrine: DoctrineConfigType, + * } + * @phpstan-type DoctrineConfigType array{ + * cache: array{ + * array: array{ + * class: class-string, + * }, + * filesystem: array{ + * class: class-string, + * directory: non-empty-string, + * namespace: non-empty-string, + * }, + * }, + * configuration: array{ + * orm_default: array{ + * entity_listener_resolver: class-string, + * result_cache: non-empty-string, + * metadata_cache: non-empty-string, + * query_cache: non-empty-string, + * hydration_cache: non-empty-string, + * typed_field_mapper: non-empty-string|null, + * second_level_cache: array{ + * enabled: bool, + * default_lifetime: int, + * default_lock_lifetime: int, + * file_lock_region_directory: string, + * regions: string[], + * }, + * }, + * }, + * driver: array{ + * orm_default: array{ + * class: class-string, + * }, + * }, + * migrations: array{ + * migrations_paths: array, + * all_or_nothing: bool, + * check_database_platform: bool, + * }, + * types: array, + * } + * @phpstan-type DependenciesType array{ + * factories: array, + * aliases: array, + * } + **/ class ConfigProvider { /** - @return array{ - * dependencies: array, - * templates: array, - * } + * @return ConfigType */ public function __invoke(): array { @@ -37,10 +87,7 @@ public function __invoke(): array } /** - * @return array{ - * delegators: array>, - * factories: array, - * } + * @return DependenciesType */ public function getDependencies(): array { @@ -84,6 +131,9 @@ public function getTemplates(): array ]; } + /** + * @return DoctrineConfigType + */ private function getDoctrineConfig(): array { return [ diff --git a/src/Book/src/ConfigProvider.php b/src/Book/src/ConfigProvider.php index 878cc83..e08b054 100644 --- a/src/Book/src/ConfigProvider.php +++ b/src/Book/src/ConfigProvider.php @@ -5,9 +5,34 @@ namespace Light\Book; use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Doctrine\Persistence\Mapping\Driver\MappingDriver; +/** + * @phpstan-type ConfigType array{ + * dependencies: DependenciesType, + * doctrine: DoctrineConfigType, + * } + * @phpstan-type DoctrineConfigType array{ + * driver: array{ + * orm_default: array{ + * drivers: array, + * }, + * BookEntities: array{ + * class: class-string, + * cache: non-empty-string, + * paths: non-empty-string[], + * }, + * }, + * } + * @phpstan-type DependenciesType array{ + * factories: array, + * } + */ class ConfigProvider { + /** + * @return ConfigType + */ public function __invoke(): array { return [ @@ -16,6 +41,9 @@ public function __invoke(): array ]; } + /** + * @return DependenciesType + */ private function getDependencies(): array { return [ @@ -24,6 +52,9 @@ private function getDependencies(): array ]; } + /** + * @return DoctrineConfigType + */ private function getDoctrineConfig(): array { return [ diff --git a/src/Book/src/Entity/Book.php b/src/Book/src/Entity/Book.php index 0e93ad0..1dbfb91 100644 --- a/src/Book/src/Entity/Book.php +++ b/src/Book/src/Entity/Book.php @@ -50,6 +50,12 @@ public function setAuthor(string $author): void $this->author = $author; } + /** + * @return array{ + * title:string|null, + * author:string|null + * } + */ public function getArrayCopy(): array { return [ From 776f323719c9ad0504ca231f14f8bd81f6a7b90d Mon Sep 17 00:00:00 2001 From: Howriq Date: Tue, 25 Nov 2025 23:10:14 +0200 Subject: [PATCH 05/12] Code chapter 2 Signed-off-by: Howriq --- data/.gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 data/.gitignore diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore From b5a00a775696b837faece2209e15929db1b4cd84 Mon Sep 17 00:00:00 2001 From: Howriq Date: Tue, 25 Nov 2025 23:43:27 +0200 Subject: [PATCH 06/12] Code chapter 2 Signed-off-by: Howriq --- src/App/src/ConfigProvider.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index d41c4bb..7dbfd2f 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -18,8 +18,8 @@ use Light\App\Types\UuidType; use Mezzio\Application; use Roave\PsrContainerDoctrine\EntityManagerFactory; - use Symfony\Component\Cache\Adapter\AdapterInterface; + use function getcwd; /** From d1c5d966d62fc40238dc64c88bb4abab4288e888 Mon Sep 17 00:00:00 2001 From: Howriq Date: Wed, 26 Nov 2025 22:31:48 +0200 Subject: [PATCH 07/12] Code chapter 2 Signed-off-by: Howriq --- data/{ => cache}/.gitignore | 0 src/App/src/ConfigProvider.php | 18 ++---- src/App/src/Entity/AbstractEntity.php | 63 ++++++++++++++++--- src/App/src/Entity/EntityInterface.php | 21 +++++++ src/App/src/Entity/TimestampsTrait.php | 53 ---------------- src/App/src/Entity/UuidIdentifierTrait.php | 33 ---------- .../Factory/EntityListenerResolverFactory.php | 16 ----- src/App/src/Repository/AbstractRepository.php | 32 ++++++++++ src/Book/src/ConfigProvider.php | 18 +----- src/Book/src/Entity/Book.php | 10 +-- src/Book/src/Repository/BookRepository.php | 14 +++++ src/Migrations/.gitkeep | 0 src/Migrations/Version20251125173634.php | 31 --------- 13 files changed, 132 insertions(+), 177 deletions(-) rename data/{ => cache}/.gitignore (100%) create mode 100644 src/App/src/Entity/EntityInterface.php delete mode 100644 src/App/src/Entity/TimestampsTrait.php delete mode 100644 src/App/src/Entity/UuidIdentifierTrait.php delete mode 100644 src/App/src/Factory/EntityListenerResolverFactory.php create mode 100644 src/App/src/Repository/AbstractRepository.php create mode 100644 src/Book/src/Repository/BookRepository.php create mode 100644 src/Migrations/.gitkeep delete mode 100644 src/Migrations/Version20251125173634.php diff --git a/data/.gitignore b/data/cache/.gitignore similarity index 100% rename from data/.gitignore rename to data/cache/.gitignore diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index 7dbfd2f..cd55997 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -6,15 +6,12 @@ use Doctrine\ORM\EntityManager; use Doctrine\ORM\EntityManagerInterface; -use Doctrine\ORM\Mapping\EntityListenerResolver as EntityListenerResolverInterface; use Doctrine\Persistence\Mapping\Driver\MappingDriver; use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Dot\Cache\Adapter\ArrayAdapter; use Dot\Cache\Adapter\FilesystemAdapter; -use Light\App\Factory\EntityListenerResolverFactory; use Light\App\Factory\GetIndexViewHandlerFactory; use Light\App\Handler\GetIndexViewHandler; -use Light\App\Resolver\EntityListenerResolver; use Light\App\Types\UuidType; use Mezzio\Application; use Roave\PsrContainerDoctrine\EntityManagerFactory; @@ -40,7 +37,6 @@ * }, * configuration: array{ * orm_default: array{ - * entity_listener_resolver: class-string, * result_cache: non-empty-string, * metadata_cache: non-empty-string, * query_cache: non-empty-string, @@ -100,7 +96,6 @@ public function getDependencies(): array 'factories' => [ 'doctrine.entity_manager.orm_default' => EntityManagerFactory::class, GetIndexViewHandler::class => GetIndexViewHandlerFactory::class, - EntityListenerResolver::class => EntityListenerResolverFactory::class, ], 'aliases' => [ EntityManager::class => 'doctrine.entity_manager.orm_default', @@ -149,13 +144,12 @@ private function getDoctrineConfig(): array ], 'configuration' => [ 'orm_default' => [ - 'entity_listener_resolver' => EntityListenerResolver::class, - 'result_cache' => 'filesystem', - 'metadata_cache' => 'filesystem', - 'query_cache' => 'filesystem', - 'hydration_cache' => 'array', - 'typed_field_mapper' => null, - 'second_level_cache' => [ + 'result_cache' => 'filesystem', + 'metadata_cache' => 'filesystem', + 'query_cache' => 'filesystem', + 'hydration_cache' => 'array', + 'typed_field_mapper' => null, + 'second_level_cache' => [ 'enabled' => true, 'default_lifetime' => 3600, 'default_lock_lifetime' => 60, diff --git a/src/App/src/Entity/AbstractEntity.php b/src/App/src/Entity/AbstractEntity.php index c88a233..b1b5fdc 100644 --- a/src/App/src/Entity/AbstractEntity.php +++ b/src/App/src/Entity/AbstractEntity.php @@ -4,8 +4,11 @@ namespace Light\App\Entity; +use DateTimeImmutable; use Doctrine\ORM\Mapping as ORM; use Laminas\Stdlib\ArraySerializableInterface; +use Ramsey\Uuid\Uuid; +use Ramsey\Uuid\UuidInterface; use function is_array; use function method_exists; @@ -14,21 +17,67 @@ #[ORM\MappedSuperclass] abstract class AbstractEntity implements ArraySerializableInterface { + #[ORM\Column(name: 'created', type: 'datetime_immutable', nullable: false)] + protected DateTimeImmutable $created; + + #[ORM\Column(name: 'updated', type: 'datetime_immutable', nullable: true)] + protected ?DateTimeImmutable $updated = null; + + #[ORM\Id] + #[ORM\Column(name: 'id', type: 'uuid', unique: true, nullable: false)] + protected UuidInterface $uuid; + + #[ORM\PrePersist] + public function created(): void + { + $this->created = new DateTimeImmutable(); + } + + #[ORM\PreUpdate] + public function touch(): void + { + $this->updated = new DateTimeImmutable(); + } + public function __construct() { - $this->initId(); + $this->uuid = Uuid::uuid7(); } - protected function initId(): void + public function getId(): UuidInterface { + return $this->uuid; } - /** - * Override this method in soft-deletable entities - */ - public function isDeleted(): bool + public function setId(UuidInterface $id): static + { + $this->uuid = $id; + + return $this; + } + + public function getCreated(): ?DateTimeImmutable { - return false; + return $this->created; + } + + public function getCreatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): string + { + return $this->created->format($dateFormat); + } + + public function getUpdated(): ?DateTimeImmutable + { + return $this->updated; + } + + public function getUpdatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): ?string + { + if ($this->updated instanceof DateTimeImmutable) { + return $this->updated->format($dateFormat); + } + + return null; } /** diff --git a/src/App/src/Entity/EntityInterface.php b/src/App/src/Entity/EntityInterface.php new file mode 100644 index 0000000..f84aa49 --- /dev/null +++ b/src/App/src/Entity/EntityInterface.php @@ -0,0 +1,21 @@ +created; - } - - public function getCreatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): string - { - return $this->created->format($dateFormat); - } - - public function getUpdated(): ?DateTimeImmutable - { - return $this->updated; - } - - public function getUpdatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): ?string - { - if ($this->updated instanceof DateTimeImmutable) { - return $this->updated->format($dateFormat); - } - - return null; - } - - #[ORM\PrePersist] - public function created(): void - { - $this->created = new DateTimeImmutable(); - } - - #[ORM\PreUpdate] - public function touch(): void - { - $this->updated = new DateTimeImmutable(); - } -} diff --git a/src/App/src/Entity/UuidIdentifierTrait.php b/src/App/src/Entity/UuidIdentifierTrait.php deleted file mode 100644 index 4dc9c98..0000000 --- a/src/App/src/Entity/UuidIdentifierTrait.php +++ /dev/null @@ -1,33 +0,0 @@ -uuid; - } - - public function setId(UuidInterface $id): static - { - $this->uuid = $id; - - return $this; - } - - protected function initId(): void - { - $this->uuid = Uuid::uuid7(); - } -} diff --git a/src/App/src/Factory/EntityListenerResolverFactory.php b/src/App/src/Factory/EntityListenerResolverFactory.php deleted file mode 100644 index 71b97ad..0000000 --- a/src/App/src/Factory/EntityListenerResolverFactory.php +++ /dev/null @@ -1,16 +0,0 @@ - + */ +class AbstractRepository extends EntityRepository +{ + public function deleteResource(EntityInterface $resource): void + { + $this->getEntityManager()->remove($resource); + $this->getEntityManager()->flush(); + } + + public function getQueryBuilder(): QueryBuilder + { + return $this->getEntityManager()->createQueryBuilder(); + } + + public function saveResource(EntityInterface $resource): void + { + $this->getEntityManager()->persist($resource); + $this->getEntityManager()->flush(); + } +} diff --git a/src/Book/src/ConfigProvider.php b/src/Book/src/ConfigProvider.php index e08b054..063acbc 100644 --- a/src/Book/src/ConfigProvider.php +++ b/src/Book/src/ConfigProvider.php @@ -9,7 +9,6 @@ /** * @phpstan-type ConfigType array{ - * dependencies: DependenciesType, * doctrine: DoctrineConfigType, * } * @phpstan-type DoctrineConfigType array{ @@ -24,9 +23,6 @@ * }, * }, * } - * @phpstan-type DependenciesType array{ - * factories: array, - * } */ class ConfigProvider { @@ -36,19 +32,7 @@ class ConfigProvider public function __invoke(): array { return [ - 'dependencies' => $this->getDependencies(), - 'doctrine' => $this->getDoctrineConfig(), - ]; - } - - /** - * @return DependenciesType - */ - private function getDependencies(): array - { - return [ - 'delegators' => [], - 'factories' => [], + 'doctrine' => $this->getDoctrineConfig(), ]; } diff --git a/src/Book/src/Entity/Book.php b/src/Book/src/Entity/Book.php index 1dbfb91..2d56194 100644 --- a/src/Book/src/Entity/Book.php +++ b/src/Book/src/Entity/Book.php @@ -6,17 +6,13 @@ use Doctrine\ORM\Mapping as ORM; use Light\App\Entity\AbstractEntity; -use Light\App\Entity\TimestampsTrait; -use Light\App\Entity\UuidIdentifierTrait; +use Light\Book\Repository\BookRepository; -#[ORM\Entity] +#[ORM\Entity(repositoryClass: BookRepository::class)] #[ORM\Table(name: 'books')] #[ORM\HasLifecycleCallbacks] class Book extends AbstractEntity { - use TimestampsTrait; - use UuidIdentifierTrait; - #[ORM\Column(name: 'title', type: 'string', length: 500, nullable: true)] private ?string $title = null; @@ -26,8 +22,6 @@ class Book extends AbstractEntity public function __construct() { parent::__construct(); - - $this->created(); } public function getTitle(): ?string diff --git a/src/Book/src/Repository/BookRepository.php b/src/Book/src/Repository/BookRepository.php new file mode 100644 index 0000000..44352b0 --- /dev/null +++ b/src/Book/src/Repository/BookRepository.php @@ -0,0 +1,14 @@ +addSql('CREATE TABLE books (title VARCHAR(500) DEFAULT NULL, author VARCHAR(500) DEFAULT NULL, id UUID NOT NULL, created DATETIME NOT NULL, updated DATETIME DEFAULT NULL, PRIMARY KEY (id)) DEFAULT CHARACTER SET utf8mb4'); - } - - public function down(Schema $schema): void - { - // this down() migration is auto-generated, please modify it to your needs - $this->addSql('DROP TABLE books'); - } -} From a93cdc8c81591b4495b4c5bddf990eb115289526 Mon Sep 17 00:00:00 2001 From: Howriq Date: Thu, 27 Nov 2025 09:57:45 +0200 Subject: [PATCH 08/12] Code chapter 2 Signed-off-by: Howriq --- README.md | 2 + phpcs.xml | 4 +- src/App/src/ConfigProvider.php | 4 +- src/App/src/{ => DBAL}/Types/UuidType.php | 2 +- src/App/src/Entity/AbstractEntity.php | 38 +++++++++---------- .../src/Migration}/.gitkeep | 0 .../src/Resolver/EntityListenerResolver.php | 27 ------------- src/Book/src/Entity/Book.php | 9 +---- 8 files changed, 27 insertions(+), 59 deletions(-) create mode 100644 README.md rename src/App/src/{ => DBAL}/Types/UuidType.php (90%) rename src/{Migrations => App/src/Migration}/.gitkeep (100%) delete mode 100644 src/App/src/Resolver/EntityListenerResolver.php diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c6c934 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# tutorial-101 +Book tutorial starting from Dotkernel Light: Level 101 beginner diff --git a/phpcs.xml b/phpcs.xml index 7110499..c6b0c09 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -19,7 +19,5 @@ test - - src/Migrations/* - + diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index cd55997..69f372f 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -10,9 +10,9 @@ use Doctrine\Persistence\Mapping\Driver\MappingDriverChain; use Dot\Cache\Adapter\ArrayAdapter; use Dot\Cache\Adapter\FilesystemAdapter; +use Light\App\DBAL\Types\UuidType; use Light\App\Factory\GetIndexViewHandlerFactory; use Light\App\Handler\GetIndexViewHandler; -use Light\App\Types\UuidType; use Mezzio\Application; use Roave\PsrContainerDoctrine\EntityManagerFactory; use Symfony\Component\Cache\Adapter\AdapterInterface; @@ -175,7 +175,7 @@ private function getDoctrineConfig(): array ], // Modify this line based on where you would like to have you migrations 'migrations_paths' => [ - 'Migrations' => 'src/Migrations', + 'Migrations' => 'src/App/src/Migration', ], 'all_or_nothing' => true, 'check_database_platform' => true, diff --git a/src/App/src/Types/UuidType.php b/src/App/src/DBAL/Types/UuidType.php similarity index 90% rename from src/App/src/Types/UuidType.php rename to src/App/src/DBAL/Types/UuidType.php index 05bb095..aafa1ac 100644 --- a/src/App/src/Types/UuidType.php +++ b/src/App/src/DBAL/Types/UuidType.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Light\App\Types; +namespace Light\App\DBAL\Types; use Doctrine\DBAL\Platforms\AbstractPlatform; diff --git a/src/App/src/Entity/AbstractEntity.php b/src/App/src/Entity/AbstractEntity.php index b1b5fdc..6eeb41e 100644 --- a/src/App/src/Entity/AbstractEntity.php +++ b/src/App/src/Entity/AbstractEntity.php @@ -17,41 +17,29 @@ #[ORM\MappedSuperclass] abstract class AbstractEntity implements ArraySerializableInterface { + #[ORM\Id] + #[ORM\Column(name: 'id', type: 'uuid', unique: true, nullable: false)] + protected UuidInterface $id; + #[ORM\Column(name: 'created', type: 'datetime_immutable', nullable: false)] protected DateTimeImmutable $created; #[ORM\Column(name: 'updated', type: 'datetime_immutable', nullable: true)] protected ?DateTimeImmutable $updated = null; - #[ORM\Id] - #[ORM\Column(name: 'id', type: 'uuid', unique: true, nullable: false)] - protected UuidInterface $uuid; - - #[ORM\PrePersist] - public function created(): void - { - $this->created = new DateTimeImmutable(); - } - - #[ORM\PreUpdate] - public function touch(): void - { - $this->updated = new DateTimeImmutable(); - } - public function __construct() { - $this->uuid = Uuid::uuid7(); + $this->id = Uuid::uuid7(); } public function getId(): UuidInterface { - return $this->uuid; + return $this->id; } public function setId(UuidInterface $id): static { - $this->uuid = $id; + $this->id = $id; return $this; } @@ -80,6 +68,18 @@ public function getUpdatedFormatted(string $dateFormat = 'Y-m-d H:i:s'): ?string return null; } + #[ORM\PrePersist] + public function created(): void + { + $this->created = new DateTimeImmutable(); + } + + #[ORM\PreUpdate] + public function touch(): void + { + $this->updated = new DateTimeImmutable(); + } + /** * @param array $array */ diff --git a/src/Migrations/.gitkeep b/src/App/src/Migration/.gitkeep similarity index 100% rename from src/Migrations/.gitkeep rename to src/App/src/Migration/.gitkeep diff --git a/src/App/src/Resolver/EntityListenerResolver.php b/src/App/src/Resolver/EntityListenerResolver.php deleted file mode 100644 index 2f32b0c..0000000 --- a/src/App/src/Resolver/EntityListenerResolver.php +++ /dev/null @@ -1,27 +0,0 @@ -container->get($className); - } -} diff --git a/src/Book/src/Entity/Book.php b/src/Book/src/Entity/Book.php index 2d56194..b73537a 100644 --- a/src/Book/src/Entity/Book.php +++ b/src/Book/src/Entity/Book.php @@ -19,11 +19,6 @@ class Book extends AbstractEntity #[ORM\Column(name: 'author', type: 'string', length: 500, nullable: true)] private ?string $author = null; - public function __construct() - { - parent::__construct(); - } - public function getTitle(): ?string { return $this->title; @@ -46,8 +41,8 @@ public function setAuthor(string $author): void /** * @return array{ - * title:string|null, - * author:string|null + * title: string|null, + * author: string|null * } */ public function getArrayCopy(): array From 8ff4031a68c322706b847519e7ec64c7285f0e82 Mon Sep 17 00:00:00 2001 From: Howriq Date: Thu, 27 Nov 2025 09:59:22 +0200 Subject: [PATCH 09/12] Code chapter 2 Signed-off-by: Howriq --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 6c6c934..4339d56 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,3 @@ # tutorial-101 + Book tutorial starting from Dotkernel Light: Level 101 beginner From 7a22f63a3589c3c0a16aecacaf8a4a2281c19e0a Mon Sep 17 00:00:00 2001 From: Howriq Date: Thu, 27 Nov 2025 10:27:20 +0200 Subject: [PATCH 10/12] Code chapter 2 Signed-off-by: Howriq --- src/App/src/ConfigProvider.php | 2 +- src/Book/src/Entity/Book.php | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php index 69f372f..fd5a51b 100644 --- a/src/App/src/ConfigProvider.php +++ b/src/App/src/ConfigProvider.php @@ -173,7 +173,7 @@ private function getDoctrineConfig(): array 'executed_at_column_name' => 'executed_at', 'execution_time_column_name' => 'execution_time', ], - // Modify this line based on where you would like to have you migrations + // Modify this line based on where you would like to have your migrations 'migrations_paths' => [ 'Migrations' => 'src/App/src/Migration', ], diff --git a/src/Book/src/Entity/Book.php b/src/Book/src/Entity/Book.php index b73537a..36d6472 100644 --- a/src/Book/src/Entity/Book.php +++ b/src/Book/src/Entity/Book.php @@ -7,17 +7,18 @@ use Doctrine\ORM\Mapping as ORM; use Light\App\Entity\AbstractEntity; use Light\Book\Repository\BookRepository; +use Ramsey\Uuid\UuidInterface; #[ORM\Entity(repositoryClass: BookRepository::class)] #[ORM\Table(name: 'books')] #[ORM\HasLifecycleCallbacks] class Book extends AbstractEntity { - #[ORM\Column(name: 'title', type: 'string', length: 500, nullable: true)] - private ?string $title = null; + #[ORM\Column(name: 'title', type: 'string', length: 500)] + private string $title; - #[ORM\Column(name: 'author', type: 'string', length: 500, nullable: true)] - private ?string $author = null; + #[ORM\Column(name: 'author', type: 'string', length: 500)] + private string $author; public function getTitle(): ?string { @@ -41,13 +42,15 @@ public function setAuthor(string $author): void /** * @return array{ - * title: string|null, - * author: string|null + * id: UuidInterface, + * title: string, + * author: string * } */ public function getArrayCopy(): array { return [ + 'id' => $this->id, 'title' => $this->title, 'author' => $this->author, ]; From 8761a5f32ed3da2e02085427a1898cd22ab4962e Mon Sep 17 00:00:00 2001 From: Howriq Date: Thu, 27 Nov 2025 10:36:17 +0200 Subject: [PATCH 11/12] Code chapter 2 Signed-off-by: Howriq --- src/Book/src/Entity/Book.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Book/src/Entity/Book.php b/src/Book/src/Entity/Book.php index 36d6472..bb24e86 100644 --- a/src/Book/src/Entity/Book.php +++ b/src/Book/src/Entity/Book.php @@ -7,7 +7,6 @@ use Doctrine\ORM\Mapping as ORM; use Light\App\Entity\AbstractEntity; use Light\Book\Repository\BookRepository; -use Ramsey\Uuid\UuidInterface; #[ORM\Entity(repositoryClass: BookRepository::class)] #[ORM\Table(name: 'books')] @@ -42,7 +41,7 @@ public function setAuthor(string $author): void /** * @return array{ - * id: UuidInterface, + * id: non-empty-string, * title: string, * author: string * } @@ -50,7 +49,7 @@ public function setAuthor(string $author): void public function getArrayCopy(): array { return [ - 'id' => $this->id, + 'id' => $this->id->toString(), 'title' => $this->title, 'author' => $this->author, ]; From cd1e8d98d1dc563373112842d16cf5fe6777581b Mon Sep 17 00:00:00 2001 From: Howriq Date: Tue, 9 Dec 2025 20:07:40 +0200 Subject: [PATCH 12/12] remove local.php Signed-off-by: Howriq --- ...ine-migrations.php => doctrine-migrations} | 0 config/autoload/local.php | 46 ------------------- 2 files changed, 46 deletions(-) rename bin/{doctrine-migrations.php => doctrine-migrations} (100%) delete mode 100644 config/autoload/local.php diff --git a/bin/doctrine-migrations.php b/bin/doctrine-migrations similarity index 100% rename from bin/doctrine-migrations.php rename to bin/doctrine-migrations diff --git a/config/autoload/local.php b/config/autoload/local.php deleted file mode 100644 index a58f3f8..0000000 --- a/config/autoload/local.php +++ /dev/null @@ -1,46 +0,0 @@ - [ - 'host' => 'localhost', - 'dbname' => 'light', - 'user' => 'root', - 'password' => '123', - 'port' => 3306, - 'driver' => 'pdo_mysql', - 'charset' => 'utf8mb4', - 'collate' => 'utf8mb4_general_ci', - ], - // you can add more database connections into this array -]; - -return [ - 'databases' => $databases, - 'doctrine' => [ - 'connection' => [ - 'orm_default' => [ - 'params' => $databases['default'], - ], - ], - ], - 'application' => [ - 'url' => $baseUrl, - ], - 'routes' => [ - 'page' => [ - 'about' => 'about', - 'who-we-are' => 'who-we-are', - ], - ], -];