diff --git a/classes/ClassRepository.php b/classes/ClassRepository.php index 37d50fd..8acc860 100644 --- a/classes/ClassRepository.php +++ b/classes/ClassRepository.php @@ -6,14 +6,18 @@ 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])) { @@ -21,12 +25,13 @@ private function loader($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')); @@ -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; } @@ -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. @@ -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("classMap[$vars['php_classname']] = $filename; } - return new $vars['php_classname'](); + + $container = $this->buildContainer($vars['php_classname'], $object); + + return new DI\SandboxedContainer($container); } } diff --git a/classes/DI/DependencyInjector.php b/classes/DI/DependencyInjector.php new file mode 100644 index 0000000..480fa28 --- /dev/null +++ b/classes/DI/DependencyInjector.php @@ -0,0 +1,91 @@ +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; + } +} diff --git a/classes/DI/SandboxedContainer.php b/classes/DI/SandboxedContainer.php new file mode 100644 index 0000000..8126936 --- /dev/null +++ b/classes/DI/SandboxedContainer.php @@ -0,0 +1,31 @@ +container = $container; + } + + public function has($id) { + return $this->container->has($id); + } + + public function get($id) { + return $this->container->get($id); + } + +} diff --git a/classes/DI/WhitelistReflectionBasedAutowiring.php b/classes/DI/WhitelistReflectionBasedAutowiring.php new file mode 100644 index 0000000..c24493d --- /dev/null +++ b/classes/DI/WhitelistReflectionBasedAutowiring.php @@ -0,0 +1,23 @@ +getProperty('coid://phpmae.cloudobjects.io/hasDependency'); - if (!isset($dependencies)) return; // no dependencies to process - if (!is_array($dependencies)) $dependencies = array($dependencies); - - $reader = new NodeReader([ - 'prefixes' => [ 'phpmae' => 'coid://phpmae.cloudobjects.io/' ] - ]); - - foreach ($dependencies as $d) { - 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!"); - $dependency = function() use ($value) { - return $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!"); - $dependency = function() use ($apiCoid, $retrieverPool, $object) { - $namespaceCoid = COIDParser::getNamespaceCOID(new IRI($object->getId())); - $apiCoid = new IRI($apiCoid); - return APIClientFactory::createClient($retrieverPool->getBaseObjectRetriever()->get($apiCoid), - $retrieverPool->getObjectRetriever($apiCoid->getHost())->get($namespaceCoid)); - }; - } else - throw new PhpMAEException("<".$object->getId()."> has an invalid dependency: unknown type!"); - - $container[$reader->getFirstValueString($d, 'phpmae:hasKey')] = $dependency; - } - } -} diff --git a/classes/Engine.php b/classes/Engine.php index 8733b0b..13c7ced 100644 --- a/classes/Engine.php +++ b/classes/Engine.php @@ -16,6 +16,8 @@ class Engine { + const SKEY = '__self'; + private $objectRetriever; private $classRepository; private $errorHandler; @@ -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 @@ -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(); } @@ -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 { diff --git a/classes/JsonRPCTransport.php b/classes/JsonRPCTransport.php index 2d98257..9175030 100644 --- a/classes/JsonRPCTransport.php +++ b/classes/JsonRPCTransport.php @@ -21,7 +21,6 @@ public function reply($data) { ->write($data); } - /** * Get the response */ diff --git a/classes/ObjectRetrieverPool.php b/classes/ObjectRetrieverPool.php index 3cc7d3a..cf17930 100644 --- a/classes/ObjectRetrieverPool.php +++ b/classes/ObjectRetrieverPool.php @@ -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() { diff --git a/classes/TypeChecker.php b/classes/TypeChecker.php index 67b2867..017d97a 100644 --- a/classes/TypeChecker.php +++ b/classes/TypeChecker.php @@ -24,21 +24,7 @@ public static function isType(Node $object, $typeString) { public static function isClass(Node $object) { return self::isType($object, 'phpmae:Class') - || self::isController($object) - || self::isFunction($object) - || self::isProvider($object); - } - - public static function isController(Node $object) { - return self::isType($object, 'phpmae:ControllerClass'); - } - - public static function isFunction(Node $object) { - return self::isType($object, 'phpmae:FunctionClass'); - } - - public static function isProvider(Node $object) { - return self::isType($object, 'phpmae:ServiceProviderClass'); + || self::isType($object, 'phpmae:HTTPInvokableClass'); } } diff --git a/phpmae.php b/phpmae.php index a60c871..f1bcc45 100644 --- a/phpmae.php +++ b/phpmae.php @@ -16,6 +16,7 @@ $app->command(new ClassValidateCommand); $app->command(new ClassTestEnvCommand); +$app->command(new DependenciesAddClassCommand); $app->command(new DependenciesAddWebAPICommand); $app->command(new DependenciesAddStaticTextCommand); $app->command(new DependenciesAddAttachmentCommand);