* $oUser=$this->User_GetUserById(1); * $oUser->setName('Claus'); * $oUser->Update(); * * Возможно получать списки объектов по фильтру: *
 * $aUsers=$this->User_GetUserItemsByAgeAndSex(18,'male');
 * // эквивалентно
 * $aUsers=$this->User_GetUserItemsByFilter(array('age'=>18,'sex'=>'male'));
 * // эквивалентно
 * $aUsers=$this->User_GetUserItemsByFilter(array('#where'=>array('age = ?d and sex = ?' => array(18,'male'))));
 * 
* * @package engine.orm * @since 1.0 */ abstract class EntityORM extends Entity { /** * Типы связей сущностей * */ const RELATION_TYPE_BELONGS_TO='belongs_to'; const RELATION_TYPE_HAS_MANY='has_many'; const RELATION_TYPE_HAS_ONE='has_one'; const RELATION_TYPE_MANY_TO_MANY='many_to_many'; const RELATION_TYPE_TREE='tree'; /** * Массив исходных данных сущности * * @var array */ protected $_aOriginalData=array(); /** * Список полей таблицы сущности * * @var array */ protected $aFields=array(); /** * Список связей * * @var array */ protected $aRelations=array(); /** * Список данных связей * * @var array */ protected $aRelationsData=array(); /** * Объекты связей many_to_many * * @var array */ protected $_aManyToManyRelations = array(); /** * Флаг новая или нет сущность * * @var bool */ protected $bIsNew=true; /** * Установка связей * @see Entity::__construct * * @param bool $aParam Ассоциативный массив данных сущности */ public function __construct($aParam=false) { parent::__construct($aParam); $this->aRelations=$this->_getRelations(); } /** * Получение primary key из схемы таблицы * * @return string|array Если индекс составной, то возвращает массив полей */ public function _getPrimaryKey() { if(!$this->sPrimaryKey) { if ($aIndex=$this->ShowPrimaryIndex()) { if (count($aIndex)>1) { // Составной индекс $this->sPrimaryKey=$aIndex; } else { $this->sPrimaryKey=$aIndex[1]; } } } return $this->sPrimaryKey; } /** * Получение значения primary key * * @return string */ public function _getPrimaryKeyValue() { return $this->_getDataOne($this->_getPrimaryKey()); } /** * Новая или нет сущность * Новая - еще не сохранялась в БД * * @return bool */ public function _isNew() { return $this->bIsNew; } /** * Установка флага "новая" * * @param bool $bIsNew Флаг - новая сущность или нет */ public function _SetIsNew($bIsNew) { $this->bIsNew=$bIsNew; } /** * Добавление сущности в БД * * @return Entity|false */ public function Add() { if ($this->beforeSave()) if ($res=$this->_Method(__FUNCTION__)) { $this->afterSave(); return $res; } return false; } /** * Обновление сущности в БД * * @return Entity|false */ public function Update() { if ($this->beforeSave()) if ($res=$this->_Method(__FUNCTION__)) { $this->afterSave(); return $res; } return false; } /** * Сохранение сущности в БД (если новая то создается) * * @return Entity|false */ public function Save() { if ($this->beforeSave()) if ($res=$this->_Method(__FUNCTION__)) { $this->afterSave(); return $res; } return false; } /** * Удаление сущности из БД * * @return Entity|false */ public function Delete() { if ($this->beforeDelete()) if ($res=$this->_Method(__FUNCTION__)) { $this->afterDelete(); return $res; } return false; } /** * Обновляет данные сущности из БД * * @return Entity|false */ public function Reload() { return $this->_Method(__FUNCTION__); } /** * Возвращает список полей сущности * * @return array */ public function ShowColumns() { return $this->_Method(__FUNCTION__ .'From'); } /** * Возвращает primary индекс сущности * * @return array */ public function ShowPrimaryIndex() { return $this->_Method(__FUNCTION__ .'From'); } /** * Хук, срабатывает перед сохранением сущности * * @return bool */ protected function beforeSave() { return true; } /** * Хук, срабатывает после сохранением сущности * */ protected function afterSave() { } /** * Хук, срабатывает перед удалением сущности * * @return bool */ protected function beforeDelete() { return true; } /** * Хук, срабатывает после удаления сущности * */ protected function afterDelete() { } /** * Для сущности со связью RELATION_TYPE_TREE возвращает список прямых потомков * * @return array */ public function getChildren() { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { return $this->_Method(__FUNCTION__ .'Of'); } return $this->__call(__FUNCTION__,array()); } /** * Для сущности со связью RELATION_TYPE_TREE возвращает список всех потомков * * @return array */ public function getDescendants() { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { return $this->_Method(__FUNCTION__ .'Of'); } return $this->__call(__FUNCTION__,array()); } /** * Для сущности со связью RELATION_TYPE_TREE возвращает предка * * @return Entity */ public function getParent() { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { return $this->_Method(__FUNCTION__ .'Of'); } return $this->__call(__FUNCTION__,array()); } /** * Для сущности со связью RELATION_TYPE_TREE возвращает список всех предков * * @return array */ public function getAncestors() { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { return $this->_Method(__FUNCTION__ .'Of'); } return $this->__call(__FUNCTION__,array()); } /** * Для сущности со связью RELATION_TYPE_TREE устанавливает потомков * * @param array $aChildren Список потомков */ public function setChildren($aChildren=array()) { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { $this->aRelationsData['children'] = $aChildren; } else { $aArgs = func_get_args(); return $this->__call(__FUNCTION__,$aArgs); } } /** * Для сущности со связью RELATION_TYPE_TREE устанавливает потомков * * @param array $aDescendants Список потомков */ public function setDescendants($aDescendants=array()) { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { $this->aRelationsData['descendants'] = $aDescendants; } else { $aArgs = func_get_args(); return $this->__call(__FUNCTION__,$aArgs); } } /** * Для сущности со связью RELATION_TYPE_TREE устанавливает предка * * @param Entity $oParent Родитель */ public function setParent($oParent=null) { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { $this->aRelationsData['parent'] = $oParent; } else { $aArgs = func_get_args(); return $this->__call(__FUNCTION__,$aArgs); } } /** * Для сущности со связью RELATION_TYPE_TREE устанавливает предков * * @param array $oParent Родитель */ public function setAncestors($oParent=null) { if(in_array(self::RELATION_TYPE_TREE,$this->aRelations)) { $this->aRelationsData['ancestors'] = $oParent; } else { $aArgs = func_get_args(); return $this->__call(__FUNCTION__,$aArgs); } } /** * Проксирует вызов методов в модуль сущности * * @param string $sName Название метода * @return mixed */ protected function _Method($sName) { $sModuleName=Engine::GetModuleName($this); $sEntityName=Engine::GetEntityName($this); $sPluginPrefix=Engine::GetPluginPrefix($this); /** * If Module not exists, try to find its root Delegater */ $aClassInfo = Engine::GetClassInfo($sPluginPrefix.'Module_'.$sModuleName,Engine::CI_MODULE); if(empty($aClassInfo[Engine::CI_MODULE]) && $sRootDelegater=$this->Plugin_GetRootDelegater('entity',get_class($this))) { $sModuleName=Engine::GetModuleName($sRootDelegater); $sPluginPrefix=Engine::GetPluginPrefix($sRootDelegater); } return Engine::GetInstance()->_CallModule("{$sPluginPrefix}{$sModuleName}_{$sName}{$sEntityName}",array($this)); } /** * Устанавливает данные сущности * * @param array $aData Ассоциативный массив данных сущности */ public function _setData($aData) { if(is_array($aData)) { foreach ($aData as $sKey => $val) { if (array_key_exists($sKey,$this->aRelations)) { $this->aRelationsData[$sKey]=$val; } else { $this->_aData[$sKey] = $val; } } $this->_aOriginalData = $this->_aData; } } /** * Возвращает все данные сущности * * @return array */ public function _getOriginalData() { return $this->_aOriginalData; } /** * Возвращает список полей сущности * * @return array */ public function _getFields() { if(empty($this->aFields)) { $this->aFields=$this->ShowColumns(); } return $this->aFields; } /** * Возвращает поле в нужном формате * * @param string $sField Название поля * @param int $iPersistence Тип "глубины" определения поля * @return null|string */ public function _getField($sField,$iPersistence=3) { if($aFields=$this->_getFields()) { if(in_array($sField,$aFields)) { return $sField; } if($iPersistence==0) { return null; } $sFieldU = func_camelize($sField); $sEntityField = func_underscore(Engine::GetEntityName($this).$sFieldU); if(in_array($sEntityField,$aFields)) { return $sEntityField; } if($iPersistence==1) { return null; } $sModuleEntityField = func_underscore(Engine::GetModuleName($this).Engine::GetEntityName($this).$sFieldU); if(in_array($sModuleEntityField,$aFields)) { return $sModuleEntityField; } if($iPersistence==2) { return null; } $sModuleField = func_underscore(Engine::GetModuleName($this).$sFieldU); if(in_array($sModuleField,$aFields)) { return $sModuleField; } } return $sField; } /** * Возвращает список связей * * @return array */ public function _getRelations() { $sParent=get_parent_class($this); if(substr_count($sParent,'_Inherits_') || substr_count($sParent,'_Inherit_')) { $sParent = get_parent_class($sParent); } $aParentRelations=array(); if(!in_array($sParent,array('Entity','EntityORM'))) { $oEntityParent=new $sParent(); $aParentRelations=$oEntityParent->_getRelations(); } return array_merge($aParentRelations,$this->aRelations); } /** * Возвращает список данный связей * * @return array */ public function _getRelationsData() { return $this->aRelationsData; } /** * Устанавливает данные связей * * @param array $aData Список связанных данных */ public function _setRelationsData($aData) { $this->aRelationsData=$aData; } /** * Ставим хук на вызов неизвестного метода и считаем что хотели вызвать метод какого либо модуля * Также производит обработку методов set* и get* * Учитывает связи и может возвращать связанные данные * @see Engine::_CallModule * * @param string $sName Имя метода * @param array $aArgs Аргументы * @return mixed */ public function __call($sName,$aArgs) { $sType=substr($sName,0,strpos(func_underscore($sName),'_')); if (!strpos($sName,'_') and in_array($sType,array('get','set','reload'))) { $sKey=func_underscore(preg_replace('/'.$sType.'/','',$sName, 1)); if ($sType=='get') { if (isset($this->_aData[$sKey])) { return $this->_aData[$sKey]; } else { $sField=$this->_getField($sKey); if($sField!=$sKey && isset($this->_aData[$sField])) { return $this->_aData[$sField]; } } /** * Проверяем на связи */ if (array_key_exists($sKey,$this->aRelations)) { $sEntityRel=$this->aRelations[$sKey][1]; $sRelationType=$this->aRelations[$sKey][0]; $sRelationKey=$this->aRelations[$sKey][2]; $sRelationJoinTable=null; $sRelationJoinTableKey=0; // foreign key в join-таблице для текущей сущности if($sRelationType == self::RELATION_TYPE_MANY_TO_MANY && array_key_exists(3, $this->aRelations[$sKey])) { $sRelationJoinTable=$this->aRelations[$sKey][3]; $sRelationJoinTableKey=isset($this->aRelations[$sKey][4]) ? $this->aRelations[$sKey][4] : $this->_getPrimaryKey(); } /** * Если связь уже загруженна, то возвращаем сразу результат */ if (array_key_exists($sKey,$this->aRelationsData)) { return $this->aRelationsData[$sKey]; } $sRelModuleName=Engine::GetModuleName($sEntityRel); $sRelEntityName=Engine::GetEntityName($sEntityRel); $sRelPluginPrefix=Engine::GetPluginPrefix($sEntityRel); $sRelPrimaryKey='id'; if($oRelEntity=Engine::GetEntity($sEntityRel)) { $sRelPrimaryKey=$oRelEntity->_getPrimaryKey(); } $iPrimaryKeyValue=$this->_getDataOne($this->_getPrimaryKey()); $sCmd=''; $mCmdArgs=array(); switch ($sRelationType) { case self::RELATION_TYPE_BELONGS_TO : $sCmd="{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}By".func_camelize($sRelPrimaryKey); $mCmdArgs=$this->_getDataOne($sRelationKey); break; case self::RELATION_TYPE_HAS_ONE : $sCmd="{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}By".func_camelize($sRelationKey); $mCmdArgs=$iPrimaryKeyValue; break; case self::RELATION_TYPE_HAS_MANY : $sCmd="{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}ItemsByFilter"; $mCmdArgs=array($sRelationKey => $iPrimaryKeyValue); break; case self::RELATION_TYPE_MANY_TO_MANY : $sCmd="{$sRelPluginPrefix}Module{$sRelModuleName}_get{$sRelEntityName}ItemsByJoinTable"; $mCmdArgs=array( '#join_table' => Config::Get($sRelationJoinTable), '#relation_key' => $sRelationKey, '#by_key' => $sRelationJoinTableKey, '#by_value' => $iPrimaryKeyValue, '#index-from-primary' => true // Для MANY_TO_MANY необходимо индексами в $aRelationsData иметь первичные ключи сущностей ); break; default: break; } // Нужно ли учитывать дополнительный фильтр $bUseFilter = is_array($mCmdArgs) && array_key_exists(0,$aArgs) && is_array($aArgs[0]); if($bUseFilter) { $mCmdArgs = array_merge($mCmdArgs, $aArgs[0]); } $res=Engine::GetInstance()->_CallModule($sCmd, array($mCmdArgs)); // Сохраняем данные только в случае "чистой" выборки if(!$bUseFilter) { $this->aRelationsData[$sKey]=$res; } // Создаём объекты-обёртки для связей MANY_TO_MANY if ($sRelationType == self::RELATION_TYPE_MANY_TO_MANY) { $this->_aManyToManyRelations[$sKey] = new LS_ManyToManyRelation($res); } return $res; } return null; } elseif ($sType=='set' and array_key_exists(0,$aArgs)) { if (array_key_exists($sKey,$this->aRelations)) { $this->aRelationsData[$sKey]=$aArgs[0]; } else { $this->_aData[$this->_getField($sKey)]=$aArgs[0]; } } elseif ($sType=='reload') { if (array_key_exists($sKey,$this->aRelationsData)) { unset($this->aRelationsData[$sKey]); return $this->__call('get'.func_camelize($sKey),$aArgs); } } } else { return Engine::getInstance()->_CallModule($sName,$aArgs); } } /** * Используется для доступа к связанным данным типа MANY_TO_MANY * * @param string $sName Название свойства к которому обращаемсяя * @return mixed */ public function __get($sName) { // Обработка обращений к обёрткам связей MANY_TO_MANY // Если связь загружена, возвращаем объект связи if (isset($this->_aManyToManyRelations[func_underscore($sName)])) { return $this->_aManyToManyRelations[func_underscore($sName)]; // Есл не загружена, но связь с таким именем существет, пробуем загрузить и вернуть объект связи } elseif (isset($this->aRelations[func_underscore($sName)]) && $this->aRelations[func_underscore($sName)][0] == self::RELATION_TYPE_MANY_TO_MANY) { $sMethod = 'get' . func_camelize($sName); $this->__call($sMethod, array()); if (isset($this->_aManyToManyRelations[func_underscore($sName)])) { return $this->_aManyToManyRelations[func_underscore($sName)]; } // В противном случае возвращаем то, что просили у объекта } else { return $this->$sName; } } /** * Сбрасывает данные необходимой связи * * @param string $sKey Ключ(поле) связи */ public function resetRelationsData($sKey) { if (isset($this->aRelationsData[$sKey])) { unset($this->aRelationsData[$sKey]); } } }