Initial commit
This commit is contained in:
1192
application/Espo/ORM/BaseEntity.php
Normal file
1192
application/Espo/ORM/BaseEntity.php
Normal file
File diff suppressed because it is too large
Load Diff
49
application/Espo/ORM/Collection.php
Normal file
49
application/Espo/ORM/Collection.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Traversable;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* A collection of entities.
|
||||
*
|
||||
* @template-covariant TEntity of Entity
|
||||
* @extends Traversable<int, TEntity>
|
||||
*/
|
||||
interface Collection extends Traversable
|
||||
{
|
||||
/**
|
||||
* Get an array of stdClass objects.
|
||||
*
|
||||
* @return stdClass[]
|
||||
*/
|
||||
public function getValueMapList(): array;
|
||||
}
|
||||
85
application/Espo/ORM/CollectionFactory.php
Normal file
85
application/Espo/ORM/CollectionFactory.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Espo\ORM\Query\Select;
|
||||
|
||||
/**
|
||||
* Creates collections.
|
||||
*/
|
||||
class CollectionFactory
|
||||
{
|
||||
public function __construct(protected EntityManager $entityManager)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*
|
||||
* @param array<Entity|array<string, mixed>> $dataList
|
||||
* @return EntityCollection<Entity>
|
||||
*/
|
||||
public function create(?string $entityType = null, array $dataList = []): EntityCollection
|
||||
{
|
||||
return new EntityCollection($dataList, $entityType, $this->entityManager->getEntityFactory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from an SQL.
|
||||
*
|
||||
* @return SthCollection<Entity>
|
||||
*/
|
||||
public function createFromSql(string $entityType, string $sql): SthCollection
|
||||
{
|
||||
return SthCollection::fromSql($entityType, $sql, $this->entityManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a query.
|
||||
*
|
||||
* @return SthCollection<Entity>
|
||||
*/
|
||||
public function createFromQuery(Select $query): SthCollection
|
||||
{
|
||||
return SthCollection::fromQuery($query, $this->entityManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create EntityCollection from SthCollection.
|
||||
*
|
||||
* @template TEntity of Entity
|
||||
* @param SthCollection<TEntity> $sthCollection
|
||||
* @return EntityCollection<TEntity>
|
||||
*/
|
||||
public function createFromSthCollection(SthCollection $sthCollection): EntityCollection
|
||||
{
|
||||
/** @var EntityCollection<TEntity> */
|
||||
return EntityCollection::fromSthCollection($sthCollection);
|
||||
}
|
||||
}
|
||||
38
application/Espo/ORM/DB/Query/Base.php
Normal file
38
application/Espo/ORM/DB/Query/Base.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\ORM\DB\Query;
|
||||
|
||||
/**
|
||||
* @deprecated As of v6.0. Not to be used directly.
|
||||
*/
|
||||
abstract class Base extends \Espo\ORM\QueryComposer\BaseQueryComposer
|
||||
{
|
||||
|
||||
}
|
||||
38
application/Espo/ORM/DataLoader/EmptyLoader.php
Normal file
38
application/Espo/ORM/DataLoader/EmptyLoader.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\ORM\DataLoader;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
class EmptyLoader implements Loader
|
||||
{
|
||||
public function load(Entity $entity): void
|
||||
{}
|
||||
}
|
||||
37
application/Espo/ORM/DataLoader/Loader.php
Normal file
37
application/Espo/ORM/DataLoader/Loader.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\ORM\DataLoader;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
interface Loader
|
||||
{
|
||||
public function load(Entity $entity): void;
|
||||
}
|
||||
65
application/Espo/ORM/DataLoader/RDBLoader.php
Normal file
65
application/Espo/ORM/DataLoader/RDBLoader.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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\ORM\DataLoader;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
class RDBLoader implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
public function load(Entity $entity): void
|
||||
{
|
||||
$loaded = $this->entityManager->getEntityById($entity->getEntityType(), $entity->getId());
|
||||
|
||||
if (!$loaded) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($loaded->getAttributeList() as $attribute) {
|
||||
if (!$loaded->has($attribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $loaded->get($attribute);
|
||||
|
||||
if (!$entity->hasFetched($attribute)) {
|
||||
$entity->setFetched($attribute, $value);
|
||||
}
|
||||
|
||||
if (!$entity->has($attribute)) {
|
||||
$entity->set($attribute, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
226
application/Espo/ORM/DatabaseParams.php
Normal file
226
application/Espo/ORM/DatabaseParams.php
Normal file
@@ -0,0 +1,226 @@
|
||||
<?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\ORM;
|
||||
|
||||
use SensitiveParameter;
|
||||
|
||||
/**
|
||||
* Immutable.
|
||||
*/
|
||||
class DatabaseParams
|
||||
{
|
||||
private ?string $platform = null;
|
||||
private ?string $host = null;
|
||||
private ?int $port = null;
|
||||
private ?string $name = null;
|
||||
private ?string $username = null;
|
||||
private ?string $password = null;
|
||||
private ?string $charset = null;
|
||||
private ?string $sslCa = null;
|
||||
private ?string $sslCert = null;
|
||||
private ?string $sslKey = null;
|
||||
private ?string $sslCaPath = null;
|
||||
private ?string $sslCipher = null;
|
||||
private bool $sslVerifyDisabled = false;
|
||||
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
public function getPlatform(): ?string
|
||||
{
|
||||
return $this->platform;
|
||||
}
|
||||
|
||||
public function getHost(): ?string
|
||||
{
|
||||
return $this->host;
|
||||
}
|
||||
|
||||
public function getPort(): ?int
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
public function getName(): ?string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getUsername(): ?string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
public function getPassword(): ?string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
public function getCharset(): ?string
|
||||
{
|
||||
return $this->charset;
|
||||
}
|
||||
|
||||
public function getSslCa(): ?string
|
||||
{
|
||||
return $this->sslCa;
|
||||
}
|
||||
|
||||
public function getSslCert(): ?string
|
||||
{
|
||||
return $this->sslCert;
|
||||
}
|
||||
|
||||
public function getSslCaPath(): ?string
|
||||
{
|
||||
return $this->sslCaPath;
|
||||
}
|
||||
|
||||
public function getSslCipher(): ?string
|
||||
{
|
||||
return $this->sslCipher;
|
||||
}
|
||||
|
||||
public function getSslKey(): ?string
|
||||
{
|
||||
return $this->sslKey;
|
||||
}
|
||||
|
||||
public function isSslVerifyDisabled(): bool
|
||||
{
|
||||
return $this->sslVerifyDisabled;
|
||||
}
|
||||
|
||||
public function withPlatform(?string $platform): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->platform = $platform;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withHost(?string $host): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->host = $host;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withPort(?int $port): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->port = $port;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withName(?string $name): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withUsername(?string $username): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->username = $username;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withPassword(#[SensitiveParameter] ?string $password): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->password = $password;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withCharset(?string $charset): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->charset = $charset;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSslCa(?string $sslCa): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->sslCa = $sslCa;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSslCaPath(?string $sslCaPath): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->sslCaPath = $sslCaPath;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSslCert(?string $sslCert): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->sslCert = $sslCert;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSslCipher(?string $sslCipher): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->sslCipher = $sslCipher;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSslKey(?string $sslKey): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->sslKey = $sslKey;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public function withSslVerifyDisabled(bool $sslVerifyDisabled = true): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->sslVerifyDisabled = $sslVerifyDisabled;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
36
application/Espo/ORM/Defs.php
Normal file
36
application/Espo/ORM/Defs.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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\ORM;
|
||||
|
||||
/**
|
||||
* Definitions.
|
||||
*/
|
||||
class Defs extends Defs\Defs
|
||||
{}
|
||||
115
application/Espo/ORM/Defs/AttributeDefs.php
Normal file
115
application/Espo/ORM/Defs/AttributeDefs.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?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\ORM\Defs;
|
||||
|
||||
use Espo\ORM\Defs\Params\AttributeParam;
|
||||
|
||||
/**
|
||||
* Attribute definitions.
|
||||
*/
|
||||
class AttributeDefs
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $data;
|
||||
private string $name;
|
||||
|
||||
private function __construct()
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $raw
|
||||
*/
|
||||
public static function fromRaw(array $raw, string $name): self
|
||||
{
|
||||
$obj = new self();
|
||||
$obj->data = $raw;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type.
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->data[AttributeParam::TYPE];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a length.
|
||||
*/
|
||||
public function getLength(): ?int
|
||||
{
|
||||
return $this->data[AttributeParam::LEN] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is not-storable. Not-storable attributes are not stored in DB.
|
||||
*/
|
||||
public function isNotStorable(): bool
|
||||
{
|
||||
return $this->data[AttributeParam::NOT_STORABLE] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is auto-increment.
|
||||
*/
|
||||
public function isAutoincrement(): bool
|
||||
{
|
||||
return $this->data[AttributeParam::AUTOINCREMENT] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value by a name.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParam(string $name)
|
||||
{
|
||||
return $this->data[$name] ?? null;
|
||||
}
|
||||
}
|
||||
99
application/Espo/ORM/Defs/Defs.php
Normal file
99
application/Espo/ORM/Defs/Defs.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?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\ORM\Defs;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Definitions.
|
||||
*/
|
||||
class Defs
|
||||
{
|
||||
public function __construct(private DefsData $data)
|
||||
{}
|
||||
|
||||
/**
|
||||
* Get an entity type list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEntityTypeList(): array
|
||||
{
|
||||
return $this->data->getEntityTypeList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity definitions list.
|
||||
*
|
||||
* @return EntityDefs[]
|
||||
*/
|
||||
public function getEntityList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->getEntityTypeList() as $name) {
|
||||
$list[] = $this->getEntity($name);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has an entity type.
|
||||
*/
|
||||
public function hasEntity(string $entityType): bool
|
||||
{
|
||||
return $this->data->hasEntity($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entity definitions.
|
||||
*/
|
||||
public function getEntity(string $entityType): EntityDefs
|
||||
{
|
||||
if (!$this->hasEntity($entityType)) {
|
||||
throw new RuntimeException("Entity type '{$entityType}' does not exist.");
|
||||
}
|
||||
|
||||
return $this->data->getEntity($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get entity definitions, if an entity type does not exist, then return null.
|
||||
*/
|
||||
public function tryGetEntity(string $entityType): ?EntityDefs
|
||||
{
|
||||
if (!$this->hasEntity($entityType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getEntity($entityType);
|
||||
}
|
||||
}
|
||||
95
application/Espo/ORM/Defs/DefsData.php
Normal file
95
application/Espo/ORM/Defs/DefsData.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?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\ORM\Defs;
|
||||
|
||||
use Espo\ORM\Metadata;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DefsData
|
||||
{
|
||||
/** @var array<string, ?EntityDefs> */
|
||||
private array $cache = [];
|
||||
|
||||
public function __construct(private Metadata $metadata)
|
||||
{}
|
||||
|
||||
public function clearCache(): void
|
||||
{
|
||||
$this->cache = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEntityTypeList(): array
|
||||
{
|
||||
return $this->metadata->getEntityTypeList();
|
||||
}
|
||||
|
||||
public function hasEntity(string $name): bool
|
||||
{
|
||||
$this->cacheEntity($name);
|
||||
|
||||
return !is_null($this->cache[$name]);
|
||||
}
|
||||
|
||||
public function getEntity(string $name): EntityDefs
|
||||
{
|
||||
$this->cacheEntity($name);
|
||||
|
||||
if (!$this->hasEntity($name)) {
|
||||
throw new RuntimeException("Entity type '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var EntityDefs */
|
||||
return $this->cache[$name];
|
||||
}
|
||||
|
||||
private function cacheEntity(string $name): void
|
||||
{
|
||||
if (array_key_exists($name, $this->cache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->cache[$name] = $this->loadEntity($name);
|
||||
}
|
||||
|
||||
private function loadEntity(string $name): ?EntityDefs
|
||||
{
|
||||
$raw = $this->metadata->get($name) ?? null;
|
||||
|
||||
if (!$raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return EntityDefs::fromRaw($raw, $name);
|
||||
}
|
||||
}
|
||||
431
application/Espo/ORM/Defs/EntityDefs.php
Normal file
431
application/Espo/ORM/Defs/EntityDefs.php
Normal file
@@ -0,0 +1,431 @@
|
||||
<?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\ORM\Defs;
|
||||
|
||||
use Espo\ORM\Defs\Params\EntityParam;
|
||||
use RuntimeException;
|
||||
|
||||
class EntityDefs
|
||||
{
|
||||
/** @var array<string, array<string, mixed>|mixed> */
|
||||
private array $data;
|
||||
private string $name;
|
||||
/** @var array<string, ?AttributeDefs> */
|
||||
private $attributeCache = [];
|
||||
/** @var array<string, ?RelationDefs> */
|
||||
private $relationCache = [];
|
||||
/** @var array<string, ?IndexDefs> */
|
||||
private $indexCache = [];
|
||||
/** @var array<string, ?FieldDefs> */
|
||||
private $fieldCache = [];
|
||||
|
||||
private function __construct()
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $raw
|
||||
*/
|
||||
public static function fromRaw(array $raw, string $name): self
|
||||
{
|
||||
$obj = new self();
|
||||
$obj->data = $raw;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity name (entity type).
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute name list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributeNameList(): array
|
||||
{
|
||||
/** @var string[] */
|
||||
return array_keys($this->data[EntityParam::ATTRIBUTES] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relation name list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRelationNameList(): array
|
||||
{
|
||||
/** @var string[] */
|
||||
return array_keys($this->data[EntityParam::RELATIONS] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an index name list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getIndexNameList(): array
|
||||
{
|
||||
/** @var string[] */
|
||||
return array_keys($this->data[EntityParam::INDEXES] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a field name list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFieldNameList(): array
|
||||
{
|
||||
/** @var string[] */
|
||||
return array_keys($this->data[EntityParam::FIELDS] ?? []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an attribute definitions list.
|
||||
*
|
||||
* @return AttributeDefs[]
|
||||
*/
|
||||
public function getAttributeList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->getAttributeNameList() as $name) {
|
||||
$list[] = $this->getAttribute($name);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relation definitions list.
|
||||
*
|
||||
* @return RelationDefs[]
|
||||
*/
|
||||
public function getRelationList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->getRelationNameList() as $name) {
|
||||
$list[] = $this->getRelation($name);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an index definitions list.
|
||||
*
|
||||
* @return IndexDefs[]
|
||||
*/
|
||||
public function getIndexList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->getIndexNameList() as $name) {
|
||||
$list[] = $this->getIndex($name);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a field definitions list.
|
||||
*
|
||||
* @return FieldDefs[]
|
||||
*/
|
||||
public function getFieldList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this->getFieldNameList() as $name) {
|
||||
$list[] = $this->getField($name);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has an attribute.
|
||||
*/
|
||||
public function hasAttribute(string $name): bool
|
||||
{
|
||||
$this->cacheAttribute($name);
|
||||
|
||||
return !is_null($this->attributeCache[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has a relation.
|
||||
*/
|
||||
public function hasRelation(string $name): bool
|
||||
{
|
||||
$this->cacheRelation($name);
|
||||
|
||||
return !is_null($this->relationCache[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has an index.
|
||||
*/
|
||||
public function hasIndex(string $name): bool
|
||||
{
|
||||
$this->cacheIndex($name);
|
||||
|
||||
return !is_null($this->indexCache[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has a field.
|
||||
*/
|
||||
public function hasField(string $name): bool
|
||||
{
|
||||
$this->cacheField($name);
|
||||
|
||||
return !is_null($this->fieldCache[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute definitions.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getAttribute(string $name): AttributeDefs
|
||||
{
|
||||
$this->cacheAttribute($name);
|
||||
|
||||
if (!$this->hasAttribute($name)) {
|
||||
throw new RuntimeException("Attribute '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var AttributeDefs */
|
||||
return $this->attributeCache[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get relation definitions.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getRelation(string $name): RelationDefs
|
||||
{
|
||||
$this->cacheRelation($name);
|
||||
|
||||
if (!$this->hasRelation($name)) {
|
||||
throw new RuntimeException("Relation '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var RelationDefs */
|
||||
return $this->relationCache[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get index definitions.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getIndex(string $name): IndexDefs
|
||||
{
|
||||
$this->cacheIndex($name);
|
||||
|
||||
if (!$this->hasIndex($name)) {
|
||||
throw new RuntimeException("Index '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var IndexDefs */
|
||||
return $this->indexCache[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get field definitions.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getField(string $name): FieldDefs
|
||||
{
|
||||
$this->cacheField($name);
|
||||
|
||||
if (!$this->hasField($name)) {
|
||||
throw new RuntimeException("Field '{$name}' does not exist.");
|
||||
}
|
||||
|
||||
/** @var FieldDefs */
|
||||
return $this->fieldCache[$name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get attribute definitions.
|
||||
*/
|
||||
public function tryGetAttribute(string $name): ?AttributeDefs
|
||||
{
|
||||
if (!$this->hasAttribute($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getAttribute($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get field definitions.
|
||||
*/
|
||||
public function tryGetField(string $name): ?FieldDefs
|
||||
{
|
||||
if (!$this->hasField($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getField($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get relation definitions.
|
||||
*/
|
||||
public function tryGetRelation(string $name): ?RelationDefs
|
||||
{
|
||||
if (!$this->hasRelation($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getRelation($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get index definitions.
|
||||
*/
|
||||
public function tryGetIndex(string $name): ?IndexDefs
|
||||
{
|
||||
if (!$this->hasIndex($name)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getIndex($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value by a name.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->data[$name] ?? null;
|
||||
}
|
||||
|
||||
private function cacheAttribute(string $name): void
|
||||
{
|
||||
if (array_key_exists($name, $this->attributeCache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->attributeCache[$name] = $this->loadAttribute($name);
|
||||
}
|
||||
|
||||
private function loadAttribute(string $name): ?AttributeDefs
|
||||
{
|
||||
$raw = $this->data[EntityParam::ATTRIBUTES][$name] ?? null;
|
||||
|
||||
if (!$raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return AttributeDefs::fromRaw($raw, $name);
|
||||
}
|
||||
|
||||
private function cacheRelation(string $name): void
|
||||
{
|
||||
if (array_key_exists($name, $this->relationCache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->relationCache[$name] = $this->loadRelation($name);
|
||||
}
|
||||
|
||||
private function loadRelation(string $name): ?RelationDefs
|
||||
{
|
||||
$raw = $this->data[EntityParam::RELATIONS][$name] ?? null;
|
||||
|
||||
if (!$raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return RelationDefs::fromRaw($raw, $name);
|
||||
}
|
||||
|
||||
private function cacheIndex(string $name): void
|
||||
{
|
||||
if (array_key_exists($name, $this->indexCache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->indexCache[$name] = $this->loadIndex($name);
|
||||
}
|
||||
|
||||
private function loadIndex(string $name): ?IndexDefs
|
||||
{
|
||||
$raw = $this->data[EntityParam::INDEXES][$name] ?? null;
|
||||
|
||||
if (!$raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return IndexDefs::fromRaw($raw, $name);
|
||||
}
|
||||
|
||||
private function cacheField(string $name): void
|
||||
{
|
||||
if (array_key_exists($name, $this->fieldCache)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->fieldCache[$name] = $this->loadField($name);
|
||||
}
|
||||
|
||||
private function loadField(string $name): ?FieldDefs
|
||||
{
|
||||
$raw = $this->data[EntityParam::FIELDS][$name] ?? null;
|
||||
|
||||
if (!$raw) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return FieldDefs::fromRaw($raw, $name);
|
||||
}
|
||||
}
|
||||
104
application/Espo/ORM/Defs/FieldDefs.php
Normal file
104
application/Espo/ORM/Defs/FieldDefs.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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\ORM\Defs;
|
||||
|
||||
use Espo\ORM\Defs\Params\FieldParam;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Field definitions.
|
||||
*/
|
||||
class FieldDefs
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $data;
|
||||
private string $name;
|
||||
|
||||
private function __construct()
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $raw
|
||||
*/
|
||||
public static function fromRaw(array $raw, string $name): self
|
||||
{
|
||||
$obj = new self();
|
||||
$obj->data = $raw;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type.
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
$type = $this->data[FieldParam::TYPE] ?? null;
|
||||
|
||||
if ($type === null) {
|
||||
throw new RuntimeException("Field '$this->name' has no type.");
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is not-storable.
|
||||
*/
|
||||
public function isNotStorable(): bool
|
||||
{
|
||||
return $this->data[FieldParam::NOT_STORABLE] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value by a name.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->data[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has a parameter value.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->data);
|
||||
}
|
||||
}
|
||||
108
application/Espo/ORM/Defs/IndexDefs.php
Normal file
108
application/Espo/ORM/Defs/IndexDefs.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?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\ORM\Defs;
|
||||
|
||||
use Espo\ORM\Defs\Params\IndexParam;
|
||||
|
||||
/**
|
||||
* Index definitions.
|
||||
*/
|
||||
class IndexDefs
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private $data;
|
||||
private string $name;
|
||||
|
||||
private function __construct()
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $raw
|
||||
*/
|
||||
public static function fromRaw(array $raw, string $name): self
|
||||
{
|
||||
$obj = new self();
|
||||
$obj->data = $raw;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a key.
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
return $this->data[IndexParam::KEY] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is unique.
|
||||
*/
|
||||
public function isUnique(): bool
|
||||
{
|
||||
// For bc.
|
||||
if (($this->data['unique'] ?? false)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$type = $this->data[IndexParam::TYPE] ?? null;
|
||||
|
||||
return $type === 'unique';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a column list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getColumnList(): array
|
||||
{
|
||||
return $this->data[IndexParam::COLUMNS] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a flag list.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getFlagList(): array
|
||||
{
|
||||
return $this->data[IndexParam::FLAGS] ?? [];
|
||||
}
|
||||
}
|
||||
96
application/Espo/ORM/Defs/Params/AttributeParam.php
Normal file
96
application/Espo/ORM/Defs/Params/AttributeParam.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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\ORM\Defs\Params;
|
||||
|
||||
/**
|
||||
* An attribute parameter.
|
||||
*/
|
||||
class AttributeParam
|
||||
{
|
||||
/**
|
||||
* A type.
|
||||
*/
|
||||
public const TYPE = 'type';
|
||||
|
||||
/**
|
||||
* Not stored in database.
|
||||
*/
|
||||
public const NOT_STORABLE = 'notStorable';
|
||||
|
||||
/**
|
||||
* A database type.
|
||||
*/
|
||||
public const DB_TYPE = 'dbType';
|
||||
|
||||
/**
|
||||
* A length.
|
||||
*/
|
||||
public const LEN = 'len';
|
||||
|
||||
/**
|
||||
* Not null.
|
||||
*/
|
||||
public const NOT_NULL = 'notNull';
|
||||
|
||||
/**
|
||||
* Autoincrement.
|
||||
*/
|
||||
public const AUTOINCREMENT = 'autoincrement';
|
||||
|
||||
/**
|
||||
* A default value.
|
||||
*/
|
||||
public const DEFAULT = 'default';
|
||||
|
||||
/**
|
||||
* A relation. For foreign attributes.
|
||||
*/
|
||||
public const RELATION = 'relation';
|
||||
|
||||
/**
|
||||
* A foreign attribute name. For foreign attributes.
|
||||
*/
|
||||
public const FOREIGN = 'foreign';
|
||||
|
||||
/**
|
||||
* Precision.
|
||||
*/
|
||||
public const PRECISION = 'precision';
|
||||
|
||||
/**
|
||||
* Scale.
|
||||
*/
|
||||
public const SCALE = 'scale';
|
||||
|
||||
/**
|
||||
* Dependee attributes.
|
||||
*/
|
||||
public const DEPENDEE_ATTRIBUTE_LIST = 'dependeeAttributeList';
|
||||
}
|
||||
56
application/Espo/ORM/Defs/Params/EntityParam.php
Normal file
56
application/Espo/ORM/Defs/Params/EntityParam.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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\ORM\Defs\Params;
|
||||
|
||||
/**
|
||||
* An entity parameter.
|
||||
*/
|
||||
class EntityParam
|
||||
{
|
||||
/**
|
||||
* Fields.
|
||||
*/
|
||||
public const FIELDS = 'fields';
|
||||
|
||||
/**
|
||||
* Attributes.
|
||||
*/
|
||||
public const ATTRIBUTES = 'attributes';
|
||||
|
||||
/**
|
||||
* Relations.
|
||||
*/
|
||||
public const RELATIONS = 'relations';
|
||||
|
||||
/**
|
||||
* Indexes.
|
||||
*/
|
||||
public const INDEXES = 'indexes';
|
||||
}
|
||||
106
application/Espo/ORM/Defs/Params/FieldParam.php
Normal file
106
application/Espo/ORM/Defs/Params/FieldParam.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?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\ORM\Defs\Params;
|
||||
|
||||
/**
|
||||
* A field parameter.
|
||||
*/
|
||||
class FieldParam
|
||||
{
|
||||
/**
|
||||
* A type.
|
||||
*/
|
||||
public const TYPE = 'type';
|
||||
|
||||
/**
|
||||
* Not stored in database.
|
||||
*/
|
||||
public const NOT_STORABLE = 'notStorable';
|
||||
|
||||
/**
|
||||
* A database type.
|
||||
*/
|
||||
public const DB_TYPE = 'dbType';
|
||||
|
||||
/**
|
||||
* Autoincrement.
|
||||
*/
|
||||
public const AUTOINCREMENT = 'autoincrement';
|
||||
|
||||
/**
|
||||
* A max length.
|
||||
*/
|
||||
public const MAX_LENGTH = 'maxLength';
|
||||
|
||||
/**
|
||||
* Not null.
|
||||
*/
|
||||
public const NOT_NULL = 'notNull';
|
||||
|
||||
/**
|
||||
* A default value.
|
||||
*/
|
||||
public const DEFAULT = 'default';
|
||||
|
||||
/**
|
||||
* Read-only.
|
||||
*/
|
||||
public const READ_ONLY = 'readOnly';
|
||||
|
||||
/**
|
||||
* Decimal.
|
||||
*/
|
||||
public const DECIMAL = 'decimal';
|
||||
|
||||
/**
|
||||
* Precision.
|
||||
*/
|
||||
public const PRECISION = 'precision';
|
||||
|
||||
/**
|
||||
* Scale.
|
||||
*/
|
||||
public const SCALE = 'scale';
|
||||
|
||||
/**
|
||||
* Dependee attributes.
|
||||
*/
|
||||
public const DEPENDEE_ATTRIBUTE_LIST = 'dependeeAttributeList';
|
||||
|
||||
/**
|
||||
* Foreign link.
|
||||
*/
|
||||
public const LINK = 'link';
|
||||
|
||||
/**
|
||||
* Foreign field.
|
||||
*/
|
||||
public const FIELD = 'field';
|
||||
}
|
||||
56
application/Espo/ORM/Defs/Params/IndexParam.php
Normal file
56
application/Espo/ORM/Defs/Params/IndexParam.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?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\ORM\Defs\Params;
|
||||
|
||||
/**
|
||||
* An index parameter.
|
||||
*/
|
||||
class IndexParam
|
||||
{
|
||||
/**
|
||||
* A type.
|
||||
*/
|
||||
public const TYPE = 'type';
|
||||
|
||||
/**
|
||||
* A key.
|
||||
*/
|
||||
public const KEY = 'key';
|
||||
|
||||
/**
|
||||
* Columns.
|
||||
*/
|
||||
public const COLUMNS = 'columns';
|
||||
|
||||
/**
|
||||
* Flags.
|
||||
*/
|
||||
public const FLAGS = 'flags';
|
||||
}
|
||||
110
application/Espo/ORM/Defs/Params/RelationParam.php
Normal file
110
application/Espo/ORM/Defs/Params/RelationParam.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?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\ORM\Defs\Params;
|
||||
|
||||
/**
|
||||
* A relation parameter.
|
||||
*/
|
||||
class RelationParam
|
||||
{
|
||||
/**
|
||||
* A type.
|
||||
*/
|
||||
public const TYPE = 'type';
|
||||
|
||||
/**
|
||||
* Indexes.
|
||||
*/
|
||||
public const INDEXES = 'indexes';
|
||||
|
||||
/**
|
||||
* A relation name.
|
||||
*/
|
||||
public const RELATION_NAME = 'relationName';
|
||||
|
||||
/**
|
||||
* A foreign entity type.
|
||||
*/
|
||||
public const ENTITY = 'entity';
|
||||
|
||||
/**
|
||||
* A foreign relation name.
|
||||
*/
|
||||
public const FOREIGN = 'foreign';
|
||||
|
||||
/**
|
||||
* Conditions.
|
||||
*/
|
||||
public const CONDITIONS = 'conditions';
|
||||
|
||||
/**
|
||||
* Additional columns.
|
||||
*/
|
||||
public const ADDITIONAL_COLUMNS = 'additionalColumns';
|
||||
|
||||
/**
|
||||
* A key.
|
||||
*/
|
||||
public const KEY = 'key';
|
||||
|
||||
/**
|
||||
* A foreign key.
|
||||
*/
|
||||
public const FOREIGN_KEY = 'foreignKey';
|
||||
|
||||
/**
|
||||
* Middle keys.
|
||||
*/
|
||||
public const MID_KEYS = 'midKeys';
|
||||
|
||||
/**
|
||||
* No join.
|
||||
*/
|
||||
public const NO_JOIN = 'noJoin';
|
||||
|
||||
/**
|
||||
* Deferred load.
|
||||
*/
|
||||
public const DEFERRED_LOAD = 'deferredLoad';
|
||||
|
||||
/**
|
||||
* Default order by. Applied on the entity level.
|
||||
*
|
||||
* @since 9.2.5
|
||||
*/
|
||||
public const ORDER_BY = 'orderBy';
|
||||
|
||||
/**
|
||||
* Default order. Applied on the entity level.
|
||||
*
|
||||
* @since 9.2.5
|
||||
*/
|
||||
public const ORDER = 'order';
|
||||
}
|
||||
362
application/Espo/ORM/Defs/RelationDefs.php
Normal file
362
application/Espo/ORM/Defs/RelationDefs.php
Normal file
@@ -0,0 +1,362 @@
|
||||
<?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\ORM\Defs;
|
||||
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Relation definitions.
|
||||
*/
|
||||
class RelationDefs
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $data;
|
||||
private string $name;
|
||||
|
||||
private function __construct()
|
||||
{}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $raw
|
||||
*/
|
||||
public static function fromRaw(array $raw, string $name): self
|
||||
{
|
||||
$obj = new self();
|
||||
$obj->data = $raw;
|
||||
$obj->name = $name;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a name.
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a type.
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
$type = $this->data[RelationParam::TYPE] ?? null;
|
||||
|
||||
if ($type === null) {
|
||||
throw new RuntimeException("Relation '{$this->name}' has no type.");
|
||||
}
|
||||
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is Many-to-Many.
|
||||
*/
|
||||
public function isManyToMany(): bool
|
||||
{
|
||||
return $this->getType() === Entity::MANY_MANY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is Has-Many (One-to-Many).
|
||||
*/
|
||||
public function isHasMany(): bool
|
||||
{
|
||||
return $this->getType() === Entity::HAS_MANY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is Has-One (Many-to-One or One-to-One).
|
||||
*/
|
||||
public function isHasOne(): bool
|
||||
{
|
||||
return $this->getType() === Entity::HAS_ONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is Has-Children (Parent-to-Children).
|
||||
*/
|
||||
public function isHasChildren(): bool
|
||||
{
|
||||
return $this->getType() === Entity::HAS_CHILDREN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is Belongs-to (Many-to-One).
|
||||
*/
|
||||
public function isBelongsTo(): bool
|
||||
{
|
||||
return $this->getType() === Entity::BELONGS_TO;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether is Belongs-to-Parent (Children-to-Parent).
|
||||
*/
|
||||
public function isBelongsToParent(): bool
|
||||
{
|
||||
return $this->getType() === Entity::BELONGS_TO_PARENT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether it has a foreign entity type is defined.
|
||||
*/
|
||||
public function hasForeignEntityType(): bool
|
||||
{
|
||||
return isset($this->data[RelationParam::ENTITY]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign entity type.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getForeignEntityType(): string
|
||||
{
|
||||
if (!$this->hasForeignEntityType()) {
|
||||
throw new RuntimeException("No 'entity' parameter defined in the relation '{$this->name}'.");
|
||||
}
|
||||
|
||||
return $this->data[RelationParam::ENTITY];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign entity type.
|
||||
*/
|
||||
public function tryGetForeignEntityType(): ?string
|
||||
{
|
||||
if (!$this->hasForeignEntityType()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getForeignEntityType();
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether it has a foreign relation name.
|
||||
*/
|
||||
public function hasForeignRelationName(): bool
|
||||
{
|
||||
return isset($this->data[RelationParam::FOREIGN]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to get a foreign relation name.
|
||||
*
|
||||
* @since 8.3.0
|
||||
*/
|
||||
public function tryGetForeignRelationName(): ?string
|
||||
{
|
||||
if (!$this->hasForeignRelationName()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getForeignRelationName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign relation name.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getForeignRelationName(): string
|
||||
{
|
||||
if (!$this->hasForeignRelationName()) {
|
||||
throw new RuntimeException("No 'foreign' parameter defined in the relation '{$this->name}'.");
|
||||
}
|
||||
|
||||
return $this->data[RelationParam::FOREIGN];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a foreign key is defined.
|
||||
*/
|
||||
public function hasForeignKey(): bool
|
||||
{
|
||||
return isset($this->data[RelationParam::FOREIGN_KEY]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign key.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getForeignKey(): string
|
||||
{
|
||||
if (!$this->hasForeignKey()) {
|
||||
throw new RuntimeException("No 'foreignKey' parameter defined in the relation '{$this->name}'.");
|
||||
}
|
||||
|
||||
return $this->data[RelationParam::FOREIGN_KEY];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a key is defined.
|
||||
*/
|
||||
public function hasKey(): bool
|
||||
{
|
||||
return isset($this->data[RelationParam::KEY]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a key.
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
if (!$this->hasKey()) {
|
||||
throw new RuntimeException("No 'key' parameter defined in the relation '{$this->name}'.");
|
||||
}
|
||||
|
||||
return $this->data[RelationParam::KEY];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a mid-key is defined. For Many-to-Many relationships only.
|
||||
*/
|
||||
public function hasMidKey(): bool
|
||||
{
|
||||
return !is_null($this->data[RelationParam::MID_KEYS][0] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mid-key. For Many-to-Many relationships only.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getMidKey(): string
|
||||
{
|
||||
if (!$this->hasMidKey()) {
|
||||
throw new RuntimeException("No 'midKey' parameter defined in the relation '{$this->name}'.");
|
||||
}
|
||||
|
||||
return $this->data[RelationParam::MID_KEYS][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a foreign mid-key is defined. For Many-to-Many relationships only.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function hasForeignMidKey(): bool
|
||||
{
|
||||
return !is_null($this->data[RelationParam::MID_KEYS][1] ?? null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a foreign mid-key. For Many-to-Many relationships only.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getForeignMidKey(): string
|
||||
{
|
||||
if (!$this->hasForeignMidKey()) {
|
||||
throw new RuntimeException("No 'foreignMidKey' parameter defined in the relation '{$this->name}'.");
|
||||
}
|
||||
|
||||
return $this->data[RelationParam::MID_KEYS][1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a relationship name is defined.
|
||||
*/
|
||||
public function hasRelationshipName(): bool
|
||||
{
|
||||
return isset($this->data[RelationParam::RELATION_NAME]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a relationship name.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getRelationshipName(): string
|
||||
{
|
||||
if (!$this->hasRelationshipName()) {
|
||||
throw new RuntimeException("No 'relationName' parameter defined in the relation '{$this->name}'.");
|
||||
}
|
||||
|
||||
return $this->data[RelationParam::RELATION_NAME];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get indexes.
|
||||
*
|
||||
* @return IndexDefs[]
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function getIndexList(): array
|
||||
{
|
||||
if ($this->getType() !== Entity::MANY_MANY) {
|
||||
throw new RuntimeException("Can't get indexes.");
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach (($this->data[RelationParam::INDEXES] ?? []) as $name => $item) {
|
||||
$list[] = IndexDefs::fromRaw($item, $name);
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get additional middle table conditions.
|
||||
*
|
||||
* @return array<string, ?scalar>
|
||||
*/
|
||||
public function getConditions(): array
|
||||
{
|
||||
if ($this->getType() !== Entity::MANY_MANY) {
|
||||
throw new RuntimeException("Can't get conditions for non many-many relationship.");
|
||||
}
|
||||
|
||||
return $this->getParam(RelationParam::CONDITIONS) ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a parameter is set.
|
||||
*/
|
||||
public function hasParam(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter value by a name.
|
||||
*/
|
||||
public function getParam(string $name): mixed
|
||||
{
|
||||
return $this->data[$name] ?? null;
|
||||
}
|
||||
}
|
||||
213
application/Espo/ORM/Entity.php
Normal file
213
application/Espo/ORM/Entity.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Espo\ORM\Type\AttributeType;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* An entity. Represents a single record in DB.
|
||||
*/
|
||||
interface Entity
|
||||
{
|
||||
public const ID = AttributeType::ID;
|
||||
public const VARCHAR = AttributeType::VARCHAR;
|
||||
public const INT = AttributeType::INT;
|
||||
public const FLOAT = AttributeType::FLOAT;
|
||||
public const TEXT = AttributeType::TEXT;
|
||||
public const BOOL = AttributeType::BOOL;
|
||||
public const FOREIGN_ID = AttributeType::FOREIGN_ID;
|
||||
public const FOREIGN = AttributeType::FOREIGN;
|
||||
public const FOREIGN_TYPE = AttributeType::FOREIGN_TYPE;
|
||||
public const DATE = AttributeType::DATE;
|
||||
public const DATETIME = AttributeType::DATETIME;
|
||||
public const JSON_ARRAY = AttributeType::JSON_ARRAY;
|
||||
public const JSON_OBJECT = AttributeType::JSON_OBJECT;
|
||||
public const PASSWORD = AttributeType::PASSWORD;
|
||||
|
||||
public const MANY_MANY = RelationType::MANY_MANY;
|
||||
public const HAS_MANY = RelationType::HAS_MANY;
|
||||
public const BELONGS_TO = RelationType::BELONGS_TO;
|
||||
public const HAS_ONE = RelationType::HAS_ONE;
|
||||
public const BELONGS_TO_PARENT = RelationType::BELONGS_TO_PARENT;
|
||||
public const HAS_CHILDREN = RelationType::HAS_CHILDREN;
|
||||
|
||||
/**
|
||||
* Get an entity ID.
|
||||
*
|
||||
* @return non-empty-string
|
||||
* @throws RuntimeException If an ID is not set.
|
||||
*/
|
||||
public function getId(): string;
|
||||
|
||||
/**
|
||||
* Whether an ID is set.
|
||||
*/
|
||||
public function hasId(): bool;
|
||||
|
||||
/**
|
||||
* Reset all attributes (empty an entity).
|
||||
*/
|
||||
public function reset(): void;
|
||||
|
||||
/**
|
||||
* Set an attribute or multiple attributes.
|
||||
*
|
||||
* Two usage options:
|
||||
* - `set($attribute, $value)`
|
||||
* - `set($valueMap)`
|
||||
*
|
||||
* @param string|stdClass|array<string, mixed> $attribute
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set($attribute, $value = null): static;
|
||||
|
||||
/**
|
||||
* Set multiple attributes.
|
||||
*
|
||||
* @param array<string, mixed>|stdClass $valueMap Values.
|
||||
* @since v8.1.0.
|
||||
*/
|
||||
public function setMultiple(array|stdClass $valueMap): static;
|
||||
|
||||
/**
|
||||
* Get an attribute value.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $attribute);
|
||||
|
||||
/**
|
||||
* Whether an attribute value is set.
|
||||
*/
|
||||
public function has(string $attribute): bool;
|
||||
|
||||
/**
|
||||
* Clear an attribute value.
|
||||
*/
|
||||
public function clear(string $attribute): void;
|
||||
|
||||
/**
|
||||
* Get an entity type.
|
||||
*/
|
||||
public function getEntityType(): string;
|
||||
|
||||
/**
|
||||
* Get attribute list defined for an entity type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAttributeList(): array;
|
||||
|
||||
/**
|
||||
* Get relation list defined for an entity type.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getRelationList(): array;
|
||||
|
||||
/**
|
||||
* Whether an entity type has an attribute defined.
|
||||
*/
|
||||
public function hasAttribute(string $attribute): bool;
|
||||
|
||||
/**
|
||||
* Whether an entity type has a relation defined.
|
||||
*/
|
||||
public function hasRelation(string $relation): bool;
|
||||
|
||||
/**
|
||||
* Get an attribute type.
|
||||
*/
|
||||
public function getAttributeType(string $attribute): ?string;
|
||||
|
||||
/**
|
||||
* Get a relation type.
|
||||
*/
|
||||
public function getRelationType(string $relation): ?string;
|
||||
|
||||
/**
|
||||
* Whether an entity is new.
|
||||
*/
|
||||
public function isNew(): bool;
|
||||
|
||||
/**
|
||||
* Set an entity as fetched. All current attribute values will be set as those that are fetched
|
||||
* from the database.
|
||||
*/
|
||||
public function setAsFetched(): void;
|
||||
|
||||
/**
|
||||
* Whether is fetched from the database.
|
||||
*/
|
||||
public function isFetched(): bool;
|
||||
|
||||
/**
|
||||
* Whether an attribute was changed (since syncing with the database).
|
||||
*/
|
||||
public function isAttributeChanged(string $name): bool;
|
||||
|
||||
/**
|
||||
* Get a fetched value of a specific attribute.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFetched(string $attribute);
|
||||
|
||||
/**
|
||||
* Whether a fetched value is set for a specific attribute.
|
||||
*/
|
||||
public function hasFetched(string $attribute): bool;
|
||||
|
||||
/**
|
||||
* Set a fetched value for a specific attribute.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function setFetched(string $attribute, $value): static;
|
||||
|
||||
/**
|
||||
* Get values.
|
||||
*/
|
||||
public function getValueMap(): stdClass;
|
||||
|
||||
/**
|
||||
* Set as not new. Meaning the entity is fetched or already saved.
|
||||
*/
|
||||
public function setAsNotNew(): void;
|
||||
|
||||
/**
|
||||
* Copy all current values to fetched values. All current attribute values will be set as those
|
||||
* that are fetched from DB.
|
||||
*/
|
||||
public function updateFetchedValues(): void;
|
||||
}
|
||||
471
application/Espo/ORM/EntityCollection.php
Normal file
471
application/Espo/ORM/EntityCollection.php
Normal file
@@ -0,0 +1,471 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Iterator;
|
||||
use Countable;
|
||||
use ArrayAccess;
|
||||
use SeekableIterator;
|
||||
use RuntimeException;
|
||||
use OutOfBoundsException;
|
||||
use InvalidArgumentException;
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* A standard collection of entities. It allocates a memory for all entities.
|
||||
*
|
||||
* @template TEntity of Entity = Entity
|
||||
* @implements Iterator<int, TEntity>
|
||||
* @implements Collection<TEntity>
|
||||
* @implements ArrayAccess<int, TEntity>
|
||||
* @implements SeekableIterator<int, TEntity>
|
||||
*/
|
||||
class EntityCollection implements Collection, Iterator, Countable, ArrayAccess, SeekableIterator
|
||||
{
|
||||
private ?EntityFactory $entityFactory;
|
||||
private ?string $entityType;
|
||||
private int $position = 0;
|
||||
private bool $isFetched = false;
|
||||
/** @var array<TEntity|array<string, mixed>> */
|
||||
protected array $dataList = [];
|
||||
|
||||
/**
|
||||
* @param array<TEntity|array<string, mixed>> $dataList
|
||||
*/
|
||||
public function __construct(
|
||||
array $dataList = [],
|
||||
?string $entityType = null,
|
||||
?EntityFactory $entityFactory = null
|
||||
) {
|
||||
$this->dataList = $dataList;
|
||||
$this->entityType = $entityType;
|
||||
$this->entityFactory = $entityFactory;
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->position = 0;
|
||||
|
||||
while (!$this->valid() && $this->position <= $this->getLastValidKey()) {
|
||||
$this->position ++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return TEntity
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return $this->getEntityByOffset($this->position);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
do {
|
||||
$this->position ++;
|
||||
|
||||
$next = false;
|
||||
|
||||
if (!$this->valid() && $this->position <= $this->getLastValidKey()) {
|
||||
$next = true;
|
||||
}
|
||||
} while ($next);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
private function getLastValidKey()
|
||||
{
|
||||
$keys = array_keys($this->dataList);
|
||||
|
||||
$i = end($keys);
|
||||
|
||||
while ($i > 0) {
|
||||
if (isset($this->dataList[$i])) {
|
||||
break;
|
||||
}
|
||||
|
||||
$i--;
|
||||
}
|
||||
|
||||
return $i;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->dataList[$this->position]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->dataList[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @return ?TEntity
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
if (!isset($this->dataList[$offset])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getEntityByOffset($offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
if (!($value instanceof Entity)) {
|
||||
throw new InvalidArgumentException('Only Entity is allowed to be added to EntityCollection.');
|
||||
}
|
||||
|
||||
/** @var TEntity $value */
|
||||
|
||||
if (is_null($offset)) {
|
||||
$this->dataList[] = $value;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->dataList[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->dataList[$offset]);
|
||||
}
|
||||
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->dataList);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
*/
|
||||
public function seek($offset): void
|
||||
{
|
||||
$this->position = $offset;
|
||||
|
||||
if (!$this->valid()) {
|
||||
throw new OutOfBoundsException("Invalid seek offset ($offset).");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TEntity $entity
|
||||
*/
|
||||
public function append(Entity $entity): void
|
||||
{
|
||||
$this->dataList[] = $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @return TEntity
|
||||
*/
|
||||
private function getEntityByOffset($offset): Entity
|
||||
{
|
||||
if (!array_key_exists($offset, $this->dataList)) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$value = $this->dataList[$offset];
|
||||
|
||||
if ($value instanceof Entity) {
|
||||
/** @var TEntity */
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (is_array($value)) {
|
||||
$this->dataList[$offset] = $this->buildEntityFromArray($value);
|
||||
|
||||
return $this->dataList[$offset];
|
||||
}
|
||||
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $dataArray
|
||||
* @return TEntity
|
||||
*/
|
||||
protected function buildEntityFromArray(array $dataArray): Entity
|
||||
{
|
||||
if (!$this->entityFactory) {
|
||||
throw new RuntimeException("Can't build from array. EntityFactory was not passed to the constructor.");
|
||||
}
|
||||
|
||||
assert($this->entityType !== null);
|
||||
|
||||
/** @var TEntity $entity */
|
||||
$entity = $this->entityFactory->create($this->entityType);
|
||||
|
||||
$entity->set($dataArray);
|
||||
|
||||
if ($this->isFetched) {
|
||||
$entity->setAsFetched();
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity type.
|
||||
*/
|
||||
public function getEntityType(): ?string
|
||||
{
|
||||
return $this->entityType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<TEntity|array<string, mixed>>
|
||||
*/
|
||||
public function getDataList(): array
|
||||
{
|
||||
return $this->dataList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge with another collection.
|
||||
*
|
||||
* @param EntityCollection<TEntity> $collection
|
||||
*/
|
||||
public function merge(EntityCollection $collection): void
|
||||
{
|
||||
$incomingDataList = $collection->getDataList();
|
||||
|
||||
foreach ($incomingDataList as $v) {
|
||||
if (!$this->contains($v)) {
|
||||
$this->dataList[] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a collection contains a specific item.
|
||||
*
|
||||
* @param TEntity|array<string, mixed> $value
|
||||
*/
|
||||
public function contains($value): bool
|
||||
{
|
||||
if ($this->indexOf($value) !== false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TEntity|array<string, mixed> $value
|
||||
* @return false|int
|
||||
*/
|
||||
public function indexOf($value)
|
||||
{
|
||||
$index = 0;
|
||||
|
||||
if (is_array($value)) {
|
||||
foreach ($this->dataList as $v) {
|
||||
if (is_array($v)) {
|
||||
if ($value[Attribute::ID] == $v[Attribute::ID]) {
|
||||
return $index;
|
||||
}
|
||||
} else if ($v instanceof Entity) {
|
||||
if ($value[Attribute::ID] == $v->getId()) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
$index ++;
|
||||
}
|
||||
} else if ($value instanceof Entity) {
|
||||
foreach ($this->dataList as $v) {
|
||||
if (is_array($v)) {
|
||||
if ($value->getId() == $v[Attribute::ID]) {
|
||||
return $index;
|
||||
}
|
||||
} else if ($v instanceof Entity) {
|
||||
if ($value === $v) {
|
||||
return $index;
|
||||
}
|
||||
}
|
||||
|
||||
$index ++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function getValueMapList(): array
|
||||
{
|
||||
$list = [];
|
||||
|
||||
foreach ($this as $entity) {
|
||||
$item = $entity->getValueMap();
|
||||
|
||||
$list[] = $item;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark as fetched from DB.
|
||||
*/
|
||||
public function setAsFetched(): void
|
||||
{
|
||||
$this->isFetched = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is fetched from DB.
|
||||
*/
|
||||
public function isFetched(): bool
|
||||
{
|
||||
return $this->isFetched;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from SthCollection.
|
||||
*
|
||||
* @param SthCollection<TEntity> $sthCollection
|
||||
* @return self<TEntity>
|
||||
*/
|
||||
public static function fromSthCollection(SthCollection $sthCollection): self
|
||||
{
|
||||
$entityList = [];
|
||||
|
||||
foreach ($sthCollection as $entity) {
|
||||
$entityList[] = $entity;
|
||||
}
|
||||
|
||||
/** @var self<TEntity> $obj */
|
||||
$obj = new EntityCollection($entityList, $sthCollection->getEntityType());
|
||||
$obj->setAsFetched();
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter.
|
||||
*
|
||||
* @param Closure(TEntity): bool $callback A filter callback.
|
||||
* @return self<TEntity> A filtered collection. A new instance.
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function filter(Closure $callback): self
|
||||
{
|
||||
$newList = [];
|
||||
|
||||
foreach ($this as $entity) {
|
||||
if ($callback($entity)) {
|
||||
$newList[] = $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return new EntityCollection($newList, $this->entityType, $this->entityFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort.
|
||||
*
|
||||
* @param Closure(TEntity, TEntity): int $callback The comparison function.
|
||||
* @return self<TEntity> A sorted collection. A new instance.
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function sort(Closure $callback): self
|
||||
{
|
||||
$newList = [...$this];
|
||||
|
||||
usort($newList, $callback);
|
||||
|
||||
return new EntityCollection($newList, $this->entityType, $this->entityFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse.
|
||||
*
|
||||
* @return self<TEntity> A reversed collection.
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public function reverse(): self
|
||||
{
|
||||
$newList = array_reverse([...$this]);
|
||||
|
||||
return new EntityCollection($newList, $this->entityType, $this->entityFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find.
|
||||
*
|
||||
* @param Closure(TEntity): bool $callback A filter callback.
|
||||
* @return ?TEntity
|
||||
* @since 9.1.0
|
||||
* @noinspection PhpDocSignatureInspection
|
||||
*/
|
||||
public function find(Closure $callback): ?Entity
|
||||
{
|
||||
foreach ($this as $entity) {
|
||||
if ($callback($entity)) {
|
||||
return $entity;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
50
application/Espo/ORM/EntityFactory.php
Normal file
50
application/Espo/ORM/EntityFactory.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Espo\ORM\Value\ValueAccessorFactory;
|
||||
|
||||
interface EntityFactory
|
||||
{
|
||||
/**
|
||||
* Create an entity.
|
||||
*/
|
||||
public function create(string $entityType): Entity;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setEntityManager(EntityManager $entityManager): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function setValueAccessorFactory(ValueAccessorFactory $valueAccessorFactory): void;
|
||||
}
|
||||
477
application/Espo/ORM/EntityManager.php
Normal file
477
application/Espo/ORM/EntityManager.php
Normal file
@@ -0,0 +1,477 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Espo\ORM\Defs\Defs;
|
||||
use Espo\ORM\Executor\DefaultQueryExecutor;
|
||||
use Espo\ORM\Executor\DefaultSqlExecutor;
|
||||
use Espo\ORM\Executor\QueryExecutor;
|
||||
use Espo\ORM\Executor\SqlExecutor;
|
||||
use Espo\ORM\QueryComposer\QueryComposer;
|
||||
use Espo\ORM\QueryComposer\QueryComposerFactory;
|
||||
use Espo\ORM\QueryComposer\QueryComposerWrapper;
|
||||
use Espo\ORM\Mapper\Mapper;
|
||||
use Espo\ORM\Mapper\MapperFactory;
|
||||
use Espo\ORM\Mapper\BaseMapper;
|
||||
use Espo\ORM\Relation\RelationsMap;
|
||||
use Espo\ORM\Repository\RDBRelation;
|
||||
use Espo\ORM\Repository\RepositoryFactory;
|
||||
use Espo\ORM\Repository\Repository;
|
||||
use Espo\ORM\Repository\RDBRepository;
|
||||
use Espo\ORM\Repository\Util as RepositoryUtil;
|
||||
use Espo\ORM\Locker\Locker;
|
||||
use Espo\ORM\Locker\BaseLocker;
|
||||
use Espo\ORM\Locker\MysqlLocker;
|
||||
use Espo\ORM\Value\ValueAccessorFactory;
|
||||
use Espo\ORM\Value\ValueFactoryFactory;
|
||||
use Espo\ORM\Value\AttributeExtractorFactory;
|
||||
use Espo\ORM\PDO\PDOProvider;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* A central access point to ORM functionality.
|
||||
*/
|
||||
class EntityManager
|
||||
{
|
||||
private CollectionFactory $collectionFactory;
|
||||
private QueryComposer $queryComposer;
|
||||
private QueryExecutor $queryExecutor;
|
||||
private QueryBuilder $queryBuilder;
|
||||
private SqlExecutor $sqlExecutor;
|
||||
private TransactionManager $transactionManager;
|
||||
private Locker $locker;
|
||||
|
||||
private const RDB_MAPPER_NAME = 'RDB';
|
||||
|
||||
/** @var array<string, Repository<Entity>> */
|
||||
private $repositoryHash = [];
|
||||
/** @var array<string, Mapper> */
|
||||
private $mappers = [];
|
||||
|
||||
/**
|
||||
* @param AttributeExtractorFactory<object> $attributeExtractorFactory
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function __construct(
|
||||
private DatabaseParams $databaseParams,
|
||||
private Metadata $metadata,
|
||||
private RepositoryFactory $repositoryFactory,
|
||||
private EntityFactory $entityFactory,
|
||||
private QueryComposerFactory $queryComposerFactory,
|
||||
ValueFactoryFactory $valueFactoryFactory,
|
||||
AttributeExtractorFactory $attributeExtractorFactory,
|
||||
EventDispatcher $eventDispatcher,
|
||||
private PDOProvider $pdoProvider,
|
||||
private RelationsMap $relationsMap,
|
||||
private ?MapperFactory $mapperFactory = null,
|
||||
?QueryExecutor $queryExecutor = null,
|
||||
?SqlExecutor $sqlExecutor = null,
|
||||
) {
|
||||
if (!$this->databaseParams->getPlatform()) {
|
||||
throw new RuntimeException("No 'platform' parameter.");
|
||||
}
|
||||
|
||||
$valueAccessorFactory = new ValueAccessorFactory(
|
||||
$valueFactoryFactory,
|
||||
$attributeExtractorFactory,
|
||||
$eventDispatcher
|
||||
);
|
||||
|
||||
$this->entityFactory->setEntityManager($this);
|
||||
$this->entityFactory->setValueAccessorFactory($valueAccessorFactory);
|
||||
|
||||
$this->initQueryComposer();
|
||||
|
||||
$this->sqlExecutor = $sqlExecutor ?? new DefaultSqlExecutor($this->pdoProvider);
|
||||
$this->queryExecutor = $queryExecutor ??
|
||||
new DefaultQueryExecutor($this->sqlExecutor, $this->getQueryComposer());
|
||||
$this->queryBuilder = new QueryBuilder();
|
||||
$this->collectionFactory = new CollectionFactory($this);
|
||||
$this->transactionManager = new TransactionManager($this->pdoProvider->get(), $this->queryComposer);
|
||||
|
||||
$this->initLocker();
|
||||
}
|
||||
|
||||
private function initQueryComposer(): void
|
||||
{
|
||||
$platform = $this->databaseParams->getPlatform() ?? '';
|
||||
|
||||
$this->queryComposer = $this->queryComposerFactory->create($platform);
|
||||
}
|
||||
|
||||
private function initLocker(): void
|
||||
{
|
||||
$platform = $this->databaseParams->getPlatform() ?? '';
|
||||
|
||||
$className = BaseLocker::class;
|
||||
|
||||
if ($platform === 'Mysql') {
|
||||
$className = MysqlLocker::class;
|
||||
}
|
||||
|
||||
$this->locker = new $className($this->pdoProvider->get(), $this->queryComposer, $this->transactionManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the query composer.
|
||||
*/
|
||||
public function getQueryComposer(): QueryComposerWrapper
|
||||
{
|
||||
return new QueryComposerWrapper($this->queryComposer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transaction manager.
|
||||
*/
|
||||
public function getTransactionManager(): TransactionManager
|
||||
{
|
||||
return $this->transactionManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the locker.
|
||||
*/
|
||||
public function getLocker(): Locker
|
||||
{
|
||||
return $this->locker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a mapper.
|
||||
*/
|
||||
public function getMapper(string $name = self::RDB_MAPPER_NAME): Mapper
|
||||
{
|
||||
if (!array_key_exists($name, $this->mappers)) {
|
||||
$this->loadMapper($name);
|
||||
}
|
||||
|
||||
return $this->mappers[$name];
|
||||
}
|
||||
|
||||
private function loadMapper(string $name): void
|
||||
{
|
||||
if ($name === self::RDB_MAPPER_NAME) {
|
||||
$mapper = new BaseMapper(
|
||||
$this->pdoProvider->get(),
|
||||
$this->entityFactory,
|
||||
$this->collectionFactory,
|
||||
$this->metadata,
|
||||
$this->queryExecutor
|
||||
);
|
||||
|
||||
$this->mappers[$name] = $mapper;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->mapperFactory) {
|
||||
throw new RuntimeException("Could not create mapper '$name'. No mapper factory.");
|
||||
}
|
||||
|
||||
$this->mappers[$name] = $this->mapperFactory->create($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity. If $id is null, a new entity instance is created.
|
||||
* If an entity with a specified ID does not exist, then NULL is returned.
|
||||
* @deprecated As of v9.0. Use getNewEntity and getEntityById instead.
|
||||
* @todo Remove in v11.0.
|
||||
*/
|
||||
public function getEntity(string $entityType, ?string $id = null): ?Entity
|
||||
{
|
||||
if (!$this->hasRepository($entityType)) {
|
||||
throw new RuntimeException("ORM: Repository '$entityType' does not exist.");
|
||||
}
|
||||
|
||||
if ($id === null) {
|
||||
return $this->getRepository($entityType)->getNew();
|
||||
}
|
||||
|
||||
return $this->getRepository($entityType)->getById($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new entity instance (w/o storing to DB).
|
||||
*/
|
||||
public function getNewEntity(string $entityType): Entity
|
||||
{
|
||||
/**
|
||||
* @var Entity
|
||||
* @noinspection PhpDeprecationInspection
|
||||
*/
|
||||
return $this->getEntity($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an entity by ID. If an entity does not exist, NULL is returned.
|
||||
*/
|
||||
public function getEntityById(string $entityType, string $id): ?Entity
|
||||
{
|
||||
/** @noinspection PhpDeprecationInspection */
|
||||
return $this->getEntity($entityType, $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store an entity.
|
||||
*
|
||||
* @param array<string, mixed> $options Options.
|
||||
*/
|
||||
public function saveEntity(Entity $entity, array $options = []): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$this->getRepository($entityType)->save($entity, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an entity as deleted (in database).
|
||||
*
|
||||
* @param array<string, mixed> $options Options.
|
||||
*/
|
||||
public function removeEntity(Entity $entity, array $options = []): void
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$this->getRepository($entityType)->remove($entity, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh an entity from the database, overwriting made changes, if any.
|
||||
* Can be used to fetch attributes that were not fetched initially.
|
||||
*
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function refreshEntity(Entity $entity): void
|
||||
{
|
||||
if ($entity->isNew()) {
|
||||
throw new RuntimeException("Can't refresh a new entity.");
|
||||
}
|
||||
|
||||
if (!$entity->hasId()) {
|
||||
throw new RuntimeException("Can't refresh an entity w/o ID.");
|
||||
}
|
||||
|
||||
$fetchedEntity = $this->getEntityById($entity->getEntityType(), $entity->getId());
|
||||
|
||||
if (!$fetchedEntity) {
|
||||
throw new RuntimeException("Can't refresh a non-existent entity.");
|
||||
}
|
||||
|
||||
$this->relationsMap->get($entity)?->resetAll();
|
||||
|
||||
$prevMap = get_object_vars($entity->getValueMap());
|
||||
$fetchedMap = get_object_vars($fetchedEntity->getValueMap());
|
||||
|
||||
foreach (array_keys($prevMap) as $attribute) {
|
||||
if (!array_key_exists($attribute, $fetchedMap)) {
|
||||
$entity->clear($attribute);
|
||||
}
|
||||
}
|
||||
|
||||
$entity->set($fetchedMap);
|
||||
$entity->setAsFetched();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create entity (and store to database).
|
||||
*
|
||||
* @param stdClass|array<string, mixed> $data Entity attributes.
|
||||
* @param array<string, mixed> $options Options.
|
||||
*/
|
||||
public function createEntity(string $entityType, $data = [], array $options = []): Entity
|
||||
{
|
||||
$entity = $this->getNewEntity($entityType);
|
||||
$entity->set($data);
|
||||
$this->saveEntity($entity, $options);
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a repository for a specific entity type exist.
|
||||
*/
|
||||
public function hasRepository(string $entityType): bool
|
||||
{
|
||||
return $this->getMetadata()->has($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a repository for a specific entity type.
|
||||
*
|
||||
* @return Repository<Entity>
|
||||
*/
|
||||
public function getRepository(string $entityType): Repository
|
||||
{
|
||||
if (!$this->hasRepository($entityType)) {
|
||||
throw new RuntimeException("Repository '$entityType' does not exist.");
|
||||
}
|
||||
|
||||
if (!array_key_exists($entityType, $this->repositoryHash)) {
|
||||
$this->repositoryHash[$entityType] = $this->repositoryFactory->create($entityType);
|
||||
}
|
||||
|
||||
return $this->repositoryHash[$entityType];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an RDB repository for a specific entity type.
|
||||
*
|
||||
* @return RDBRepository<Entity>
|
||||
*/
|
||||
public function getRDBRepository(string $entityType): RDBRepository
|
||||
{
|
||||
$repository = $this->getRepository($entityType);
|
||||
|
||||
if (!$repository instanceof RDBRepository) {
|
||||
throw new RuntimeException("Repository '$entityType' is not RDB.");
|
||||
}
|
||||
|
||||
return $repository;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an RDB repository by an entity class name.
|
||||
*
|
||||
* @template T of Entity
|
||||
* @param class-string<T> $className An entity class name.
|
||||
* @return RDBRepository<T>
|
||||
*/
|
||||
public function getRDBRepositoryByClass(string $className): RDBRepository
|
||||
{
|
||||
$entityType = RepositoryUtil::getEntityTypeByClass($className);
|
||||
|
||||
/** @var RDBRepository<T> */
|
||||
return $this->getRDBRepository($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a repository by an entity class name.
|
||||
*
|
||||
* @template T of Entity
|
||||
* @param class-string<T> $className An entity class name.
|
||||
* @return Repository<T>
|
||||
*/
|
||||
public function getRepositoryByClass(string $className): Repository
|
||||
{
|
||||
$entityType = RepositoryUtil::getEntityTypeByClass($className);
|
||||
|
||||
/** @var Repository<T> */
|
||||
return $this->getRepository($entityType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an access point for a specific relation of a record.
|
||||
*
|
||||
* @return RDBRelation<Entity>
|
||||
* @since 8.4.0
|
||||
*/
|
||||
public function getRelation(Entity $entity, string $relationName): RDBRelation
|
||||
{
|
||||
return $this->getRDBRepository($entity->getEntityType())->getRelation($entity, $relationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata definitions.
|
||||
*/
|
||||
public function getDefs(): Defs
|
||||
{
|
||||
return $this->metadata->getDefs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query builder.
|
||||
*/
|
||||
public function getQueryBuilder(): QueryBuilder
|
||||
{
|
||||
return $this->queryBuilder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metadata.
|
||||
*/
|
||||
public function getMetadata(): Metadata
|
||||
{
|
||||
return $this->metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the entity factory.
|
||||
*/
|
||||
public function getEntityFactory(): EntityFactory
|
||||
{
|
||||
return $this->entityFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the collection factory.
|
||||
*/
|
||||
public function getCollectionFactory(): CollectionFactory
|
||||
{
|
||||
return $this->collectionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Query Executor.
|
||||
*/
|
||||
public function getQueryExecutor(): QueryExecutor
|
||||
{
|
||||
return $this->queryExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SQL Executor.
|
||||
*/
|
||||
public function getSqlExecutor(): SqlExecutor
|
||||
{
|
||||
return $this->sqlExecutor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v7.0. Use `getCollectionFactory`.
|
||||
* @param array<string, mixed> $data
|
||||
* @return EntityCollection<Entity>
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function createCollection(?string $entityType = null, array $data = []): EntityCollection
|
||||
{
|
||||
return $this->collectionFactory->create($entityType, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v7.0. Use the Query Builder instead. Otherwise, code will be not portable.
|
||||
*/
|
||||
public function getPDO(): PDO
|
||||
{
|
||||
return $this->pdoProvider->get();
|
||||
}
|
||||
}
|
||||
79
application/Espo/ORM/EventDispatcher.php
Normal file
79
application/Espo/ORM/EventDispatcher.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Closure;
|
||||
|
||||
/**
|
||||
* Event dispatcher.
|
||||
*/
|
||||
class EventDispatcher
|
||||
{
|
||||
/** @var array{'metadataUpdate': Closure[]} */
|
||||
private array $data;
|
||||
|
||||
private const METADATA_UPDATE = 'metadataUpdate';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->data = [
|
||||
self::METADATA_UPDATE => [],
|
||||
];
|
||||
}
|
||||
|
||||
public function subscribeToMetadataUpdate(Closure $callback): void
|
||||
{
|
||||
$this->data[self::METADATA_UPDATE][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @since 8.4.0
|
||||
*/
|
||||
public function unsubscribeFromMetadataUpdate(Closure $closure): void
|
||||
{
|
||||
$list = &$this->data[self::METADATA_UPDATE];
|
||||
|
||||
$index = array_search($closure, $list);
|
||||
|
||||
if ($index !== false) {
|
||||
unset($list[$index]);
|
||||
|
||||
$list = array_values($list);
|
||||
}
|
||||
}
|
||||
|
||||
public function dispatchMetadataUpdate(): void
|
||||
{
|
||||
foreach ($this->data[self::METADATA_UPDATE] as $callback) {
|
||||
$callback();
|
||||
}
|
||||
}
|
||||
}
|
||||
50
application/Espo/ORM/Executor/DefaultQueryExecutor.php
Normal file
50
application/Espo/ORM/Executor/DefaultQueryExecutor.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?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\ORM\Executor;
|
||||
|
||||
use Espo\ORM\Query\Query;
|
||||
use Espo\ORM\QueryComposer\QueryComposerWrapper;
|
||||
|
||||
use PDOStatement;
|
||||
|
||||
class DefaultQueryExecutor implements QueryExecutor
|
||||
{
|
||||
public function __construct(
|
||||
private SqlExecutor $sqlExecutor,
|
||||
private QueryComposerWrapper $queryComposer
|
||||
) {}
|
||||
|
||||
public function execute(Query $query): PDOStatement
|
||||
{
|
||||
$sql = $this->queryComposer->compose($query);
|
||||
|
||||
return $this->sqlExecutor->execute($sql, true);
|
||||
}
|
||||
}
|
||||
108
application/Espo/ORM/Executor/DefaultSqlExecutor.php
Normal file
108
application/Espo/ORM/Executor/DefaultSqlExecutor.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?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\ORM\Executor;
|
||||
|
||||
use Espo\ORM\PDO\PDOProvider;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
use PDO;
|
||||
use PDOStatement;
|
||||
use PDOException;
|
||||
use Exception;
|
||||
use RuntimeException;
|
||||
|
||||
class DefaultSqlExecutor implements SqlExecutor
|
||||
{
|
||||
private const MAX_ATTEMPT_COUNT = 4;
|
||||
|
||||
private PDO $pdo;
|
||||
|
||||
public function __construct(
|
||||
PDOProvider $pdoProvider,
|
||||
private ?LoggerInterface $logger = null,
|
||||
private bool $logAll = false,
|
||||
private bool $logFailed = false
|
||||
) {
|
||||
$this->pdo = $pdoProvider->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query.
|
||||
*/
|
||||
public function execute(string $sql, bool $rerunIfDeadlock = false): PDOStatement
|
||||
{
|
||||
if ($this->logAll) {
|
||||
$this->logger?->info("SQL: " . $sql, ['isSql' => true]);
|
||||
}
|
||||
|
||||
if (!$rerunIfDeadlock) {
|
||||
return $this->executeSqlWithDeadlockHandling($sql, 1);
|
||||
}
|
||||
|
||||
return $this->executeSqlWithDeadlockHandling($sql);
|
||||
}
|
||||
|
||||
private function executeSqlWithDeadlockHandling(string $sql, ?int $counter = null): PDOStatement
|
||||
{
|
||||
$counter = $counter ?? self::MAX_ATTEMPT_COUNT;
|
||||
|
||||
try {
|
||||
$sth = $this->pdo->query($sql);
|
||||
} catch (Exception $e) {
|
||||
$counter--;
|
||||
|
||||
if ($counter === 0 || !$this->isExceptionIsDeadlock($e)) {
|
||||
if ($this->logFailed) {
|
||||
$this->logger?->error("SQL failed: " . $sql, ['isSql' => true]);
|
||||
}
|
||||
|
||||
/** @var PDOException $e */
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return $this->executeSqlWithDeadlockHandling($sql, $counter);
|
||||
}
|
||||
|
||||
if (!$sth) {
|
||||
throw new RuntimeException("Query execution failure.");
|
||||
}
|
||||
|
||||
return $sth;
|
||||
}
|
||||
|
||||
private function isExceptionIsDeadlock(Exception $e): bool
|
||||
{
|
||||
if (!$e instanceof PDOException) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isset($e->errorInfo) && $e->errorInfo[0] == 40001 && $e->errorInfo[1] == 1213;
|
||||
}
|
||||
}
|
||||
45
application/Espo/ORM/Executor/QueryExecutor.php
Normal file
45
application/Espo/ORM/Executor/QueryExecutor.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\ORM\Executor;
|
||||
|
||||
use Espo\ORM\Query\Query;
|
||||
|
||||
use PDOStatement;
|
||||
|
||||
/**
|
||||
* Executes queries by given query params instances.
|
||||
*/
|
||||
interface QueryExecutor
|
||||
{
|
||||
/**
|
||||
* Execute a query.
|
||||
*/
|
||||
public function execute(Query $query): PDOStatement;
|
||||
}
|
||||
43
application/Espo/ORM/Executor/SqlExecutor.php
Normal file
43
application/Espo/ORM/Executor/SqlExecutor.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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\ORM\Executor;
|
||||
|
||||
use PDOStatement;
|
||||
|
||||
/**
|
||||
* Executes SQL queries.
|
||||
*/
|
||||
interface SqlExecutor
|
||||
{
|
||||
/**
|
||||
* Execute a query.
|
||||
*/
|
||||
public function execute(string $sql, bool $rerunIfDeadlock = false): PDOStatement;
|
||||
}
|
||||
122
application/Espo/ORM/Locker/BaseLocker.php
Normal file
122
application/Espo/ORM/Locker/BaseLocker.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?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\ORM\Locker;
|
||||
|
||||
use Espo\ORM\Query\LockTableBuilder;
|
||||
use Espo\ORM\QueryComposer\QueryComposer;
|
||||
use Espo\ORM\TransactionManager;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class BaseLocker implements Locker
|
||||
{
|
||||
private bool $isLocked = false;
|
||||
|
||||
public function __construct(
|
||||
private PDO $pdo,
|
||||
private QueryComposer $queryComposer,
|
||||
private TransactionManager $transactionManager
|
||||
) {}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLocked(): bool
|
||||
{
|
||||
return $this->isLocked;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lockExclusive(string $entityType): void
|
||||
{
|
||||
$this->isLocked = true;
|
||||
|
||||
$this->transactionManager->start();
|
||||
|
||||
$query = (new LockTableBuilder())
|
||||
->table($entityType)
|
||||
->inExclusiveMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->queryComposer->composeLockTable($query);
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lockShare(string $entityType): void
|
||||
{
|
||||
$this->isLocked = true;
|
||||
|
||||
$this->transactionManager->start();
|
||||
|
||||
$query = (new LockTableBuilder())
|
||||
->table($entityType)
|
||||
->inShareMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->queryComposer->composeLockTable($query);
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit(): void
|
||||
{
|
||||
if (!$this->isLocked) {
|
||||
throw new RuntimeException("Can't commit, it was not locked.");
|
||||
}
|
||||
|
||||
$this->transactionManager->commit();
|
||||
|
||||
$this->isLocked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function rollback(): void
|
||||
{
|
||||
if (!$this->isLocked) {
|
||||
throw new RuntimeException("Can't rollback, it was not locked.");
|
||||
}
|
||||
|
||||
$this->transactionManager->rollback();
|
||||
|
||||
$this->isLocked = false;
|
||||
}
|
||||
}
|
||||
62
application/Espo/ORM/Locker/Locker.php
Normal file
62
application/Espo/ORM/Locker/Locker.php
Normal 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\ORM\Locker;
|
||||
|
||||
/**
|
||||
* Locks and unlocks tables.
|
||||
* Wraps operations between lock and unlock into a transaction.
|
||||
*/
|
||||
interface Locker
|
||||
{
|
||||
/**
|
||||
* Whether any table has been locked.
|
||||
*/
|
||||
public function isLocked(): bool;
|
||||
|
||||
/**
|
||||
* Locks a table in an exclusive mode. Starts a transaction on first call.
|
||||
*/
|
||||
public function lockExclusive(string $entityType): void;
|
||||
|
||||
/**
|
||||
* Locks a table in a share mode. Starts a transaction on first call.
|
||||
*/
|
||||
public function lockShare(string $entityType): void;
|
||||
|
||||
/**
|
||||
* Commits changes and unlocks tables.
|
||||
*/
|
||||
public function commit(): void;
|
||||
|
||||
/**
|
||||
* Rollbacks changes and unlocks tables.
|
||||
*/
|
||||
public function rollback(): void;
|
||||
}
|
||||
138
application/Espo/ORM/Locker/MysqlLocker.php
Normal file
138
application/Espo/ORM/Locker/MysqlLocker.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?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\ORM\Locker;
|
||||
|
||||
use Espo\ORM\QueryComposer\QueryComposer;
|
||||
use Espo\ORM\QueryComposer\MysqlQueryComposer;
|
||||
use Espo\ORM\Query\LockTableBuilder;
|
||||
use Espo\ORM\TransactionManager;
|
||||
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Transactions within locking is not supported for MySQL.
|
||||
*/
|
||||
class MysqlLocker implements Locker
|
||||
{
|
||||
private MysqlQueryComposer $queryComposer;
|
||||
/** @phpstan-ignore-next-line */
|
||||
private TransactionManager $transactionManager;
|
||||
|
||||
private bool $isLocked = false;
|
||||
|
||||
public function __construct(
|
||||
private PDO $pdo,
|
||||
QueryComposer $queryComposer,
|
||||
TransactionManager $transactionManager
|
||||
) {
|
||||
$this->transactionManager = $transactionManager;
|
||||
|
||||
if (!$queryComposer instanceof MysqlQueryComposer) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
$this->queryComposer = $queryComposer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isLocked(): bool
|
||||
{
|
||||
return $this->isLocked;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lockExclusive(string $entityType): void
|
||||
{
|
||||
$this->isLocked = true;
|
||||
|
||||
$query = (new LockTableBuilder())
|
||||
->table($entityType)
|
||||
->inExclusiveMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->queryComposer->composeLockTable($query);
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function lockShare(string $entityType): void
|
||||
{
|
||||
$this->isLocked = true;
|
||||
|
||||
$query = (new LockTableBuilder())
|
||||
->table($entityType)
|
||||
->inShareMode()
|
||||
->build();
|
||||
|
||||
$sql = $this->queryComposer->composeLockTable($query);
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function commit(): void
|
||||
{
|
||||
if (!$this->isLocked) {
|
||||
throw new RuntimeException("Can't commit, it was not locked.");
|
||||
}
|
||||
|
||||
$this->isLocked = false;
|
||||
|
||||
$sql = $this->queryComposer->composeUnlockTables();
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lift locking.
|
||||
* Rolling back within locking is not supported for MySQL.
|
||||
*/
|
||||
public function rollback(): void
|
||||
{
|
||||
if (!$this->isLocked) {
|
||||
throw new RuntimeException("Can't rollback, it was not locked.");
|
||||
}
|
||||
|
||||
$this->isLocked = false;
|
||||
|
||||
$sql = $this->queryComposer->composeUnlockTables();
|
||||
|
||||
$this->pdo->exec($sql);
|
||||
}
|
||||
}
|
||||
1758
application/Espo/ORM/Mapper/BaseMapper.php
Normal file
1758
application/Espo/ORM/Mapper/BaseMapper.php
Normal file
File diff suppressed because it is too large
Load Diff
156
application/Espo/ORM/Mapper/Helper.php
Normal file
156
application/Espo/ORM/Mapper/Helper.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?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\ORM\Mapper;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Metadata;
|
||||
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use RuntimeException;
|
||||
|
||||
class Helper
|
||||
{
|
||||
public function __construct(private Metadata $metadata)
|
||||
{}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* key: string,
|
||||
* foreignKey: string,
|
||||
* foreignType?: string,
|
||||
* nearKey?: string,
|
||||
* distantKey?: string,
|
||||
* typeKey?: string,
|
||||
* }
|
||||
*/
|
||||
public function getRelationKeys(Entity $entity, string $relationName): array
|
||||
{
|
||||
$entityType = $entity->getEntityType();
|
||||
|
||||
$defs = $this->metadata->getDefs()
|
||||
->getEntity($entityType)
|
||||
->getRelation($relationName);
|
||||
|
||||
$type = $defs->getType();
|
||||
|
||||
switch ($type) {
|
||||
|
||||
case Entity::BELONGS_TO:
|
||||
$key = $defs->hasKey() ?
|
||||
$defs->getKey() :
|
||||
$relationName . 'Id';
|
||||
|
||||
$foreignKey = $defs->hasForeignKey() ?
|
||||
$defs->getForeignKey() :
|
||||
Attribute::ID;
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'foreignKey' => $foreignKey,
|
||||
];
|
||||
|
||||
case Entity::HAS_MANY:
|
||||
case Entity::HAS_ONE:
|
||||
$key = $defs->hasKey() ? $defs->getKey() : Attribute::ID;
|
||||
|
||||
$foreign = $defs->hasForeignRelationName() ?
|
||||
$defs->getForeignRelationName() :
|
||||
null;
|
||||
|
||||
$foreignKey = $defs->hasForeignKey() ?
|
||||
$defs->getForeignKey() :
|
||||
null;
|
||||
|
||||
if (!$foreignKey && $foreign) {
|
||||
$foreignKey = $foreign . 'Id';
|
||||
}
|
||||
|
||||
if (!$foreignKey) {
|
||||
$foreignKey = lcfirst($entity->getEntityType()) . 'Id';
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'foreignKey' => $foreignKey,
|
||||
];
|
||||
|
||||
case Entity::HAS_CHILDREN:
|
||||
$key = $defs->hasKey() ? $defs->getKey() : Attribute::ID;
|
||||
|
||||
$foreignKey = $defs->hasForeignKey() ?
|
||||
$defs->getForeignKey() :
|
||||
'parentId';
|
||||
|
||||
$foreignType = $defs->getParam('foreignType') ?? 'parentType';
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'foreignKey' => $foreignKey,
|
||||
'foreignType' => $foreignType,
|
||||
];
|
||||
|
||||
case Entity::MANY_MANY:
|
||||
$key = $defs->hasKey() ?
|
||||
$defs->getKey() :
|
||||
Attribute::ID;
|
||||
|
||||
$foreignKey = $defs->hasForeignKey() ?
|
||||
$defs->getForeignKey() :
|
||||
Attribute::ID;
|
||||
|
||||
$nearKey = $defs->hasMidKey() ?
|
||||
$defs->getMidKey() :
|
||||
lcfirst($entityType) . 'Id';
|
||||
|
||||
$distantKey = $defs->hasForeignMidKey() ?
|
||||
$defs->getForeignMidKey() :
|
||||
lcfirst($defs->getForeignEntityType()) . 'Id';
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'foreignKey' => $foreignKey,
|
||||
'nearKey' => $nearKey,
|
||||
'distantKey' => $distantKey,
|
||||
];
|
||||
|
||||
case Entity::BELONGS_TO_PARENT:
|
||||
$key = $relationName . 'Id';
|
||||
$typeKey = $relationName . 'Type';
|
||||
|
||||
return [
|
||||
'key' => $key,
|
||||
'typeKey' => $typeKey,
|
||||
'foreignKey' => Attribute::ID,
|
||||
];
|
||||
}
|
||||
|
||||
throw new RuntimeException("Relation type '{$type}' not supported for 'getKeys'.");
|
||||
}
|
||||
}
|
||||
83
application/Espo/ORM/Mapper/Mapper.php
Normal file
83
application/Espo/ORM/Mapper/Mapper.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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\ORM\Mapper;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Collection;
|
||||
use Espo\ORM\Query\Select;
|
||||
|
||||
interface Mapper
|
||||
{
|
||||
/**
|
||||
* Get a first entity from DB.
|
||||
*/
|
||||
public function selectOne(Select $select): ?Entity;
|
||||
|
||||
/**
|
||||
* Select entities from DB.
|
||||
*
|
||||
* @return Collection<Entity>
|
||||
*/
|
||||
public function select(Select $select): Collection;
|
||||
|
||||
/**
|
||||
* Get a number of records in DB.
|
||||
*/
|
||||
public function count(Select $select): int;
|
||||
|
||||
/**
|
||||
* Insert an entity into DB.
|
||||
*/
|
||||
public function insert(Entity $entity): void;
|
||||
|
||||
/**
|
||||
* Insert a collection into DB.
|
||||
*
|
||||
* @param Collection<Entity> $collection
|
||||
*/
|
||||
public function massInsert(Collection $collection): void;
|
||||
|
||||
/**
|
||||
* Update an entity in DB.
|
||||
*/
|
||||
public function update(Entity $entity): void;
|
||||
|
||||
/**
|
||||
* Delete an entity from DB or mark as deleted.
|
||||
*/
|
||||
public function delete(Entity $entity): void;
|
||||
|
||||
/**
|
||||
* Insert an entity into DB, on duplicate key update specified attributes.
|
||||
*
|
||||
* @param string[] $onDuplicateUpdateAttributeList
|
||||
*/
|
||||
public function insertOnDuplicateUpdate(Entity $entity, array $onDuplicateUpdateAttributeList): void;
|
||||
}
|
||||
35
application/Espo/ORM/Mapper/MapperFactory.php
Normal file
35
application/Espo/ORM/Mapper/MapperFactory.php
Normal 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\ORM\Mapper;
|
||||
|
||||
interface MapperFactory
|
||||
{
|
||||
public function create(string $name): Mapper;
|
||||
}
|
||||
117
application/Espo/ORM/Mapper/RDBMapper.php
Normal file
117
application/Espo/ORM/Mapper/RDBMapper.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?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\ORM\Mapper;
|
||||
|
||||
use Espo\ORM\Collection;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Query\Select;
|
||||
|
||||
interface RDBMapper extends Mapper
|
||||
{
|
||||
/**
|
||||
* Relate an entity with another entity.
|
||||
*
|
||||
* @param Entity $entity An entity.
|
||||
* @param string $relationName A relation name.
|
||||
* @param Entity $foreignEntity A foreign entity.
|
||||
* @param array<string, mixed>|null $columnData Column values.
|
||||
* @return bool True if the row was affected.
|
||||
*/
|
||||
public function relate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData): bool;
|
||||
|
||||
/**
|
||||
* Unrelate an entity from another entity.
|
||||
*
|
||||
* @param Entity $entity An entity.
|
||||
* @param string $relationName A relation name.
|
||||
* @param Entity $foreignEntity A foreign entity.
|
||||
*/
|
||||
public function unrelate(Entity $entity, string $relationName, Entity $foreignEntity): void;
|
||||
|
||||
/**
|
||||
* Relate an entity from another entity by a given ID.
|
||||
*
|
||||
* @param Entity $entity An entity.
|
||||
* @param string $relationName A relation name.
|
||||
* @param string $id A foreign ID.
|
||||
* @param array<string, mixed>|null $columnData Column values.
|
||||
*/
|
||||
public function relateById(Entity $entity, string $relationName, string $id, ?array $columnData = null): bool;
|
||||
|
||||
/**
|
||||
* Unrelate an entity from another entity by a given ID.
|
||||
*
|
||||
* @param Entity $entity An entity.
|
||||
* @param string $relationName A relation name.
|
||||
* @param string $id A foreign ID.
|
||||
*/
|
||||
public function unrelateById(Entity $entity, string $relationName, string $id): void;
|
||||
|
||||
/**
|
||||
* Mass relate.
|
||||
*/
|
||||
public function massRelate(Entity $entity, string $relationName, Select $select): void;
|
||||
|
||||
/**
|
||||
* Update relationship columns.
|
||||
*
|
||||
* @param array<string, mixed> $columnData
|
||||
*/
|
||||
public function updateRelationColumns(
|
||||
Entity $entity,
|
||||
string $relationName,
|
||||
string $id,
|
||||
array $columnData
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Get a relationship column value.
|
||||
*
|
||||
* @return string|int|float|bool|null A relationship column value.
|
||||
*/
|
||||
public function getRelationColumn(
|
||||
Entity $entity,
|
||||
string $relationName,
|
||||
string $id,
|
||||
string $column
|
||||
): string|int|float|bool|null;
|
||||
|
||||
/**
|
||||
* Select related entities from DB.
|
||||
*
|
||||
* @return Collection<Entity>|Entity|null
|
||||
*/
|
||||
public function selectRelated(Entity $entity, string $relationName, ?Select $select = null): Collection|Entity|null;
|
||||
|
||||
/**
|
||||
* Get a number of related entities in DB.
|
||||
*/
|
||||
public function countRelated(Entity $entity, string $relationName, ?Select $select = null): int;
|
||||
}
|
||||
155
application/Espo/ORM/Metadata.php
Normal file
155
application/Espo/ORM/Metadata.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Espo\ORM\Defs\DefsData;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Metadata.
|
||||
*/
|
||||
class Metadata
|
||||
{
|
||||
/** @var array<string, mixed> */
|
||||
private array $data;
|
||||
|
||||
private Defs $defs;
|
||||
private DefsData $defsData;
|
||||
private EventDispatcher $eventDispatcher;
|
||||
|
||||
public function __construct(
|
||||
private MetadataDataProvider $dataProvider,
|
||||
?EventDispatcher $eventDispatcher = null
|
||||
) {
|
||||
$this->data = $dataProvider->get();
|
||||
$this->defsData = new DefsData($this);
|
||||
$this->defs = new Defs($this->defsData);
|
||||
$this->eventDispatcher = $eventDispatcher ?? new EventDispatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update data from the data provider.
|
||||
*/
|
||||
public function updateData(): void
|
||||
{
|
||||
$this->data = $this->dataProvider->get();
|
||||
|
||||
$this->defsData->clearCache();
|
||||
|
||||
$this->eventDispatcher->dispatchMetadataUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get definitions.
|
||||
*/
|
||||
public function getDefs(): Defs
|
||||
{
|
||||
return $this->defs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a parameter or parameters by key. Key can be a string or array path.
|
||||
*
|
||||
* @param string $entityType An entity type.
|
||||
* @param string[]|string|null $key A Key.
|
||||
* @param mixed $default A default value.
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $entityType, $key = null, $default = null)
|
||||
{
|
||||
if (!$this->has($entityType)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $this->data[$entityType];
|
||||
|
||||
if ($key === null) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return self::getValueByKey($data, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an entity type is available.
|
||||
*/
|
||||
public function has(string $entityType): bool
|
||||
{
|
||||
return array_key_exists($entityType, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of entity types.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEntityTypeList(): array
|
||||
{
|
||||
return array_keys($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $data
|
||||
* @param string[]|string|null $key
|
||||
* @param mixed $default A default value.
|
||||
* @return mixed
|
||||
*/
|
||||
private static function getValueByKey(array $data, $key = null, $default = null)
|
||||
{
|
||||
if (!is_string($key) && !is_array($key) && !is_null($key)) { /** @phpstan-ignore-line */
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
if (is_null($key) || empty($key)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$path = $key;
|
||||
|
||||
if (is_string($key)) {
|
||||
$path = explode('.', $key);
|
||||
}
|
||||
|
||||
/** @var string[] $path */
|
||||
|
||||
$item = $data;
|
||||
|
||||
foreach ($path as $k) {
|
||||
if (!array_key_exists($k, $item)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$item = $item[$k];
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
41
application/Espo/ORM/MetadataDataProvider.php
Normal file
41
application/Espo/ORM/MetadataDataProvider.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?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\ORM;
|
||||
|
||||
/**
|
||||
* Provides data for metadata.
|
||||
*/
|
||||
interface MetadataDataProvider
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function get(): array;
|
||||
}
|
||||
36
application/Espo/ORM/Name/Attribute.php
Normal file
36
application/Espo/ORM/Name/Attribute.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?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\ORM\Name;
|
||||
|
||||
interface Attribute
|
||||
{
|
||||
public const ID = 'id';
|
||||
public const DELETED = 'deleted';
|
||||
}
|
||||
59
application/Espo/ORM/PDO/DefaultPDOProvider.php
Normal file
59
application/Espo/ORM/PDO/DefaultPDOProvider.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\ORM\PDO;
|
||||
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use PDO;
|
||||
|
||||
class DefaultPDOProvider implements PDOProvider
|
||||
{
|
||||
private ?PDO $pdo = null;
|
||||
|
||||
public function __construct(
|
||||
private DatabaseParams $databaseParams,
|
||||
private PDOFactory $pdoFactory
|
||||
) {}
|
||||
|
||||
public function get(): PDO
|
||||
{
|
||||
if (!$this->pdo) {
|
||||
$this->intPDO();
|
||||
}
|
||||
|
||||
assert($this->pdo !== null);
|
||||
|
||||
return $this->pdo;
|
||||
}
|
||||
|
||||
private function intPDO(): void
|
||||
{
|
||||
$this->pdo = $this->pdoFactory->create($this->databaseParams);
|
||||
}
|
||||
}
|
||||
79
application/Espo/ORM/PDO/MysqlPDOFactory.php
Normal file
79
application/Espo/ORM/PDO/MysqlPDOFactory.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?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\ORM\PDO;
|
||||
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class MysqlPDOFactory implements PDOFactory
|
||||
{
|
||||
private const DEFAULT_CHARSET = 'utf8mb4';
|
||||
|
||||
public function create(DatabaseParams $databaseParams): PDO
|
||||
{
|
||||
$platform = strtolower($databaseParams->getPlatform() ?? '');
|
||||
|
||||
$host = $databaseParams->getHost();
|
||||
$port = $databaseParams->getPort();
|
||||
$dbname = $databaseParams->getName();
|
||||
$charset = $databaseParams->getCharset() ?? self::DEFAULT_CHARSET;
|
||||
$username = $databaseParams->getUsername();
|
||||
$password = $databaseParams->getPassword();
|
||||
|
||||
if (!$platform) {
|
||||
throw new RuntimeException("No 'platform' parameter.");
|
||||
}
|
||||
|
||||
if (!$host) {
|
||||
throw new RuntimeException("No 'host' parameter.");
|
||||
}
|
||||
|
||||
$dsn = $platform . ':' . 'host=' . $host;
|
||||
|
||||
if ($port) {
|
||||
$dsn .= ';' . 'port=' . (string) $port;
|
||||
}
|
||||
|
||||
if ($dbname) {
|
||||
$dsn .= ';' . 'dbname=' . $dbname;
|
||||
}
|
||||
|
||||
$dsn .= ';' . 'charset=' . $charset;
|
||||
|
||||
$options = Options::getOptionsFromDatabaseParams($databaseParams);
|
||||
|
||||
$pdo = new PDO($dsn, $username, $password, $options);
|
||||
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
}
|
||||
71
application/Espo/ORM/PDO/Options.php
Normal file
71
application/Espo/ORM/PDO/Options.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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\ORM\PDO;
|
||||
|
||||
use Espo\ORM\DatabaseParams;
|
||||
|
||||
use PDO;
|
||||
|
||||
class Options
|
||||
{
|
||||
/**
|
||||
* @return array<int, mixed>
|
||||
*/
|
||||
public static function getOptionsFromDatabaseParams(DatabaseParams $databaseParams): array
|
||||
{
|
||||
$options = [];
|
||||
|
||||
if ($databaseParams->getSslCa()) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CA] = $databaseParams->getSslCa();
|
||||
}
|
||||
|
||||
if ($databaseParams->getSslCert()) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CERT] = $databaseParams->getSslCert();
|
||||
}
|
||||
|
||||
if ($databaseParams->getSslKey()) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_KEY] = $databaseParams->getSslKey();
|
||||
}
|
||||
|
||||
if ($databaseParams->getSslCaPath()) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CAPATH] = $databaseParams->getSslCaPath();
|
||||
}
|
||||
|
||||
if ($databaseParams->getSslCipher()) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_CIPHER] = $databaseParams->getSslCipher();
|
||||
}
|
||||
|
||||
if ($databaseParams->isSslVerifyDisabled()) {
|
||||
$options[PDO::MYSQL_ATTR_SSL_VERIFY_SERVER_CERT] = false;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
}
|
||||
38
application/Espo/ORM/PDO/PDOFactory.php
Normal file
38
application/Espo/ORM/PDO/PDOFactory.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?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\ORM\PDO;
|
||||
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use PDO;
|
||||
|
||||
interface PDOFactory
|
||||
{
|
||||
public function create(DatabaseParams $databaseParams): PDO;
|
||||
}
|
||||
37
application/Espo/ORM/PDO/PDOProvider.php
Normal file
37
application/Espo/ORM/PDO/PDOProvider.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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\ORM\PDO;
|
||||
|
||||
use PDO;
|
||||
|
||||
interface PDOProvider
|
||||
{
|
||||
public function get(): PDO;
|
||||
}
|
||||
81
application/Espo/ORM/PDO/PostgresqlPDOFactory.php
Normal file
81
application/Espo/ORM/PDO/PostgresqlPDOFactory.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?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\ORM\PDO;
|
||||
|
||||
use Espo\ORM\DatabaseParams;
|
||||
use PDO;
|
||||
use RuntimeException;
|
||||
|
||||
class PostgresqlPDOFactory implements PDOFactory
|
||||
{
|
||||
private const DEFAULT_CHARSET = 'utf8';
|
||||
|
||||
public function create(DatabaseParams $databaseParams): PDO
|
||||
{
|
||||
$platform = strtolower($databaseParams->getPlatform() ?? '');
|
||||
|
||||
$host = $databaseParams->getHost();
|
||||
$port = $databaseParams->getPort();
|
||||
$dbname = $databaseParams->getName();
|
||||
$charset = $databaseParams->getCharset() ?? self::DEFAULT_CHARSET;
|
||||
$username = $databaseParams->getUsername();
|
||||
$password = $databaseParams->getPassword();
|
||||
|
||||
if (!$platform) {
|
||||
throw new RuntimeException("No 'platform' parameter.");
|
||||
}
|
||||
|
||||
if (!$host) {
|
||||
throw new RuntimeException("No 'host' parameter.");
|
||||
}
|
||||
|
||||
$dsn = 'pgsql:' . 'host=' . $host;
|
||||
|
||||
if ($port) {
|
||||
$dsn .= ';' . 'port=' . (string) $port;
|
||||
}
|
||||
|
||||
if ($dbname) {
|
||||
$dsn .= ';' . 'dbname=' . $dbname;
|
||||
}
|
||||
|
||||
$dsn .= ';' . 'options=' . "'--client_encoding={$charset}'";
|
||||
|
||||
$options = Options::getOptionsFromDatabaseParams($databaseParams);
|
||||
|
||||
$pdo = new PDO($dsn, $username, $password, $options);
|
||||
|
||||
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$pdo->query("SET time zone 'UTC'");
|
||||
|
||||
return $pdo;
|
||||
}
|
||||
}
|
||||
60
application/Espo/ORM/Query/BaseBuilderTrait.php
Normal file
60
application/Espo/ORM/Query/BaseBuilderTrait.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
trait BaseBuilderTrait
|
||||
{
|
||||
/**
|
||||
* Must be protected for compatibility reasons.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $params = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
private function isEmpty(): bool
|
||||
{
|
||||
return empty($this->params);
|
||||
}
|
||||
|
||||
private function cloneInternal(Query $query): void
|
||||
{
|
||||
if (!$this->isEmpty()) {
|
||||
throw new RuntimeException("Clone can be called only on a new empty builder instance.");
|
||||
}
|
||||
|
||||
$this->params = $query->getRaw();
|
||||
}
|
||||
}
|
||||
70
application/Espo/ORM/Query/BaseTrait.php
Normal file
70
application/Espo/ORM/Query/BaseTrait.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
trait BaseTrait
|
||||
{
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $params = [];
|
||||
|
||||
/**
|
||||
* Get parameters in RAW format.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getRaw(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from RAW params.
|
||||
*
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
public static function fromRaw(array $params): self
|
||||
{
|
||||
$obj = new self();
|
||||
|
||||
$obj->validateRawParams($params);
|
||||
|
||||
$obj->params = $params;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function validateRawParams(array $params): void
|
||||
{}
|
||||
}
|
||||
42
application/Espo/ORM/Query/Builder.php
Normal file
42
application/Espo/ORM/Query/Builder.php
Normal 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\ORM\Query;
|
||||
|
||||
/**
|
||||
* Builds query parameters.
|
||||
* Builder instances are one-off, meaning that you need to instantiate it for every new building process.
|
||||
*/
|
||||
interface Builder
|
||||
{
|
||||
/**
|
||||
* Build a query instance.
|
||||
*/
|
||||
public function build(): Query;
|
||||
}
|
||||
81
application/Espo/ORM/Query/Delete.php
Normal file
81
application/Espo/ORM/Query/Delete.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Delete parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Delete implements Query
|
||||
{
|
||||
use SelectingTrait;
|
||||
use BaseTrait;
|
||||
|
||||
/**
|
||||
* Get an entity type.
|
||||
*/
|
||||
public function getFrom(): string
|
||||
{
|
||||
return $this->params['from'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a from-alias
|
||||
*/
|
||||
public function getFromAlias(): ?string
|
||||
{
|
||||
return $this->params['fromAlias'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a LIMIT.
|
||||
*/
|
||||
public function getLimit(): ?int
|
||||
{
|
||||
return $this->params['limit'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function validateRawParams(array $params): void
|
||||
{
|
||||
$this->validateRawParamsSelecting($params);
|
||||
|
||||
$from = $params['from'] ?? null;
|
||||
|
||||
if (!$from || !is_string($from)) {
|
||||
throw new RuntimeException("Select params: Missing 'from'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
88
application/Espo/ORM/Query/DeleteBuilder.php
Normal file
88
application/Espo/ORM/Query/DeleteBuilder.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class DeleteBuilder implements Builder
|
||||
{
|
||||
use SelectingBuilderTrait;
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a DELETE query.
|
||||
*/
|
||||
public function build(): Delete
|
||||
{
|
||||
return Delete::fromRaw($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query for a subsequent modifying and building.
|
||||
*/
|
||||
public function clone(Delete $query): self
|
||||
{
|
||||
$this->cloneInternal($query);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set FROM parameter. For what entity type to build a query.
|
||||
*/
|
||||
public function from(string $entityType, ?string $alias = null): self
|
||||
{
|
||||
if (isset($this->params['from'])) {
|
||||
throw new RuntimeException("Method 'from' can be called only once.");
|
||||
}
|
||||
|
||||
$this->params['from'] = $entityType;
|
||||
$this->params['fromAlias'] = $alias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply LIMIT.
|
||||
*/
|
||||
public function limit(?int $limit = null): self
|
||||
{
|
||||
$this->params['limit'] = $limit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
72
application/Espo/ORM/Query/Insert.php
Normal file
72
application/Espo/ORM/Query/Insert.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Insert parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Insert implements Query
|
||||
{
|
||||
use BaseTrait;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function validateRawParams(array $params): void
|
||||
{
|
||||
$into = $params['into'] ?? null;
|
||||
|
||||
if (!$into || !is_string($into)) {
|
||||
throw new RuntimeException("Bad or missing 'into' parameter.");
|
||||
}
|
||||
|
||||
$columns = $params['columns'] ?? [];
|
||||
|
||||
if (!is_array($columns)) {
|
||||
throw new RuntimeException("Bad 'columns' parameter.");
|
||||
}
|
||||
|
||||
$values = $params['values'] ?? [];
|
||||
|
||||
if (!is_array($values)) {
|
||||
throw new RuntimeException("Bad 'values' parameter.");
|
||||
}
|
||||
|
||||
$updateSet = $params['updateSet'] ?? null;
|
||||
|
||||
if ($updateSet && !is_array($updateSet)) {
|
||||
throw new RuntimeException("Bad 'updateSet' parameter.");
|
||||
}
|
||||
}
|
||||
}
|
||||
122
application/Espo/ORM/Query/InsertBuilder.php
Normal file
122
application/Espo/ORM/Query/InsertBuilder.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
class InsertBuilder implements Builder
|
||||
{
|
||||
use BaseBuilderTrait;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
protected $params = [];
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a INSERT query.
|
||||
*/
|
||||
public function build(): Insert
|
||||
{
|
||||
return Insert::fromRaw($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query for a subsequent modifying and building.
|
||||
*/
|
||||
public function clone(Insert $query): self
|
||||
{
|
||||
$this->cloneInternal($query);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Into what entity type to insert.
|
||||
*/
|
||||
public function into(string $entityType): self
|
||||
{
|
||||
$this->params['into'] = $entityType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* What columns to set with values. A list of columns.
|
||||
*
|
||||
* @param string[] $columns
|
||||
*/
|
||||
public function columns(array $columns): self
|
||||
{
|
||||
$this->params['columns'] = $columns;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* What values to insert. A key-value map or a list of key-value maps.
|
||||
*
|
||||
* @param array<string, ?scalar>|array<string, ?scalar>[] $values
|
||||
*/
|
||||
public function values(array $values): self
|
||||
{
|
||||
$this->params['values'] = $values;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Values to set on duplicate key. A key-value map.
|
||||
*
|
||||
* @param array<string, ?scalar> $updateSet
|
||||
*/
|
||||
public function updateSet(array $updateSet): self
|
||||
{
|
||||
$this->params['updateSet'] = $updateSet;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* For a mass insert by a select sub-query.
|
||||
*/
|
||||
public function valuesQuery(SelectingQuery $query): self
|
||||
{
|
||||
$this->params['valuesQuery'] = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
59
application/Espo/ORM/Query/LockTable.php
Normal file
59
application/Espo/ORM/Query/LockTable.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* LOCK TABLE parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class LockTable implements Query
|
||||
{
|
||||
use BaseTrait;
|
||||
|
||||
public const MODE_SHARE = 'SHARE';
|
||||
public const MODE_EXCLUSIVE = 'EXCLUSIVE';
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
protected function validateRawParams(array $params): void
|
||||
{
|
||||
if (empty($params['table'])) {
|
||||
throw new RuntimeException("LockTable params: No table specified.");
|
||||
}
|
||||
|
||||
if (empty($params['mode'])) {
|
||||
throw new RuntimeException("LockTable params: No mode specified.");
|
||||
}
|
||||
}
|
||||
}
|
||||
91
application/Espo/ORM/Query/LockTableBuilder.php
Normal file
91
application/Espo/ORM/Query/LockTableBuilder.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
class LockTableBuilder implements Builder
|
||||
{
|
||||
use BaseBuilderTrait;
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a LOCK TABLE query.
|
||||
*/
|
||||
public function build(): LockTable
|
||||
{
|
||||
return LockTable::fromRaw($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query for a subsequent modifying and building.
|
||||
*/
|
||||
public function clone(LockTable $query): self
|
||||
{
|
||||
$this->cloneInternal($query);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* What entity type to lock.
|
||||
*/
|
||||
public function table(string $entityType): self
|
||||
{
|
||||
$this->params['table'] = $entityType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* In SHARE mode.
|
||||
*/
|
||||
public function inShareMode(): self
|
||||
{
|
||||
$this->params['mode'] = LockTable::MODE_SHARE;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* In EXCLUSIVE mode.
|
||||
*/
|
||||
public function inExclusiveMode(): self
|
||||
{
|
||||
$this->params['mode'] = LockTable::MODE_EXCLUSIVE;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
217
application/Espo/ORM/Query/Part/Condition.php
Normal file
217
application/Espo/ORM/Query/Part/Condition.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
use Espo\ORM\Query\Part\Where\AndGroup;
|
||||
use Espo\ORM\Query\Part\Where\Comparison;
|
||||
use Espo\ORM\Query\Part\Where\Exists;
|
||||
use Espo\ORM\Query\Part\Where\Not;
|
||||
use Espo\ORM\Query\Part\Where\OrGroup;
|
||||
|
||||
use Espo\ORM\Query\Select;
|
||||
|
||||
/**
|
||||
* A util-class for creating items that can be used as a where-clause.
|
||||
*/
|
||||
class Condition
|
||||
{
|
||||
private function __construct()
|
||||
{}
|
||||
|
||||
/**
|
||||
* Create 'AND' group.
|
||||
*/
|
||||
public static function and(WhereItem ...$items): AndGroup
|
||||
{
|
||||
return AndGroup::create(...$items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'OR' group.
|
||||
*/
|
||||
public static function or(WhereItem ...$items): OrGroup
|
||||
{
|
||||
return OrGroup::create(...$items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'NOT'.
|
||||
*/
|
||||
public static function not(WhereItem $item): Not
|
||||
{
|
||||
return Not::create($item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create `EXISTS`.
|
||||
*/
|
||||
public static function exists(Select $subQuery): Exists
|
||||
{
|
||||
return Exists::create($subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a column reference expression.
|
||||
*
|
||||
* @param string $expression Examples: `columnName`, `alias.columnName`.
|
||||
*/
|
||||
public static function column(string $expression): Expression
|
||||
{
|
||||
return Expression::column($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float|bool|null $argument2 A scalar, expression or sub-query.
|
||||
*/
|
||||
public static function equal(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float|bool|null $argument2
|
||||
): Comparison {
|
||||
|
||||
return Comparison::equal($argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '!=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float|bool|null $argument2 A scalar, expression or sub-query.
|
||||
*/
|
||||
public static function notEqual(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float|bool|null $argument2
|
||||
): Comparison {
|
||||
|
||||
return Comparison::notEqual($argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'LIKE' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Expression|string $pattern A pattern.
|
||||
*/
|
||||
public static function like(Expression $subject, Expression|string $pattern): Comparison
|
||||
{
|
||||
return Comparison::like($subject, $pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'NOT LIKE' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Expression|string $pattern A pattern.
|
||||
*/
|
||||
public static function notLike(Expression $subject, Expression|string $pattern): Comparison
|
||||
{
|
||||
return Comparison::notLike($subject, $pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '>' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
*/
|
||||
public static function greater(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float $argument2
|
||||
): Comparison {
|
||||
|
||||
return Comparison::greater($argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '>=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
*/
|
||||
public static function greaterOrEqual(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float $argument2
|
||||
): Comparison {
|
||||
|
||||
return Comparison::greaterOrEqual($argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '<' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
*/
|
||||
public static function less(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float $argument2
|
||||
): Comparison {
|
||||
|
||||
return Comparison::less($argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '<=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
*/
|
||||
public static function lessOrEqual(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float $argument2
|
||||
): Comparison {
|
||||
|
||||
return Comparison::lessOrEqual($argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'IN' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Select|scalar[] $set A set of values. A select query or array of scalars.
|
||||
*/
|
||||
public static function in(Expression $subject, Select|array $set): Comparison
|
||||
{
|
||||
return Comparison::in($subject, $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'NOT IN' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Select|scalar[] $set A set of values. A select query or array of scalars.
|
||||
*/
|
||||
public static function notIn(Expression $subject, Select|array $set): Comparison
|
||||
{
|
||||
return Comparison::notIn($subject, $set);
|
||||
}
|
||||
}
|
||||
820
application/Espo/ORM/Query/Part/Expression.php
Normal file
820
application/Espo/ORM/Query/Part/Expression.php
Normal file
@@ -0,0 +1,820 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
use Espo\ORM\Query\Part\Expression\Util;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A complex expression. Can be a function or a simple column reference. Immutable.
|
||||
*/
|
||||
class Expression implements WhereItem
|
||||
{
|
||||
private string $expression;
|
||||
|
||||
public function __construct(string $expression)
|
||||
{
|
||||
if ($expression === '') {
|
||||
throw new RuntimeException("Expression can't be empty.");
|
||||
}
|
||||
|
||||
if (str_ends_with($expression, ':')) {
|
||||
throw new RuntimeException("Expression should not end with `:`.");
|
||||
}
|
||||
|
||||
$this->expression = $expression;
|
||||
}
|
||||
|
||||
public function getRaw(): array
|
||||
{
|
||||
return [$this->getRawKey() => null];
|
||||
}
|
||||
|
||||
public function getRawKey(): string
|
||||
{
|
||||
return $this->expression . ':';
|
||||
}
|
||||
|
||||
public function getRawValue(): mixed
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a string expression.
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an expression from a string.
|
||||
*/
|
||||
public static function create(string $expression): self
|
||||
{
|
||||
return new self($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an expression from a scalar value or NULL.
|
||||
*
|
||||
* @param string|float|int|bool|null $value A scalar or NULL.
|
||||
*/
|
||||
public static function value(string|float|int|bool|null $value): self
|
||||
{
|
||||
return self::create(self::stringifyArgument($value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a column reference expression.
|
||||
*
|
||||
* @param string $expression Examples: `columnName`, `alias.columnName`.
|
||||
*/
|
||||
public static function column(string $expression): self
|
||||
{
|
||||
$string = $expression;
|
||||
|
||||
if (strlen($string) && $string[0] === '@') {
|
||||
$string = substr($string, 1);
|
||||
}
|
||||
|
||||
if ($string === '') {
|
||||
throw new RuntimeException("Empty column.");
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z\d.]+$/', $string)) {
|
||||
throw new RuntimeException("Bad column. Must be of letters, digits. Can have a dot.");
|
||||
}
|
||||
|
||||
return self::create($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an alias reference expression.
|
||||
*
|
||||
* @param string $expression Examples: `someAlias`, `subQueryAlias.someAlias`.
|
||||
* @since 8.1.0
|
||||
*/
|
||||
public static function alias(string $expression): self
|
||||
{
|
||||
if ($expression === '') {
|
||||
throw new RuntimeException("Empty alias.");
|
||||
}
|
||||
|
||||
if (!preg_match('/^[a-zA-Z\d.]+$/', $expression)) {
|
||||
throw new RuntimeException("Bad alias expression. Must be of letters, digits. Can have a dot.");
|
||||
}
|
||||
|
||||
if (str_contains($expression, '.')) {
|
||||
[$left, $right] = explode('.', $expression, 2);
|
||||
|
||||
return self::create($left . '.#' . $right);
|
||||
}
|
||||
|
||||
return self::create('#' . $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'COUNT' function.
|
||||
*
|
||||
* @param Expression $expression
|
||||
*/
|
||||
public static function count(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('COUNT', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'MIN' function.
|
||||
*
|
||||
* @param Expression $expression
|
||||
*/
|
||||
public static function min(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('MIN', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'MAX' function.
|
||||
*
|
||||
* @param Expression $expression
|
||||
*/
|
||||
public static function max(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('MAX', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'SUM' function.
|
||||
*
|
||||
* @param Expression $expression
|
||||
*/
|
||||
public static function sum(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('SUM', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'AVG' function.
|
||||
*
|
||||
* @param Expression $expression
|
||||
*/
|
||||
public static function average(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('AVG', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'IF' function. Return $then if a condition is true, $else otherwise.
|
||||
*
|
||||
* @param Expression $condition A condition.
|
||||
* @param Expression|string|int|float|bool|null $then Then.
|
||||
* @param Expression|string|int|float|bool|null $else Else.
|
||||
*/
|
||||
public static function if(
|
||||
Expression $condition,
|
||||
Expression|string|int|float|bool|null $then,
|
||||
Expression|string|int|float|bool|null $else
|
||||
): self {
|
||||
|
||||
return self::composeFunction('IF', $condition, $then, $else);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'CASE' expression. Even arguments define 'WHEN' conditions, following odd arguments
|
||||
* define 'THEN' values. The last unmatched argument defines the 'ELSE' value.
|
||||
*
|
||||
* @param Expression|scalar|null ...$arguments Arguments.
|
||||
*/
|
||||
public static function switch(Expression|string|int|float|bool|null ...$arguments): self
|
||||
{
|
||||
if (count($arguments) < 2) {
|
||||
throw new RuntimeException("Too few arguments.");
|
||||
}
|
||||
|
||||
return self::composeFunction('SWITCH', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'CASE' expression that maps keys to values. The first argument is the value to map.
|
||||
* Odd arguments define keys, the following even arguments define mapped values.
|
||||
* The last unmatched argument defines the 'ELSE' value.
|
||||
*
|
||||
* @param Expression|scalar|null ...$arguments Arguments.
|
||||
*/
|
||||
public static function map(Expression|string|int|float|bool|null ...$arguments): self
|
||||
{
|
||||
if (count($arguments) < 3) {
|
||||
throw new RuntimeException("Too few arguments.");
|
||||
}
|
||||
|
||||
return self::composeFunction('MAP', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'IFNULL' function. If the first argument is not NULL, returns it,
|
||||
* otherwise returns the second argument.
|
||||
*
|
||||
* @param Expression $value A value.
|
||||
* @param Expression|string|int|float|bool $fallbackValue A fallback value.
|
||||
*/
|
||||
public static function ifNull(Expression $value, Expression|string|int|float|bool $fallbackValue): self
|
||||
{
|
||||
return self::composeFunction('IFNULL', $value, $fallbackValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'NULLIF' function. If $arg1 = $arg2, returns NULL,
|
||||
* otherwise returns the first argument.
|
||||
*
|
||||
* @param Expression|string|int|float|bool $argument1
|
||||
* @param Expression|string|int|float|bool $argument2
|
||||
*/
|
||||
public static function nullIf(
|
||||
Expression|string|int|float|bool $argument1,
|
||||
Expression|string|int|float|bool $argument2
|
||||
): self {
|
||||
|
||||
return self::composeFunction('NULLIF', $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'LIKE' operator.
|
||||
*
|
||||
* Example: `like(Expression:column('test'), 'test%'`.
|
||||
*
|
||||
* @param Expression $subject A subject.
|
||||
* @param Expression|string $pattern A pattern.
|
||||
*/
|
||||
public static function like(Expression $subject, Expression|string $pattern): self
|
||||
{
|
||||
return self::composeFunction('LIKE', $subject, $pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* '=' operator.
|
||||
*
|
||||
* @param Expression|string|int|float|bool $argument1
|
||||
* @param Expression|string|int|float|bool $argument2
|
||||
*/
|
||||
public static function equal(
|
||||
Expression|string|int|float|bool $argument1,
|
||||
Expression|string|int|float|bool $argument2
|
||||
): self {
|
||||
|
||||
return self::composeFunction('EQUAL', $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* '<>' operator.
|
||||
*
|
||||
* @param Expression|string|int|float|bool $argument1
|
||||
* @param Expression|string|int|float|bool $argument2
|
||||
*/
|
||||
public static function notEqual(
|
||||
Expression|string|int|float|bool $argument1,
|
||||
Expression|string|int|float|bool $argument2
|
||||
): self {
|
||||
|
||||
return self::composeFunction('NOT_EQUAL', $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* '>' operator.
|
||||
*
|
||||
* @param Expression|string|int|float|bool $argument1
|
||||
* @param Expression|string|int|float|bool $argument2
|
||||
*/
|
||||
public static function greater(
|
||||
Expression|string|int|float|bool $argument1,
|
||||
Expression|string|int|float|bool $argument2
|
||||
): self {
|
||||
|
||||
return self::composeFunction('GREATER_THAN', $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* '<' operator.
|
||||
*
|
||||
* @param Expression|string|int|float|bool $argument1
|
||||
* @param Expression|string|int|float|bool $argument2
|
||||
*/
|
||||
public static function less(
|
||||
Expression|string|int|float|bool $argument1,
|
||||
Expression|string|int|float|bool $argument2
|
||||
): self {
|
||||
|
||||
return self::composeFunction('LESS_THAN', $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* '>=' operator.
|
||||
*
|
||||
* @param Expression|string|int|float|bool $argument1
|
||||
* @param Expression|string|int|float|bool $argument2
|
||||
*/
|
||||
public static function greaterOrEqual(
|
||||
Expression|string|int|float|bool $argument1,
|
||||
Expression|string|int|float|bool $argument2
|
||||
): self {
|
||||
|
||||
return self::composeFunction('GREATER_THAN_OR_EQUAL', $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* '<=' operator.
|
||||
*
|
||||
* @param Expression|string|int|float|bool $argument1
|
||||
* @param Expression|string|int|float|bool $argument2
|
||||
*/
|
||||
public static function lessOrEqual(
|
||||
Expression|string|int|float|bool $argument1,
|
||||
Expression|string|int|float|bool $argument2
|
||||
): self {
|
||||
|
||||
return self::composeFunction('LESS_THAN_OR_EQUAL', $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'IS NULL' operator.
|
||||
*
|
||||
* @param Expression $expression
|
||||
*/
|
||||
public static function isNull(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('IS_NULL', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'IS NOT NULL' operator.
|
||||
*
|
||||
* @param Expression $expression
|
||||
*/
|
||||
public static function isNotNull(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('IS_NOT_NULL', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'IN' operator. Check whether a value is within a set of values.
|
||||
*
|
||||
* @param Expression $expression
|
||||
* @param Expression[]|string[]|int[]|float[]|bool[] $values
|
||||
*/
|
||||
public static function in(Expression $expression, array $values): self
|
||||
{
|
||||
return self::composeFunction('IN', $expression, ...$values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'NOT IN' operator. Check whether a value is not within a set of values.
|
||||
*
|
||||
* @param Expression $expression
|
||||
* @param Expression[]|string[]|int[]|float[]|bool[] $values
|
||||
*/
|
||||
public static function notIn(Expression $expression, array $values): self
|
||||
{
|
||||
return self::composeFunction('NOT_IN', $expression, ...$values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'COALESCE' function. Returns the first non-NULL value in the list.
|
||||
*/
|
||||
public static function coalesce(Expression ...$expressions): self
|
||||
{
|
||||
return self::composeFunction('COALESCE', ...$expressions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'MONTH' function. Returns a month number of a passed date or date-time.
|
||||
*
|
||||
* @param Expression $date
|
||||
*/
|
||||
public static function month(Expression $date): self
|
||||
{
|
||||
return self::composeFunction('MONTH_NUMBER', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'WEEK' function. Returns a week number of a passed date or date-time.
|
||||
*
|
||||
* @param Expression $date
|
||||
* @param int $weekStart A week start. `0` for Sunday, `1` for Monday.
|
||||
*/
|
||||
public static function week(Expression $date, int $weekStart = 0): self
|
||||
{
|
||||
if ($weekStart !== 0 && $weekStart !== 1) {
|
||||
throw new RuntimeException("Week start can be only 0 or 1.");
|
||||
}
|
||||
|
||||
if ($weekStart === 1) {
|
||||
return self::composeFunction('WEEK_NUMBER_1', $date);
|
||||
}
|
||||
|
||||
return self::composeFunction('WEEK_NUMBER', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'DAYOFWEEK' function. A day of week of a passed date or date-time. 1..7.
|
||||
*
|
||||
* @param Expression $date
|
||||
*/
|
||||
public static function dayOfWeek(Expression $date): self
|
||||
{
|
||||
return self::composeFunction('DAYOFWEEK', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'DAYOFMONTH' function. A day of month of a passed date or date-time. 1..31.
|
||||
*
|
||||
* @param Expression $date
|
||||
*/
|
||||
public static function dayOfMonth(Expression $date): self
|
||||
{
|
||||
return self::composeFunction('DAYOFMONTH', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'YEAR' function. A year number of a passed date or date-time.
|
||||
*
|
||||
* @param Expression $date
|
||||
*/
|
||||
public static function year(Expression $date): self
|
||||
{
|
||||
return self::composeFunction('YEAR', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'YEAR' function taking into account a fiscal year start.
|
||||
*
|
||||
* @param Expression $date
|
||||
* @param int $fiscalYearStart A month number of a fiscal year start. 1..12.
|
||||
*/
|
||||
public static function yearFiscal(Expression $date, int $fiscalYearStart = 1): self
|
||||
{
|
||||
if ($fiscalYearStart < 1 || $fiscalYearStart > 12) {
|
||||
throw new RuntimeException("Bad fiscal year start.");
|
||||
}
|
||||
|
||||
return self::composeFunction('YEAR_' . strval($fiscalYearStart), $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'QUARTER' function. A quarter number of a passed date or date-time. 1..4.
|
||||
*
|
||||
* @param Expression $date
|
||||
*/
|
||||
public static function quarter(Expression $date): self
|
||||
{
|
||||
return self::composeFunction('QUARTER_NUMBER', $date);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'HOUR' function. A hour number of a passed date-time. 0..23.
|
||||
*
|
||||
* @param Expression $dateTime
|
||||
*/
|
||||
public static function hour(Expression $dateTime): self
|
||||
{
|
||||
return self::composeFunction('HOUR', $dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'MINUTE' function. A minute number of a passed date-time. 0..59.
|
||||
*
|
||||
* @param Expression $dateTime
|
||||
*/
|
||||
public static function minute(Expression $dateTime): self
|
||||
{
|
||||
return self::composeFunction('MINUTE', $dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'SECOND' function. A second number of a passed date-time. 0..59.
|
||||
*
|
||||
* @param Expression $dateTime
|
||||
*/
|
||||
public static function second(Expression $dateTime): self
|
||||
{
|
||||
return self::composeFunction('SECOND', $dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'UNIX_TIMESTAMP' function. Seconds.
|
||||
*
|
||||
* @param Expression $dateTime
|
||||
* @since 9.0.0
|
||||
*/
|
||||
public static function unixTimestamp(Expression $dateTime): self
|
||||
{
|
||||
return self::composeFunction('UNIX_TIMESTAMP', $dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'NOW' function. A current date and time.
|
||||
*/
|
||||
public static function now(): self
|
||||
{
|
||||
return self::composeFunction('NOW');
|
||||
}
|
||||
|
||||
/**
|
||||
* 'DATE' function. Returns a date part of a date-time.
|
||||
*
|
||||
* @param Expression $dateTime
|
||||
*/
|
||||
public static function date(Expression $dateTime): self
|
||||
{
|
||||
return self::composeFunction('DATE', $dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Time zone conversion function. Converts a passed data-time applying a hour offset.
|
||||
*
|
||||
* @param Expression $date
|
||||
*/
|
||||
public static function convertTimezone(Expression $date, float $offset): self
|
||||
{
|
||||
return self::composeFunction('TZ', $date, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'CONCAT' function. Concatenates multiple strings.
|
||||
*
|
||||
* @param Expression|string ...$strings Strings.
|
||||
*/
|
||||
public static function concat(Expression|string ...$strings): self
|
||||
{
|
||||
return self::composeFunction('CONCAT', ...$strings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'LEFT' function. Returns a specified number of characters from the left of a string.
|
||||
*/
|
||||
public static function left(Expression $string, int $offset): self
|
||||
{
|
||||
return self::composeFunction('LEFT', $string, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'LOWER' function. Converts a string to a lower case.
|
||||
*/
|
||||
public static function lowerCase(Expression $string): self
|
||||
{
|
||||
return self::composeFunction('LOWER', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'UPPER' function. Converts a string to an upper case.
|
||||
*/
|
||||
public static function upperCase(Expression $string): self
|
||||
{
|
||||
return self::composeFunction('UPPER', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'TRIM' function. Removes leading and trailing spaces.
|
||||
*/
|
||||
public static function trim(Expression $string): self
|
||||
{
|
||||
return self::composeFunction('TRIM', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'BINARY' function. Converts a string value to a binary string.
|
||||
*/
|
||||
public static function binary(Expression $string): self
|
||||
{
|
||||
return self::composeFunction('BINARY', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'CHAR_LENGTH' function. A number of characters in a string.
|
||||
*/
|
||||
public static function charLength(Expression $string): self
|
||||
{
|
||||
return self::composeFunction('CHAR_LENGTH', $string);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'REPLACE' function. Replaces all the occurrences of a sub-string within a string.
|
||||
*
|
||||
* @param Expression $haystack A subject.
|
||||
* @param Expression|string $needle A string to be replaced.
|
||||
* @param Expression|string $replaceWith A string to replace with.
|
||||
*/
|
||||
public static function replace(
|
||||
Expression $haystack,
|
||||
Expression|string $needle,
|
||||
Expression|string $replaceWith
|
||||
): self {
|
||||
|
||||
return self::composeFunction('REPLACE', $haystack, $needle, $replaceWith);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'FIELD' operator (in MySQL). Returns an index (position) of an expression
|
||||
* in a list. Returns `0` if not found. The first index is `1`.
|
||||
*
|
||||
* @param Expression $expression
|
||||
* @param Expression[]|string[]|int[]|float[] $list
|
||||
*/
|
||||
public static function positionInList(Expression $expression, array $list): self
|
||||
{
|
||||
return self::composeFunction('POSITION_IN_LIST', $expression, ...$list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'ADD' function. Adds two or more numbers.
|
||||
*
|
||||
* @param Expression|int|float ...$arguments
|
||||
*/
|
||||
public static function add(Expression|int|float ...$arguments): self
|
||||
{
|
||||
if (count($arguments) < 2) {
|
||||
throw new RuntimeException("Too few arguments.");
|
||||
}
|
||||
|
||||
return self::composeFunction('ADD', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'SUB' function. Subtraction.
|
||||
*
|
||||
* @param Expression|int|float ...$arguments
|
||||
*/
|
||||
public static function subtract(Expression|int|float ...$arguments): self
|
||||
{
|
||||
if (count($arguments) < 2) {
|
||||
throw new RuntimeException("Too few arguments.");
|
||||
}
|
||||
|
||||
return self::composeFunction('SUB', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'MUL' function. Multiplication.
|
||||
*
|
||||
* @param Expression|int|float ...$arguments
|
||||
*/
|
||||
public static function multiply(Expression|int|float ...$arguments): self
|
||||
{
|
||||
if (count($arguments) < 2) {
|
||||
throw new RuntimeException("Too few arguments.");
|
||||
}
|
||||
|
||||
return self::composeFunction('MUL', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'DIV' function. Division.
|
||||
*
|
||||
* @param Expression|int|float ...$arguments
|
||||
*/
|
||||
public static function divide(Expression|int|float ...$arguments): self
|
||||
{
|
||||
if (count($arguments) < 2) {
|
||||
throw new RuntimeException("Too few arguments.");
|
||||
}
|
||||
|
||||
return self::composeFunction('DIV', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'MOD' function. Returns a remainder of a number divided by another number.
|
||||
*
|
||||
* @param Expression|int|float ...$arguments
|
||||
*/
|
||||
public static function modulo(Expression|int|float ...$arguments): self
|
||||
{
|
||||
if (count($arguments) < 2) {
|
||||
throw new RuntimeException("Too few arguments.");
|
||||
}
|
||||
|
||||
return self::composeFunction('MOD', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'FLOOR' function. The largest integer value not greater than the argument.
|
||||
*/
|
||||
public static function floor(Expression $number): self
|
||||
{
|
||||
return self::composeFunction('FLOOR', $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'CEIL' function. The largest integer value not greater than the argument.
|
||||
*/
|
||||
public static function ceil(Expression $number): self
|
||||
{
|
||||
return self::composeFunction('CEIL', $number);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'ROUND' function. Rounds a number to a specified number of decimal places.
|
||||
*/
|
||||
public static function round(Expression $number, int $precision = 0): self
|
||||
{
|
||||
return self::composeFunction('ROUND', $number, $precision);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'GREATEST' function. A max value from a list of expressions.
|
||||
*/
|
||||
public static function greatest(Expression ...$arguments): self
|
||||
{
|
||||
return self::composeFunction('GREATEST', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'LEAST' function. A min value from a list of expressions.
|
||||
*/
|
||||
public static function least(Expression ...$arguments): self
|
||||
{
|
||||
return self::composeFunction('LEAST', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'ANY_VALUE' function.
|
||||
*
|
||||
* @since 9.1.6
|
||||
*/
|
||||
public function anyValue(Expression $expression): self
|
||||
{
|
||||
return self::composeFunction('ANY_VALUE', $expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'AND' operator. Returns TRUE if all arguments are TRUE.
|
||||
*/
|
||||
public static function and(Expression ...$arguments): self
|
||||
{
|
||||
return self::composeFunction('AND', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'OR' operator. Returns TRUE if at least one argument is TRUE.
|
||||
*/
|
||||
public static function or(Expression ...$arguments): self
|
||||
{
|
||||
return self::composeFunction('OR', ...$arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'NOT' operator. Negates an expression.
|
||||
*/
|
||||
public static function not(Expression $argument): self
|
||||
{
|
||||
return self::composeFunction('NOT', $argument);
|
||||
}
|
||||
|
||||
/**
|
||||
* 'ROW' constructor.
|
||||
*/
|
||||
public static function row(Expression ...$arguments): self
|
||||
{
|
||||
return self::composeFunction('ROW', ...$arguments);
|
||||
}
|
||||
|
||||
private static function composeFunction(
|
||||
string $function,
|
||||
Expression|bool|int|float|string|null ...$arguments
|
||||
): self {
|
||||
|
||||
return Util::composeFunction($function, ...$arguments);
|
||||
}
|
||||
|
||||
private static function stringifyArgument(Expression|bool|int|float|string|null $argument): string
|
||||
{
|
||||
return Util::stringifyArgument($argument);
|
||||
}
|
||||
}
|
||||
88
application/Espo/ORM/Query/Part/Expression/Util.php
Normal file
88
application/Espo/ORM/Query/Part/Expression/Util.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?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\ORM\Query\Part\Expression;
|
||||
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
|
||||
class Util
|
||||
{
|
||||
/**
|
||||
* Compose an expression by a function name and arguments.
|
||||
*
|
||||
* @param Expression|bool|int|float|string|null ...$arguments Arguments
|
||||
*/
|
||||
public static function composeFunction(
|
||||
string $function,
|
||||
Expression|bool|int|float|string|null ...$arguments
|
||||
): Expression {
|
||||
|
||||
$stringifiedItems = array_map(
|
||||
function ($item) {
|
||||
return self::stringifyArgument($item);
|
||||
},
|
||||
$arguments
|
||||
);
|
||||
|
||||
$expression = $function . ':(' . implode(', ', $stringifiedItems) . ')';
|
||||
|
||||
return Expression::create($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stringify an argument.
|
||||
*
|
||||
* @param Expression|bool|int|float|string|null $argument
|
||||
*/
|
||||
public static function stringifyArgument(Expression|bool|int|float|string|null $argument): string
|
||||
{
|
||||
|
||||
if ($argument instanceof Expression) {
|
||||
return $argument->getValue();
|
||||
}
|
||||
|
||||
if (is_null($argument)) {
|
||||
return 'NULL';
|
||||
}
|
||||
|
||||
if (is_bool($argument)) {
|
||||
return $argument ? 'TRUE': 'FALSE';
|
||||
}
|
||||
|
||||
if (is_int($argument)) {
|
||||
return strval($argument);
|
||||
}
|
||||
|
||||
if (is_float($argument)) {
|
||||
return strval($argument);
|
||||
}
|
||||
|
||||
return '\'' . str_replace('\'', '\\\'', $argument) . '\'';
|
||||
}
|
||||
}
|
||||
300
application/Espo/ORM/Query/Part/Join.php
Normal file
300
application/Espo/ORM/Query/Part/Join.php
Normal file
@@ -0,0 +1,300 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
use Espo\ORM\Query\Part\Join\JoinType;
|
||||
use Espo\ORM\Query\Select;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* A join item. Immutable.
|
||||
*/
|
||||
class Join
|
||||
{
|
||||
/** A table join. */
|
||||
public const MODE_TABLE = 0;
|
||||
/** A relation join. */
|
||||
public const MODE_RELATION = 1;
|
||||
/** A sub-query join. */
|
||||
public const MODE_SUB_QUERY = 3;
|
||||
|
||||
private ?WhereItem $conditions = null;
|
||||
private bool $onlyMiddle = false;
|
||||
private bool $isLateral = false;
|
||||
private ?JoinType $type = null;
|
||||
|
||||
private function __construct(
|
||||
private string|Select $target,
|
||||
private ?string $alias = null
|
||||
) {
|
||||
if ($target === '' || $alias === '') {
|
||||
throw new RuntimeException("Bad join.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a join target. A relation name, table or sub-query.
|
||||
* A relation name is in camelCase, a table is in CamelCase.
|
||||
*/
|
||||
public function getTarget(): string|Select
|
||||
{
|
||||
return $this->target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an alias.
|
||||
*/
|
||||
public function getAlias(): ?string
|
||||
{
|
||||
return $this->alias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get join conditions.
|
||||
*/
|
||||
public function getConditions(): ?WhereItem
|
||||
{
|
||||
return $this->conditions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a sub-query join.
|
||||
*/
|
||||
public function isSubQuery(): bool
|
||||
{
|
||||
return !is_string($this->target);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a table join.
|
||||
*/
|
||||
public function isTable(): bool
|
||||
{
|
||||
return is_string($this->target) && $this->target[0] === ucfirst($this->target[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a relation join.
|
||||
*/
|
||||
public function isRelation(): bool
|
||||
{
|
||||
return !$this->isSubQuery() && !$this->isTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a join mode.
|
||||
*
|
||||
* @return self::MODE_TABLE|self::MODE_RELATION|self::MODE_SUB_QUERY
|
||||
*/
|
||||
public function getMode(): int
|
||||
{
|
||||
if ($this->isSubQuery()) {
|
||||
return self::MODE_SUB_QUERY;
|
||||
}
|
||||
|
||||
if ($this->isRelation()) {
|
||||
return self::MODE_RELATION;
|
||||
}
|
||||
|
||||
return self::MODE_TABLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is only middle table to be joined.
|
||||
*/
|
||||
public function isOnlyMiddle(): bool
|
||||
{
|
||||
return $this->onlyMiddle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is LATERAL.
|
||||
*
|
||||
* @since 9.1.6
|
||||
*/
|
||||
public function isLateral(): bool
|
||||
{
|
||||
return $this->isLateral;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a join type.
|
||||
*
|
||||
* @return JoinType|null
|
||||
*
|
||||
* @since 9.2.0
|
||||
*/
|
||||
public function getType(): ?JoinType
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*
|
||||
* @param string|Select $target
|
||||
* A relation name, table or sub-query. A relation name should be in camelCase, a table in CamelCase.
|
||||
* When joining a table or sub-query, conditions should be specified.
|
||||
* When joining a relation, conditions will be applied automatically, additional conditions can
|
||||
* be specified as well.
|
||||
* @param ?string $alias An alias.
|
||||
*/
|
||||
public static function create(string|Select $target, ?string $alias = null): self
|
||||
{
|
||||
return new self($target, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a table target.
|
||||
*
|
||||
* @param string $table A table name. Should start with an upper case letter.
|
||||
* @param ?string $alias An alias.
|
||||
*/
|
||||
public static function createWithTableTarget(string $table, ?string $alias = null): self
|
||||
{
|
||||
return self::create(ucfirst($table), $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a relation target. Conditions will be applied automatically.
|
||||
*
|
||||
* @param string $relation A relation name. Should start with a lower case letter.
|
||||
* @param ?string $alias An alias.
|
||||
*/
|
||||
public static function createWithRelationTarget(string $relation, ?string $alias = null): self
|
||||
{
|
||||
return self::create(lcfirst($relation), $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create with a sub-query.
|
||||
*
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @param string $alias An alias.
|
||||
*/
|
||||
public static function createWithSubQuery(Select $subQuery, string $alias): self
|
||||
{
|
||||
return new self($subQuery, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an alias.
|
||||
*/
|
||||
public function withAlias(?string $alias): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->alias = $alias;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with join conditions.
|
||||
*/
|
||||
public function withConditions(?WhereItem $conditions): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->conditions = $conditions;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join only middle table. For many-to-many relationships.
|
||||
*/
|
||||
public function withOnlyMiddle(bool $onlyMiddle = true): self
|
||||
{
|
||||
if (!$this->isRelation()) {
|
||||
throw new LogicException("Only-middle is compatible only with relation joins.");
|
||||
}
|
||||
|
||||
$obj = clone $this;
|
||||
$obj->onlyMiddle = $onlyMiddle;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With LATERAL. Only for a sub-query join.
|
||||
*
|
||||
* @since 9.1.6
|
||||
*/
|
||||
public function withLateral(bool $isLateral = true): self
|
||||
{
|
||||
if (!$this->isSubQuery()) {
|
||||
throw new LogicException("Lateral can be used only with sub-query joins.");
|
||||
}
|
||||
|
||||
$obj = clone $this;
|
||||
$obj->isLateral = $isLateral;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With LEFT type.
|
||||
*
|
||||
* @since 9.2.0.
|
||||
*/
|
||||
public function withLeft(): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->type = JoinType::left;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With INNER type.
|
||||
*
|
||||
* @since 9.2.0.
|
||||
*/
|
||||
public function withInner(): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->type = JoinType::inner;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* With a join type.
|
||||
*
|
||||
* @since 9.2.0.
|
||||
*/
|
||||
public function withType(JoinType $type): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->type = $type;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
46
application/Espo/ORM/Query/Part/Join/JoinType.php
Normal file
46
application/Espo/ORM/Query/Part/Join/JoinType.php
Normal file
@@ -0,0 +1,46 @@
|
||||
<?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\ORM\Query\Part\Join;
|
||||
|
||||
/**
|
||||
* @since 9.2.0
|
||||
*/
|
||||
enum JoinType: string
|
||||
{
|
||||
/**
|
||||
* An INNER join.
|
||||
*/
|
||||
case inner = 'inner';
|
||||
|
||||
/**
|
||||
* A LEFT join.
|
||||
*/
|
||||
case left = 'left';
|
||||
}
|
||||
156
application/Espo/ORM/Query/Part/Order.php
Normal file
156
application/Espo/ORM/Query/Part/Order.php
Normal file
@@ -0,0 +1,156 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* An order item. Immutable.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Order
|
||||
{
|
||||
public const ASC = 'ASC';
|
||||
public const DESC = 'DESC';
|
||||
|
||||
private Expression $expression;
|
||||
private bool $isDesc = false;
|
||||
|
||||
private function __construct(Expression $expression)
|
||||
{
|
||||
$this->expression = $expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an expression.
|
||||
*/
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function isDesc(): bool
|
||||
{
|
||||
return $this->isDesc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a direction.
|
||||
*
|
||||
* @return self::DESC|self::ASC
|
||||
*/
|
||||
public function getDirection(): string
|
||||
{
|
||||
return $this->isDesc ? self::DESC : self::ASC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create.
|
||||
*/
|
||||
public static function create(Expression $expression): self
|
||||
{
|
||||
return new self($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create from a string expression.
|
||||
*/
|
||||
public static function fromString(string $expression): self
|
||||
{
|
||||
return self::create(
|
||||
Expression::create($expression)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an order by position in list.
|
||||
* Note: Reverses the list and applies DESC order.
|
||||
*
|
||||
* @param string[]|int[]|float[] $list
|
||||
*/
|
||||
public static function createByPositionInList(Expression $expression, array $list): self
|
||||
{
|
||||
$orderExpression = Expression::positionInList($expression, array_reverse($list));
|
||||
|
||||
return self::create($orderExpression)->withDesc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with an ascending direction.
|
||||
*/
|
||||
public function withAsc(): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->isDesc = false;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a descending direction.
|
||||
*/
|
||||
public function withDesc(): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->isDesc = true;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a direction.
|
||||
*
|
||||
* @params self::ASC|self::DESC $direction
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function withDirection(string $direction): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->isDesc = strtoupper($direction) === self::DESC;
|
||||
|
||||
if (!in_array(strtoupper($direction), [self::DESC, self::ASC])) {
|
||||
throw new RuntimeException("Bad order direction.");
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone with a reverse direction.
|
||||
*/
|
||||
public function withReverseDirection(): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->isDesc = !$this->isDesc;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
96
application/Espo/ORM/Query/Part/OrderList.php
Normal file
96
application/Espo/ORM/Query/Part/OrderList.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
|
||||
/**
|
||||
* A list of order items.
|
||||
*
|
||||
* Immutable.
|
||||
*
|
||||
* @implements Iterator<Order>
|
||||
*/
|
||||
class OrderList implements Iterator
|
||||
{
|
||||
private int $position = 0;
|
||||
/** @var Order[] */
|
||||
private array $list;
|
||||
|
||||
/**
|
||||
* @param Order[] $list
|
||||
*/
|
||||
private function __construct(array $list)
|
||||
{
|
||||
foreach ($list as $item) {
|
||||
if (!$item instanceof Order) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
$this->list = $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*
|
||||
* @param Order[] $list
|
||||
*/
|
||||
public static function create(array $list): self
|
||||
{
|
||||
return new self($list);
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->position = 0;
|
||||
}
|
||||
|
||||
public function current(): Order
|
||||
{
|
||||
return $this->list[$this->position];
|
||||
}
|
||||
|
||||
public function key(): int
|
||||
{
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function next(): void
|
||||
{
|
||||
++$this->position;
|
||||
}
|
||||
|
||||
public function valid(): bool
|
||||
{
|
||||
return isset($this->list[$this->position]);
|
||||
}
|
||||
}
|
||||
73
application/Espo/ORM/Query/Part/Selection.php
Normal file
73
application/Espo/ORM/Query/Part/Selection.php
Normal file
@@ -0,0 +1,73 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
/**
|
||||
* A select item. Immutable.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Selection
|
||||
{
|
||||
private function __construct(
|
||||
private Expression $expression,
|
||||
private ?string $alias = null
|
||||
) {}
|
||||
|
||||
public function getExpression(): Expression
|
||||
{
|
||||
return $this->expression;
|
||||
}
|
||||
|
||||
public function getAlias(): ?string
|
||||
{
|
||||
return $this->alias;
|
||||
}
|
||||
|
||||
public static function create(Expression $expression, ?string $alias = null): self
|
||||
{
|
||||
return new self($expression, $alias);
|
||||
}
|
||||
|
||||
public static function fromString(string $expression): self
|
||||
{
|
||||
return self::create(
|
||||
Expression::create($expression)
|
||||
);
|
||||
}
|
||||
|
||||
public function withAlias(?string $alias): self
|
||||
{
|
||||
$obj = clone $this;
|
||||
$obj->alias = $alias;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
108
application/Espo/ORM/Query/Part/Where/AndGroup.php
Normal file
108
application/Espo/ORM/Query/Part/Where/AndGroup.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?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\ORM\Query\Part\Where;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
/**
|
||||
* AND-group. Immutable.
|
||||
*/
|
||||
class AndGroup implements WhereItem
|
||||
{
|
||||
/** @var array<string|int, mixed> */
|
||||
private $rawValue = [];
|
||||
|
||||
/**
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
public function getRaw(): array
|
||||
{
|
||||
return ['AND' => $this->getRawValue()];
|
||||
}
|
||||
|
||||
public function getRawKey(): string
|
||||
{
|
||||
return 'AND';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
public function getRawValue(): array
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number of items.
|
||||
*/
|
||||
public function getItemCount(): int
|
||||
{
|
||||
return count($this->rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $whereClause
|
||||
* @return self
|
||||
*/
|
||||
public static function fromRaw(array $whereClause): self
|
||||
{
|
||||
if (count($whereClause) === 1 && array_keys($whereClause)[0] === 0) {
|
||||
$whereClause = $whereClause[0];
|
||||
}
|
||||
|
||||
// Do not refactor.
|
||||
$obj = static::class === WhereClause::class ?
|
||||
new WhereClause() :
|
||||
new self();
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$obj->rawValue = $whereClause;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function create(WhereItem ...$itemList): self
|
||||
{
|
||||
$builder = self::createBuilder();
|
||||
|
||||
foreach ($itemList as $item) {
|
||||
$builder->add($item);
|
||||
}
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
public static function createBuilder(): AndGroupBuilder
|
||||
{
|
||||
return new AndGroupBuilder();
|
||||
}
|
||||
}
|
||||
95
application/Espo/ORM/Query/Part/Where/AndGroupBuilder.php
Normal file
95
application/Espo/ORM/Query/Part/Where/AndGroupBuilder.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?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\ORM\Query\Part\Where;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
class AndGroupBuilder
|
||||
{
|
||||
/** @var array<string|int, mixed> */
|
||||
private array $raw = [];
|
||||
|
||||
public function build(): AndGroup
|
||||
{
|
||||
return AndGroup::fromRaw($this->raw);
|
||||
}
|
||||
|
||||
public function add(WhereItem $item): self
|
||||
{
|
||||
$key = $item->getRawKey();
|
||||
$value = $item->getRawValue();
|
||||
|
||||
if ($item instanceof AndGroup) {
|
||||
$this->raw = self::normalizeRaw($this->raw);
|
||||
|
||||
$this->raw[] = $item->getRawValue();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (count($this->raw) === 0) {
|
||||
$this->raw[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->raw = self::normalizeRaw($this->raw);
|
||||
|
||||
$this->raw[] = [$key => $value];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge with another AndGroup.
|
||||
*/
|
||||
public function merge(AndGroup $andGroup): self
|
||||
{
|
||||
$this->raw = array_merge(
|
||||
self::normalizeRaw($this->raw),
|
||||
self::normalizeRaw($andGroup->getRawValue())
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $raw
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
private static function normalizeRaw(array $raw): array
|
||||
{
|
||||
if (count($raw) === 1 && array_keys($raw)[0] !== 0) {
|
||||
return [$raw];
|
||||
}
|
||||
|
||||
return $raw;
|
||||
}
|
||||
}
|
||||
433
application/Espo/ORM/Query/Part/Where/Comparison.php
Normal file
433
application/Espo/ORM/Query/Part/Where/Comparison.php
Normal file
@@ -0,0 +1,433 @@
|
||||
<?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\ORM\Query\Part\Where;
|
||||
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\Select;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Compares an expression to a value or another expression. Immutable.
|
||||
*/
|
||||
class Comparison implements WhereItem
|
||||
{
|
||||
private const OPERATOR_EQUAL = '=';
|
||||
private const OPERATOR_NOT_EQUAL = '!=';
|
||||
private const OPERATOR_GREATER = '>';
|
||||
private const OPERATOR_GREATER_OR_EQUAL = '>=';
|
||||
private const OPERATOR_LESS = '<';
|
||||
private const OPERATOR_LESS_OR_EQUAL = '<=';
|
||||
private const OPERATOR_LIKE = '*';
|
||||
private const OPERATOR_NOT_LIKE = '!*';
|
||||
private const OPERATOR_IN_SUB_QUERY = '=s';
|
||||
private const OPERATOR_NOT_IN_SUB_QUERY = '!=s';
|
||||
private const OPERATOR_NOT_EQUAL_ANY = '!=any';
|
||||
private const OPERATOR_GREATER_ANY = '>any';
|
||||
private const OPERATOR_GREATER_OR_EQUAL_ANY = '>=any';
|
||||
private const OPERATOR_LESS_ANY = '<any';
|
||||
private const OPERATOR_LESS_OR_EQUAL_ANY = '<=any';
|
||||
private const OPERATOR_EQUAL_ALL = '=all';
|
||||
private const OPERATOR_GREATER_ALL = '>all';
|
||||
private const OPERATOR_GREATER_OR_EQUAL_ALL = '>=all';
|
||||
private const OPERATOR_LESS_ALL = '<all';
|
||||
private const OPERATOR_LESS_OR_EQUAL_ALL = '<=all';
|
||||
|
||||
private string $rawKey;
|
||||
private mixed $rawValue;
|
||||
|
||||
private function __construct(string $rawKey, mixed $rawValue)
|
||||
{
|
||||
$this->rawKey = $rawKey;
|
||||
$this->rawValue = $rawValue;
|
||||
}
|
||||
|
||||
public function getRaw(): array
|
||||
{
|
||||
return [$this->rawKey => $this->rawValue];
|
||||
}
|
||||
|
||||
public function getRawKey(): string
|
||||
{
|
||||
return $this->rawKey;
|
||||
}
|
||||
|
||||
public function getRawValue(): mixed
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float|bool|null $argument2 A scalar, expression or sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function equal(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float|bool|null $argument2
|
||||
): self {
|
||||
|
||||
return self::createComparison(self::OPERATOR_EQUAL, $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '!=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float|bool|null $argument2 A scalar, expression or sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function notEqual(
|
||||
Expression $argument1,
|
||||
Expression|Select|string|int|float|bool|null $argument2
|
||||
): self {
|
||||
|
||||
return self::createComparison(self::OPERATOR_NOT_EQUAL, $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'LIKE' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Expression|string $pattern A pattern.
|
||||
* @return self
|
||||
*/
|
||||
public static function like(Expression $subject, Expression|string $pattern): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_LIKE, $subject, $pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'NOT LIKE' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Expression|string $pattern A pattern.
|
||||
* @return self
|
||||
*/
|
||||
public static function notLike(Expression $subject, Expression|string $pattern): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_NOT_LIKE, $subject, $pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '>' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function greater(Expression $argument1, Expression|Select|string|int|float $argument2): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_GREATER, $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '>=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function greaterOrEqual(Expression $argument1, Expression|Select|string|int|float $argument2): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_GREATER_OR_EQUAL, $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '<' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function less(Expression $argument1, Expression|Select|string|int|float $argument2): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_LESS, $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '<=' comparison.
|
||||
*
|
||||
* @param Expression $argument1 An expression.
|
||||
* @param Expression|Select|string|int|float $argument2 A scalar, expression or sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function lessOrEqual(Expression $argument1, Expression|Select|string|int|float $argument2): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_LESS_OR_EQUAL, $argument1, $argument2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'IN' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Select|scalar[] $set A set of values. A select query or array of scalars.
|
||||
* @return self
|
||||
*/
|
||||
public static function in(Expression $subject, Select|array $set): self
|
||||
{
|
||||
if ($set instanceof Select) {
|
||||
return self::createInOrNotInSubQuery(self::OPERATOR_IN_SUB_QUERY, $subject, $set);
|
||||
}
|
||||
|
||||
return self::createInOrNotInArray(self::OPERATOR_EQUAL, $subject, $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create 'NOT IN' comparison.
|
||||
*
|
||||
* @param Expression $subject What to test.
|
||||
* @param Select|scalar[] $set A set of values. A select query or array of scalars.
|
||||
* @return self
|
||||
*/
|
||||
public static function notIn(Expression $subject, Select|array $set): self
|
||||
{
|
||||
if ($set instanceof Select) {
|
||||
return self::createInOrNotInSubQuery(self::OPERATOR_NOT_IN_SUB_QUERY, $subject, $set);
|
||||
}
|
||||
|
||||
return self::createInOrNotInArray(self::OPERATOR_NOT_EQUAL, $subject, $set);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '!= ANY' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function notEqualAny(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_NOT_EQUAL_ANY, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '> ANY' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function greaterAny(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_GREATER_ANY, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '< ANY' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function lessAny(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_LESS_ANY, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '>= ANY' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function greaterOrEqualAny(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_GREATER_OR_EQUAL_ANY, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '<= ANY' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function lessOrEqualAny(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_LESS_OR_EQUAL_ANY, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '= ALL' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function equalAll(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_EQUAL_ALL, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '> ALL' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function greaterAll(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_GREATER_ALL, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '< ALL' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function lessAll(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_LESS_ALL, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '>= ALL' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function greaterOrEqualAll(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_GREATER_OR_EQUAL_ALL, $argument, $subQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create '<= ALL' comparison.
|
||||
*
|
||||
* @param Expression $argument An expression.
|
||||
* @param Select $subQuery A sub-query.
|
||||
* @return self
|
||||
*/
|
||||
public static function lessOrEqualAll(Expression $argument, Select $subQuery): self
|
||||
{
|
||||
return self::createComparison(self::OPERATOR_LESS_OR_EQUAL_ALL, $argument, $subQuery);
|
||||
}
|
||||
|
||||
private static function createComparison(
|
||||
string $operator,
|
||||
Expression|string $argument1,
|
||||
Expression|Select|string|int|float|bool|null $argument2
|
||||
): self {
|
||||
|
||||
if (is_string($argument1)) {
|
||||
$key = $argument1;
|
||||
|
||||
if ($key === '') {
|
||||
throw new RuntimeException("Expression can't be empty.");
|
||||
}
|
||||
} else {
|
||||
$key = $argument1->getValue();
|
||||
}
|
||||
|
||||
if (str_ends_with($key, ':')) {
|
||||
throw new RuntimeException("Expression should not end with `:`.");
|
||||
}
|
||||
|
||||
$key .= $operator;
|
||||
|
||||
if ($argument2 instanceof Expression) {
|
||||
$key .= ':';
|
||||
|
||||
$value = $argument2->getValue();
|
||||
} else {
|
||||
$value = $argument2;
|
||||
}
|
||||
|
||||
return new self($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param scalar[] $valueList
|
||||
*/
|
||||
private static function createInOrNotInArray(
|
||||
string $operator,
|
||||
Expression|string $argument1,
|
||||
array $valueList
|
||||
): self {
|
||||
|
||||
foreach ($valueList as $item) {
|
||||
if (!is_scalar($item)) {
|
||||
throw new RuntimeException("Array items must be scalar.");
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($argument1)) {
|
||||
$key = $argument1;
|
||||
|
||||
if ($key === '') {
|
||||
throw new RuntimeException("Expression can't be empty.");
|
||||
}
|
||||
|
||||
if (str_ends_with($key, ':')) {
|
||||
throw new RuntimeException("Expression can't end with `:`.");
|
||||
}
|
||||
} else {
|
||||
$key = $argument1->getValue();
|
||||
}
|
||||
|
||||
$key .= $operator;
|
||||
|
||||
return new self($key, $valueList);
|
||||
}
|
||||
|
||||
private static function createInOrNotInSubQuery(
|
||||
string $operator,
|
||||
Expression|string $argument1,
|
||||
Select $query
|
||||
): self {
|
||||
|
||||
if (is_string($argument1)) {
|
||||
$key = $argument1;
|
||||
|
||||
if ($key === '') {
|
||||
throw new RuntimeException("Expression can't be empty.");
|
||||
}
|
||||
|
||||
if (str_ends_with($key, ':')) {
|
||||
throw new RuntimeException("Expression can't end with `:`.");
|
||||
}
|
||||
} else {
|
||||
$key = $argument1->getValue();
|
||||
}
|
||||
|
||||
$key .= $operator;
|
||||
|
||||
return new self($key, $query);
|
||||
}
|
||||
}
|
||||
61
application/Espo/ORM/Query/Part/Where/Exists.php
Normal file
61
application/Espo/ORM/Query/Part/Where/Exists.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?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\ORM\Query\Part\Where;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\Select;
|
||||
|
||||
/**
|
||||
* An EXISTS-operator. Immutable.
|
||||
*/
|
||||
class Exists implements WhereItem
|
||||
{
|
||||
private function __construct(private Select $rawValue) {}
|
||||
|
||||
public function getRaw(): array
|
||||
{
|
||||
return ['EXISTS' => $this->getRawValue()];
|
||||
}
|
||||
|
||||
public function getRawKey(): string
|
||||
{
|
||||
return 'EXISTS';
|
||||
}
|
||||
|
||||
public function getRawValue(): Select
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
public static function create(Select $subQuery): self
|
||||
{
|
||||
return new self($subQuery);
|
||||
}
|
||||
}
|
||||
80
application/Espo/ORM/Query/Part/Where/Not.php
Normal file
80
application/Espo/ORM/Query/Part/Where/Not.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?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\ORM\Query\Part\Where;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
/**
|
||||
* A NOT-operator. Immutable.
|
||||
*/
|
||||
class Not implements WhereItem
|
||||
{
|
||||
/** @var array<string|int, mixed> */
|
||||
private $rawValue = [];
|
||||
|
||||
public function getRaw(): array
|
||||
{
|
||||
return ['NOT' => $this->getRawValue()];
|
||||
}
|
||||
|
||||
public function getRawKey(): string
|
||||
{
|
||||
return 'NOT';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
public function getRawValue(): array
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $whereClause
|
||||
*/
|
||||
public static function fromRaw(array $whereClause): self
|
||||
{
|
||||
if (count($whereClause) === 1 && array_keys($whereClause)[0] === 0) {
|
||||
$whereClause = $whereClause[0];
|
||||
}
|
||||
|
||||
$obj = new self();
|
||||
|
||||
$obj->rawValue = $whereClause;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function create(WhereItem $item): self
|
||||
{
|
||||
return self::fromRaw($item->getRaw());
|
||||
}
|
||||
}
|
||||
100
application/Espo/ORM/Query/Part/Where/OrGroup.php
Normal file
100
application/Espo/ORM/Query/Part/Where/OrGroup.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?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\ORM\Query\Part\Where;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
/**
|
||||
* OR-group. Immutable.
|
||||
*/
|
||||
class OrGroup implements WhereItem
|
||||
{
|
||||
|
||||
/** @var array<string|int, mixed> */
|
||||
private $rawValue = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function getRaw(): array
|
||||
{
|
||||
return ['OR' => $this->rawValue];
|
||||
}
|
||||
|
||||
public function getRawKey(): string
|
||||
{
|
||||
return 'OR';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
public function getRawValue(): array
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a number of items.
|
||||
*/
|
||||
public function getItemCount(): int
|
||||
{
|
||||
return count($this->rawValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $whereClause
|
||||
*/
|
||||
public static function fromRaw(array $whereClause): self
|
||||
{
|
||||
$obj = new self();
|
||||
|
||||
$obj->rawValue = $whereClause;
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
public static function create(WhereItem ...$itemList): self
|
||||
{
|
||||
$builder = self::createBuilder();
|
||||
|
||||
foreach ($itemList as $item) {
|
||||
$builder->add($item);
|
||||
}
|
||||
|
||||
return $builder->build();
|
||||
}
|
||||
|
||||
public static function createBuilder(): OrGroupBuilder
|
||||
{
|
||||
return new OrGroupBuilder();
|
||||
}
|
||||
}
|
||||
95
application/Espo/ORM/Query/Part/Where/OrGroupBuilder.php
Normal file
95
application/Espo/ORM/Query/Part/Where/OrGroupBuilder.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?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\ORM\Query\Part\Where;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
class OrGroupBuilder
|
||||
{
|
||||
/** @var array<string|int, mixed> */
|
||||
private array $raw = [];
|
||||
|
||||
public function build(): OrGroup
|
||||
{
|
||||
return OrGroup::fromRaw($this->raw);
|
||||
}
|
||||
|
||||
public function add(WhereItem $item): self
|
||||
{
|
||||
$key = $item->getRawKey();
|
||||
$value = $item->getRawValue();
|
||||
|
||||
if ($item instanceof AndGroup) {
|
||||
$this->raw = self::normalizeRaw($this->raw);
|
||||
|
||||
$this->raw[] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (count($this->raw) === 0) {
|
||||
$this->raw[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->raw = self::normalizeRaw($this->raw);
|
||||
|
||||
$this->raw[] = [$key => $value];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge with another OrGroup.
|
||||
*/
|
||||
public function merge(OrGroup $orGroup): self
|
||||
{
|
||||
$this->raw = array_merge(
|
||||
self::normalizeRaw($this->raw),
|
||||
self::normalizeRaw($orGroup->getRawValue())
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string|int, mixed> $raw
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
private static function normalizeRaw(array $raw): array
|
||||
{
|
||||
if (count($raw) === 1 && array_keys($raw)[0] !== 0) {
|
||||
return [$raw];
|
||||
}
|
||||
|
||||
return $raw;
|
||||
}
|
||||
}
|
||||
45
application/Espo/ORM/Query/Part/WhereClause.php
Normal file
45
application/Espo/ORM/Query/Part/WhereClause.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
use Espo\ORM\Query\Part\Where\AndGroup;
|
||||
|
||||
/**
|
||||
* A where-clause. Immutable.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class WhereClause extends AndGroup
|
||||
{
|
||||
public function getRaw(): array
|
||||
{
|
||||
return $this->getRawValue();
|
||||
}
|
||||
}
|
||||
45
application/Espo/ORM/Query/Part/WhereItem.php
Normal file
45
application/Espo/ORM/Query/Part/WhereItem.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?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\ORM\Query\Part;
|
||||
|
||||
/**
|
||||
* Can be used as a where-clause.
|
||||
*/
|
||||
interface WhereItem
|
||||
{
|
||||
/**
|
||||
* @return array<string|int, mixed>
|
||||
*/
|
||||
public function getRaw(): array;
|
||||
|
||||
public function getRawKey(): string;
|
||||
|
||||
public function getRawValue(): mixed;
|
||||
}
|
||||
43
application/Espo/ORM/Query/Query.php
Normal file
43
application/Espo/ORM/Query/Query.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
/**
|
||||
* Query parameters. Instances are immutable. Need to clone with a builder to get a copy for a further modification.
|
||||
*/
|
||||
interface Query
|
||||
{
|
||||
/**
|
||||
* Get parameters in RAW format.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function getRaw(): array;
|
||||
}
|
||||
207
application/Espo/ORM/Query/Select.php
Normal file
207
application/Espo/ORM/Query/Select.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\Selection;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Select parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*
|
||||
* @todo Add validation and normalization.
|
||||
*/
|
||||
class Select implements SelectingQuery
|
||||
{
|
||||
use SelectingTrait;
|
||||
use BaseTrait;
|
||||
|
||||
public const ORDER_ASC = Order::ASC;
|
||||
public const ORDER_DESC = Order::DESC;
|
||||
|
||||
/**
|
||||
* Get an entity type.
|
||||
*/
|
||||
public function getFrom(): ?string
|
||||
{
|
||||
return $this->params['from'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a from-alias
|
||||
*/
|
||||
public function getFromAlias(): ?string
|
||||
{
|
||||
return $this->params['fromAlias'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a from-query.
|
||||
*/
|
||||
public function getFromQuery(): ?SelectingQuery
|
||||
{
|
||||
return $this->params['fromQuery'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an OFFSET.
|
||||
*/
|
||||
public function getOffset(): ?int
|
||||
{
|
||||
return $this->params['offset'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a LIMIT.
|
||||
*/
|
||||
public function getLimit(): ?int
|
||||
{
|
||||
return $this->params['limit'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get USE INDEX (list of indexes).
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getUseIndex(): array
|
||||
{
|
||||
return $this->params['useIndex'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SELECT items.
|
||||
*
|
||||
* @return Selection[]
|
||||
*/
|
||||
public function getSelect(): array
|
||||
{
|
||||
return array_map(
|
||||
function ($item) {
|
||||
if (is_array($item) && count($item)) {
|
||||
return Selection::fromString($item[0])
|
||||
->withAlias($item[1] ?? null);
|
||||
}
|
||||
|
||||
if (is_string($item)) {
|
||||
return Selection::fromString($item);
|
||||
}
|
||||
|
||||
throw new RuntimeException("Bad select item.");
|
||||
},
|
||||
$this->params['select'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether DISTINCT is applied.
|
||||
*/
|
||||
public function isDistinct(): bool
|
||||
{
|
||||
return $this->params['distinct'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a FOR SHARE lock mode is set.
|
||||
*/
|
||||
public function isForShare(): bool
|
||||
{
|
||||
return $this->params['forShare'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a FOR UPDATE lock mode is set.
|
||||
*/
|
||||
public function isForUpdate(): bool
|
||||
{
|
||||
return $this->params['forUpdate'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get GROUP BY items.
|
||||
*
|
||||
* @return Expression[]
|
||||
*/
|
||||
public function getGroup(): array
|
||||
{
|
||||
return array_map(
|
||||
function (string $item) {
|
||||
return Expression::create($item);
|
||||
},
|
||||
$this->params['groupBy'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HAVING clause.
|
||||
*/
|
||||
public function getHaving(): ?WhereClause
|
||||
{
|
||||
$havingClause = $this->params['havingClause'] ?? null;
|
||||
|
||||
if ($havingClause === null || $havingClause === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$having = WhereClause::fromRaw($havingClause);
|
||||
|
||||
if (!$having instanceof WhereClause) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return $having;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function validateRawParams(array $params): void
|
||||
{
|
||||
$this->validateRawParamsSelecting($params);
|
||||
|
||||
if (
|
||||
(
|
||||
!empty($params['joins']) ||
|
||||
!empty($params['leftJoins']) ||
|
||||
!empty($params['whereClause']) ||
|
||||
!empty($params['orderBy'])
|
||||
)
|
||||
&&
|
||||
empty($params['from']) && empty($params['fromQuery'])
|
||||
) {
|
||||
throw new RuntimeException("Select params: Missing 'from'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
335
application/Espo/ORM/Query/SelectBuilder.php
Normal file
335
application/Espo/ORM/Query/SelectBuilder.php
Normal file
@@ -0,0 +1,335 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\Selection;
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
|
||||
class SelectBuilder implements Builder
|
||||
{
|
||||
use SelectingBuilderTrait;
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a SELECT query.
|
||||
*/
|
||||
public function build(): Select
|
||||
{
|
||||
return Select::fromRaw($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query for a subsequent modifying and building.
|
||||
*/
|
||||
public function clone(Select $query): self
|
||||
{
|
||||
$this->cloneInternal($query);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set FROM. For what entity type to build a query.
|
||||
*/
|
||||
public function from(string $entityType, ?string $alias = null): self
|
||||
{
|
||||
if (isset($this->params['from']) && $entityType !== $this->params['from']) {
|
||||
throw new RuntimeException("Method 'from' can be called only once.");
|
||||
}
|
||||
|
||||
if (isset($this->params['fromQuery'])) {
|
||||
throw new RuntimeException("Method 'from' can't be if 'fromQuery' is set.");
|
||||
}
|
||||
|
||||
$this->params['from'] = $entityType;
|
||||
|
||||
if ($alias) {
|
||||
$this->params['fromAlias'] = $alias;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set FROM sub-query.
|
||||
*/
|
||||
public function fromQuery(SelectingQuery $query, string $alias): self
|
||||
{
|
||||
if (isset($this->params['from'])) {
|
||||
throw new RuntimeException("Method 'fromQuery' can be called only once.");
|
||||
}
|
||||
|
||||
if (isset($this->params['fromQuery'])) {
|
||||
throw new RuntimeException("Method 'fromQuery' can't be if 'from' is set.");
|
||||
}
|
||||
|
||||
if ($alias === '') {
|
||||
throw new RuntimeException("Alias can't be empty.");
|
||||
}
|
||||
|
||||
$this->params['fromQuery'] = $query;
|
||||
$this->params['fromAlias'] = $alias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set DISTINCT parameter.
|
||||
*/
|
||||
public function distinct(): self
|
||||
{
|
||||
$this->params['distinct'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply OFFSET and LIMIT.
|
||||
*/
|
||||
public function limit(?int $offset = null, ?int $limit = null): self
|
||||
{
|
||||
$this->params['offset'] = $offset;
|
||||
$this->params['limit'] = $limit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify SELECT. Columns and expressions to be selected. If not called, then
|
||||
* all entity attributes will be selected. Passing an array will reset
|
||||
* previously set items. Passing a SelectExpression|Expression|string will append the item.
|
||||
*
|
||||
* Usage options:
|
||||
* * `select(SelectExpression $expression)`
|
||||
* * `select([$expr1, $expr2, ...])`
|
||||
* * `select(string $expression, string $alias)`
|
||||
*
|
||||
* @param Selection|Selection[]|Expression|Expression[]|string[]|string|array<int, string[]|string> $select
|
||||
* An array of expressions or one expression.
|
||||
* @param string|null $alias An alias. Actual if the first parameter is not an array.
|
||||
*/
|
||||
public function select($select, ?string $alias = null): self
|
||||
{
|
||||
/** @phpstan-var mixed $select */
|
||||
|
||||
if (is_array($select)) {
|
||||
$this->params['select'] = $this->normalizeSelectExpressionArray($select);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($select instanceof Expression) {
|
||||
$select = $select->getValue();
|
||||
} else if ($select instanceof Selection) {
|
||||
$alias = $alias ?? $select->getAlias();
|
||||
$select = $select->getExpression()->getValue();
|
||||
}
|
||||
|
||||
if (is_string($select)) {
|
||||
$this->params['select'] = $this->params['select'] ?? [];
|
||||
|
||||
$this->params['select'][] = $alias ?
|
||||
[$select, $alias] :
|
||||
$select;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify GROUP BY.
|
||||
* Passing an array will reset previously set items.
|
||||
* Passing a string|Expression will append an item.
|
||||
*
|
||||
* Usage options:
|
||||
* * `groupBy(Expression|string $expression)`
|
||||
* * `groupBy([$expr1, $expr2, ...])`
|
||||
*
|
||||
* @param Expression|Expression[]|string|string[] $groupBy
|
||||
*/
|
||||
public function group($groupBy): self
|
||||
{
|
||||
/** @phpstan-var mixed $groupBy */
|
||||
|
||||
if (is_array($groupBy)) {
|
||||
$this->params['groupBy'] = $this->normalizeExpressionItemArray($groupBy);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($groupBy instanceof Expression) {
|
||||
$groupBy = $groupBy->getValue();
|
||||
}
|
||||
|
||||
if (is_string($groupBy)) {
|
||||
$this->params['groupBy'] = $this->params['groupBy'] ?? [];
|
||||
|
||||
$this->params['groupBy'][] = $groupBy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `group` method.
|
||||
* @param Expression|Expression[]|string|string[] $groupBy
|
||||
*/
|
||||
public function groupBy($groupBy): self
|
||||
{
|
||||
return $this->group($groupBy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use index.
|
||||
*/
|
||||
public function useIndex(string $index): self
|
||||
{
|
||||
$this->params['useIndex'] = $this->params['useIndex'] ?? [];
|
||||
|
||||
$this->params['useIndex'][] = $index;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a HAVING clause.
|
||||
*
|
||||
* Usage options:
|
||||
* * `having(WhereItem $clause)`
|
||||
* * `having(array $clause)`
|
||||
* * `having(string $key, string $value)`
|
||||
*
|
||||
* @param WhereItem|array<int|string, mixed>|string $clause A key or where clause.
|
||||
* @param mixed[]|scalar|null $value A value. Omitted if the first argument is not string.
|
||||
*/
|
||||
public function having($clause, $value = null): self
|
||||
{
|
||||
$this->applyWhereClause('havingClause', $clause, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock selected rows in shared mode. To be used within a transaction.
|
||||
*/
|
||||
public function forShare(): self
|
||||
{
|
||||
if (isset($this->params['forUpdate'])) {
|
||||
throw new RuntimeException("Can't use two lock modes together.");
|
||||
}
|
||||
|
||||
$this->params['forShare'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock selected rows. To be used within a transaction.
|
||||
*/
|
||||
public function forUpdate(): self
|
||||
{
|
||||
if (isset($this->params['forShare'])) {
|
||||
throw new RuntimeException("Can't use two lock modes together.");
|
||||
}
|
||||
|
||||
$this->params['forUpdate'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Remove?
|
||||
*/
|
||||
public function withDeleted(): self
|
||||
{
|
||||
$this->params['withDeleted'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Expression|Selection|mixed[]> $itemList
|
||||
* @return array<array{0: string, 1?: string}|string>
|
||||
*/
|
||||
private function normalizeSelectExpressionArray(array $itemList): array
|
||||
{
|
||||
$resultList = [];
|
||||
|
||||
foreach ($itemList as $item) {
|
||||
if ($item instanceof Expression) {
|
||||
$resultList[] = $item->getValue();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item instanceof Selection) {
|
||||
$resultList[] = $item->getAlias() ?
|
||||
[$item->getExpression()->getValue(), $item->getAlias()] :
|
||||
[$item->getExpression()->getValue()];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_array($item) || !count($item) || !$item[0] instanceof Expression) {
|
||||
/** @var array{0:string,1?:string} $item */
|
||||
$resultList[] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newItem = [$item[0]->getValue()];
|
||||
|
||||
if (count($item) > 1) {
|
||||
$newItem[] = $item[1];
|
||||
}
|
||||
|
||||
/** @var array{0: string, 1?: string} $newItem */
|
||||
|
||||
$resultList[] = $newItem;
|
||||
}
|
||||
|
||||
return $resultList;
|
||||
}
|
||||
}
|
||||
429
application/Espo/ORM/Query/SelectingBuilderTrait.php
Normal file
429
application/Espo/ORM/Query/SelectingBuilderTrait.php
Normal file
@@ -0,0 +1,429 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use Espo\ORM\Query\Part\WhereItem;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\Query\Part\Join;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
|
||||
trait SelectingBuilderTrait
|
||||
{
|
||||
use BaseBuilderTrait;
|
||||
|
||||
/**
|
||||
* Add a WHERE clause.
|
||||
*
|
||||
* Usage options:
|
||||
* * `where(WhereItem $clause)`
|
||||
* * `where(array $clause)`
|
||||
* * `where(string $key, string $value)`
|
||||
*
|
||||
* @param WhereItem|array<string|int, mixed>|string $clause A key or where clause.
|
||||
* @param mixed[]|scalar|null $value A value. Omitted if the first argument is not string.
|
||||
*/
|
||||
public function where($clause, $value = null): self
|
||||
{
|
||||
$this->applyWhereClause('whereClause', $clause, $value);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param WhereItem|array<string|int, mixed>|string $clause A key or where clause.
|
||||
* @param mixed[]|scalar|null $value A value. Omitted if the first argument is not string.
|
||||
*/
|
||||
private function applyWhereClause(string $type, $clause, $value): void
|
||||
{
|
||||
if ($clause instanceof WhereItem) {
|
||||
$clause = $clause->getRaw();
|
||||
}
|
||||
|
||||
$this->params[$type] = $this->params[$type] ?? [];
|
||||
|
||||
$original = $this->params[$type];
|
||||
|
||||
if (!is_string($clause) && !is_array($clause)) {
|
||||
throw new InvalidArgumentException("Bad where clause.");
|
||||
}
|
||||
|
||||
if (is_array($clause)) {
|
||||
$new = $clause;
|
||||
}
|
||||
|
||||
if (is_string($clause)) {
|
||||
$new = [$clause => $value];
|
||||
}
|
||||
|
||||
$containsSameKeys = (bool) count(
|
||||
array_intersect(
|
||||
array_keys($new),
|
||||
array_keys($original)
|
||||
)
|
||||
);
|
||||
|
||||
if ($containsSameKeys) {
|
||||
$this->params[$type][] = $new;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->params[$type] = $new + $original;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply ORDER. Passing an array will override previously set items.
|
||||
* Passing non-array will append an item,
|
||||
*
|
||||
* Usage options:
|
||||
* * `order(OrderExpression $expression)
|
||||
* * `order([$expr1, $expr2, ...])
|
||||
* * `order(string $expression, string $direction)
|
||||
*
|
||||
* @param Order|Order[]|Expression|string|array<int, string[]>|string[] $orderBy
|
||||
* An attribute to order by or an array or order items.
|
||||
* Passing an array will reset a previously set order.
|
||||
* @param (Order::ASC|Order::DESC)|bool|null $direction A direction. True for DESC.
|
||||
*/
|
||||
public function order($orderBy, $direction = null): self
|
||||
{
|
||||
if (is_bool($direction)) {
|
||||
$direction = $direction ? Order::DESC : Order::ASC;
|
||||
}
|
||||
|
||||
if (is_array($orderBy)) {
|
||||
$this->params['orderBy'] = $this->normalizeOrderExpressionItemArray(
|
||||
$orderBy,
|
||||
$direction ?? Order::ASC
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (!$orderBy) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
$this->params['orderBy'] = $this->params['orderBy'] ?? [];
|
||||
|
||||
if ($orderBy instanceof Expression) {
|
||||
$orderBy = $orderBy->getValue();
|
||||
$direction = $direction ?? Order::ASC;
|
||||
} else if ($orderBy instanceof Order) {
|
||||
$direction = $direction ?? $orderBy->getDirection();
|
||||
$orderBy = $orderBy->getExpression()->getValue();
|
||||
} else {
|
||||
$direction = $direction ?? Order::ASC;
|
||||
}
|
||||
|
||||
$this->params['orderBy'][] = [$orderBy, $direction];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add JOIN.
|
||||
*
|
||||
* @param Join|string|Select $target A relation name, table or sub-query. A relation name should be in camelCase,
|
||||
* a table in CamelCase.
|
||||
* @param ?string $alias An alias.
|
||||
* @param WhereItem|array<string|int, mixed>|null $conditions Join conditions.
|
||||
*/
|
||||
public function join(
|
||||
$target,
|
||||
?string $alias = null,
|
||||
WhereItem|array|null $conditions = null
|
||||
): self {
|
||||
|
||||
return $this->joinInternal('joins', $target, $alias, $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add LEFT JOIN.
|
||||
*
|
||||
* @param Join|string|Select $target A relation name, table or sub-query. A relation name should be in camelCase,
|
||||
* a table in CamelCase.
|
||||
* @param ?string $alias An alias.
|
||||
* @param WhereItem|array<string|int, mixed>|null $conditions Join conditions.
|
||||
*/
|
||||
public function leftJoin(
|
||||
$target,
|
||||
?string $alias = null,
|
||||
WhereItem|array|null $conditions = null
|
||||
): self {
|
||||
|
||||
return $this->joinInternal('leftJoins', $target, $alias, $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param 'leftJoins'|'joins' $type
|
||||
* @todo Support USE INDEX in Join.
|
||||
* @param Join|string|Select $target $target
|
||||
* @param WhereItem|array<string|int, mixed>|null $conditions
|
||||
*/
|
||||
private function joinInternal(
|
||||
string $type,
|
||||
$target,
|
||||
?string $alias = null,
|
||||
WhereItem|array|null $conditions = null
|
||||
): self {
|
||||
|
||||
$onlyMiddle = false;
|
||||
$isLateral = false;
|
||||
|
||||
/** @var string|Join|array<int, mixed> $target */
|
||||
|
||||
$joinType = null;
|
||||
|
||||
if ($target instanceof Join) {
|
||||
$alias = $alias ?? $target->getAlias();
|
||||
$conditions = $conditions ?? $target->getConditions();
|
||||
$onlyMiddle = $target->isOnlyMiddle();
|
||||
$isLateral = $target->isLateral();
|
||||
$joinType = $target->getType();
|
||||
|
||||
$target = $target->getTarget();
|
||||
}
|
||||
|
||||
if ($type === 'leftJoins') {
|
||||
$joinType = Join\JoinType::left;
|
||||
}
|
||||
|
||||
if ($target instanceof Select && !$alias) {
|
||||
throw new LogicException("Sub-query join can't be used w/o alias.");
|
||||
}
|
||||
|
||||
$noLeftAlias = false;
|
||||
|
||||
if ($conditions instanceof WhereItem) {
|
||||
$conditions = $conditions->getRaw();
|
||||
|
||||
$noLeftAlias = true;
|
||||
}
|
||||
|
||||
$this->params['joins'] ??= [];
|
||||
|
||||
// For bc.
|
||||
// @todo Remove in v10.0.
|
||||
if (is_array($target)) {
|
||||
// @todo Log deprecation.
|
||||
|
||||
$joinList = $target;
|
||||
|
||||
$this->params[$type] ??= [];
|
||||
|
||||
foreach ($joinList as $item) {
|
||||
$this->params[$type][] = $item;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (
|
||||
is_null($alias) &&
|
||||
is_null($conditions) &&
|
||||
is_string($target) &&
|
||||
$this->hasJoinAliasInternal('joins', $target)
|
||||
) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$params = [];
|
||||
|
||||
if ($noLeftAlias) {
|
||||
$params['noLeftAlias'] = true;
|
||||
}
|
||||
|
||||
if ($onlyMiddle) {
|
||||
$params['onlyMiddle'] = true;
|
||||
}
|
||||
|
||||
if ($isLateral) {
|
||||
$params['isLateral'] = true;
|
||||
}
|
||||
|
||||
$params['type'] = $joinType;
|
||||
|
||||
$this->params['joins'][] = [$target, $alias, $conditions, $params];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function hasJoinAliasInternal(string $type, string $alias): bool
|
||||
{
|
||||
$joins = $this->params[$type] ?? [];
|
||||
|
||||
if (in_array($alias, $joins)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($joins as $item) {
|
||||
if (is_array($item) && count($item) > 1) {
|
||||
if ($item[1] === $alias) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
$item[1] === null &&
|
||||
$item[0] === $alias &&
|
||||
lcfirst($item[0]) === $alias
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of v9.2.0. Use `hasJoinAlias`.
|
||||
*/
|
||||
public function hasLeftJoinAlias(string $alias): bool
|
||||
{
|
||||
return $this->hasJoinAlias($alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an alias is in joins.
|
||||
*/
|
||||
public function hasJoinAlias(string $alias): bool
|
||||
{
|
||||
return $this->hasJoinAliasInternal('joins', $alias) ||
|
||||
// For bc.
|
||||
$this->hasJoinAliasInternal('leftJoins', $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Expression|mixed[]> $itemList
|
||||
* @return array<array{0: string, 1?: string}|string>
|
||||
*/
|
||||
private function normalizeExpressionItemArray(array $itemList): array
|
||||
{
|
||||
$resultList = [];
|
||||
|
||||
foreach ($itemList as $item) {
|
||||
if ($item instanceof Expression) {
|
||||
$resultList[] = $item->getValue();
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_array($item) || !count($item) || !$item[0] instanceof Expression) {
|
||||
/** @var array{0:string, 1?:string} $item */
|
||||
$resultList[] = $item;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newItem = [$item[0]->getValue()];
|
||||
|
||||
if (count($item) > 1) {
|
||||
$newItem[] = $item[1];
|
||||
}
|
||||
|
||||
/** @var array{0:string,1?:string} $newItem */
|
||||
|
||||
$resultList[] = $newItem;
|
||||
}
|
||||
|
||||
return $resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Order|mixed[]|string> $itemList
|
||||
* @param string|bool|null $direction
|
||||
* @return array<array{string, string|bool}>
|
||||
*/
|
||||
private function normalizeOrderExpressionItemArray(array $itemList, $direction): array
|
||||
{
|
||||
$resultList = [];
|
||||
|
||||
foreach ($itemList as $item) {
|
||||
if (is_string($item)) {
|
||||
$resultList[] = [$item, $direction];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_int($item)) {
|
||||
$resultList[] = [(string) $item, $direction];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item instanceof Order) {
|
||||
$resultList[] = [
|
||||
$item->getExpression()->getValue(),
|
||||
$item->getDirection()
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item instanceof Expression) {
|
||||
$resultList[] = [
|
||||
$item->getValue(),
|
||||
$direction
|
||||
];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_array($item) || !count($item)) {
|
||||
throw new RuntimeException("Bad order item.");
|
||||
}
|
||||
|
||||
$itemValue = $item[0] instanceof Expression ?
|
||||
$item[0]->getValue() :
|
||||
$item[0];
|
||||
|
||||
if (!is_string($itemValue) && !is_int($itemValue)) {
|
||||
throw new RuntimeException("Bad order item.");
|
||||
}
|
||||
|
||||
$itemDirection = count($item) > 1 ? $item[1] : $direction;
|
||||
|
||||
if (is_bool($itemDirection)) {
|
||||
$itemDirection = $itemDirection ?
|
||||
Order::DESC :
|
||||
Order::ASC;
|
||||
}
|
||||
|
||||
$resultList[] = [$itemValue, $itemDirection];
|
||||
}
|
||||
|
||||
return $resultList;
|
||||
}
|
||||
}
|
||||
33
application/Espo/ORM/Query/SelectingQuery.php
Normal file
33
application/Espo/ORM/Query/SelectingQuery.php
Normal file
@@ -0,0 +1,33 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
interface SelectingQuery extends Query
|
||||
{}
|
||||
146
application/Espo/ORM/Query/SelectingTrait.php
Normal file
146
application/Espo/ORM/Query/SelectingTrait.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\Query\Part\WhereClause;
|
||||
use Espo\ORM\Query\Part\Join;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
trait SelectingTrait
|
||||
{
|
||||
/**
|
||||
* Get ORDER items.
|
||||
*
|
||||
* @return Order[]
|
||||
*/
|
||||
public function getOrder(): array
|
||||
{
|
||||
return array_map(
|
||||
function ($item) {
|
||||
if (is_array($item) && count($item)) {
|
||||
$itemValue = is_int($item[0]) ? (string) $item[0] : $item[0];
|
||||
|
||||
return Order::fromString($itemValue)
|
||||
->withDirection($item[1] ?? Order::ASC);
|
||||
}
|
||||
|
||||
if (is_string($item)) {
|
||||
return Order::fromString($item);
|
||||
}
|
||||
|
||||
throw new RuntimeException("Bad order item.");
|
||||
},
|
||||
$this->params['orderBy'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WHERE clause.
|
||||
*/
|
||||
public function getWhere(): ?WhereClause
|
||||
{
|
||||
$whereClause = $this->params['whereClause'] ?? null;
|
||||
|
||||
if ($whereClause === null || $whereClause === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$where = WhereClause::fromRaw($whereClause);
|
||||
|
||||
if (!$where instanceof WhereClause) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JOIN items.
|
||||
*
|
||||
* @return Join[]
|
||||
*/
|
||||
public function getJoins(): array
|
||||
{
|
||||
return array_map(
|
||||
function ($item) {
|
||||
if (is_string($item)) {
|
||||
$item = [$item];
|
||||
}
|
||||
|
||||
$conditions = isset($item[2]) ?
|
||||
WhereClause::fromRaw($item[2]) : null;
|
||||
|
||||
$params = $item[3] ?? [];
|
||||
|
||||
$type = $params['type'] ?? null;
|
||||
$type ??= Join\JoinType::inner;
|
||||
|
||||
return Join::create($item[0])
|
||||
->withAlias($item[1] ?? null)
|
||||
->withConditions($conditions)
|
||||
->withType($type);
|
||||
},
|
||||
$this->params['joins'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Join[]
|
||||
* @deprecated As of 9.2.0. Use getJoins and check join type.
|
||||
*/
|
||||
public function getLeftJoins(): array
|
||||
{
|
||||
return array_map(
|
||||
function ($item) {
|
||||
if (is_string($item)) {
|
||||
$item = [$item];
|
||||
}
|
||||
|
||||
$conditions = isset($item[2]) ?
|
||||
WhereClause::fromRaw($item[2]) : null;
|
||||
|
||||
return Join::create($item[0])
|
||||
->withAlias($item[1] ?? null)
|
||||
->withConditions($conditions)
|
||||
->withLeft();
|
||||
},
|
||||
$this->params['leftJoins'] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private static function validateRawParamsSelecting(array $params): void
|
||||
{
|
||||
}
|
||||
}
|
||||
52
application/Espo/ORM/Query/Union.php
Normal file
52
application/Espo/ORM/Query/Union.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Union parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Union implements SelectingQuery
|
||||
{
|
||||
use BaseTrait;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function validateRawParams(array $params): void
|
||||
{
|
||||
if (empty($params['queries'])) {
|
||||
throw new RuntimeException("Union params: No query were added.");
|
||||
}
|
||||
}
|
||||
}
|
||||
146
application/Espo/ORM/Query/UnionBuilder.php
Normal file
146
application/Espo/ORM/Query/UnionBuilder.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class UnionBuilder implements Builder
|
||||
{
|
||||
use BaseBuilderTrait;
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a UNION select query.
|
||||
*/
|
||||
public function build(): Union
|
||||
{
|
||||
return Union::fromRaw($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query for a subsequent modifying and building.
|
||||
*/
|
||||
public function clone(Union $query): self
|
||||
{
|
||||
$this->cloneInternal($query);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use UNION ALL.
|
||||
*/
|
||||
public function all(): self
|
||||
{
|
||||
$this->params['all'] = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function query(Select $query): self
|
||||
{
|
||||
$this->params['queries'] = $this->params['queries'] ?? [];
|
||||
$this->params['queries'][] = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply OFFSET and LIMIT.
|
||||
*/
|
||||
public function limit(?int $offset = null, ?int $limit = null): self
|
||||
{
|
||||
$this->params['offset'] = $offset;
|
||||
$this->params['limit'] = $limit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply ORDER.
|
||||
*
|
||||
* @param string|array<array{string, (Order::ASC|Order::DESC)|bool}|array{string}> $orderBy A select alias.
|
||||
* @param (Order::ASC|Order::DESC)|bool $direction A direction. True for DESC.
|
||||
*/
|
||||
public function order($orderBy, string|bool $direction = Order::ASC): self
|
||||
{
|
||||
if (is_bool($direction)) {
|
||||
$direction = $direction ? Order::DESC : Order::ASC;
|
||||
}
|
||||
|
||||
if (!$orderBy) {
|
||||
throw new InvalidArgumentException();
|
||||
}
|
||||
|
||||
if (is_array($orderBy)) {
|
||||
foreach ($orderBy as $item) {
|
||||
/** @var mixed[] $item */
|
||||
|
||||
if (count($item) === 2) {
|
||||
/** @var array{string, bool|(Order::ASC|Order::DESC)} $item */
|
||||
$this->order($item[0], $item[1]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count($item) === 1) {
|
||||
/** @var array{string} $item */
|
||||
$this->order($item[0]);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException("Bad order.");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @var object|scalar $orderBy */
|
||||
|
||||
if (!is_string($orderBy) && !is_int($orderBy)) {
|
||||
throw new InvalidArgumentException("Bad order.");
|
||||
}
|
||||
|
||||
$this->params['orderBy'] = $this->params['orderBy'] ?? [];
|
||||
$this->params['orderBy'][] = [$orderBy, $direction];
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
109
application/Espo/ORM/Query/Update.php
Normal file
109
application/Espo/ORM/Query/Update.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Update parameters.
|
||||
*
|
||||
* Immutable.
|
||||
*/
|
||||
class Update implements Query
|
||||
{
|
||||
use SelectingTrait;
|
||||
use BaseTrait;
|
||||
|
||||
/**
|
||||
* Get an entity type.
|
||||
*/
|
||||
public function getIn(): string
|
||||
{
|
||||
$in = $this->params['from'];
|
||||
|
||||
if ($in === null) {
|
||||
throw new RuntimeException("Missing 'in'.");
|
||||
}
|
||||
|
||||
return $in;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a LIMIT.
|
||||
*/
|
||||
public function getLimit(): ?int
|
||||
{
|
||||
return $this->params['limit'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SET values.
|
||||
*
|
||||
* @return array<string, scalar|Expression|null>
|
||||
*/
|
||||
public function getSet(): array
|
||||
{
|
||||
$set = [];
|
||||
/** @var array<string, ?scalar> $raw */
|
||||
$raw = $this->params['set'];
|
||||
|
||||
foreach ($raw as $key => $value) {
|
||||
if (str_ends_with($key, ':')) {
|
||||
$key = substr($key, 0, -1);
|
||||
$value = Expression::create((string) $value);
|
||||
}
|
||||
|
||||
$set[$key] = $value;
|
||||
}
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
private function validateRawParams(array $params): void
|
||||
{
|
||||
$this->validateRawParamsSelecting($params);
|
||||
|
||||
$from = $params['from'] ?? null;
|
||||
|
||||
if (!$from || !is_string($from)) {
|
||||
throw new RuntimeException("Update params: Missing 'in'.");
|
||||
}
|
||||
|
||||
$set = $params['set'] ?? null;
|
||||
|
||||
if (!$set || !is_array($set)) {
|
||||
throw new RuntimeException("Update params: Bad or missing 'set' parameter.");
|
||||
}
|
||||
}
|
||||
}
|
||||
114
application/Espo/ORM/Query/UpdateBuilder.php
Normal file
114
application/Espo/ORM/Query/UpdateBuilder.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?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\ORM\Query;
|
||||
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use RuntimeException;
|
||||
|
||||
class UpdateBuilder implements Builder
|
||||
{
|
||||
use SelectingBuilderTrait;
|
||||
|
||||
/**
|
||||
* Create an instance.
|
||||
*/
|
||||
public static function create(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a UPDATE query.
|
||||
*/
|
||||
public function build(): Update
|
||||
{
|
||||
return Update::fromRaw($this->params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query for a subsequent modifying and building.
|
||||
*/
|
||||
public function clone(Update $query): self
|
||||
{
|
||||
$this->cloneInternal($query);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* For what entity type to build a query.
|
||||
*/
|
||||
public function in(string $entityType): self
|
||||
{
|
||||
if (isset($this->params['from'])) {
|
||||
throw new RuntimeException("Method 'in' can be called only once.");
|
||||
}
|
||||
|
||||
$this->params['from'] = $entityType;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Values to set. Column => Value map.
|
||||
*
|
||||
* @param array<string, scalar|Expression|null> $set
|
||||
*/
|
||||
public function set(array $set): self
|
||||
{
|
||||
$modified = [];
|
||||
|
||||
foreach ($set as $key => $value) {
|
||||
if (!$value instanceof Expression) {
|
||||
$modified[$key] = $value;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$newKey = rtrim($key, ':') . ':';
|
||||
|
||||
$modified[$newKey] = $value->getValue();
|
||||
}
|
||||
|
||||
$this->params['set'] = $modified;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply LIMIT.
|
||||
*/
|
||||
public function limit(?int $limit = null): self
|
||||
{
|
||||
$this->params['limit'] = $limit;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
140
application/Espo/ORM/QueryBuilder.php
Normal file
140
application/Espo/ORM/QueryBuilder.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?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\ORM;
|
||||
|
||||
use Espo\ORM\Query\Delete;
|
||||
use Espo\ORM\Query\DeleteBuilder;
|
||||
use Espo\ORM\Query\Insert;
|
||||
use Espo\ORM\Query\InsertBuilder;
|
||||
use Espo\ORM\Query\Part\Expression;
|
||||
use Espo\ORM\Query\Part\Selection;
|
||||
use Espo\ORM\Query\Query;
|
||||
use Espo\ORM\Query\Select;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\ORM\Query\Union;
|
||||
use Espo\ORM\Query\UnionBuilder;
|
||||
use Espo\ORM\Query\Update;
|
||||
use Espo\ORM\Query\UpdateBuilder;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Creates query builders for specific query types.
|
||||
*/
|
||||
class QueryBuilder
|
||||
{
|
||||
/**
|
||||
* Specify SELECT. Columns and expressions to be selected. If not called, then
|
||||
* all entity attributes will be selected. Passing an array will reset
|
||||
* previously set items. Passing a SelectExpression|Expression|string will append the item.
|
||||
*
|
||||
* Usage options:
|
||||
* * `select(SelectExpression $expression)`
|
||||
* * `select([$expr1, $expr2, ...])`
|
||||
* * `select(string $expression, string $alias)`
|
||||
*
|
||||
* @param Selection|Selection[]|Expression|string[]|string $select
|
||||
* An array of expressions or one expression.
|
||||
* @param ?string $alias An alias. Actual if the first parameter is not an array.
|
||||
*/
|
||||
public function select($select = null, ?string $alias = null): SelectBuilder
|
||||
{
|
||||
$builder = new SelectBuilder();
|
||||
|
||||
if ($select === null) {
|
||||
return $builder;
|
||||
}
|
||||
|
||||
return $builder->select($select, $alias);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proceed with UPDATE builder.
|
||||
*/
|
||||
public function update(): UpdateBuilder
|
||||
{
|
||||
return new UpdateBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Proceed with DELETE builder.
|
||||
*/
|
||||
public function delete(): DeleteBuilder
|
||||
{
|
||||
return new DeleteBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Proceed with INSERT builder.
|
||||
*/
|
||||
public function insert(): InsertBuilder
|
||||
{
|
||||
return new InsertBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Proceed with UNION builder.
|
||||
*/
|
||||
public function union(): UnionBuilder
|
||||
{
|
||||
return new UnionBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clone an existing query and proceed modifying it.
|
||||
*
|
||||
* @return SelectBuilder|UpdateBuilder|DeleteBuilder|InsertBuilder|UnionBuilder
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public function clone(Query $query): SelectBuilder|UpdateBuilder|DeleteBuilder|InsertBuilder|UnionBuilder
|
||||
{
|
||||
if ($query instanceof Select) {
|
||||
return $this->select()->clone($query);
|
||||
}
|
||||
|
||||
if ($query instanceof Update) {
|
||||
return $this->update()->clone($query);
|
||||
}
|
||||
|
||||
if ($query instanceof Delete) {
|
||||
return $this->delete()->clone($query);
|
||||
}
|
||||
|
||||
if ($query instanceof Insert) {
|
||||
return $this->insert()->clone($query);
|
||||
}
|
||||
|
||||
if ($query instanceof Union) {
|
||||
return $this->union()->clone($query);
|
||||
}
|
||||
|
||||
throw new RuntimeException("Can't clone an unsupported query.");
|
||||
}
|
||||
}
|
||||
3799
application/Espo/ORM/QueryComposer/BaseQueryComposer.php
Normal file
3799
application/Espo/ORM/QueryComposer/BaseQueryComposer.php
Normal file
File diff suppressed because it is too large
Load Diff
165
application/Espo/ORM/QueryComposer/Functions.php
Normal file
165
application/Espo/ORM/QueryComposer/Functions.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?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\ORM\QueryComposer;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class Functions
|
||||
{
|
||||
public const FUNCTION_LIST = [
|
||||
'ROW',
|
||||
'COUNT',
|
||||
'SUM',
|
||||
'AVG',
|
||||
'MAX',
|
||||
'MIN',
|
||||
'DATE',
|
||||
'MONTH',
|
||||
'DAY',
|
||||
'YEAR',
|
||||
'WEEK',
|
||||
'WEEK_0',
|
||||
'WEEK_1',
|
||||
'QUARTER',
|
||||
'DAYOFMONTH',
|
||||
'DAYOFWEEK',
|
||||
'DAYOFWEEK_NUMBER',
|
||||
'MONTH_NUMBER',
|
||||
'DATE_NUMBER',
|
||||
'YEAR_NUMBER',
|
||||
'HOUR_NUMBER',
|
||||
'HOUR',
|
||||
'MINUTE_NUMBER',
|
||||
'MINUTE',
|
||||
'QUARTER_NUMBER',
|
||||
'WEEK_NUMBER',
|
||||
'WEEK_NUMBER_0',
|
||||
'WEEK_NUMBER_1',
|
||||
'LOWER',
|
||||
'UPPER',
|
||||
'TRIM',
|
||||
'REPLACE',
|
||||
'LENGTH',
|
||||
'CHAR_LENGTH',
|
||||
'YEAR_0',
|
||||
'YEAR_1',
|
||||
'YEAR_2',
|
||||
'YEAR_3',
|
||||
'YEAR_4',
|
||||
'YEAR_5',
|
||||
'YEAR_6',
|
||||
'YEAR_7',
|
||||
'YEAR_8',
|
||||
'YEAR_9',
|
||||
'YEAR_10',
|
||||
'YEAR_11',
|
||||
'QUARTER_0',
|
||||
'QUARTER_1',
|
||||
'QUARTER_2',
|
||||
'QUARTER_3',
|
||||
'QUARTER_4',
|
||||
'QUARTER_5',
|
||||
'QUARTER_6',
|
||||
'QUARTER_7',
|
||||
'QUARTER_8',
|
||||
'QUARTER_9',
|
||||
'QUARTER_10',
|
||||
'QUARTER_11',
|
||||
'CONCAT',
|
||||
'LEFT',
|
||||
'TZ',
|
||||
'NOW',
|
||||
'ADD',
|
||||
'SUB',
|
||||
'MUL',
|
||||
'DIV',
|
||||
'MOD',
|
||||
'FLOOR',
|
||||
'CEIL',
|
||||
'ROUND',
|
||||
'GREATEST',
|
||||
'LEAST',
|
||||
'COALESCE',
|
||||
'IF',
|
||||
'LIKE',
|
||||
'NOT_LIKE',
|
||||
'EQUAL',
|
||||
'NOT_EQUAL',
|
||||
'GREATER_THAN',
|
||||
'LESS_THAN',
|
||||
'GREATER_THAN_OR_EQUAL',
|
||||
'LESS_THAN_OR_EQUAL',
|
||||
'IS_NULL',
|
||||
'IS_NOT_NULL',
|
||||
'OR',
|
||||
'AND',
|
||||
'NOT',
|
||||
'IN',
|
||||
'NOT_IN',
|
||||
'IFNULL',
|
||||
'NULLIF',
|
||||
'SWITCH',
|
||||
'MAP',
|
||||
'BINARY',
|
||||
'MD5',
|
||||
'UNIX_TIMESTAMP',
|
||||
'TIMESTAMPDIFF_DAY',
|
||||
'TIMESTAMPDIFF_MONTH',
|
||||
'TIMESTAMPDIFF_YEAR',
|
||||
'TIMESTAMPDIFF_WEEK',
|
||||
'TIMESTAMPDIFF_HOUR',
|
||||
'TIMESTAMPDIFF_MINUTE',
|
||||
'TIMESTAMPDIFF_SECOND',
|
||||
'POSITION_IN_LIST',
|
||||
'MATCH_BOOLEAN',
|
||||
'MATCH_NATURAL_LANGUAGE',
|
||||
'ANY_VALUE',
|
||||
];
|
||||
|
||||
public const COMPARISON_FUNCTION_LIST = [
|
||||
'LIKE',
|
||||
'NOT_LIKE',
|
||||
'EQUAL',
|
||||
'NOT_EQUAL',
|
||||
'GREATER_THAN',
|
||||
'LESS_THAN',
|
||||
'GREATER_THAN_OR_EQUAL',
|
||||
'LESS_THAN_OR_EQUAL',
|
||||
];
|
||||
|
||||
public const MATH_OPERATION_FUNCTION_LIST = [
|
||||
'ADD',
|
||||
'SUB',
|
||||
'MUL',
|
||||
'DIV',
|
||||
'MOD',
|
||||
];
|
||||
}
|
||||
101
application/Espo/ORM/QueryComposer/MysqlQueryComposer.php
Normal file
101
application/Espo/ORM/QueryComposer/MysqlQueryComposer.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?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\ORM\QueryComposer;
|
||||
|
||||
use Espo\ORM\Query\LockTable as LockTableQuery;
|
||||
|
||||
use LogicException;
|
||||
|
||||
class MysqlQueryComposer extends BaseQueryComposer
|
||||
{
|
||||
public function composeLockTable(LockTableQuery $query): string
|
||||
{
|
||||
$params = $query->getRaw();
|
||||
|
||||
$entityType = $this->sanitize($params['table']);
|
||||
|
||||
$table = $this->toDb($entityType);
|
||||
|
||||
$mode = $params['mode'];
|
||||
|
||||
if (empty($table)) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
if (!in_array($mode, [LockTableQuery::MODE_SHARE, LockTableQuery::MODE_EXCLUSIVE])) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
$sql = "LOCK TABLES " . $this->quoteIdentifier($table) . " ";
|
||||
|
||||
$modeMap = [
|
||||
LockTableQuery::MODE_SHARE => 'READ',
|
||||
LockTableQuery::MODE_EXCLUSIVE => 'WRITE',
|
||||
];
|
||||
|
||||
$sql .= $modeMap[$mode];
|
||||
|
||||
if (str_contains($table, '_')) {
|
||||
// MySQL has an issue that aliased tables must be locked with alias.
|
||||
$sql .= ", " .
|
||||
$this->quoteIdentifier($table) . " AS " .
|
||||
$this->quoteIdentifier(lcfirst($entityType)) . " " . $modeMap[$mode];
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
public function composeUnlockTables(): string
|
||||
{
|
||||
return "UNLOCK TABLES";
|
||||
}
|
||||
|
||||
protected function limit(string $sql, ?int $offset = null, ?int $limit = null): string
|
||||
{
|
||||
if (!is_null($offset) && !is_null($limit)) {
|
||||
$offset = intval($offset);
|
||||
$limit = intval($limit);
|
||||
|
||||
$sql .= " LIMIT $offset, $limit";
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
if (!is_null($limit)) {
|
||||
$limit = intval($limit);
|
||||
|
||||
$sql .= " LIMIT $limit";
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
@@ -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\ORM\QueryComposer\Part;
|
||||
|
||||
interface FunctionConverter
|
||||
{
|
||||
public function convert(string ...$argumentList): string;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?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\ORM\QueryComposer\Part;
|
||||
|
||||
interface FunctionConverterFactory
|
||||
{
|
||||
public function create(string $name): FunctionConverter;
|
||||
|
||||
public function isCreatable(string $name): bool;
|
||||
}
|
||||
656
application/Espo/ORM/QueryComposer/PostgresqlQueryComposer.php
Normal file
656
application/Espo/ORM/QueryComposer/PostgresqlQueryComposer.php
Normal file
@@ -0,0 +1,656 @@
|
||||
<?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\ORM\QueryComposer;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Delete as DeleteQuery;
|
||||
use Espo\ORM\Query\DeleteBuilder;
|
||||
use Espo\ORM\Query\Insert as InsertQuery;
|
||||
use Espo\ORM\Query\LockTable as LockTableQuery;
|
||||
|
||||
use Espo\ORM\Query\Part\Condition as Cond;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
use Espo\ORM\Query\Update as UpdateQuery;
|
||||
use Espo\ORM\Query\UpdateBuilder;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
|
||||
class PostgresqlQueryComposer extends BaseQueryComposer
|
||||
{
|
||||
protected string $identifierQuoteCharacter = '"';
|
||||
protected bool $indexHints = false;
|
||||
protected bool $skipForeignIfForUpdate = true;
|
||||
protected int $aliasMaxLength = 128;
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected array $comparisonOperatorMap = [
|
||||
'!=s' => 'NOT IN',
|
||||
'=s' => 'IN',
|
||||
'!=' => '<>',
|
||||
'!*' => 'NOT ILIKE',
|
||||
'*' => 'ILIKE',
|
||||
'>=any' => '>= ANY',
|
||||
'<=any' => '<= ANY',
|
||||
'>any' => '> ANY',
|
||||
'<any' => '< ANY',
|
||||
'!=any' => '<> ANY',
|
||||
'=any' => '= ANY',
|
||||
'>=all' => '>= ALL',
|
||||
'<=all' => '<= ALL',
|
||||
'>all' => '> ALL',
|
||||
'<all' => '< ALL',
|
||||
'!=all' => '<> ALL',
|
||||
'=all' => '= ALL',
|
||||
];
|
||||
|
||||
/** @var array<string, string> */
|
||||
protected array $comparisonFunctionOperatorMap = [
|
||||
'LIKE' => 'ILIKE',
|
||||
'NOT_LIKE' => 'NOT ILIKE',
|
||||
'EQUAL' => '=',
|
||||
'NOT_EQUAL' => '<>',
|
||||
'GREATER_THAN' => '>',
|
||||
'LESS_THAN' => '<',
|
||||
'GREATER_THAN_OR_EQUAL' => '>=',
|
||||
'LESS_THAN_OR_EQUAL' => '<=',
|
||||
'IS_NULL' => 'IS NULL',
|
||||
'IS_NOT_NULL' => 'IS NOT NULL',
|
||||
'IN' => 'IN',
|
||||
'NOT_IN' => 'NOT IN',
|
||||
];
|
||||
|
||||
protected function quoteColumn(string $column): string
|
||||
{
|
||||
$list = explode('.', $column);
|
||||
$list = array_map(fn ($item) => '"' . $item . '"', $list);
|
||||
|
||||
return implode('.', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Make protected.
|
||||
*
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function quote($value): string
|
||||
{
|
||||
if (is_null($value)) {
|
||||
return 'NULL';
|
||||
}
|
||||
|
||||
if (is_bool($value)) {
|
||||
return $value ? 'true' : 'false';
|
||||
}
|
||||
|
||||
if (is_int($value)) {
|
||||
return strval($value);
|
||||
}
|
||||
|
||||
if (is_float($value)) {
|
||||
return strval($value);
|
||||
}
|
||||
|
||||
return $this->pdo->quote($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $argumentPartList
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
protected function getFunctionPart(
|
||||
string $function,
|
||||
string $part,
|
||||
array $params,
|
||||
string $entityType,
|
||||
bool $distinct,
|
||||
array $argumentPartList = []
|
||||
): string {
|
||||
|
||||
if (in_array($function, ['MATCH_BOOLEAN', 'MATCH_NATURAL_LANGUAGE'])) {
|
||||
if (count($argumentPartList) < 2) {
|
||||
throw new RuntimeException("Not enough arguments for MATCH function.");
|
||||
}
|
||||
|
||||
$queryPart = end($argumentPartList);
|
||||
$columnsPart = implode(
|
||||
" || ' ' || ",
|
||||
array_map(
|
||||
fn ($item) => "COALESCE($item, '')",
|
||||
array_slice($argumentPartList, 0, -1)
|
||||
)
|
||||
);
|
||||
|
||||
return "TS_RANK_CD(TO_TSVECTOR($columnsPart), PLAINTO_TSQUERY($queryPart))";
|
||||
}
|
||||
|
||||
if ($function === 'IF') {
|
||||
if (count($argumentPartList) < 3) {
|
||||
throw new RuntimeException("Not enough arguments for IF function.");
|
||||
}
|
||||
|
||||
$conditionPart = $argumentPartList[0];
|
||||
$thenPart = $argumentPartList[1];
|
||||
$elsePart = $argumentPartList[2];
|
||||
|
||||
return "CASE WHEN $conditionPart THEN $thenPart ELSE $elsePart END";
|
||||
}
|
||||
|
||||
if ($function === 'ROUND') {
|
||||
if (count($argumentPartList) === 2 && $argumentPartList[1] === '0') {
|
||||
$argumentPartList = array_slice($argumentPartList, 0, -1);
|
||||
|
||||
return "ROUND($argumentPartList[0])";
|
||||
}
|
||||
}
|
||||
|
||||
if ($function === 'UNIX_TIMESTAMP') {
|
||||
$arg = $argumentPartList[0] ?? 'NOW()';
|
||||
|
||||
return "FLOOR(EXTRACT(EPOCH FROM $arg))";
|
||||
}
|
||||
|
||||
if ($function === 'BINARY') {
|
||||
// Not supported.
|
||||
return $argumentPartList[0] ?? '0';
|
||||
}
|
||||
|
||||
if ($function === 'TZ') {
|
||||
if (count($argumentPartList) < 2) {
|
||||
throw new RuntimeException("Not enough arguments for function TZ.");
|
||||
}
|
||||
|
||||
$offsetHoursString = $argumentPartList[1];
|
||||
if (str_starts_with($offsetHoursString, '\'') && str_ends_with($offsetHoursString, '\'')) {
|
||||
$offsetHoursString = substr($offsetHoursString, 1, -1);
|
||||
}
|
||||
|
||||
if (str_contains($offsetHoursString, '.')) {
|
||||
$minutes = (int) (floatval($offsetHoursString) * 60);
|
||||
$minutesString = (string) $minutes;
|
||||
|
||||
return "$argumentPartList[0] + INTERVAL '$minutesString MINUTE'";
|
||||
}
|
||||
|
||||
return "$argumentPartList[0] + INTERVAL '$offsetHoursString HOUR'";
|
||||
}
|
||||
|
||||
if ($function === 'POSITION_IN_LIST') {
|
||||
if (count($argumentPartList) <= 1) {
|
||||
return $this->quote(1);
|
||||
}
|
||||
|
||||
$field = $argumentPartList[0];
|
||||
|
||||
$pairs = array_map(
|
||||
fn($i) => [$i, $argumentPartList[$i]],
|
||||
array_keys($argumentPartList)
|
||||
);
|
||||
|
||||
$whenParts = array_map(function ($item) use ($field) {
|
||||
$resolution = intval($item[0]);
|
||||
$value = $item[1];
|
||||
|
||||
return " WHEN $field = $value THEN $resolution";
|
||||
}, array_slice($pairs, 1));
|
||||
|
||||
return "CASE" . implode('', $whenParts) . " ELSE 0 END";
|
||||
}
|
||||
|
||||
if ($function === 'IFNULL') {
|
||||
$function = 'COALESCE';
|
||||
}
|
||||
|
||||
if (str_starts_with($function, 'YEAR_') && $function !== 'YEAR_NUMBER') {
|
||||
$fiscalShift = substr($function, 5);
|
||||
|
||||
if (is_numeric($fiscalShift)) {
|
||||
$fiscalShift = (int) $fiscalShift;
|
||||
$fiscalFirstMonth = $fiscalShift + 1;
|
||||
|
||||
return
|
||||
"CASE WHEN EXTRACT(MONTH FROM $part) >= $fiscalFirstMonth THEN ".
|
||||
"EXTRACT(YEAR FROM $part) ".
|
||||
"ELSE EXTRACT(YEAR FROM $part) - 1 END";
|
||||
}
|
||||
}
|
||||
|
||||
if (str_starts_with($function, 'QUARTER_') && $function !== 'QUARTER_NUMBER') {
|
||||
$fiscalShift = substr($function, 8);
|
||||
|
||||
if (is_numeric($fiscalShift)) {
|
||||
$fiscalShift = (int) $fiscalShift;
|
||||
$fiscalFirstMonth = $fiscalShift + 1;
|
||||
$fiscalDistractedMonth = $fiscalFirstMonth < 4 ?
|
||||
12 - $fiscalFirstMonth :
|
||||
12 - $fiscalFirstMonth + 1;
|
||||
|
||||
return
|
||||
"CASE WHEN EXTRACT(MONTH FROM $part) >= $fiscalFirstMonth " .
|
||||
"THEN " .
|
||||
"CONCAT(" .
|
||||
"EXTRACT(YEAR FROM $part), '_', " .
|
||||
"FLOOR((EXTRACT(MONTH FROM $part) - $fiscalFirstMonth) / 3) + 1" .
|
||||
") " .
|
||||
"ELSE " .
|
||||
"CONCAT(" .
|
||||
"EXTRACT(YEAR FROM $part) - 1, '_', " .
|
||||
"CEIL((EXTRACT(MONTH FROM $part) + $fiscalDistractedMonth) / 3)" .
|
||||
") " .
|
||||
"END";
|
||||
}
|
||||
}
|
||||
|
||||
switch ($function) {
|
||||
case 'MONTH':
|
||||
return "TO_CHAR($part, 'YYYY-MM')";
|
||||
|
||||
case 'DAY':
|
||||
return "TO_CHAR($part, 'YYYY-MM-DD')";
|
||||
|
||||
case 'WEEK':
|
||||
case 'WEEK_0':
|
||||
case 'WEEK_1':
|
||||
if (str_starts_with($part, "'")) {
|
||||
$part = "DATE " . $part;
|
||||
}
|
||||
|
||||
return "CONCAT(TO_CHAR($part, 'YYYY'), '/', TRIM(LEADING '0' FROM TO_CHAR($part, 'IW')))";
|
||||
|
||||
case 'QUARTER':
|
||||
return "CONCAT(TO_CHAR($part, 'YYYY'), '_', TO_CHAR($part, 'Q'))";
|
||||
|
||||
case 'WEEK_NUMBER_0':
|
||||
case 'WEEK_NUMBER':
|
||||
case 'WEEK_NUMBER_1':
|
||||
// Monday week-start not implemented.
|
||||
return "TO_CHAR($part, 'IW')::INTEGER";
|
||||
|
||||
case 'HOUR_NUMBER':
|
||||
case 'HOUR':
|
||||
return "EXTRACT(HOUR FROM $part)";
|
||||
|
||||
case 'MINUTE_NUMBER':
|
||||
case 'MINUTE':
|
||||
return "EXTRACT(MINUTE FROM $part)";
|
||||
|
||||
case 'SECOND_NUMBER':
|
||||
case 'SECOND':
|
||||
return "FLOOR(EXTRACT(SECOND FROM $part))";
|
||||
|
||||
case 'DATE_NUMBER':
|
||||
case 'DAYOFMONTH':
|
||||
return "EXTRACT(DAY FROM $part)";
|
||||
|
||||
case 'DAYOFWEEK_NUMBER':
|
||||
case 'DAYOFWEEK':
|
||||
return "EXTRACT(DOW FROM $part)";
|
||||
|
||||
case 'MONTH_NUMBER':
|
||||
return "EXTRACT(MONTH FROM $part)";
|
||||
|
||||
case 'YEAR_NUMBER':
|
||||
case 'YEAR':
|
||||
return "EXTRACT(YEAR FROM $part)";
|
||||
|
||||
case 'QUARTER_NUMBER':
|
||||
return "EXTRACT(QUARTER FROM $part)";
|
||||
}
|
||||
|
||||
if (str_starts_with($function, 'TIMESTAMPDIFF_')) {
|
||||
$from = $argumentPartList[0] ?? $this->quote(0);
|
||||
$to = $argumentPartList[1] ?? $this->quote(0);
|
||||
|
||||
switch ($function) {
|
||||
case 'TIMESTAMPDIFF_YEAR':
|
||||
return "EXTRACT(YEAR FROM $to - $from)";
|
||||
|
||||
case 'TIMESTAMPDIFF_MONTH':
|
||||
return "EXTRACT(MONTH FROM $to - $from)";
|
||||
|
||||
case 'TIMESTAMPDIFF_WEEK':
|
||||
return "FLOOR(EXTRACT(DAY FROM $to - $from) / 7)";
|
||||
|
||||
case 'TIMESTAMPDIFF_DAY':
|
||||
return "EXTRACT(DAY FROM ($to) - $from)";
|
||||
|
||||
case 'TIMESTAMPDIFF_HOUR':
|
||||
return "EXTRACT(HOUR FROM $to - $from)";
|
||||
|
||||
case 'TIMESTAMPDIFF_MINUTE':
|
||||
return "EXTRACT(MINUTE FROM $to - $from)";
|
||||
|
||||
case 'TIMESTAMPDIFF_SECOND':
|
||||
return "FLOOR(EXTRACT(SECOND FROM $to - $from))";
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getFunctionPart(
|
||||
$function,
|
||||
$part,
|
||||
$params,
|
||||
$entityType,
|
||||
$distinct,
|
||||
$argumentPartList
|
||||
);
|
||||
}
|
||||
|
||||
public function composeDelete(DeleteQuery $query): string
|
||||
{
|
||||
if (
|
||||
$query->getJoins() !== [] ||
|
||||
$query->getLeftJoins() !== [] ||
|
||||
$query->getLimit() !== null ||
|
||||
$query->getOrder() !== []
|
||||
) {
|
||||
$subQueryBuilder = SelectBuilder::create()
|
||||
->select(Attribute::ID)
|
||||
->from($query->getFrom())
|
||||
->order($query->getOrder());
|
||||
|
||||
foreach ($query->getJoins() as $join) {
|
||||
$subQueryBuilder->join($join);
|
||||
}
|
||||
|
||||
foreach ($query->getLeftJoins() as $join) {
|
||||
$subQueryBuilder->leftJoin($join);
|
||||
}
|
||||
|
||||
if ($query->getWhere()) {
|
||||
$subQueryBuilder->where($query->getWhere());
|
||||
}
|
||||
|
||||
if ($query->getLimit() !== null) {
|
||||
$subQueryBuilder->limit(null, $query->getLimit());
|
||||
}
|
||||
|
||||
$builder = DeleteBuilder::create()
|
||||
->from($query->getFrom(), $query->getFromAlias())
|
||||
->where(
|
||||
Cond::in(
|
||||
Cond::column(Attribute::ID),
|
||||
$subQueryBuilder->build()
|
||||
)
|
||||
);
|
||||
|
||||
$query = $builder->build();
|
||||
}
|
||||
|
||||
return parent::composeDelete($query);
|
||||
}
|
||||
|
||||
public function composeUpdate(UpdateQuery $query): string
|
||||
{
|
||||
if (
|
||||
$query->getJoins() !== [] ||
|
||||
$query->getLeftJoins() !== [] ||
|
||||
$query->getLimit() !== null ||
|
||||
$query->getOrder() !== []
|
||||
) {
|
||||
$subQueryBuilder = SelectBuilder::create()
|
||||
->select(Attribute::ID)
|
||||
->from($query->getIn())
|
||||
->order($query->getOrder())
|
||||
->forUpdate();
|
||||
|
||||
foreach ($query->getJoins() as $join) {
|
||||
$subQueryBuilder->join($join);
|
||||
}
|
||||
|
||||
foreach ($query->getLeftJoins() as $join) {
|
||||
$subQueryBuilder->leftJoin($join);
|
||||
}
|
||||
|
||||
if ($query->getWhere()) {
|
||||
$subQueryBuilder->where($query->getWhere());
|
||||
}
|
||||
|
||||
if ($query->getLimit() !== null) {
|
||||
$subQueryBuilder->limit(null, $query->getLimit());
|
||||
}
|
||||
|
||||
$builder = UpdateBuilder::create()
|
||||
->in($query->getIn())
|
||||
->set($query->getSet())
|
||||
->where(
|
||||
Cond::in(
|
||||
Cond::column(Attribute::ID),
|
||||
$subQueryBuilder->build()
|
||||
)
|
||||
);
|
||||
|
||||
$query = $builder->build();
|
||||
}
|
||||
|
||||
return parent::composeUpdate($query);
|
||||
}
|
||||
|
||||
public function composeInsert(InsertQuery $query): string
|
||||
{
|
||||
$params = $query->getRaw();
|
||||
$params = $this->normalizeInsertParams($params);
|
||||
|
||||
$entityType = $params['into'];
|
||||
$columns = $params['columns'];
|
||||
$updateSet = $params['updateSet'];
|
||||
|
||||
$columnsPart = $this->getInsertColumnsPart($columns);
|
||||
$valuesPart = $this->getInsertValuesPart($entityType, $params);
|
||||
$updatePart = $updateSet ? $this->getInsertUpdatePart($updateSet) : null;
|
||||
|
||||
$table = $this->toDb($entityType);
|
||||
|
||||
$sql = "INSERT INTO " . $this->quoteIdentifier($table) . " ($columnsPart) $valuesPart";
|
||||
|
||||
if ($updatePart) {
|
||||
$updateColumnsPart = implode(', ',
|
||||
array_map(fn ($item) => $this->quoteIdentifier($this->toDb($this->sanitize($item))),
|
||||
$this->getEntityUniqueColumns($entityType)
|
||||
)
|
||||
);
|
||||
|
||||
$sql .= " ON CONFLICT($updateColumnsPart) DO UPDATE SET " . $updatePart;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
private function getEntityUniqueColumns(string $entityType): array
|
||||
{
|
||||
$indexes = $this->metadata
|
||||
->getDefs()
|
||||
->getEntity($entityType)
|
||||
->getIndexList();
|
||||
|
||||
foreach ($indexes as $index) {
|
||||
if ($index->isUnique()) {
|
||||
return $index->getColumnList();
|
||||
}
|
||||
}
|
||||
|
||||
return [Attribute::ID];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $values
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
protected function getSetPart(Entity $entity, array $values, array $params): string
|
||||
{
|
||||
if (!count($values)) {
|
||||
throw new RuntimeException("ORM Query: No SET values for update query.");
|
||||
}
|
||||
|
||||
$list = [];
|
||||
|
||||
foreach ($values as $attribute => $value) {
|
||||
$isNotValue = false;
|
||||
|
||||
if (str_ends_with($attribute, ':')) {
|
||||
$attribute = substr($attribute, 0, -1);
|
||||
$isNotValue = true;
|
||||
}
|
||||
|
||||
if (strpos($attribute, '.') > 0) {
|
||||
[$alias, $attribute] = explode('.', $attribute);
|
||||
|
||||
$alias = $this->sanitize($alias);
|
||||
$column = $this->toDb($this->sanitize($attribute));
|
||||
|
||||
$left = $this->quoteColumn("{$alias}.{$column}");
|
||||
} else {
|
||||
$column = $this->toDb($this->sanitize($attribute));
|
||||
|
||||
$left = $this->quoteColumn("{$column}"); // Diff.
|
||||
}
|
||||
|
||||
$right = $isNotValue ?
|
||||
$this->convertComplexExpression($entity, $value, false, $params) :
|
||||
$this->quote($value);
|
||||
|
||||
$list[] = $left . " = " . $right;
|
||||
}
|
||||
|
||||
return implode(', ', $list);
|
||||
}
|
||||
|
||||
public function composeRollbackToSavepoint(string $savepointName): string
|
||||
{
|
||||
return 'ROLLBACK TRANSACTION TO SAVEPOINT ' . $this->sanitize($savepointName);
|
||||
}
|
||||
|
||||
public function composeLockTable(LockTableQuery $query): string
|
||||
{
|
||||
$params = $query->getRaw();
|
||||
|
||||
$table = $this->toDb($this->sanitize($params['table']));
|
||||
|
||||
$mode = $params['mode'];
|
||||
|
||||
if (empty($table)) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
if (!in_array($mode, [LockTableQuery::MODE_SHARE, LockTableQuery::MODE_EXCLUSIVE])) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
$sql = "LOCK TABLE " . $this->quoteIdentifier($table) . " IN ";
|
||||
|
||||
$modeMap = [
|
||||
LockTableQuery::MODE_SHARE => 'SHARE',
|
||||
LockTableQuery::MODE_EXCLUSIVE => 'EXCLUSIVE',
|
||||
];
|
||||
|
||||
$sql .= $modeMap[$mode] . " MODE";
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
protected function limit(string $sql, ?int $offset = null, ?int $limit = null): string
|
||||
{
|
||||
if (!is_null($offset) && !is_null($limit)) {
|
||||
$offset = intval($offset);
|
||||
$limit = intval($limit);
|
||||
|
||||
$sql .= " LIMIT $limit OFFSET $offset";
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
if (!is_null($limit)) {
|
||||
$limit = intval($limit);
|
||||
|
||||
$sql .= " LIMIT $limit";
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $params
|
||||
*/
|
||||
protected function getSelectTailPart(array $params): ?string
|
||||
{
|
||||
$forShare = $params['forShare'] ?? null;
|
||||
$forUpdate = $params['forUpdate'] ?? null;
|
||||
|
||||
if ($forShare) {
|
||||
return "FOR SHARE";
|
||||
}
|
||||
|
||||
if ($forUpdate) {
|
||||
return "FOR UPDATE";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function composeDeleteQuery(
|
||||
string $table,
|
||||
?string $alias,
|
||||
string $where,
|
||||
?string $joins,
|
||||
?string $order,
|
||||
?int $limit
|
||||
): string {
|
||||
|
||||
$sql = "DELETE ";
|
||||
|
||||
$sql .= "FROM " . $this->quoteIdentifier($table);
|
||||
|
||||
if ($alias) {
|
||||
$sql .= " AS " . $this->quoteIdentifier($alias);
|
||||
}
|
||||
|
||||
if ($joins) {
|
||||
$sql .= " $joins";
|
||||
}
|
||||
|
||||
if ($where) {
|
||||
$sql .= " WHERE $where";
|
||||
}
|
||||
|
||||
if ($order) {
|
||||
$sql .= " ORDER BY $order";
|
||||
}
|
||||
|
||||
if ($limit !== null) {
|
||||
$sql = $this->limit($sql, null, $limit);
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
}
|
||||
58
application/Espo/ORM/QueryComposer/QueryComposer.php
Normal file
58
application/Espo/ORM/QueryComposer/QueryComposer.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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\ORM\QueryComposer;
|
||||
|
||||
use Espo\ORM\Query\Select as SelectQuery;
|
||||
use Espo\ORM\Query\Update as UpdateQuery;
|
||||
use Espo\ORM\Query\Insert as InsertQuery;
|
||||
use Espo\ORM\Query\Delete as DeleteQuery;
|
||||
use Espo\ORM\Query\Union as UnionQuery;
|
||||
use Espo\ORM\Query\LockTable as LockTableQuery;
|
||||
|
||||
interface QueryComposer
|
||||
{
|
||||
public function composeSelect(SelectQuery $query): string;
|
||||
|
||||
public function composeUpdate(UpdateQuery $query): string;
|
||||
|
||||
public function composeDelete(DeleteQuery $query): string;
|
||||
|
||||
public function composeInsert(InsertQuery $query): string;
|
||||
|
||||
public function composeUnion(UnionQuery $query): string;
|
||||
|
||||
public function composeLockTable(LockTableQuery $query): string;
|
||||
|
||||
public function composeCreateSavepoint(string $savepointName): string;
|
||||
|
||||
public function composeReleaseSavepoint(string $savepointName): string;
|
||||
|
||||
public function composeRollbackToSavepoint(string $savepointName): string;
|
||||
}
|
||||
35
application/Espo/ORM/QueryComposer/QueryComposerFactory.php
Normal file
35
application/Espo/ORM/QueryComposer/QueryComposerFactory.php
Normal 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\ORM\QueryComposer;
|
||||
|
||||
interface QueryComposerFactory
|
||||
{
|
||||
public function create(string $platform): QueryComposer;
|
||||
}
|
||||
127
application/Espo/ORM/QueryComposer/QueryComposerWrapper.php
Normal file
127
application/Espo/ORM/QueryComposer/QueryComposerWrapper.php
Normal file
@@ -0,0 +1,127 @@
|
||||
<?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\ORM\QueryComposer;
|
||||
|
||||
use Espo\ORM\Query\Query as Query;
|
||||
use Espo\ORM\Query\Select as SelectQuery;
|
||||
use Espo\ORM\Query\Update as UpdateQuery;
|
||||
use Espo\ORM\Query\Insert as InsertQuery;
|
||||
use Espo\ORM\Query\Delete as DeleteQuery;
|
||||
use Espo\ORM\Query\Union as UnionQuery;
|
||||
use Espo\ORM\Query\LockTable as LockTableQuery;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class QueryComposerWrapper implements QueryComposer
|
||||
{
|
||||
private QueryComposer $queryComposer;
|
||||
|
||||
public function __construct(QueryComposer $queryComposer)
|
||||
{
|
||||
$this->queryComposer = $queryComposer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose an SQL query.
|
||||
*/
|
||||
public function compose(Query $query): string
|
||||
{
|
||||
if ($query instanceof SelectQuery) {
|
||||
return $this->composeSelect($query);
|
||||
}
|
||||
|
||||
if ($query instanceof UpdateQuery) {
|
||||
return $this->composeUpdate($query);
|
||||
}
|
||||
|
||||
if ($query instanceof InsertQuery) {
|
||||
return $this->composeInsert($query);
|
||||
}
|
||||
|
||||
if ($query instanceof DeleteQuery) {
|
||||
return $this->composeDelete($query);
|
||||
}
|
||||
|
||||
if ($query instanceof UnionQuery) {
|
||||
return $this->composeUnion($query);
|
||||
}
|
||||
|
||||
if ($query instanceof LockTableQuery) {
|
||||
return $this->composeLockTable($query);
|
||||
}
|
||||
|
||||
throw new RuntimeException("ORM Query: Unknown query type passed.");
|
||||
}
|
||||
|
||||
public function composeSelect(SelectQuery $query): string
|
||||
{
|
||||
return $this->queryComposer->composeSelect($query);
|
||||
}
|
||||
|
||||
public function composeUpdate(UpdateQuery $query): string
|
||||
{
|
||||
return $this->queryComposer->composeUpdate($query);
|
||||
}
|
||||
|
||||
public function composeDelete(DeleteQuery $query): string
|
||||
{
|
||||
return $this->queryComposer->composeDelete($query);
|
||||
}
|
||||
|
||||
public function composeInsert(InsertQuery $query): string
|
||||
{
|
||||
return $this->queryComposer->composeInsert($query);
|
||||
}
|
||||
|
||||
public function composeUnion(UnionQuery $query): string
|
||||
{
|
||||
return $this->queryComposer->composeUnion($query);
|
||||
}
|
||||
|
||||
public function composeLockTable(LockTableQuery $query): string
|
||||
{
|
||||
return $this->queryComposer->composeLockTable($query);
|
||||
}
|
||||
|
||||
public function composeCreateSavepoint(string $savepointName): string
|
||||
{
|
||||
return $this->queryComposer->composeCreateSavepoint($savepointName);
|
||||
}
|
||||
|
||||
public function composeReleaseSavepoint(string $savepointName): string
|
||||
{
|
||||
return $this->queryComposer->composeReleaseSavepoint($savepointName);
|
||||
}
|
||||
|
||||
public function composeRollbackToSavepoint(string $savepointName): string
|
||||
{
|
||||
return $this->queryComposer->composeRollbackToSavepoint($savepointName);
|
||||
}
|
||||
}
|
||||
199
application/Espo/ORM/QueryComposer/Util.php
Normal file
199
application/Espo/ORM/QueryComposer/Util.php
Normal file
@@ -0,0 +1,199 @@
|
||||
<?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\ORM\QueryComposer;
|
||||
|
||||
class Util
|
||||
{
|
||||
public static function isComplexExpression(string $string): bool
|
||||
{
|
||||
if (
|
||||
self::isArgumentString($string) ||
|
||||
self::isArgumentNumeric($string) ||
|
||||
self::isArgumentBoolOrNull($string)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($string, '.')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_contains($string, ':')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_starts_with($string, '#')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function isArgumentString(string $argument): bool
|
||||
{
|
||||
return
|
||||
str_starts_with($argument, '\'') && str_ends_with($argument, '\'')
|
||||
||
|
||||
str_starts_with($argument, '"') && str_ends_with($argument, '"');
|
||||
}
|
||||
|
||||
public static function isArgumentNumeric(string $argument): bool
|
||||
{
|
||||
return is_numeric($argument);
|
||||
}
|
||||
|
||||
public static function isArgumentBoolOrNull(string $argument): bool
|
||||
{
|
||||
return in_array(strtoupper($argument), ['NULL', 'TRUE', 'FALSE']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $expression
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAllAttributesFromComplexExpression(string $expression): array
|
||||
{
|
||||
return self::getAllAttributesFromComplexExpressionImplementation($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[]|null $list
|
||||
* @return string[]
|
||||
*/
|
||||
private static function getAllAttributesFromComplexExpressionImplementation(
|
||||
string $expression,
|
||||
?array &$list = null
|
||||
): array {
|
||||
|
||||
if (!$list) {
|
||||
$list = [];
|
||||
}
|
||||
|
||||
if (!strpos($expression, ':')) {
|
||||
if (
|
||||
!self::isArgumentString($expression) &&
|
||||
!self::isArgumentNumeric($expression) &&
|
||||
!self::isArgumentBoolOrNull($expression) &&
|
||||
!str_contains($expression, '#')
|
||||
) {
|
||||
$list[] = $expression;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
$delimiterPosition = strpos($expression, ':');
|
||||
$arguments = substr($expression, $delimiterPosition + 1);
|
||||
|
||||
if (str_starts_with($arguments, '(') && str_ends_with($arguments, ')')) {
|
||||
$arguments = substr($arguments, 1, -1);
|
||||
}
|
||||
|
||||
$argumentList = self::parseArgumentListFromFunctionContent($arguments);
|
||||
|
||||
foreach ($argumentList as $argument) {
|
||||
self::getAllAttributesFromComplexExpressionImplementation($argument, $list);
|
||||
}
|
||||
|
||||
return $list ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
static public function parseArgumentListFromFunctionContent(string $functionContent): array
|
||||
{
|
||||
$functionContent = trim($functionContent);
|
||||
|
||||
$isString = false;
|
||||
$isSingleQuote = false;
|
||||
|
||||
if ($functionContent === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
$commaIndexList = [];
|
||||
$braceCounter = 0;
|
||||
|
||||
for ($i = 0; $i < strlen($functionContent); $i++) {
|
||||
if ($functionContent[$i] === "'" && ($i === 0 || $functionContent[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isSingleQuote = true;
|
||||
} else {
|
||||
if ($isSingleQuote) {
|
||||
$isString = false;
|
||||
}
|
||||
}
|
||||
} else if ($functionContent[$i] === "\"" && ($i === 0 || $functionContent[$i - 1] !== "\\")) {
|
||||
if (!$isString) {
|
||||
$isString = true;
|
||||
$isSingleQuote = false;
|
||||
} else {
|
||||
if (!$isSingleQuote) {
|
||||
$isString = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$isString) {
|
||||
if ($functionContent[$i] === '(') {
|
||||
$braceCounter++;
|
||||
} else if ($functionContent[$i] === ')') {
|
||||
$braceCounter--;
|
||||
}
|
||||
}
|
||||
|
||||
if ($braceCounter === 0 && !$isString && $functionContent[$i] === ',') {
|
||||
$commaIndexList[] = $i;
|
||||
}
|
||||
}
|
||||
|
||||
$commaIndexList[] = strlen($functionContent);
|
||||
|
||||
$argumentList = [];
|
||||
|
||||
for ($i = 0; $i < count($commaIndexList); $i++) {
|
||||
if ($i > 0) {
|
||||
$previousCommaIndex = $commaIndexList[$i - 1] + 1;
|
||||
} else {
|
||||
$previousCommaIndex = 0;
|
||||
}
|
||||
|
||||
$argument = trim(
|
||||
substr($functionContent, $previousCommaIndex, $commaIndexList[$i] - $previousCommaIndex)
|
||||
);
|
||||
|
||||
$argumentList[] = $argument;
|
||||
}
|
||||
|
||||
return $argumentList;
|
||||
}
|
||||
}
|
||||
104
application/Espo/ORM/Relation/EmptyRelations.php
Normal file
104
application/Espo/ORM/Relation/EmptyRelations.php
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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\ORM\Relation;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityCollection;
|
||||
use LogicException;
|
||||
use RuntimeException as RuntimeExceptionAlias;
|
||||
|
||||
class EmptyRelations implements Relations
|
||||
{
|
||||
/** @var array<string, Entity|null> */
|
||||
private array $setData = [];
|
||||
|
||||
public function __construct() {}
|
||||
|
||||
public function resetAll(): void
|
||||
{
|
||||
$this->setData = [];
|
||||
}
|
||||
|
||||
public function reset(string $relation): void
|
||||
{
|
||||
unset($this->setData[$relation]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity|null $related
|
||||
*/
|
||||
public function set(string $relation, Entity|null $related): void
|
||||
{
|
||||
$this->setData[$relation] = $related;
|
||||
}
|
||||
|
||||
public function isSet(string $relation): bool
|
||||
{
|
||||
return array_key_exists($relation, $this->setData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function getSet(string $relation): Entity|null
|
||||
{
|
||||
if (!array_key_exists($relation, $this->setData)) {
|
||||
throw new RuntimeExceptionAlias("Relation '$relation' is not set.");
|
||||
}
|
||||
|
||||
return $this->setData[$relation];
|
||||
}
|
||||
|
||||
public function getOne(string $relation): ?Entity
|
||||
{
|
||||
$entity = $this->setData[$relation] ?? null;
|
||||
|
||||
if ($entity instanceof EntityCollection) {
|
||||
throw new LogicException("Not an entity.");
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/***
|
||||
* @return EntityCollection<Entity>
|
||||
*/
|
||||
public function getMany(string $relation): EntityCollection
|
||||
{
|
||||
$collection = $this->setData[$relation] ?? new EntityCollection();
|
||||
|
||||
if (!$collection instanceof EntityCollection) {
|
||||
throw new LogicException("Not a collection.");
|
||||
}
|
||||
|
||||
/** @var EntityCollection<Entity> */
|
||||
return $collection;
|
||||
}
|
||||
}
|
||||
409
application/Espo/ORM/Relation/RDBRelations.php
Normal file
409
application/Espo/ORM/Relation/RDBRelations.php
Normal file
@@ -0,0 +1,409 @@
|
||||
<?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\ORM\Relation;
|
||||
|
||||
use Espo\ORM\BaseEntity;
|
||||
use Espo\ORM\Defs\Defs;
|
||||
use Espo\ORM\Defs\Params\RelationParam;
|
||||
use Espo\ORM\Defs\RelationDefs;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityCollection;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Mapper\RDBMapper;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\ORM\Repository\RDBRelationSelectBuilder;
|
||||
use Espo\ORM\Type\RelationType;
|
||||
use LogicException;
|
||||
use RuntimeException;
|
||||
use WeakReference;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RDBRelations implements Relations
|
||||
{
|
||||
/** @var array<string, Entity|EntityCollection<Entity>|null> */
|
||||
private array $data = [];
|
||||
/** @var array<string, Entity|null> */
|
||||
private array $setData = [];
|
||||
|
||||
/** @var WeakReference<Entity>|null */
|
||||
private ?WeakReference $entity = null;
|
||||
|
||||
/** @var string[] */
|
||||
private array $manyTypeList = [
|
||||
RelationType::MANY_MANY,
|
||||
RelationType::HAS_MANY,
|
||||
RelationType::HAS_CHILDREN,
|
||||
];
|
||||
|
||||
private Defs $defs;
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
) {
|
||||
$this->defs = $this->entityManager->getDefs();
|
||||
}
|
||||
|
||||
public function getEntity(): Entity
|
||||
{
|
||||
if (!$this->entity) {
|
||||
throw new LogicException("Entity not set.");
|
||||
}
|
||||
|
||||
return $this->entity->get() ?? throw new LogicException("Entity was destroyed.");
|
||||
}
|
||||
|
||||
public function setEntity(Entity $entity): void
|
||||
{
|
||||
if ($this->entity) {
|
||||
throw new LogicException("Entity is already set.");
|
||||
}
|
||||
|
||||
$this->entity = WeakReference::create($entity);
|
||||
}
|
||||
|
||||
public function reset(string $relation): void
|
||||
{
|
||||
unset($this->data[$relation]);
|
||||
unset($this->setData[$relation]);
|
||||
}
|
||||
|
||||
public function resetAll(): void
|
||||
{
|
||||
$this->data = [];
|
||||
$this->setData = [];
|
||||
}
|
||||
|
||||
public function isSet(string $relation): bool
|
||||
{
|
||||
return array_key_exists($relation, $this->setData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function getSet(string $relation): Entity|null
|
||||
{
|
||||
if (!array_key_exists($relation, $this->setData)) {
|
||||
throw new RuntimeException("Relation '$relation' is not set.");
|
||||
}
|
||||
|
||||
return $this->setData[$relation];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Entity|null $related
|
||||
*/
|
||||
public function set(string $relation, Entity|null $related): void
|
||||
{
|
||||
if (!$this->entity) {
|
||||
throw new LogicException("No entity set.");
|
||||
}
|
||||
|
||||
$type = $this->getRelationType($relation);
|
||||
|
||||
if (!$type) {
|
||||
throw new LogicException("Relation '$relation' does not exist.");
|
||||
}
|
||||
|
||||
if (
|
||||
!in_array($type, [
|
||||
RelationType::BELONGS_TO,
|
||||
RelationType::BELONGS_TO_PARENT,
|
||||
RelationType::HAS_ONE,
|
||||
])
|
||||
) {
|
||||
throw new LogicException("Relation type '$type' is not supported for setting.");
|
||||
}
|
||||
|
||||
if ($related) {
|
||||
$nameAttribute = $this->entityManager
|
||||
->getDefs()
|
||||
->getEntity($this->getEntity()->getEntityType())
|
||||
->getRelation($relation)
|
||||
->getParam('nameAttribute') ?? 'name';
|
||||
|
||||
$valueMap = [
|
||||
$relation . 'Id' => $related->getId(),
|
||||
$relation . 'Name' => $related->get($nameAttribute),
|
||||
];
|
||||
|
||||
if ($type === RelationType::BELONGS_TO_PARENT) {
|
||||
$valueMap[$relation . 'Type'] = $related->getEntityType();
|
||||
}
|
||||
} else {
|
||||
$valueMap = [
|
||||
$relation . 'Id' => null,
|
||||
$relation . 'Name' => null,
|
||||
];
|
||||
|
||||
if ($type === RelationType::BELONGS_TO_PARENT) {
|
||||
$valueMap[$relation . 'Type'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
$this->getEntity()->setMultiple($valueMap);
|
||||
|
||||
$this->setData[$relation] = $related;
|
||||
}
|
||||
|
||||
public function getOne(string $relation): ?Entity
|
||||
{
|
||||
$entity = $this->get($relation);
|
||||
|
||||
if ($entity instanceof EntityCollection) {
|
||||
throw new LogicException("Not an entity. Use `getMany` instead.");
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityCollection<Entity>
|
||||
*/
|
||||
public function getMany(string $relation): EntityCollection
|
||||
{
|
||||
$collection = $this->get($relation);
|
||||
|
||||
if (!$collection instanceof EntityCollection) {
|
||||
throw new LogicException("Not a collection. Use `getOne` instead.");
|
||||
}
|
||||
|
||||
/** @var EntityCollection<Entity> */
|
||||
return $collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $relation
|
||||
* @return Entity|EntityCollection<Entity>|null
|
||||
*/
|
||||
private function get(string $relation): Entity|EntityCollection|null
|
||||
{
|
||||
if (array_key_exists($relation, $this->setData)) {
|
||||
return $this->setData[$relation];
|
||||
}
|
||||
|
||||
if (!array_key_exists($relation, $this->data)) {
|
||||
if (!$this->entity) {
|
||||
throw new LogicException("No entity set.");
|
||||
}
|
||||
|
||||
$isMany = in_array($this->getRelationType($relation), $this->manyTypeList);
|
||||
|
||||
$this->data[$relation] = $isMany ?
|
||||
$this->findMany($relation) :
|
||||
$this->findOne($relation);
|
||||
}
|
||||
|
||||
$object = $this->data[$relation];
|
||||
|
||||
if ($object instanceof EntityCollection) {
|
||||
/** @var EntityCollection<Entity> $object */
|
||||
$object = new EntityCollection(iterator_to_array($object));
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
private function findOne(string $relation): ?Entity
|
||||
{
|
||||
if (!$this->entity) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
if (!$this->getEntity()->hasId() && $this->getRelationType($relation) === RelationType::HAS_ONE) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$foreignEntity = $this->getPartiallyLoadedForeignEntity($relation);
|
||||
|
||||
if ($foreignEntity === false) {
|
||||
// Parent type does not exist. Not throwing an error deliberately.
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($foreignEntity) {
|
||||
return $foreignEntity;
|
||||
}
|
||||
|
||||
$mapper = $this->entityManager->getMapper();
|
||||
|
||||
/** @noinspection PhpConditionAlreadyCheckedInspection */
|
||||
if (!$mapper instanceof RDBMapper) {
|
||||
throw new RuntimeException("Non RDB mapper.");
|
||||
}
|
||||
|
||||
// We use the Mapper as RDBRelation requires an entity with ID.
|
||||
|
||||
$entity = $mapper->selectRelated($this->getEntity(), $relation);
|
||||
|
||||
if (!$entity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$entity instanceof Entity) {
|
||||
throw new LogicException("Bad mapper return.");
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return EntityCollection<Entity>
|
||||
*/
|
||||
private function findMany(string $relation): EntityCollection
|
||||
{
|
||||
if (!$this->entity) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
if (!$this->getEntity()->hasId()) {
|
||||
/** @var EntityCollection<Entity> */
|
||||
return new EntityCollection();
|
||||
}
|
||||
|
||||
$relationDefs = $this->defs
|
||||
->getEntity($this->getEntity()->getEntityType())
|
||||
->getRelation($relation);
|
||||
|
||||
$builder = $this->entityManager
|
||||
->getRelation($this->getEntity(), $relation)
|
||||
->createBuilder();
|
||||
|
||||
$this->applyOrder($relationDefs, $builder);
|
||||
|
||||
$collection = $builder->find();
|
||||
|
||||
if (!$collection instanceof EntityCollection) {
|
||||
$collection = new EntityCollection(iterator_to_array($collection));
|
||||
}
|
||||
|
||||
/** @var EntityCollection<Entity> */
|
||||
return $collection;
|
||||
}
|
||||
|
||||
private function getRelationType(string $relation): ?string
|
||||
{
|
||||
if (!$this->entity) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
return $this->defs
|
||||
->getEntity($this->getEntity()->getEntityType())
|
||||
->tryGetRelation($relation)
|
||||
?->getType();
|
||||
}
|
||||
|
||||
private function getPartiallyLoadedForeignEntity(string $relation): BaseEntity|false|null
|
||||
{
|
||||
if (!$this->entity) {
|
||||
throw new LogicException();
|
||||
}
|
||||
|
||||
$defs = $this->defs
|
||||
->getEntity($this->getEntity()->getEntityType())
|
||||
->getRelation($relation);
|
||||
|
||||
if (!$defs->getParam(RelationParam::DEFERRED_LOAD)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$relationType = $defs->getType();
|
||||
|
||||
$id = null;
|
||||
$foreignEntityType = null;
|
||||
|
||||
if ($relationType === RelationType::BELONGS_TO) {
|
||||
$foreignEntityType = $defs->getForeignEntityType();
|
||||
$nameAttribute = $relation . 'Name';
|
||||
$id = $this->getEntity()->get($relation . 'Id');
|
||||
$name = $this->getEntity()->get($nameAttribute);
|
||||
|
||||
if (
|
||||
$id &&
|
||||
$name === null &&
|
||||
$this->getEntity()->hasAttribute($nameAttribute) &&
|
||||
$this->getEntity()->has($nameAttribute)
|
||||
) {
|
||||
$hasDeleted = $this->defs
|
||||
->getEntity($foreignEntityType)
|
||||
->hasAttribute(Attribute::DELETED);
|
||||
|
||||
if ($hasDeleted) {
|
||||
// Could be either soft-deleted or have name set to null.
|
||||
// We resort to not using a partially loaded entity.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
} else if ($relationType === RelationType::BELONGS_TO_PARENT) {
|
||||
$foreignEntityType = $this->getEntity()->get($relation . 'Type');
|
||||
$id = $this->getEntity()->get($relation . 'Id');
|
||||
|
||||
if (!$this->entityManager->hasRepository($foreignEntityType)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$foreignEntityType || !$id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$foreignEntity = $this->entityManager->getNewEntity($foreignEntityType);
|
||||
|
||||
if (!$foreignEntity instanceof BaseEntity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$foreignEntity->set(Attribute::ID, $id);
|
||||
$foreignEntity->setAsFetched();
|
||||
$foreignEntity->setAsPartiallyLoaded();
|
||||
|
||||
return $foreignEntity;
|
||||
}
|
||||
|
||||
private function applyOrder(RelationDefs $relationDefs, RDBRelationSelectBuilder $builder): void
|
||||
{
|
||||
$orderBy = $relationDefs->getParam(RelationParam::ORDER_BY);
|
||||
|
||||
if (!$orderBy) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order = $relationDefs->getParam(RelationParam::ORDER);
|
||||
|
||||
if ($order !== null) {
|
||||
$order = strtoupper($order) === Order::DESC ? Order::DESC : Order::ASC;
|
||||
}
|
||||
|
||||
$builder->order($orderBy, $order);
|
||||
}
|
||||
}
|
||||
78
application/Espo/ORM/Relation/Relations.php
Normal file
78
application/Espo/ORM/Relation/Relations.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?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\ORM\Relation;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityCollection;
|
||||
|
||||
/**
|
||||
* @internal Not ready for production.
|
||||
*/
|
||||
interface Relations
|
||||
{
|
||||
/**
|
||||
* Reset a specific relation.
|
||||
*/
|
||||
public function reset(string $relation): void;
|
||||
|
||||
/**
|
||||
* Reset all.
|
||||
*/
|
||||
public function resetAll(): void;
|
||||
|
||||
/**
|
||||
* @param Entity|null $related
|
||||
*/
|
||||
public function set(string $relation, Entity|null $related): void;
|
||||
|
||||
/**
|
||||
* Is a relation set (updated).
|
||||
*/
|
||||
public function isSet(string $relation): bool;
|
||||
|
||||
/**
|
||||
* Get set (updated) record or records.
|
||||
*
|
||||
* @return Entity|null
|
||||
*/
|
||||
public function getSet(string $relation): Entity|null;
|
||||
|
||||
/**
|
||||
* Get one related record. For has-one, belongs-to.
|
||||
*/
|
||||
public function getOne(string $relation): ?Entity;
|
||||
|
||||
/**
|
||||
* Get a collection of related records. For has-many, many-many, has-children.
|
||||
*
|
||||
* @return EntityCollection<Entity>
|
||||
*/
|
||||
public function getMany(string $relation): EntityCollection;
|
||||
}
|
||||
54
application/Espo/ORM/Relation/RelationsMap.php
Normal file
54
application/Espo/ORM/Relation/RelationsMap.php
Normal 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\ORM\Relation;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use WeakMap;
|
||||
|
||||
class RelationsMap
|
||||
{
|
||||
/** @var WeakMap<Entity, Relations> */
|
||||
private WeakMap $map;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->map = new WeakMap();
|
||||
}
|
||||
|
||||
public function get(Entity $entity): ?Relations
|
||||
{
|
||||
return $this->map[$entity] ?? null;
|
||||
}
|
||||
|
||||
public function set(Entity $entity, Relations $relations): void
|
||||
{
|
||||
$this->map[$entity] = $relations;
|
||||
}
|
||||
}
|
||||
@@ -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\ORM\Repository\Deprecation;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @template TEntity of Entity
|
||||
*/
|
||||
trait RDBRepositoryDeprecationTrait
|
||||
{
|
||||
/**
|
||||
* Get an entity. If ID is NULL, a new entity is returned.
|
||||
*
|
||||
* @deprecated Use `getById` and `getNew`.
|
||||
* @todo Remove in v10.0.
|
||||
*/
|
||||
public function get(?string $id = null): ?Entity
|
||||
{
|
||||
if (is_null($id)) {
|
||||
return $this->getNew();
|
||||
}
|
||||
|
||||
return $this->getById($id);
|
||||
}
|
||||
}
|
||||
66
application/Espo/ORM/Repository/EmptyHookMediator.php
Normal file
66
application/Espo/ORM/Repository/EmptyHookMediator.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?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\ORM\Repository;
|
||||
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Query\Select;
|
||||
|
||||
class EmptyHookMediator implements HookMediator
|
||||
{
|
||||
public function beforeSave(Entity $entity, array $options): void
|
||||
{}
|
||||
|
||||
public function afterSave(Entity $entity, array $options): void
|
||||
{}
|
||||
|
||||
public function beforeRemove(Entity $entity, array $options): void
|
||||
{}
|
||||
|
||||
public function afterRemove(Entity $entity, array $options): void
|
||||
{}
|
||||
|
||||
public function beforeRelate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData, array $options): void
|
||||
{}
|
||||
|
||||
public function afterRelate(Entity $entity, string $relationName, Entity $foreignEntity, ?array $columnData, array $options): void
|
||||
{}
|
||||
|
||||
public function beforeUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options): void
|
||||
{}
|
||||
|
||||
public function afterUnrelate(Entity $entity, string $relationName, Entity $foreignEntity, array $options): void
|
||||
{}
|
||||
|
||||
public function beforeMassRelate(Entity $entity, string $relationName, Select $query, array $options): void
|
||||
{}
|
||||
|
||||
public function afterMassRelate(Entity $entity, string $relationName, Select $query, array $options): void
|
||||
{}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user