mirror of
https://github.com/marcostoll/FF.git
synced 2025-03-21 15:40:00 +01:00
Merge branch 'feature/Events' into develop
This commit is contained in:
commit
f6d2eee488
@ -14,7 +14,8 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"fastforward/factories": "^1.0.0"
|
||||
"fastforward/data-structures": "^1.0.0",
|
||||
"fastforward/factories": "^1.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8"
|
||||
|
118
readme.md
118
readme.md
@ -16,7 +16,8 @@ fast and easy. It addresses the common tasks and provides configurable and/or ex
|
||||
to use.
|
||||
|
||||
Currently **FF** is composed of the following features:
|
||||
1. Services and a Service Factory
|
||||
1. Services and the Service Factory
|
||||
2. Events and the Event Broker
|
||||
|
||||
More features will follow (see the Road Map section below).
|
||||
|
||||
@ -59,7 +60,7 @@ of certain domain. Common attributes of services should be
|
||||
## Writing services
|
||||
|
||||
[**CONVENTION**] Service classes extend `FF\Services\AbstractService`.
|
||||
[**CONVENTION**] Services should be located in your project's `MyProject\Services` namespace (or any sub namespace
|
||||
[**CONVENTION**] Services must be located in your project's `MyProject\Services` namespace (or any sub namespace
|
||||
thereof) to be found by the `ServicesFactory`.
|
||||
|
||||
***Example: Basic service implementation***
|
||||
@ -115,7 +116,7 @@ For convenience reasons there is a shorthand class `SF` that lets you retrieve o
|
||||
|
||||
***Example: Getting a single service***
|
||||
|
||||
use FF\Services\SF;
|
||||
use FF\Factories\SF;
|
||||
use MyProject\Services\MyService;
|
||||
|
||||
/** @var MyService $myService */
|
||||
@ -123,8 +124,8 @@ For convenience reasons there is a shorthand class `SF` that lets you retrieve o
|
||||
|
||||
***Example: Getting multiple services at once***
|
||||
|
||||
use FF\Services\Events\EventBroker;
|
||||
use FF\Services\SF;
|
||||
use FF\Factories\SF;
|
||||
use FF\Services\Events\EventBroker;
|
||||
use MyProject\Services\MyService;
|
||||
|
||||
/** @var EventBroker $eventBroker */
|
||||
@ -160,10 +161,115 @@ naming conventions and registering the `ServiceFactory` as shown in the **Bootst
|
||||
/** @var MyProject\Services\Runtime\ExceptionHandler $exceptionHandler */
|
||||
$exceptionHandler = SF::get('Runtime\ExceptionHandler');
|
||||
|
||||
# Events and the Event Broker
|
||||
|
||||
This feature provides a basic observer/observable pattern implementation. The key class is the `EventBroker`. Any
|
||||
other class may act as an observable by firing events using the `EventBroker`'s api. Other classes my act as observers
|
||||
by subscribing to specific types of events and being notified by the `EventBroker` in each time this type of event was
|
||||
fired.
|
||||
|
||||
## Firing Events
|
||||
|
||||
If an observable wants to notify potential observers of notable changes it simply fires a suitable event using the
|
||||
`EventBroker`'s api.
|
||||
|
||||
***Example: Fire an event***
|
||||
|
||||
use FF\Factories\SF;
|
||||
use FF\Services\Events\EventBroker;
|
||||
|
||||
class MyExceptionHandler
|
||||
{
|
||||
/**
|
||||
* Generic exception handler callback
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @see http://php.net/set_exception_handler
|
||||
*/
|
||||
public function onException(\Throwable $e)
|
||||
{
|
||||
try {
|
||||
/** @var EventBroker $eventBroker */
|
||||
$eventBroker = SF::i()->get('Events\EventBroker');
|
||||
$eventBroker->fire('Runtime\Exception', $e);
|
||||
} catch (\Exception $e) {
|
||||
// do not handle exceptions thrown while
|
||||
// processing the on-exception event
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
The `fire` method of the `EventBroker` uses the `EventsFactory` to instantiate the actual event object passing any
|
||||
additional argument provided to the event class's constructor.
|
||||
|
||||
## Subscribing to Events
|
||||
|
||||
A valid event handling method must meet the following requirements:
|
||||
|
||||
- must be public
|
||||
- must not be static or abstract
|
||||
- accept exactly one argument: the event classes instance
|
||||
|
||||
***Example: Subscribe to an event***
|
||||
|
||||
use FF\Events;
|
||||
use FF\Factories\SF;
|
||||
use FF\Services\Events\EventBroker;
|
||||
|
||||
class MyErrorObserver
|
||||
{
|
||||
/**
|
||||
* Event handling callback
|
||||
*
|
||||
* @param Runtime\Error $event
|
||||
public function onRuntimeError(Runtime\Error $event)
|
||||
{
|
||||
// handle the Error event
|
||||
var_dump(
|
||||
$event->getErrNo(),
|
||||
$event->getErrMsg(),
|
||||
$event->getErrFile(),
|
||||
$event->getErrLine()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// subscribe to the Runtime\Error event
|
||||
/** @var EventBroker $eventBroker */
|
||||
$eventBroker = SF::i()->get('Events\EventBroker');
|
||||
$eventBroker->subscribe([new MyErrorObserver, 'onRuntimeError'], 'Runtime\Error');
|
||||
|
||||
The subscription is bases on the class identifier of the event class. This is exactly the same string to use by the
|
||||
observable when firing the event.
|
||||
|
||||
## Defining Custom Events
|
||||
|
||||
[**CONVENTION**] Event classes extend `FF\Events\AbstractEvent`.
|
||||
[**CONVENTION**] Events must be located in your project's `MyProject\Events` namespace (or any sub namespace
|
||||
thereof) to be found by the `EventsFactory`.
|
||||
|
||||
***Example: A custom event***
|
||||
|
||||
namespace MyProject\Events;
|
||||
|
||||
use FF\Events\AbstractEvent;
|
||||
|
||||
/**
|
||||
* This event's class identifier would just be 'Logoff'
|
||||
*/
|
||||
class Logoff extends AbstractEvent
|
||||
{
|
||||
/**
|
||||
* Define constructor arguments (the event data) to meet your needs.
|
||||
*/
|
||||
public function __construct($eventData)
|
||||
{
|
||||
var_dump($eventData);
|
||||
}
|
||||
}
|
||||
|
||||
# Road map
|
||||
|
||||
- Events
|
||||
- Runtime
|
||||
- Controllers
|
||||
- Sessions
|
||||
|
70
src/Events/AbstractEvent.php
Normal file
70
src/Events/AbstractEvent.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of AbstractEvent
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Events;
|
||||
|
||||
use FF\Factories\ClassLocators\ClassIdentifierAwareInterface;
|
||||
|
||||
/**
|
||||
* Class AbstractEvent
|
||||
*
|
||||
* @package FF\Events
|
||||
*/
|
||||
abstract class AbstractEvent implements ClassIdentifierAwareInterface
|
||||
{
|
||||
/**
|
||||
* For use with the BaseNamespaceClassLocator of the EventsFactory
|
||||
*/
|
||||
const COMMON_NS_SUFFIX = 'Events';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isCanceled = false;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isCanceled(): bool
|
||||
{
|
||||
return $this->isCanceled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $isCanceled
|
||||
* @return $this
|
||||
*/
|
||||
public function setIsCanceled(bool $isCanceled)
|
||||
{
|
||||
$this->isCanceled = $isCanceled;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function cancel()
|
||||
{
|
||||
return $this->setIsCanceled(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function getClassIdentifier(): string
|
||||
{
|
||||
$className = get_called_class();
|
||||
$needle = '\\' . self::COMMON_NS_SUFFIX . '\\';
|
||||
$pos = strpos($className, $needle);
|
||||
if ($pos === false) return $className;
|
||||
|
||||
return substr($className, $pos + strlen($needle));
|
||||
}
|
||||
}
|
81
src/Factories/EventsFactory.php
Normal file
81
src/Factories/EventsFactory.php
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of EventsFactory
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Factories;
|
||||
|
||||
use FF\Events\AbstractEvent;
|
||||
use FF\Factories\ClassLocators\BaseNamespaceClassLocator;
|
||||
use FF\Factories\ClassLocators\ClassLocatorInterface;
|
||||
|
||||
/**
|
||||
* Class EventsFactory
|
||||
*
|
||||
* @package FF\Factories
|
||||
*/
|
||||
class EventsFactory extends AbstractFactory
|
||||
{
|
||||
/**
|
||||
* @var EventsFactory
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Declared protected to prevent external usage.
|
||||
* Uses a BaseNamespaceClassLocator pre-configured with the 'Events' as common suffix and the FF namespace.
|
||||
*
|
||||
* @see \FF\Factories\ClassLocators\BaseNamespaceClassLocator
|
||||
*/
|
||||
protected function __construct()
|
||||
{
|
||||
parent::__construct(new BaseNamespaceClassLocator(AbstractEvent::COMMON_NS_SUFFIX, 'FF'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Declared protected to prevent external usage
|
||||
*/
|
||||
protected function __clone()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return BaseNamespaceClassLocator
|
||||
*/
|
||||
public function getClassLocator(): ClassLocatorInterface
|
||||
{
|
||||
return parent::getClassLocator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the singleton instance of this class
|
||||
*
|
||||
* @return EventsFactory
|
||||
*/
|
||||
public static function getInstance(): EventsFactory
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
self::$instance = new EventsFactory();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return AbstractEvent
|
||||
*/
|
||||
public function create(string $classIdentifier, ...$args)
|
||||
{
|
||||
/** @var AbstractEvent $event */
|
||||
$event = parent::create($classIdentifier, ...$args);
|
||||
return $event;
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
namespace FF\Factories;
|
||||
|
||||
/**
|
||||
* Class SF
|
@ -8,18 +8,18 @@
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
namespace FF\Factories;
|
||||
|
||||
use FF\Factories\AbstractSingletonFactory;
|
||||
use FF\Factories\ClassLocators\BaseNamespaceClassLocator;
|
||||
use FF\Factories\ClassLocators\ClassLocatorInterface;
|
||||
use FF\Factories\ClassLocators\NamespaceClassLocator;
|
||||
use FF\Factories\Exceptions\ClassNotFoundException;
|
||||
use FF\Services\AbstractService;
|
||||
use FF\Services\Exceptions\ConfigurationException;
|
||||
|
||||
/**
|
||||
* Class ServicesFactory
|
||||
*
|
||||
* @package FF\Services
|
||||
* @package FF\Factories
|
||||
*/
|
||||
class ServicesFactory extends AbstractSingletonFactory
|
||||
{
|
||||
@ -34,13 +34,14 @@ class ServicesFactory extends AbstractSingletonFactory
|
||||
protected $servicesOptions;
|
||||
|
||||
/**
|
||||
* Uses a NamespaceClassLocator pre-configured with the FF\Services namespace.
|
||||
* Uses a BaseNamespaceClassLocator pre-configured with the 'Services' as common suffix and the FF namespace.
|
||||
*
|
||||
* @param array $servicesOptions
|
||||
* @see \FF\Factories\ClassLocators\NamespaceClassLocator
|
||||
* @see \FF\Factories\ClassLocators\BaseNamespaceClassLocator
|
||||
*/
|
||||
public function __construct(array $servicesOptions = [])
|
||||
{
|
||||
parent::__construct(new NamespaceClassLocator(__NAMESPACE__));
|
||||
parent::__construct(new BaseNamespaceClassLocator(AbstractService::COMMON_NS_SUFFIX, 'FF'));
|
||||
|
||||
$this->servicesOptions = $servicesOptions;
|
||||
}
|
||||
@ -116,7 +117,7 @@ class ServicesFactory extends AbstractSingletonFactory
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return NamespaceClassLocator
|
||||
* @return BaseNamespaceClassLocator
|
||||
*/
|
||||
public function getClassLocator(): ClassLocatorInterface
|
||||
{
|
@ -10,16 +10,24 @@ declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
|
||||
use FF\Factories\ClassLocators\ClassIdentifierAwareInterface;
|
||||
use FF\Services\Exceptions\ConfigurationException;
|
||||
use FF\Services\Traits\EventEmitterTrait;
|
||||
use FF\Services\Traits\ServiceLocatorTrait;
|
||||
|
||||
/**
|
||||
* Class AbstractService
|
||||
*
|
||||
* @package FF\Services
|
||||
*/
|
||||
abstract class AbstractService
|
||||
abstract class AbstractService implements ClassIdentifierAwareInterface
|
||||
{
|
||||
use ServiceLocatorTrait;
|
||||
use EventEmitterTrait, ServiceLocatorTrait;
|
||||
|
||||
/**
|
||||
* For use with the BaseNamespaceClassLocator of the ServicesFactory
|
||||
*/
|
||||
const COMMON_NS_SUFFIX = 'Services';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
@ -57,6 +65,19 @@ abstract class AbstractService
|
||||
return $this->options[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public static function getClassIdentifier(): string
|
||||
{
|
||||
$className = get_called_class();
|
||||
$needle = '\\' . self::COMMON_NS_SUFFIX . '\\';
|
||||
$pos = strpos($className, $needle);
|
||||
if ($pos === false) return $className;
|
||||
|
||||
return substr($className, $pos + strlen($needle));
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the service
|
||||
*
|
||||
|
245
src/Services/Events/EventBroker.php
Normal file
245
src/Services/Events/EventBroker.php
Normal file
@ -0,0 +1,245 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of EventBroker
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services\Events;
|
||||
|
||||
use FF\DataStructures\IndexedCollection;
|
||||
use FF\DataStructures\OrderedCollection;
|
||||
use FF\Events\AbstractEvent;
|
||||
use FF\Factories\EventsFactory;
|
||||
use FF\Factories\Exceptions\ClassNotFoundException;
|
||||
use FF\Services\AbstractService;
|
||||
|
||||
/**
|
||||
* Class EventBroker
|
||||
*
|
||||
* @package FF\Services
|
||||
*/
|
||||
class EventBroker extends AbstractService
|
||||
{
|
||||
/**
|
||||
* @var IndexedCollection
|
||||
*/
|
||||
protected $subscriptions;
|
||||
|
||||
/**
|
||||
* @var EventsFactory
|
||||
*/
|
||||
protected $eventsFactory;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function initialize(array $options)
|
||||
{
|
||||
parent::initialize($options);
|
||||
|
||||
$this->subscriptions = new IndexedCollection();
|
||||
$this->eventsFactory = EventsFactory::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EventsFactory
|
||||
*/
|
||||
public function getEventsFactory(): EventsFactory
|
||||
{
|
||||
return $this->eventsFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return IndexedCollection
|
||||
*/
|
||||
public function getSubscriptions(): IndexedCollection
|
||||
{
|
||||
return $this->subscriptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $classIdentifier
|
||||
* @return OrderedCollection
|
||||
*/
|
||||
public function getSubscribers(string $classIdentifier): OrderedCollection
|
||||
{
|
||||
$this->initializeSubscribersCollection($classIdentifier);
|
||||
return $this->subscriptions->get($classIdentifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a listener to the subscribers list of an event
|
||||
*
|
||||
* Removes any previous subscriptions of the listener first to the named event.
|
||||
*
|
||||
* @param callable $listener
|
||||
* @param string $classIdentifier
|
||||
* @return $this
|
||||
*/
|
||||
public function subscribe(callable $listener, string $classIdentifier)
|
||||
{
|
||||
$this->unsubscribe($listener, $classIdentifier);
|
||||
$this->initializeSubscribersCollection($classIdentifier);
|
||||
|
||||
$this->subscriptions->get($classIdentifier)->push($listener);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepends a listener to the subscriber list of an event
|
||||
*
|
||||
* Removes any previous subscriptions of the listener first to the named event.
|
||||
*
|
||||
* @param callable $listener
|
||||
* @param string $classIdentifier
|
||||
* @return $this
|
||||
*/
|
||||
public function subscribeFirst(callable $listener, string $classIdentifier)
|
||||
{
|
||||
$this->unsubscribe($listener, $classIdentifier);
|
||||
$this->initializeSubscribersCollection($classIdentifier);
|
||||
|
||||
$this->subscriptions->get($classIdentifier)->unshift($listener);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes a listener
|
||||
*
|
||||
* If $name is omitted, the listener will be unsubscribed from each event it was subscribed to.
|
||||
*
|
||||
* @param callable $listener
|
||||
* @param string $classIdentifier
|
||||
* @return $this
|
||||
*/
|
||||
public function unsubscribe(callable $listener, string $classIdentifier = null)
|
||||
{
|
||||
/** @var OrderedCollection $listenerCollection */
|
||||
foreach ($this->subscriptions as $name => $listenerCollection) {
|
||||
if (!is_null($classIdentifier) && $classIdentifier != $name) continue;
|
||||
|
||||
$index = $listenerCollection->search($listener, true);
|
||||
if (is_null($index)) continue;
|
||||
|
||||
// remove listener from event
|
||||
unset($listenerCollection[$index]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all subscriptions for this event
|
||||
*
|
||||
* @param string $classIdentifier
|
||||
* @return $this
|
||||
*/
|
||||
public function unsubscribeAll(string $classIdentifier)
|
||||
{
|
||||
unset($this->subscriptions[$classIdentifier]);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any listeners where subscribed to the named event
|
||||
*
|
||||
* @param string $classIdentifier
|
||||
* @return bool
|
||||
*/
|
||||
public function hasSubscribers(string $classIdentifier): bool
|
||||
{
|
||||
return $this->subscriptions->has($classIdentifier) && !$this->subscriptions->get($classIdentifier)->isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks of the listener has been subscribed to the given event
|
||||
*
|
||||
* @param callable $listener
|
||||
* @param string $classIdentifier
|
||||
* @return bool
|
||||
*/
|
||||
public function isSubscribed(callable $listener, string $classIdentifier): bool
|
||||
{
|
||||
if (!$this->hasSubscribers($classIdentifier)) return false;
|
||||
|
||||
return !is_null($this->subscriptions->get($classIdentifier)->search($listener));
|
||||
}
|
||||
|
||||
/**Notifies all listeners of events of the given type
|
||||
*
|
||||
* Listeners will be notified in the order of their subscriptions.
|
||||
* Does nothing if no listeners subscribed to the type of the event.
|
||||
*
|
||||
* Creates an event instance and fires it.
|
||||
* Does nothing if no suitable event model could be created.
|
||||
*
|
||||
* Any given $args will be passed to the constructor of the suitable event
|
||||
* model class in the given order.
|
||||
*
|
||||
* @param string $classIdentifier
|
||||
* @param mixed ...$args
|
||||
* @return $this
|
||||
*/
|
||||
public function fire(string $classIdentifier, ...$args)
|
||||
{
|
||||
$event = $this->createEvent($classIdentifier, ...$args);
|
||||
if (is_null($event)) return $this;
|
||||
|
||||
foreach ($this->getSubscribers($classIdentifier) as $listener) {
|
||||
$this->notify($listener, $event);
|
||||
|
||||
if ($event->isCanceled()) {
|
||||
// stop notifying further listeners if event has been canceled
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize listener collection if necessary
|
||||
*
|
||||
* @param string $classIdentifier
|
||||
*/
|
||||
protected function initializeSubscribersCollection(string $classIdentifier)
|
||||
{
|
||||
if ($this->subscriptions->has($classIdentifier)) return;
|
||||
|
||||
$this->subscriptions->set($classIdentifier, new OrderedCollection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a fresh event instance
|
||||
*
|
||||
* @param string $classIdentifier
|
||||
* @param mixed ...$args
|
||||
* @return AbstractEvent|null
|
||||
*/
|
||||
protected function createEvent(string $classIdentifier, ...$args): ?AbstractEvent
|
||||
{
|
||||
try {
|
||||
return $this->eventsFactory->create($classIdentifier, ...$args);
|
||||
} catch (ClassNotFoundException $e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Passes the event to the listener
|
||||
*
|
||||
* The listener will be invoked with the event as the first and only argument.
|
||||
* Any return values of the listener will be discarded.
|
||||
*
|
||||
* @param callable $listener
|
||||
* @param AbstractEvent $event
|
||||
*/
|
||||
protected function notify(callable $listener, AbstractEvent $event)
|
||||
{
|
||||
call_user_func($listener, $event);
|
||||
}
|
||||
}
|
45
src/Services/Traits/EventEmitterTrait.php
Normal file
45
src/Services/Traits/EventEmitterTrait.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of EventEmitterTrait
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services\Traits;
|
||||
|
||||
use FF\Factories\SF;
|
||||
use FF\Services\Events\EventBroker;
|
||||
|
||||
/**
|
||||
* Trait EventEmitterTrait
|
||||
*
|
||||
* @package FF\Services\Traits
|
||||
*/
|
||||
trait EventEmitterTrait
|
||||
{
|
||||
/**
|
||||
* Creates an event instance and fires it
|
||||
*
|
||||
* Delegates the execution to the EventBroker provided by the ServiceFactory.
|
||||
*
|
||||
* @param string $classIdentifier
|
||||
* @param mixed ...$args
|
||||
* @return $this
|
||||
*/
|
||||
protected function fire(string $classIdentifier, ...$args)
|
||||
{
|
||||
/** @var EventBroker $eventBroker */
|
||||
static $eventBroker = null;
|
||||
|
||||
if (is_null($eventBroker)) {
|
||||
$eventBroker = SF::i()->get('Events\EventBroker');
|
||||
}
|
||||
|
||||
$eventBroker->fire($classIdentifier, ...$args);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
@ -8,12 +8,15 @@
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
namespace FF\Services\Traits;
|
||||
|
||||
use FF\Factories\SF;
|
||||
use FF\Services\AbstractService;
|
||||
|
||||
/**
|
||||
* Trait ServiceLocatorTrait
|
||||
*
|
||||
* @package FF\Services
|
||||
* @package FF\Services\Traits
|
||||
*/
|
||||
trait ServiceLocatorTrait
|
||||
{
|
68
tests/Events/AbstractEventTest.php
Normal file
68
tests/Events/AbstractEventTest.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of AbstractEventTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Events;
|
||||
|
||||
use FF\Events\AbstractEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test AbstractEventTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class AbstractEventTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var MyEvent
|
||||
*/
|
||||
protected $uut;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->uut = new MyEvent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testSetGetIsCanceled()
|
||||
{
|
||||
$same = $this->uut->setIsCanceled(true);
|
||||
$this->assertSame($this->uut, $same);
|
||||
$this->assertTrue($this->uut->isCanceled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testCancel()
|
||||
{
|
||||
$same = $this->uut->cancel();
|
||||
$this->assertSame($this->uut, $same);
|
||||
$this->assertTrue($this->uut->isCanceled());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetClassIdentifier()
|
||||
{
|
||||
$this->assertEquals('MyEvent', MyEvent::getClassIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
class MyEvent extends AbstractEvent
|
||||
{
|
||||
|
||||
}
|
41
tests/Factories/EventsFactoryTest.php
Normal file
41
tests/Factories/EventsFactoryTest.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of EventsFactoryTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Factories;
|
||||
|
||||
use FF\Factories\ClassLocators\BaseNamespaceClassLocator;
|
||||
use FF\Factories\EventsFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test EventsFactoryTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class EventsFactoryTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetInstance()
|
||||
{
|
||||
$instance = EventsFactory::getInstance();
|
||||
$this->assertInstanceOf(EventsFactory::class, $instance);
|
||||
$this->assertSame($instance, EventsFactory::getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetClassLocator()
|
||||
{
|
||||
$this->assertInstanceOf(BaseNamespaceClassLocator::class, EventsFactory::getInstance()->getClassLocator());
|
||||
}
|
||||
}
|
@ -8,10 +8,10 @@
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Services;
|
||||
namespace FF\Tests\Factories;
|
||||
|
||||
use FF\Services\ServicesFactory;
|
||||
use FF\Services\SF;
|
||||
use FF\Factories\ServicesFactory;
|
||||
use FF\Factories\SF;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
146
tests/Factories/ServicesFactoryTest.php
Normal file
146
tests/Factories/ServicesFactoryTest.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of ServicesFactoryTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Factories {
|
||||
|
||||
use FF\Factories\ClassLocators\BaseNamespaceClassLocator;
|
||||
use FF\Factories\Exceptions\ClassNotFoundException;
|
||||
use FF\Factories\ServicesFactory;
|
||||
use FF\Services\Exceptions\ConfigurationException;
|
||||
use FF\Tests\Services\ServiceOne;
|
||||
use FF\Tests\Services\ServiceTwo;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test ServicesFactoryTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class ServicesFactoryTest extends TestCase
|
||||
{
|
||||
const TEST_OPTIONS = ['ServiceOne' => ['foo' => 'bar']];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
ServicesFactory::clearInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetInstanceConfigException()
|
||||
{
|
||||
$this->expectException(ConfigurationException::class);
|
||||
|
||||
ServicesFactory::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testSetGetInstance()
|
||||
{
|
||||
$instance = new ServicesFactory(self::TEST_OPTIONS);
|
||||
$instance->getClassLocator()->prependNamespaces('FF\Tests');
|
||||
ServicesFactory::setInstance($instance);
|
||||
|
||||
$this->assertSame($instance, ServicesFactory::getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetClassLocator()
|
||||
{
|
||||
$this->assertInstanceOf(
|
||||
BaseNamespaceClassLocator::class,
|
||||
ServicesFactory::getInstance()->getClassLocator()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testSetServiceOptions()
|
||||
{
|
||||
$this->assertEquals(
|
||||
self::TEST_OPTIONS['ServiceOne'],
|
||||
ServicesFactory::getInstance()->getServiceOptions('ServiceOne')
|
||||
);
|
||||
$this->assertEquals([], ServicesFactory::getInstance()->getServiceOptions('ServiceTwo'));
|
||||
$this->assertEquals([], ServicesFactory::getInstance()->getServiceOptions('unknown'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testGetSingle()
|
||||
{
|
||||
$service = ServicesFactory::getInstance()->get('ServiceOne');
|
||||
$this->assertInstanceOf(ServiceOne::class, $service);
|
||||
$this->assertEquals(self::TEST_OPTIONS['ServiceOne'], $service->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testGetMultiples()
|
||||
{
|
||||
$services = ServicesFactory::getInstance()->get('ServiceOne', 'ServiceTwo');
|
||||
$this->assertEquals(2, count($services));
|
||||
$this->assertInstanceOf(ServiceOne::class, $services[0]);
|
||||
$this->assertInstanceOf(ServiceTwo::class, $services[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testGetClassNotFound()
|
||||
{
|
||||
$this->expectException(ClassNotFoundException::class);
|
||||
|
||||
ServicesFactory::getInstance()->get('ServiceUnknown');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace FF\Tests\Services {
|
||||
|
||||
use FF\Services\AbstractService;
|
||||
|
||||
class ServiceOne extends AbstractService
|
||||
{
|
||||
protected function validateOptions(array $options, array &$errors): bool
|
||||
{
|
||||
if (isset($options['foo']) && $options['foo'] != 'bar') {
|
||||
$errors[] = 'foo is not bar';
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::validateOptions($options, $errors);
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceTwo extends AbstractService
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -71,6 +71,14 @@ class AbstractServiceTest extends TestCase
|
||||
|
||||
new MyService(['foo' => 'baz']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetClassIdentifier()
|
||||
{
|
||||
$this->assertEquals('MyService', MyService::getClassIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
class MyService extends AbstractService
|
||||
|
288
tests/Services/Events/EventBrokerTest.php
Normal file
288
tests/Services/Events/EventBrokerTest.php
Normal file
@ -0,0 +1,288 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of EventBrokerTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Services\Events;
|
||||
|
||||
use FF\DataStructures\IndexedCollection;
|
||||
use FF\DataStructures\OrderedCollection;
|
||||
use FF\Events\AbstractEvent;
|
||||
use FF\Factories\EventsFactory;
|
||||
use FF\Services\Events\EventBroker;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test EventBrokerTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class EventBrokerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var EventBroker
|
||||
*/
|
||||
protected $uut;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->uut = new EventBroker();
|
||||
$this->uut->getEventsFactory()
|
||||
->getClassLocator()
|
||||
->prependNamespaces('FF\Tests\Services');
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
$this->uut->unsubscribeAll('EventA');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetEventsFactory()
|
||||
{
|
||||
$this->assertInstanceOf(EventsFactory::class, $this->uut->getEventsFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetSubscriptions()
|
||||
{
|
||||
$this->assertInstanceOf(IndexedCollection::class, $this->uut->getSubscriptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetSubscribers()
|
||||
{
|
||||
$this->assertInstanceOf(OrderedCollection::class, $this->uut->getSubscribers('EventA'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testSubscribe()
|
||||
{
|
||||
$callback = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$same = $this->uut->subscribe($callback, 'EventA');
|
||||
$this->assertSame($this->uut, $same);
|
||||
$this->assertEquals(1, count($this->uut->getSubscribers('EventA')));
|
||||
$this->assertSame($callback, $this->uut->getSubscribers('EventA')->getFirst());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testSubscribeRepeated()
|
||||
{
|
||||
$callback = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$this->uut->subscribe($callback, 'EventA')->subscribe($callback, 'EventA');
|
||||
$this->assertEquals(1, count($this->uut->getSubscribers('EventA')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testSubscribeAppend()
|
||||
{
|
||||
$callback1 = [new ListenerA(), 'shoutA'];
|
||||
$callback2 = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$this->uut->subscribe($callback1, 'EventA')
|
||||
->subscribe($callback2, 'EventA');
|
||||
$this->assertSame($callback1, $this->uut->getSubscribers('EventA')->get(0));
|
||||
$this->assertSame($callback2, $this->uut->getSubscribers('EventA')->get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testSubscribeFirst()
|
||||
{
|
||||
$callback1 = [new ListenerA(), 'shoutA'];
|
||||
$callback2 = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$same = $this->uut->subscribe($callback1, 'EventA')
|
||||
->subscribeFirst($callback2, 'EventA');
|
||||
$this->assertSame($this->uut, $same);
|
||||
$this->assertSame($callback1, $this->uut->getSubscribers('EventA')->get(1));
|
||||
$this->assertSame($callback2, $this->uut->getSubscribers('EventA')->get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testUnsubscribe()
|
||||
{
|
||||
$callback1 = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$this->uut->subscribe($callback1, 'EventA')
|
||||
->subscribe($callback1, 'EventB')
|
||||
->unsubscribe($callback1);
|
||||
$this->assertTrue($this->uut->getSubscribers('EventA')->isEmpty());
|
||||
$this->assertTrue($this->uut->getSubscribers('EventB')->isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testUnsubscribeNamed()
|
||||
{
|
||||
$callback1 = [new ListenerA(), 'shoutA'];
|
||||
$callback2 = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$same = $this->uut->subscribe($callback1, 'EventA')
|
||||
->subscribe($callback2, 'EventA')
|
||||
->unsubscribe($callback1, 'EventA');
|
||||
$this->assertSame($this->uut, $same);
|
||||
$this->assertEquals(1, count($this->uut->getSubscribers('EventA')));
|
||||
$this->assertSame($callback2, $this->uut->getSubscribers('EventA')->get(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testUnsubscribeAll()
|
||||
{
|
||||
$callback1 = [new ListenerA(), 'shoutA'];
|
||||
$callback2 = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$same = $this->uut->subscribe($callback1, 'EventA')
|
||||
->subscribe($callback2, 'EventA')
|
||||
->unsubscribeAll('EventA');
|
||||
$this->assertSame($this->uut, $same);
|
||||
$this->assertTrue($this->uut->getSubscribers('EventA')->isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testHasSubscribers()
|
||||
{
|
||||
$callback1 = [new ListenerA(), 'shoutA'];
|
||||
|
||||
$this->uut->subscribe($callback1, 'EventA');
|
||||
|
||||
$this->assertTrue($this->uut->hasSubscribers('EventA'));
|
||||
$this->assertFalse($this->uut->hasSubscribers('EventB'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testIsSubscribed()
|
||||
{
|
||||
$callback1 = [new ListenerA(), 'shoutA'];
|
||||
$callback2 = [new ListenerA(), 'shoutB'];
|
||||
|
||||
$this->uut->subscribe($callback1, 'EventA');
|
||||
|
||||
$this->assertTrue($this->uut->isSubscribed($callback1, 'EventA'));
|
||||
$this->assertFalse($this->uut->isSubscribed($callback1, 'EventB'));
|
||||
$this->assertFalse($this->uut->isSubscribed($callback2, 'EventA'));
|
||||
$this->assertFalse($this->uut->isSubscribed($callback2, 'EventB'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testFire()
|
||||
{
|
||||
$same = $this->uut->fire('EventA', 'foo');
|
||||
$this->assertSame($this->uut, $same);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testFireWithListener()
|
||||
{
|
||||
$this->expectOutputString('foo');
|
||||
|
||||
$this->uut->subscribe([new ListenerA(), 'shoutA'], 'EventA')
|
||||
->fire('EventA', 'foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testFireCancel()
|
||||
{
|
||||
$this->expectOutputString('foo'); // output would be 'foofoo' if event canceling does not work
|
||||
|
||||
$this->uut->subscribe([new ListenerA(), 'cancelA'], 'EventA')
|
||||
->subscribe([new ListenerB(), 'neverToReach'], 'EventA')
|
||||
->fire('EventA', 'foo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetClassIdentifier()
|
||||
{
|
||||
$this->assertEquals('Events\EventBroker', EventBroker::getClassIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
class ListenerA
|
||||
{
|
||||
public function shoutA(EventA $event)
|
||||
{
|
||||
print $event->content;
|
||||
}
|
||||
|
||||
public function shoutB(EventB $event)
|
||||
{
|
||||
print $event->content;
|
||||
}
|
||||
|
||||
public function cancelA(EventA $event)
|
||||
{
|
||||
print $event->content;
|
||||
$event->cancel();
|
||||
}
|
||||
}
|
||||
|
||||
class ListenerB extends ListenerA
|
||||
{
|
||||
public function shoutB(EventB $event)
|
||||
{
|
||||
print $event->content;
|
||||
}
|
||||
|
||||
public function neverToReach(EventA $event)
|
||||
{
|
||||
print $event->content;
|
||||
}
|
||||
}
|
||||
|
||||
class EventA extends AbstractEvent
|
||||
{
|
||||
public $content;
|
||||
|
||||
public function __construct(string $content)
|
||||
{
|
||||
$this->content = $content;
|
||||
}
|
||||
}
|
||||
|
||||
class EventB extends EventA
|
||||
{
|
||||
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of ServicesFactoryTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Services;
|
||||
|
||||
use FF\Factories\Exceptions\ClassNotFoundException;
|
||||
use FF\Services\AbstractService;
|
||||
use FF\Services\Exceptions\ConfigurationException;
|
||||
use FF\Services\ServicesFactory;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test ServicesFactoryTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class ServicesFactoryTest extends TestCase
|
||||
{
|
||||
const TEST_OPTIONS = ['ServiceOne' => ['foo' => 'bar']];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
ServicesFactory::clearInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetInstanceConfigException()
|
||||
{
|
||||
$this->expectException(ConfigurationException::class);
|
||||
|
||||
ServicesFactory::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testSetGetInstance()
|
||||
{
|
||||
$instance = new ServicesFactory(self::TEST_OPTIONS);
|
||||
$instance->getClassLocator()->prependNamespaces(__NAMESPACE__);
|
||||
ServicesFactory::setInstance($instance);
|
||||
|
||||
$this->assertSame($instance, ServicesFactory::getInstance());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testSetServiceOptions()
|
||||
{
|
||||
$this->assertEquals(
|
||||
self::TEST_OPTIONS['ServiceOne'],
|
||||
ServicesFactory::getInstance()->getServiceOptions('ServiceOne')
|
||||
);
|
||||
$this->assertEquals([], ServicesFactory::getInstance()->getServiceOptions('unknown'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testGetSingle()
|
||||
{
|
||||
$service = ServicesFactory::getInstance()->get('ServiceOne');
|
||||
$this->assertInstanceOf(ServiceOne::class, $service);
|
||||
$this->assertEquals(self::TEST_OPTIONS['ServiceOne'], $service->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testGetMultiples()
|
||||
{
|
||||
$services = ServicesFactory::getInstance()->get('ServiceOne', 'ServiceOne');
|
||||
$this->assertEquals(2, count($services));
|
||||
$this->assertInstanceOf(ServiceOne::class, $services[0]);
|
||||
$this->assertInstanceOf(ServiceOne::class, $services[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*
|
||||
* @depends testSetGetInstance
|
||||
*/
|
||||
public function testGetClassNotFound()
|
||||
{
|
||||
$this->expectException(ClassNotFoundException::class);
|
||||
|
||||
ServicesFactory::getInstance()->get('ServiceUnknown');
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceOne extends AbstractService
|
||||
{
|
||||
protected function validateOptions(array $options, array &$errors): bool
|
||||
{
|
||||
if (isset($options['foo']) && $options['foo'] != 'bar') {
|
||||
$errors[] = 'foo is not bar';
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::validateOptions($options, $errors);
|
||||
}
|
||||
}
|
||||
|
||||
class ServiceTwo extends AbstractService
|
||||
{
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user