diff --git a/application/Espo/ORM/EntityManager.php b/application/Espo/ORM/EntityManager.php index 30f66b3733..a2079a0aaa 100644 --- a/application/Espo/ORM/EntityManager.php +++ b/application/Espo/ORM/EntityManager.php @@ -94,10 +94,12 @@ class EntityManager $this->entityFactory->setEntityManager($this); $this->repositoryFactory = $repositoryFactory; + + $this->queryExecutor = new RDBQueryExecutor($this); } /** - * Get Query. + * Get a Query. */ public function getQuery() : Query { @@ -128,7 +130,7 @@ class EntityManager } /** - * Get Mapper. + * Get a Mapper. */ public function getMapper(?string $name = null) : Mapper { @@ -171,15 +173,16 @@ class EntityManager } $this->pdo = new PDO( - $platform . ':host='.$params['host'].';'.$port.'dbname=' . $params['dbname'] . ';charset=' . $params['charset'], + $platform . ':host=' . $params['host'] . ';'. $port.'dbname=' . $params['dbname'] . ';charset=' . $params['charset'], $params['user'], $params['password'], $options ); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } /** - * Get entity. If $id is null, a new entity instance is created. - * If entity with a specified $id does not exist, then NULL is returned. + * 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. */ public function getEntity(string $entityType, ?string $id = null) : ?Entity { @@ -191,42 +194,48 @@ class EntityManager } /** - * Store entity (in database). + * Store an entity (in database). */ public function saveEntity(Entity $entity, array $options = []) { $entityType = $entity->getEntityType(); + $this->getRepository($entityType)->save($entity, $options); } /** - * Mark entity as deleted (in database). + * Mark an entity as deleted (in database). */ public function removeEntity(Entity $entity, array $options = []) { $entityType = $entity->getEntityType(); + $this->getRepository($entityType)->remove($entity, $options); } /** * Create entity (store it in database). * - * @param \StdClass|array $data Entity attributes. + * @param StdClass|array $data Entity attributes. */ public function createEntity(string $entityType, $data, array $options = []) : Entity { $entity = $this->getEntity($entityType); $entity->set($data); $this->saveEntity($entity, $options); + return $entity; } /** - * Fetch entity (from database). + * Fetch an entity (from database). */ public function fetchEntity(string $entityType, string $id) : ?Entity { - if (empty($id)) return null; + if (empty($id)) { + return null; + } + return $this->getEntity($entityType, $id); } @@ -244,12 +253,13 @@ class EntityManager public function getRepository(string $entityType) : ?Repository { if (!$this->hasRepository($entityType)) { - // TODO Throw error + throw new Error("Repository '{$entityType}' does not exist."); } if (empty($this->repositoryHash[$entityType])) { $this->repositoryHash[$entityType] = $this->repositoryFactory->create($entityType); } + return $this->repositoryHash[$entityType] ?? null; } @@ -274,26 +284,26 @@ class EntityManager /** * Get an instance of PDO. */ - public function getPDO() : \PDO + public function getPDO() : PDO { if (empty($this->pdo)) { $this->initPDO(); } + return $this->pdo; } /** - * Create a Collection. + * Create a collection. * Entity type can be omitted. */ public function createCollection(?string $entityType = null, array $data = []) { - $collection = new EntityCollection($data, $entityType, $this->entityFactory); - return $collection; + return new EntityCollection($data, $entityType, $this->entityFactory); } /** - * Create an Sth Collection. Sth collection is preferable when a select query returns a large number of rows. + * Create an STH collection. An STH collection is preferable when a select query returns a large number of rows. */ public function createSthCollection(string $entityType, array $selectParams = []) { @@ -305,6 +315,14 @@ class EntityManager return $this->entityFactory; } + /** + * Get a Query Executor. + */ + public function getQueryExecutor() : RDBQueryExecutor + { + return $this->queryExecutor; + } + /** * Run a query. Returns a result. * diff --git a/application/Espo/ORM/RDBQueryExecutor.php b/application/Espo/ORM/RDBQueryExecutor.php new file mode 100644 index 0000000000..c99ff44fb4 --- /dev/null +++ b/application/Espo/ORM/RDBQueryExecutor.php @@ -0,0 +1,59 @@ +entityManager = $entityManager; + } + + public function update(RDBSelect $select, array $values) + { + $sql = $this->entityManager->getQuery()->createUpdateQuery($select->getEntityType(), $select->getRawParams(), $values); + + $this->entityManager->runQuery($sql, true); + } + + public function delete(RDBSelect $select) + { + $sql = $this->entityManager->getQuery()->createDeleteQuery($select->getEntityType(), $select->getRawParams()); + + $this->entityManager->runQuery($sql, true); + } +} diff --git a/application/Espo/ORM/RDBSelectParams.php b/application/Espo/ORM/RDBSelect.php similarity index 96% rename from application/Espo/ORM/RDBSelectParams.php rename to application/Espo/ORM/RDBSelect.php index 4b94e97cca..f8cb704845 100644 --- a/application/Espo/ORM/RDBSelectParams.php +++ b/application/Espo/ORM/RDBSelect.php @@ -32,7 +32,7 @@ namespace Espo\ORM; /** * Select parameters. */ -class RDBSelectParams +class RDBSelect { protected $entityType; @@ -55,7 +55,7 @@ class RDBSelectParams /** * Get parameters in RAW format. */ - public function getRaw() : array + public function getRawParams() : array { return $this->params; } diff --git a/application/Espo/ORM/RDBSelectBuilder.php b/application/Espo/ORM/RDBSelectBuilder.php index 3bb2d9cadc..f9664474e4 100644 --- a/application/Espo/ORM/RDBSelectBuilder.php +++ b/application/Espo/ORM/RDBSelectBuilder.php @@ -321,11 +321,11 @@ class RDBSelectBuilder implements Findable /** * Builds result select parameters. */ - public function build() : RDBSelectParams + public function build() : RDBSelect { $this->processExecutableCheck(); - return new RDBSelectParams($this->entityType, $this->getMergedParams()); + return new RDBSelect($this->entityType, $this->getMergedParams()); } protected function getMergedParams(array $params = []) : array diff --git a/application/Espo/Repositories/EmailAddress.php b/application/Espo/Repositories/EmailAddress.php index c3ee8c724b..d69f9c9b91 100644 --- a/application/Espo/Repositories/EmailAddress.php +++ b/application/Espo/Repositories/EmailAddress.php @@ -494,30 +494,31 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements if ($primary) { $emailAddress = $this->getByAddress($primary); - if ($emailAddress) { - $query = " - UPDATE entity_email_address - SET `primary` = 0 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityType())." AND - `primary` = 1 AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); - $query = " - UPDATE entity_email_address - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityType())." AND - email_address_id = ".$pdo->quote($emailAddress->id)." AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); + if ($emailAddress) { + $updateSelect = $this->getEntityManager()->createSelectBuilder() + ->from('EntityEmailAddress') + ->where([ + 'entityId' => $entity->id, + 'entityType' => $entity->getEntityType(), + 'primary' => true, + 'deleted' => false, + ]) + ->build(); + + $this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => false]); + + $updateSelect = $this->getEntityManager()->createSelectBuilder() + ->from('EntityEmailAddress') + ->where([ + 'entityId' => $entity->id, + 'entityType' => $entity->getEntityType(), + 'emailAddressId' => $emailAddress->id, + 'deleted' => false, + ]) + ->build(); + + $this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => true]); } } @@ -544,6 +545,7 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements } $entityRepository = $this->getEntityManager()->getRepository($entity->getEntityType()); + if (!empty($emailAddressValue)) { if ($emailAddressValue != $entity->getFetched('emailAddress')) { @@ -572,16 +574,21 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements $this->markAddressOptedOut($emailAddressValue, !!$entity->get('emailAddressIsOptedOut')); } - $query = " - UPDATE entity_email_address - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityType())." AND - email_address_id = ".$pdo->quote($emailAddressNew->id)." - "; - $sth = $pdo->prepare($query); - $sth->execute(); + $updateSelect = $this->getEntityManager()->createSelectBuilder() + ->from('EntityEmailAddress') + ->where([ + 'entityId' => $entity->id, + 'entityType' => $entity->getEntityType(), + 'emailAddressId' => $emailAddressNew->id, + ]) + ->build(); + + $sql = $this->getEntityManager()->getQuery()->createUpdateQuery('EntityEmailAddress', $updateSelect->getRawParams(), [ + 'primary' => true, + ]); + + $this->getEntityManager()->runQuery($sql, true); + } else { if ( $entity->has('emailAddressIsOptedOut') @@ -608,7 +615,6 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements } } } - } public function storeEntityEmailAddress(Entity $entity) @@ -625,7 +631,7 @@ class EmailAddress extends \Espo\Core\Repositories\Database implements } } - // TODO move it to another place + // @todo move it to another place protected function checkChangeIsForbidden($entity, $excludeEntity) { return !$this->aclManager->getImplementation('EmailAddress') diff --git a/application/Espo/Repositories/PhoneNumber.php b/application/Espo/Repositories/PhoneNumber.php index 25600f64ba..755aa23a36 100644 --- a/application/Espo/Repositories/PhoneNumber.php +++ b/application/Espo/Repositories/PhoneNumber.php @@ -423,29 +423,30 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements if ($primary) { $phoneNumber = $this->getByNumber($primary); if ($phoneNumber) { - $query = " - UPDATE entity_phone_number - SET `primary` = 0 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityType())." AND - `primary` = 1 AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); - $query = " - UPDATE entity_phone_number - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityType())." AND - phone_number_id = ".$pdo->quote($phoneNumber->id)." AND - deleted = 0 - "; - $sth = $pdo->prepare($query); - $sth->execute(); + $updateSelect = $this->getEntityManager()->createSelectBuilder() + ->from('EntityPhoneNumber') + ->where([ + 'entityId' => $entity->id, + 'entityType' => $entity->getEntityType(), + 'primary' => true, + 'deleted' => false, + ]) + ->build(); + + $this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => false]); + + $updateSelect = $this->getEntityManager()->createSelectBuilder() + ->from('EntityPhoneNumber') + ->where([ + 'entityId' => $entity->id, + 'entityType' => $entity->getEntityType(), + 'phoneNumberId' => $phoneNumber->id, + 'deleted' => false, + ]) + ->build(); + + $this->getEntityManager()->getQueryExecutor()->update($updateSelect, ['primary' => true]); } } @@ -501,16 +502,21 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements $this->markNumberOptedOut($phoneNumberValue, !!$entity->get('phoneNumberIsOptedOut')); } - $query = " - UPDATE entity_phone_number - SET `primary` = 1 - WHERE - entity_id = ".$pdo->quote($entity->id)." AND - entity_type = ".$pdo->quote($entity->getEntityType())." AND - phone_number_id = ".$pdo->quote($phoneNumberNew->id)." - "; - $sth = $pdo->prepare($query); - $sth->execute(); + $updateSelect = $this->getEntityManager()->createSelectBuilder() + ->from('EntityPhoneNumber') + ->where([ + 'entityId' => $entity->id, + 'entityType' => $entity->getEntityType(), + 'phoneNumberId' => $phoneNumberNew->id, + ]) + ->build(); + + $sql = $this->getEntityManager()->getQuery()->createUpdateQuery('EntityPhoneNumber', $updateSelect->getRawParams(), [ + 'primary' => true, + ]); + + $this->getEntityManager()->runQuery($sql, true); + } else { if ( $entity->has('phoneNumberIsOptedOut') @@ -553,7 +559,7 @@ class PhoneNumber extends \Espo\Core\Repositories\Database implements } } - // TODO move it to another place + // @todo move it to another place protected function checkChangeIsForbidden($entity, $excludeEntity) { return !$this->aclManager->getImplementation('PhoneNumber')