Start using StorageTrait for AbstractObject and AbstractObjectCollection

This commit is contained in:
Matias Griese
2017-03-07 15:45:15 +02:00
parent c1ee217cef
commit 703080a329
5 changed files with 130 additions and 385 deletions

View File

@@ -1,7 +1,6 @@
<?php
namespace Grav\Common\Object;
use Grav\Common\Object\Storage\StorageInterface;
use RocketTheme\Toolbox\ArrayTraits\ArrayAccessWithGetters;
use RocketTheme\Toolbox\ArrayTraits\Export;
@@ -12,36 +11,35 @@ use RocketTheme\Toolbox\ArrayTraits\Export;
*/
abstract class AbstractObject implements ObjectInterface
{
use ObjectStorageTrait {
check as traitcheck;
}
use ArrayAccessWithGetters, Export;
/**
* If you don't have global instance ids, override this in extending class.
* @var array
*/
static protected $instances = [];
/**
* If you don't have global storage, override this in extending class.
* @var StorageInterface
*/
static protected $storage;
/**
* If you don't have global storage, override this in extending class.
* @var ObjectFinderInterface
*/
static protected $finder;
/**
* Primary key for the object.
* @var array
*/
static protected $primaryKey = [
'id' => null
];
/**
* Default properties for the object.
* @var array
*/
static protected $defaults = ['id' => null];
static protected $defaults = [];
/**
* @var string
*/
static protected $collectionClass = 'Grav\\Common\\Object\\ObjectCollection';
static protected $collectionClass = 'Grav\\Common\\Object\\AbstractObjectCollection';
/**
* Properties of the object.
@@ -49,32 +47,6 @@ abstract class AbstractObject implements ObjectInterface
*/
protected $items;
/**
* Does object exist in storage?
* @var boolean
*/
protected $exists = false;
/**
* Readonly object.
* @var bool
*/
protected $readonly = false;
/**
* @var bool
*/
protected $initialized = false;
/**
* @param StorageInterface $storage
*/
static public function setStorage(StorageInterface $storage)
{
static::$storage = $storage;
}
/**
* @param ObjectFinderInterface $finder
*/
@@ -83,48 +55,10 @@ abstract class AbstractObject implements ObjectInterface
static::$finder = $finder;
}
/**
* Returns the global instance to the object.
*
* Note that using array of fields will always make a query, but it's very useful feature if you want to search one
* item by using arbitrary set of matching fields. If there are more than one matching object, first one gets returned.
*
* @param string|array $keys An optional primary key value to load the object by, or an array of fields to match.
* @param boolean $reload Force object to reload.
*
* @return Object
*/
static public function instance($keys = null, $reload = false)
{
// If we are creating or loading a new item or we load instance by alternative keys, we need to create a new object.
if (!$keys || is_array($keys) || (is_scalar($keys) && !isset(static::$instances[$keys]))) {
$c = get_called_class();
/** @var ObjectStorageTrait $instance */
$instance = new $c();
$instance->load($keys);
/** @var Object $instance */
if (!$instance->exists()) return $instance;
// Instance exists: make sure that we return the global instance.
$keys = $instance->id;
}
// Return global instance from the identifier.
$instance = static::$instances[$keys];
if ($reload) {
$instance->load();
}
return $instance;
}
/**
* @param array $ids List of primary Ids or null to return everything that has been loaded.
* @param bool $readonly
* @return ObjectCollection
* @return AbstractObjectCollection
*/
static public function instances(array $ids = null, $readonly = true)
{
@@ -154,11 +88,10 @@ abstract class AbstractObject implements ObjectInterface
foreach ($list as $keys) {
/** @var static $instance */
$instance = new $c();
$instance->set($keys);
$instance->exists(true);
$instance->initialize();
$id = $instance->id;
$instance->doLoad($keys);
$id = $instance->getId();
if ($id && !isset(static::$instances[$id])) {
$instance->initialize();
static::$instances[$id] = $instance;
}
}
@@ -173,6 +106,14 @@ abstract class AbstractObject implements ObjectInterface
return new $collectionClass($results);
}
/**
* @return string
*/
public function getId()
{
return implode('-', $this->getKeys());
}
/**
* @return ObjectFinderInterface
*/
@@ -181,186 +122,6 @@ abstract class AbstractObject implements ObjectInterface
return static::$finder;
}
/**
* Removes all or selected instances from the object cache.
*
* @param null|string|array $ids An optional primary key or list of keys.
*/
static public function freeInstances($ids = null)
{
if ($ids === null) {
$ids = array_keys(static::$instances);
}
$ids = (array) $ids;
foreach ($ids as $id) {
unset(static::$instances[$id]);
}
}
/**
* Override this function if you need to initialize object right after creating it.
*
* Can be used for example if the fields need to be converted from json strings to array.
*
* @return bool True if initialization was done, false if object was already initialized.
*/
public function initialize()
{
$initialized = $this->initialized;
$this->initialized = true;
return !$initialized;
}
/**
* Convert instance to a read only object.
*
* @return $this
*/
public function readonly()
{
$this->readonly = true;
return $this;
}
/**
* Returns true if the object exists.
*
* @param boolean $exists Internal parameter to change state.
*
* @return boolean True if object exists.
*/
public function exists($exists = null)
{
$return = $this->exists;
if ($exists !== null) {
$this->exists = (bool) $exists;
}
return $return;
}
/**
* Method to load object from the storage.
*
* @param mixed $keys An optional primary key value to load the object by, or an array of fields to match. If not
* set the instance key value is used.
* @param bool $getKey Internal parameter, please do not use.
*
* @return bool True on success, false if the object doesn't exist.
*/
public function load($keys = null, $getKey = true)
{
if ($getKey) {
if (is_scalar($keys)) {
$keys = ['id' => (string) $keys];
}
// Fetch internal key.
$key = $this->getStorageKey($keys);
} else {
// Internal key was passed.
$key = $keys;
$keys = [];
}
// Get storage.
$storage = $this->getStorage();
// Load the object based on the keys.
$items = $storage->load($key);
$this->exists = !empty($items);
// Append the defaults and keys if they were not set by load().
$this->items = array_merge(static::$defaults, $keys, $this->items, $items);
$this->initialize();
if ($this->id) {
if (!isset(static::$instances[$this->id])) {
static::$instances[$this->id] = $this;
}
}
return $this->exists;
}
/**
* Method to save the object to the storage.
*
* Before saving the object, this method checks if object can be safely saved.
*
* @params bool $includeChildren
* @return bool True on success.
*/
public function save($includeChildren = false)
{
// Check the object.
if ($this->readonly || !$this->check($includeChildren) || !$this->onBeforeSave()) {
return false;
}
// Get storage.
$storage = $this->getStorage();
$key = $this->getStorageKey();
// Get data to be saved.
$data = $this->prepareSave($this->toArray());
// Save the object.
$id = $storage->save($key, $data);
if (!$id) {
throw new \LogicException('No id specified');
}
// If item was created, load the object.
if (!$this->exists) {
$this->load($id, false);
}
if ($includeChildren) {
$this->saveChildren();
}
$this->onAfterSave();
return true;
}
/**
* Method to delete the object from the database.
*
* @param bool $includeChildren
* @return bool True on success.
*/
public function delete($includeChildren = false)
{
if ($this->readonly || !$this->exists || !$this->onBeforeDelete()) {
return false;
}
if ($includeChildren) {
$this->deleteChildren();
}
// Get storage.
$storage = $this->getStorage();
if (!$storage->delete($this->getStorageKey())) {
return false;
}
$this->exists = false;
$this->onAfterDelete();
return true;
}
/**
* Method to perform sanity checks on the instance properties to ensure they are safe to store in the storage.
*
@@ -371,17 +132,46 @@ abstract class AbstractObject implements ObjectInterface
*/
public function check($includeChildren = false)
{
$result = true;
return $this->checkKeys() && $this->traitCheck($includeChildren);
}
if ($includeChildren) {
foreach ($this->items as $field => $value) {
if (is_object($value) && method_exists($value, 'check')) {
$result = $result && $value->check();
/**
* @param array $keys
* @return array
*/
public function getKeys(array $keys = [])
{
foreach (static::$primaryKey as $key => $value) {
if (!isset($keys[$key])) {
if (isset($this->items[$key])) {
$keys[$key] = $this->items[$key];
} else {
$keys[$key] = $value;
}
}
}
return $result && !empty($this->items['id']);
return $keys;
}
/**
* @param array $keys
* @return bool
*/
public function checkKeys(array $keys = [])
{
if (!$keys) {
$keys = $this->getKeys();
}
foreach ($keys as $key => $value) {
if ($value === null) {
return false;
}
}
return true;
}
/**
@@ -394,94 +184,24 @@ abstract class AbstractObject implements ObjectInterface
return $this->toArray();
}
/**
* Returns a string representation of this object.
*
* @return string
*/
public function __toString()
{
return __CLASS__ . '@' . spl_object_hash($this);
}
// Internal functions
/**
* @param array $items
* @return $this
* @param array $keys
*/
protected function set(array $items)
protected function doLoad(array $items, array $keys = [])
{
$this->items = $items;
return $this;
}
/**
* @return bool
*/
protected function onBeforeSave()
{
return true;
}
protected function onAfterSave()
{
}
/**
* @return bool
*/
protected function onBeforeDelete()
{
return true;
}
protected function onAfterDelete()
{
}
protected function saveChildren()
{
foreach ($this->toArray() as $field => $value) {
if (is_object($value) && method_exists($value, 'save')) {
$value->save(true);
}
}
}
protected function deleteChildren()
{
foreach ($this->toArray() as $field => $value) {
if (is_object($value) && method_exists($value, 'delete')) {
$value->delete(true);
}
}
}
protected function prepareSave(array $data)
{
foreach ($data as $field => $value) {
if (is_object($value) && method_exists($value, 'save')) {
unset($data[$field]);
}
}
return $data;
}
/**
* Method to get the storage key for the object.
*
* @param array
* @return string
*/
abstract public function getStorageKey(array $keys = []);
/**
* @return StorageInterface
*/
protected static function getStorage()
{
if (!static::$storage) {
static::loadStorage();
}
return static::$storage;
}
protected static function loadStorage()
{
throw new \RuntimeException('Storage has not been set.');
$this->items = array_replace(static::$defaults, $this->items, $this->getKeys($keys), $items);
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace Grav\Common\Object;
use Grav\Common\Collection\Collection;
use Grav\Common\Collection\ObjectCollectionInterface;
use Grav\Common\Collection\ObjectCollectionTrait;
abstract class AbstractObjectCollection extends Collection implements ObjectCollectionInterface
{
use ObjectCollectionTrait, ObjectStorageTrait;
protected $id;
public function setId($id)
{
$this->id = $id;
}
public function getId()
{
return $this->id;
}
}

View File

@@ -1,11 +0,0 @@
<?php
namespace Grav\Common\Object;
use Grav\Common\Collection\Collection;
use Grav\Common\Collection\ObjectCollectionInterface;
use Grav\Common\Collection\ObjectCollectionTrait;
class ObjectCollection extends Collection implements ObjectCollectionInterface
{
use ObjectCollectionTrait;
}

View File

@@ -19,7 +19,7 @@ interface ObjectInterface extends \ArrayAccess, \JsonSerializable
/**
* @param array $ids List of primary Ids or null to return everything that has been loaded.
* @param bool $readonly
* @return ObjectCollection
* @return AbstractObjectCollection
*/
static public function instances(array $ids = null, $readonly = true);
@@ -47,13 +47,11 @@ interface ObjectInterface extends \ArrayAccess, \JsonSerializable
public function readonly();
/**
* Returns true if the object exists.
*
* @param boolean $exists Internal parameter to change state.
* Returns true if the object exists in the storage.
*
* @return boolean True if object exists.
*/
public function exists($exists = null);
public function isSaved();
/**
* Method to load object from the storage.

View File

@@ -55,8 +55,13 @@ trait ObjectStorageTrait
*/
static public function instance($keys = null, $reload = false)
{
if (is_scalar($keys)) {
$keys = ['id' => (string) $keys];
}
$id = $keys ? static::getInstanceId($keys) : null;
// If we are creating or loading a new item or we load instance by alternative keys, we need to create a new object.
if (!$keys || is_array($keys) || (is_scalar($keys) && !isset(static::$instances[$keys]))) {
if (!$id || !isset(static::$instances[$id])) {
$c = get_called_class();
/** @var ObjectStorageTrait|ObjectInterface $instance */
@@ -66,12 +71,12 @@ trait ObjectStorageTrait
}
// Instance exists in storage: make sure that we return the global instance.
$keys = $instance->getInstanceId();
$id = $instance->getId();
$reload = false;
}
// Return global instance from the identifier.
$instance = static::$instances[$keys];
$instance = static::$instances[$id];
if ($reload) {
$instance->load();
@@ -151,7 +156,7 @@ trait ObjectStorageTrait
}
// Fetch internal key.
$key = $this->getStorageKey($keys);
$key = $keys ? $this->getStorageKey($keys) : null;
} else {
// Internal key was passed.
@@ -159,15 +164,10 @@ trait ObjectStorageTrait
$keys = [];
}
// Get storage.
$storage = $this->getStorage();
// Load the object based on the keys.
$this->items = $storage->load($key);
$this->doLoad($this->getStorage()->load($key), $keys);
$this->initialize();
$id = $this->getInstanceId();
$id = $this->getId();
if ($id) {
if (!isset(static::$instances[$id])) {
static::$instances[$id] = $this;
@@ -195,12 +195,12 @@ trait ObjectStorageTrait
// Get storage.
$storage = $this->getStorage();
$key = $this->getStorageKey();
$exists = $this->getStorage()->exists($key);
// Get data to be saved.
$data = $this->prepareSave();
// Save the object.
$exists = $storage->exists($key);
$id = $storage->save($key, $data);
if (!$id) {
@@ -262,7 +262,7 @@ trait ObjectStorageTrait
$result = true;
if ($includeChildren) {
foreach ($this as $field => $value) {
foreach ($this->toArray() as $field => $value) {
if (is_object($value) && method_exists($value, 'check')) {
$result = $result && $value->check();
}
@@ -274,6 +274,8 @@ trait ObjectStorageTrait
// Internal functions
abstract protected function doLoad(array $items, array $keys = []);
/**
* @return bool
*/
@@ -300,7 +302,7 @@ trait ObjectStorageTrait
protected function saveChildren()
{
foreach ($this as $field => $value) {
foreach ($this->toArray() as $field => $value) {
if (is_object($value) && method_exists($value, 'save')) {
$value->save(true);
}
@@ -309,7 +311,7 @@ trait ObjectStorageTrait
protected function deleteChildren()
{
foreach ($this as $field => $value) {
foreach ($this->toArray() as $field => $value) {
if (is_object($value) && method_exists($value, 'delete')) {
$value->delete(true);
}
@@ -339,11 +341,24 @@ trait ObjectStorageTrait
*/
abstract public function getStorageKey(array $keys = []);
public function getInstanceId(array $keys = [])
/**
* @param array $keys
* @return string
*/
public function getInstanceId(array $keys)
{
return $this->getStorageKey($keys);
}
/**
* @return string
*/
public function getId()
{
return $this->getStorageKey();
}
//abstract public function setStorageKey(array $keys = []);
/**