Implemented all ideas for the Christmas holidays

This commit is contained in:
Abel Hoogeveen 2019-01-01 18:37:14 +01:00
parent ef149a953f
commit f0865bb761
No known key found for this signature in database
GPG Key ID: 96C2234920BF4292
14 changed files with 476 additions and 40 deletions

View File

@ -57,6 +57,13 @@ class Config
*/
protected $cfg = [];
/**
* Array of config values that will be overridden
*
* @var array of config values
*/
public static $configOverrides = [];
/**
* Paths where Helpers can be found.
*
@ -160,7 +167,17 @@ class Config
if (file_exists($file))
{
// Load object
return (new ConfigORM())->load($file);
$configORM = (new ConfigORM())->load($file);
// Override config values if they exist
if (isset(self::$configOverrides[$event->configName]))
{
foreach (self::$configOverrides[$event->configName] as $configKey => $configValue)
$configORM->{$configKey} = $configValue;
}
// Return object
return $configORM;
break;
}
}
@ -176,6 +193,28 @@ class Config
throw new ConfigException("Could not load config. File $event->configName not found", 1);
}
/**
* Override a config value before FuzeWorks is loaded.
*
* Allows the user to change any value in config files loaded by FuzeWorks.
*
* @param string $configName
* @param string $configKey
* @param $configValue
*/
public static function overrideConfig(string $configName, string $configKey, $configValue)
{
// Convert configName
$configName = strtolower($configName);
// If config doesn't exist yet, create it
if (!isset(self::$configOverrides[$configName]))
self::$configOverrides[$configName] = [];
// And set the value
self::$configOverrides[$configName][$configKey] = $configValue;
}
/**
* Set the directories. Automatically gets invoked if configPaths are added to FuzeWorks\Configurator.
*

View File

@ -62,10 +62,10 @@ class Configurator
protected $parameters = ['debugEnabled' => false];
/**
* Components that will be attached to the Factory.
* Components that have been added to FuzeWorks
*
* @var array Array of classnames
*/
* @var iComponent[]
*/
protected $components = [];
/**
@ -77,6 +77,13 @@ class Configurator
*/
protected $directories = ['app' => []];
/**
* Array of ComponentClass methods to be invoked once ComponentClass is loaded
*
* @var DeferredComponentClass[]
*/
protected $deferredComponentClassMethods = [];
const COOKIE_SECRET = 'fuzeworks-debug';
/* ---------------- Core Directories--------------------- */
@ -137,15 +144,50 @@ class Configurator
*/
public function addComponent(iComponent $component): Configurator
{
foreach ($component->getClasses() as $objectName => $className) {
$this->components[$objectName] = $className;
}
$this->components[] = $component;
$component->onAddComponent($this);
return $this;
}
/**
* @param string $componentClass
* @param string $method
* @param callable|null $callable
* @param mixed $parameters,... Parameters for the method to be invoked
* @return DeferredComponentClass
*/
public function deferComponentClassMethod(string $componentClass, string $method, callable $callable = null)
{
// Retrieve arguments
$arguments = (func_num_args() > 3 ? array_slice(func_get_args(), 3) : []);
// Add component
if (!isset($this->deferredComponentClassMethods[$componentClass]))
$this->deferredComponentClassMethods[$componentClass] = [];
$deferredComponentClass = new DeferredComponentClass($componentClass, $method, $arguments, $callable);
return $this->deferredComponentClassMethods[$componentClass][] = $deferredComponentClass;
}
/* ---------------- Other Features ---------------------- */
/**
* Override a config value before FuzeWorks is loaded.
*
* Allows the user to change any value in config files loaded by FuzeWorks.
*
* @param string $configFileName
* @param string $configKey
* @param $configValue
* @return Configurator
*/
public function setConfigOverride(string $configFileName, string $configKey, $configValue): Configurator
{
Config::overrideConfig($configFileName, $configKey, $configValue);
return $this;
}
/**
* Sets the default timezone.
* @param string $timezone
@ -290,12 +332,57 @@ class Configurator
// Then load the framework
$container = Core::init();
// Add all components
foreach ($this->components as $componentName => $componentClass) {
if (!class_exists($componentClass))
throw new ConfiguratorException("Could not load component '".$componentName."'. Class '".$componentClass."' does not exist.", 1);
// Invoke deferredComponentClass on FuzeWorks\Core classes
foreach ($this->deferredComponentClassMethods as $componentClass => $deferredComponentClasses)
{
// @todo Verify if system works
if ($container->instanceIsset($componentClass))
{
// @codeCoverageIgnoreStart
foreach ($deferredComponentClasses as $deferredComponentClass)
{
$container->setInstance($componentName, new $componentClass());
$deferredComponentClass->invoke(call_user_func_array(
array($container->{$deferredComponentClass->componentClass}),
$deferredComponentClass->arguments
));
}
// @codeCoverageIgnoreEnd
}
}
// Add all components
foreach ($this->components as $component)
{
foreach ($component->getClasses() as $componentName => $componentClass)
{
if (is_object($componentClass))
{
$container->setInstance($componentName, $componentClass);
}
else
{
if (!class_exists($componentClass))
throw new ConfiguratorException("Could not load component '".$componentName."'. Class '".$componentClass."' does not exist.", 1);
$container->setInstance($componentName, new $componentClass());
}
// Invoke deferredComponentClass
if (isset($this->deferredComponentClassMethods[$componentName]))
{
$dfcm = $this->deferredComponentClassMethods[$componentName];
foreach ($dfcm as $deferredComponentClass)
{
$deferredComponentClass->invoke(call_user_func_array(
array($container->{$deferredComponentClass->componentClass}, $deferredComponentClass->method),
$deferredComponentClass->arguments
));
}
}
}
$component->onCreateContainer($this);
}
// And add all directories to the components

View File

@ -0,0 +1,102 @@
<?php
/**
* FuzeWorks Framework Core.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2018 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2018, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 0.0.1
*
* @version Version 1.2.0
*/
namespace FuzeWorks;
class DeferredComponentClass
{
/**
* @var string Name of the class to be invoked
*/
public $componentClass;
/**
* @var string name of the method to be invoked
*/
public $method;
/**
* @var array arguments to invoke the method with
*/
public $arguments = [];
/**
* @var mixed return from the invoked method
*/
protected $return;
/**
* @var bool Whether the method has been invoked
*/
protected $invoked = false;
/**
* @var callable A callback to call when method has been invoked.
*/
protected $callback;
public function __construct(string $componentClass, string $method, array $arguments, callable $callback = null)
{
$this->componentClass = $componentClass;
$this->method = $method;
$this->arguments = $arguments;
$this->callback = $callback;
}
public function invoke($result)
{
$this->return = $result;
$this->invoked = true;
if (is_callable($this->callback))
call_user_func($this->callback, $result);
}
public function isInvoked(): bool
{
return $this->invoked;
}
public function getResult()
{
if ($this->invoked == true)
return $this->return;
else
return false;
}
}

View File

@ -78,27 +78,22 @@ class Events
/**
* Adds a function as listener.
*
* @param mixed $callback The callback when the events get fired, see {@link http://php.net/manual/en/language.types.callable.php PHP.net}
* @param string $eventName The name of the event
* @param int $priority The priority, even though integers are valid, please use EventPriority (for example EventPriority::Lowest)
* @param mixed $parameters,... Parameters for the listener
* @param callable $callback The callback when the events get fired, see {@link http://php.net/manual/en/language.types.callable.php PHP.net}
* @param string $eventName The name of the event
* @param int $priority The priority, even though integers are valid, please use EventPriority (for example EventPriority::Lowest)
* @param mixed $parameters,... Parameters for the listener
*
* @see EventPriority
*
* @throws EventException
*/
public static function addListener($callback, $eventName, $priority = EventPriority::NORMAL)
public static function addListener(callable $callback, string $eventName, int $priority = EventPriority::NORMAL)
{
// Perform multiple checks
if (EventPriority::getPriority($priority) == false) {
throw new EventException('Can not add listener: Unknown priority '.$priority, 1);
}
if (!is_callable($callback))
{
throw new EventException("Can not add listener: Callback is not callable", 1);
}
if (empty($eventName))
{
throw new EventException("Can not add listener: No eventname provided", 1);
@ -134,7 +129,7 @@ class Events
*
* @throws EventException
*/
public static function removeListener($callback, $eventName, $priority = EventPriority::NORMAL)
public static function removeListener(callable $callback, string $eventName, $priority = EventPriority::NORMAL)
{
if (EventPriority::getPriority($priority) == false) {
throw new EventException('Unknown priority '.$priority);

View File

@ -35,6 +35,7 @@
*/
namespace FuzeWorks;
use FuzeWorks\Exception\CoreException;
use FuzeWorks\Exception\FactoryException;
/**
@ -145,13 +146,17 @@ class Factory
/**
* Finalizes the Factory and sends out a coreStartEvent
*
* @throws Exception\EventException
* @return Factory
* @throws CoreException
*/
public function init()
{
// Load the config file of the FuzeWorks core
$cfg = $this->config->get('core');
try {
$cfg = $this->config->get('core');
} catch (Exception\ConfigException $e) {
throw new CoreException("Could not initiate Factory. Config 'core 'could not be found.");
}
// Disable events if requested to do so
if (!$cfg->enable_events)
@ -170,7 +175,11 @@ class Factory
$this->plugins->loadHeadersFromPluginPaths();
// And fire the coreStartEvent
Events::fireEvent('coreStartEvent');
try {
Events::fireEvent('coreStartEvent');
} catch (Exception\EventException $e) {
throw new CoreException("Could not initiate Factory. coreStartEvent threw exception: ".$e->getMessage());
}
return $this;
}

View File

@ -41,4 +41,6 @@ namespace FuzeWorks;
interface iComponent
{
public function getClasses(): array;
public function onAddComponent(Configurator $configurator): Configurator;
public function onCreateContainer(Configurator $configurator): Configurator;
}

View File

@ -34,6 +34,7 @@
* @version Version 1.2.0
*/
namespace FuzeWorks\Component;
use FuzeWorks\Configurator;
use FuzeWorks\iComponent;
class TestComponent implements iComponent
@ -43,6 +44,16 @@ class TestComponent implements iComponent
{
return ['test' => 'FuzeWorks\Component\Test'];
}
public function onAddComponent(Configurator $configurator): Configurator
{
return $configurator;
}
public function onCreateContainer(Configurator $configurator): Configurator
{
return $configurator;
}
}
class Test

View File

@ -34,6 +34,7 @@
* @version Version 1.2.0
*/
namespace FuzeWorks\Component;
use FuzeWorks\Configurator;
use FuzeWorks\iComponent;
class TestAddComponentDirectoryComponent implements iComponent
@ -43,6 +44,16 @@ class TestAddComponentDirectoryComponent implements iComponent
{
return ['testaddcomponentdirectory' => 'FuzeWorks\Component\TestAddComponentDirectory'];
}
public function onAddComponent(Configurator $configurator): Configurator
{
return $configurator;
}
public function onCreateContainer(Configurator $configurator): Configurator
{
return $configurator;
}
}
class TestAddComponentDirectory

View File

@ -34,6 +34,7 @@
* @version Version 1.2.0
*/
namespace FuzeWorks\Component;
use FuzeWorks\Configurator;
use FuzeWorks\iComponent;
class TestAddComponentFailComponent implements iComponent
@ -43,6 +44,16 @@ class TestAddComponentFailComponent implements iComponent
{
return ['test' => 'FuzeWorks\Component\TestAddComponentNotExist'];
}
public function onAddComponent(Configurator $configurator): Configurator
{
return $configurator;
}
public function onCreateContainer(Configurator $configurator): Configurator
{
return $configurator;
}
}
class TestAddComponentFail

View File

@ -0,0 +1,38 @@
<?php
/**
* FuzeWorks Framework Core.
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2018 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2018, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.2.0
*
* @version Version 1.2.0
*/
return array(
'initial' => 'value'
);

View File

@ -35,9 +35,9 @@
*/
use PHPUnit\Framework\TestCase;
use FuzeWorks\Events;
use FuzeWorks\Layout;
use FuzeWorks\Factory;
use FuzeWorks\Core;
use FuzeWorks\Config;
use FuzeWorks\LoggerTracyBridge;
/**
@ -57,7 +57,7 @@ abstract class CoreTestAbstract extends TestCase
// Clear all events created by tests
Events::$listeners = array();
// Re-register the LoggerTracyBridge to supress errors
// Re-register the LoggerTracyBridge to suppress errors
LoggerTracyBridge::register();
// Reset all config files
@ -68,5 +68,8 @@ abstract class CoreTestAbstract extends TestCase
// Reset the HTTP status code
Core::$http_status_code = 200;
// Remove Config overrides
Config::$configOverrides = [];
}
}

View File

@ -111,6 +111,25 @@ class configTest extends CoreTestAbstract
$this->assertEquals('exists', $config->it);
}
/**
* @depends testLoadConfig
* @covers \FuzeWorks\Config::overrideConfig
* @covers \FuzeWorks\Config::loadConfigFile
*/
public function testLoadConfigOverride()
{
// Load file without override
$this->assertEquals(['initial' => 'value'], $this->config->getConfig('testLoadConfigOverride', ['test'.DS.'config'.DS.'testLoadConfigOverride'])->toArray());
// Discard to reset test
$this->config->discardConfigFiles();
// Create override
Config::overrideConfig('testLoadConfigOverride', 'initial', 'different');
$this->assertEquals(['initial' => 'different'], $this->config->getConfig('testLoadConfigOverride', ['test'.DS.'config'.DS.'testLoadConfigOverride'])->toArray());
}
/**
* @expectedException FuzeWorks\Exception\ConfigException
*/

View File

@ -36,6 +36,7 @@
use FuzeWorks\Configurator;
use FuzeWorks\Core;
use FuzeWorks\iComponent;
use FuzeWorks\Logger;
/**
@ -95,6 +96,25 @@ class configuratorTest extends CoreTestAbstract
$this->assertEquals(5, $container->test->variable);
}
/**
* @depends testAddComponent
*/
public function testAddComponentClassByObject()
{
// Create object
$object = $this->getMockBuilder(MockComponentClass::class)->getMock();
$object->variable = 'value';
// Create and add component
$component = $this->getMockBuilder(MockComponent::class)->setMethods(['getClasses'])->getMock();
$component->method('getClasses')->willReturn(['componentobject' => $object]);
$this->assertInstanceOf('FuzeWorks\Configurator', $this->configurator->addComponent($component));
// Create container and test for variable
$container = $this->configurator->createContainer()->init();
$this->assertEquals('value', $container->componentobject->variable);
}
/**
* @depends testAddComponent
* @expectedException FuzeWorks\Exception\ConfiguratorException
@ -214,6 +234,69 @@ class configuratorTest extends CoreTestAbstract
$this->assertEquals($container->testaddcomponentdirectory->directories, [vfsStream::url('testAddComponentDirectory')]);
}
/* ---------------------------------- Deferred Invocation --------------------------------------- */
/**
* @depends testAddComponent
*/
public function testDeferComponentClassMethod()
{
// Create mocks
$componentClass = $this->getMockBuilder(MockComponentClass::class)->setMethods(['update'])->getMock();
$componentClass->expects($this->once())->method('update')->willReturn('result');
$component = $this->getMockBuilder(MockComponent::class)->setMethods(['getClasses'])->getMock();
$component->method('getClasses')->willReturn(['test' => $componentClass]);
// Add the Component
$this->configurator->addComponent($component);
// Defer method
$deferred = $this->configurator->deferComponentClassMethod('test', 'update');
// Expect false before execution
$this->assertFalse($deferred->isInvoked());
$this->assertFalse($deferred->getResult());
// Create container
$this->configurator->createContainer();
// Make assertions
$this->assertTrue($deferred->isInvoked());
$this->assertEquals('result', $deferred->getResult());
}
/**
* @depends testDeferComponentClassMethod
*/
public function testDeferComponentClassMethodWithCallback()
{
// Create mocks
$componentClass = $this->getMockBuilder(MockComponentClass::class)->setMethods(['update'])->getMock();
$componentClass->expects($this->once())->method('update')->with('some_argument')->willReturn('result');
$component = $this->getMockBuilder(MockComponent::class)->setMethods(['getClasses'])->getMock();
$component->method('getClasses')->willReturn(['test' => $componentClass]);
// Add the Component
$this->configurator->addComponent($component);
// Defer method
$deferred = $this->configurator->deferComponentClassMethod(
'test',
'update',
function($result){
$this->assertEquals('result', $result);
},
'some_argument'
);
// Create container
$this->configurator->createContainer();
// Make assertions
$this->assertTrue($deferred->isInvoked());
$this->assertEquals('result', $deferred->getResult());
}
/* ---------------------------------- Parameters ------------------------------------------------ */
/**
@ -250,6 +333,18 @@ class configuratorTest extends CoreTestAbstract
$this->assertEquals('fake_directory', Core::$tempDir);
}
public function testSetConfigOverride()
{
// Set an override that can be verified
$this->configurator->setConfigOverride('test', 'somekey', 'somevalue');
// Create container
$this->configurator->createContainer()->init();
// Verify that the variable is set in the Config class
$this->assertEquals(['test' => ['somekey' => 'somevalue']], \FuzeWorks\Config::$configOverrides);
}
/* ---------------------------------- Debugging ------------------------------------------------- */
/**
@ -362,4 +457,27 @@ class configuratorTest extends CoreTestAbstract
$this->configurator->createContainer()->init();
$this->assertEquals('test@email.com', \Tracy\Debugger::$email);
}
}
class MockComponent implements iComponent
{
public function getClasses(): array
{
}
public function onAddComponent(Configurator $configurator): Configurator
{
return $configurator;
}
public function onCreateContainer(Configurator $configurator): Configurator
{
return $configurator;
}
}
class MockComponentClass
{
//public function update(){}
}

View File

@ -172,16 +172,7 @@ class eventsTest extends CoreTestAbstract
*/
public function testAddInvalidPriorityListener()
{
Events::addListener('fakeCallable', 'mockEvent', 99);
}
/**
* @depends testAddAndRemoveListener
* @expectedException FuzeWorks\Exception\EventException
*/
public function testAddInvalidCallableListener()
{
Events::addListener(array('nonExistingClass', 'nonExistingMethod'), 'mockEvent', EventPriority::NORMAL);
Events::addListener(function($event){}, 'mockEvent', 99);
}
/**
@ -199,7 +190,7 @@ class eventsTest extends CoreTestAbstract
*/
public function testRemoveInvalidPriorityListener()
{
Events::removeListener('fakeCallable', 'mockEvent', 99);
Events::removeListener(function($event){}, 'mockEvent', 99);
}
/**
@ -207,7 +198,7 @@ class eventsTest extends CoreTestAbstract
*/
public function testRemoveUnsetEventListener()
{
$this->assertNull(Events::removeListener('fakeCallable', 'emptyListenerArray', EventPriority::NORMAL));
$this->assertNull(Events::removeListener(function($event){}, 'emptyListenerArray', EventPriority::NORMAL));
}
/**