Initial commit

This commit is contained in:
root
2026-01-19 17:44:46 +01:00
commit 823af8b11d
8721 changed files with 1130846 additions and 0 deletions

View File

@@ -0,0 +1,185 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
use Espo\Core\Binding\Key\NamedClassKey;
use LogicException;
use Closure;
class Binder
{
public function __construct(private BindingData $data)
{}
/**
* Bind an interface to an implementation.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<T> $implementationClassName An implementation class name.
*/
public function bindImplementation(string|NamedClassKey $key, string $implementationClassName): self
{
$key = self::keyToString($key);
$this->validateBindingKey($key);
$this->data->addGlobal(
$key,
Binding::createFromImplementationClassName($implementationClassName)
);
return $this;
}
/**
* Bind an interface to a specific service.
*
* @param class-string<object>|NamedClassKey<object> $key An interface or interface with a parameter name.
* @param string $serviceName A service name.
*/
public function bindService(string|NamedClassKey $key, string $serviceName): self
{
$key = self::keyToString($key);
$this->validateBindingKey($key);
$this->data->addGlobal(
$key,
Binding::createFromServiceName($serviceName)
);
return $this;
}
/**
* Bind an interface to a callback.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param Closure $callback A callback that will resolve a dependency.
* @todo Change to Closure(...): T Once https://github.com/phpstan/phpstan/issues/8214 is implemented.
*/
public function bindCallback(string|NamedClassKey $key, Closure $callback): self
{
$key = self::keyToString($key);
$this->validateBindingKey($key);
$this->data->addGlobal(
$key,
Binding::createFromCallback($callback)
);
return $this;
}
/**
* Bind an interface to a specific instance.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param T $instance An instance.
* @noinspection PhpDocSignatureInspection
*/
public function bindInstance(string|NamedClassKey $key, object $instance): self
{
$key = self::keyToString($key);
$this->validateBindingKey($key);
$this->data->addGlobal(
$key,
Binding::createFromValue($instance)
);
return $this;
}
/**
* Bind an interface to a factory.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<Factory<T>> $factoryClassName A factory class name.
*/
public function bindFactory(string|NamedClassKey $key, string $factoryClassName): self
{
$key = self::keyToString($key);
$this->validateBindingKey($key);
$this->data->addGlobal(
$key,
Binding::createFromFactoryClassName($factoryClassName)
);
return $this;
}
/**
* Creates a contextual binder and pass it as an argument of a callback.
*
* @param class-string<object> $className A context.
* @param Closure(ContextualBinder): void $callback A callback with a `ContextualBinder` argument.
*/
public function inContext(string $className, Closure $callback): self
{
$contextualBinder = new ContextualBinder($this->data, $className);
$callback($contextualBinder);
return $this;
}
/**
* Creates a contextual binder.
*
* @param class-string<object> $className A context.
*/
public function for(string $className): ContextualBinder
{
return new ContextualBinder($this->data, $className);
}
/**
* @param string|NamedClassKey<object> $key
*/
private static function keyToString(string|NamedClassKey $key): string
{
return is_string($key) ? $key : $key->toString();
}
private function validateBindingKey(string $key): void
{
if (!$key) {
throw new LogicException("Bad binding.");
}
if ($key[0] === '$') {
throw new LogicException("Can't binding a parameter name w/o an interface globally.");
}
}
}

View File

@@ -0,0 +1,113 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
use LogicException;
class Binding
{
public const IMPLEMENTATION_CLASS_NAME = 1;
public const CONTAINER_SERVICE = 2;
public const VALUE = 3;
public const CALLBACK = 4;
public const FACTORY_CLASS_NAME = 5;
private int $type;
/** @var mixed */
private $value;
/**
* @param mixed $value
*/
private function __construct(int $type, $value)
{
$this->type = $type;
$this->value = $value;
}
public function getType(): int
{
return $this->type;
}
/**
* @return mixed
*/
public function getValue()
{
return $this->value;
}
/**
* @param class-string<object> $implementationClassName
*/
public static function createFromImplementationClassName(string $implementationClassName): self
{
if (!$implementationClassName) {
throw new LogicException("Bad binding.");
}
return new self(self::IMPLEMENTATION_CLASS_NAME, $implementationClassName);
}
public static function createFromServiceName(string $serviceName): self
{
if (!$serviceName) {
throw new LogicException("Bad binding.");
}
return new self(self::CONTAINER_SERVICE, $serviceName);
}
/**
* @param mixed $value
*/
public static function createFromValue($value): self
{
return new self(self::VALUE, $value);
}
public static function createFromCallback(callable $callback): self
{
return new self(self::CALLBACK, $callback);
}
/**
* @param class-string<Factory<object>> $factoryClassName
*/
public static function createFromFactoryClassName(string $factoryClassName): self
{
if (!$factoryClassName) {
throw new LogicException("Bad binding.");
}
return new self(self::FACTORY_CLASS_NAME, $factoryClassName);
}
}

View File

@@ -0,0 +1,177 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
use ReflectionClass;
use ReflectionParameter;
use ReflectionNamedType;
use LogicException;
/**
* Access point for bindings.
*/
class BindingContainer
{
public function __construct(private BindingData $data)
{}
/**
* Has binding by a reflection parameter.
*
* @param ?ReflectionClass<object> $class
*/
public function hasByParam(?ReflectionClass $class, ReflectionParameter $param): bool
{
if ($this->getInternal($class, $param) === null) {
return false;
}
return true;
}
/**
* Get binding by a reflection parameter.
*
* @param ?ReflectionClass<object> $class
*/
public function getByParam(?ReflectionClass $class, ReflectionParameter $param): Binding
{
if (!$this->hasByParam($class, $param)) {
throw new LogicException("Cannot get not existing binding.");
}
/** @var Binding */
return $this->getInternal($class, $param);
}
/**
* Has global binding by an interface.
*
* @param class-string $interfaceName
*/
public function hasByInterface(string $interfaceName): bool
{
return $this->data->hasGlobal($interfaceName);
}
/**
* Get global binding by an interface.
*
* @param class-string $interfaceName
*/
public function getByInterface(string $interfaceName): Binding
{
if (!$this->hasByInterface($interfaceName)) {
throw new LogicException("Binding for interface `$interfaceName` does not exist.");
}
if (!interface_exists($interfaceName) && !class_exists($interfaceName)) {
throw new LogicException("Interface `$interfaceName` does not exist.");
}
return $this->data->getGlobal($interfaceName);
}
/**
* @param ?ReflectionClass<object> $class
*/
private function getInternal(?ReflectionClass $class, ReflectionParameter $param): ?Binding
{
$className = null;
$key = null;
if ($class) {
$className = $class->getName();
$key = '$' . $param->getName();
}
$type = $param->getType();
if (
$className &&
$key &&
$this->data->hasContext($className, $key)
) {
$binding = $this->data->getContext($className, $key);
$notMatching =
$type instanceof ReflectionNamedType &&
!$type->isBuiltin() &&
$binding->getType() === Binding::VALUE &&
is_scalar($binding->getValue());
if (!$notMatching) {
return $binding;
}
}
$dependencyClassName = null;
if (
$type instanceof ReflectionNamedType &&
!$type->isBuiltin()
) {
$dependencyClassName = $type->getName();
}
$key = null;
$keyWithParamName = null;
if ($dependencyClassName) {
$key = $dependencyClassName;
$keyWithParamName = $key . ' $' . $param->getName();
}
if ($keyWithParamName) {
if ($className && $this->data->hasContext($className, $keyWithParamName)) {
return $this->data->getContext($className, $keyWithParamName);
}
if ($this->data->hasGlobal($keyWithParamName)) {
return $this->data->getGlobal($keyWithParamName);
}
}
if ($key) {
if ($className && $this->data->hasContext($className, $key)) {
return $this->data->getContext($className, $key);
}
if ($this->data->hasGlobal($key)) {
return $this->data->getGlobal($key);
}
}
return null;
}
}

View File

@@ -0,0 +1,150 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
use Closure;
use Espo\Core\Binding\Key\NamedClassKey;
class BindingContainerBuilder
{
private BindingData $data;
private Binder $binder;
public function __construct()
{
$this->data = new BindingData();
$this->binder = new Binder($this->data);
}
/**
* Bind an interface to an implementation.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<T> $implementationClassName An implementation class name.
*/
public function bindImplementation(string|NamedClassKey $key, string $implementationClassName): self
{
$this->binder->bindImplementation($key, $implementationClassName);
return $this;
}
/**
* Bind an interface to a specific service.
*
* @param class-string<object>|NamedClassKey<object> $key An interface or interface with a parameter name.
* @param string $serviceName A service name.
* @noinspection PhpUnused
*/
public function bindService(string|NamedClassKey $key, string $serviceName): self
{
$this->binder->bindService($key, $serviceName);
return $this;
}
/**
* Bind an interface to a callback.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param Closure $callback A callback that will resolve a dependency.
* @todo Change to Closure(...): T Once https://github.com/phpstan/phpstan/issues/8214 is implemented.
* @noinspection PhpUnused
*/
public function bindCallback(string|NamedClassKey $key, Closure $callback): self
{
$this->binder->bindCallback($key, $callback);
return $this;
}
/**
* Bind an interface to a specific instance.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param T $instance An instance.
* @noinspection PhpDocSignatureInspection
*/
public function bindInstance(string|NamedClassKey $key, object $instance): self
{
$this->binder->bindInstance($key, $instance);
return $this;
}
/**
* Bind an interface to a factory.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<Factory<T>> $factoryClassName A factory class name.
* @noinspection PhpUnused
*/
public function bindFactory(string|NamedClassKey $key, string $factoryClassName): self
{
$this->binder->bindFactory($key, $factoryClassName);
return $this;
}
/**
* Creates a contextual binder and pass it as an argument of a callback.
*
* @param class-string<object> $className A context.
* @param Closure(ContextualBinder): void $callback A callback with a `ContextualBinder` argument.
*/
public function inContext(string $className, Closure $callback): self
{
$contextualBinder = new ContextualBinder($this->data, $className);
$callback($contextualBinder);
return $this;
}
/**
* Build.
*/
public function build(): BindingContainer
{
return new BindingContainer($this->data);
}
/**
* Create an instance.
*/
public static function create(): self
{
return new self();
}
}

View File

@@ -0,0 +1,136 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
use LogicException;
use stdClass;
class BindingData
{
private stdClass $global;
private stdClass $context;
public function __construct()
{
$this->global = (object) [];
$this->context = (object) [];
}
public function addContext(string $className, string $key, Binding $binding): void
{
if (!property_exists($this->context, $className)) {
$this->context->$className = (object) [];
}
$this->context->$className->$key = $binding;
}
public function addGlobal(string $key, Binding $binding): void
{
$this->global->$key = $binding;
}
/**
* @param class-string<object> $className
*/
public function hasContext(string $className, string $key): bool
{
if (!property_exists($this->context, $className)) {
return false;
}
if (!property_exists($this->context->$className, $key)) {
return false;
}
return true;
}
/**
* @param class-string<object> $className
*/
public function getContext(string $className, string $key): Binding
{
if (!$this->hasContext($className, $key)) {
throw new LogicException("No data.");
}
return $this->context->$className->$key;
}
public function hasGlobal(string $key): bool
{
if (!property_exists($this->global, $key)) {
return false;
}
return true;
}
public function getGlobal(string $key): Binding
{
if (!$this->hasGlobal($key)) {
throw new LogicException("No data.");
}
return $this->global->$key;
}
/**
* @return string[]
*/
public function getGlobalKeyList(): array
{
return array_keys(
get_object_vars($this->global)
);
}
/**
* @return class-string<object>[]
*/
public function getContextList(): array
{
/** @var class-string<object>[] */
return array_keys(
get_object_vars($this->context)
);
}
/**
* @return string[]
*/
public function getContextKeyList(string $context): array
{
return array_keys(
get_object_vars($this->context->$context ?? (object) [])
);
}
}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
interface BindingLoader
{
public function load(): BindingData;
}

View File

@@ -0,0 +1,35 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
interface BindingProcessor
{
public function process(Binder $binder): void;
}

View File

@@ -0,0 +1,210 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
use Closure;
use Espo\Core\Binding\Key\NamedClassKey;
use Espo\Core\Binding\Key\NamedKey;
use LogicException;
class ContextualBinder
{
private BindingData $data;
/** @var class-string<object> */
private string $className;
/**
* @param class-string<object> $className
*/
public function __construct(BindingData $data, string $className)
{
$this->data = $data;
$this->className = $className;
}
/**
* Bind an interface to an implementation.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<T> $implementationClassName An implementation class name.
*/
public function bindImplementation(string|NamedClassKey $key, string $implementationClassName): self
{
$key = self::keyToString($key);
$this->validateBindingKeyNoParameterName($key);
$this->data->addContext(
$this->className,
$key,
Binding::createFromImplementationClassName($implementationClassName)
);
return $this;
}
/**
* Bind an interface to a specific service.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param string $serviceName A service name.
*/
public function bindService(string|NamedClassKey $key, string $serviceName): self
{
$key = self::keyToString($key);
$this->validateBindingKeyNoParameterName($key);
$this->data->addContext(
$this->className,
$key,
Binding::createFromServiceName($serviceName)
);
return $this;
}
/**
* Bind an interface or parameter name to a specific value.
*
* @param string|NamedKey|NamedClassKey<object> $key Parameter name (`$name`) or interface with a parameter name.
* @param mixed $value A value of any type.
*/
public function bindValue(string|NamedKey|NamedClassKey $key, $value): self
{
$key = self::keyToString($key);
$this->validateBindingKeyParameterName($key);
$this->data->addContext(
$this->className,
$key,
Binding::createFromValue($value)
);
return $this;
}
/**
* Bind an interface to a specific instance.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param T $instance An instance.
* @noinspection PhpDocSignatureInspection
*/
public function bindInstance(string|NamedClassKey $key, object $instance): self
{
$key = self::keyToString($key);
$this->validateBindingKeyNoParameterName($key);
$this->data->addContext(
$this->className,
$key,
Binding::createFromValue($instance)
);
return $this;
}
/**
* Bind an interface or parameter name to a callback.
*
* @param class-string<object>|NamedClassKey<object>|NamedKey $key An interface, parameter name or both.
* @param Closure $callback A callback that will resolve a dependency.
* @todo Change to Closure(...): mixed Once https://github.com/phpstan/phpstan/issues/8214 is implemented.
*/
public function bindCallback(string|NamedClassKey|NamedKey $key, Closure $callback): self
{
$key = self::keyToString($key);
$this->validateBinding($key);
$this->data->addContext(
$this->className,
$key,
Binding::createFromCallback($callback)
);
return $this;
}
/**
* Bind an interface to a factory.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T> $key An interface or interface with a parameter name.
* @param class-string<Factory<T>> $factoryClassName A factory class name.
*/
public function bindFactory(string|NamedClassKey $key, string $factoryClassName): self
{
$key = self::keyToString($key);
$this->validateBindingKeyNoParameterName($key);
$this->data->addContext(
$this->className,
$key,
Binding::createFromFactoryClassName($factoryClassName)
);
return $this;
}
private function validateBinding(string $key): void
{
if (!$key) {
throw new LogicException("Bad binding.");
}
}
private function validateBindingKeyNoParameterName(string $key): void
{
$this->validateBinding($key);
if ($key[0] === '$') {
throw new LogicException("Can't bind a parameter name w/o an interface.");
}
}
private function validateBindingKeyParameterName(string $key): void
{
$this->validateBinding($key);
if (!str_contains($key, '$')) {
throw new LogicException("Can't bind w/o a parameter name.");
}
}
/**
* @param string|NamedKey|NamedClassKey<object> $key
*/
private static function keyToString(string|NamedKey|NamedClassKey $key): string
{
return is_string($key) ? $key : $key->toString();
}
}

View File

@@ -0,0 +1,87 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
use Espo\Core\Utils\Module;
use Espo\Binding;
class EspoBindingLoader implements BindingLoader
{
/** @var string[] */
private array $moduleNameList;
public function __construct(Module $module)
{
$this->moduleNameList = $module->getOrderedList();
}
public function load(): BindingData
{
$data = new BindingData();
$binder = new Binder($data);
(new Binding())->process($binder);
foreach ($this->moduleNameList as $moduleName) {
$this->loadModule($binder, $moduleName);
}
$this->loadCustom($binder);
return $data;
}
private function loadModule(Binder $binder, string $moduleName): void
{
$className = 'Espo\\Modules\\' . $moduleName . '\\Binding';
if (!class_exists($className)) {
return;
}
/** @var class-string<BindingProcessor> $className */
(new $className())->process($binder);
}
private function loadCustom(Binder $binder): void
{
/** @var class-string<BindingProcessor>|string $className */
$className = 'Espo\\Custom\\Binding';
if (!class_exists($className)) {
return;
}
/** @var class-string<BindingProcessor> $className */
(new $className())->process($binder);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding;
/**
* @template T of object
*/
interface Factory
{
/**
* @return T
* @noinspection PhpDocSignatureInspection
*/
public function create(): object;
}

View File

@@ -0,0 +1,62 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding\Key;
/**
* A key for a class-type-hinted constructor parameter with a parameter name.
*
* @template-covariant T of object
*/
class NamedClassKey
{
/**
* @param class-string<T> $className
*/
private function __construct(private string $className, private string $parameterName)
{}
/**
* Create.
*
* @template TC of object
* @param class-string<TC> $className An interface.
* @param string $parameterName A constructor parameter name (w/o '$').
* @return self<TC>
*/
public static function create(string $className, string $parameterName): self
{
return new self($className, $parameterName);
}
public function toString(): string
{
return $this->className . ' $' . $this->parameterName;
}
}

View File

@@ -0,0 +1,54 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM Open Source CRM application.
* Copyright (C) 2014-2025 EspoCRM, Inc.
* Website: https://www.espocrm.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU Affero General Public License version 3.
*
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Core\Binding\Key;
/**
* A parameter-name-only key.
*/
class NamedKey
{
private function __construct(private string $parameterName)
{}
/**
* Create.
*
* @param string $parameterName A constructor parameter name (w/o '$').
*/
public static function create(string $parameterName): self
{
return new self($parameterName);
}
public function toString(): string
{
return '$' . $this->parameterName;
}
}