<?php
/**
* @version 1.0.0
* @category Anahita Social Engineâ¢
* @copyright Copyright (C) 2008 - 2010 rmdStudio Inc. and Peerglobe Technology Inc. All rights reserved.
* @license GNU GPLv2 <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>
* @link http://www.anahitapolis.com
*/
abstract class AnDomainModelAbstract extends KObject implements AnDomainModelInterface
{
/**
*
*/
protected $_command_chain = null;
/**
*
*/
protected $_identifier = null;
/**
*
*/
protected $_context = null;
/**
*
*/
protected $_mapper = null;
/**
*
*/
public $_properties = array();
/**
*
* @return
* @param $model Object
*/
abstract static function describe($model);
/**
*
* @return
* @param $options Object[optional]
*/
public function __construct($options = array())
{
$this->_identifier = $options['identifier'];
$this->_mapper = $options['mapper'];
$options = $this->_initialize($options);
$this->_context = KFactory::get('lib.anahita.domain.context');
$this->_properties = $this->_mapper->getDefaultValues();
$this->_initialize();
}
public function inheritanceTree($class=null, array $plist=array())
{
$class = $class ? $class : $this;
$parent = get_parent_class($class);
if ( $parent == __CLASS__ ) {
return $plist;
}
$plist[] = $parent;
$plist = self::inheritanceTree($parent, $plist);
return $plist;
}
/**
* Called when a model is fetched from a database
*/
public function awakeFromFetch()
{
}
/**
* Called when an enity life cycle has begun. it's used to set default values
*/
public function awakeFromInsert()
{
//sub class can implement that to set initializtion scripts
}
/**
* Called after a new instance has been created or clone. Entities are usually cloned for performance
* so this method allows to initialize the entitty state even if it's cloned
* @return
*/
protected function _initialize()
{
//sub class can implement that to set initializtion scripts
}
/**
* Called before a db operation. This gives the entities a chacne to register before/after
* hooks for database operation
* @return
*/
protected function _registerHooks()
{
//sub classes can implement this method to add custom hooks
}
/**
* Valiate the state of the model by validating it's properties and calling appropicate hooks
* @return BOOL
* @param $context KCommandContext Object
*/
public function isValid(KCommandContext $context = null)
{
if ( is_null($context) )$context = KFactory::tmp('lib.koowa.command.context');
if ( $this->isClean() )
return true;
//don't need to validate properties if we are deleting an entity
if ( $this->isDirty() || $this->isNew() )
if ( $this->validateProperties($context) === false )
return false;
if ( $this->isNew() ) {
$ret = $this->validateForInsert($context);
} else if ( $this->isDirty() )
$ret = $this->validateForUpdate($context);
else
$ret = $this->validateForDelete($context);
if ( $ret !== false )
$ret = $this->validate($context);
return $ret === false ? false : true;
}
/**
* Validate model properties
* @return
* @param $context Object[optional]
*/
public function validateProperties(KCommandContext $context)
{
$properties = $this->getMapper()->getProperties();
foreach($properties as $name => $property) {
if ( $property instanceof AnDomainDescriptionRelationshipOnetomany )
continue;
$value = $this->$name;
if ( $property->isRequired() && !$property->validatePresenceOf( $value ) )
{
$context->setError(get_class($this)." requires {$name}");
return false;
}
if ( $property instanceof AnDomainDescriptionAttribute && $filter = $property->getFilter() )
{
if ( !$filter->validate($value) )
{
$context->setError('Property validation failed for '.get_class($this).'::'.$name);
return false;
}
}
$method = 'validate'.ucfirst($name);
if ( method_exists($this, $method ) )
return $this->$method($value, $context);
}
return true;
}
/**
* Validate an entity for all of its state
* @return BOOL
* @param $context KCommandContext Object
*/
public function validate(KCommandContext $context) {}
/**
* Validate before an entity is deleted
* @return BOOL
* @param $context KCommandContext Object
*/
public function validateForDelete(KCommandContext $context) {}
/**
* Validate before an entity is inserted
* @return BOOL
* @param $context KCommandContext Object
*/
public function validateForInsert(KCommandContext $context) {}
/**
* Validate before an entity is updated
* @return
* @param $context KCommandContext Object
*/
public function validateForUpdate(KCommandContext $context) {}
/**
* @TODO must be removed
* sets the primary key value of this model
* @return
* @param $id Object
*/
final public function setId($id)
{
$this->_set('id', $id);
}
/**
* @TODO must be removed
* get the primary key value of the model
* @return
*/
final public function getId()
{
return $this->_get('id');
}
public function uniqueValues()
{
$values = array();
foreach($this->getMapper()->getUniqueKeys() as $name => $property) {
if ( $value = $this->$name ) {
$values[$name] = $value;
}
}
return $values;
}
/**
* gets the primitive value of a property
* @return
* @param $property Object
*/
protected function _get($name)
{
return isset($this->_properties[$name]) ? $this->_properties[$name] : null;
}
/**
* sets the primitive value of a property
* @return
* @param $property Object
* @param $value Object
*/
protected function _set($name, $value)
{
$property = $this->getMapper()->getProperty($name);
if ( !$property ) {
//wrong
$this->_properties[$name] = $value;
return;
}
if ( $property instanceof AnDomainRelationshipOnetomany )
throw new Exception("Trying to set a many relationship");
$type = $property->getType();
if ( $value && $type && !$value instanceof $type ) {
throw new Exception("{$name} must be an instance of {$type}");
}
if ( $property instanceof AnDomainDescriptionAttribute && $filter = $property->getFilter() ) {
$value = $filter->sanitize($value);
}
$this->_properties[$name] = $value;
$unique_properties = $this->getMapper()->getUniqueKeys();
$this->_context->propertyValueChanged($this, $name, $value);
}
/**
* sets a property of the entity - it check to see if a set{$property} exists
* @return
* @param $property Object
* @param $value Object[optional]
*/
public final function setProperty( $property, $value = null )
{
if(is_object($property))
$property = (array) $property;
if(is_array($property)) {
foreach ($property as $k => $v)
$this->setProperty($k, $v);
} else {
$method = 'set'.ucfirst($property);
if (method_exists($this, $method)) {
$this->$method($value);
return ;
}
$this->_set($property, $value);
}
return $this;
}
/**
* gets a value of a property
* @return
* @param $property Object[optional]
* @param $default Object[optional]
*/
public function getProperty($property = null, $default = null)
{
if ( is_null($property) )
return $this->_properties;
$method = 'get'.ucfirst($property);
if (method_exists($this,$method)) {
$value = $this->$method();
} else {
$value = $this->_get($property);
}
if ( is_null($value) ) $value = $default;
return $value;
}
/**
* Return a list of properties whose value have changed
* @return Array
*/
public function updatedProperties()
{
$changes = $this->_context->propertyChangeTracks($this);
if ( $changes )
return $changes;
return array();
}
/**
* Return true or false depending if the value of the property has changed
* @return Bool
* @param $name String
*/
public function trackChanges($name)
{
$changes = $this->_context->propertyChangeTracks($this);
if ( !isset($changes[$name]) ) return null;
return $changes[$name];
}
public function __set($name, $value)
{
$this->setProperty($name, $value);
}
public function __get($name)
{
return $this->getProperty($name, null);
}
public function __isset($name)
{
return isset($this->_properties[$name]);
}
public function __unset($name)
{
unset($this->_properties[$name]);
}
public function eql($entity)
{
return $entity instanceof AnDomainProxyEntity ? $entity->getObject() === $entity : $this === $entity;
}
public function markNew()
{
$this->_context->addNew($this);
}
public function markDeleted()
{
$this->_context->addDelete($this);
}
public function markDirty()
{
$this->_context->addDirty($this);
}
public function markClean()
{
$this->_context->addClean($this);
}
public function isClean()
{
return !$this->isDirty() && !$this->isNew() && !$this->isDeleted();
}
public function isDirty()
{
return $this->_context->isDirty($this);
}
public function isNew()
{
return $this->_context->isNew($this);
}
public function isDeleted()
{
return $this->_context->isDeleted($this);
}
public function delete($context = null)
{
$this->markDeleted();
return $this->save($context);
}
/**
* Hook called before an entity is inserted into the database
* @return
* @param $context KCommandContext Object
*/
protected function _beforeInsert(KCommandContext $context) {}
/**
* Hook called after an entity is inserted into the database
* @param $context KCommandContext Object
*/
protected function _afterInsert(KCommandContext $context) {}
/**
* Hook called before an entity is updated
* @param $context KCommandContext Object
*/
protected function _beforeUpdate(KCommandContext $context) {}
/**
* Hook called after an entity is updated
* @return
* @param $context KCommandContext Object
*/
protected function _afterUpdate(KCommandContext $context) {}
/**
* Hook called before an entity is deleted
* @return
* @param $context KCommandContext Object
*/
protected function _beforeDelete(KCommandContext $context) {}
/**
* Hook called after an entity is deleted
* @param $context KCommandContext Object
*/
protected function _afterDelete(KCommandContext $context) {}
/**
* Hook called before any database commit - It can be insert, update or delete. The state of the entity can
* be checked by $entity->isNew(), $entity->isDeleted() or $entity->isDirty()
* @param $context KCommandContext Object
*/
protected function _beforeSave(KCommandContext $context) {}
/**
* Hook called after any database commit - It can be insert, update or delete. The state of the entity can
* @return
* @param $context Object
*/
protected function _afterSave(KCommandContext $context) {}
/**
* Persist the entity in to the database. It first validates the entity, if valid then it's stored in the
* database
* @return
* @param $context Object[optional]
*/
public function save(KCommandContext $context = null)
{
if ( $this->isClean() )
return;
if ( !$context )
$context = KFactory::tmp('lib.koowa.command.context');
if ( $this->isValid($context) === false )
return false;
$command = strtolower($this->isNew() ? 'Insert' : ( $this->isDirty() ? 'Update' : 'Delete' ));
$context['data'] = array();
$context['command'] = $command;
// $this->getRepository()->getCommandChain()->run("entity.before.$command", $context);
$this->getCommandChain()->run("entity.before.$command", $context);
$this->{"_before$command"}($context);
$this->_beforeSave($context);
$this->getMapper()->commit($this, $context['data']);
$this->_afterSave($context);
$this->{"_after$command"}($context);
$this->getCommandChain()->run("entity.after.$command", $context);
// $this->getRepository()->getCommandChain()->run("entity.after.$command", $context);
}
/**
* Return the command chain
* @return
*/
public function getCommandChain()
{
if ( !($this->_command_chain) ) {
$this->_command_chain = new KCommandChain();
$this->mixin( KFactory::tmp('lib.koowa.mixin.command', array('command_chain'=>$this->_command_chain)));
$this->_registerHooks();
}
return $this->_command_chain;
}
public function getIdentifier()
{
return $this->_identifier;
}
public function getDomainContext()
{
return $this->_context;
}
public function setMapper($mapper)
{
$this->_mapper = $mapper;
return $this;
}
public function getMapper()
{
return $this->_mapper;
}
public function serialize()
{
return $this->getMapper()->serialize($this->_properties);
}
public function getRepository()
{
return $this->getMapper()->getRepository();
}
/**
* clones a model - this is for when creating a list of models and we don't want to instantiate them
* @return
*/
public function __clone()
{
$this->_properties = $this->_mapper->getDefaultValues();
$this->_initialize();
}
public function toArray()
{
return $this->getProperty();
}
}