Skip to content

Commit

Permalink
Implemented Dependency Injection architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
LukasRos committed Sep 29, 2018
1 parent f027c9b commit 6891b70
Show file tree
Hide file tree
Showing 10 changed files with 199 additions and 111 deletions.
63 changes: 43 additions & 20 deletions classes/ClassRepository.php
Expand Up @@ -6,27 +6,32 @@

namespace CloudObjects\PhpMAE;

use Psr\Container\ContainerInterface;
use ML\IRI\IRI, ML\JsonLD\Node;
use DI\Container;
use DI\Definition\Source\DefinitionArray, DI\Definition\Source\SourceChain;
use CloudObjects\SDK\ObjectRetriever, CloudObjects\SDK\COIDParser;
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;

class ClassRepository {

private $options;
private $classMap = array();
private $classMap = [];
private $container;

private function loader($classname) {
if (isset($this->classMap[$classname])) {
include $this->classMap[$classname];
}
}

public function __construct($options = array()) {
// Merge options with defaults
$this->options = array_merge(array(
'cache_dir' => '',
'uploads_dir' => '',
), $options);
public function __construct(ContainerInterface $container) {
$this->options = [
'cache_dir' => $container->get('cache_dir'),
'uploads_dir' => $container->get('uploads_dir')
];

$this->container = $container;

// Initialize autoloader
spl_autoload_register(array($this, 'loader'));
Expand All @@ -52,7 +57,9 @@ private function getURIVars(IRI $uri) {
public function getCustomFilesCachePath(Node $object) {
$path = $this->options['cache_dir'].DIRECTORY_SEPARATOR
.strtoupper(md5($object->getId())).DIRECTORY_SEPARATOR
.$object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue();
.($object->getProperty(ObjectRetriever::REVISION_PROPERTY)
? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue()
: 'LocalConfig');
if (!is_dir($path)) mkdir($path, 0777, true);
return $path;
}
Expand All @@ -75,8 +82,30 @@ public function storeUploadedFile(Node $object, $content) {
return true;
}

private function buildContainer($className, Node $object) {
$autowiring = new DI\WhitelistReflectionBasedAutowiring;

$sources = [
new DefinitionArray($this->container->get(DI\DependencyInjector::class)
->getDependencies($object), $autowiring),
new DefinitionArray([
Engine::SKEY => \DI\autowire($className)
], $autowiring),
$autowiring
];

$source = new SourceChain($sources);
$source->setMutableDefinitionSource(new DefinitionArray([], $autowiring));

// TODO: add compilation

return new Container($source);
}

/**
* Create an instance of a class.
* Create an instance of a class. Returns a container that includes the class itself as Engine::SKEY
* as well as all dependencies specified by the class.
*
* @param Node $object The object describing the class.
* @param ObjectRetriever $objectRetriever The repository, which is used to fetch the implementation.
* @param ErrorHandler $errorHandler An error handler; used to add information about the class for debugging.
Expand Down Expand Up @@ -114,16 +143,7 @@ public function createInstance(Node $object, ObjectRetriever $objectRetriever, E

// Run source code through validator to ensure sanity
$validator = new ClassValidator;

if (TypeChecker::isController($object))
// Validate as controller
$validator->validateAsController($sourceCode);
elseif (TypeChecker::isFunction($object))
// Validate as function
$validator->validateAsFunction($sourceCode);
elseif (TypeChecker::isProvider($object))
// Validate as provider
$validator->validateAsProvider($sourceCode);
$validator->validate($sourceCode);

// Add namespace declaration
$sourceCode = str_replace("<?php", "<?php namespace ".$vars['php_namespace'].";", $sourceCode);
Expand All @@ -136,7 +156,10 @@ public function createInstance(Node $object, ObjectRetriever $objectRetriever, E

$this->classMap[$vars['php_classname']] = $filename;
}
return new $vars['php_classname']();

$container = $this->buildContainer($vars['php_classname'], $object);

return new DI\SandboxedContainer($container);
}

}
91 changes: 91 additions & 0 deletions classes/DI/DependencyInjector.php
@@ -0,0 +1,91 @@
<?php

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

namespace CloudObjects\PhpMAE\DI;

use ML\JsonLD\Node;
use ML\IRI\IRI;
use DI\ContainerBuilder;
use CloudObjects\SDK\ObjectRetriever, CloudObjects\SDK\NodeReader, CloudObjects\SDK\COIDParser;
use CloudObjects\SDK\WebAPI\APIClientFactory;
use CloudObjects\PhpMAE\ObjectRetrieverPool, CloudObjects\PhpMAE\ClassRepository, CloudObjects\PhpMAE\ErrorHandler, CloudObjects\PhpMAE\Engine;
use CloudObjects\PhpMAE\Exceptions\PhpMAEException;

/**
* The DependencyInjector returns all the dependencies specified for a PHP class.
*/
class DependencyInjector {

private $retrieverPool;
private $classRepository;

public function __construct(ObjectRetrieverPool $retrieverPool, ClassRepository $classRepository) {
$this->retrieverPool = $retrieverPool;
$this->classRepository = $classRepository;
}

/**
* Get all dependencies for injection.
*
* @param Node $object The object representing the PHP class.
*/
public function getDependencies(Node $object) {
$reader = new NodeReader([
'prefixes' => [ 'phpmae' => 'coid://phpmae.cloudobjects.io/' ]
]);

$dependencies = $reader->getAllValuesNode($object, 'phpmae:hasDependency');

$definitions = [];

foreach ($dependencies as $d) {
$keyedDependency = null;

if (!$d->getProperty('coid://phpmae.cloudobjects.io/hasKey'))
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: no key!");

if ($reader->hasType($d, 'phpmae:StaticTextDependency')) {
// Static Text Dependency
$value = $reader->getFirstValueString($d, 'phpmae:hasValue');
if (!isset($value))
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: StaticTextDependency without value!");

$keyedDependency = $value;
} else
if ($reader->hasType($d, 'phpmae:WebAPIDependency')) {
// Web API Dependency
$apiCoid = $reader->getFirstValueString($d, 'phpmae:hasAPI');
if (!isset($apiCoid))
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: WebAPIDependency without API!");

$keyedDependency = function() use ($apiCoid, $retrieverPool, $object) {
$namespaceCoid = COIDParser::getNamespaceCOID(new IRI($object->getId()));
$apiCoid = new IRI($apiCoid);
return APIClientFactory::createClient($this->retrieverPool->getBaseObjectRetriever()->get($apiCoid),
$retrieverPool->getObjectRetriever($apiCoid->getHost())->get($namespaceCoid));
};
} else
if ($reader->hasType($d, 'phpmae:ClassDependency')) {
// Class Dependency
$classCoid = $reader->getFirstValueIRI($d, 'phpmae:hasClass');
if (!isset($classCoid))
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: ClassDependency without class!");

$classRepository = $this->classRepository;
$keyedDependency = function() use ($classRepository, $classCoid) {
$dependencyContainer = $classRepository->createInstance($this->retrieverPool->getBaseObjectRetriever()->getObject($classCoid),
$this->retrieverPool->getBaseObjectRetriever(), new ErrorHandler);
return $dependencyContainer->get(Engine::SKEY);
};
} else
throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: unknown type!");

$definitions[$reader->getFirstValueString($d, 'phpmae:hasKey')] = $keyedDependency;
}

return $definitions;
}
}
31 changes: 31 additions & 0 deletions classes/DI/SandboxedContainer.php
@@ -0,0 +1,31 @@
<?php

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

namespace CloudObjects\PhpMAE\DI;

use Psr\Container\ContainerInterface;

/**
* The SandboxedContainer is a proxy class for a PSR-11 container that
* ensures that only interface methods are available.
*/
class SandboxedContainer implements ContainerInterface {

private $container;

public function __construct(ContainerInterface $container) {
$this->container = $container;
}

public function has($id) {
return $this->container->has($id);
}

public function get($id) {
return $this->container->get($id);
}

}
23 changes: 23 additions & 0 deletions classes/DI/WhitelistReflectionBasedAutowiring.php
@@ -0,0 +1,23 @@
<?php

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

namespace CloudObjects\PhpMAE\DI;

use DI\Definition\ObjectDefinition;
use DI\Definition\Source\DefinitionSource, DI\Definition\Source\Autowiring,
DI\Definition\Source\ReflectionBasedAutowiring;

/**
* This is an extension of ReflectionBasedAutowiring that only allows autowiring
* for classes that have a definition. Used for sandboxing containers.
*/
class WhitelistReflectionBasedAutowiring extends ReflectionBasedAutowiring implements DefinitionSource, Autowiring {

public function autowire(string $name, ObjectDefinition $definition = null) {
return isset($definition) ? parent::autowire($name, $definition) : null;
}

}
68 changes: 0 additions & 68 deletions classes/DependencyInjector.php

This file was deleted.

10 changes: 6 additions & 4 deletions classes/Engine.php
Expand Up @@ -16,6 +16,8 @@

class Engine {

const SKEY = '__self';

private $objectRetriever;
private $classRepository;
private $errorHandler;
Expand All @@ -41,7 +43,7 @@ private function executeInvokableClass($runClass, RequestInterface $request) {
if (!is_array($input))
$input = [];

$result = $runClass->__invoke($input);
$result = $runClass->get(self::SKEY)->__invoke($input);

if (!isset($result))
// Empty response
Expand All @@ -62,9 +64,9 @@ private function executeInvokableClass($runClass, RequestInterface $request) {
/**
* Executes a standard class using JSON-RPC.
*/
private function executeJsonRPC($runClass, RequestInterface $request) {
private function executeJsonRPC(SandboxedContainer $runClass, RequestInterface $request) {
$transport = new JsonRPCTransport;
$server = new JsonRPC($runClass, $transport);
$server = new JsonRPC($runClass->get(self::SKEY), $transport);
$server->receive((string)$request->getBody());
return $transport->getResponse();
}
Expand All @@ -87,7 +89,7 @@ public function execute(RequestInterface $request) {
if (!isset($object))
throw new PhpMAEException("The object <" . (string)$coid . "> does not exist or this phpMAE instance is not allowed to access it.");
$runClass = $this->classRepository->createInstance($object, $this->objectRetriever, $this->errorHandler);
if (ClassValidator::isInvokableClass($runClass)) {
if (ClassValidator::isInvokableClass($runClass->get(self::SKEY))) {
// Run as invokable class
return $this->executeInvokableClass($runClass, $request);
} else {
Expand Down
1 change: 0 additions & 1 deletion classes/JsonRPCTransport.php
Expand Up @@ -21,7 +21,6 @@ public function reply($data) {
->write($data);
}


/**
* Get the response
*/
Expand Down
6 changes: 3 additions & 3 deletions classes/ObjectRetrieverPool.php
Expand Up @@ -16,11 +16,11 @@ class ObjectRetrieverPool {
private $options;
private $objectRetrievers = [];

public function __construct(ObjectRetriever $baseObjectRetriever, $baseHostname, $options) {
public function __construct(ObjectRetriever $baseObjectRetriever) { // TODO: add $baseHostname, $options) {
$this->baseObjectRetriever = $baseObjectRetriever;
$this->baseHostname = $baseHostname;
/*$this->baseHostname = $baseHostname;
$this->objectRetrievers[$baseHostname] = $baseObjectRetriever;
$this->options = $options;
$this->options = $options; */
}

public function getBaseObjectRetriever() {
Expand Down

0 comments on commit 6891b70

Please sign in to comment.