snapshot of https://github.com/espocrm/espocrm.git (master, 2026-09-18; blobs > 131072B omitted)
Some checks failed
Test / Test on PHP 8.3 (push) Failing after 5s
Test / Test on PHP 8.4 (push) Failing after 4s
Test / Test on PHP 8.5 (push) Failing after 3s
TypeScript static analysis / TypeScript check (push) Failing after 14s
Test Integration on MySQL / Test on PHP 8.3 (push) Failing after 2s
Test Integration on MySQL / Test on PHP 8.5 (push) Failing after 0s
Test Integration on PostgreSQL / Test on PHP 8.4 fix (push) Failing after 1s
Test Integration on PostgreSQL / Test on PHP 8.4 master (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-18 04:19:15 +05:00
commit 7cc7e6b4f3
10077 changed files with 900756 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Attributes;
use Attribute;
/**
* Assigns a qualifier to a constructor parameter.
*
* @since 10.1.0
*/
#[Attribute(Attribute::TARGET_PARAMETER)]
readonly class Qualify
{
public function __construct(
public string $qualifier,
) {}
}

View File

@@ -0,0 +1,182 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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 Espo\Core\Binding\Key\QualifiedClassKey;
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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param class-string<T> $implementationClassName An implementation class name.
*/
public function bindImplementation(
string|NamedClassKey|QualifiedClassKey $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>|QualifiedClassKey<object> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param string $serviceName A service name.
*/
public function bindService(string|NamedClassKey|QualifiedClassKey $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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @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|QualifiedClassKey $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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param T $instance An instance.
*/
public function bindInstance(string|NamedClassKey|QualifiedClassKey $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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param class-string<Factory<T>> $factoryClassName A factory class name.
*/
public function bindFactory(string|NamedClassKey|QualifiedClassKey $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>|QualifiedClassKey<object> $key
*/
private static function keyToString(string|NamedClassKey|QualifiedClassKey $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.");
}
if ($key[0] === '#') {
throw new LogicException("Can't binding a qualification name w/o an interface globally.");
}
}
}

View File

@@ -0,0 +1,107 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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 int IMPLEMENTATION_CLASS_NAME = 1;
public const int CONTAINER_SERVICE = 2;
public const int VALUE = 3;
public const int CALLBACK = 4;
public const int FACTORY_CLASS_NAME = 5;
private int $type;
private mixed $value;
private function __construct(int $type, mixed $value)
{
$this->type = $type;
$this->value = $value;
}
public function getType(): int
{
return $this->type;
}
public function getValue(): mixed
{
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,222 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\Attributes\Qualify;
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.");
}
return $this->getInternal($class, $param) ?? throw new LogicException();
}
/**
* 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
{
if ($class) {
$binding = $this->getInternalContextualNamed($class, $param);
if ($binding) {
return $binding;
}
}
$paramClassName = $this->getClassNameFromParameterType($param->getType());
if ($paramClassName === null) {
return null;
}
$qualifiedName = $this->getQualifiedName($param);
if ($qualifiedName) {
$keyQualified = $paramClassName . ' #' . $qualifiedName;
$binding = $this->getInternalByClassNameKey($class?->getName(), $keyQualified);
if ($binding) {
return $binding;
}
}
$keyWithName = $paramClassName . ' $' . $param->getName();
$binding = $this->getInternalByClassNameKey($class?->getName(), $keyWithName);
if ($binding) {
return $binding;
}
$key = $paramClassName;
$binding = $this->getInternalByClassNameKey($class?->getName(), $key);
if ($binding) {
return $binding;
}
return null;
}
private function getQualifiedName(ReflectionParameter $param): ?string
{
$qualifierClass = $param->getAttributes(Qualify::class)[0] ?? null;
if (!$qualifierClass) {
return null;
}
$qualifier = $qualifierClass->newInstance();
return $qualifier->qualifier;
}
/**
* @param ReflectionClass<object> $class
*/
private function getInternalContextualNamed(ReflectionClass $class, ReflectionParameter $param): ?Binding
{
$key = '$' . $param->getName();
if (!$this->data->hasContext($class->getName(), $key)) {
return null;
}
$type = $param->getType();
$binding = $this->data->getContext($class->getName(), $key);
$notMatching =
$type instanceof ReflectionNamedType &&
!$type->isBuiltin() &&
$binding->getType() === Binding::VALUE &&
is_scalar($binding->getValue());
if ($notMatching) {
return null;
}
return $binding;
}
/**
* @param ?class-string<object> $className
*/
private function getInternalByClassNameKey(?string $className, string $key): ?Binding
{
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;
}
private function getClassNameFromParameterType(mixed $type): ?string
{
$dependencyClassName = null;
if (
$type instanceof ReflectionNamedType &&
!$type->isBuiltin()
) {
$dependencyClassName = $type->getName();
}
return $dependencyClassName;
}
}

View File

@@ -0,0 +1,155 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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\QualifiedClassKey;
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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param class-string<T> $implementationClassName An implementation class name.
*/
public function bindImplementation(string|NamedClassKey|QualifiedClassKey $key, string $implementationClassName): self
{
$this->binder->bindImplementation($key, $implementationClassName);
return $this;
}
/**
* Bind an interface to a specific service.
*
* @param class-string<object>|NamedClassKey<object>|QualifiedClassKey<object> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param string $serviceName A service name.
* @noinspection PhpUnused
*/
public function bindService(string|NamedClassKey|QualifiedClassKey $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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @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|QualifiedClassKey $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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param T $instance An instance.
*/
public function bindInstance(string|NamedClassKey|QualifiedClassKey $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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param class-string<Factory<T>> $factoryClassName A factory class name.
* @noinspection PhpUnused
*/
public function bindFactory(string|NamedClassKey|QualifiedClassKey $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-2026 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-2026 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-2026 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,196 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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 Espo\Core\Binding\Key\QualifiedClassKey;
use LogicException;
class ContextualBinder
{
/**
* @param class-string<object> $className
*/
public function __construct(
private BindingData $data,
private string $className,
) {}
/**
* Bind an interface to an implementation.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param class-string<T> $implementationClassName An implementation class name.
*/
public function bindImplementation(
string|NamedClassKey|QualifiedClassKey $key,
string $implementationClassName,
): self {
$key = self::keyToString($key);
$this->validateBindingKeyNoName($key);
$binding = Binding::createFromImplementationClassName($implementationClassName);
$this->data->addContext($this->className, $key, $binding);
return $this;
}
/**
* Bind an interface to a specific service.
*
* @template T of object
* @param class-string<T>|NamedClassKey<T>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param string $serviceName A service name.
*/
public function bindService(string|NamedClassKey|QualifiedClassKey $key, string $serviceName): self
{
$key = self::keyToString($key);
$this->validateBindingKeyNoName($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
* A parameter name (`$name`) or an 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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param T $instance An instance.
*/
public function bindInstance(string|NamedClassKey|QualifiedClassKey $key, object $instance): self
{
$key = self::keyToString($key);
$this->validateBindingKeyNoName($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|QualifiedClassKey<object> $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|QualifiedClassKey $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>|QualifiedClassKey<T> $key
* An interface, an interface with a parameter name or an interface with a qualifier.
* @param class-string<Factory<T>> $factoryClassName A factory class name.
*/
public function bindFactory(string|NamedClassKey|QualifiedClassKey $key, string $factoryClassName): self
{
$key = self::keyToString($key);
$this->validateBindingKeyNoName($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 validateBindingKeyNoName(string $key): void
{
$this->validateBinding($key);
if ($key[0] === '$') {
throw new LogicException("Can't bind a parameter name w/o an interface.");
}
if ($key[0] === '#') {
throw new LogicException("Can't bind a qualification 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>|QualifiedClassKey<object> $key
*/
private static function keyToString(string|NamedKey|NamedClassKey|QualifiedClassKey $key): string
{
return is_string($key) ? $key : $key->toString();
}
}

View File

@@ -0,0 +1,91 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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,
private ?BindingProcessor $binding = null,
) {
$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);
$this->binding?->process($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-2026 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-2026 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-2026 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;
}
}

View File

@@ -0,0 +1,69 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM – Open Source CRM application.
* Copyright (C) 2014-2026 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 qualifier.
* Use it to bind to qualified constructor parameters. For example, one interface
* can be used with a qualifier and without it. In the latter case,
* the default binding it applied. While in the formed case, a special binding is applied.
* For example, a caching pool interface may be bound to two different services:
* core cache and application cache.
*
* @since 10.1.0
*
* @template-covariant T of object
*/
class QualifiedClassKey
{
/**
* @param class-string<T> $className
*/
private function __construct(private string $className, private string $qualifier)
{}
/**
* Create.
*
* @template TC of object
* @param class-string<TC> $className An interface.
* @param string $qualifier A qualification name.
* @return self<TC>
*/
public static function create(string $className, string $qualifier): self
{
return new self($className, $qualifier);
}
public function toString(): string
{
return $this->className . ' #' . $this->qualifier;
}
}