I want to talk about my experience using Doctrine ODM in a relatively small PHP project in which the main code base is concentrated in daemon processes. And in general, how we fastened Doctrine ODM to Yii2. I warn you right away - the story will be very boring and most likely only interesting to those who have already encountered problems when working with Doctrine ODM in daemon processes.
Where we will screw
About 6 years ago, they entrusted me with the task: to cut from one monolith into a Yii1
piece of code into a separate service. No sooner said than done. The framework didnโt choose for a long time, except Yii
I didnโt know anything, and then they announced an imminent release Yii2
, which at that time seemed very stylish / fashionable / youthful, but knowledge of the first version of this framework allowed reducing the threshold for entering the second version. MongoDB was also fashionable, with which we were already familiar and worked Yii1
with a separate extension, but Yii2
it was supported out of the box.
Initially, the microservice was quite simple: it was necessary to receive files from users and store them in a persistent storage, keep records of these files in the database, issue information regarding these files and links to them in response to relevant requests through the API.
Over time, this micro-service began to grow: video processing, virus checking, distributed storage across different regions (a la CDN) appeared. Time passed quickly, the project actively developed and even underwent refactoring, but at some point, due to new trends and professional growth, too, a feeling of some discomfort appeared when interacting with this project. The desire to work on this project became less and less and I seriously thought.
One of the reasons for the discomfort was the inability to work conveniently with the Embedded Document . The inconvenience was that I had to deal with native PHP
arrays. At first, this did not cause problems, but as the project grew, these arrays began to appear everywhere, they became more and more, and the knowledge about the old code was forgotten and the understanding of everything deteriorated. And in general, I wanted to have beautiful objects, and not an incomprehensible set of fields whose indices must be constantly remembered. I'm not talking about the fact that such code is very poorly connected statically.
PHP
7- , . ActiveRecord
Yii2
โ "" , , phpdoc. MongoDB
, - , - {user_id : '12'}
, . - , Yii2 . , , , , , .
, , MongoDB
.
Symfony
Doctrine ORM
, Doctrine ODM
โ ORM
MongoDB
.
Yii2 Symfony , , :
- โ , , .
- โ ActiveRecord
" " Yii2
, ActiveRecord
Doctrine ODM
, . , ORM .
. Doctrine DocumentManager
( EntityManager Doctrine ORM). " ".
Dependency Injection Container Yii2
, .
Yii2
DataProvider'a
. -, CRUD- . " " . , Yii2
. , , โ , .
, Doctrine UnitOfWork , .
, : DI- , . - , Symfony .
, :
while (!$this->isShuttingDown()) {
$task = $this->taskProvider->getNextTask();
try {
$this->someService->runTask($task);
} catch (SomeException $e) {
$this->switchTaskToError($task, $e);
}
}
, - dirty ( flush() ), , , .
โ clear()
"" . , , . clear()
, , , $this->someService->runTask($task);
, ( , โ ), . . , , . , , . .
:
while (!$this->isShuttingDown()) {
$task = $this->taskProvider->getNextTask();
try {
$this->someService->runTask($task);
} catch (SomeException $e) {
$this->switchTaskToError($task, $e);
}
}
taskProvider
someService
, .
merge
. runTask()
:
public function runTask(Task $task) {
$dm = $thic->dmFactory->createDocumentManager();
$task = $dm->merge($task);
....
$task->setStatus(Task::STATUS_OK);
$dm->flush();
}
switchTaskToError()
:
public function switchTaskToError(Task $task, Throwable $) {
$dm = $this->dmFactory->createDocumentManager();
$task = $dm->merge($task);
$task->setStatus(Task::STATUS_ERROR);
$task->setError($e->getMessage());
$dm->flush();
}
, $dm
.
, , - . , :
$tasks = $this->taskProvider->getProblemTasks();
$dm = $thic->dmFactory->createDocumentManager();
foreach ($tasks as $task) {
$task = $dm->merge($task);
$task->setState(..);
}
$dm->flush();
getProblemTasks
, , "" , Task
merge()
. , merge()
โ .. , .
, , :
$dm = $thic->dmFactory->createDocumentManager();
$tasks = $this->taskProvider->getProblemTasks($dm);
foreach ($tasks as $task) {
$task->setState(..);
}
$dm->flush();
.
JPA
, Doctrine JPA: , UnitOfWork
, (managed, detached ..). JPA , , . , โ . (Spring Framework ). JPA , , .
, , . - , , - (? ), ( ). , , .
, hibernate Doctrine.
Doctrine ODM
" doctrine" , - http , , , .
, . , . . , :
$session = $this->sessionManager->createNewSession();
try {
$dm= $session->getDocumentManager();
$tasks = $this->taskProvider->getProblemTasks();
foreach ($tasks as $task) {
$task->setState(..);
}
$dm->flush();
} finally {
$this->sessionManager->close($session);
}
getProblemTasks
public function getProblemTasks(): array
{
$repo = $this->sessionManager
->getCurrentSession()
->getDocumentManager()
->getRepository(Task::class);
return $repo->findBy(['status' => Task::STATUS_ERROR]);
}
, , , , .
:
public function switchTaskToError(Task $task, Throwable $) {
$session $this->sessionManager->createNewSession();
$dm = $session->getDocumentManager();
try {
$task = $dm->merge($task);
$task->setStatus(Task::STATUS_ERROR);
$task->setError($e->getMessage());
$dm->flush();
finally {
$this->sessionManager->close($session);
}
}
, , , wrap
, , .
, 3 SessionManager
createNewSession
โ , "" .
getCurrentSession
โ .
close
โ
, ( php-ds).
:
createNewSession
โ , .
getCurrentSession
โ , , .
close
โ .
, , , .
wrap
, , :
public function switchTaskToError(Task $task, Throwable $) {
$this->sessionManager->wrapBySession(function(DocumentManager $dm) use ($task, $e) {
$task = $dm->merge($task);
$task->setStatus(Task::STATUS_ERROR);
$task->setError($e->getMessage());
$dm->flush();
});
}
In general, the session mechanism turned out to be quite convenient. At least he solved all our problems. Of course, it would be nice to also manage sessions using a declarative approach, as is done in the Spring Framework, for example. This would significantly reduce overhead in the form of redundant code, but at the moment I can not even imagine how this can be done in PHP.