Skip to content

Commit

Permalink
Introduce onSetPolymorphicRelationOwner() and `onSetPolymorphicRela…
Browse files Browse the repository at this point in the history
…tionObject()`
  • Loading branch information
martin-rueegg committed Sep 21, 2023
1 parent 0c35722 commit 1efad91
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 19 deletions.
59 changes: 43 additions & 16 deletions protected/humhub/components/behaviors/PolymorphicRelation.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?php

/**
* @link https://www.humhub.org/
* @copyright Copyright (c) 2017 HumHub GmbH & Co. KG
Expand All @@ -8,15 +9,14 @@
namespace humhub\components\behaviors;

use Exception;
use humhub\components\ActiveRecord;
use humhub\modules\content\components\ContentActiveRecord;
use humhub\modules\content\components\ContentAddonActiveRecord;
use ReflectionClass;
use ReflectionException;
use Yii;
use yii\base\Behavior;
use yii\base\Model;
use yii\db\ActiveRecord;
use yii\db\BaseActiveRecord;
use yii\db\ActiveRecordInterface;
use yii\db\IntegrityException;

/**
Expand All @@ -26,16 +26,20 @@
*/
class PolymorphicRelation extends Behavior
{

/**
* @var string the class name attribute
*/
public $classAttribute = 'object_model';
public string $classAttribute = 'object_model';

/**
* @var string the primary key attribute
*/
public $pkAttribute = 'object_id';
public string $pkAttribute = 'object_id';

/**
* @var callable|null
*/
public $onSet;

/**
* @var boolean if set to true an exception is thrown if `object_model` and `object_id` is set but does not exist
Expand All @@ -55,10 +59,10 @@ class PolymorphicRelation extends Behavior
/**
* Returns the Underlying Object
*
* @return mixed
* @return ActiveRecordInterface|ActiveRecord|null
* @throws IntegrityException
*/
public function getPolymorphicRelation()
public function getPolymorphicRelation(): ?ActiveRecordInterface
{
if ($this->cached !== null) {
return $this->cached;
Expand All @@ -70,7 +74,11 @@ public function getPolymorphicRelation()
);

if ($this->strict && !$object && !empty($this->classAttribute) && !empty($this->pkAttribute)) {
throw new IntegrityException('Call to an inconsistent polymorphic relation detected on ' . get_class($this->owner) . ' (' . $this->owner->getAttribute($this->classAttribute) . ':' . $this->owner->getAttribute($this->pkAttribute) . ')');
throw new IntegrityException(
'Call to an inconsistent polymorphic relation detected on '
. ($this->owner === null ? 'NULL' : get_class($this->owner))
. ' (' . $this->owner->getAttribute($this->classAttribute) . ':' . $this->owner->getAttribute($this->pkAttribute) . ')'
);
}

if ($object !== null && $this->validateUnderlyingObjectType($object)) {
Expand All @@ -91,28 +99,46 @@ public function setPolymorphicRelation($object)
{
if ($this->validateUnderlyingObjectType($object)) {
$this->cached = $object;
if ($object instanceof ActiveRecord) {
if ($object instanceof ActiveRecordInterface) {
$this->owner->setAttribute($this->classAttribute, self::getObjectModel($object));
$this->owner->setAttribute($this->pkAttribute, $object->getPrimaryKey());
}

if ($object->hasMethod('onSetPolymorphicRelationOwner')) {
// signature: public function onSetPolymorphicRelationOwner(?Component $owner, Behavior $behavior)
$object->onSetPolymorphicRelationOwner($this->owner, $this);
}

if ($this->owner->hasMethod('onSetPolymorphicRelationObject')) {
// signature: public function onSetPolymorphicRelationObject($object, Behavior $behavior)
$this->owner->onSetPolymorphicRelationObject($object, $this);
}
}
}

public static function getObjectModel(Model $object): string
public static function getObjectModel(ActiveRecordInterface $object): string
{
return $object instanceof ContentActiveRecord || $object instanceof ContentAddonActiveRecord
? $object::getObjectModel()
: get_class($object);
}

/**
* Resets the already loaded $_cached instance of related object
* Resets the already loaded $_cached instance of a related object
*/
public function resetPolymorphicRelation()
{
$this->cached = null;
}

/**
* Returns if the polymorphic relation is established
*/
public function isPolymorphicRelation(): bool
{
return $this->cached !== null;
}

/**
* Validates if given object is of allowed type
*
Expand Down Expand Up @@ -142,9 +168,9 @@ private function validateUnderlyingObjectType($object)
*
* @param $className
* @param $primaryKey
* @return null|ActiveRecord
* @return null|ActiveRecord|ActiveRecordInterface
*/
public static function loadActiveRecord($className, $primaryKey)
public static function loadActiveRecord($className, $primaryKey): ?ActiveRecordInterface
{
if (empty($className) || empty($primaryKey)) {
return null;
Expand All @@ -157,12 +183,13 @@ public static function loadActiveRecord($className, $primaryKey)
return null;
}

if (!$class->isSubclassOf(BaseActiveRecord::class)) {
Yii::error('Could not load polymorphic relation! Class (Class is no ActiveRecord: ' . $className . ')');
if (!$class->implementsInterface(ActiveRecordInterface::class)) {
Yii::error('Could not load polymorphic relation! Class (Class does not implement ActiveRecordInterface: ' . $className . ')');
return null;
}

try {
/** @var ActiveRecordInterface $className */
$primaryKeyNames = $className::primaryKey();
if (count($primaryKeyNames) !== 1) {
Yii::error('Could not load polymorphic relation! Only one primary key is supported!');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
use humhub\libs\BasePermission;
use humhub\libs\ProfileBannerImage;
use humhub\libs\ProfileImage;
use humhub\libs\UUID;
use humhub\modules\content\models\Content;
use humhub\modules\content\models\ContentContainer;
use humhub\modules\content\models\ContentContainerBlockedUsers;
use humhub\modules\content\models\ContentContainerTagRelation;
use humhub\modules\user\models\User;
use humhub\modules\user\Module as UserModule;
use Yii;
use yii\base\Component;
use yii\db\ActiveQuery;
use yii\helpers\Url;
use yii\web\IdentityInterface;
Expand Down Expand Up @@ -222,9 +224,8 @@ public function afterSave($insert, $changedAttributes)
{
if ($insert) {
$contentContainer = new ContentContainer();
$contentContainer->guid = $this->guid;
$contentContainer->class = static::class;
$contentContainer->pk = $this->getPrimaryKey();
$contentContainer->setPolymorphicRelation($this);

if ($this instanceof User) {
$contentContainer->owner_user_id = $this->id;
} elseif ($this->hasAttribute('created_by')) {
Expand All @@ -248,6 +249,37 @@ public function afterSave($insert, $changedAttributes)
parent::afterSave($insert, $changedAttributes);
}

public function onSetPolymorphicRelationOwner(?Component $owner)
{
if (!$owner instanceof ContentContainer) {
return;
}

$thisGUID = UUID::validate($this->guid);
$theirGUID = UUID::validate($owner->guid);

switch (true) {
case $thisGUID === null && $theirGUID === null:
$this->guid = $owner->guid = UUID::v4();
break;

case $thisGUID === null:
$this->guid = $thisGUID;
break;

case $theirGUID === null:
$owner->guid = $thisGUID;
break;

case $thisGUID !== $theirGUID:
Yii::warning(sprintf(
"ContentContainer with GUID %s does not match the GUID of the related ContentContainerActiveRecord: %s",
$thisGUID,
$theirGUID
));
}
}

/**
* @inheritdoc
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ public function testGuid()

$this->assertTrue($contentContainer->save());
$this->assertEmpty($contentContainer->getErrors('guid'));

$user = new User();
$user->id = 10;
$contentContainer = new ContentContainer();
$contentContainer->setPolymorphicRelation($user);

$this->assertTrue($contentContainer->save());
$this->assertEmpty($contentContainer->getErrors('guid'));
}

public function testModelRequired()
Expand Down

0 comments on commit 1efad91

Please sign in to comment.