3
\$\begingroup\$

This question is connected to the previous one: Request handler based solely on reflections

I have edited the code, taking tim's answer in regard, editing the Kernel class cohesion.

<?php

namespace Qobox;

class Kernel
{
    private $request;
    private $module;

    /**
     * Kernel constructor.
     */
    public function __construct()
    {
        $this->request = $this->getRequest();
        $requestedModuleName = $this->getModule();
        $moduleInstance = $this->getModuleInstance($requestedModuleName);
        $this->setModuleProperties($moduleInstance);
        $moduleMethod = $this->getModuleMethod();
        $argumentArray = $this->setMethodArguments($moduleMethod);
        $moduleMethod->invokeArgs($moduleInstance, $argumentArray);
    }

    /**
     * Grab the Fully Qualified Module Name, including namespace
     *
     * @return string $FullyQualifiedModuleName
     */
    private function getModule()
    {
        $moduleList = require_once "Config/Modules.php";
        /*$moduleList = array(
            "Main" => array(),
            "Nord" => array("Main"),
        );*/
        $namespaceArray = $this->getModuleNamespace($moduleList, array("\\Output"));
        return implode("\\", $namespaceArray);
    }

    /**
     * Get the requested module instance
     *
     * @param $requestedModuleName
     * @return object
     */
    private function getModuleInstance($requestedModuleName)
    {
        try {
            $this->module = new \ReflectionClass($requestedModuleName);
        } catch (\ReflectionException $e) {
            print_r($e);
            exit;
        }

        if ($this->module->isInstantiable() === TRUE) {
            return $this->module->newInstance();
        } else
        {
            // todo: proper user-friendly error handler
            echo 'Module is not instantiable';
            exit;
        }
    }

    /**
     * Get the requested method of the requested module
     *
     * @return mixed
     */
    private function getModuleMethod()
    {
        $moduleMethods = $this->module->getMethods(\ReflectionMethod::IS_PUBLIC);

        foreach ($moduleMethods as $methodKey => $method) {
            if ($method->name == "__construct") {
                unset($moduleMethods[$methodKey]);
            }
        }
        $moduleMethods = array_values($moduleMethods);

        if (!empty($this->request) && $this->module->hasMethod(array_keys($this->request)[0])) {
            // the method exists, load it
            $moduleMethod = $this->module->getMethod(array_shift($this->request));
        } else {
            $moduleMethod = $moduleMethods[0];
        }

        return $moduleMethod;
    }

    /**
     * Combines the full qualified namespace for requested module
     * Recursive function
     *
     * @param array $moduleList
     * @param array $namespaceArray
     * @return array
     */
    private function getModuleNamespace(Array $moduleList, Array $namespaceArray = array())
{   //print_r(is_array($moduleList[array_keys($moduleList)[0]]));exit;
    if (!empty($this->request) && (array_key_exists(array_keys($this->request)[0], $moduleList) || in_array(array_keys($this->request)[0], $moduleList))) {
        $loader = array_shift($this->request);
    } else if (is_array($moduleList[array_keys($moduleList)[0]])) {
        $loader = array_keys($moduleList)[0];
    } else {
        $loader = array_values($moduleList)[0];
    }

    $namespaceArray[] = $loader;

    if (array_key_exists($loader, $moduleList) && is_array($moduleList[$loader]) && !empty($moduleList[$loader])) {
        return $this->getModuleNamespace($moduleList[$loader], $namespaceArray);
    } else {
        return $namespaceArray;
    }
}

    /**
     * Get the module properties' instances
     * Recursive function
     *
     * @param array $propertyValue
     * @param String $moduleName
     * @return \stdClass
     */
    private function getModulePropertyInstance(Array $propertyValue, String $moduleName)
    {
        $propertyClass = new \stdClass();
        foreach ($propertyValue as $moduleCaller => $moduleSubname) {
            if (is_array($moduleSubname) === TRUE) {
                $propertyClass->$moduleCaller = $this->getModulePropertyInstance($moduleSubname, $moduleName . "\\" . $moduleCaller);
            } else {
                $propertyClass->$moduleSubname = $this->getSubmodule($moduleName . "\\" . $moduleSubname);
            }
        }

        return $propertyClass;
    }

    /**
     * Get the parsed _$REQUEST array
     *
     * @return array
     */
    private function getRequest()
    {
        $request = array();
        foreach ($_REQUEST as $key => $value) {
            $key = htmlspecialchars($key, ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5, "UTF-8");
            $value = htmlspecialchars($value, ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5, "UTF-8");

            if (empty($value)) {
                $request[$key] = $key;
            } else {
                $request[$key] = $value;
            }
        }

        return $request;
    }

    /**
     * Get submodule instance
     *
     * @param String $submoduleName
     * @return object
     */
    private function getSubmodule(String $submoduleName)
    {
        $namespacePrefixList = array("\\Qobox\\", "\\Modules\\", "\\Output\\");
        foreach ($namespacePrefixList as $namespacePrefix) {
            $submoduleNamespace = $namespacePrefix . $submoduleName;

            try {
                $submodule = new \ReflectionClass($submoduleNamespace);
                break;
            } catch (\ReflectionException $e) {
            }
        }

        $submoduleInstance = $submodule->newInstance();
        $submoduleProperties = $submodule->getProperties(\ReflectionProperty::IS_PROTECTED);

        foreach ($submoduleProperties as $submoduleProperty) {
            $submoduleProperty->setAccessible(true);
            if (empty($submoduleProperty->getValue($submoduleInstance))) {
                $submoduleName = $submoduleProperty->name;
                $submoduleProperty->setValue($submoduleInstance, $this->getSubmodule($submoduleName));
            }
            $submoduleProperty->setAccessible(false);
        }
        return $submoduleInstance;
    }

    /**
     * Set requested module arguments
     *
     * @param $moduleMethod
     * @return array
     */
    private function setMethodArguments($moduleMethod)
    {
        $argumentArray = array();
        $moduleMethodArguments = $moduleMethod->getParameters();
        foreach ($moduleMethodArguments as $Parameter) {
            if (isset($request[$Parameter->name]) && !empty($request[$Parameter->name]) && $request[$Parameter->name] != $Parameter->name) {
                $argumentArray[$Parameter->name] = $request[$Parameter->name];
            } else {
                if ($Parameter->isDefaultValueAvailable() === TRUE) {
                    $argumentArray[$Parameter->name] = $Parameter->getDefaultValue();
                } else {
                    $argumentArray[$Parameter->name] = "";
                }
            }
        }

        return $argumentArray;
    }

    /**
     * Set requested module properties
     *
     * @param $moduleInstance
     */
    private function setModuleProperties($moduleInstance)
    {
        $moduleProperties = $this->module->getProperties();
        foreach ($moduleProperties as $property)
        {
            $moduleName = $property->name;
            $property->setAccessible(true);
            $propertyValue = $property->getValue($moduleInstance);

            if (is_array($propertyValue) === TRUE)
            {
                $propertyClass = $this->getModulePropertyInstance($propertyValue, $moduleName);
                $property->setValue($moduleInstance, $propertyClass);
            } else
            {
                $property->setValue($moduleInstance, $this->getSubmodule($moduleName . "\\" . $moduleName));
            }
        }
    }
}

I intend to leave input sanitation as it is, putting GET and POST variables indistinguishably in one variable, only sanitizing each with htmlspecialchars($key, ENT_QUOTES | ENT_DISALLOWED | ENT_HTML5, "UTF-8"). FILES and SERVER variables are left as it is and are dealt with in another (kernel module) class.

The routing whitelist is put as mock data in the comment in the code. So,

  • Quering for index.php?Main will load class /Output/Main/Main.php
  • Quering for index.php?Bananaz will load class /Output/Main/Main.php (because Main is loaded by default)

Sure, you could just say that all public methods are, well, public.

Well, yes, only public functions are available, the required logic is in private and protected functions. I don't think users might mess something here up.

An extended example

Output/Example/Example.php

<?php

namespace Output\Main;

class Main
{
    private $Test;
    private $Database;

    public function runExample($hello)
    {
        echo $hello. ', ';
        echo $this->Test->printWorld();
        echo '<br><pre>';
        print_r($this);
    }
}

Modules/Test/Test.php

<?php

namespace Modules\Test;

class Test
{
    public function printWorld()
    {
        return 'World!';
    }
}

Qobox/Database/Database.php

<?php

namespace Qobox\Database;

class Database {

    private $Connection;
    private $Statement;

    public function __construct() {
        include "Config/Database.php";

        try {
            $this->Connection = new \PDO($DatabaseConnect["DBEngine"].":host=".$DatabaseConnect["DBHost"].";dbname=".$DatabaseConnect["DBName"].";charset=utf8", $DatabaseConnect["DBUser"], $DatabaseConnect["DBPass"]);
            $this->Connection->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); 
            $this->Connection->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
        } catch(\PDOException $e) {
            echo "Connection to the database failed: ".$e->getMessage();
        }
    }
...
}

The output of calling index.php?Example&hello=Hello would be similar to this:

Hello, World
Output\Main Object
(
    [Database:Output\Main:private] => Qobox\Database\Database Object
        (
            [Connection:Qobox\Database\Database:private] => PDO Object
                (
                )

            [Statement:Qobox\Database\Database:private] => 
        )

    [Test:Output\Main:private] => Modules\Test\Test Object
        (
        )
)
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.