Improved model performance a little bit

This commit is contained in:
Marcin Kurczewski 2014-05-09 17:54:52 +02:00
parent 8ee80ea170
commit 343268d029
16 changed files with 196 additions and 163 deletions

View file

@ -9,7 +9,22 @@ abstract class AbstractCrudModel implements IModel
public static function spawn()
{
$entityClassName = static::getEntityClassName();
return new $entityClassName();
$entity = new $entityClassName(new static);
$entity->fillNew();
return $entity;
}
public static function spawnFromDatabaseRows($input)
{
return array_map([get_called_class(), 'spawnFromDatabaseRow'], $input);
}
public static function spawnFromDatabaseRow($row)
{
$entityClassName = static::getEntityClassName();
$entity = new $entityClassName(new static);
$entity->fillFromDatabase($row);
return $entity;
}
public static function remove($entities)
@ -40,7 +55,7 @@ abstract class AbstractCrudModel implements IModel
$row = Database::fetchOne($stmt);
return $row
? static::convertRow($row)
? static::spawnFromDatabaseRow($row)
: null;
}
@ -53,7 +68,7 @@ abstract class AbstractCrudModel implements IModel
$rows = Database::fetchAll($stmt);
if ($rows)
return static::convertRows($rows);
return static::spawnFromDatabaseRows($rows);
return [];
}
@ -76,55 +91,6 @@ abstract class AbstractCrudModel implements IModel
return $entityClassName;
}
public static function convertRow($row)
{
$entity = static::spawn();
//todo: force this to be implemented by children
//instead of providing clumsy generic solution
if (isset($row['id']))
$row['id'] = (int) $row['id'];
foreach ($row as $key => $val)
{
if (isset(self::$keyCache[$key]))
{
$key = self::$keyCache[$key];
}
else
{
$key = self::$keyCache[$key] = TextCaseConverter::convert($key,
TextCaseConverter::SNAKE_CASE,
TextCaseConverter::LOWER_CAMEL_CASE);
}
if (property_exists($entity, $key))
{
$reflectionProperty = new ReflectionProperty(get_class($entity), $key);
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue($entity, $val);
}
else
{
$entity->$key = $val;
}
}
return $entity;
}
public static function convertRows(array $rows)
{
$entities = [];
foreach ($rows as $i => $row)
{
$entities[$i] = static::convertRow($row);
}
return $entities;
}
public static function forgeId($entity)
{
$table = static::getTableName();

View file

@ -9,16 +9,10 @@ final class CommentModel extends AbstractCrudModel
return 'comment';
}
public static function spawn()
{
$comment = new CommentEntity;
$comment->setCreationTime(time());
return $comment;
}
public static function save($comment)
{
$comment->validate();
$comment->getPost()->removeCache('comment_count');
Database::transaction(function() use ($comment)
{
@ -47,6 +41,7 @@ final class CommentModel extends AbstractCrudModel
{
Database::transaction(function() use ($comment)
{
$comment->getPost()->removeCache('comment_count');
$stmt = new Sql\DeleteStatement();
$stmt->setTable('comment');
$stmt->setCriterion(new Sql\EqualsFunctor('id', new Sql\Binding($comment->getId())));
@ -65,7 +60,7 @@ final class CommentModel extends AbstractCrudModel
$rows = Database::fetchAll($stmt);
if ($rows)
return self::convertRows($rows);
return self::spawnFromDatabaseRows($rows);
return [];
}

View file

@ -1,9 +1,18 @@
<?php
abstract class AbstractEntity implements IValidatable
{
protected $model;
protected $id;
protected $__cache = [];
public abstract function fillNew();
public abstract function fillFromDatabase($row);
public function __construct($model)
{
$this->model = $model;
}
public function getId()
{
return $this->id;
@ -14,16 +23,6 @@ abstract class AbstractEntity implements IValidatable
$this->id = $id;
}
public function resetCache()
{
$this->__cache = [];
}
public function setCache($key, $value)
{
$this->__cache[$key] = $value;
}
public function getCache($key)
{
return isset($this->__cache[$key])
@ -31,8 +30,37 @@ abstract class AbstractEntity implements IValidatable
: null;
}
public function setCache($key, $value)
{
$this->__cache[$key] = $value;
}
public function removeCache($key)
{
unset($this->__cache[$key]);
}
public function resetCache()
{
$this->__cache = [];
}
public function hasCache($key)
{
return isset($this->__cache[$key]);
}
protected function getColumnWithCache($columnName)
{
if ($this->hasCache($columnName))
return $this->getCache($columnName);
$stmt = new \Chibi\Sql\SelectStatement();
$stmt->setTable($this->model->getTableName());
$stmt->setColumn($columnName);
$stmt->setCriterion(new \Chibi\Sql\EqualsFunctor('id', new \Chibi\Sql\Binding($this->getId())));
$value = \Chibi\Database::fetchOne($stmt)[$columnName];
$this->setCache($columnName, $value);
return $value;
}
}

View file

@ -1,10 +1,24 @@
<?php
final class CommentEntity extends AbstractEntity implements IValidatable
{
protected $text;
protected $postId;
protected $commentDate;
protected $commenterId;
private $text;
private $postId;
private $commentDate;
private $commenterId;
public function fillNew()
{
$this->commentDate = time();
}
public function fillFromDatabase($row)
{
$this->id = (int) $row['id'];
$this->text = $row['text'];
$this->postId = (int) $row['post_id'];
$this->commentDate = $row['comment_date'];
$this->commenterId = (int) $row['commenter_id'];
}
public function validate()
{

View file

@ -2,7 +2,7 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class PostEntity extends AbstractEntity implements IValidatable
final class PostEntity extends AbstractEntity implements IValidatable
{
protected $type;
protected $name;
@ -18,9 +18,38 @@ class PostEntity extends AbstractEntity implements IValidatable
protected $uploaderId;
protected $source;
protected $commentCount = 0;
protected $favCount = 0;
protected $score = 0;
public function fillNew()
{
$this->setSafety(new PostSafety(PostSafety::Safe));
$this->setHidden(false);
$this->setCreationTime(time());
do
{
$this->setName(md5(mt_rand() . uniqid()));
}
while (file_exists($this->getFullPath()));
}
public function fillFromDatabase($row)
{
$this->id = (int) $row['id'];
$this->name = $row['name'];
$this->origName = $row['orig_name'];
$this->fileHash = $row['file_hash'];
$this->fileSize = (int) $row['file_size'];
$this->mimeType = $row['mime_type'];
$this->hidden = (bool) $row['hidden'];
$this->uploadDate = $row['upload_date'];
$this->imageWidth = (int) $row['image_width'];
$this->imageHeight = (int) $row['image_height'];
$this->uploaderId = (int) $row['uploader_id'];
$this->source = $row['source'];
$this->setCache('comment_count', $row['comment_count']);
$this->setCache('fav_count', $row['fav_count']);
$this->setCache('score', $row['score']);
$this->setType(new PostType($row['type']));
$this->setSafety(new PostSafety($row['safety']));
}
public function validate()
{
@ -80,24 +109,24 @@ class PostEntity extends AbstractEntity implements IValidatable
$stmt->addInnerJoin('favoritee', new Sql\EqualsFunctor('favoritee.user_id', 'user.id'));
$stmt->setCriterion(new Sql\EqualsFunctor('favoritee.post_id', new Sql\Binding($this->getId())));
$rows = Database::fetchAll($stmt);
$favorites = UserModel::convertRows($rows);
$favorites = UserModel::spawnFromDatabaseRows($rows);
$this->setCache('favoritee', $favorites);
return $favorites;
}
public function getScore()
{
return $this->score;
return (int) $this->getColumnWithCache('score');
}
public function getCommentCount()
{
return $this->commentCount;
return (int) $this->getColumnWithCache('comment_count');
}
public function getFavoriteCount()
{
return $this->favCount;
return (int) $this->getColumnWithCache('fav_count');
}
public function getRelations()
@ -119,7 +148,7 @@ class PostEntity extends AbstractEntity implements IValidatable
->add(new Sql\EqualsFunctor('post.id', 'crossref.post_id'))
->add(new Sql\EqualsFunctor('crossref.post2_id', $binding))));
$rows = Database::fetchAll($stmt);
$posts = PostModel::convertRows($rows);
$posts = PostModel::spawnFromDatabaseRows($rows);
$this->setCache('relations', $posts);
return $posts;
}

View file

@ -2,9 +2,22 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class TagEntity extends AbstractEntity implements IValidatable
final class TagEntity extends AbstractEntity implements IValidatable
{
protected $name;
private $name;
public function fillNew()
{
}
public function fillFromDatabase($row)
{
$this->id = (int) $row['id'];
$this->name = $row['name'];
if (isset($row['post_count']))
$this->setCache('post_count', (int) $row['post_count']);
}
public function validate()
{

View file

@ -1,10 +1,23 @@
<?php
class TokenEntity extends AbstractEntity implements IValidatable
final class TokenEntity extends AbstractEntity implements IValidatable
{
protected $userId;
protected $token;
protected $used;
protected $expires;
private $userId;
private $token;
private $used;
private $expires;
public function fillNew()
{
}
public function fillFromDatabase($row)
{
$this->id = (int) $row['id'];
$this->userId = (int) $row['user_id'];
$this->token = $row['token'];
$this->used = (bool) $row['used'];
$this->expires = $row['expires'];
}
public function validate()
{

View file

@ -2,7 +2,7 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class UserEntity extends AbstractEntity implements IValidatable
final class UserEntity extends AbstractEntity implements IValidatable
{
protected $name;
protected $passSalt;
@ -19,6 +19,28 @@ class UserEntity extends AbstractEntity implements IValidatable
protected $__passwordChanged = false;
protected $__password;
public function fillNew()
{
$this->setAccessRank(new AccessRank(AccessRank::Anonymous));
$this->setPasswordSalt(md5(mt_rand() . uniqid()));
}
public function fillFromDatabase($row)
{
$this->id = (int) $row['id'];
$this->name = $row['name'];
$this->passSalt = $row['pass_salt'];
$this->passHash = $row['pass_hash'];
$this->staffConfirmed = $row['staff_confirmed'];
$this->emailUnconfirmed = $row['email_unconfirmed'];
$this->emailConfirmed = $row['email_confirmed'];
$this->joinDate = $row['join_date'];
$this->lastLoginDate = $row['last_login_date'];
$this->settings = $row['settings'];
$this->banned = $row['banned'];
$this->setAccessRank(new AccessRank($row['access_rank']));
}
public function validate()
{
$this->validateUserName();

View file

@ -2,40 +2,13 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class PostModel extends AbstractCrudModel
final class PostModel extends AbstractCrudModel
{
public static function getTableName()
{
return 'post';
}
public static function convertRow($row)
{
$entity = parent::convertRow($row);
if (isset($row['type']))
$entity->setType(new PostType($row['type']));
if (isset($row['safety']))
$entity->setSafety(new PostSafety($row['safety']));
return $entity;
}
public static function spawn()
{
$post = new PostEntity;
$post->setSafety(new PostSafety(PostSafety::Safe));
$post->setHidden(false);
$post->setCreationTime(time());
do
{
$post->setName(md5(mt_rand() . uniqid()));
}
while (file_exists($post->getFullPath()));
return $post;
}
public static function save($post)
{
$post->validate();
@ -162,7 +135,7 @@ class PostModel extends AbstractCrudModel
$row = Database::fetchOne($stmt);
return $row
? self::convertRow($row)
? self::spawnFromDatabaseRow($row)
: null;
}
@ -192,7 +165,7 @@ class PostModel extends AbstractCrudModel
$row = Database::fetchOne($stmt);
return $row
? self::convertRow($row)
? self::spawnFromDatabaseRow($row)
: null;
}
@ -224,8 +197,7 @@ class PostModel extends AbstractCrudModel
{
if (isset($comments[$row['id']]))
continue;
unset($row['post_id']);
$comment = CommentModel::convertRow($row);
$comment = CommentModel::spawnFromDatabaseRow($row);
$comments[$row['id']] = $comment;
}
@ -267,7 +239,7 @@ class PostModel extends AbstractCrudModel
if (isset($tags[$row['id']]))
continue;
unset($row['post_id']);
$tag = TagModel::convertRow($row);
$tag = TagModel::spawnFromDatabaseRow($row);
$tags[$row['id']] = $tag;
}

View file

@ -2,7 +2,7 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class PropertyModel implements IModel
final class PropertyModel implements IModel
{
const FeaturedPostId = 0;
const FeaturedPostUserName = 1;

View file

@ -56,7 +56,7 @@ abstract class AbstractSearchService
{
$modelClassName = self::getModelClassName();
$rows = static::getEntitiesRows($searchQuery, $perPage, $page);
return $modelClassName::convertRows($rows);
return $modelClassName::spawnFromDatabaseRows($rows);
}
public static function getEntityCount($searchQuery)

View file

@ -62,7 +62,7 @@ class TagSearchService extends AbstractSearchService
usort($rows, function($a, $b) { return intval($b['sort']) - intval($a['sort']); });
return TagModel::convertRows($rows);
return TagModel::spawnFromDatabaseRows($rows);
}
public static function getMostUsedTag()
@ -74,6 +74,6 @@ class TagSearchService extends AbstractSearchService
->setGroupBy('post_tag.tag_id')
->setOrderBy('post_count', Sql\SelectStatement::ORDER_DESC)
->setLimit(1, 0);
return TagModel::convertRow(Database::fetchOne($stmt));
return TagModel::spawnFromDatabaseRow(Database::fetchOne($stmt));
}
}

View file

@ -2,21 +2,13 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class TagModel extends AbstractCrudModel
final class TagModel extends AbstractCrudModel
{
public static function getTableName()
{
return 'tag';
}
public static function convertRow($row)
{
$entity = parent::convertRow($row);
if (isset($row['post_count']))
$entity->setCache('post_count', $row['post_count']);
return $entity;
}
public static function save($tag)
{
$tag->validate();
@ -126,7 +118,7 @@ class TagModel extends AbstractCrudModel
$rows = Database::fetchAll($stmt);
if ($rows)
return self::convertRows($rows);
return self::spawnFromDatabaseRows($rows);
return [];
}
@ -147,7 +139,7 @@ class TagModel extends AbstractCrudModel
$row = Database::fetchOne($stmt);
return $row
? self::convertRow($row)
? self::spawnFromDatabaseRow($row)
: null;
}

View file

@ -2,7 +2,7 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class TokenModel extends AbstractCrudModel
final class TokenModel extends AbstractCrudModel
{
public static function getTableName()
{
@ -57,7 +57,7 @@ class TokenModel extends AbstractCrudModel
$row = Database::fetchOne($stmt);
return $row
? self::convertRow($row)
? self::spawnFromDatabaseRow($row)
: null;
}

View file

@ -2,7 +2,7 @@
use \Chibi\Sql as Sql;
use \Chibi\Database as Database;
class UserModel extends AbstractCrudModel
final class UserModel extends AbstractCrudModel
{
const SETTING_SAFETY = 1;
const SETTING_ENDLESS_SCROLLING = 2;
@ -14,24 +14,6 @@ class UserModel extends AbstractCrudModel
return 'user';
}
public static function convertRow($row)
{
$entity = parent::convertRow($row);
if (isset($row['access_rank']))
$entity->setAccessRank(new AccessRank($row['access_rank']));
return $entity;
}
public static function spawn()
{
$user = new UserEntity();
$user->setAccessRank(new AccessRank(AccessRank::Anonymous));
$user->setPasswordSalt(md5(mt_rand() . uniqid()));
return $user;
}
public static function save($user)
{
$user->validate();
@ -118,7 +100,7 @@ class UserModel extends AbstractCrudModel
$row = Database::fetchOne($stmt);
return $row
? self::convertRow($row)
? self::spawnFromDatabaseRow($row)
: null;
}
@ -144,7 +126,7 @@ class UserModel extends AbstractCrudModel
$row = Database::fetchOne($stmt);
return $row
? self::convertRow($row)
? self::spawnFromDatabaseRow($row)
: null;
}
@ -154,6 +136,7 @@ class UserModel extends AbstractCrudModel
{
Database::transaction(function() use ($user, $post, $score)
{
$post->removeCache('score');
$stmt = new Sql\DeleteStatement();
$stmt->setTable('post_score');
$stmt->setCriterion((new Sql\ConjunctionFunctor)
@ -177,6 +160,7 @@ class UserModel extends AbstractCrudModel
{
Database::transaction(function() use ($user, $post)
{
$post->removeCache('fav_count');
self::removeFromUserFavorites($user, $post);
$stmt = new Sql\InsertStatement();
$stmt->setTable('favoritee');
@ -191,6 +175,7 @@ class UserModel extends AbstractCrudModel
{
Database::transaction(function() use ($user, $post)
{
$post->removeCache('fav_count');
$stmt = new Sql\DeleteStatement();
$stmt->setTable('favoritee');
$stmt->setCriterion((new Sql\ConjunctionFunctor)

View file

@ -18,12 +18,16 @@ class ListCommentJobTest extends AbstractTest
$this->assert->areEqual(0, CommentModel::getCount());
$this->mockComment($this->mockUser());
$comment = $this->mockComment($this->mockUser());
$ret = $this->runApi(1);
$this->assert->areEqual(1, count($ret->entities));
$post = $ret->entities[0];
$newComment = $post->getComments()[0];
$this->assert->areEqual($comment->getPostId(), $newComment->getPostId());
$this->assert->areEqual($comment->getPost()->getId(), $newComment->getPost()->getId());
$samePost = $this->assert->doesNotThrow(function() use ($post)
{
return PostModel::getById($post->getId());