Skip to content

Commit

Permalink
tec: Refactor initialization of meta fields
Browse files Browse the repository at this point in the history
  • Loading branch information
marien-probesys committed Feb 15, 2023
1 parent b5862d1 commit 3063858
Show file tree
Hide file tree
Showing 25 changed files with 241 additions and 51 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Developers guide:
- [Managing the dependencies](/docs/developers/dependencies.md)
- [Using the translations](/docs/developers/translations.md)
- [Working with the roles & permissions](/docs/developers/roles.md)
- [Declaring a new Entity](/docs/developers/entity.md)
- Frontend:
- [Working with the icons](/docs/developers/icons.md)
- [Working with the modals](/docs/developers/modals.md)
Expand Down
4 changes: 4 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ services:
- '../src/Entity/'
- '../src/Kernel.php'

App\EntityListener\EntitySetMetaListener:
tags:
- { name: doctrine.orm.entity_listener }

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
App\Twig\ViteTagsExtension:
Expand Down
123 changes: 123 additions & 0 deletions docs/developers/entity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Declaring a new Entity

Entities are declared under the [`src/Entity` directory](/src/Entity).

You can declare a new Entity with the default Symfony console command:

```console
$ ./docker/bin/console make:entity
```

Then, don’t forget to create a new migration with:

```console
$ make migration
```

You should adapt the generated migrations, at least to handle both PostgreSQL and MariaDB databases (more on that below).

Documentation: [symfony.com](https://symfony.com/doc/current/doctrine.html).

## Meta fields

All the entities must include a set of meta fields:

- `uid`: the id used in the URLs and forms (more difficult to guess than an incremental id)
- `createdAt`: the creation date of the entity
- `createdBy`: the user who created the entity

To handle that, please add the [`EntitySetMetaListener`](/src/EntityListener/EntitySetMetaListener.php) entity listener to your entity.
You must implement the [`MetaEntityInterface`](/src/Entity/MetaEntityInterface.php) interface too.
It requires to implements the setters and getters for the mentionned fields.

```php
namespace App\Entity;

use App\EntityListener\EntitySetMetaListener;
use Doctrine\ORM\Mapping as ORM;

#[ORM\EntityListeners([EntitySetMetaListener::class])]
class Foo implements MetaEntityInterface
{
// ...
}
```

For this to work, the corresponding repository must implements the [`UidGeneratorInterface`](/src/Repository/UidGeneratorInterface.php).
This can be easily done by using the trait [`UidGeneratorTrait`](/src/Repository/UidGeneratorTrait.php).

```php
namespace App\Repository;

use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;

class FooRepository extends ServiceEntityRepository implements UidGeneratorInterface
{
use UidGeneratorTrait;

// ...
}
```

## Migrations

Migrations can be generated with:

```console
$ make migration
```

And applied with:

```console
$ make db-migrate
```

### Rename the file

You should rename the generated file and class by appending a more comprehensive name.
For instance, rename `Version20230214161800` by `Version20230214161800CreateFoo` to indicate that the migration create the `foo` table.

This helps to find quickly a migration by simply browsing the files.

### Clear the file

Remove all the auto-generated comments, and add a comprehensive description in the `getDescription()` method.

### Add support for all databases types

Migrations must handle both PostgreSQL and MariaDB databases.
To do that, start by adding the following code to both `up()` and `down()` methods:

```php
$dbPlatform = $this->connection->getDatabasePlatform()->getName();
if ($dbPlatform === 'postgresql') {
// here goes the SQL queries for the PostgreSQL database
} elseif ($dbPlatform === 'mysql') {
// here goes the SQL queries for the MariaDB database
}
```

Then, generate the migration for the other database.
You can do that by changing the `DATABASE_URL` environment variable of the [`.env`](/.env) file (see the commented variable).
You must also reverse the commented database service in the [`docker-compose.yml`](/docker/docker-compose.yml) file and restart the Docker containers.
Don’t forget to re-setup the database:

```console
$ make db-reset FORCE=true
```

Then, re-run the `make migration` command, and move the generated code in the previous file.

You can now delete the last generated migration file, and reverse your changes:

```console
$ rm migrations/VersionXXXX.php
$ git checkout -- .env docker/docker-compose.yml
```

Restart the docker containers, and re-setup the database as previously.

**Note:** it is indeed quite inconvenient.
You’re very welcome to suggest a better system to handle migrations for several databases!
4 changes: 0 additions & 4 deletions src/Command/Users/CreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,6 @@ protected function execute(InputInterface $input, OutputInterface $output): int
$hashedPassword = $this->passwordHasher->hashPassword($user, $password);
$user->setPassword($hashedPassword);

$userUid = $this->userRepository->generateUid();
$user->setUid($userUid);
$user->setCreatedAt(Time::now());

$errors = $this->validator->validate($user);
if (count($errors) > 0) {
$output = $output instanceof ConsoleOutputInterface ? $output->getErrorOutput() : $output;
Expand Down
10 changes: 0 additions & 10 deletions src/Controller/Organizations/TicketsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,6 @@ public function create(
$ticket = new Ticket();
$ticket->setTitle($title);
$ticket->setStatus($status);

$uid = $ticketRepository->generateUid();
$ticket->setUid($uid);

$ticket->setCreatedAt(Time::now());
$ticket->setCreatedBy($user);
$ticket->setOrganization($organization);

$ticket->setRequester($requester);
Expand All @@ -210,10 +204,6 @@ public function create(

$message = new Message();
$message->setContent($messageContent);
$uid = $messageRepository->generateUid();
$message->setUid($uid);
$message->setCreatedAt(Time::now());
$message->setCreatedBy($user);
$message->setTicket($ticket);
$message->setIsConfidential(false);
$message->setVia('webapp');
Expand Down
4 changes: 0 additions & 4 deletions src/Controller/OrganizationsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -131,10 +131,6 @@ public function create(
$organization->setParent($selectedParentOrganization);
}

$uid = $orgaRepository->generateUid();
$organization->setUid($uid);
$organization->setCreatedAt(Time::now());

$errors = $validator->validate($organization);
if (count($errors) > 0) {
return $this->renderBadRequest('organizations/new.html.twig', [
Expand Down
5 changes: 0 additions & 5 deletions src/Controller/RolesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,6 @@ public function create(
}

$role = new Role();
$uid = $roleRepository->generateUid();
$role->setUid($uid);
$role->setCreatedAt(Time::now());
$role->setCreatedBy($user);

$role->setName($name);
$role->setDescription($description);
$role->setType($type);
Expand Down
4 changes: 0 additions & 4 deletions src/Controller/Tickets/MessagesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ public function create(

$message = new Message();
$message->setContent($messageContent);
$uid = $messageRepository->generateUid();
$message->setUid($uid);
$message->setCreatedAt(Time::now());
$message->setCreatedBy($user);
$message->setTicket($ticket);
$message->setIsConfidential($isConfidential);
$message->setVia('webapp');
Expand Down
3 changes: 0 additions & 3 deletions src/Controller/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,6 @@ public function create(
$newUser = new User();
$newUser->setEmail($email);
$newUser->setName($name);
$uid = $userRepository->generateUid();
$newUser->setUid($uid);
$newUser->setCreatedAt(Time::now());
$newUser->setLocale($user->getLocale());
$newUser->setPassword(Random::hex(50));

Expand Down
4 changes: 3 additions & 1 deletion src/Entity/Authorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,21 @@

namespace App\Entity;

use App\EntityListener\EntitySetMetaListener;
use App\Repository\AuthorizationRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Translation\TranslatableMessage;

#[ORM\Entity(repositoryClass: AuthorizationRepository::class)]
#[ORM\EntityListeners([EntitySetMetaListener::class])]
#[ORM\Table(name: '`authorizations`')]
#[UniqueEntity(
fields: 'uid',
message: new TranslatableMessage('The uid {{ value }} is already used.', [], 'validators'),
)]
class Authorization
class Authorization implements MetaEntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
Expand Down
4 changes: 3 additions & 1 deletion src/Entity/Message.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace App\Entity;

use App\EntityListener\EntitySetMetaListener;
use App\Repository\MessageRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
Expand All @@ -14,11 +15,12 @@
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: MessageRepository::class)]
#[ORM\EntityListeners([EntitySetMetaListener::class])]
#[UniqueEntity(
fields: 'uid',
message: new TranslatableMessage('The uid {{ value }} is already used.', [], 'validators'),
)]
class Message
class Message implements MetaEntityInterface
{
public const VIAS = ['webapp'];
public const DEFAULT_VIA = 'webapp';
Expand Down
22 changes: 22 additions & 0 deletions src/Entity/MetaEntityInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

// This file is part of Bileto.
// Copyright 2022-2023 Probesys
// SPDX-License-Identifier: AGPL-3.0-or-later

namespace App\Entity;

interface MetaEntityInterface
{
public function getUid(): ?string;

public function setUid(string $uid): self;

public function getCreatedAt(): ?\DateTimeImmutable;

public function setCreatedAt(\DateTimeImmutable $createdAt): self;

public function getCreatedBy(): ?User;

public function setCreatedBy(?User $createdBy): self;
}
4 changes: 3 additions & 1 deletion src/Entity/Organization.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace App\Entity;

use App\EntityListener\EntitySetMetaListener;
use App\Repository\OrganizationRepository;
use App\Validator as AppAssert;
use Doctrine\Common\Collections\ArrayCollection;
Expand All @@ -18,11 +19,12 @@
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: OrganizationRepository::class)]
#[ORM\EntityListeners([EntitySetMetaListener::class])]
#[UniqueEntity(
fields: 'uid',
message: new TranslatableMessage('The uid {{ value }} is already used.', [], 'validators'),
)]
class Organization
class Organization implements MetaEntityInterface
{
public const MAX_DEPTH = 3;

Expand Down
4 changes: 3 additions & 1 deletion src/Entity/Role.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace App\Entity;

use App\EntityListener\EntitySetMetaListener;
use App\Repository\RoleRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
Expand All @@ -16,6 +17,7 @@
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

#[ORM\Entity(repositoryClass: RoleRepository::class)]
#[ORM\EntityListeners([EntitySetMetaListener::class])]
#[UniqueEntity(
fields: 'uid',
message: new TranslatableMessage('The uid {{ value }} is already used.', [], 'validators'),
Expand All @@ -24,7 +26,7 @@
fields: 'name',
message: new TranslatableMessage('The role {{ value }} is already used.', [], 'validators'),
)]
class Role
class Role implements MetaEntityInterface
{
public const TYPES = ['super', 'admin', 'orga'];

Expand Down
4 changes: 3 additions & 1 deletion src/Entity/Ticket.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace App\Entity;

use App\EntityListener\EntitySetMetaListener;
use App\Repository\TicketRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
Expand All @@ -18,11 +19,12 @@
use Symfony\Component\Validator\Constraints as Assert;

#[ORM\Entity(repositoryClass: TicketRepository::class)]
#[ORM\EntityListeners([EntitySetMetaListener::class])]
#[UniqueEntity(
fields: 'uid',
message: new TranslatableMessage('The uid {{ value }} is already used.', [], 'validators'),
)]
class Ticket
class Ticket implements MetaEntityInterface
{
public const TYPES = ['request', 'incident'];
public const DEFAULT_TYPE = 'request';
Expand Down
4 changes: 3 additions & 1 deletion src/Entity/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

namespace App\Entity;

use App\EntityListener\EntitySetMetaListener;
use App\Repository\UserRepository;
use App\Utils\Locales;
use Doctrine\Common\Collections\ArrayCollection;
Expand All @@ -19,6 +20,7 @@
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\EntityListeners([EntitySetMetaListener::class])]
#[ORM\Table(name: '`users`')]
#[UniqueEntity(
fields: 'email',
Expand All @@ -28,7 +30,7 @@
fields: 'uid',
message: new TranslatableMessage('The uid {{ value }} is already used.', [], 'validators'),
)]
class User implements UserInterface, PasswordAuthenticatedUserInterface
class User implements UserInterface, PasswordAuthenticatedUserInterface, MetaEntityInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
Expand Down
Loading

0 comments on commit 3063858

Please sign in to comment.