Skip to content

Commit

Permalink
Add support for an actor provider for extensions which use a user ref…
Browse files Browse the repository at this point in the history
…erence
  • Loading branch information
mbabker committed Feb 1, 2025
1 parent b6048c1 commit 8074b17
Show file tree
Hide file tree
Showing 8 changed files with 248 additions and 22 deletions.
37 changes: 26 additions & 11 deletions src/Blameable/BlameableListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use Gedmo\AbstractTrackingListener;
use Gedmo\Blameable\Mapping\Event\BlameableAdapter;
use Gedmo\Exception\InvalidArgumentException;
use Gedmo\Tool\ActorProviderInterface;

/**
* The Blameable listener handles the update of
Expand All @@ -26,6 +27,8 @@
*/
class BlameableListener extends AbstractTrackingListener
{
protected ?ActorProviderInterface $actorProvider = null;

/**
* @var mixed
*/
Expand All @@ -42,34 +45,46 @@ class BlameableListener extends AbstractTrackingListener
*/
public function getFieldValue($meta, $field, $eventAdapter)
{
$actor = $this->actorProvider instanceof ActorProviderInterface ? $this->actorProvider->getActor() : $this->user;

if ($meta->hasAssociation($field)) {
if (null !== $this->user && !is_object($this->user)) {
if (null !== $actor && !is_object($actor)) {
throw new InvalidArgumentException('Blame is reference, user must be an object');
}

return $this->user;
return $actor;
}

// ok so it's not an association, then it is a string, or an object
if (is_object($this->user)) {
if (method_exists($this->user, 'getUserIdentifier')) {
return (string) $this->user->getUserIdentifier();
if (is_object($actor)) {
if (method_exists($actor, 'getUserIdentifier')) {
return (string) $actor->getUserIdentifier();
}
if (method_exists($this->user, 'getUsername')) {
return (string) $this->user->getUsername();
if (method_exists($actor, 'getUsername')) {
return (string) $actor->getUsername();
}
if (method_exists($this->user, '__toString')) {
return $this->user->__toString();
if (method_exists($actor, '__toString')) {
return $actor->__toString();
}

throw new InvalidArgumentException('Field expects string, user must be a string, or object should have method getUserIdentifier, getUsername or __toString');
}

return $this->user;
return $actor;
}

/**
* Set an actor provider for the user value.
*/
public function setActorProvider(ActorProviderInterface $actorProvider): void
{
$this->actorProvider = $actorProvider;
}

/**
* Set a user value to return
* Set a user value to return.
*
* If an actor provider is also provided, it will take precedence over this value.
*
* @param mixed $user
*
Expand Down
46 changes: 46 additions & 0 deletions src/Loggable/LoggableListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Gedmo\Exception\InvalidArgumentException;
use Gedmo\Loggable\Mapping\Event\LoggableAdapter;
use Gedmo\Mapping\MappedEventSubscriber;
use Gedmo\Tool\ActorProviderInterface;
use Gedmo\Tool\Wrapper\AbstractWrapper;

/**
Expand Down Expand Up @@ -55,6 +56,8 @@ class LoggableListener extends MappedEventSubscriber
*/
public const ACTION_REMOVE = LogEntryInterface::ACTION_REMOVE;

protected ?ActorProviderInterface $actorProvider = null;

/**
* Username for identification
*
Expand Down Expand Up @@ -85,9 +88,19 @@ class LoggableListener extends MappedEventSubscriber
*/
protected $pendingRelatedObjects = [];

/**
* Set an actor provider for the user value.
*/
public function setActorProvider(ActorProviderInterface $actorProvider): void
{
$this->actorProvider = $actorProvider;
}

/**
* Set username for identification
*
* If an actor provider is also provided, it will take precedence over this value.
*
* @param mixed $username
*
* @throws InvalidArgumentException Invalid username
Expand Down Expand Up @@ -229,6 +242,39 @@ protected function getLogEntryClass(LoggableAdapter $ea, $class)
return self::$configurations[$this->name][$class]['logEntryClass'] ?? $ea->getDefaultLogEntryClass();
}

/**
* Retrieve the username to use for the log entry.
*
* This method will try to fetch a username from the actor provider first, falling back to the {@see $this->username}
* property if the provider is not set or does not provide a value.
*/
protected function getUsername(): ?string
{
if ($this->actorProvider instanceof ActorProviderInterface) {
$actor = $this->actorProvider->getActor();

if (is_string($actor)) {
return $actor;
}

if (method_exists($actor, 'getUserIdentifier')) {
return (string) $actor->getUserIdentifier();
}

if (method_exists($actor, 'getUsername')) {
return (string) $actor->getUsername();
}

if (method_exists($actor, '__toString')) {
return $actor->__toString();
}

throw new InvalidArgumentException(\sprintf('The loggable extension requires the actor provider to return a string or an object implementing the "getUserIdentifier", "getUsername", or "__toString" methods. "%s" cannot be used as an actor.', get_class($actor)));
}

return $this->username;
}

/**
* Handle any custom LogEntry functionality that needs to be performed
* before persisting it
Expand Down
21 changes: 21 additions & 0 deletions src/Tool/ActorProviderInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/*
* This file is part of the Doctrine Behavioral Extensions package.
* (c) Gediminas Morkevicius <[email protected]> http://www.gediminasm.org
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Gedmo\Tool;

/**
* Interface for a provider for an actor for extensions supporting actor/user references.
*/
interface ActorProviderInterface
{
/**
* @return object|string
*/
public function getActor();
}
54 changes: 50 additions & 4 deletions tests/Gedmo/Blameable/BlameableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
namespace Gedmo\Tests\Blameable;

use Doctrine\Common\EventManager;
use Gedmo\Blameable\Blameable;
use Gedmo\Blameable\BlameableListener;
use Gedmo\Tests\Blameable\Fixture\Entity\Article;
use Gedmo\Tests\Blameable\Fixture\Entity\Comment;
use Gedmo\Tests\Blameable\Fixture\Entity\Type;
use Gedmo\Tests\TestActorProvider;
use Gedmo\Tests\Tool\BaseTestCaseORM;

/**
Expand All @@ -26,15 +26,17 @@
*/
final class BlameableTest extends BaseTestCaseORM
{
private BlameableListener $listener;

protected function setUp(): void
{
parent::setUp();

$listener = new BlameableListener();
$listener->setUserValue('testuser');
$this->listener = new BlameableListener();
$this->listener->setUserValue('testuser');

$evm = new EventManager();
$evm->addEventSubscriber($listener);
$evm->addEventSubscriber($this->listener);

$this->getDefaultMockSqliteEntityManager($evm);
}
Expand Down Expand Up @@ -81,6 +83,50 @@ public function testBlameable(): void
static::assertSame('testuser', $sport->getPublished());
}

public function testBlameableWithActorProvider(): void
{
$this->listener->setActorProvider(new TestActorProvider('testactor'));

$sport = new Article();
$sport->setTitle('Sport');

$sportComment = new Comment();
$sportComment->setMessage('hello');
$sportComment->setArticle($sport);
$sportComment->setStatus(0);

$this->em->persist($sport);
$this->em->persist($sportComment);
$this->em->flush();
$this->em->clear();

$sport = $this->em->getRepository(Article::class)->findOneBy(['title' => 'Sport']);
static::assertSame('testactor', $sport->getCreated());
static::assertSame('testactor', $sport->getUpdated());
static::assertNull($sport->getPublished());

$sportComment = $this->em->getRepository(Comment::class)->findOneBy(['message' => 'hello']);
static::assertSame('testactor', $sportComment->getModified());
static::assertNull($sportComment->getClosed());

$sportComment->setStatus(1);
$published = new Type();
$published->setTitle('Published');

$sport->setTitle('Updated');
$sport->setType($published);
$this->em->persist($sport);
$this->em->persist($published);
$this->em->persist($sportComment);
$this->em->flush();
$this->em->clear();

$sportComment = $this->em->getRepository(Comment::class)->findOneBy(['message' => 'hello']);
static::assertSame('testactor', $sportComment->getClosed());

static::assertSame('testactor', $sport->getPublished());
}

public function testForcedValues(): void
{
$sport = new Article();
Expand Down
6 changes: 3 additions & 3 deletions tests/Gedmo/Loggable/AnnotationLoggableEntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ protected function setUp(): void
parent::setUp();

$evm = new EventManager();
$loggableListener = new LoggableListener();
$loggableListener->setUsername('jules');
$evm->addEventSubscriber($loggableListener);
$this->listener = new LoggableListener();
$this->listener->setUsername('jules');
$evm->addEventSubscriber($this->listener);

$this->em = $this->getDefaultMockSqliteEntityManager($evm);
}
Expand Down
8 changes: 4 additions & 4 deletions tests/Gedmo/Loggable/AttributeLoggableEntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ protected function setUp(): void
parent::setUp();

$evm = new EventManager();
$loggableListener = new LoggableListener();
$loggableListener->setAnnotationReader(new AttributeReader());
$loggableListener->setUsername('jules');
$evm->addEventSubscriber($loggableListener);
$this->listener = new LoggableListener();
$this->listener->setAnnotationReader(new AttributeReader());
$this->listener->setUsername('jules');
$evm->addEventSubscriber($this->listener);

$this->em = $this->getDefaultMockSqliteEntityManager($evm);
}
Expand Down
56 changes: 56 additions & 0 deletions tests/Gedmo/Loggable/LoggableEntityTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use Doctrine\DBAL\Types\ArrayType;
use Gedmo\Loggable\Entity\LogEntry;
use Gedmo\Loggable\Entity\Repository\LogEntryRepository;
use Gedmo\Loggable\Loggable;
use Gedmo\Loggable\LoggableListener;
use Gedmo\Tests\Loggable\Fixture\Entity\Address;
use Gedmo\Tests\Loggable\Fixture\Entity\Article;
use Gedmo\Tests\Loggable\Fixture\Entity\Comment;
Expand All @@ -23,6 +25,7 @@
use Gedmo\Tests\Loggable\Fixture\Entity\GeoLocation;
use Gedmo\Tests\Loggable\Fixture\Entity\Log\Comment as CommentLog;
use Gedmo\Tests\Loggable\Fixture\Entity\RelatedArticle;
use Gedmo\Tests\TestActorProvider;
use Gedmo\Tests\Tool\BaseTestCaseORM;

/**
Expand All @@ -32,6 +35,11 @@
*/
abstract class LoggableEntityTest extends BaseTestCaseORM
{
/**
* @var LoggableListener<Loggable|object>
*/
protected LoggableListener $listener;

public static function setUpBeforeClass(): void
{
if (!class_exists(ArrayType::class)) {
Expand Down Expand Up @@ -106,6 +114,54 @@ public function testLoggable(): void
static::assertNull($log->getData());
}

public function testLoggableWithActorProvider(): void
{
$this->listener->setActorProvider(new TestActorProvider('testactor'));

$logRepo = $this->em->getRepository(LogEntry::class);
$articleRepo = $this->em->getRepository(Article::class);
static::assertCount(0, $logRepo->findAll());

$art0 = new Article();
$art0->setTitle('Title');

$this->em->persist($art0);
$this->em->flush();

$log = $logRepo->findOneBy(['objectId' => $art0->getId()]);

static::assertNotNull($log);
static::assertSame('create', $log->getAction());
static::assertSame(get_class($art0), $log->getObjectClass());
static::assertSame('testactor', $log->getUsername());
static::assertSame(1, $log->getVersion());
$data = $log->getData();
static::assertCount(1, $data);
static::assertArrayHasKey('title', $data);
static::assertSame('Title', $data['title']);

// test update
$article = $articleRepo->findOneBy(['title' => 'Title']);

$article->setTitle('New');
$this->em->persist($article);
$this->em->flush();
$this->em->clear();

$log = $logRepo->findOneBy(['version' => 2, 'objectId' => $article->getId()]);
static::assertSame('update', $log->getAction());

// test delete
$article = $articleRepo->findOneBy(['title' => 'New']);
$this->em->remove($article);
$this->em->flush();
$this->em->clear();

$log = $logRepo->findOneBy(['version' => 3, 'objectId' => 1]);
static::assertSame('remove', $log->getAction());
static::assertNull($log->getData());
}

public function testVersionControl(): void
{
$this->populate();
Expand Down
Loading

0 comments on commit 8074b17

Please sign in to comment.