classMap[$classname])) { include $this->classMap[$classname]; } } public function __construct(ContainerInterface $container) { $this->options = [ 'cache_dir' => $container->get('cache_dir') . '/classes', 'uploads_dir' => $container->get('uploads_dir'), 'uploads' => $container->get('uploads') ]; $this->container = $container; $this->reader = new NodeReader([ 'prefixes' => [ 'phpmae' => 'coid://phpmae.dev/' ] ]); // Initialize autoloader spl_autoload_register(array($this, 'loader')); } private function getURIVars(IRI $uri) { $vars = array('uri_hash' => strtoupper(md5((string)$uri))); $vars['php_namespace'] = "CloudObjects\\PhpMAE\\Class_".$vars['uri_hash']; $vars['php_classname_local'] = (COIDParser::getType($uri)==COIDParser::COID_VERSIONED) ? substr($uri->getPath(), 1, strrpos($uri->getPath(), '/')-1) : substr($uri->getPath(), 1); $vars['php_classname'] = $vars['php_namespace'].'\\'.$vars['php_classname_local']; $vars['cache_path'] = $this->options['cache_dir'].DIRECTORY_SEPARATOR.$vars['uri_hash']; $vars['upload_filename'] = $this->options['uploads_dir'].DIRECTORY_SEPARATOR.$vars['uri_hash'].'.php'; if ($this->options['uploads'] == true && !is_dir($this->options['uploads_dir'])) mkdir($this->options['uploads_dir'], 0777, true); return $vars; } public function coidToClassName(IRI $uri) { $vars = $this->getURIVars($uri); return $vars['php_classname']; } /** * Get a path on which custom files can be cached for a object. * @param Node $object */ public function getCustomFilesCachePath(Node $object) { $path = $this->options['cache_dir'].DIRECTORY_SEPARATOR .strtoupper(md5($object->getId())).DIRECTORY_SEPARATOR .($object->getProperty(ObjectRetriever::REVISION_PROPERTY) ? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue() : 'LocalConfig'); if (!is_dir($path)) mkdir($path, 0777, true); return $path; } public function storeUploadedFile(Node $object, $content) { $uri = new IRI($object->getId()); $vars = $this->getURIVars($uri); // Fetch class description $revision = $object->getProperty(ObjectRetriever::REVISION_PROPERTY) ? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue() : 'LocalConfig'; // Clear cache if cached version exists $cachedFilename = $vars['cache_path'].DIRECTORY_SEPARATOR.$revision.".php"; if (file_exists($cachedFilename)) unlink($cachedFilename); // Store source file_put_contents($vars['upload_filename'], $content); return true; } private function buildContainer($className, Node $object, $additionalDefinitions = []) { $autowiring = new DI\WhitelistReflectionBasedAutowiring; $sources = [ new DefinitionArray($this->container->get(DI\DependencyInjector::class) ->getDependencies($object, $additionalDefinitions), $autowiring), new DefinitionArray([ Engine::SKEY => \DI\autowire($className) ], $autowiring), $autowiring ]; $source = new SourceChain($sources); $source->setMutableDefinitionSource(new DefinitionArray([], $autowiring)); // TODO: add compilation $container = new Container($source); $sandboxedContainer = new DI\SandboxedContainer($container); $container->set(ContainerInterface::class, $sandboxedContainer); $container->set(Container::class, $sandboxedContainer); $container->set(FactoryInterface::class, $sandboxedContainer); $container->set(InvokerInterface::class, $sandboxedContainer); return $sandboxedContainer; } /** * 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 RequestInterface $request The optional request, if it should be made available * @param array $additionalDefinitions Definitions to add to the DI container for this class * @param string $sourceCode Optional source code to use for the class implementation instead of retrieving. */ public function createInstance(Node $object, RequestInterface $request = null, array $additionalDefinitions = [], string $sourceCode = null) { // Check type if (!TypeChecker::isClass($object)) throw new PhpMAEException("<".$object->getId()."> must have a valid type."); $uri = new IRI($object->getId()); $vars = $this->getURIVars($uri); if (!isset($this->classMap[$vars['php_classname']])) { $objectRetriever = $this->container->get(ObjectRetriever::class); // Get revision $revision = $object->getProperty(ObjectRetriever::REVISION_PROPERTY) ? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue() : 'LocalConfig'; // Build filename where cached version should exist $filename = $vars['cache_path'].DIRECTORY_SEPARATOR.$revision.".php"; $this->container->get(ErrorHandler::class) ->addMapping($filename, $object); // Check for required implemented interfaces $interfaces = []; foreach (TypeChecker::getAdditionalTypes($object) as $i) { $interfaceObject = $objectRetriever->get($i); if (!TypeChecker::isInterface($interfaceObject)) continue; $this->createInterfaceInstance($interfaceObject); $interfaces[] = $i; } // Load the stack that the class requires $stack = $this->reader->getFirstValueString($object, 'phpmae:usesStack', self::DEFAULT_STACK); $stackDir = __DIR__.'/../stacks/'.md5($stack); if (!is_dir($stackDir)) throw new PhpMAEException("The stack <".$stack."> is not installed on this phpMAE instance."); require_once $stackDir.'/vendor/autoload.php'; if (!file_exists($filename) || $sourceCode != null) { if (!isset($sourceCode)) { // File does not exist -> check in uploads first if (file_exists($vars['upload_filename'])) { $sourceCode = file_get_contents($vars['upload_filename']); } else { // Not found in local uploads -> download source from CloudObjects $sourceUrl = $object->getProperty('coid://phpmae.dev/hasSourceFile'); if (!$sourceUrl) throw new PhpMAEException("<".$object->getId()."> does not have an implementation source file."); if (get_class($sourceUrl)=='ML\JsonLD\Node') $sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getId()); else $sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getValue()); } } // Run source code through validator to ensure sanity // and convert to sandbox $validator = new ClassValidator; $sourceCode = $validator->validate($sourceCode, $uri, $interfaces); // Add namespaces for dependencies and interfaces $use = ''; $classList = array_merge( $this->container->get(DI\DependencyInjector::class)->getClassDependencyList($object), $interfaces ); foreach ($classList as $cl) $use .= " use ".$this->coidToClassName($cl).";"; // Add namespace declaration $sourceCode = "classMap[$vars['php_classname']] = $filename; } if (isset($request)) { $additionalDefinitions['request'] = $request; $additionalDefinitions[RequestInterface::class] = $request; } return $this->buildContainer($vars['php_classname'], $object, $additionalDefinitions); } /** * Creates an instance of an interface. * * @param Node $object The object describing the interface. */ public function createInterfaceInstance(Node $object) { // Check type if (!TypeChecker::isInterface($object)) throw new PhpMAEException("<".$object->getId()."> must have a valid type."); $uri = new IRI($object->getId()); $vars = $this->getURIVars($uri); if (!isset($this->classMap[$vars['php_classname']])) { $objectRetriever = $this->container->get(ObjectRetriever::class); // Get revision $revision = $object->getProperty(ObjectRetriever::REVISION_PROPERTY) ? $object->getProperty(ObjectRetriever::REVISION_PROPERTY)->getValue() : 'LocalConfig'; // Build filename where cached version should exist $filename = $vars['cache_path'].DIRECTORY_SEPARATOR.$revision.".php"; $this->container->get(ErrorHandler::class) ->addMapping($filename, $object); if (!file_exists($filename)) { // File does not exist -> check in uploads first if (file_exists($vars['upload_filename'])) { $sourceCode = file_get_contents($vars['upload_filename']); } else { // Not found in local uploads -> download source from CloudObjects $sourceUrl = $object->getProperty('coid://phpmae.dev/hasDefinitionFile'); if (!$sourceUrl) throw new PhpMAEException("<".$object->getId()."> does not have a definition file."); if (get_class($sourceUrl)=='ML\JsonLD\Node') $sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getId()); else $sourceCode = $objectRetriever->getAttachment($uri, $sourceUrl->getValue()); } // Run source code through validator to ensure sanity $validator = new ClassValidator; $validator->validateInterface($sourceCode, $uri); // Add namespace declaration $sourceCode = str_replace("classMap[$vars['php_classname']] = $filename; } } }