PHP Classes

File: src/Di/Traits/ComponentTrait.php

Recommend this page to a friend!
  Packages of Thierry Feuzeu   Jaxon   src/Di/Traits/ComponentTrait.php   Download  
File: src/Di/Traits/ComponentTrait.php
Role: Class source
Content type: text/plain
Description: Class source
Class: Jaxon
Call PHP classes from JavaScript using AJAX
Author: By
Last change:
Date: 1 year ago
Size: 9,064 bytes
 

Contents

Class file image Download
<?php

/**
 * ComponentTrait.php
 *
 * Functions for the component container.
 *
 * @package jaxon-core
 * @author Thierry Feuzeu <thierry.feuzeu@gmail.com>
 * @copyright 2024 Thierry Feuzeu <thierry.feuzeu@gmail.com>
 * @license https://opensource.org/licenses/BSD-3-Clause BSD 3-Clause License
 * @link https://github.com/jaxon-php/jaxon-core
 */

namespace Jaxon\Di\Traits;

use Jaxon\Di\Container;
use Jaxon\App\FuncComponent;
use Jaxon\App\NodeComponent;
use Jaxon\App\I18n\Translator;
use Jaxon\App\Metadata\InputData;
use Jaxon\App\Metadata\Metadata;
use Jaxon\Config\Config;
use Jaxon\Exception\SetupException;
use Jaxon\Plugin\Request\CallableClass\CallableObject;
use Jaxon\Plugin\Request\CallableClass\ComponentOptions;
use Jaxon\Plugin\Request\CallableClass\ComponentRegistry;
use ReflectionClass;
use ReflectionMethod;
use ReflectionProperty;

use function array_filter;
use function array_map;
use function in_array;
use function str_replace;
use function substr;

trait ComponentTrait
{
    /**
     * The classes, both registered and found in registered directories.
     *
     * @var array
     */
    protected $aComponents = [];

    /**
     * The container for parameters
     *
     * @return Container
     */
    abstract protected function cn(): Container;

    /**
     * @param class-string $sClassName
     *
     * @return CallableObject|null
     * @throws SetupException
     */
    abstract public function makeCallableObject(string $sClassName): ?CallableObject;

    /**
     * @param class-string $sClassName    The class name
     * @param array $aOptions    The class options
     *
     * @return void
     */
    abstract public function saveComponent(string $sClassName, array $aOptions): void;

    /**
     * @param class-string $sClassName The component name
     *
     * @return string
     */
    private function getCallableObjectKey(string $sClassName): string
    {
        return "{$sClassName}_CallableObject";
    }

    /**
     * @param class-string $sClassName The component name
     *
     * @return string
     */
    private function getCallableHelperKey(string $sClassName): string
    {
        return "{$sClassName}_CallableHelper";
    }

    /**
     * @param class-string $sClassName The component name
     *
     * @return string
     */
    private function getReflectionClassKey(string $sClassName): string
    {
        return "{$sClassName}_ReflectionClass";
    }

    /**
     * @param class-string $sClassName The component name
     *
     * @return string
     */
    private function getRequestFactoryKey(string $sClassName): string
    {
        return "{$sClassName}_RequestFactory";
    }

    /**
     * @param string $sClassName
     * @param array $aOptions
     *
     * @return void
     */
    private function _saveClassOptions(string $sClassName, array $aOptions): void
    {
        $sOptionsId = str_replace('\\', $aOptions['separator'], $sClassName);
        $this->aComponents[$sOptionsId] = $aOptions;
    }

    /**
     * @param string $sClassName
     *
     * @return array
     */
    private function _getClassOptions(string $sClassName): array
    {
        return $this->aComponents[str_replace('\\', '.', $sClassName)] ??
            $this->aComponents[str_replace('\\', '_', $sClassName)];
    }

    /**
     * Find a component amongst the registered namespaces and directories.
     *
     * @param class-string $sClassName The class name
     *
     * @return void
     * @throws SetupException
     */
    private function discoverComponent(string $sClassName): void
    {
        $xRegistry = $this->cn()->g(ComponentRegistry::class);
        $xRegistry->updateHash(false); // Disable hash calculation.

        $sComponentId = str_replace('\\', '.', $sClassName);
        if(!isset($this->aComponents[$sComponentId]))
        {
            $aOptions = $xRegistry->getNamespaceComponentOptions($sClassName);
            if($aOptions !== null)
            {
                $this->saveComponent($sClassName, $aOptions);
            }
        }
        if(isset($this->aComponents[$sComponentId]))
        {
            return; // The component is found.
        }

        // The component was not found in a registered namespace. We need to parse all
        // the directories to be able to find a component registered without a namespace.
        $sComponentId = str_replace('\\', '_', $sClassName);
        if(!isset($this->aComponents[$sComponentId]))
        {
            $xRegistry->registerComponentsInDirectories();
        }
        if(isset($this->aComponents[$sComponentId]))
        {
            return; // The component is found.
        }

        throw new SetupException($this->cn()->g(Translator::class)
            ->trans('errors.class.invalid', ['name' => $sClassName]));
    }

    /**
     * Get callable objects for known classes
     *
     * @return array
     * @throws SetupException
     */
    public function getCallableObjects(): array
    {
        $aCallableObjects = [];
        foreach($this->aComponents as $sComponentId => $_)
        {
            $aCallableObjects[$sComponentId] = $this->makeCallableObject($sComponentId);
        }
        return $aCallableObjects;
    }

    /**
     * @param ReflectionClass $xReflectionClass
     * @param string $sMethodName
     *
     * @return bool
     */
    private function isNotCallable(ReflectionClass $xReflectionClass, string $sMethodName): bool
    {
        // Don't take the magic __call, __construct, __destruct methods,
        // and the public methods of the Component base classes.
        return substr($sMethodName, 0, 2) === '__' ||
            ($xReflectionClass->isSubclassOf(NodeComponent::class) &&
            in_array($sMethodName, ['item', 'html'])) ||
            ($xReflectionClass->isSubclassOf(FuncComponent::class) &&
            in_array($sMethodName, ['paginator']));
    }

    /**
     * Get the public methods of the callable object
     *
     * @param ReflectionClass $xReflectionClass
     *
     * @return array
     */
    public function getPublicMethods(ReflectionClass $xReflectionClass): array
    {
        $aMethods = array_map(fn($xMethod) => $xMethod->getShortName(),
            $xReflectionClass->getMethods(ReflectionMethod::IS_PUBLIC));

        return array_filter($aMethods, fn($sMethodName) =>
            !$this->isNotCallable($xReflectionClass, $sMethodName));
    }

    /**
     * @param ReflectionClass $xReflectionClass
     * @param array $aOptions
     *
     * @return Metadata|null
     */
    private function getComponentMetadata(ReflectionClass $xReflectionClass,
        array $aOptions): ?Metadata
    {
        /** @var Config|null */
        $xPackageConfig = $aOptions['config'] ?? null;
        if($xPackageConfig === null || (bool)($aOptions['excluded'] ?? false))
        {
            return null;
        }
        $sMetadataFormat = $xPackageConfig->getOption('metadata.format');
        if(!in_array($sMetadataFormat, ['attributes', 'annotations']))
        {
            return null;
        }

        // Try to get the class metadata from the cache.
        $di = $this->cn();
        $xMetadata = null;
        $xMetadataCache = null;
        $xConfig = $di->config();
        if($xConfig->getAppOption('metadata.cache.enabled', false))
        {
            if(!$di->h('jaxon_metadata_cache_dir'))
            {
                $sCacheDir = $xConfig->getAppOption('metadata.cache.dir');
                $di->val('jaxon_metadata_cache_dir', $sCacheDir);
            }
            $xMetadataCache = $di->getMetadataCache();
            $xMetadata = $xMetadataCache->read($xReflectionClass->getName());
            if($xMetadata !== null)
            {
                return $xMetadata;
            }
        }

        $aProperties = array_map(fn($xProperty) => $xProperty->getName(),
            $xReflectionClass->getProperties(ReflectionProperty::IS_PUBLIC |
                ReflectionProperty::IS_PROTECTED));
        $aMethods = $this->getPublicMethods($xReflectionClass);

        $xMetadataReader = $di->getMetadataReader($sMetadataFormat);
        $xInput = new InputData($xReflectionClass, $aMethods, $aProperties);
        $xMetadata = $xMetadataReader->getAttributes($xInput);

        // Try to save the metadata in the cache
        if($xMetadataCache !== null && $xMetadata !== null)
        {
            $xMetadataCache->save($xReflectionClass->getName(), $xMetadata);
        }

        return $xMetadata;
    }

    /**
     * @param ReflectionClass $xReflectionClass
     * @param array $aOptions
     *
     * @return ComponentOptions
     */
    private function getComponentOptions(ReflectionClass $xReflectionClass,
        array $aOptions): ComponentOptions
    {
        $xMetadata = $this->getComponentMetadata($xReflectionClass, $aOptions);
        return !$xMetadata ? new ComponentOptions($aOptions) :
            new ComponentOptions($aOptions, $xMetadata->isExcluded(),
            $xMetadata->getProtectedMethods(), $xMetadata->getProperties());
    }
}