1
0
Fork 0
mirror of https://github.com/Oreolek/ifhub.club.git synced 2024-06-29 04:55:02 +03:00

Доработка MANY_TO_MANY связей

This commit is contained in:
kirsan 2011-05-03 11:55:17 +00:00
parent a29dc1735e
commit 4e2dde7dc5
6 changed files with 268 additions and 12 deletions

View file

@ -32,6 +32,8 @@ require_once("ModuleORM.class.php");
require_once("EntityORM.class.php");
require_once("MapperORM.class.php");
require_once("ManyToManyRelation.class.php");
/**
* Основной класс движка, который позволяет напрямую обращаться к любому модулю

View file

@ -21,6 +21,7 @@
*/
abstract class Entity extends Object {
protected $_aData=array();
protected $sPrimaryKey = null;
/**
* Если передать в конструктор ассоциативный массив свойств и их значений, то они автоматом загрузятся в сущность
@ -88,5 +89,26 @@ abstract class Entity extends Object {
return Engine::getInstance()->_CallModule($sName,$aArgs);
}
}
/**
* Получение первичного ключа сущности (ключ, а не значение!)
*/
public function _getPrimaryKey()
{
if (!$this->sPrimaryKey) {
if (isset($this->_aData['id'])) {
$this->sPrimaryKey = 'id';
} else {
// Получение primary_key из схемы бд (пока отсутствует)
$this->sPrimaryKey = 'id';
}
}
return $this->sPrimaryKey;
}
public function _getPrimaryKeyValue() {
return $this->_getDataOne($this->_getPrimaryKey());
}
}
?>

View file

@ -33,6 +33,9 @@ abstract class EntityORM extends Entity {
protected $aRelations=array();
protected $aRelationsData=array();
// Объекты связей many_to_many
protected $_aManyToManyRelations = array();
protected $sPrimaryKey='id';
protected $bIsNew=true;
@ -55,6 +58,10 @@ abstract class EntityORM extends Entity {
}
return $this->sPrimaryKey;
}
public function _getPrimaryKeyValue() {
return $this->_getDataOne($this->_getPrimaryKey());
}
public function _isNew() {
return $this->bIsNew;
@ -283,7 +290,7 @@ abstract class EntityORM extends Entity {
}
public function __call($sName,$aArgs) {
$sType=substr($sName,0,strpos(func_underscore($sName),'_'));
$sType=substr($sName,0,strpos(func_underscore($sName),'_'));
if (!strpos($sName,'_') and in_array($sType,array('get','set','reload'))) {
$sKey=func_underscore(str_replace($sType,'',$sName));
if ($sType=='get') {
@ -326,7 +333,6 @@ abstract class EntityORM extends Entity {
}
$iPrimaryKeyValue=$this->_getDataOne($this->_getPrimaryKey());
$sCmd='';
$aCmdArgs=array();
switch ($sRelationType) {
@ -345,10 +351,11 @@ abstract class EntityORM extends Entity {
case self::RELATION_TYPE_MANY_TO_MANY :
$sCmd="{$sRelPluginPrefix}Module{$sRelModuleName}_get{$sRelEntityName}ItemsByJoinTable";
$aCmdArgs[0]=array(
'#join_table' => $sRelationJoinTable,
'#join_table' => Config::Get($sRelationJoinTable),
'#relation_key' => $sRelationKey,
'#by_key' => $sRelationJoinTableKey,
'#by_value' => $iPrimaryKeyValue,
'#index-from-primary' => true // Для MANY_TO_MANY необходимо индексами в $aRelationsData иметь первичные ключи сущностей
);
break;
default:
@ -360,6 +367,10 @@ abstract class EntityORM extends Entity {
$res=Engine::GetInstance()->_CallModule($sCmd,$aCmdArgs);
$this->aRelationsData[$sKey]=$res;
// Создаём объекты-обёртки для связей MANY_TO_MANY
if ($sRelationType == self::RELATION_TYPE_MANY_TO_MANY) {
$this->_aManyToManyRelations[$sKey] = new LS_ManyToManyRelation($this->aRelationsData[$sKey]);
}
return $res;
}
@ -380,5 +391,25 @@ abstract class EntityORM extends Entity {
return Engine::getInstance()->_CallModule($sName,$aArgs);
}
}
public function __get($sName)
{
// Обработка обращений к обёрткам связей MANY_TO_MANY
// Если связь загружена, возвращаем объект связи
var_dump(($this->aRelations[strtolower($sName)]));
if (isset($this->_aManyToManyRelations[strtolower($sName)])) {
return $this->_aManyToManyRelations[strtolower($sName)];
// Есл не загружена, но связь с таким именем существет, пробуем загрузить и вернуть объект связи
} elseif (isset($this->aRelations[strtolower($sName)]) && $this->aRelations[strtolower($sName)][0] == self::RELATION_TYPE_MANY_TO_MANY) {
$sMethod = 'get' . ucfirst($sName);
$this->__call($sMethod, array());
if (isset($this->_aManyToManyRelations[strtolower($sName)])) {
return $this->_aManyToManyRelations[strtolower($sName)];
}
// В противном случае возвращаем то, что просили у объекта
} else {
return $this->$sName;
}
}
}
?>

View file

@ -0,0 +1,46 @@
<?php
/**
* Класс. представляющий собой обёертку для связей MANY_TO_MANY.
* Позволяет оперровать коллекцией загруженных по связи эдементов через имя связи
* Например, $oTopic->Tags->add($oTag) или $oTopic->Tags->delete($oTag->getId()) при
* наличии настроенной MANY_TO_MANY связи 'tags'
*/
class LS_ManyToManyRelation
{
// Ссылка на $oEntityORM->aRelationsData[<relation_name>],
// где relation_name - имя сязи, которую представляет объект
protected $_aCollection = array();
public function __construct(&$aCollection)
{
$this->_aCollection = &$aCollection;
}
/**
* Добавление объекта в коллекцию
* @param <type> $oEntity
*/
public function add($oEntity)
{
$this->_aCollection[$oEntity->_getPrimaryKeyValue()] = $oEntity;
}
/**
* Удаление объекта из коллекции по его id или массиву id
* @param <type> $iId
*/
public function delete($iId)
{
if (is_array($iId)) {
foreach ($iId as $id) {
if (isset($this->_aCollection[$id])) {
unset($this->_aCollection[$id]);
}
}
} elseif (isset($this->_aCollection[$iId])) {
unset($this->_aCollection[$iId]);
}
}
}
?>

View file

@ -261,5 +261,68 @@ class MapperORM extends Mapper {
return Config::Get('db.table.prefix').$sTable;
}
}
/**
* Загрузка данных из таблицы связи many_to_many
* @param <type> $sDbTableAlias Алиас имени таблицы связи
* @param <type> $sEntityKey Название поля в таблице связи с id сущности, для которой зегружаются объекты.
* @param <type> $iEntityId Id сущнсоти, для который загружаются объекты
* @param <type> $sRelationKey Название поля в таблице связи с id сущности, объекты которой загружаются по связи.
* @return <type> список id из столбца $sRelationKey, у которых столбец $sEntityKey = $iEntityId
*/
public function getManyToManySet($sDbTableAlias, $sEntityKey, $iEntityId, $sRelationKey)
{
if (!Config::Get($sDbTableAlias)) return array();
$sql = 'SELECT ?# FROM '.Config::Get($sDbTableAlias).' WHERE ?# = ?d';
return $this->oDb->selectCol($sql, $sRelationKey, $sEntityKey, $iEntityId);
}
/**
* Обновление связи many_to_many
* @param <type> $sDbTableAlias Алиас имени таблицы связи
* @param <type> $sEntityKey Название поля в таблице связи с id сущности, для которой обновляются связи.
* @param <type> $iEntityId Id сущнсоти, для который обновляются связи
* @param <type> $sRelationKey Название поля в таблице связи с id сущности, с объектами которой назначаются связи.
* @param <type> $aInsertSet Массив id для $sRelationKey, которые нужно добавить
* @param <type> $aDeleteSet Массив id для $sRelationKey, которые нужно удалить
* @return <type>
*/
public function updateManyToManySet($sDbTableAlias, $sEntityKey, $iEntityId, $sRelationKey, $aInsertSet, $aDeleteSet)
{
if (!Config::Get($sDbTableAlias)) return false;
if (count($aDeleteSet)) {
$sql = 'DELETE FROM '.Config::Get($sDbTableAlias).' WHERE ?# = ?d AND ?# IN (?a)';
$this->oDb->query($sql, $sEntityKey, $iEntityId, $sRelationKey, $aDeleteSet);
}
if (count($aInsertSet)) {
$sql = 'INSERT INTO '.Config::Get($sDbTableAlias).' (?#,?#) VALUES ';
$aParams = array();
foreach ($aInsertSet as $iId) {
$sql .= '(?d, ?d), ';
$aParams[] = $iEntityId;
$aParams[] = $iId;
}
$sql = substr($sql, 0, -2); // удаление последних ", "
call_user_func_array(array($this->oDb, 'query'), array_merge(array($sql,$sEntityKey, $sRelationKey), $aParams));
}
}
/**
* Удаление связей many_to_many для объекта. Используется при удалении сущности,
* чтобы не удалять большие коллекции связанных объектов через updateManyToManySet(),
* где используется IN.
*
* @param <type> $sDbTableAlias Алиас имени таблицы связи
* @param <type> $sEntityKey Название поля в таблице связи с id сущности, для которой удаляются связи.
* @param <type> $iEntityId Id сущнсоти, для который удаляются связи
* @return <type>
*/
public function deleteManyToManySet($sDbTableAlias, $sEntityKey, $iEntityId)
{
if (!Config::Get($sDbTableAlias)) return false;
$sql = 'DELETE FROM '.Config::Get($sDbTableAlias).' WHERE ?# = ?d';
$this->oDb->query($sql, $sEntityKey, $iEntityId);
}
}
?>

View file

@ -45,6 +45,13 @@ abstract class ModuleORM extends Module {
} elseif ($res) {
// есть автоинкремент, устанавливаем его
$oEntity->_setData(array($oEntity->_getPrimaryKey() => $res));
// Обновление связей many_to_many
$aRelationsData = $oEntity->_getRelationsData();
foreach ($oEntity->_getRelations() as $sRelName => $aRelation) {
if ($aRelation[0] == EntityORM::RELATION_TYPE_MANY_TO_MANY) {
$this->_updateManyToManySet($aRelation, $aRelationsData[$sRelName], $oEntity->_getDataOne($oEntity->_getPrimaryKey()));
}
}
return $oEntity;
}
return false;
@ -52,7 +59,15 @@ abstract class ModuleORM extends Module {
protected function _UpdateEntity($oEntity) {
$res=$this->oMapperORM->UpdateEntity($oEntity);
if ($res===0 or $res) { // запись не изменилась, либо изменилась
// Обновление связей many_to_many
$aRelationsData = $oEntity->_getRelationsData();
foreach ($oEntity->_getRelations() as $sRelName => $aRelation) {
if ($aRelation[0] == EntityORM::RELATION_TYPE_MANY_TO_MANY) {
$this->_updateManyToManySet($aRelation, $aRelationsData[$sRelName], $oEntity->_getDataOne($oEntity->_getPrimaryKey()));
}
}
// сбрасываем кеш
$sEntity=$this->Plugin_GetRootDelegater('entity',get_class($oEntity));
$this->Cache_Clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,array($sEntity.'_save'));
@ -75,6 +90,15 @@ abstract class ModuleORM extends Module {
// сбрасываем кеш
$sEntity=$this->Plugin_GetRootDelegater('entity',get_class($oEntity));
$this->Cache_Clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG,array($sEntity.'_delete'));
// Обновление связей many_to_many
$aRelationsData = $oEntity->getRelationsData();
foreach ($oEntity->_getRelations() as $sRelName => $aRelation) {
if ($aRelation[0] == EntityORM::RELATION_TYPE_MANY_TO_MANY) {
$this->_deleteManyToManySet($aRelation[3], $aRelation[4], $oEntity->_getPrimaryKeyValue());
}
}
return $oEntity;
}
return false;
@ -316,18 +340,29 @@ abstract class ModuleORM extends Module {
* Returns assotiative array, indexed by PRIMARY KEY or another field.
*/
if (in_array('#index-from-primary', $aFilter) || !empty($aFilter['#index-from'])) {
$aIndexedEntities=array();
foreach ($aEntities as $oEntity) {
$sKey = in_array('#index-from-primary', $aFilter) || ( !empty($aFilter['#index-from']) && $aFilter['#index-from'] == '#primary' ) ?
$oEntity->_getPrimaryKey() :
$oEntity->_getField($aFilter['#index-from']);
$aIndexedEntities[$oEntity->_getDataOne($sKey)]=$oEntity;
}
$aEntities = $aIndexedEntities;
$aEntities = $this->_setIndexesFromField($aEntities, $aFilter);
}
return $aEntities;
}
/**
* Returns assotiative array, indexed by PRIMARY KEY or another field.
* @param <type> $oEntity
* @param <type> $aFilter
* @return <type>
*/
protected function _setIndexesFromField($aEntities, $aFilter)
{
$aIndexedEntities=array();
foreach ($aEntities as $oEntity) {
$sKey = in_array('#index-from-primary', $aFilter) || ( !empty($aFilter['#index-from']) && $aFilter['#index-from'] == '#primary' ) ?
$oEntity->_getPrimaryKey() :
$oEntity->_getField($aFilter['#index-from']);
$aIndexedEntities[$oEntity->_getDataOne($sKey)]=$oEntity;
}
return $aIndexedEntities;
}
public function GetCountItemsByFilter($aFilter=array(),$sEntityFull=null) {
if (is_null($sEntityFull)) {
@ -359,7 +394,13 @@ abstract class ModuleORM extends Module {
} elseif (!substr_count($sEntityFull,'_')) {
$sEntityFull=Engine::GetPluginPrefix($this).'Module'.Engine::GetModuleName($this).'_Entity'.$sEntityFull;
}
return $this->oMapperORM->GetItemsByJoinTable($aJoinData,$sEntityFull);
$aEntities = $this->oMapperORM->GetItemsByJoinTable($aJoinData,$sEntityFull);
if (in_array('#index-from-primary', $aJoinData) || !empty($aJoinData['#index-from'])) {
$aEntities = $this->_setIndexesFromField($aEntities, $aJoinData);
}
return $aEntities;
}
public function __call($sName,$aArgs) {
@ -513,5 +554,56 @@ abstract class ModuleORM extends Module {
}
return $aList;
}
/**
* Обновление связи many_to_many в бд
* @param <type> $aRelation Соответствующий связи элемент массива из $oEntityORM->aRelations
* @param <type> $aRelationData Соответствующий связи элемент массива из $oEntityORM->aRelationsData
* @param <type> $iEntityId Id сущности, для которой обновляются связи
* @return <type>
*/
protected function _updateManyToManySet($aRelation, $aRelationData, $iEntityId)
{
/*
* Описание параметров связи many_to_many
* Для примера возьмём такую связь в сущности $oTopic
* 'tags' => array(self::RELATION_TYPE_MANY_TO_MANY,'ModuleTopic_EntityTag', 'tag_id', 'db.table.topic_tag_rel', 'topic_id'),
* И используется таблица связи
* table prefix_topic_tag_rel
* topic_id | ефп_id
* Тогда тут
* [0] -> self::RELATION_TYPE_MANY_TO_MANY - тип связи
* [1] -> 'ModuleTopic_EntityTag' - имя сущности объектов связи
* [2] -> 'tag_id' - названия столбца в таблице связи, в котором содержатся id объектов связи, в нашем случае тегов.
* [3] -> 'db.table.topic_tag_rel' - алиас (идентификатор из конфига) таблицы связи.
* Обратите внмание на то, что ORM для определения таблиц сущностей использует модуль и название сущности, то есть
* если мы захотим таблицу связи назвать prefix_topic_tag, что, в общем-то, логично, то будет конфликт имён, потому что
* ModuleTopic_EntityTag также преобразуется в prefix_topic_tag.
* Поэтому необходимо следить за корректным именованием таблиц (точнее алиасов в конфиге, сами таблицы в бд могут
* называться как угодно). В данном примере используется суффикс '_rel'.
* [4] -> 'topic_id' - название столбца в таблице связи, в котором содержатся id сущности, для которой объявляется связь,
* в нашем случае топиков
*/
$aSavedSet = $this->oMapperORM->getManyToManySet($aRelation[3], $aRelation[4], $iEntityId, $aRelation[2]);
$aCurrentSet = array();
foreach ($aRelationData as $oEntity) {
$aCurrentSet[] = $oEntity->_getDataOne($oEntity->_getPrimaryKey());
}
if ($aSavedSet == $aCurrentSet) return;
$aInsertSet = array_diff($aCurrentSet, $aSavedSet);
$aDeleteSet = array_diff($aSavedSet, $aCurrentSet);
$this->oMapperORM->updateManyToManySet($aRelation[3], $aRelation[4], $iEntityId, $aRelation[2], $aInsertSet, $aDeleteSet);
}
/**
* Удаление связи many_to_many в бд
* @param <type> $sDbTableAlias Алиас имени таблицы связи
* @param <type> $sEntityKey Название поля в таблице связи с id сущности, для которой удаляются связи.
* @param <type> $iEntityId Id сущнсоти, для который удаляются связи
*/
protected function _deleteManyToManySet($sDbTableAlias, $sEntityKey, $iEntityId)
{
$this->oMapperORM->deleteManyToManySet($sDbTableAlias, $sEntityKey, $iEntityId);
}
}
?>