Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/settings.local.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
"Bash(git commit:*)",
"Bash(git push:*)",
"Bash(git config:*)",
"Bash(git remote set-url:*)"
"Bash(git remote set-url:*)",
"Bash(make:*)",
"Bash(find:*)"
],
"deny": []
}
Expand Down
59 changes: 53 additions & 6 deletions docs/markdown/database.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
# Database Components

This guide provides documentation for the Database components in the Framework. These components provide a simple wrapper around Doctrine's EntityManager to manage database operations.
This guide provides documentation for the Database components in the Framework. These components provide a simple wrapper around Symfony's MessageBus to manage database operations through command messages.

## Database Class

The `Database` class is a wrapper around Doctrine's EntityManager that implements the `DatabasePersistenceInterface`:
The `Database` class is a wrapper around Symfony's MessageBus that implements the `DatabasePersistenceInterface`. It uses command messages to handle database operations:

```php
<?php

use Atournayre\Common\Persistance\Database;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Messenger\MessageBusInterface;

// Create a new Database instance
$database = Database::new(
entityManager: $entityManager, // Instance of EntityManagerInterface
object: $entity, // The entity to be managed
bus: $commandBus, // Instance of CommandBusInterface
object: $entity, // The entity to be managed
);

// Persist the entity and flush changes in one chain
Expand All @@ -29,6 +29,48 @@ $database->flush();
$database->remove()->flush();
```

## Command Messages

The Database class internally uses synchronous command messages to handle database operations:

### DatabasePersistCommand

```php
<?php

use Atournayre\Common\Persistance\Command\DatabasePersistCommand;

// The persist() method dispatches this command
$command = DatabasePersistCommand::new(object: $entity);
$command->dispatch($commandBus);
```

### DatabaseRemoveCommand

```php
<?php

use Atournayre\Common\Persistance\Command\DatabaseRemoveCommand;

// The remove() method dispatches this command
$command = DatabaseRemoveCommand::new(object: $entity);
$command->dispatch($commandBus);
```

### DatabaseFlushCommand

```php
<?php

use Atournayre\Common\Persistance\Command\DatabaseFlushCommand;

// The flush() method dispatches this command
$command = DatabaseFlushCommand::new();
$command->dispatch($commandBus);
```

All these commands implement `SyncCommandInterface` to ensure they are executed synchronously.

## DatabaseEntityInterface

The `DatabaseEntityInterface` defines the contract for entities that can be persisted to a database:
Expand Down Expand Up @@ -70,12 +112,14 @@ class MyEntity implements DatabaseEntityInterface
public function save(): void
{
// Get a Database instance for this entity and use fluent interface
// This will dispatch DatabasePersistCommand and DatabaseFlushCommand
$this->database()->persist()->flush();
}

public function delete(): void
{
// Get a Database instance for this entity and use fluent interface
// This will dispatch DatabaseRemoveCommand and DatabaseFlushCommand
$this->database()->remove()->flush();
}
}
Expand All @@ -93,17 +137,20 @@ $entity = new MyEntity();
$entity->setName('Example');

// Persist and flush directly from the entity using fluent interface
// This dispatches DatabasePersistCommand and DatabaseFlushCommand
$entity->database()->persist()->flush();

// Update an entity
$entity->setName('Updated Example');
// This dispatches DatabaseFlushCommand
$entity->database()->flush();

// Remove an entity using fluent interface
// This dispatches DatabaseRemoveCommand and DatabaseFlushCommand
$entity->database()->remove()->flush();
```

This approach provides a cleaner and more intuitive developer experience, allowing you to work directly with your entities without creating separate database instances.
This approach provides a cleaner and more intuitive developer experience, allowing you to work directly with your entities without creating separate database instances. All operations are now handled through command messages for better architecture and testability.

## DatabasePersistenceInterface

Expand Down
11 changes: 11 additions & 0 deletions docs/markdown/domain-events.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Use these interfaces to tag your messages for Symfony Messenger:

use Atournayre\Contracts\CommandBus\CommandInterface;
use Atournayre\Contracts\CommandBus\QueryInterface;
use Atournayre\Contracts\CommandBus\SyncCommandInterface;

// Command for async processing
class CreateUserCommand implements CommandInterface
Expand All @@ -77,6 +78,14 @@ class CreateUserCommand implements CommandInterface
) {}
}

// Synchronous command for immediate execution
class DatabasePersistCommand implements SyncCommandInterface
{
public function __construct(
public readonly object $object
) {}
}

// Query for sync processing
class GetUserQuery implements QueryInterface
{
Expand Down Expand Up @@ -173,6 +182,8 @@ framework:
routing:
# Route commands to async transport
'Atournayre\Contracts\CommandBus\CommandInterface': async
# Route synchronous commands to sync transport
'Atournayre\Contracts\CommandBus\SyncCommandInterface': sync
# Route queries to sync transport
'Atournayre\Contracts\CommandBus\QueryInterface': sync
```
Expand Down
23 changes: 23 additions & 0 deletions src/Common/Persistance/Command/DatabaseFlushCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Atournayre\Common\Persistance\Command;

use Atournayre\Common\AbstractCommandEvent;
use Atournayre\Contracts\CommandBus\SyncCommandInterface;

/**
* Command to flush all changes to the database.
*/
final class DatabaseFlushCommand extends AbstractCommandEvent implements SyncCommandInterface
{
private function __construct()
{
}

public static function new(): self
{
return new self();
}
}
28 changes: 28 additions & 0 deletions src/Common/Persistance/Command/DatabasePersistCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Atournayre\Common\Persistance\Command;

use Atournayre\Common\AbstractCommandEvent;
use Atournayre\Contracts\CommandBus\SyncCommandInterface;

/**
* Command to persist an object to the database.
*/
final class DatabasePersistCommand extends AbstractCommandEvent implements SyncCommandInterface
{
private function __construct(private readonly object $object)
{
}

public static function new(object $object): self
{
return new self(object: $object);
}

public function object(): object
{
return $this->object;
}
}
28 changes: 28 additions & 0 deletions src/Common/Persistance/Command/DatabaseRemoveCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace Atournayre\Common\Persistance\Command;

use Atournayre\Common\AbstractCommandEvent;
use Atournayre\Contracts\CommandBus\SyncCommandInterface;

/**
* Command to remove an object from the database.
*/
final class DatabaseRemoveCommand extends AbstractCommandEvent implements SyncCommandInterface
{
private function __construct(private readonly object $object)
{
}

public static function new(object $object): self
{
return new self(object: $object);
}

public function object(): object
{
return $this->object;
}
}
50 changes: 29 additions & 21 deletions src/Common/Persistance/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@

namespace Atournayre\Common\Persistance;

use Atournayre\Common\Persistance\Command\DatabaseFlushCommand;
use Atournayre\Common\Persistance\Command\DatabasePersistCommand;
use Atournayre\Common\Persistance\Command\DatabaseRemoveCommand;
use Atournayre\Contracts\CommandBus\CommandBusInterface;
use Atournayre\Contracts\Persistance\DatabasePersistenceInterface;
use Doctrine\ORM\EntityManagerInterface;

/**
* Database class provides a simple wrapper around Doctrine's EntityManager.
* Database class provides a simple wrapper around Symfony's MessageBus for database operations.
*
* This class implements the DatabasePersistenceInterface and provides methods
* to persist, flush, and remove objects from the database.
* to persist, flush, and remove objects from the database using command messages.
*
* Usage with direct instantiation:
* ```php
* $database = Database::new($entityManager, $entity);
* $database = Database::new($commandBus, $entity);
* $database->persist();
* $database->flush();
* ```
Expand All @@ -37,77 +40,82 @@
/**
* Private constructor to enforce usage of the factory method.
*
* @param EntityManagerInterface $entityManager The Doctrine entity manager
* @param object $object The object to be managed by the entity manager
* @param CommandBusInterface $commandBus The command bus for dispatching database operations
* @param object $object The object to be managed by the database
*/
private function __construct(
private EntityManagerInterface $entityManager,
private CommandBusInterface $commandBus,
private object $object,
) {
}

/**
* Creates a new Database instance.
*
* @param EntityManagerInterface $entityManager The Doctrine entity manager
* @param object $object The object to be managed by the entity manager
* @param CommandBusInterface $commandBus The command bus for dispatching database operations
* @param object $object The object to be managed by the database
*
* @return self A new Database instance
*
* @api
*/
public static function new(
EntityManagerInterface $entityManager,
CommandBusInterface $commandBus,
object $object,
): self {
return new self(
entityManager: $entityManager,
commandBus: $commandBus,
object: $object,
);
}

/**
* Persists the object to the database.
*
* This method tells Doctrine to "manage" the object, making it aware of the object
* without actually executing the SQL INSERT/UPDATE statement.
* This method dispatches a DatabasePersistCommand to handle the persistence operation.
*
* @return self For method chaining
*
* @see EntityManagerInterface::persist
* @see DatabasePersistCommand
*/
public function persist(): self
{
$this->entityManager->persist($this->object);
DatabasePersistCommand::new(object: $this->object)
->command(bus: $this->commandBus)
;

return $this;
}

/**
* Flushes all changes to the database.
*
* This method executes all SQL statements needed to persist the changes to the database.
* This method dispatches a DatabaseFlushCommand to handle the flush operation.
*
* @see EntityManagerInterface::flush
* @see DatabaseFlushCommand
*/
public function flush(): void
{
$this->entityManager->flush();
DatabaseFlushCommand::new()
->command(bus: $this->commandBus)
;
}

/**
* Removes the object from the database.
*
* This method tells Doctrine to remove the object from the database.
* This method dispatches a DatabaseRemoveCommand to handle the removal operation.
* The actual DELETE statement will be executed when flush() is called.
*
* @return self For method chaining
*
* @see EntityManagerInterface::remove
* @see DatabaseRemoveCommand
*/
public function remove(): self
{
$this->entityManager->remove($this->object);
DatabaseRemoveCommand::new(object: $this->object)
->command(bus: $this->commandBus)
;

return $this;
}
Expand Down
8 changes: 5 additions & 3 deletions src/Common/Persistance/DatabaseTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Atournayre\Common\Assert\Assert as Assertion;
use Atournayre\Contracts\Exception\ThrowableInterface;
use Atournayre\Contracts\Persistance\DatabasePersistenceInterface;
use Atournayre\DependencyInjection\EntityDependencyInjection;

/**
* Trait that provides database persistence functionality.
Expand All @@ -24,11 +25,12 @@ trait DatabaseTrait
*/
public function database(): DatabasePersistenceInterface
{
Assertion::notNull($this->dependencyInjection, 'Dependency injection is not available. Did you forget to add the $dependencyInjection property to your class?');
Assertion::true(isset($this->dependencyInjection->entityManager), 'Entity manager is not available. Did you forget to add the EntityManagerInterface to your dependency injection class?');
/** @var EntityDependencyInjection $dependencyInjection */
$dependencyInjection = $this->dependencyInjection;
Assertion::notNull($dependencyInjection, 'Dependency injection is not available. Did you forget to add the $dependencyInjection property to your class?');

return Database::new(
entityManager: $this->dependencyInjection->entityManager,
commandBus: $dependencyInjection->commandBus(),
object: $this,
);
}
Expand Down
Loading
Loading