Skip to content

Commit

Permalink
Refactored exceptions
Browse files Browse the repository at this point in the history
- Replaced messages in production classes with static constructors
- Added TrackedRecordNotFoundException to avoid rethrowing default
  RecordNotFoundException after call stack was added
- Added trait for exceptions building call stack path
- Renamed InvalidArgumentException to InvalidTypeException and
  changed parent for InvalidIdException to native invalid argument
  exception
- Removed redundant exception handling for decorated record composition
  thrown from renaming procedure
  • Loading branch information
shudd3r committed Nov 13, 2019
2 parents 2419bc3 + 13d0201 commit 175cf18
Show file tree
Hide file tree
Showing 15 changed files with 133 additions and 69 deletions.
2 changes: 1 addition & 1 deletion src/CompositeContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private function fromContainers($id)
{
[$containerId, $itemId] = $this->splitId($id);
if (!isset($this->containers[$containerId])) {
throw new Exception\RecordNotFoundException(sprintf('Record `%s` not defined', $id));
throw Exception\RecordNotFoundException::undefined($id);
}

return $itemId ? $this->containers[$containerId]->get($itemId) : $this->containers[$containerId];
Expand Down
2 changes: 1 addition & 1 deletion src/ConfigContainer.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function get($id)
$keys = explode(self::SEPARATOR, $id);
foreach ($keys as $key) {
if (!is_array($data) || !array_key_exists($key, $data)) {
throw new Exception\RecordNotFoundException(sprintf('Record `%s` not defined', $id));
throw Exception\RecordNotFoundException::undefined($id);
}
$data = &$data[$key];
}
Expand Down
22 changes: 22 additions & 0 deletions src/Exception/CallStackMessageMethod.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

/*
* This file is part of Polymorphine/Container package.
*
* (c) Shudd3r <q3.shudder@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Polymorphine\Container\Exception;


trait CallStackMessageMethod
{
private static function extendMessage(string $message, array $callStack, string $lastCall = '...'): string
{
$stack = implode('->', array_keys($callStack)) . ($lastCall ? '->' . $lastCall : '');
return "$message [call stack: $stack]";
}
}
7 changes: 7 additions & 0 deletions src/Exception/CircularReferenceException.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,11 @@

class CircularReferenceException extends RuntimeException implements ContainerExceptionInterface
{
use CallStackMessageMethod;

public function __construct(string $id = '', array $callStack = [])
{
$message = "Lazy composition of `$id` record is using reference to itself";
parent::__construct(self::extendMessage($message, $callStack, $id));
}
}
19 changes: 0 additions & 19 deletions src/Exception/InvalidArgumentException.php

This file was deleted.

19 changes: 18 additions & 1 deletion src/Exception/InvalidIdException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,24 @@

namespace Polymorphine\Container\Exception;

use Psr\Container\ContainerExceptionInterface;
use InvalidArgumentException;

class InvalidIdException extends InvalidArgumentException

class InvalidIdException extends InvalidArgumentException implements ContainerExceptionInterface
{
public static function alreadyDefined(string $resource): self
{
return new self("Cannot overwrite defined $resource");
}

public static function prefixConflict(string $prefix): self
{
return new self("Record id prefix `$prefix` already used by stored container");
}

public static function unexpectedPrefixSeparator(string $separator, string $id): self
{
return new self("Container id cannot contain `$separator` separator - `$id` id given");
}
}
29 changes: 29 additions & 0 deletions src/Exception/InvalidTypeException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

/*
* This file is part of Polymorphine/Container package.
*
* (c) Shudd3r <q3.shudder@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Polymorphine\Container\Exception;

use Psr\Container\ContainerExceptionInterface;
use InvalidArgumentException;


class InvalidTypeException extends InvalidArgumentException implements ContainerExceptionInterface
{
public static function recordExpected(string $id): self
{
return new self("Setup constructor expected instance of Record as records `$id` value");
}

public static function containerExpected(string $id): self
{
return new self("Setup constructor expected instance of ContainerInterface as containers `$id` value");
}
}
13 changes: 6 additions & 7 deletions src/Exception/RecordNotFoundException.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@

class RecordNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface
{
private $hasCallStack = false;
public static function undefined(string $id): self
{
return new self("Record `$id` not defined");
}

public function withCallStack(string $callStack): self
public static function cannotWrap(string $id): self
{
if (!$this->hasCallStack) {
$this->message .= ' [call stack: ' . $callStack . ' ]';
$this->hasCallStack = true;
}
return $this;
throw new self("Attempted to decorate non-existent `$id` record with new composition");
}
}
26 changes: 26 additions & 0 deletions src/Exception/TrackedRecordNotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* This file is part of Polymorphine/Container package.
*
* (c) Shudd3r <q3.shudder@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Polymorphine\Container\Exception;

use Psr\Container\NotFoundExceptionInterface;
use InvalidArgumentException;


class TrackedRecordNotFoundException extends InvalidArgumentException implements NotFoundExceptionInterface
{
use CallStackMessageMethod;

public function __construct(string $message = '', array $callStack = [])
{
parent::__construct(self::extendMessage($message, $callStack));
}
}
2 changes: 1 addition & 1 deletion src/Records.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public function has(string $id): bool
public function get(string $id, ContainerInterface $container)
{
if (!isset($this->records[$id])) {
throw new Exception\RecordNotFoundException(sprintf('Record `%s` not defined', $id));
throw Exception\RecordNotFoundException::undefined($id);
}
return $this->records[$id]->value($container);
}
Expand Down
10 changes: 2 additions & 8 deletions src/Records/TrackedRecords.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,18 @@ class TrackedRecords extends Records
public function get(string $id, ContainerInterface $container)
{
if (isset($this->callStack[$id])) {
$message = 'Lazy composition of `%s` record is using reference to itself [call stack: %s]';
throw new Exception\CircularReferenceException(sprintf($message, (string) $id, $this->callStackPath($id)));
throw new Exception\CircularReferenceException($id, $this->callStack);
}

$this->callStack[$id] = true;

try {
$item = parent::get($id, $container);
} catch (Exception\RecordNotFoundException $e) {
throw $e->withCallStack($this->callStackPath('...'));
throw new Exception\TrackedRecordNotFoundException($e->getMessage(), $this->callStack);
}

unset($this->callStack[$id]);
return $item;
}

private function callStackPath(string $id): string
{
return implode('->', array_keys($this->callStack)) . '->' . $id;
}
}
7 changes: 3 additions & 4 deletions src/Setup/Collection.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public function container(): ContainerInterface
public function add(string $id, Records\Record $record): void
{
if (isset($this->records[$id])) {
throw new Exception\InvalidIdException(sprintf('Cannot overwrite defined `%s` Record', $id));
throw Exception\InvalidIdException::alreadyDefined("`$id` record");
}

$this->records[$id] = $record;
Expand All @@ -54,7 +54,7 @@ public function add(string $id, Records\Record $record): void
public function addContainer(string $id, ContainerInterface $container): void
{
if (isset($this->containers[$id])) {
throw new Exception\InvalidIdException(sprintf('Cannot overwrite defined `%s` container', $id));
throw Exception\InvalidIdException::alreadyDefined("`$id` container");
}

$this->containers[$id] = $container;
Expand All @@ -63,8 +63,7 @@ public function addContainer(string $id, ContainerInterface $container): void
public function moveRecord(string $id): string
{
if (!isset($this->records[$id])) {
$message = 'Undefined `%s` record cannot be moved';
throw new Exception\RecordNotFoundException(sprintf($message, $id));
throw Exception\RecordNotFoundException::cannotWrap($id);
}

$newId = $id . '.WRAP';
Expand Down
12 changes: 1 addition & 11 deletions src/Setup/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public function compose(string $className, string ...$dependencies): void
{
$idx = array_search($this->name, $dependencies, true);
if ($idx !== false) {
$dependencies[$idx] = $this->decoratedRecordAlias();
$dependencies[$idx] = $this->records->moveRecord($this->name);
}

$this->useRecord(new Record\ComposeRecord($className, ...$dependencies));
Expand All @@ -122,14 +122,4 @@ public function container(ContainerInterface $container)
{
$this->records->addContainer($this->name, $container);
}

private function decoratedRecordAlias(): string
{
try {
return $this->records->moveRecord($this->name);
} catch (Exception\RecordNotFoundException $e) {
$message = 'Undefined `%s` record for decorating composition';
throw new Exception\RecordNotFoundException(sprintf($message, $this->name));
}
}
}
25 changes: 12 additions & 13 deletions src/Setup/ValidatedCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,43 +56,42 @@ private function validateState()
private function checkRecord(string $id, $value): void
{
if (!$value instanceof Records\Record) {
$message = 'Setup record expected instance of Record in `%s` field';
throw new Exception\InvalidArgumentException(sprintf($message, $id));
throw Exception\InvalidTypeException::recordExpected($id);
}
$this->checkRecordId($id);
}

private function checkRecordId(string $id): void
{
if (isset($this->containers[$id])) {
throw Exception\InvalidIdException::alreadyDefined("`$id` container");
}

$separator = strpos($id, self::SEPARATOR);
$prefix = $separator === false ? $id : substr($id, 0, $separator);
if (isset($this->containers[$prefix])) {
$message = 'Record id or prefix `%s` already used by stored Container';
throw new Exception\InvalidIdException(sprintf($message, $prefix));
$reserved = $separator === false ? $id : substr($id, 0, $separator);
if (isset($this->containers[$reserved])) {
throw Exception\InvalidIdException::prefixConflict($reserved);
}

$this->reservedIds[$prefix] = true;
$this->reservedIds[$reserved] = true;
}

private function checkContainer(string $id, $value): void
{
if (!$value instanceof ContainerInterface) {
$message = 'Setup config expected instance of ContainerInterface in `%s` field';
throw new Exception\InvalidArgumentException(sprintf($message, $id));
throw Exception\InvalidTypeException::containerExpected($id);
}
$this->checkContainerId($id);
}

private function checkContainerId(string $id): void
{
if (strpos($id, self::SEPARATOR) !== false) {
$message = 'Container id cannot contain `%s` separator - `%s` id given';
throw new Exception\InvalidIdException(sprintf($message, self::SEPARATOR, $id));
throw Exception\InvalidIdException::unexpectedPrefixSeparator(self::SEPARATOR, $id);
}

if (isset($this->reservedIds[$id])) {
$message = 'Container id `%s` already used by some record (possibly as prefix)';
throw new Exception\InvalidIdException(sprintf($message, $id));
throw Exception\InvalidIdException::alreadyDefined("`$id` record (or record prefix)");
}
}
}
7 changes: 4 additions & 3 deletions tests/ContainerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ class ContainerTest extends TestCase
public function testInstantiation()
{
$this->assertInstanceOf(ContainerInterface::class, Setup::withData()->container());
$this->assertInstanceOf(ContainerExceptionInterface::class, new Exception\InvalidArgumentException());
$this->assertInstanceOf(ContainerExceptionInterface::class, new Exception\InvalidTypeException());
$this->assertInstanceOf(ContainerExceptionInterface::class, new Exception\InvalidIdException());
$this->assertInstanceOf(NotFoundExceptionInterface::class, new Exception\RecordNotFoundException());
$this->assertInstanceOf(NotFoundExceptionInterface::class, new Exception\TrackedRecordNotFoundException());
$this->assertInstanceOf(ContainerExceptionInterface::class, new Exception\CircularReferenceException());
}

Expand Down Expand Up @@ -390,8 +391,8 @@ public function invalidSetupConstructorParams()
return [
[['foo.bar' => $record], ['foo' => $container], Exception\InvalidIdException::class],
[['foo' => $record], ['foo' => $container], Exception\InvalidIdException::class],
[['foo' => true], ['bar' => $container], Exception\InvalidArgumentException::class],
[['foo' => $record], ['bar' => []], Exception\InvalidArgumentException::class]
[['foo' => true], ['bar' => $container], Exception\InvalidTypeException::class],
[['foo' => $record], ['bar' => []], Exception\InvalidTypeException::class]
];
}

Expand Down

0 comments on commit 175cf18

Please sign in to comment.