inakianduaga/eloquent-external-storage

View on GitHub
src/Models/ModelWithExternalStorageTrait.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php namespace InakiAnduaga\EloquentExternalStorage\Models;

use Illuminate\Foundation\Application as App;
use InakiAnduaga\EloquentExternalStorage\Drivers\DriverInterface as StorageDriver;
use InakiAnduaga\EloquentExternalStorage\Models\ModelWithExternalStorageInterface as Model;

/**
 * Adds external storage capabilities to any eloquent model
 * Uses a storage driver to do the storing. (an actual implementation such as file or AwsS3 needs to be binded to the IoC).
 */
trait ModelWithExternalStorageTrait
{
    // ---------------
    // Configuration
    // ---------------

    /**
     * The storage driver configuration path
     *
     * @var string
     */
    protected static $storageDriverConfigPath;

    /**
     * Under what db field we store the content path/md5 for this model
     * Can be overriden by the actual model implementation
     *
     * @var string
     */
    protected $databaseFields = array(
        'contentPath' => 'content_path',
        'contentMD5' => 'content_md5',
        'storage_driver' => 'storage_driver',
    );


    // ---------------
    // Properties
    // ---------------

    /**
     * @var StorageDriver
     */
    protected static $storageDriver;

    /**
     * The model's external content
     * @var string
     */
    private $content;


    // ---------------
    // Events
    // ---------------

    /**
     * Eloquent event registration for storing/removing binded content transparently upon model creation/deletion
     *
     * @return void
     */
    protected static function boot()
    {
        parent::boot();

        //Attempt to auto-inject the storage driver if we haven't initialized it yet
        if(static::$storageDriver === null) {
            static::injectStorageDriver();
        }

        /**
         * Creating Event
         *
         *  - Before creating the attachment, if the content is previously set, we will store it and update the path
         */
        static::creating(function (Model $model) {
            if ($model->hasInMemoryContent()) {

                //Set the stored content's path
                $storagePath = $model->getStorageDriverInstance()->store($model->getContent());
                $model->setPath($storagePath);

                //Just in case somebody set the md5 manually to a wrong value, we resync it
                $model->syncContentMD5();

                //Set the value of the storage driver class
                $model->syncStorageDriverField();
            }
        });

        /**
         * Updating event
         *
         *  - Before running a model update, if the content is previously set, we will run an update
         */
        static::updating(function (Model $model) {
            if ($model->hasInMemoryContent() && !$model->doesMD5MatchInMemoryContent()) {

                //Set the stored content's path
                $storagePath = $model->getStorageDriverInstance()->store($model->getContent());
                $model->setPath($storagePath);

                //Sync new md5 value before updating
                $model->syncContentMD5();

                //Set the value of the storage driver class
                $model->syncStorageDriverField();
            }
        });

        /**
         * Deleting Event
         *
         *  - Before deleting the attachment, we need to remove the contents from storage
         *  - TODO: Clean up content path, storage driver fields upon deletion
         */
        static::deleting(function (Model $model) {
            if ($model->hasInMemoryContent()) {
                $model->getStorageDriverInstance()->remove($model->getPath());
            }
        });
    }

    public static function setStorageDriverConfigurationPath($path)
    {
        static::$storageDriverConfigPath = $path;

        static::getStorageDriverInstance()->setConfigKey($path);
    }

    public static function getStorageDriverInstance()
    {
        return static::$storageDriver;
    }

    // ---------------
    // Setter / Getters
    // ---------------

    public function setContent($string)
    {
        $this->content = $string;

        $this->syncContentMD5();

        return $this;
    }


    public function getContent()
    {
        //If there is no in-memory content and there is no content path, the content must be null
        //Otherwise, get content from in-memory or cold storage
        if(!$this->hasInMemoryContent() && empty($this->getPath())) {
            return null;
        } else {
            return !empty($this->content) ? $this->content : $this->getStorageDriverInstance()->fetch($this->getPath());
        }
    }


    // ---------------
    // Public API
    // ---------------

    public function hasInMemoryContent()
    {
        return !is_null($this->content);
    }

    /**
     * Returns the storage path
     *
     * @return string|null
     */
    public function getPath()
    {
        return $this->{$this->databaseFields['contentPath']};
    }

    /**
     * Sets the model storage path
     *
     * @param  string $path
     * @return self
     */
    public function setPath($path)
    {
        return $this->{$this->databaseFields['contentPath']} = $path;
    }


    public function syncContentMD5()
    {
        if(!empty($this->content)) {
            $this->{$this->databaseFields['contentMD5']} = md5($this->content);
        } else {
            $this->{$this->databaseFields['contentMD5']} = null;
        }

        return $this;
    }


    public static function setStorageDriver(StorageDriver $driver, $storageDriverConfigurationPath = null)
    {
        static::$storageDriver = $driver;

        if(!empty($storageDriverConfigurationPath)) {
            static::setStorageDriverConfigurationPath($storageDriverConfigurationPath);
        }
    }


    public function syncStorageDriverField()
    {
        $this->{$this->databaseFields['storage_driver']} = static::getStorageDriverInstanceClass();
    }

    // ---------------------------
    // Private / Protected methods
    // ---------------------------

    /**
     * Inject storage driver and pass configuration using the app IoC container
     */
    private static function injectStorageDriver()
    {
        //Only attempt to inject driver if there is a IoC binding for the interface
        if(App::getBindings()[StorageDriver::class]) {
            static::$storageDriver = app(StorageDriver::class);
            static::$storageDriver->setConfigKey(static::$storageDriverConfigPath);
        }
    }

    /**
     * Determines whether the current (in memory, not stored) content matches the current md5 signature
     *
     * @return boolean
     */
    public function doesMD5MatchInMemoryContent()
    {
        return $this->{$this->databaseFields['contentMD5']} == md5($this->content) && !$this->isDirty($this->databaseFields['contentMD5']);
    }

    /**
     * The class name of the current storage driver
     * @return null|string
     */
    private static function getStorageDriverInstanceClass()
    {
        $driver = static::getStorageDriverInstance();

        return $driver !== null ? get_class($driver) : null;
    }
}