Added id column to tags

This commit is contained in:
Marcin Kurczewski 2014-09-21 09:35:43 +02:00
parent 42e3559cb4
commit af3908a23c
21 changed files with 258 additions and 93 deletions

4
TODO
View file

@ -126,9 +126,7 @@ everything related to comments:
- score (see notes about scoring)
refactors:
- make PostDao use TagDao when creating not existing tags (perhaps use
separate PostTagDao for this)
- post view proxy should retrieve full tags and full user
- post view proxy should retrieve full user
- centralize markdown prefix decorators
- add enum validation in IValidatables (needs refactors of enums and
possible disposal of EnumHelper in favor of something more subtle)

View file

@ -1,6 +1,6 @@
<li class="post post-type-<%= post.contentType %> ">
<a class="link"
title="<%= _.map(post.tags, function(tag) { return '#' + tag; }).join(', ') %>"
title="<%= _.map(post.tags, function(tag) { return '#' + tag.name; }).join(', ') %>"
href="#/post/<%= post.id %>">
<img class="thumb" src="/data/thumbnails/160x160/posts/<%= post.name %>" alt="@<%= post.id %>"/>

View file

@ -3,6 +3,13 @@ namespace Szurubooru\Controllers\ViewProxies;
class PostViewProxy extends AbstractViewProxy
{
private $tagViewProxy;
public function __construct(TagViewProxy $tagViewProxy)
{
$this->tagViewProxy = $tagViewProxy;
}
public function fromEntity($post)
{
$result = new \StdClass;
@ -20,7 +27,7 @@ class PostViewProxy extends AbstractViewProxy
$result->source = $post->getSource();
$result->imageWidth = $post->getImageWidth();
$result->imageHeight = $post->getImageHeight();
$result->tags = $post->getTags();
$result->tags = $this->tagViewProxy->fromArray($post->getTags());
$result->originalFileSize = $post->getOriginalFileSize();
}
return $result;

View file

@ -0,0 +1,15 @@
<?php
namespace Szurubooru\Controllers\ViewProxies;
class TagViewProxy extends AbstractViewProxy
{
public function fromEntity($tag)
{
$result = new \StdClass;
if ($tag)
{
$result->name = $tag->getName();
}
return $result;
}
}

View file

@ -13,8 +13,7 @@ abstract class AbstractDao implements ICrudDao
$tableName,
\Szurubooru\Dao\EntityConverters\IEntityConverter $entityConverter)
{
$this->pdo = $databaseConnection->getPDO();
$this->fpdo = new \FluentPDO($this->pdo);
$this->setDatabaseConnection($databaseConnection);
$this->tableName = $tableName;
$this->entityConverter = $entityConverter;
$this->entityConverter->setEntityDecorator(function($entity)
@ -23,6 +22,12 @@ abstract class AbstractDao implements ICrudDao
});
}
public function setDatabaseConnection(\Szurubooru\DatabaseConnection $databaseConnection)
{
$this->pdo = $databaseConnection->getPDO();
$this->fpdo = new \FluentPDO($this->pdo);
}
public function getTableName()
{
return $this->tableName;
@ -61,7 +66,12 @@ abstract class AbstractDao implements ICrudDao
public function findById($entityId)
{
return $this->findOneBy('id', $entityId);
return $this->findOneBy($this->getIdColumn(), $entityId);
}
public function findByIds($entityIds)
{
return $this->findBy($this->getIdColumn(), $entityIds);
}
public function deleteAll()
@ -75,13 +85,13 @@ abstract class AbstractDao implements ICrudDao
public function deleteById($entityId)
{
return $this->deleteBy('id', $entityId);
return $this->deleteBy($this->getIdColumn(), $entityId);
}
protected function update(\Szurubooru\Entities\Entity $entity)
{
$arrayEntity = $this->entityConverter->toArray($entity);
$this->fpdo->update($this->tableName)->set($arrayEntity)->where('id', $entity->getId())->execute();
$this->fpdo->update($this->tableName)->set($arrayEntity)->where($this->getIdColumn(), $entity->getId())->execute();
return $entity;
}
@ -93,6 +103,11 @@ abstract class AbstractDao implements ICrudDao
return $entity;
}
protected function getIdColumn()
{
return 'id';
}
protected function hasAnyRecords()
{
return count(iterator_to_array($this->fpdo->from($this->tableName)->limit(1))) > 0;

View file

@ -7,13 +7,14 @@ class TagEntityConverter extends AbstractEntityConverter implements IEntityConve
{
return
[
'id' => $entity->getId(),
'name' => $entity->getName(),
];
}
public function toBasicEntity(array $array)
{
$entity = new \Szurubooru\Entities\Tag($array['name']);
$entity = new \Szurubooru\Entities\Tag($array['id']);
$entity->setName($array['name']);
return $entity;
}

View file

@ -3,11 +3,13 @@ namespace Szurubooru\Dao;
class PostDao extends AbstractDao implements ICrudDao
{
private $tagDao;
private $fileService;
private $thumbnailService;
public function __construct(
\Szurubooru\DatabaseConnection $databaseConnection,
\Szurubooru\Dao\TagDao $tagDao,
\Szurubooru\Services\FileService $fileService,
\Szurubooru\Services\ThumbnailService $thumbnailService)
{
@ -16,6 +18,7 @@ class PostDao extends AbstractDao implements ICrudDao
'posts',
new \Szurubooru\Dao\EntityConverters\PostEntityConverter());
$this->tagDao = $tagDao;
$this->fileService = $fileService;
$this->thumbnailService = $thumbnailService;
}
@ -58,17 +61,12 @@ class PostDao extends AbstractDao implements ICrudDao
{
$this->syncContent($post);
$this->syncThumbnailSourceContent($post);
$this->syncTags($post->getId(), $post->getTags());
$this->syncTags($post);
}
private function getTags(\Szurubooru\Entities\Post $post)
{
$postId = $post->getId();
$result = [];
$query = $this->fpdo->from('postTags')->where('postId', $postId)->select('tagName');
foreach ($query as $arrayEntity)
$result[] = $arrayEntity['tagName'];
return $result;
return $this->tagDao->findByPostId($post->getId());
}
private function syncContent(\Szurubooru\Entities\Post $post)
@ -93,44 +91,41 @@ class PostDao extends AbstractDao implements ICrudDao
$this->thumbnailService->deleteUsedThumbnails($targetPath);
}
private function syncTags($postId, array $tags)
private function syncTags(\Szurubooru\Entities\Post $post)
{
$existingTags = array_map(
$tagNames = array_filter(array_unique(array_map(
function ($tag)
{
return $tag->getName();
},
$post->getTags())));
$this->tagDao->createMissingTags($tagNames);
$tagIds = array_map(
function($tag)
{
return $tag->getId();
},
$this->tagDao->findByNames($tagNames));
$existingTagRelationIds = array_map(
function($arrayEntity)
{
return $arrayEntity['tagName'];
return $arrayEntity['tagId'];
},
iterator_to_array($this->fpdo->from('postTags')->where('postId', $postId)));
$tagRelationsToInsert = array_diff($tags, $existingTags);
$tagRelationsToDelete = array_diff($existingTags, $tags);
$this->createMissingTags($tags);
foreach ($tagRelationsToInsert as $tag)
{
$this->fpdo->insertInto('postTags')->values(['postId' => $postId, 'tagName' => $tag])->execute();
}
foreach ($tagRelationsToDelete as $tag)
{
$this->fpdo->deleteFrom('postTags')->where('postId', $postId)->and('tagName', $tag)->execute();
}
}
iterator_to_array($this->fpdo->from('postTags')->where('postId', $post->getId())));
private function createMissingTags(array $tags)
{
if (empty($tags))
return;
$tagRelationsToInsert = array_diff($tagIds, $existingTagRelationIds);
$tagRelationsToDelete = array_diff($existingTagRelationIds, $tagIds);
$tagsNotToCreate = array_map(
function($arrayEntity)
foreach ($tagRelationsToInsert as $tagId)
{
return $arrayEntity['name'];
},
iterator_to_array($this->fpdo->from('tags')->where('name', $tags)));
$tagsToCreate = array_diff($tags, $tagsNotToCreate);
foreach ($tagsToCreate as $tag)
$this->fpdo->insertInto('postTags')->values(['postId' => $post->getId(), 'tagId' => $tagId])->execute();
}
foreach ($tagRelationsToDelete as $tagId)
{
$this->fpdo->insertInto('tags')->values(['name' => $tag])->execute();
$this->fpdo->deleteFrom('postTags')->where('postId', $post->getId())->and('tagId', $tagId)->execute();
}
}
}

View file

@ -10,4 +10,43 @@ class TagDao extends AbstractDao implements ICrudDao
'tags',
new \Szurubooru\Dao\EntityConverters\TagEntityConverter());
}
public function findByNames($tagNames)
{
return $this->findBy('name', $tagNames);
}
public function findByPostId($postId)
{
$query = $this->fpdo->from('postTags')->where('postId', $postId);
$tagIds = array_map(function($arrayEntity)
{
return $arrayEntity['tagId'];
},
iterator_to_array($query));
return $this->findByIds($tagIds);
}
public function createMissingTags(array $tagNames)
{
$tagNames = array_filter(array_unique($tagNames));
if (empty($tagNames))
return;
$tagNamesNotToCreate = array_map(
function($arrayEntity)
{
return $arrayEntity['name'];
},
iterator_to_array($this->fpdo->from('tags')->where('name', $tagNames)));
$tagNamesToCreate = array_diff($tagNames, $tagNamesNotToCreate);
foreach ($tagNamesToCreate as $tagName)
{
$tag = new \Szurubooru\Entities\Tag;
$tag->setName($tagName);
$this->save($tag);
}
}
}

View file

@ -6,15 +6,6 @@ final class Tag extends Entity
protected $name;
protected $usages;
public function getId()
{
return $this->name;
}
public function setId($id)
{
$this->name = $id;
}
public function getName()
{

View file

@ -178,9 +178,16 @@ class PostService
}
}
private function updatePostTags(\Szurubooru\Entities\Post $post, array $newTags)
private function updatePostTags(\Szurubooru\Entities\Post $post, array $newTagNames)
{
$post->setTags($newTags);
$tags = [];
foreach ($newTagNames as $tagName)
{
$tag = new \Szurubooru\Entities\Tag();
$tag->setName($tagName);
$tags[] = $tag;
}
$post->setTags($tags);
}
private function assertNoPostWithThisContentChecksum(\Szurubooru\Entities\Post $parent)

View file

@ -3,30 +3,27 @@ namespace Szurubooru\Upgrades;
class Upgrade04 implements IUpgrade
{
private $postDao;
private $postService;
private $fileService;
private $thumbnailService;
public function __construct(
\Szurubooru\Dao\PostDao $postDao,
\Szurubooru\Services\PostService $postService,
\Szurubooru\Services\FileService $fileService,
\Szurubooru\Services\ThumbnailService $thumbnailService)
\Szurubooru\Services\FileService $fileService)
{
$this->postDao = $postDao;
$this->postService = $postService;
$this->fileService = $fileService;
$this->thumbnailService = $thumbnailService;
}
public function run(\Szurubooru\DatabaseConnection $databaseConnection)
{
$this->postDao->setDatabaseConnection($databaseConnection);
$databaseConnection->getPDO()->exec('ALTER TABLE "posts" ADD COLUMN contentMimeType TEXT DEFAULT NULL');
$postDao = new \Szurubooru\Dao\PostDao(
$databaseConnection,
$this->fileService,
$this->thumbnailService);
$posts = $postDao->findAll();
$posts = $this->postDao->findAll();
foreach ($posts as $post)
{
if ($post->getContentType() !== \Szurubooru\Entities\Post::POST_TYPE_YOUTUBE)
@ -34,7 +31,7 @@ class Upgrade04 implements IUpgrade
$fullPath = $this->fileService->getFullPath($this->postService->getPostContentPath($post));
$mime = \Szurubooru\Helpers\MimeHelper::getMimeTypeFromFile($fullPath);
$post->setContentMimeType($mime);
$postDao->save($post);
$this->postDao->save($post);
}
}
}

View file

@ -0,0 +1,32 @@
<?php
namespace Szurubooru\Upgrades;
class Upgrade05 implements IUpgrade
{
public function run(\Szurubooru\DatabaseConnection $databaseConnection)
{
$pdo = $databaseConnection->getPDO();
$pdo->exec('
CREATE TABLE tags2
(
id INTEGER PRIMARY KEY NOT NULL,
name TEXT UNIQUE NOT NULL,
usages INTEGER NOT NULL DEFAULT 0
)');
$pdo->exec('INSERT INTO tags2(name, usages) SELECT name, (SELECT COUNT(1) FROM postTags WHERE tagName = tags.name) FROM tags');
$pdo->exec('DROP TABLE tags');
$pdo->exec('ALTER TABLE tags2 RENAME TO tags');
$pdo->exec('
CREATE TABLE postTags2
(
postId INTEGER NOT NULL,
tagId INTEGER NOT NULL,
PRIMARY KEY (postId, tagId)
)');
$pdo->exec('INSERT INTO postTags2(postId, tagId) SELECT postId, (SELECT tags.id FROM tags WHERE tags.name = postTags.tagName) FROM postTags');
$pdo->exec('DROP TABLE postTags');
$pdo->exec('ALTER TABLE postTags2 RENAME TO postTags');
}
}

View file

@ -20,6 +20,7 @@ return [
$container->get(\Szurubooru\Upgrades\Upgrade02::class),
$container->get(\Szurubooru\Upgrades\Upgrade03::class),
$container->get(\Szurubooru\Upgrades\Upgrade04::class),
$container->get(\Szurubooru\Upgrades\Upgrade05::class),
];
}),

View file

@ -24,4 +24,28 @@ abstract class AbstractDatabaseTestCase extends \Szurubooru\Tests\AbstractTestCa
if ($this->databaseConnection)
$this->databaseConnection->close();
}
protected function assertEntitiesEqual($expected, $actual)
{
if (!is_array($expected))
{
$expected = [$expected];
$actual = [$actual];
}
$this->assertEquals(count($expected), count($actual));
$this->assertEquals(array_keys($expected), array_keys($actual));
foreach (array_keys($expected) as $key)
{
if ($expected[$key] === null)
{
$this->assertNull($actual[$key]);
}
else
{
$expected[$key]->resetLazyLoaders();
$actual[$key]->resetLazyLoaders();
$this->assertEquals($expected[$key], $actual[$key]);
}
}
}
}

View file

@ -3,12 +3,14 @@ namespace Szurubooru\Tests\Dao;
final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
{
private $tagDao;
private $fileServiceMock;
private $thumbnailServiceMock;
public function setUp()
{
parent::setUp();
$this->tagDao = new \Szurubooru\Dao\TagDao($this->databaseConnection);
$this->fileServiceMock = $this->mock(\Szurubooru\Services\FileService::class);
$this->thumbnailServiceMock = $this->mock(\Szurubooru\Services\ThumbnailService::class);
}
@ -46,15 +48,13 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
$postDao->save($post2);
$actual = $postDao->findAll();
foreach ($actual as $post)
$post->resetLazyLoaders();
$expected = [
$post1->getId() => $post1,
$post2->getId() => $post2,
];
$this->assertEquals($expected, $actual);
$this->assertEntitiesEqual($expected, $actual);
}
public function testGettingById()
@ -68,10 +68,8 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
$actualPost1 = $postDao->findById($post1->getId());
$actualPost2 = $postDao->findById($post2->getId());
$actualPost1->resetLazyLoaders();
$actualPost2->resetLazyLoaders();
$this->assertEquals($post1, $actualPost1);
$this->assertEquals($post2, $actualPost2);
$this->assertEntitiesEqual($post1, $actualPost1);
$this->assertEntitiesEqual($post2, $actualPost2);
}
public function testDeletingAll()
@ -87,8 +85,8 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
$actualPost1 = $postDao->findById($post1->getId());
$actualPost2 = $postDao->findById($post2->getId());
$this->assertEquals(null, $actualPost1);
$this->assertEquals(null, $actualPost2);
$this->assertNull($actualPost1);
$this->assertNull($actualPost2);
$this->assertEquals(0, count($postDao->findAll()));
}
@ -105,22 +103,27 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
$actualPost1 = $postDao->findById($post1->getId());
$actualPost2 = $postDao->findById($post2->getId());
$this->assertEquals(null, $actualPost1);
$this->assertEquals($actualPost2, $actualPost2);
$this->assertNull($actualPost1);
$this->assertEntitiesEqual($actualPost2, $actualPost2);
$this->assertEquals(1, count($postDao->findAll()));
}
public function testSavingTags()
{
$testTags = ['tag1', 'tag2'];
$tag1 = new \Szurubooru\Entities\Tag();
$tag1->setName('tag1');
$tag2 = new \Szurubooru\Entities\Tag();
$tag2->setName('tag2');
$testTags = ['tag1' => $tag1, 'tag2' => $tag2];
$postDao = $this->getPostDao();
$post = $this->getPost();
$post->setTags($testTags);
$postDao->save($post);
$savedPost = $postDao->findById($post->getId());
$this->assertEquals($testTags, $post->getTags());
$this->assertEquals($post->getTags(), $savedPost->getTags());
$this->assertEntitiesEqual($testTags, $post->getTags());
$this->assertEquals(2, count($savedPost->getTags()));
$tagDao = $this->getTagDao();
$this->assertEquals(2, count($tagDao->findAll()));
@ -199,13 +202,14 @@ final class PostDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
{
return new \Szurubooru\Dao\PostDao(
$this->databaseConnection,
$this->tagDao,
$this->fileServiceMock,
$this->thumbnailServiceMock);
}
private function getTagDao()
{
return new \Szurubooru\Dao\TagDao($this->databaseConnection);
return $this->tagDao;
}
private function getPost()

View file

@ -80,7 +80,9 @@ class UserSearchServiceTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
$actual = $userSearchService->getFiltered($searchFilter);
foreach ($actual->entities as $entity)
$entity->resetLazyLoaders();
$this->assertEquals($expected, $actual);
$this->assertEquals($expected->filter, $actual->filter);
$this->assertEquals($expected->totalRecords, $actual->totalRecords);
$this->assertEntitiesEqual($expected->entities, $actual->entities);
}
private function getUserSearchService()

36
tests/Dao/TagDaoTest.php Normal file
View file

@ -0,0 +1,36 @@
<?php
namespace Szurubooru\Tests\Dao;
final class TagDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
{
public function testFindByPostIds()
{
$pdo = $this->databaseConnection->getPDO();
$transactionManager = new \Szurubooru\Dao\TransactionManager($this->databaseConnection);
$transactionManager->commit(function() use ($pdo)
{
$pdo->exec('INSERT INTO tags(id, name) VALUES (1, \'test1\')');
$pdo->exec('INSERT INTO tags(id, name) VALUES (2, \'test2\')');
$pdo->exec('INSERT INTO postTags(postId, tagId) VALUES (5, 1)');
$pdo->exec('INSERT INTO postTags(postId, tagId) VALUES (6, 1)');
$pdo->exec('INSERT INTO postTags(postId, tagId) VALUES (5, 2)');
$pdo->exec('INSERT INTO postTags(postId, tagId) VALUES (6, 2)');
});
$tag1 = new \Szurubooru\Entities\Tag(1);
$tag1->setName('test1');
$tag2 = new \Szurubooru\Entities\Tag(2);
$tag2->setName('test2');
$expected = [
$tag1->getId() => $tag1,
$tag2->getId() => $tag2,
];
$tagDao = $this->getTagDao();
$actual = $tagDao->findByPostId(5);
$this->assertEntitiesEqual($expected, $actual);
}
private function getTagDao()
{
return new \Szurubooru\Dao\TagDao($this->databaseConnection);
}
}

View file

@ -15,7 +15,7 @@ final class TokenDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
$expected = $token;
$actual = $tokenDao->findByName($token->getName());
$this->assertEquals($actual, $expected);
$this->assertEntitiesEqual($actual, $expected);
}
public function testRetrievingByInvalidName()

View file

@ -16,7 +16,7 @@ class TransactionManagerTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
});
$this->assertNotNull($testEntity->getId());
$this->assertEquals($testEntity, $testDao->findById($testEntity->getId()));
$this->assertEntitiesEqual($testEntity, $testDao->findById($testEntity->getId()));
}
public function testRollback()
@ -54,7 +54,7 @@ class TransactionManagerTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
});
$this->assertNotNull($testEntity->getId());
$this->assertEquals($testEntity, $testDao->findById($testEntity->getId()));
$this->assertEntitiesEqual($testEntity, $testDao->findById($testEntity->getId()));
}
private function getTestEntity()

View file

@ -23,8 +23,7 @@ final class UserDaoTest extends \Szurubooru\Tests\AbstractDatabaseTestCase
$expected = $user;
$actual = $userDao->findByName($user->getName());
$actual->resetLazyLoaders();
$this->assertEquals($actual, $expected);
$this->assertEntitiesEqual($actual, $expected);
}
public function testRetrievingByInvalidName()

View file

@ -49,7 +49,9 @@ class PostServiceTest extends \Szurubooru\Tests\AbstractTestCase
$this->assertNull($savedPost->getImageHeight());
$this->assertEquals($formData->url, $savedPost->getOriginalFileName());
$this->assertNull($savedPost->getOriginalFileSize());
$this->assertEquals(['test', 'test2'], $savedPost->getTags());
$this->assertEquals(2, count($savedPost->getTags()));
$this->assertEquals('test', $savedPost->getTags()[0]->getName());
$this->assertEquals('test2', $savedPost->getTags()[1]->getName());
}
public function testCreatingPosts()