1
0
Fork 0
mirror of https://github.com/Oreolek/ifhub.club.git synced 2024-07-05 16:04:24 +03:00
ifhub.club/engine/classes/EntityORM.class.php

617 lines
19 KiB
PHP
Raw Normal View History

2010-08-13 19:26:19 +03:00
<?php
/*-------------------------------------------------------
*
* LiveStreet Engine Social Networking
* Copyright © 2008 Mzhelskiy Maxim
*
*--------------------------------------------------------
*
* Official site: www.livestreet.ru
* Contact e-mail: rus.engine@gmail.com
*
* GNU General Public License, version 2:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
*
---------------------------------------------------------
*/
/**
* Абстрактный класс сущности ORM - аналог active record
* Позволяет без написания SQL запросов работать с базой данных.
* <pre>
* $oUser=$this->User_GetUserById(1);
* $oUser->setName('Claus');
* $oUser->Update();
* </pre>
* Возможно получать списки объектов по фильтру:
* <pre>
* $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'))));
* </pre>
2010-08-13 19:26:19 +03:00
*
* @package engine.orm
* @since 1.0
2010-08-13 19:26:19 +03:00
*/
2011-05-31 16:04:05 +03:00
abstract class EntityORM extends Entity {
/**
* Типы связей сущностей
*
*/
2010-08-13 19:26:19 +03:00
const RELATION_TYPE_BELONGS_TO='belongs_to';
const RELATION_TYPE_HAS_MANY='has_many';
const RELATION_TYPE_HAS_ONE='has_one';
2010-08-29 10:03:53 +03:00
const RELATION_TYPE_MANY_TO_MANY='many_to_many';
const RELATION_TYPE_TREE='tree';
2011-05-31 16:04:05 +03:00
/**
* Массив исходных данных сущности
*
* @var array
*/
2011-05-31 16:04:05 +03:00
protected $_aOriginalData=array();
/**
* Список полей таблицы сущности
*
* @var array
*/
protected $aFields=array();
/**
* Список связей
*
* @var array
*/
2010-08-13 19:26:19 +03:00
protected $aRelations=array();
/**
* Список данных связей
*
* @var array
*/
2010-08-13 19:26:19 +03:00
protected $aRelationsData=array();
/**
* Объекты связей many_to_many
*
* @var array
*/
protected $_aManyToManyRelations = array();
/**
* Флаг новая или нет сущность
*
* @var bool
*/
2010-08-13 19:26:19 +03:00
protected $bIsNew=true;
2011-05-31 16:04:05 +03:00
/**
* Установка связей
* @see Entity::__construct
*
* @param bool $aParam Ассоциативный массив данных сущности
*/
public function __construct($aParam=false) {
2010-08-13 19:26:19 +03:00
parent::__construct($aParam);
$this->aRelations=$this->_getRelations();
2010-08-13 19:26:19 +03:00
}
/**
* Получение 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];
2010-08-29 10:03:53 +03:00
}
}
}
2010-08-13 19:26:19 +03:00
return $this->sPrimaryKey;
}
/**
* Получение значения primary key
*
* @return string
*/
public function _getPrimaryKeyValue() {
return $this->_getDataOne($this->_getPrimaryKey());
}
/**
* Новая или нет сущность
* Новая - еще не сохранялась в БД
*
* @return bool
*/
2010-08-13 19:26:19 +03:00
public function _isNew() {
return $this->bIsNew;
}
/**
* Установка флага "новая"
*
* @param bool $bIsNew Флаг - новая сущность или нет
*/
2010-08-13 19:26:19 +03:00
public function _SetIsNew($bIsNew) {
$this->bIsNew=$bIsNew;
}
/**
* Добавление сущности в БД
*
* @return Entity|false
*/
2010-08-13 19:26:19 +03:00
public function Add() {
if ($this->beforeSave())
if ($res=$this->_Method(__FUNCTION__)) {
$this->afterSave();
return $res;
}
return false;
2010-08-13 19:26:19 +03:00
}
/**
* Обновление сущности в БД
*
* @return Entity|false
*/
2010-08-13 19:26:19 +03:00
public function Update() {
if ($this->beforeSave())
if ($res=$this->_Method(__FUNCTION__)) {
$this->afterSave();
return $res;
}
return false;
2010-08-13 19:26:19 +03:00
}
/**
* Сохранение сущности в БД (если новая то создается)
*
* @return Entity|false
*/
2010-08-13 19:26:19 +03:00
public function Save() {
if ($this->beforeSave())
if ($res=$this->_Method(__FUNCTION__)) {
$this->afterSave();
return $res;
}
return false;
2010-08-13 19:26:19 +03:00
}
/**
* Удаление сущности из БД
*
* @return Entity|false
*/
2010-08-13 19:26:19 +03:00
public function Delete() {
if ($this->beforeDelete())
if ($res=$this->_Method(__FUNCTION__)) {
$this->afterDelete();
return $res;
}
return false;
2011-05-31 16:04:05 +03:00
}
/**
* Обновляет данные сущности из БД
*
* @return Entity|false
*/
2010-08-29 10:03:53 +03:00
public function Reload() {
return $this->_Method(__FUNCTION__);
2011-05-31 16:04:05 +03:00
}
/**
* Возвращает список полей сущности
*
* @return array
*/
public function ShowColumns() {
return $this->_Method(__FUNCTION__ .'From');
2010-08-13 19:26:19 +03:00
}
/**
* Возвращает primary индекс сущности
*
* @return array
*/
public function ShowPrimaryIndex() {
return $this->_Method(__FUNCTION__ .'From');
}
/**
* Хук, срабатывает перед сохранением сущности
*
* @return bool
*/
protected function beforeSave() {
return true;
}
/**
* Хук, срабатывает после сохранением сущности
*
*/
protected function afterSave() {
2011-05-31 16:04:05 +03:00
}
/**
* Хук, срабатывает перед удалением сущности
*
* @return bool
*/
protected function beforeDelete() {
return true;
}
/**
* Хук, срабатывает после удаления сущности
*
*/
protected function afterDelete() {
2011-05-31 16:04:05 +03:00
}
/**
* Для сущности со связью 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);
}
2011-05-31 16:04:05 +03:00
}
/**
* Для сущности со связью 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
*/
2011-05-31 16:04:05 +03:00
protected function _Method($sName) {
2010-08-13 19:26:19 +03:00
$sModuleName=Engine::GetModuleName($this);
$sEntityName=Engine::GetEntityName($this);
2011-05-31 16:04:05 +03:00
$sPluginPrefix=Engine::GetPluginPrefix($this);
2010-11-18 19:09:22 +02:00
/**
* If Module not exists, try to find its root Delegater
*/
2010-11-18 19:46:19 +02:00
$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);
2011-05-31 16:04:05 +03:00
$sPluginPrefix=Engine::GetPluginPrefix($sRootDelegater);
2010-11-17 23:32:57 +02:00
}
2010-08-13 19:26:19 +03:00
return Engine::GetInstance()->_CallModule("{$sPluginPrefix}{$sModuleName}_{$sName}{$sEntityName}",array($this));
}
/**
* Устанавливает данные сущности
*
* @param array $aData Ассоциативный массив данных сущности
*/
2010-08-14 01:40:01 +03:00
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;
2010-08-14 01:40:01 +03:00
}
}
/**
* Возвращает все данные сущности
*
* @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() {
2010-11-17 20:38:44 +02:00
$sParent=get_parent_class($this);
2010-11-17 23:32:57 +02:00
if(substr_count($sParent,'_Inherits_') || substr_count($sParent,'_Inherit_')) {
2010-11-17 20:38:44 +02:00
$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);
2011-05-31 16:04:05 +03:00
}
/**
* Возвращает список данный связей
*
* @return array
*/
public function _getRelationsData() {
return $this->aRelationsData;
}
/**
* Устанавливает данные связей
*
* @param array $aData Список связанных данных
*/
public function _setRelationsData($aData) {
$this->aRelationsData=$aData;
2011-05-31 16:04:05 +03:00
}
/**
* Ставим хук на вызов неизвестного метода и считаем что хотели вызвать метод какого либо модуля
* Также производит обработку методов set* и get*
* Учитывает связи и может возвращать связанные данные
* @see Engine::_CallModule
*
* @param string $sName Имя метода
* @param array $aArgs Аргументы
* @return mixed
*/
2010-08-13 19:26:19 +03:00
public function __call($sName,$aArgs) {
$sType=substr($sName,0,strpos(func_underscore($sName),'_'));
2011-05-31 16:04:05 +03:00
if (!strpos($sName,'_') and in_array($sType,array('get','set','reload'))) {
2011-08-18 10:21:12 +03:00
$sKey=func_underscore(preg_replace('/'.$sType.'/','',$sName, 1));
2010-08-13 19:26:19 +03:00
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];
2010-08-13 19:26:19 +03:00
}
}
/**
* Проверяем на связи
*/
if (array_key_exists($sKey,$this->aRelations)) {
$sEntityRel=$this->aRelations[$sKey][1];
$sRelationType=$this->aRelations[$sKey][0];
$sRelationKey=$this->aRelations[$sKey][2];
2010-08-29 10:03:53 +03:00
$sRelationJoinTable=null;
$sRelationJoinTableKey=0; // foreign key в join-таблице для текущей сущности
2010-08-29 10:03:53 +03:00
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();
2010-08-29 10:03:53 +03:00
}
2011-05-31 16:04:05 +03:00
2010-08-13 19:26:19 +03:00
/**
* Если связь уже загруженна, то возвращаем сразу результат
*/
if (array_key_exists($sKey,$this->aRelationsData)) {
return $this->aRelationsData[$sKey];
}
2011-05-31 16:04:05 +03:00
2010-08-29 10:03:53 +03:00
$sRelModuleName=Engine::GetModuleName($sEntityRel);
$sRelEntityName=Engine::GetEntityName($sEntityRel);
$sRelPluginPrefix=Engine::GetPluginPrefix($sEntityRel);
$sRelPrimaryKey='id';
if($oRelEntity=Engine::GetEntity($sEntityRel)) {
$sRelPrimaryKey=$oRelEntity->_getPrimaryKey();
2010-08-29 10:03:53 +03:00
}
2011-05-31 16:04:05 +03:00
$iPrimaryKeyValue=$this->_getDataOne($this->_getPrimaryKey());
2010-08-13 19:26:19 +03:00
$sCmd='';
$mCmdArgs=array();
2010-08-13 19:26:19 +03:00
switch ($sRelationType) {
2010-08-29 10:03:53 +03:00
case self::RELATION_TYPE_BELONGS_TO :
2011-03-28 15:53:49 +03:00
$sCmd="{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}By".func_camelize($sRelPrimaryKey);
$mCmdArgs=$this->_getDataOne($sRelationKey);
2010-08-29 10:03:53 +03:00
break;
case self::RELATION_TYPE_HAS_ONE :
2011-03-28 15:53:49 +03:00
$sCmd="{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}By".func_camelize($sRelationKey);
$mCmdArgs=$iPrimaryKeyValue;
2010-08-29 10:03:53 +03:00
break;
2010-08-13 19:26:19 +03:00
case self::RELATION_TYPE_HAS_MANY :
$sCmd="{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}ItemsByFilter";
$mCmdArgs=array($sRelationKey => $iPrimaryKeyValue);
2010-08-13 19:26:19 +03:00
break;
2010-08-29 10:03:53 +03:00
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 иметь первичные ключи сущностей
2010-08-29 10:03:53 +03:00
);
break;
2010-08-13 19:26:19 +03:00
default:
break;
2010-08-29 10:03:53 +03:00
}
// Нужно ли учитывать дополнительный фильтр
$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));
2010-08-29 10:03:53 +03:00
// Сохраняем данные только в случае "чистой" выборки
if(!$bUseFilter) {
$this->aRelationsData[$sKey]=$res;
}
// Создаём объекты-обёртки для связей MANY_TO_MANY
if ($sRelationType == self::RELATION_TYPE_MANY_TO_MANY) {
$this->_aManyToManyRelations[$sKey] = new LS_ManyToManyRelation($res);
}
2010-08-13 19:26:19 +03:00
return $res;
}
2011-05-31 16:04:05 +03:00
2010-08-13 19:26:19 +03:00
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];
2010-08-13 19:26:19 +03:00
}
2010-08-29 10:03:53 +03:00
} elseif ($sType=='reload') {
if (array_key_exists($sKey,$this->aRelationsData)) {
unset($this->aRelationsData[$sKey]);
return $this->__call('get'.func_camelize($sKey),$aArgs);
}
2010-08-13 19:26:19 +03:00
}
} 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]);
}
}
}