919 lines
31 KiB
PHP
919 lines
31 KiB
PHP
<?php
|
||
/*
|
||
* LiveStreet CMS
|
||
* Copyright © 2013 OOO "ЛС-СОФТ"
|
||
*
|
||
* ------------------------------------------------------
|
||
*
|
||
* Official site: www.livestreetcms.com
|
||
* Contact e-mail: office@livestreetcms.com
|
||
*
|
||
* GNU General Public License, version 2:
|
||
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
|
||
*
|
||
* ------------------------------------------------------
|
||
*
|
||
* @link http://www.livestreetcms.com
|
||
* @copyright 2013 OOO "ЛС-СОФТ"
|
||
* @author Maxim Mzhelskiy <rus.engine@gmail.com>
|
||
*
|
||
*/
|
||
|
||
/**
|
||
* Абстрактный класс сущности 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'));
|
||
* // эквивалентно, но при использовании #where необходимо указывать префикс таблицы "t"
|
||
* $aUsers=$this->User_GetUserItemsByFilter(array('#where'=>array('t.age = ?d and t.sex = ?' => array(18,'male'))));
|
||
* </pre>
|
||
*
|
||
* @package framework.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();
|
||
/**
|
||
* Список полей, которые нужно хранить как json строку
|
||
*
|
||
* @var array
|
||
*/
|
||
protected $aJsonFields = 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();
|
||
}
|
||
|
||
/**
|
||
* Устанавливает данные сущности из БД
|
||
*
|
||
* @param $aData
|
||
*/
|
||
public function _setDataFromDb($aData)
|
||
{
|
||
$this->_SetIsNew(false);
|
||
$this->_setOriginalData($aData);
|
||
foreach ($aData as $sField => $mValue) {
|
||
if (in_array($sField, $this->aJsonFields)) {
|
||
if (!is_null($mValue) and $mJsonData = @json_decode($mValue, true)) {
|
||
$aData[$sField] = $mJsonData;
|
||
} else {
|
||
$aData[$sField] = null;
|
||
}
|
||
}
|
||
}
|
||
$this->_setData($aData);
|
||
}
|
||
|
||
/**
|
||
* Получение 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());
|
||
}
|
||
|
||
/**
|
||
* Получение имени родительского поля. Используется в связи RELATION_TYPE_TREE
|
||
*
|
||
* @return string
|
||
*/
|
||
public function _getTreeParentKey()
|
||
{
|
||
return 'parent_id';
|
||
}
|
||
|
||
/**
|
||
* Получение значения родителя. Используется в связи RELATION_TYPE_TREE
|
||
*
|
||
* @return string
|
||
*/
|
||
public function _getTreeParentKeyValue()
|
||
{
|
||
return $this->_getDataOne($this->_getTreeParentKey());
|
||
}
|
||
|
||
/**
|
||
* Новая или нет сущность
|
||
* Новая - еще не сохранялась в БД
|
||
*
|
||
* @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()
|
||
{
|
||
$bResult = true;
|
||
$this->RunBehaviorHook('before_save', array('bResult' => &$bResult));
|
||
return $bResult;
|
||
}
|
||
|
||
/**
|
||
* Хук, срабатывает после сохранения сущности
|
||
*
|
||
*/
|
||
protected function afterSave()
|
||
{
|
||
$this->RunBehaviorHook('after_save');
|
||
}
|
||
|
||
/**
|
||
* Хук, срабатывает перед удалением сущности
|
||
*
|
||
* @return bool
|
||
*/
|
||
protected function beforeDelete()
|
||
{
|
||
$bResult = true;
|
||
$this->RunBehaviorHook('before_delete', array('bResult' => &$bResult));
|
||
return $bResult;
|
||
}
|
||
|
||
/**
|
||
* Хук, срабатывает после удаления сущности
|
||
*
|
||
*/
|
||
protected function afterDelete()
|
||
{
|
||
$this->RunBehaviorHook('after_delete');
|
||
}
|
||
|
||
/**
|
||
* Для сущности со связью RELATION_TYPE_TREE возвращает список прямых потомков
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getChildren()
|
||
{
|
||
if ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
return $this->_Method(__FUNCTION__ . 'Of');
|
||
}
|
||
return $this->__call(__FUNCTION__, array());
|
||
}
|
||
|
||
/**
|
||
* Для сущности со связью RELATION_TYPE_TREE возвращает список всех потомков
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getDescendants()
|
||
{
|
||
if ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
return $this->_Method(__FUNCTION__ . 'Of');
|
||
}
|
||
return $this->__call(__FUNCTION__, array());
|
||
}
|
||
|
||
/**
|
||
* Для сущности со связью RELATION_TYPE_TREE возвращает предка
|
||
*
|
||
* @return Entity
|
||
*/
|
||
public function getParent()
|
||
{
|
||
if ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
return $this->_Method(__FUNCTION__ . 'Of');
|
||
}
|
||
return $this->__call(__FUNCTION__, array());
|
||
}
|
||
|
||
/**
|
||
* Для сущности со связью RELATION_TYPE_TREE возвращает список всех предков
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getAncestors()
|
||
{
|
||
if ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
return $this->_Method(__FUNCTION__ . 'Of');
|
||
}
|
||
return $this->__call(__FUNCTION__, array());
|
||
}
|
||
|
||
/**
|
||
* Для сущности со связью RELATION_TYPE_TREE устанавливает потомков
|
||
*
|
||
* @param array $aChildren Список потомков
|
||
*/
|
||
public function setChildren($aChildren = array())
|
||
{
|
||
if ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
$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 ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
$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 ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
$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 ($this->_isUsedRelationType(self::RELATION_TYPE_TREE)) {
|
||
$this->aRelationsData['ancestors'] = $oParent;
|
||
} else {
|
||
$aArgs = func_get_args();
|
||
return $this->__call(__FUNCTION__, $aArgs);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Проксирует вызов методов в модуль сущности
|
||
*
|
||
* @param string $sName Название метода
|
||
* @return mixed
|
||
*/
|
||
protected function _Method($sName)
|
||
{
|
||
$sRootDelegater = $this->Plugin_GetRootDelegater('entity', get_class($this));
|
||
|
||
$sModuleName = Engine::GetModuleName($sRootDelegater);
|
||
$sPluginPrefix = Engine::GetPluginPrefix($sRootDelegater);
|
||
$sEntityName = Engine::GetEntityName($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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Устанавливает все оригинальные данные
|
||
*
|
||
* @param $aData
|
||
*/
|
||
public function _setOriginalData($aData)
|
||
{
|
||
$this->_aOriginalData = $aData;
|
||
}
|
||
|
||
/**
|
||
* Возвращает все оригинальные данные сущности
|
||
*
|
||
* @return array
|
||
*/
|
||
public function _getOriginalData()
|
||
{
|
||
return $this->_aOriginalData;
|
||
}
|
||
|
||
/**
|
||
* Возвращает "оригинальные" данные по конкретному полю
|
||
*
|
||
* @param string $sKey Название поля, например <pre>'my_property'</pre>
|
||
* @return null|mixed
|
||
*/
|
||
public function _getOriginalDataOne($sKey)
|
||
{
|
||
if (array_key_exists($sKey, $this->_aOriginalData)) {
|
||
return $this->_aOriginalData[$sKey];
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Возвращает данные для списка полей сущности
|
||
*
|
||
* @return array
|
||
*/
|
||
public function _getDataFields($bOnlyChanged = false)
|
||
{
|
||
$aData = $this->_getData($this->_getFields());
|
||
if ($bOnlyChanged) {
|
||
/**
|
||
* Сравниваем список оригинальных значений с текущими
|
||
*/
|
||
$aDataOriginal = $this->_getOriginalData();
|
||
foreach ($aData as $sKey => $sValue) {
|
||
if (array_key_exists($sKey, $aDataOriginal) and $aDataOriginal[$sKey] === $sValue) {
|
||
unset($aData[$sKey]);
|
||
}
|
||
}
|
||
}
|
||
return $aData;
|
||
}
|
||
|
||
public function _getDataFieldsForDb($bOnlyChanged = false)
|
||
{
|
||
$aData = $this->_getDataFields($bOnlyChanged);
|
||
/**
|
||
* Проверяем на json поля
|
||
*/
|
||
foreach ($aData as $sField => $mValue) {
|
||
if (in_array($sField, $this->aJsonFields)) {
|
||
$aData[$sField] = json_encode($mValue);
|
||
}
|
||
}
|
||
return $aData;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список полей сущности
|
||
*
|
||
* @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)
|
||
{
|
||
$sRootDelegater = $this->Plugin_GetRootDelegater('entity', get_class($this));
|
||
|
||
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($sRootDelegater) . $sFieldU);
|
||
if (in_array($sEntityField, $aFields)) {
|
||
return $sEntityField;
|
||
}
|
||
if ($iPersistence == 1) {
|
||
return null;
|
||
}
|
||
$sModuleEntityField = func_underscore(Engine::GetModuleName($sRootDelegater) . Engine::GetEntityName($sRootDelegater) . $sFieldU);
|
||
if (in_array($sModuleEntityField, $aFields)) {
|
||
return $sModuleEntityField;
|
||
}
|
||
if ($iPersistence == 2) {
|
||
return null;
|
||
}
|
||
$sModuleField = func_underscore(Engine::GetModuleName($sRootDelegater) . $sFieldU);
|
||
if (in_array($sModuleField, $aFields)) {
|
||
return $sModuleField;
|
||
}
|
||
}
|
||
return $sField;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список связей
|
||
*
|
||
* @return array
|
||
*/
|
||
public function _getRelations()
|
||
{
|
||
/**
|
||
* Преобразуем связи к единому ассоциативному виду и проставляем дефолтные значения
|
||
*/
|
||
foreach ($this->aRelations as $sName => $aParams) {
|
||
if (is_int($sName)) {
|
||
/**
|
||
* Старый вариант использования связи RELATION_TYPE_TREE
|
||
*/
|
||
$this->aRelations[$sName] = array(
|
||
'type' => self::RELATION_TYPE_TREE
|
||
);
|
||
continue;
|
||
}
|
||
if (isset($aParams['type'])) {
|
||
/**
|
||
* Проставляем дефолтные значения
|
||
*/
|
||
if (!array_key_exists('filter', $aParams)) {
|
||
$aParams['filter'] = array();
|
||
}
|
||
if ($aParams['type'] == EntityORM::RELATION_TYPE_BELONGS_TO) {
|
||
if (!array_key_exists('rel_key_to', $aParams)) {
|
||
$aParams['rel_key_to'] = null;
|
||
}
|
||
}
|
||
if ($aParams['type'] == EntityORM::RELATION_TYPE_HAS_MANY) {
|
||
if (!array_key_exists('key_from', $aParams)) {
|
||
$aParams['key_from'] = null;
|
||
}
|
||
}
|
||
$this->aRelations[$sName] = $aParams;
|
||
continue;
|
||
}
|
||
/**
|
||
* Преобразование от старой записи к новой
|
||
*/
|
||
$aParamsNew = array(
|
||
'type' => $aParams[0],
|
||
'rel_entity' => $aParams[1],
|
||
'rel_key' => $aParams[2],
|
||
'filter' => array()
|
||
);
|
||
if ($aParamsNew['type'] == EntityORM::RELATION_TYPE_BELONGS_TO) {
|
||
$aParams['rel_key_to'] = null;
|
||
}
|
||
if ($aParamsNew['type'] == EntityORM::RELATION_TYPE_HAS_ONE) {
|
||
if (isset($aParams[3])) {
|
||
$aParamsNew['filter'] = $aParams[3];
|
||
}
|
||
}
|
||
if ($aParamsNew['type'] == EntityORM::RELATION_TYPE_HAS_MANY) {
|
||
if (isset($aParams[3])) {
|
||
$aParamsNew['filter'] = $aParams[3];
|
||
}
|
||
$aParams['key_from'] = null;
|
||
}
|
||
if ($aParamsNew['type'] == EntityORM::RELATION_TYPE_MANY_TO_MANY) {
|
||
$aParamsNew['join_entity'] = $aParams[3];
|
||
$aParamsNew['join_key'] = $aParams[4];
|
||
if (isset($aParams[5])) {
|
||
$aParamsNew['filter'] = $aParams[5];
|
||
}
|
||
}
|
||
$this->aRelations[$sName] = $aParamsNew;
|
||
}
|
||
return $this->aRelations;
|
||
}
|
||
|
||
/**
|
||
* Проверяет факт использования сущностью определенного типа связи
|
||
*
|
||
* @param $sType
|
||
* @return bool
|
||
*/
|
||
public function _isUsedRelationType($sType)
|
||
{
|
||
$aRelations = $this->_getRelations();
|
||
foreach ($aRelations as $sName => $aParams) {
|
||
if (isset($aParams['type']) and $aParams['type'] == $sType) {
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Возвращает список данных связей
|
||
*
|
||
* @param string|null $sKey
|
||
*
|
||
* @return array|null
|
||
*/
|
||
public function _getRelationsData($sKey = null)
|
||
{
|
||
if ($sKey) {
|
||
if (array_key_exists($sKey, $this->aRelationsData)) {
|
||
return $this->aRelationsData[$sKey];
|
||
}
|
||
return null;
|
||
}
|
||
return $this->aRelationsData;
|
||
}
|
||
|
||
/**
|
||
* Устанавливает данные связей
|
||
*
|
||
* @param array $aData Список связанных данных
|
||
*/
|
||
public function _setRelationsData($aData)
|
||
{
|
||
$this->aRelationsData = $aData;
|
||
}
|
||
|
||
/**
|
||
* Устанавливает вспомогательные объекты для связи MANY_TO_MANY
|
||
*
|
||
* @param array $aData
|
||
* @param string|null $sRelationKey
|
||
*/
|
||
public function _setManyToManyRelations($aData, $sRelationKey = null)
|
||
{
|
||
if ($sRelationKey) {
|
||
$this->_aManyToManyRelations[$sRelationKey] = $aData;
|
||
} else {
|
||
$this->_aManyToManyRelations = $aData;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Возвращает сущность связи при MANY_TO_MANY
|
||
* Актуально только в том случае, если текущая сущность была получена через обращение к связи MANY_TO_MANY
|
||
*
|
||
* @return mixed|null
|
||
*/
|
||
public function _getManyToManyRelationEntity()
|
||
{
|
||
return $this->_getDataOne('_relation_entity');
|
||
}
|
||
|
||
/**
|
||
* Ставим хук на вызов неизвестного метода и считаем что хотели вызвать метод какого либо модуля
|
||
* Также производит обработку методов 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];
|
||
}
|
||
}
|
||
/**
|
||
* Проверяем на связи
|
||
*/
|
||
$aRelations = $this->_getRelations();
|
||
if (array_key_exists($sKey, $aRelations)) {
|
||
$sEntityRel = $aRelations[$sKey]['rel_entity'];
|
||
$sRelationType = $aRelations[$sKey]['type'];
|
||
$sRelationKey = $aRelations[$sKey]['rel_key'];
|
||
|
||
$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());
|
||
$bUseFilter = array_key_exists(0, $aArgs) && is_array($aArgs[0]);
|
||
$sCmd = '';
|
||
$mCmdArgs = array();
|
||
switch ($sRelationType) {
|
||
case self::RELATION_TYPE_BELONGS_TO :
|
||
if (!$this->_getDataOne($sRelationKey)) {
|
||
return null;
|
||
}
|
||
$sKeyTo = $aRelations[$sKey]['rel_key_to'] ?: $sRelPrimaryKey;
|
||
$sCmd = "{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}By" . func_camelize($sKeyTo);
|
||
$mCmdArgs = array($this->_getDataOne($sRelationKey));
|
||
break;
|
||
case self::RELATION_TYPE_HAS_ONE :
|
||
$aFilterAdd = $aRelations[$sKey]['filter'];
|
||
$sCmd = "{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}ByFilter";
|
||
$aFilterAdd = array_merge(array($sRelationKey => $iPrimaryKeyValue), $aFilterAdd);
|
||
if ($bUseFilter) {
|
||
$aFilterAdd = array_merge($aFilterAdd, $aArgs[0]);
|
||
}
|
||
$mCmdArgs = array($aFilterAdd);
|
||
break;
|
||
case self::RELATION_TYPE_HAS_MANY :
|
||
if ($aRelations[$sKey]['key_from']) {
|
||
$sRelationKeyValue = $this->_getDataOne($aRelations[$sKey]['key_from']);
|
||
} else {
|
||
$sRelationKeyValue = $iPrimaryKeyValue;
|
||
}
|
||
$aFilterAdd = $aRelations[$sKey]['filter'];
|
||
$sCmd = "{$sRelPluginPrefix}{$sRelModuleName}_get{$sRelEntityName}ItemsByFilter";
|
||
$aFilterAdd = array_merge(array($sRelationKey => $sRelationKeyValue), $aFilterAdd);
|
||
if ($bUseFilter) {
|
||
$aFilterAdd = array_merge($aFilterAdd, $aArgs[0]);
|
||
}
|
||
$mCmdArgs = array($aFilterAdd);
|
||
break;
|
||
case self::RELATION_TYPE_MANY_TO_MANY :
|
||
$sEntityJoin = $aRelations[$sKey]['join_entity'];
|
||
$sKeyJoin = $aRelations[$sKey]['join_key'];
|
||
$aFilterAdd = $aRelations[$sKey]['filter'];
|
||
$sCmd = "{$sRelPluginPrefix}Module{$sRelModuleName}_get{$sRelEntityName}ItemsByJoinEntity";
|
||
if ($bUseFilter) {
|
||
$aFilterAdd = array_merge($aFilterAdd, $aArgs[0]);
|
||
}
|
||
$mCmdArgs = array($sEntityJoin, $sKeyJoin, $sRelationKey, $iPrimaryKeyValue, $aFilterAdd);
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
/**
|
||
* Если связь уже загруженна, то возвращаем результат
|
||
*/
|
||
if (!$bUseFilter and array_key_exists($sKey, $this->aRelationsData)) {
|
||
return $this->aRelationsData[$sKey];
|
||
}
|
||
// Нужно ли учитывать дополнительный фильтр
|
||
$res = Engine::GetInstance()->_CallModule($sCmd, $mCmdArgs);
|
||
|
||
// Сохраняем данные только в случае "чистой" выборки
|
||
if (!$bUseFilter) {
|
||
$this->aRelationsData[$sKey] = $res;
|
||
}
|
||
// Создаём объекты-обёртки для связей MANY_TO_MANY
|
||
if ($sRelationType == self::RELATION_TYPE_MANY_TO_MANY) {
|
||
$this->_aManyToManyRelations[$sKey] = new ORMRelationManyToMany($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];
|
||
}
|
||
return $this;
|
||
} elseif ($sType == 'reload') {
|
||
if (array_key_exists($sKey, $this->aRelationsData)) {
|
||
unset($this->aRelationsData[$sKey]);
|
||
return $this->__call('get' . func_camelize($sKey), $aArgs);
|
||
}
|
||
}
|
||
} else {
|
||
return parent::__call($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)]['type'] == 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 parent::__get($sName);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Сбрасывает данные необходимой связи
|
||
*
|
||
* @param string $sKey Ключ(поле) связи
|
||
*/
|
||
public function resetRelationsData($sKey)
|
||
{
|
||
if (isset($this->aRelationsData[$sKey])) {
|
||
unset($this->aRelationsData[$sKey]);
|
||
}
|
||
}
|
||
} |