Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
33.33% |
2 / 6 |
CRAP | |
58.76% |
57 / 97 |
PostTransitions | |
0.00% |
0 / 1 |
|
33.33% |
2 / 6 |
48.05 | |
58.76% |
57 / 97 |
__construct | |
100.00% |
1 / 1 |
1 | |
100.00% |
6 / 6 |
|||
create | |
0.00% |
0 / 1 |
5 | |
95.24% |
20 / 21 |
|||
update | |
0.00% |
0 / 1 |
5 | |
95.00% |
19 / 20 |
|||
assignTags | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 35 |
|||
addComment | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
delete | |
100.00% |
1 / 1 |
2 | |
100.00% |
12 / 12 |
<?php declare(strict_types = 1); | |
/* | |
* Copyright (c) 2019, Josef Kufner <josef@kufner.cz> | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
* | |
*/ | |
namespace Smalldb\StateMachine\Test\Example\Post; | |
use Doctrine\DBAL\Connection; | |
use Doctrine\DBAL\DBALException; | |
use Doctrine\DBAL\FetchMode; | |
use Smalldb\StateMachine\Test\Example\Comment\Comment; | |
use Smalldb\StateMachine\Test\Example\Comment\CommentData\CommentData; | |
use Smalldb\StateMachine\Test\Example\Comment\CommentRepository; | |
use Smalldb\StateMachine\Test\Example\Post\PostData\PostData; | |
use Smalldb\StateMachine\Test\Example\Tag\TagData\TagData; | |
use Smalldb\StateMachine\Test\Example\Tag\TagRepository; | |
use Smalldb\StateMachine\Transition\MethodTransitionsDecorator; | |
use Smalldb\StateMachine\Transition\TransitionDecorator; | |
use Smalldb\StateMachine\Transition\TransitionEvent; | |
use Smalldb\StateMachine\Transition\TransitionGuard; | |
use Symfony\Component\String\Slugger\SluggerInterface; | |
class PostTransitions extends MethodTransitionsDecorator implements TransitionDecorator | |
{ | |
private Connection $db; | |
private CommentRepository $commentRepository; | |
private SluggerInterface $slugger; | |
private TagRepository $tagRepository; | |
public function __construct(TransitionGuard $guard, Connection $db, TagRepository $tagRepository, CommentRepository $commentRepository, SluggerInterface $slugger) | |
{ | |
parent::__construct($guard); | |
$this->db = $db; | |
$this->commentRepository = $commentRepository; | |
$this->slugger = $slugger; | |
$this->tagRepository = $tagRepository; | |
} | |
/** | |
* @throws DBALException | |
*/ | |
protected function create(TransitionEvent $transitionEvent, Post $ref, PostData $data, ?array $tags = null): int | |
{ | |
$this->db->beginTransaction(); | |
$stmt = $this->db->prepare(" | |
INSERT INTO symfony_demo_post (id, author_id, title, slug, summary, content, published_at) | |
VALUES (:id, :authorId, :title, :slug, :summary, :content, :publishedAt) | |
"); | |
$slug = $this->slugger->slug($data->getTitle()); | |
$id = $ref->getMachineId(); | |
$stmt->execute([ | |
'id' => $id, | |
'authorId' => $data->getAuthorId(), | |
'title' => $data->getTitle(), | |
'slug' => $slug, | |
'summary' => $data->getSummary(), | |
'content' => $data->getContent(), | |
'publishedAt' => ($d = $data->getPublishedAt()) ? $d->format(DATE_ISO8601) : null, | |
]); | |
if ($id === null) { | |
$newId = (int)$this->db->lastInsertId(); | |
if ($tags !== null) { | |
$this->assignTags($newId, $tags); | |
} | |
$this->db->commit(); | |
$transitionEvent->setNewId($newId); | |
return $newId; | |
} else { | |
$this->db->commit(); | |
return $id; | |
} | |
} | |
/** | |
* @throws DBALException | |
*/ | |
protected function update(TransitionEvent $transitionEvent, Post $ref, PostData $data, ?array $tags = null): void | |
{ | |
$this->db->beginTransaction(); | |
$stmt = $this->db->prepare(" | |
UPDATE symfony_demo_post | |
SET | |
id = :newId, | |
author_id = :authorId, | |
title = :title, | |
slug = :slug, | |
summary = :summary, | |
content = :content, | |
published_at = :publishedAt | |
WHERE | |
id = :oldId | |
"); | |
$oldId = $ref->getMachineId(); | |
$newId = $data->getId(); | |
$slug = $this->slugger->slug($data->getTitle()); | |
$stmt->execute([ | |
'oldId' => $oldId, | |
'newId' => $newId, | |
'authorId' => $data->getAuthorId(), | |
'title' => $data->getTitle(), | |
'slug' => $slug, | |
'summary' => $data->getSummary(), | |
'content' => $data->getContent(), | |
'publishedAt' => ($d = $data->getPublishedAt()) ? $d->format(DATE_ISO8601) : null, | |
]); | |
if ($tags !== null) { | |
$this->assignTags($newId, $tags); | |
} | |
$this->db->commit(); | |
if ($oldId != $newId) { | |
$transitionEvent->setNewId($newId); | |
} | |
} | |
/** | |
* @param TagData[] $tags | |
* @return int Number of changed rows. | |
*/ | |
private function assignTags($postId, array $tags): int | |
{ | |
$tagIds = []; | |
$createTagIds = []; | |
foreach ($tags as $t) { | |
if ($t->getId() === null) { | |
// If tag has no ID, then create it. | |
$tRef = $this->tagRepository->ref(null); | |
$tRef->create($t); | |
$createTagIds[] = $tRef->getId(); | |
} else { | |
// Collect list of IDs that post should already have | |
$tagIds[] = $t->getId(); | |
} | |
} | |
$oldTagIds = $this->db->createQueryBuilder()->select("tag_id") | |
->from("symfony_demo_post_tag") | |
->where("post_id = :postId") | |
->setParameter('postId', $postId) | |
->execute() | |
->fetchAll(FetchMode::COLUMN); | |
// Calculate the differences | |
$removeIds = array_diff($oldTagIds, $tagIds); | |
$insertIds = array_diff($tagIds, $oldTagIds); | |
$newIds = array_merge($insertIds, $createTagIds); | |
$rows = 0; | |
// Delete tags that the post should not have | |
if (!empty($removeIds)) { | |
$rows += $this->db->createQueryBuilder()->delete("symfony_demo_post_tag") | |
->andWhere("tag_id IN (:tagIds)") | |
->andWhere("post_id = :postId") | |
->setParameter(":tagIds", $removeIds, Connection::PARAM_STR_ARRAY) | |
->setParameter(":postId", $postId) | |
->execute(); | |
} | |
// Assign the existing and created tags | |
if (!empty($newIds)) { | |
$iq = $this->db->createQueryBuilder() | |
->insert("symfony_demo_post_tag") | |
->values(['post_id' => ':postId', 'tag_id' => ':tagId']); | |
$insertStmt = $this->db->prepare($iq->getSQL()); | |
$insertStmt->bindValue(':postId', $postId); | |
foreach ($newIds as $tagId) { | |
$insertStmt->bindValue(':tagId', $tagId); | |
$rows += $insertStmt->execute(); | |
} | |
} | |
return $rows; | |
} | |
protected function addComment(TransitionEvent $transitionEvent, Post $ref, CommentData $commentData): Comment | |
{ | |
$commentRef = $this->commentRepository->ref(null); | |
$commentRef->create($commentData); | |
return $commentRef; | |
} | |
/** | |
* @throws DBALException | |
*/ | |
protected function delete(TransitionEvent $transitionEvent, Post $ref): void | |
{ | |
$postId = $ref->getMachineId(); | |
$this->db->beginTransaction(); | |
// Delete the tags associated with this blog post. This should be done | |
// automatically, except for SQLite (the database used in this application) | |
// because foreign key support is not enabled by default in SQLite | |
$this->db->prepare(" | |
DELETE FROM symfony_demo_post_tag | |
WHERE post_id = :postId | |
")->execute(['postId' => $postId]); | |
$stmt = $this->db->prepare(" | |
DELETE FROM symfony_demo_post | |
WHERE id = :id | |
"); | |
$stmt->execute([ | |
'id' => $postId, | |
]); | |
$rowCount = $stmt->rowCount(); | |
$this->db->commit(); | |
if ($rowCount === 1) { | |
$transitionEvent->setNewId(null); | |
} | |
} | |
} |