feat: entité Website liée à Customer avec UUID, type et state machine
Entity Website : - customer (ManyToOne, CASCADE) : client propriétaire - name : nom du site - uuid : UUID v4 auto-généré (unique, 36 chars) - type : vitrine | ecommerce - state : created → install_progress → open → suspended → closed - createdAt / updatedAt (auto sur setState) - isOpen() : vérifie si state === open WebsiteTest (5 tests, 20 assertions) : - testConstructor : valeurs par défaut, uuid 36 chars, type vitrine - testConstructorEcommerce : type ecommerce - testSetters : name, type, updatedAt - testState : transitions created→install_progress→open→suspended→closed - testUuidUnique : 2 sites ont des uuid différents Dépendance : symfony/uid ajouté pour Uuid::v4() Migration : CREATE TABLE website avec FK customer, uuid unique Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
"symfony/string": "8.0.*",
|
||||
"symfony/translation": "8.0.*",
|
||||
"symfony/twig-bundle": "8.0.*",
|
||||
"symfony/uid": "8.0.*",
|
||||
"symfony/validator": "8.0.*",
|
||||
"symfony/web-link": "8.0.*",
|
||||
"symfony/yaml": "8.0.*",
|
||||
|
||||
163
composer.lock
generated
163
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "c89df7b95449dfc734150e681bc309f2",
|
||||
"content-hash": "9234633550a505d8b9b7ed8ee0118699",
|
||||
"packages": [
|
||||
{
|
||||
"name": "async-aws/core",
|
||||
@@ -8628,6 +8628,89 @@
|
||||
],
|
||||
"time": "2025-06-23T16:12:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/polyfill-uuid",
|
||||
"version": "v1.33.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-uuid.git",
|
||||
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
|
||||
"reference": "21533be36c24be3f4b1669c4725c7d1d2bab4ae2",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"provide": {
|
||||
"ext-uuid": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-uuid": "For best performance"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"thanks": {
|
||||
"url": "https://github.com/symfony/polyfill",
|
||||
"name": "symfony/polyfill"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Symfony\\Polyfill\\Uuid\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Grégoire Pineau",
|
||||
"email": "lyrixx@lyrixx.info"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Symfony polyfill for uuid functions",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"compatibility",
|
||||
"polyfill",
|
||||
"portable",
|
||||
"uuid"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/polyfill-uuid/tree/v1.33.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-09-09T11:45:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/process",
|
||||
"version": "v8.0.5",
|
||||
@@ -10301,6 +10384,84 @@
|
||||
],
|
||||
"time": "2026-03-04T13:55:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/uid",
|
||||
"version": "v8.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/uid.git",
|
||||
"reference": "f63fa6096a24147283bce4d29327d285326438e0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/uid/zipball/f63fa6096a24147283bce4d29327d285326438e0",
|
||||
"reference": "f63fa6096a24147283bce4d29327d285326438e0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.4",
|
||||
"symfony/polyfill-uuid": "^1.15"
|
||||
},
|
||||
"require-dev": {
|
||||
"symfony/console": "^7.4|^8.0"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Symfony\\Component\\Uid\\": ""
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Grégoire Pineau",
|
||||
"email": "lyrixx@lyrixx.info"
|
||||
},
|
||||
{
|
||||
"name": "Nicolas Grekas",
|
||||
"email": "p@tchwork.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"description": "Provides an object-oriented API to generate and represent UIDs",
|
||||
"homepage": "https://symfony.com",
|
||||
"keywords": [
|
||||
"UID",
|
||||
"ulid",
|
||||
"uuid"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/uid/tree/v8.0.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://symfony.com/sponsor",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-03-30T15:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/validator",
|
||||
"version": "v8.0.7",
|
||||
|
||||
35
migrations/Version20260404190626.php
Normal file
35
migrations/Version20260404190626.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260404190626 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 website (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, name VARCHAR(255) NOT NULL, uuid VARCHAR(36) NOT NULL, type VARCHAR(20) NOT NULL, state VARCHAR(20) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, customer_id INT NOT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_476F5DE7D17F50A6 ON website (uuid)');
|
||||
$this->addSql('CREATE INDEX IDX_476F5DE79395C3F3 ON website (customer_id)');
|
||||
$this->addSql('ALTER TABLE website ADD CONSTRAINT FK_476F5DE79395C3F3 FOREIGN KEY (customer_id) REFERENCES customer (id) ON DELETE CASCADE NOT DEFERRABLE');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE website DROP CONSTRAINT FK_476F5DE79395C3F3');
|
||||
$this->addSql('DROP TABLE website');
|
||||
}
|
||||
}
|
||||
129
src/Entity/Website.php
Normal file
129
src/Entity/Website.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
#[ORM\Entity]
|
||||
class Website
|
||||
{
|
||||
public const TYPE_VITRINE = 'vitrine';
|
||||
public const TYPE_ECOMMERCE = 'ecommerce';
|
||||
|
||||
public const STATE_CREATED = 'created';
|
||||
public const STATE_INSTALL_PROGRESS = 'install_progress';
|
||||
public const STATE_OPEN = 'open';
|
||||
public const STATE_SUSPENDED = 'suspended';
|
||||
public const STATE_CLOSED = 'closed';
|
||||
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue]
|
||||
#[ORM\Column]
|
||||
private ?int $id = null;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: Customer::class)]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
private Customer $customer;
|
||||
|
||||
#[ORM\Column(length: 255)]
|
||||
private string $name;
|
||||
|
||||
#[ORM\Column(length: 36, unique: true)]
|
||||
private string $uuid;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
private string $type;
|
||||
|
||||
#[ORM\Column(length: 20)]
|
||||
private string $state = self::STATE_CREATED;
|
||||
|
||||
#[ORM\Column]
|
||||
private \DateTimeImmutable $createdAt;
|
||||
|
||||
#[ORM\Column(nullable: true)]
|
||||
private ?\DateTimeImmutable $updatedAt = null;
|
||||
|
||||
public function __construct(Customer $customer, string $name, string $type = self::TYPE_VITRINE)
|
||||
{
|
||||
$this->customer = $customer;
|
||||
$this->name = $name;
|
||||
$this->type = $type;
|
||||
$this->uuid = Uuid::v4()->toRfc4122();
|
||||
$this->createdAt = new \DateTimeImmutable();
|
||||
}
|
||||
|
||||
public function getId(): ?int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getCustomer(): Customer
|
||||
{
|
||||
return $this->customer;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function setName(string $name): static
|
||||
{
|
||||
$this->name = $name;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUuid(): string
|
||||
{
|
||||
return $this->uuid;
|
||||
}
|
||||
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
public function setType(string $type): static
|
||||
{
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getState(): string
|
||||
{
|
||||
return $this->state;
|
||||
}
|
||||
|
||||
public function setState(string $state): static
|
||||
{
|
||||
$this->state = $state;
|
||||
$this->updatedAt = new \DateTimeImmutable();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isOpen(): bool
|
||||
{
|
||||
return self::STATE_OPEN === $this->state;
|
||||
}
|
||||
|
||||
public function getCreatedAt(): \DateTimeImmutable
|
||||
{
|
||||
return $this->createdAt;
|
||||
}
|
||||
|
||||
public function getUpdatedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->updatedAt;
|
||||
}
|
||||
|
||||
public function setUpdatedAt(?\DateTimeImmutable $updatedAt): static
|
||||
{
|
||||
$this->updatedAt = $updatedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
87
tests/Entity/WebsiteTest.php
Normal file
87
tests/Entity/WebsiteTest.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Entity;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\User;
|
||||
use App\Entity\Website;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class WebsiteTest extends TestCase
|
||||
{
|
||||
private function createCustomer(): Customer
|
||||
{
|
||||
$user = new User();
|
||||
$user->setEmail('c@t.com');
|
||||
$user->setFirstName('C');
|
||||
$user->setLastName('T');
|
||||
$user->setPassword('h');
|
||||
|
||||
return new Customer($user);
|
||||
}
|
||||
|
||||
public function testConstructor(): void
|
||||
{
|
||||
$customer = $this->createCustomer();
|
||||
$site = new Website($customer, 'Mon Site');
|
||||
|
||||
$this->assertNull($site->getId());
|
||||
$this->assertSame($customer, $site->getCustomer());
|
||||
$this->assertSame('Mon Site', $site->getName());
|
||||
$this->assertSame(36, \strlen($site->getUuid()));
|
||||
$this->assertSame(Website::TYPE_VITRINE, $site->getType());
|
||||
$this->assertSame(Website::STATE_CREATED, $site->getState());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $site->getCreatedAt());
|
||||
$this->assertNull($site->getUpdatedAt());
|
||||
}
|
||||
|
||||
public function testConstructorEcommerce(): void
|
||||
{
|
||||
$site = new Website($this->createCustomer(), 'Boutique', Website::TYPE_ECOMMERCE);
|
||||
$this->assertSame(Website::TYPE_ECOMMERCE, $site->getType());
|
||||
}
|
||||
|
||||
public function testSetters(): void
|
||||
{
|
||||
$site = new Website($this->createCustomer(), 'Test');
|
||||
|
||||
$site->setName('Nouveau nom');
|
||||
$this->assertSame('Nouveau nom', $site->getName());
|
||||
|
||||
$site->setType(Website::TYPE_ECOMMERCE);
|
||||
$this->assertSame(Website::TYPE_ECOMMERCE, $site->getType());
|
||||
|
||||
$now = new \DateTimeImmutable();
|
||||
$site->setUpdatedAt($now);
|
||||
$this->assertSame($now, $site->getUpdatedAt());
|
||||
}
|
||||
|
||||
public function testState(): void
|
||||
{
|
||||
$site = new Website($this->createCustomer(), 'Test');
|
||||
$this->assertFalse($site->isOpen());
|
||||
|
||||
$site->setState(Website::STATE_INSTALL_PROGRESS);
|
||||
$this->assertSame(Website::STATE_INSTALL_PROGRESS, $site->getState());
|
||||
$this->assertFalse($site->isOpen());
|
||||
|
||||
$site->setState(Website::STATE_OPEN);
|
||||
$this->assertTrue($site->isOpen());
|
||||
$this->assertInstanceOf(\DateTimeImmutable::class, $site->getUpdatedAt());
|
||||
|
||||
$site->setState(Website::STATE_SUSPENDED);
|
||||
$this->assertFalse($site->isOpen());
|
||||
|
||||
$site->setState(Website::STATE_CLOSED);
|
||||
$this->assertFalse($site->isOpen());
|
||||
}
|
||||
|
||||
public function testUuidUnique(): void
|
||||
{
|
||||
$customer = $this->createCustomer();
|
||||
$site1 = new Website($customer, 'Site 1');
|
||||
$site2 = new Website($customer, 'Site 2');
|
||||
|
||||
$this->assertNotSame($site1->getUuid(), $site2->getUuid());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user