honeybee/honeybee

View on GitHub
src/Projection/EventHandler/ProjectionFileHandler.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

namespace Honeybee\Projection\EventHandler;

use Exception;
use Honeybee\Common\Error\FilesystemError;
use Honeybee\EntityTypeInterface;
use Honeybee\Infrastructure\Config\ConfigInterface;
use Honeybee\Infrastructure\Event\EventHandler;
use Honeybee\Infrastructure\Filesystem\FilesystemServiceInterface;
use Honeybee\Model\Aggregate\AggregateRootTypeMap;
use Honeybee\Model\Event\HasEmbeddedEntityEventsInterface;
use Honeybee\Model\Task\CreateAggregateRoot\AggregateRootCreatedEvent;
use Honeybee\Model\Task\ModifyAggregateRoot\AggregateRootModifiedEvent;
use Honeybee\Model\Task\ProceedWorkflow\WorkflowProceededEvent;
use Psr\Log\LoggerInterface;
use Trellis\Runtime\Attribute\HandlesFileInterface;
use Trellis\Runtime\Attribute\HandlesFileListInterface;

class ProjectionFileHandler extends EventHandler
{
    protected $aggregate_root_type_map;

    protected $filesystem_service;

    public function __construct(
        ConfigInterface $config,
        LoggerInterface $logger,
        AggregateRootTypeMap $aggregate_root_type_map,
        FilesystemServiceInterface $filesystem_service
    ) {
        parent::__construct($config, $logger);

        $this->aggregate_root_type_map = $aggregate_root_type_map;
        $this->filesystem_service = $filesystem_service;
    }

    protected function onAggregateRootCreated(AggregateRootCreatedEvent $event)
    {
        $ar_type = $this->aggregate_root_type_map->getItem($event->getAggregateRootType());
        $this->moveTempFilesToFinalLocation($event, $ar_type);
    }

    protected function onAggregateRootModified(AggregateRootModifiedEvent $event)
    {
        $ar_type = $this->aggregate_root_type_map->getItem($event->getAggregateRootType());
        $this->moveTempFilesToFinalLocation($event, $ar_type);
    }

    protected function onWorkflowProceeded(WorkflowProceededEvent $event)
    {
        // @todo do we support file operations upon workflow traversal?
    }

    protected function moveTempFilesToFinalLocation(
        HasEmbeddedEntityEventsInterface $event,
        EntityTypeInterface $entity_type
    ) {
        foreach ($event->getData() as $attr_name => $attr_data) {
            $attribute = $entity_type->getAttribute($attr_name);
            $art = $attribute->getRootType();
            if ($attribute instanceof HandlesFileListInterface) {
                $property_name = $attribute->getFileLocationPropertyName();
                foreach ($attr_data as $file) {
                    $this->moveTempFileToFinalLocation($file[$property_name], $art);
                }
            } elseif ($attribute instanceof HandlesFileInterface) {
                $this->moveTempFileToFinalLocation($attr_data[$attribute->getFileLocationPropertyName()], $art);
            }
        }

        // there may be embedded entity events that have files on their attributes as well => recurse into them
        foreach ($event->getEmbeddedEntityEvents() as $embedded_event) {
            $attr_name = $embedded_event->getParentAttributeName();
            $embedded_entity_type = $embedded_event->getEmbeddedEntityType();
            $embedded_attribute = $entity_type->getAttribute($attr_name);
            $embedded_type = $embedded_attribute->getEmbeddedTypeByPrefix($embedded_entity_type);
            $this->moveTempFilesToFinalLocation($embedded_event, $embedded_type);
        }
    }

    protected function moveTempFileToFinalLocation($location, EntityTypeInterface $art)
    {
        $from_uri = $this->filesystem_service->createTempUri($location, $art); // from temporary storage
        $to_uri = $this->filesystem_service->createUri($location, $art); // to final storage

        try {
            $file_copied = $this->filesystem_service->has($to_uri);
            if (true !== $file_copied) {
                $file_copied = $this->filesystem_service->copy($from_uri, $to_uri);
                if (true !== $file_copied) {
                    throw new FilesystemError(sprintf('File copy failed with result "%s".', $file_copied));
                }
            }
        } catch (Exception $copy_error) {
            $this->logger->error(
                '[{method}] File could not be copied from {from_uri} to {to_uri}. Error: {error}',
                [
                    'method' => __METHOD__,
                    'from_uri' => $from_uri,
                    'to_uri' => $to_uri,
                    'error' => $copy_error->getMessage()
                ]
            );
            return false;
        }

        // source file deletion failure is acceptable so the deletion process is done separately.
        // the file must exist at final destination in order to get to this block.
        try {
            $file_deleted = $this->filesystem_service->delete($from_uri);
            if (true !== $file_deleted) {
                throw new FilesystemError(sprintf('File deletion failed with result "%s".', $file_deleted));
            }
        } catch (Exception $deletion_error) {
            // log deletion error and continue
            $this->logger->error(
                '[{method}] File could not be deleted from {from_uri}. Error: {error}',
                [
                    'method' => __METHOD__,
                    'from_uri' => $from_uri,
                    'error' => $deletion_error->getMessage()
                ]
            );
        }

        return true;
    }
}