Skip to content

Commit

Permalink
feat: category entity (#15)
Browse files Browse the repository at this point in the history
close: #13

* refac(logger): replace LoggerInterface with ExceptionLogger

* refac(Service): rename Project classes

* feat: add Category entity

* chore: update monolog config

* feat(category): add fixtures

* refac(entities): add cascade/orphanRemoval attributes
  • Loading branch information
n3wborn committed Sep 19, 2023
1 parent cb7b48e commit 4d5ec6f
Show file tree
Hide file tree
Showing 20 changed files with 401 additions and 125 deletions.
19 changes: 9 additions & 10 deletions config/packages/monolog.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@ when@dev:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
path: "php://stdout"
level: info
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
formatter: monolog.line_formatter
debug_file:
type: stream
path: "%kernel.logs_dir%/debug.log"
level: debug
channels: ["!event", "!doctrine"]
formatter: monolog.line_formatter
console:
type: console
process_psr_3_messages: false
Expand Down
15 changes: 9 additions & 6 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,20 @@ parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

# makes classes in src/ available to be used as services
monolog.line_formatter: # Your name
class: Monolog\Formatter\LineFormatter
arguments: [~, ~, true]
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
resource: "../src/"
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- "../src/DependencyInjection/"
- "../src/Entity/"
- "../src/Kernel.php"

# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones
31 changes: 0 additions & 31 deletions migrations/Version20230716151324.php

This file was deleted.

27 changes: 0 additions & 27 deletions migrations/Version20230906163121.php

This file was deleted.

47 changes: 47 additions & 0 deletions migrations/Version20230912004347.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20230912004347 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add Project and Category';
}

public function up(Schema $schema): void
{
$this->addSql('CREATE TABLE category (id UUID NOT NULL, name VARCHAR(255) DEFAULT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, archived_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, slug VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN category.id IS \'(DC2Type:uuid)\'');
$this->addSql('COMMENT ON COLUMN category.created_at IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN category.updated_at IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN category.archived_at IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('CREATE TABLE category_project (category_id UUID NOT NULL, project_id UUID NOT NULL, PRIMARY KEY(category_id, project_id))');
$this->addSql('CREATE INDEX IDX_E86B909012469DE2 ON category_project (category_id)');
$this->addSql('CREATE INDEX IDX_E86B9090166D1F9C ON category_project (project_id)');
$this->addSql('COMMENT ON COLUMN category_project.category_id IS \'(DC2Type:uuid)\'');
$this->addSql('COMMENT ON COLUMN category_project.project_id IS \'(DC2Type:uuid)\'');
$this->addSql('CREATE TABLE project (id UUID NOT NULL, name VARCHAR(255) NOT NULL, description VARCHAR(255) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, updated_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, archived_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, slug VARCHAR(255) NOT NULL, PRIMARY KEY(id))');
$this->addSql('COMMENT ON COLUMN project.id IS \'(DC2Type:uuid)\'');
$this->addSql('COMMENT ON COLUMN project.created_at IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN project.updated_at IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('COMMENT ON COLUMN project.archived_at IS \'(DC2Type:datetime_immutable)\'');
$this->addSql('ALTER TABLE category_project ADD CONSTRAINT FK_E86B909012469DE2 FOREIGN KEY (category_id) REFERENCES category (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('ALTER TABLE category_project ADD CONSTRAINT FK_E86B9090166D1F9C FOREIGN KEY (project_id) REFERENCES project (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
}

public function down(Schema $schema): void
{
$this->addSql('CREATE SCHEMA public');
$this->addSql('ALTER TABLE category_project DROP CONSTRAINT FK_E86B909012469DE2');
$this->addSql('ALTER TABLE category_project DROP CONSTRAINT FK_E86B9090166D1F9C');
$this->addSql('DROP TABLE category');
$this->addSql('DROP TABLE category_project');
$this->addSql('DROP TABLE project');
}
}
9 changes: 5 additions & 4 deletions src/Controller/ProjectController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
namespace App\Controller;

use App\Entity\Project;
use App\Service\Project\DTO;
use App\Service\Project\Handler;
use App\Service\Category\CategoryFinder;
use App\Service\Project\ProjectDTO;
use App\Service\Project\ProjectHandler;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -14,7 +15,7 @@
final class ProjectController extends AbstractController
{
public function __construct(
private Handler $handler,
private ProjectHandler $handler,
) {
}

Expand All @@ -34,7 +35,7 @@ public function getProject(?Project $project): JsonResponse
#[Route('/project/{slug}', name: self::ROUTE_EDIT, methods: Request::METHOD_POST)]
public function persistProject(
?Project $project,
#[MapRequestPayload()] ?DTO $dto,
#[MapRequestPayload()] ?ProjectDTO $dto,
Request $request,
): JsonResponse {
return $this->handler->handlePersistProject($project, $request, $dto);
Expand Down
28 changes: 28 additions & 0 deletions src/DataFixtures/CategoryFixtures.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

namespace App\DataFixtures;

use App\Entity\Category;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;

final class CategoryFixtures extends Fixture implements OrderedFixtureInterface
{
public const CATEGORY_QUANTITY = 20;

public function load(ObjectManager $manager): void
{
for ($i = 1; $i <= self::CATEGORY_QUANTITY; ++$i) {
$category = (new Category())->setName("category-$i");
$manager->persist($category);
}

$manager->flush();
}

public function getOrder(): int
{
return 1;
}
}
21 changes: 20 additions & 1 deletion src/DataFixtures/ProjectFixtures.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,32 @@
namespace App\DataFixtures;

use App\Entity\Project;
use App\Repository\CategoryRepository;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Doctrine\Persistence\ObjectManager;

final class ProjectFixtures extends Fixture
final class ProjectFixtures extends Fixture implements OrderedFixtureInterface
{
public const QUANTITY = 10;

public function __construct(
private CategoryRepository $categoryRepository,
) {
}

public function load(ObjectManager $manager): void
{
for ($i = 1; $i <= self::QUANTITY; ++$i) {
$count = rand(1, 5);
$project = $this->createProject($i);
$manager->persist($project);

for ($j = 1; $j <= $count; ++$j) {
$randCategory = $this->categoryRepository->findOneBy(['name' => 'category-'.rand(1, CategoryFixtures::CATEGORY_QUANTITY)]);
$randCategory->addProject($project);
$manager->persist($randCategory);
}
}

$manager->flush();
Expand All @@ -26,4 +40,9 @@ private function createProject(int $number): Project
->setName("project-$number")
->setDescription("project-description-$number");
}

public function getOrder(): int
{
return 2;
}
}
115 changes: 115 additions & 0 deletions src/Entity/Category.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

namespace App\Entity;

use App\Repository\CategoryRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Types\UuidType;
use Symfony\Component\Uid\Uuid;

#[ORM\Entity(repositoryClass: CategoryRepository::class)]
#[ORM\Table(name: 'category')]
#[ORM\HasLifecycleCallbacks]
class Category
{
use ArchivableEntity;
use SluggableTrait;

#[ORM\Id]
#[ORM\Column(type: UuidType::NAME, unique: true)]
#[ORM\GeneratedValue(strategy: 'CUSTOM')]
#[ORM\CustomIdGenerator(class: 'doctrine.uuid_generator')]
private ?Uuid $id = null;

#[ORM\Column(length: 255, nullable: true)]
private ?string $name = null;

#[ORM\Column(type: 'datetime_immutable')]
private ?\DateTimeImmutable $createdAt = null;

#[ORM\Column(nullable: true, type: 'datetime_immutable')]
private ?\DateTimeImmutable $updatedAt = null;

#[ORM\ManyToMany(targetEntity: Project::class, inversedBy: 'categories', cascade: ['persist'], fetch: 'EAGER', orphanRemoval: true)]
private Collection $projects;

public function __construct()
{
$this->projects = new ArrayCollection();
}

final public function getId(): ?Uuid
{
return $this->id;
}

final public function getName(): ?string
{
return $this->name;
}

final public function setName(?string $name): self
{
$this->name = $name;

return $this;
}

final public function getCreatedAt(): ?\DateTimeImmutable
{
return $this->createdAt;
}

final public function setCreatedAt(\DateTimeImmutable $createdAt): self
{
$this->createdAt = $createdAt;

return $this;
}

final public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}

final public function setUpdatedAt(?\DateTimeImmutable $updatedAt): self
{
$this->updatedAt = $updatedAt;

return $this;
}

#[ORM\PrePersist]
final public function setCreatedAtValue(): void
{
$this->createdAt = new \DateTimeImmutable();
}

#[ORM\PreUpdate]
final public function setUpdatedAtValue(): void
{
$this->updatedAt = new \DateTimeImmutable();
}

final public function getProjects(): Collection
{
return $this->projects;
}

final public function addProject(Project $project): self
{
!$this->projects->contains($project)
&& $this->projects->add($project);

return $this;
}

final public function removeProject(Project $project): self
{
$this->projects->removeElement($project);

return $this;
}
}
Loading

0 comments on commit 4d5ec6f

Please sign in to comment.