Skip to content

Instantly share code, notes, and snippets.

@chasecmiller
Created January 22, 2020 15:38
Show Gist options
  • Save chasecmiller/01cb4c0ef72b81aa13e46d1888c8c0bf to your computer and use it in GitHub Desktop.
Save chasecmiller/01cb4c0ef72b81aa13e46d1888c8c0bf to your computer and use it in GitHub Desktop.
A Laravel trait to allow any data to be added to a model .
<?php
// Customize this.
namespace Crumbls\ExtendedData\Traits;
use Str;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
/**
* Trait HasExtendedData
* A simple way to add data to a model while it's still being developed.
* Just add a MySQL JSON column named extended to the table.
* When you're done, it's generally best practice to create actual columns for data in a relational database.
* @package Crumbls\ExtendedData\Traits
*/
trait HasExtendedData
{
/**
* Getter for extended column name.
* @return string
*/
public function getExtendedColumn()
{
return 'extended';
}
/**
* Bootable for our trait.
*/
public static function bootHasExtendedData()
{
static::saving(function ($model) {
self::addExtendedColumn($model);
$key = $model->getExtendedColumn();
});
static::updating(function ($model) {
self::addExtendedColumn($model);
$key = $model->getExtendedColumn();
});
static::retrieved(function ($model) {
self::addExtendedColumn($model);
});
static::creating(function ($model) {
self::addExtendedColumn($model);
});
}
/**
* Get the casts array.
*
* @return array
*/
public function getCasts()
{
if (!array_key_exists($this->getExtendedColumn(), $this->casts)) {
$name = $this->getExtendedColumn();
$this->casts[$name] = 'array';
}
if ($this->getIncrementing()) {
return array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts);
}
return $this->casts;
}
/**
* Setup our extended data.
* @param $model
* @return bool
*/
protected static function addExtendedColumn($model)
{
$name = $model->getExtendedColumn();
if (array_key_exists($name, $model->getCasts())) {
return $model;
}
$model->casts[$name] = 'array';
return $model;
}
/**
* Dynamically retrieve attributes on the model.
*
* @param string $key
* @return mixed
*/
public function __get($key)
{
$ret = parent::getAttribute($key);
$name = $this->getExtendedColumn();
if (!$ret && array_key_exists($name, $this->attributes)) {
$temp = is_string($this->attributes[$name]) ? (array)@json_decode($this->attributes[$name], true) : $this->attributes[$name];
if (is_null($temp)) {
return null;
}
if (array_key_exists($key, $temp)) {
return $temp[$key];
}
}
return $ret;
}
/**
* Set a given attribute on the model.
* Overridden to add our extra data heree.
*
* @param string $key
* @param mixed $value
* @return mixed
*/
public function setAttribute($key, $value)
{
// First we will check for the presence of a mutator for the set operation
// which simply lets the developers tweak the attribute as it is set on
// the model, such as "json_encoding" an listing of data for storage.
if ($this->hasSetMutator($key)) {
return $this->setMutatedAttributeValue($key, $value);
}
// If an attribute is listed as a "date", we'll convert it from a DateTime
// instance into a form proper for storage on the database tables using
// the connection grammar's date format. We will auto set the values.
elseif ($value && $this->isDateAttribute($key)) {
$value = $this->fromDateTime($value);
}
if ($this->isJsonCastable($key) && !is_null($value)) {
$value = $this->castAttributeAsJson($key, $value);
}
// If this attribute contains a JSON ->, we'll set the proper value in the
// attribute's underlying array. This takes care of properly nesting an
// attribute in the array's value in the case of deeply nested items.
if (Str::contains($key, '->')) {
return $this->fillJsonAttribute($key, $value);
}
// Cached table names
$schema = Cache::remember('table-schema-' . $this->getTable(), 60, function () {
return $this->getTableColumns();
});
if (in_array($key, $schema)) {
$this->attributes[$key] = $value;
} else {
$this->setExtendedOption($key, $value);
}
return $this;
}
/**
* We can improve this, but it works for now.
* @param string $key
* @param $value
* @return bool
*/
public function setExtendedOption(string $key, $value): bool
{
$name = $this->getExtendedColumn();
$temp = $this->$name;
$temp[$key] = $value;
$this->$name = $temp;
return true;
}
/**
* Get a collection of our table columns.
* @param null $table
* @return mixed
*/
public function getTableColumns($table = null)
{
if (!$table) {
$table = $this->getTable();
}
return DB::getSchemaBuilder()->getColumnListing($table);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment