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: 4 months 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());
    }
}