497 lines
11 KiB
PHP
497 lines
11 KiB
PHP
<?php
|
|
/**
|
|
* @package Joomla.Component.Builder
|
|
*
|
|
* @created 4th September, 2022
|
|
* @author Llewellyn van der Merwe <https://dev.vdm.io>
|
|
* @git Joomla Component Builder <https://git.vdm.dev/joomla/Component-Builder>
|
|
* @copyright Copyright (C) 2015 Vast Development Method. All rights reserved.
|
|
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
|
*/
|
|
|
|
namespace VDM\Joomla\Componentbuilder\Power\Generator;
|
|
|
|
|
|
use VDM\Joomla\Componentbuilder\Power\Database\Load as Database;
|
|
use VDM\Joomla\Componentbuilder\Compiler\Power\Parser;
|
|
use VDM\Joomla\Componentbuilder\Power\Generator\Bucket;
|
|
use VDM\Joomla\Utilities\String\ClassfunctionHelper;
|
|
|
|
|
|
/**
|
|
* Power code Generator Search of JCB
|
|
*
|
|
* @since 3.2.0
|
|
*/
|
|
final class Search
|
|
{
|
|
/**
|
|
* The Database Class
|
|
*
|
|
* @var Database
|
|
* @since 3.2.0
|
|
**/
|
|
protected Database $database;
|
|
|
|
/**
|
|
* The Code Parser Class
|
|
*
|
|
* @var Parser
|
|
* @since 3.2.0
|
|
**/
|
|
protected Parser $parser;
|
|
|
|
/**
|
|
* The found powers
|
|
*
|
|
* @var Bucket
|
|
* @since 3.2.0
|
|
**/
|
|
protected Bucket $bucket;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param Database $database The Database object.
|
|
* @param Parser $parser The parser object.
|
|
* @param Bucket $bucket The bucket object.
|
|
*
|
|
* @since 3.2.0
|
|
*/
|
|
public function __construct(Database $database, Parser $parser, Bucket $bucket)
|
|
{
|
|
$this->database = $database;
|
|
$this->parser = $parser;
|
|
$this->bucket = $bucket;
|
|
}
|
|
|
|
/**
|
|
* Get the power object
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
*
|
|
* @return object|null
|
|
* @since 3.2.0
|
|
*/
|
|
public function power(string $guid): ?object
|
|
{
|
|
if (($power = $this->bucket->get("power.{$guid}")) === null)
|
|
{
|
|
if (($power = $this->database->item(['guid' => $guid])) === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$this->bucket->set("power.{$guid}", $power);
|
|
}
|
|
|
|
return $power;
|
|
}
|
|
|
|
/**
|
|
* Get the power alias to use in container calls
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
* @param string $className The current class name
|
|
*
|
|
* @return string
|
|
* @since 3.2.0
|
|
*/
|
|
public function alias(string $guid, string $className): string
|
|
{
|
|
if (($alias = $this->bucket->get("alias.{$guid}")) !== null)
|
|
{
|
|
return $alias;
|
|
}
|
|
|
|
// load the service providers where its already linked
|
|
if (($service_providers = $this->serviceProviders($guid)) !== null)
|
|
{
|
|
foreach ($service_providers as $service_provider)
|
|
{
|
|
$dependency_name = $this->getServiceProviderDependencyName($service_provider, $guid) ?? $className;
|
|
|
|
if (($alias = $this->getAliasFromServiceProvider($service_provider, $dependency_name)) === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (empty($alias))
|
|
{
|
|
// build it based on the class name and namespace of this power
|
|
$alias = $this->getAliasFromPower($guid);
|
|
}
|
|
|
|
// finally we set the alias for later use
|
|
$alias = $alias ?? "Set.Me.$className";
|
|
$alias = trim($alias);
|
|
$this->bucket->set("alias.{$guid}", $alias);
|
|
|
|
return $alias;
|
|
}
|
|
|
|
/**
|
|
* Check if a power class is valid to inject into another class
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
*
|
|
* @return bool True if class can (should) be injected
|
|
* @since 3.2.0
|
|
*/
|
|
public function validInject(string $guid): bool
|
|
{
|
|
if (($valid_inject = $this->bucket->get("valid_inject.{$guid}")) !== null)
|
|
{
|
|
return $valid_inject;
|
|
}
|
|
|
|
if (($power = $this->power($guid)) === null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Types: [class, abstract class, final class, interface, trait]
|
|
// Allowed: [class, final class, interface]
|
|
if ($power->type === 'class' || $power->type === 'final class' || $power->type === 'interface')
|
|
{
|
|
$this->bucket->set("valid_inject.{$guid}", true);
|
|
|
|
return true;
|
|
}
|
|
|
|
$this->bucket->set("valid_inject.{$guid}", false);
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get the power class name
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
* @param string $as The use AS value
|
|
*
|
|
* @return string|null
|
|
* @since 3.2.0
|
|
*/
|
|
public function name(string $guid, string $as = 'default'): ?string
|
|
{
|
|
if ($as !== 'default')
|
|
{
|
|
return $as;
|
|
}
|
|
|
|
if (($name = $this->bucket->get("name.{$guid}")) !== null)
|
|
{
|
|
return $name;
|
|
}
|
|
|
|
if (($power = $this->power($guid)) === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (strpos($power->name, '[') !== false)
|
|
{
|
|
$name = 'DynamicClassName';
|
|
}
|
|
else
|
|
{
|
|
$name = ClassfunctionHelper::safe($power->name);
|
|
}
|
|
|
|
$name = trim($name);
|
|
$this->bucket->set("name.{$guid}", $name);
|
|
|
|
return $name;
|
|
}
|
|
|
|
/**
|
|
* Get the power class description
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
*
|
|
* @return string|null
|
|
* @since 3.2.0
|
|
*/
|
|
public function description(string $guid): ?string
|
|
{
|
|
if (($description = $this->bucket->get("description.{$guid}")) !== null)
|
|
{
|
|
return $description;
|
|
}
|
|
|
|
if (($power = $this->power($guid)) === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (strpos($power->name, '[') !== false)
|
|
{
|
|
$description = 'The Dynamic {$power->name} Name';
|
|
}
|
|
else
|
|
{
|
|
$description = "The {$power->name} Class.";
|
|
}
|
|
|
|
$this->bucket->set("description.{$guid}", $description);
|
|
|
|
return $description;
|
|
}
|
|
|
|
/**
|
|
* Get all service providers where this power is linked
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
*
|
|
* @return array|null
|
|
* @since 3.2.0
|
|
*/
|
|
public function serviceProviders(string $guid): ?array
|
|
{
|
|
if (($service_providers = $this->bucket->get("service_providers.{$guid}")) === null)
|
|
{
|
|
if (($powers = $this->database->items([
|
|
'use_selection' => [
|
|
'operator' => 'LIKE',
|
|
'value' => "'%{$guid}%'",
|
|
'quote' => false
|
|
],
|
|
'implements_custom' => [
|
|
'operator' => 'LIKE',
|
|
'value' => "'%ServiceProviderInterface%'",
|
|
'quote' => false
|
|
]
|
|
])) === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$service_providers = [];
|
|
foreach ($powers as $power)
|
|
{
|
|
$this->bucket->set("power.{$power->guid}", $power);
|
|
$service_providers[] = $power->guid;
|
|
}
|
|
|
|
$this->bucket->set("service_providers.{$guid}", $service_providers);
|
|
}
|
|
|
|
return $service_providers;
|
|
}
|
|
|
|
/**
|
|
* Get all the power dependencies
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
*
|
|
* @return array|null
|
|
* @since 3.2.0
|
|
*/
|
|
public function dependencies(string $guid): ?array
|
|
{
|
|
if (($dependencies = $this->bucket->get("dependencies.{$guid}")) !== null)
|
|
{
|
|
return $dependencies;
|
|
}
|
|
|
|
if (($power = $this->power($guid)) === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (empty($power->use_selection) || !is_object($power->use_selection))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$dependencies = [];
|
|
foreach ($power->use_selection as $use_selection)
|
|
{
|
|
if (!$this->validInject($use_selection->use))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (($name = $this->name($use_selection->use, $use_selection->as)) === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
$dependencies[] = $this->alias($use_selection->use, $name);
|
|
}
|
|
|
|
if ($dependencies === [])
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$this->bucket->set("dependencies.{$guid}", $dependencies);
|
|
|
|
return $dependencies;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the alias form linked service provider.
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
* @param string $className The current class name
|
|
*
|
|
* @return string|null Returns the alias if found, otherwise returns null.
|
|
* @since 3.2.0
|
|
*/
|
|
private function getAliasFromServiceProvider(string $guid, string $className): ?string
|
|
{
|
|
if (($power = $this->power($guid)) === null || empty($power->main_class_code))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$code = $this->parser->code($power->main_class_code);
|
|
|
|
if (empty($code['methods']))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
$method = null;
|
|
|
|
foreach ($code['methods'] as $_method)
|
|
{
|
|
if ($_method['name'] === 'register' && strlen($_method['body']) > 5)
|
|
{
|
|
$method = $_method['body'];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (empty($method))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return $this->getAliasFromRegisterMethod($method, $className);
|
|
}
|
|
|
|
/**
|
|
* Retrieves the alias for a given class from a provided string.
|
|
*
|
|
* @param string $content The string to search.
|
|
* @param string $className The name of the class whose alias is to be retrieved.
|
|
*
|
|
* @return string|null Returns the alias if found, otherwise returns null.
|
|
* @since 3.2.0
|
|
*/
|
|
private function getAliasFromRegisterMethod(string $content, string $className): ?string
|
|
{
|
|
|
|
// Escaping any special characters in the class name to use in regex
|
|
$escapedClassName = preg_quote($className, '/');
|
|
|
|
// Regular expression to match the pattern where class name and its alias are specified
|
|
$pattern = "/\\\$container->alias\s*\(\s*{$escapedClassName}::class\s*,\s*['\"](.*?)['\"]\s*\)\s*->/s";
|
|
|
|
if (preg_match($pattern, $content, $matches))
|
|
{
|
|
return $matches[1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Retrieves the alias form linked service provider.
|
|
*
|
|
* @param string $guid The global unique id of the power
|
|
*
|
|
* @return string|null Returns the alias if found, otherwise returns null.
|
|
* @since 3.2.0
|
|
*/
|
|
private function getAliasFromPower(string $guid): ?string
|
|
{
|
|
if (($power = $this->power($guid)) === null || empty($power->namespace))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return $this->getAliasFromNamespace($power->namespace);
|
|
}
|
|
|
|
/**
|
|
* Converts the namespace of a power into an class alias
|
|
*
|
|
* @param string $input The namespaced string to process.
|
|
*
|
|
* @return string The modified string.
|
|
* @since 3.2.0
|
|
*/
|
|
private function getAliasFromNamespace(string $input): string
|
|
{
|
|
// 1. Split on backslash to get the components of the namespace path.
|
|
$parts = explode('\\', $input);
|
|
|
|
// 2. Consider only the last part after the final backslash for further processing.
|
|
$lastSegment = end($parts);
|
|
|
|
// 3. Get the part after the first dot.
|
|
$target = (strpos($lastSegment, '.') !== false)
|
|
? substr($lastSegment, strpos($lastSegment, '.') + 1)
|
|
: $lastSegment;
|
|
|
|
// 4. Split on dots.
|
|
$dotParts = explode('.', $target);
|
|
|
|
// 5. Modify segments with camel case words.
|
|
$modifiedDotParts = array_map(function ($part) {
|
|
return preg_replace('/(?<=[a-z])(?=[A-Z])/', '.', $part);
|
|
}, $dotParts);
|
|
|
|
// 6. Implode the array with dots and return.
|
|
return implode('.', $modifiedDotParts);
|
|
}
|
|
|
|
/**
|
|
* Get dependency name linked to service provider
|
|
*
|
|
* @param string $serviceProvider The global unique id of the (service provider) power
|
|
* @param string $dependency The global unique id of the (dependency) power
|
|
*
|
|
* @return string|null
|
|
* @since 3.2.0
|
|
*/
|
|
private function getServiceProviderDependencyName(string $serviceProvider, string $dependency): ?string
|
|
{
|
|
if (($power = $this->power($serviceProvider)) === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (empty($power->use_selection) || !is_object($power->use_selection))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach ($power->use_selection as $use_selection)
|
|
{
|
|
if ($use_selection->use !== $dependency)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (($name = $this->name($use_selection->use, $use_selection->as)) === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return $name;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|