Hi,
I've been using for quite a while the CommandBus in a project, the fact is that make's my life easier but I'm not sure who to handle more complex scenarios while maintaining the idea of CommandBus and a Repository Pattern. To put you inline, I'll show you a very basic CRUD operation within my app.
Actual working example
<?php
class SeriesController extends BaseController {
public function store()
{
extract(Input::only('name', 'description', 'category_id'));
$series = $this->execute(new RegisterSeriesCommand($name, $description, $category_id));
return Redirect::route('backoffice.series.show', $series->id);
}
}
class RegisterSeriesCommand {
public $name;
public $slug;
public $description;
public $category_id;
public function __construct($name, $description, $category_id)
{
$this->name = $name;
$this->slug = Str::slug($name);
$this->description = $description;
$this->category_id = $category_id;
}
}
class RegisterSeriesCommandHandler implements CommandHandler {
public function __construct(SeriesRepositoryInterface $repository)
{
$this->repository = $repository;
}
public function handle($command)
{
$serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
return $this->repository->save($serie);
}
}
class EloquentSeriesRepository extends EloquentRepository implements SeriesRepositoryInterface {
public function save(SeriesEntity $series)
{
$model = new SeriesEloquent($series);
if ($model->save()) return $series->setRawAttributes($model);
return false;
}
}
This is an example of how a category could be created, very convenient.
Adding tags (list of ids) to a category
My problem is, I would like to introduce a tags related to the series I just created. This tags, in my database, are created in a table tags, they're unique and related to a series with a table series_tags. The input form would send a group of id's that belongs to the tags table.
Where do you usually perform the action to attach tags to a series? To be clear I'm using Eloquent but I never return an instance of Eloquent and my Repositories don't accept an instance of a Eloquent model. You can't do a $series->attach($tags);.
One scenario I could see is the next:
<?php
class RegisterSeriesCommandHandler implements CommandHandler {
public function handle($command)
{
$serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
$serie->tags = $tags; // a collection of TagEntity
return $this->repository->save($serie);
}
}
class EloquentSeriesRepository extends EloquentRepository implements SeriesRepositoryInterface {
public function save(SeriesEntity $series)
{
$model = new SeriesEloquent($series);
if ($model->save()) {
// yeah, this would be bettern attaching an array of ids
foreach ($series->tags as $tag) $model->tags()->attach($tag->id);
return $this->fill($model);
}
return false;
}
protected function fill(SeriesEloquent $model)
{
$entity = new SeriesEntity;
$entity->setRawAttributes($model);
$entity->tags = $model->tags;
return $entity;
}
}
Each implementation of SeriesRepositoryInterface should know how to handle the tags associated in a entity. In this scenario, Eloquent, knows how to attach it once the model is created, so I think it's fine implementing it like this. Thoughts?
Adding tags (a list of strings) to a category
Cool, what about if I told you that we are not providing a list of IDs but the name of the tag? Some of them may exist, some other could be new. BOOM.
What first came to my mind was doing the next. I'm not sure how does it fit, but it works. I really need feedback on approaches or suggestions:
<?php
class RegisterSeriesCommandHandler implements CommandHandler {
public function __construct(SeriesRepositoryInterface $seriesRepository, TagRepositoryInterface $tagRepository)
{
$this->seriesRepository = $seriesRepository;
$this->tagRepository = $tagRepository;
}
public function handle($command)
{
$serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
$serie->tags = $this->tags($command->tags);
return $this->repository->save($serie);
}
protected function tags(array $tags = [])
{
$entities = new Collection;
foreach ($tags as $tag) {
$collection->push($this->tagRepository->findOrCreate($tag));
}
return $entities;
}
}
My eloquent implementation of SeriesRepositoryInterface would be the same as the second example, but we're creating/validating the list of tags provided by name.
How this sounds to you? Do you think this is a proper way of doing it respecting the repository pattern and the command bus?
What about file uploads?
If both of the previous examples are super straight what I don't know where to start is with files attached to a category. Let's say that a category can have multiple files. Files are stored in a files table and related to a category with category_files table. Everything is sent within the same request, the user just see a form. I guess my command should have a new attribute for an array of files and then, as we handle our command save it to the storage and then attach the files.
<?php
class RegisterSeriesCommand {
public $name;
public $slug;
public $description;
public $category_id;
public $files;
public function __construct($name, $description, $category_id, $files)
{
$this->name = $name;
$this->slug = Str::slug($name);
$this->description = $description;
$this->category_id = $category_id;
$this->files = $files;
}
}
class RegisterSeriesCommandHandler implements CommandHandler {
public function __construct(SeriesRepositoryInterface $seriesRepository, FileRepositoryInterface $fileRepository)
{
$this->seriesRepository = $seriesRepository;
$this->fileRepository = $fileRepository;
}
public function handle($command)
{
$serie = SeriesEntity::register($command->name, $command->slug, $command->description , $command->category_id);
$serie->files = $this->files($command->files);
return $this->repository->save($serie);
}
protected function files(array $files = [])
{
$entities = new Collection;
foreach ($files as $file) {
// here we will save file to the desired storage, if it's OK
// create a new entity that we can later save in our collection
$entity = $this->fileRepository->create($file['name'], $file['whatever']);
$collection->push($entity);
}
return $entities;
}
}
class EloquentSeriesRepository extends EloquentRepository implements SeriesRepositoryInterface {
public function save(SeriesEntity $series)
{
$model = new SeriesEloquent($series);
if ($model->save()) {
foreach ($series->tags as $tag) {
$model->tags()->attach($tag->id);
}
foreach ($series->files as $file) {
$model->files()->attach($file->id);
}
return $this->fill($model);
}
return false;
}
protected function fill(SeriesEloquent $model)
{
$entity = new SeriesEntity;
$entity->setRawAttributes($model);
$entity->tags = $model->tags;
$entity->files = $model->files;
return $entity;
}
}
What do you think of this way of processing a form that apart of basic text inputs will send a comma delimited list of tags that could be or not in the database, plus some <input type="file"> that would be saved to the storage and later associated to our entity and persisted to the repository using Eloquent?
Thanks in advance
Isern