diff --git a/README.md b/README.md index a102057..4339d56 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,3 @@ # 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/docs/book/v1/chapter-1.md b/docs/book/v1/chapter-1.md index 4640dc8..66dcb07 100644 --- a/docs/book/v1/chapter-1.md +++ b/docs/book/v1/chapter-1.md @@ -5,7 +5,7 @@ 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: -![composer.json](images/composer.png) +![composer.json](images/chapter-1/composer.png) ```text "dotkernel/dot-cache": "^4.0", @@ -47,7 +47,7 @@ After successfully installing our dependencies, we now need to configure our Doc In the file `config/autoload/local.php` the structure would be updated like this: -![local.php](images/local.png) +![local.php](images/chapter-1/local.png) ```php $databases = [ @@ -84,9 +84,9 @@ This package takes all the provided configs from the `config/config.php` file an Our new `src/App/src/ConfigProvider.php` class would look like this now: -![config-provider-1](images/config-provider-1.png) +![config-provider-1](images/chapter-1/config-provider-1.png) -![config-provider-2](images/config-provider-2.png) +![config-provider-2](images/chapter-1/config-provider-2.png) ```php public function __invoke(): array @@ -165,7 +165,7 @@ private function getDoctrineConfig(): array Now that everything has been configured we only need to do one last thing, to create an executable for the Doctrine CLI. In our case we will create a `doctrine` file inside the application's `bin` directory: -![doctrine](images/doctrine.png) +![doctrine](images/chapter-1/doctrine.png) ```php #!/usr/bin/env php diff --git a/docs/book/v1/chapter-2.md b/docs/book/v1/chapter-2.md new file mode 100644 index 0000000..a441360 --- /dev/null +++ b/docs/book/v1/chapter-2.md @@ -0,0 +1,517 @@ +# Entities and Migrations + +In the previous tutorial we have shown you how to install the basic functionality of Doctrine. +In this tutorial we are going to show you how to make changes to the database schema through `Entities` and `Migrations`. + +## What are Entities ? + +In Doctrine, entities are PHP classes that represent database tables. +Each property in the class corresponds to a column in the table. +Entities define the structure and behavior of your data. + +## What are Migrations ? + +Doctrine migrations are version-controlled changes to the database schema. +They allow you to safely update the structure of your database (create tables, add columns, etc.) over time — similar to Git for your database. + +## Setting Up the Migrations Functionality + +To enable Doctrine migrations, we configure the migrations section in the `getDoctrineConfig()` function from `src/App/ConfigProvider`. +This defines `where migration files will be stored`, as well as `how the migration table is managed`. +The configuration includes settings for the migrations table, execution tracking, and the path where new migration classes will be generated. + +![config-migrations](images/chapter-2/config-migrations.png) + +```php +'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 your migrations + 'migrations_paths' => [ + 'Migrations' => 'src/App/src/Migration', + ], + 'all_or_nothing' => true, + 'check_database_platform' => true, +], +``` + +Along with those we also need to create the file `config/cli-config.php`. +This `cli-config.php` file sets up Doctrine so that you can run **migration commands** from the command line (CLI). +It connects the Doctrine Migrations system with your application's existing `EntityManager`. + +```php +get(EntityManager::class); +$entityManager->getEventManager(); + +return DependencyFactory::fromEntityManager( + new ConfigurationArray($container->get('config')['doctrine']['migrations']), + new ExistingEntityManager($entityManager) +); +``` + +## Used UUID type + +In our latest releases we have decided to switch from a `binary UUID to a string UUID`. +Because of that, we are also implementing our own custom UUID type in the folder `src/App/src/DBAL/Types/UuidType.php`. +With this method we can also add our own custom types in the future. + +```php +id = Uuid::uuid7(); + } + + public function getId(): UuidInterface + { + return $this->id; + } + + public function setId(UuidInterface $id): static + { + $this->id = $id; + + return $this; + } + + public function getCreated(): ?DateTimeImmutable + { + 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; + } + + #[ORM\PrePersist] + public function created(): void + { + $this->created = new DateTimeImmutable(); + } + + #[ORM\PreUpdate] + public function touch(): void + { + $this->updated = new DateTimeImmutable(); + } + + /** + * @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); + } + } + } +} +``` + +We extend this class to **any Entity** that we create so that all can have an identifier(`id`), a field for knowing when it was `created` and one to mark a point in time for when we `update` it. + +It is time to finally time to work on our `Book` Entity. +We now create a new module with the location `src/Book/src`. +In this module we create a directory called `Entity` in which we shall create our `Book.php` class. + +```php +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; + } + + /** + * @return array{ + * id: non-empty-string, + * title: string, + * author: string + * } + */ + public function getArrayCopy(): array + { + return [ + 'id' => $this->id->toString(), + 'title' => $this->title, + 'author' => $this->author, + ]; + } +} +``` + +In the book class you can see various Doctrine tags such as `#[ORM\Table(name: 'books')]` which specify the name of the table that will be related to this Entity. +There are many tags that can be added, but for the sake of simplicity, we will stick to the table and the columns that will populate it. + +## Repositories + +In the class we created earlier, you may have noticed that our Entity is linked to a Repository using the following annotation: `#[ORM\Entity(repositoryClass: BookRepository::class)]`. +This specifies which Repository will handle queries for this Entity. +This allows us to write custom database logic for it. + +Same as for the Entities, we need to create a base and our future Repositories. +Because of that, we will create two new directories: `src/App/src/Repository` and `src/Book/src/Repository`. + +Two repositories shall be created: + +- `src/App/src/Repository/AbstractRepository.php` + +```php + + */ +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(); + } +} +``` + +- `src/App/src/Book/BookRepository.php` + +```php + $this->getDoctrineConfig(), + ]; + } + + private function getDoctrineConfig(): array + { + return [ + 'driver' => [ + 'orm_default' => [ + 'drivers' => [ + 'Light\Book\Entity' => 'BookEntities', + ], + ], + 'BookEntities' => [ + 'class' => AttributeDriver::class, + 'cache' => 'array', + 'paths' => [__DIR__ . '/Entity'], + ], + ], + ]; + } +} +``` + +This class should be added in `config/config.php`. + +![config-config](images/chapter-2/config-config.png) + +```php +\Light\Book\ConfigProvider::class, +``` + +### Step 2 + +Registering the namespace is fairly simple. +We just add it in our `composer.json` file by adding `"Light\\Book\\": "src/Book/src/"` under the `autoload`.`psr-4` key: + +![composer](images/chapter-2/composer.png), + +```text +"Light\\Book\\": "src/Book/src/" +``` + +We now have to run the following command to have it registered: + +```shell +composer dump-autoload +``` + +The module is now registered. + +### Step 3 + +For the last step we will create a migration using our `bin/doctrine-migrations` file and then execute it: + +```shell +php bin/doctrine-migrations.php migrations:diff +``` + +You should get an output similar to: + +```terminaloutput +Generated new migration class to "src/App/src/Migration/Version20251127183637.php" +``` + +The `migrations:diff` flag tells Doctrine to compare our current database schema and create a file in which the differences will be transformed into SQL syntax. +Inside this file you should have something similar to: + +```php +final class Version20251127183637 extends AbstractMigration +{ + public function getDescription(): string + { + return ''; + } + + public function up(Schema $schema): void + { + // this up() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE TABLE 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'); + } +} +``` + +We only have to execute the migrations now with the command: + +```shell +php bin/doctrine-migrations.php migrations:migrate +``` + +The output should be similar to this: + +```terminaloutput +[OK] Successfully migrated to version: Migrations\Version2025112718363 +``` diff --git a/docs/book/v1/images/composer.png b/docs/book/v1/images/chapter-1/composer.png similarity index 100% rename from docs/book/v1/images/composer.png rename to docs/book/v1/images/chapter-1/composer.png diff --git a/docs/book/v1/images/config-provider-1.png b/docs/book/v1/images/chapter-1/config-provider-1.png similarity index 100% rename from docs/book/v1/images/config-provider-1.png rename to docs/book/v1/images/chapter-1/config-provider-1.png diff --git a/docs/book/v1/images/config-provider-2.png b/docs/book/v1/images/chapter-1/config-provider-2.png similarity index 100% rename from docs/book/v1/images/config-provider-2.png rename to docs/book/v1/images/chapter-1/config-provider-2.png diff --git a/docs/book/v1/images/doctrine.png b/docs/book/v1/images/chapter-1/doctrine.png similarity index 100% rename from docs/book/v1/images/doctrine.png rename to docs/book/v1/images/chapter-1/doctrine.png diff --git a/docs/book/v1/images/local.png b/docs/book/v1/images/chapter-1/local.png similarity index 100% rename from docs/book/v1/images/local.png rename to docs/book/v1/images/chapter-1/local.png diff --git a/docs/book/v1/images/chapter-2/composer.png b/docs/book/v1/images/chapter-2/composer.png new file mode 100644 index 0000000..edc97dc Binary files /dev/null and b/docs/book/v1/images/chapter-2/composer.png differ diff --git a/docs/book/v1/images/chapter-2/config-config.png b/docs/book/v1/images/chapter-2/config-config.png new file mode 100644 index 0000000..d6bd043 Binary files /dev/null and b/docs/book/v1/images/chapter-2/config-config.png differ diff --git a/docs/book/v1/images/chapter-2/config-migrations.png b/docs/book/v1/images/chapter-2/config-migrations.png new file mode 100644 index 0000000..85fd162 Binary files /dev/null and b/docs/book/v1/images/chapter-2/config-migrations.png differ diff --git a/docs/book/v1/images/cli-config.png b/docs/book/v1/images/cli-config.png deleted file mode 100644 index 1549580..0000000 Binary files a/docs/book/v1/images/cli-config.png and /dev/null differ diff --git a/docs/book/v1/images/doctrine-migrations.png b/docs/book/v1/images/doctrine-migrations.png deleted file mode 100644 index 1dad7f0..0000000 Binary files a/docs/book/v1/images/doctrine-migrations.png and /dev/null differ diff --git a/mkdocs.yml b/mkdocs.yml index e767e89..cd61629 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,7 +9,8 @@ nav: - Home: index.md - Tutorial: - Introduction: v1/introduction.md - - Chapter 1: v1/chapter-1.md + - 1. Installing Doctrine: v1/chapter-1.md + - 2. Entities and Migrations: v1/chapter-2.md site_name: Dotkernel 101 site_description: "Beginner tutorial for using Dotkernel" repo_url: "https://github.com/dotkernel/tutorial-101"