Added tag relations database interface

This commit is contained in:
Marcin Kurczewski 2014-10-15 16:00:03 +02:00
parent 850e496215
commit 0bd0589e32
6 changed files with 184 additions and 11 deletions

21
TODO
View file

@ -28,16 +28,17 @@ everything related to tags:
- tag editing - tag editing
- category (from config.ini) - category (from config.ini)
- description - description
- implications - when entering child tag, add parent tag - relations
- take care of recursion - handle tag relations editing in frontend
- listing - privileges
- sort alphabetically - template
- sort by time of addition - ajax
- adding - handle tag relations editing in backend
- removing - tagservice::update
- editing - tageditformdata
- source tag - tagcontroller
- target tags (allow multiple) - privileges
- handle relations in autocomplete
refactors: refactors:
- add enum validation in IValidatables (needs refactors of enums and - add enum validation in IValidatables (needs refactors of enums and

View file

@ -3,9 +3,14 @@ namespace Szurubooru\Dao;
use Szurubooru\Dao\EntityConverters\PostEntityConverter; use Szurubooru\Dao\EntityConverters\PostEntityConverter;
use Szurubooru\Dao\EntityConverters\TagEntityConverter; use Szurubooru\Dao\EntityConverters\TagEntityConverter;
use Szurubooru\DatabaseConnection; use Szurubooru\DatabaseConnection;
use Szurubooru\Entities\Entity;
use Szurubooru\Entities\Tag;
class TagDao extends AbstractDao implements ICrudDao class TagDao extends AbstractDao implements ICrudDao
{ {
const TAG_RELATION_IMPLICATION = 1;
const TAG_RELATION_SUGGESTION = 2;
public function __construct(DatabaseConnection $databaseConnection) public function __construct(DatabaseConnection $databaseConnection)
{ {
parent::__construct( parent::__construct(
@ -49,7 +54,7 @@ class TagDao extends AbstractDao implements ICrudDao
->disableSmartJoin() ->disableSmartJoin()
->innerJoin('postTags pt1 ON pt1.tagId = tags.id') ->innerJoin('postTags pt1 ON pt1.tagId = tags.id')
->innerJoin('postTags pt2 ON pt2.postId = pt1.postId') ->innerJoin('postTags pt2 ON pt2.postId = pt1.postId')
->where('pt2.tagId = ?', $tagId) ->where('pt2.tagId', $tagId)
->groupBy('tags.id') ->groupBy('tags.id')
->orderBy('tags.usages DESC, name ASC'); ->orderBy('tags.usages DESC, name ASC');
$arrayEntities = iterator_to_array($query); $arrayEntities = iterator_to_array($query);
@ -60,4 +65,87 @@ class TagDao extends AbstractDao implements ICrudDao
{ {
$this->deleteBy('usages', 0); $this->deleteBy('usages', 0);
} }
protected function afterLoad(Entity $tag)
{
$tag->setLazyLoader(
Tag::LAZY_LOADER_IMPLIED_TAGS,
function (Tag $tag)
{
return $this->findImpliedTags($tag);
});
$tag->setLazyLoader(
Tag::LAZY_LOADER_SUGGESTED_TAGS,
function (Tag $tag)
{
return $this->findSuggested($tag);
});
}
protected function afterSave(Entity $tag)
{
$this->syncImpliedTags($tag);
$this->syncSuggestedTags($tag);
}
private function findImpliedTags(Tag $tag)
{
return $this->findRelatedTagsByType($tag, self::TAG_RELATION_IMPLICATION);
}
private function findSuggested(Tag $tag)
{
return $this->findRelatedTagsByType($tag, self::TAG_RELATION_SUGGESTION);
}
private function syncImpliedTags($tag)
{
$this->syncRelatedTagsByType($tag, $tag->getImpliedTags(), self::TAG_RELATION_IMPLICATION);
}
private function syncSuggestedTags($tag)
{
$this->syncRelatedTagsByType($tag, $tag->getSuggestedTags(), self::TAG_RELATION_SUGGESTION);
}
private function syncRelatedTagsByType(Tag $tag, array $relatedTags, $type)
{
$this->fpdo->deleteFrom('tagRelations')
->where('tag1id', $tag->getId())
->where('type', $type)
->execute();
$relatedTagIds = array_filter(array_unique(array_map(
function ($tag)
{
if (!$tag->getId())
throw new \RuntimeException('Unsaved entities found');
return $tag->getId();
},
$relatedTags)));
foreach ($relatedTagIds as $tagId)
{
$this->fpdo
->insertInto('tagRelations')
->values([
'tag1id' => $tag->getId(),
'tag2id' => $tagId,
'type' => $type])
->execute();
}
}
private function findRelatedTagsByType(Tag $tag, $type)
{
$tagId = $tag->getId();
$query = $this->fpdo->from($this->tableName)
->disableSmartJoin()
->innerJoin('tagRelations tr ON tags.id = tr.tag2id')
->where('tr.type', $type)
->where('tr.tag1id', $tagId);
$arrayEntities = iterator_to_array($query);
return $this->arrayToEntities($arrayEntities);
}
} }

View file

@ -7,6 +7,9 @@ final class Tag extends Entity
private $creationTime; private $creationTime;
private $banned = false; private $banned = false;
const LAZY_LOADER_IMPLIED_TAGS = 'implications';
const LAZY_LOADER_SUGGESTED_TAGS = 'suggestions';
const META_USAGES = 'usages'; const META_USAGES = 'usages';
public function getName() public function getName()
@ -44,4 +47,23 @@ final class Tag extends Entity
return $this->getMeta(self::META_USAGES); return $this->getMeta(self::META_USAGES);
} }
public function getImpliedTags()
{
return $this->lazyLoad(self::LAZY_LOADER_IMPLIED_TAGS, []);
}
public function setImpliedTags(array $impliedTags)
{
$this->lazySave(self::LAZY_LOADER_IMPLIED_TAGS, $impliedTags);
}
public function getSuggestedTags()
{
return $this->lazyLoad(self::LAZY_LOADER_SUGGESTED_TAGS, []);
}
public function setSuggestedTags(array $suggestedTags)
{
$this->lazySave(self::LAZY_LOADER_SUGGESTED_TAGS, $suggestedTags);
}
} }

View file

@ -0,0 +1,22 @@
<?php
namespace Szurubooru\Upgrades;
use Szurubooru\DatabaseConnection;
use Szurubooru\Services\TagService;
class Upgrade24 implements IUpgrade
{
public function run(DatabaseConnection $databaseConnection)
{
$pdo = $databaseConnection->getPDO();
$driver = $databaseConnection->getDriver();
$pdo->exec(
'CREATE TABLE tagRelations (
id INTEGER PRIMARY KEY ' . ($driver === 'mysql' ? 'AUTO_INCREMENT' : 'AUTOINCREMENT') . ',
tag1id INTEGER NOT NULL,
tag2id INTEGER NOT NULL,
type INTEGER(2) NOT NULL,
UNIQUE (tag1id, tag2id, type)
)');
}
}

View file

@ -39,6 +39,7 @@ return [
$container->get(\Szurubooru\Upgrades\Upgrade21::class), $container->get(\Szurubooru\Upgrades\Upgrade21::class),
$container->get(\Szurubooru\Upgrades\Upgrade22::class), $container->get(\Szurubooru\Upgrades\Upgrade22::class),
$container->get(\Szurubooru\Upgrades\Upgrade23::class), $container->get(\Szurubooru\Upgrades\Upgrade23::class),
$container->get(\Szurubooru\Upgrades\Upgrade24::class),
]; ];
}), }),

View file

@ -25,6 +25,45 @@ final class TagDaoTest extends AbstractDatabaseTestCase
$this->assertEntitiesEqual($tag, $actualTag); $this->assertEntitiesEqual($tag, $actualTag);
} }
public function testSavingRelations()
{
$tag1 = new Tag();
$tag1->setName('test 1');
$tag1->setCreationTime(date('c'));
$tag2 = new Tag();
$tag2->setName('test 2');
$tag2->setCreationTime(date('c'));
$tag3 = new Tag();
$tag3->setName('test 3');
$tag3->setCreationTime(date('c'));
$tag4 = new Tag();
$tag4->setName('test 4');
$tag4->setCreationTime(date('c'));
$tagDao = $this->getTagDao();
$tagDao->save($tag1);
$tagDao->save($tag2);
$tagDao->save($tag3);
$tagDao->save($tag4);
$tag = new Tag();
$tag->setName('test1');
$tag->setCreationTime(date('c'));
$tag->setImpliedTags([$tag1, $tag3]);
$tag->setSuggestedTags([$tag2, $tag4]);
$this->assertGreaterThan(0, count($tag->getImpliedTags()));
$this->assertGreaterThan(0, count($tag->getSuggestedTags()));
$tagDao->save($tag);
$actualTag = $tagDao->findById($tag->getId());
$this->assertEntitiesEqual($tag, $actualTag);
$this->assertEntitiesEqual(array_values($tag->getImpliedTags()), array_values($actualTag->getImpliedTags()));
$this->assertEntitiesEqual(array_values($tag->getSuggestedTags()), array_values($actualTag->getSuggestedTags()));
$this->assertGreaterThan(0, count($actualTag->getImpliedTags()));
$this->assertGreaterThan(0, count($actualTag->getSuggestedTags()));
}
public function testFindByPostIds() public function testFindByPostIds()
{ {
$pdo = $this->databaseConnection->getPDO(); $pdo = $this->databaseConnection->getPDO();