Skip to content

Commit

Permalink
[TASK] Tune RenderingContext withAttribute() and getAttribute() (#882)
Browse files Browse the repository at this point in the history
getAttribute($className) now always returns an instance of
$className, or throws. This avoids checking the returned
instance and helps with type hinting in IDEs.

withAttribute() now requires a value is an instance of
given class name.

Checking if an attribute has been set for given class name
can be done using hasAttribute() to avoid a try-catch for
a generic \RuntimeException.

This change is not considered breaking since it is an adjustment
of a new API added just recently.
  • Loading branch information
lolli42 authored Jun 26, 2024
1 parent e3d21cc commit f8556bc
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 43 deletions.
46 changes: 21 additions & 25 deletions src/Core/Rendering/RenderingContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,11 @@ class RenderingContext implements RenderingContextInterface

/**
* Attributes can be used to attach additional data to the
* rendering context to be used e. g. in ViewHelpers.
* rendering context to be used e.g. in ViewHelpers.
*
* @var object[]
*/
protected array $attributes = [];
private array $attributes = [];

/**
* Constructor
Expand Down Expand Up @@ -363,33 +365,27 @@ public function buildParserConfiguration()
return $parserConfiguration;
}

/**
* Retrieve a single attribute.
*
* @see withAttribute()
* @param string $name The attribute name.
* @return object|null null if the specified attribute hasn't been set
*/
public function getAttribute(string $name): ?object
public function withAttribute(string $className, object $value): static
{
return $this->attributes[$name] ?? null;
if (!$value instanceof $className) {
throw new \RuntimeException('$value is not an instance of ' . $className, 1719410580);
}
$clonedObject = clone $this;
$clonedObject->attributes[$className] = $value;
return $clonedObject;
}

/**
* Return an instance with the specified attribute.
*
* This method allows you to attach arbitrary objects to the
* rendering context to be used later e. g. in ViewHelpers.
*
* @param string $name The attribute name.
* @param object $value The value of the attribute.
* @return static
*/
public function withAttribute(string $name, object $value): static
public function hasAttribute(string $className): bool
{
$clonedObject = clone $this;
$clonedObject->attributes[$name] = $value;
return $clonedObject;
return isset($this->attributes[$className]);
}

public function getAttribute(string $className): object
{
if (!isset($this->attributes[$className])) {
throw new \RuntimeException('An attribute of type ' . $className . ' has not been set', 1719394231);
}
return $this->attributes[$className];
}

/**
Expand Down
32 changes: 20 additions & 12 deletions src/Core/Rendering/RenderingContextInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -181,23 +181,31 @@ public function getControllerAction();
public function setControllerAction($action);

/**
* Retrieve a single attribute.
* Return an instance with the specified attribute being attached.
*
* @see withAttribute()
* @param string $name The attribute name.
* @return object|null null if the specified attribute hasn't been set
* This method allows you to attach arbitrary objects to the
* rendering context to be used later e. g. in ViewHelpers.
*
* @template T of object
* @param class-string<T> $className
* @param T $value
*/
public function getAttribute(string $name): ?object;
public function withAttribute(string $className, object $value): RenderingContextInterface;

/**
* Return an instance with the specified attribute.
* Return true if an attribute object of that type exists.
*
* This method allows you to attach arbitrary objects to the
* rendering context to be used later e. g. in ViewHelpers.
* @template T of object
* @param class-string<T> $className
*/
public function hasAttribute(string $className): bool;

/**
* Retrieve a single attribute instance.
*
* @param string $name The attribute name.
* @param object $value The value of the attribute.
* @return static
* @template T of object
* @param class-string<T> $className
* @return T
*/
public function withAttribute(string $name, object $value): RenderingContextInterface;
public function getAttribute(string $className): object;
}
38 changes: 32 additions & 6 deletions tests/Unit/Core/Rendering/RenderingContextTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,40 @@ public function isCacheEnabledReturnsTrueIfCacheIsEnabled(): void
}

#[Test]
public function withAndGetAttribute(): void
public function withAttributeThrowsIfValueIsNotInstanceofClassName(): void
{
$object = new stdClass();
$object->test = 'value';
$this->expectException(\RuntimeException::class);
$this->expectExceptionCode(1719410580);
(new RenderingContext())->withAttribute(RenderingContext::class, new stdClass());
}

#[Test]
public function hasAttributeReturnsFalseIfNotSet(): void
{
self::assertFalse((new RenderingContext())->hasAttribute(stdClass::class));
}

#[Test]
public function hasAttributeReturnsTrueIfSet(): void
{
$subject = (new RenderingContext())->withAttribute(stdClass::class, new stdClass());
self::assertTrue($subject->hasAttribute(stdClass::class));
}

#[Test]
public function getAttributeThrowsWithNoSuchAttribute(): void
{
$this->expectException(\RuntimeException::class);
$this->expectExceptionCode(1719394231);
(new RenderingContext())->getAttribute(stdClass::class);
}

#[Test]
public function getAttributeReturnsInstanceSetUsingWithAttribute(): void
{
$object = new stdClass();
$subject = new RenderingContext();
$clonedSubject = $subject->withAttribute('test', $object);
self::assertNull($subject->getAttribute('test'));
self::assertEquals($object, $clonedSubject->getAttribute('test'));
$clonedSubject = $subject->withAttribute(stdClass::class, $object);
self::assertEquals($object, $clonedSubject->getAttribute(stdClass::class));
}
}

0 comments on commit f8556bc

Please sign in to comment.