mirror of
https://github.com/marcostoll/FF.git
synced 2025-03-13 19:59:39 +01:00
[FEATURE] services and services factory
This commit is contained in:
parent
a4cb81dee1
commit
78e793f8e3
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "fastforward",
|
||||
"name": "fastforward/fastforward",
|
||||
"description": "A template for building web applications - part of the Fast Forward Family",
|
||||
"type": "library",
|
||||
"license": "MIT",
|
||||
@ -14,9 +14,7 @@
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"fastforward/data-structures": "^1.0.0",
|
||||
"fastforward/factories": "^1.0.0",
|
||||
"fastforward/utils": "^1.0.0"
|
||||
"fastforward/factories": "^1.0.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8"
|
||||
|
173
readme.md
Normal file
173
readme.md
Normal file
@ -0,0 +1,173 @@
|
||||
Fast Forward
|
||||
========================================================================================================================
|
||||
|
||||
by Marco Stoll
|
||||
|
||||
- <marco.stoll@rocketmail.com>
|
||||
- <http://marcostoll.de>
|
||||
- <https://github.com/marcostoll>
|
||||
- <https://github.com/marcostoll/FF>
|
||||
------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
# Introduction - What is Fast Forward?
|
||||
|
||||
**Fast Forward** (in short **FF**) is a generic application template for building web and/or command line applications
|
||||
fast and easy. It addresses the common tasks and provides configurable and/or extendable default implementations for you
|
||||
to use.
|
||||
|
||||
Currently **FF** is composed of the following features:
|
||||
1. Services and a Service Factory
|
||||
|
||||
More features will follow (see the Road Map section below).
|
||||
|
||||
# A Warning before you start
|
||||
|
||||
**FF** is highly opinionated and depends on a bunch of conventions! So be sure to consult the documentation
|
||||
before deciding to develop your application based on **FF**.
|
||||
But if you do **FF** ensures a minimal amount of setup, letting you concentrate your efforts on your business logic
|
||||
instead of soe framework configuration.
|
||||
|
||||
# Dependencies
|
||||
|
||||
- FF Family
|
||||
|
||||
**FF** makes heavy usage of the **Fast Forward Family** components, a collection of independent components providing
|
||||
generic implementations (like data structures of design patterns) used by many of **FF**'s features.
|
||||
|
||||
# Installation
|
||||
|
||||
## via Composer
|
||||
|
||||
## manual Installation
|
||||
|
||||
# Bootstrapping
|
||||
|
||||
## Bootstrap a web application
|
||||
|
||||
## Bootstrap a command line application
|
||||
|
||||
# Services and the Service Factory
|
||||
|
||||
## Services - a definition
|
||||
|
||||
From the **FF** perspective a service is a singular component (mostly class) that provide needed functionality as part
|
||||
of certain domain. Common attributes of services should be
|
||||
- Make minimal assumptions of the surrounding runtime environment.
|
||||
- Be stateless.
|
||||
- Be unit testable.
|
||||
|
||||
## 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
|
||||
thereof) to be found by the `ServicesFactory`.
|
||||
|
||||
***Example: Basic service implementation***
|
||||
|
||||
namespace MyProject\Services;
|
||||
|
||||
use FF\Services\AbstractService;
|
||||
|
||||
class MyService extends AbstractService
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
**FF** services can be configured if needed. The `ServicesFactory` will initialize a service's instance with available
|
||||
config options. But it will be your responsibility to validate that any given options will meet your service's
|
||||
requirements.
|
||||
|
||||
***Example: Configurable service implementation***
|
||||
|
||||
namespace MyProject\Services;
|
||||
|
||||
use FF\Services\AbstractService;
|
||||
|
||||
class MyConfigurableService extends AbstractService
|
||||
{
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function validateOptions(array $options, array &$errors): bool
|
||||
{
|
||||
// place you option validation logic here
|
||||
// example
|
||||
if (!isset($options['some-option'])) {
|
||||
$errors[] = 'missing options [some-options]';
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
## Using the Service Factory
|
||||
|
||||
**FF**'s service factory is based on the `AbstractSingletonFactory` of the **FF-Factories** component. Be sure to
|
||||
consult component's [documentation](https://github.com/marcostoll/FF-Factories) for further information about using
|
||||
**FF** factories.
|
||||
|
||||
To retrieve a service from the factory you have to know thw service's **class identifier**. These class identifiers are
|
||||
composed of the service's class name prefixed by any sub namespace relative to `FF\Services` (for built-in services) or
|
||||
`MyProject\Services` (for you custom services).
|
||||
|
||||
For convenience reasons there is a shorthand class `SF` that lets you retrieve one or more services with minimal effort.
|
||||
|
||||
***Example: Getting a single service***
|
||||
|
||||
use FF\Services\SF;
|
||||
use MyProject\Services\MyService;
|
||||
|
||||
/** @var MyService $myService */
|
||||
$myService = SF::i()->get('MyService'); // finds MyProject\Services\MyService
|
||||
|
||||
***Example: Getting multiple services at once***
|
||||
|
||||
use FF\Services\Events\EventBroker;
|
||||
use FF\Services\SF;
|
||||
use MyProject\Services\MyService;
|
||||
|
||||
/** @var EventBroker $eventBroker */
|
||||
/** @var MyService $myService */
|
||||
list ($eventBroker, $myService) = SF::i()->get('Events\EventBroker', MyService');
|
||||
|
||||
|
||||
## Extending built-in FF Services
|
||||
|
||||
The `ServicesFactory` uses a `FF\Factories\NamespaceClassLocator` locator to find services definitions bases on a class
|
||||
identifier. To archive this, it searches the list of registered base namespace for any suitable service class **in the
|
||||
given order**.
|
||||
|
||||
This feature lets you sub class and replace **FF**'s built-in service implementations easily by just following the
|
||||
naming conventions and registering the `ServiceFactory` as shown in the **Bootstrapping** section of this document.
|
||||
|
||||
***Example: Extend/Replace a built-in service***
|
||||
|
||||
namespace MyProject\Services\Runtime;
|
||||
|
||||
use FF\Services\Runtime\ExceptionHandler as FFExceptionHandler;
|
||||
|
||||
class ExceptionHandler extends FFExceptionHandler
|
||||
{
|
||||
// place your custom logic here
|
||||
}
|
||||
|
||||
###############
|
||||
|
||||
// when ever some component refers to the 'Runtime\ExceptionHandler' service via the ServicesFactory
|
||||
// an instance of your service extension will be used instead of the built-in service
|
||||
|
||||
/** @var MyProject\Services\Runtime\ExceptionHandler $exceptionHandler */
|
||||
$exceptionHandler = SF::get('Runtime\ExceptionHandler');
|
||||
|
||||
|
||||
# Road map
|
||||
|
||||
- Events
|
||||
- Runtime
|
||||
- Controllers
|
||||
- Sessions
|
||||
- Security
|
||||
- CLI
|
||||
- ORM
|
||||
- Bootstrapping
|
93
src/Services/AbstractService.php
Normal file
93
src/Services/AbstractService.php
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of AbstractService
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
|
||||
use FF\Services\Exceptions\ConfigurationException;
|
||||
|
||||
/**
|
||||
* Class AbstractService
|
||||
*
|
||||
* @package FF\Services
|
||||
*/
|
||||
abstract class AbstractService
|
||||
{
|
||||
use ServiceLocatorTrait;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* @param array $options
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public final function __construct(array $options = [])
|
||||
{
|
||||
$this->initialize($options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a config option
|
||||
*
|
||||
* If no option is present indexed with the given $key, the $default value is returned instead.
|
||||
*
|
||||
* @param string $key
|
||||
* @param null $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function getOption(string $key, $default = null)
|
||||
{
|
||||
return $this->options[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the service
|
||||
*
|
||||
* Overwrite this method to place custom service initialization logic.
|
||||
*
|
||||
* @param array $options
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected function initialize(array $options)
|
||||
{
|
||||
$errors = [];
|
||||
if (!$this->validateOptions($options, $errors)) {
|
||||
throw new ConfigurationException($errors);
|
||||
}
|
||||
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the service's options
|
||||
*
|
||||
* Fills $errors with messages regarding erroneous service configuration.
|
||||
*
|
||||
* Overwrite this method to do configuration validation for any concrete service depending on specific options.
|
||||
*
|
||||
* @param array $options
|
||||
* @param string[] $errors
|
||||
* @return bool
|
||||
*/
|
||||
protected function validateOptions(array $options, array &$errors): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
27
src/Services/Exceptions/ConfigurationException.php
Normal file
27
src/Services/Exceptions/ConfigurationException.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Class ConfigurationException
|
||||
*
|
||||
* @package FF\Services\Exceptions
|
||||
* @author Marco Stoll <m.stoll@core4.de>
|
||||
* @link http://core4.de CORE4 GmbH & Co. KG
|
||||
* @filesource
|
||||
*/
|
||||
|
||||
namespace FF\Services\Exceptions;
|
||||
|
||||
/**
|
||||
* Class ConfigurationException
|
||||
*/
|
||||
class ConfigurationException extends \RuntimeException
|
||||
{
|
||||
/**
|
||||
* @param string[] $errors
|
||||
* @param int $code
|
||||
* @param \Throwable $previous
|
||||
*/
|
||||
public function __construct(array $errors, int $code = 0, \Throwable $previous = null)
|
||||
{
|
||||
parent::__construct(implode(PHP_EOL, $errors), $code, $previous);
|
||||
}
|
||||
}
|
31
src/Services/SF.php
Normal file
31
src/Services/SF.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of SF
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
|
||||
/**
|
||||
* Class SF
|
||||
*
|
||||
* @package FF\Services
|
||||
*/
|
||||
class SF extends ServicesFactory
|
||||
{
|
||||
/**
|
||||
* Retrieves the ServicesFactory's singleton instance
|
||||
*
|
||||
* @return ServicesFactory
|
||||
*/
|
||||
public static function i(): ServicesFactory
|
||||
{
|
||||
return parent::getInstance();
|
||||
}
|
||||
|
||||
|
||||
}
|
30
src/Services/ServiceLocatorTrait.php
Normal file
30
src/Services/ServiceLocatorTrait.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of ServiceLocatorTrait
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
|
||||
/**
|
||||
* Trait ServiceLocatorTrait
|
||||
*
|
||||
* @package FF\Services
|
||||
*/
|
||||
trait ServiceLocatorTrait
|
||||
{
|
||||
/**
|
||||
* Retrieves services from the factory
|
||||
*
|
||||
* @param string[] $classIdentifiers
|
||||
* @return AbstractService|AbstractService[]
|
||||
*/
|
||||
protected function getService(string ...$classIdentifiers)
|
||||
{
|
||||
return SF::i()->get(...$classIdentifiers);
|
||||
}
|
||||
}
|
136
src/Services/ServicesFactory.php
Normal file
136
src/Services/ServicesFactory.php
Normal file
@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of ServicesFactory
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Services;
|
||||
|
||||
use FF\Factories\AbstractSingletonFactory;
|
||||
use FF\Factories\ClassLocators\ClassLocatorInterface;
|
||||
use FF\Factories\ClassLocators\NamespaceClassLocator;
|
||||
use FF\Factories\Exceptions\ClassNotFoundException;
|
||||
use FF\Services\Exceptions\ConfigurationException;
|
||||
|
||||
/**
|
||||
* Class ServicesFactory
|
||||
*
|
||||
* @package FF\Services
|
||||
*/
|
||||
class ServicesFactory extends AbstractSingletonFactory
|
||||
{
|
||||
/**
|
||||
* @var ServicesFactory
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $servicesOptions;
|
||||
|
||||
/**
|
||||
* Uses a NamespaceClassLocator pre-configured with the FF\Services namespace.
|
||||
* @param array $servicesOptions
|
||||
* @see \FF\Factories\ClassLocators\NamespaceClassLocator
|
||||
*/
|
||||
public function __construct(array $servicesOptions = [])
|
||||
{
|
||||
parent::__construct(new NamespaceClassLocator(__NAMESPACE__));
|
||||
|
||||
$this->servicesOptions = $servicesOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the singleton instance of this class
|
||||
*
|
||||
* @param ServicesFactory $instance
|
||||
*/
|
||||
public static function setInstance(ServicesFactory $instance)
|
||||
{
|
||||
self::$instance = $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the singleton instance of this class
|
||||
*
|
||||
* @return ServicesFactory
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public static function getInstance(): ServicesFactory
|
||||
{
|
||||
if (is_null(self::$instance)) {
|
||||
throw new ConfigurationException(['singleton instance of the service factory has not been initialized']);
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes the singleton instance of this class
|
||||
*/
|
||||
public static function clearInstance()
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves one ro more fully initialized services
|
||||
*
|
||||
* Returns a single service instance if only one class identifier was given as argument.
|
||||
* Returns an array of service instances instead if two or more class identifiers were passed. The returned list
|
||||
* will ordered in the same way as the class identifier arguments have been passed.
|
||||
*
|
||||
* @param string ...$classIdentifiers
|
||||
* @return AbstractService|AbstractService[]
|
||||
* @throws ClassNotFoundException
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public function get(string ...$classIdentifiers)
|
||||
{
|
||||
$services = [];
|
||||
foreach ($classIdentifiers as $classIdentifier)
|
||||
{
|
||||
$services[] = parent::create($classIdentifier, $this->getServiceOptions($classIdentifier));
|
||||
}
|
||||
|
||||
return count($services) == 1 ? $services[0] : $services;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @return AbstractService
|
||||
*/
|
||||
public function create(string $classIdentifier, ...$args)
|
||||
{
|
||||
/** @var AbstractService $service */
|
||||
$service = parent::create($classIdentifier, ...$args);
|
||||
return $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @return NamespaceClassLocator
|
||||
*/
|
||||
public function getClassLocator(): ClassLocatorInterface
|
||||
{
|
||||
return parent::getClassLocator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the options for a specific service
|
||||
*
|
||||
* @param string $classIdentifier
|
||||
* @return array
|
||||
*/
|
||||
public function getServiceOptions(string $classIdentifier)
|
||||
{
|
||||
return $this->servicesOptions[$classIdentifier] ?? [];
|
||||
}
|
||||
}
|
87
tests/Services/AbstractServiceTest.php
Normal file
87
tests/Services/AbstractServiceTest.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of AbstractServiceTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Services;
|
||||
|
||||
use FF\Services\AbstractService;
|
||||
use FF\Services\Exceptions\ConfigurationException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test AbstractServiceTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class AbstractServiceTest extends TestCase
|
||||
{
|
||||
const TEST_OPTIONS = ['foo' => 'bar'];
|
||||
|
||||
/**
|
||||
* @var MyService
|
||||
*/
|
||||
protected $uut;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->uut = new MyService(self::TEST_OPTIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetOptions()
|
||||
{
|
||||
$this->assertEquals(self::TEST_OPTIONS, $this->uut->getOptions());
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetOption()
|
||||
{
|
||||
$this->assertEquals(self::TEST_OPTIONS['foo'], $this->uut->getOption('foo'));
|
||||
$this->assertNull($this->uut->getOption('unknown'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testGetDefault()
|
||||
{
|
||||
$default = 'default';
|
||||
$this->assertEquals($default, $this->uut->getOption('unknown', $default));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testConfigurationException()
|
||||
{
|
||||
$this->expectException(ConfigurationException::class);
|
||||
|
||||
new MyService(['foo' => 'baz']);
|
||||
}
|
||||
}
|
||||
|
||||
class MyService 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);
|
||||
}
|
||||
}
|
42
tests/Services/SFTest.php
Normal file
42
tests/Services/SFTest.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Definition of SFTest
|
||||
*
|
||||
* @author Marco Stoll <marco@fast-forward-encoding.de>
|
||||
* @copyright 2019-forever Marco Stoll
|
||||
* @filesource
|
||||
*/
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FF\Tests\Services;
|
||||
|
||||
use FF\Services\ServicesFactory;
|
||||
use FF\Services\SF;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Test SFTest
|
||||
*
|
||||
* @package FF\Tests
|
||||
*/
|
||||
class SFTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
$servicesFactory = new ServicesFactory();
|
||||
|
||||
SF::setInstance($servicesFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the namesake method/feature
|
||||
*/
|
||||
public function testI()
|
||||
{
|
||||
$sf = SF::i();
|
||||
$this->assertInstanceOf(ServicesFactory::class, $sf);
|
||||
}
|
||||
}
|
126
tests/Services/ServicesFactoryTest.php
Normal file
126
tests/Services/ServicesFactoryTest.php
Normal file
@ -0,0 +1,126 @@
|
||||
<?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
|
||||
{
|
||||
|
||||
}
|
8
tests/testsuite.xml
Normal file
8
tests/testsuite.xml
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit>
|
||||
<testsuites>
|
||||
<testsuite name="FF">
|
||||
<directory>./</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
</phpunit>
|
Loading…
x
Reference in New Issue
Block a user