From 343268d0298a326143004dfb9e13ee67f51d090e Mon Sep 17 00:00:00 2001 From: Marcin Kurczewski Date: Fri, 9 May 2014 17:54:52 +0200 Subject: [PATCH] Improved model performance a little bit --- src/Models/AbstractCrudModel.php | 70 +++++-------------- src/Models/CommentModel.php | 11 +-- src/Models/Entities/AbstractEntity.php | 48 ++++++++++--- src/Models/Entities/CommentEntity.php | 22 ++++-- src/Models/Entities/PostEntity.php | 47 ++++++++++--- src/Models/Entities/TagEntity.php | 17 ++++- src/Models/Entities/TokenEntity.php | 23 ++++-- src/Models/Entities/UserEntity.php | 24 ++++++- src/Models/PostModel.php | 38 ++-------- src/Models/PropertyModel.php | 2 +- .../SearchServices/AbstractSearchService.php | 2 +- .../SearchServices/TagSearchService.php | 4 +- src/Models/TagModel.php | 14 +--- src/Models/TokenModel.php | 4 +- src/Models/UserModel.php | 27 ++----- tests/JobTests/ListCommentsJobTest.php | 6 +- 16 files changed, 196 insertions(+), 163 deletions(-) diff --git a/src/Models/AbstractCrudModel.php b/src/Models/AbstractCrudModel.php index 2afd85fa..50e603c3 100644 --- a/src/Models/AbstractCrudModel.php +++ b/src/Models/AbstractCrudModel.php @@ -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(); diff --git a/src/Models/CommentModel.php b/src/Models/CommentModel.php index 9b378d7c..d3a75e04 100644 --- a/src/Models/CommentModel.php +++ b/src/Models/CommentModel.php @@ -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 []; } diff --git a/src/Models/Entities/AbstractEntity.php b/src/Models/Entities/AbstractEntity.php index 61b41d09..fc0cba6d 100644 --- a/src/Models/Entities/AbstractEntity.php +++ b/src/Models/Entities/AbstractEntity.php @@ -1,9 +1,18 @@ 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; + } } diff --git a/src/Models/Entities/CommentEntity.php b/src/Models/Entities/CommentEntity.php index 3b9e9d4c..56e7d7f5 100644 --- a/src/Models/Entities/CommentEntity.php +++ b/src/Models/Entities/CommentEntity.php @@ -1,10 +1,24 @@ 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() { diff --git a/src/Models/Entities/PostEntity.php b/src/Models/Entities/PostEntity.php index 37d217ae..839b9862 100644 --- a/src/Models/Entities/PostEntity.php +++ b/src/Models/Entities/PostEntity.php @@ -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; } diff --git a/src/Models/Entities/TagEntity.php b/src/Models/Entities/TagEntity.php index b9d69792..54f37bf1 100644 --- a/src/Models/Entities/TagEntity.php +++ b/src/Models/Entities/TagEntity.php @@ -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() { diff --git a/src/Models/Entities/TokenEntity.php b/src/Models/Entities/TokenEntity.php index 37d31449..5d11fee2 100644 --- a/src/Models/Entities/TokenEntity.php +++ b/src/Models/Entities/TokenEntity.php @@ -1,10 +1,23 @@ 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() { diff --git a/src/Models/Entities/UserEntity.php b/src/Models/Entities/UserEntity.php index 2cb7fde0..86ceb1d9 100644 --- a/src/Models/Entities/UserEntity.php +++ b/src/Models/Entities/UserEntity.php @@ -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(); diff --git a/src/Models/PostModel.php b/src/Models/PostModel.php index 62a33ef5..af81a424 100644 --- a/src/Models/PostModel.php +++ b/src/Models/PostModel.php @@ -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; } diff --git a/src/Models/PropertyModel.php b/src/Models/PropertyModel.php index 5484871c..2da4e4ad 100644 --- a/src/Models/PropertyModel.php +++ b/src/Models/PropertyModel.php @@ -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; diff --git a/src/Models/SearchServices/AbstractSearchService.php b/src/Models/SearchServices/AbstractSearchService.php index fcea3c9c..29af6c89 100644 --- a/src/Models/SearchServices/AbstractSearchService.php +++ b/src/Models/SearchServices/AbstractSearchService.php @@ -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) diff --git a/src/Models/SearchServices/TagSearchService.php b/src/Models/SearchServices/TagSearchService.php index 4c447c6b..ea7f18a3 100644 --- a/src/Models/SearchServices/TagSearchService.php +++ b/src/Models/SearchServices/TagSearchService.php @@ -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)); } } diff --git a/src/Models/TagModel.php b/src/Models/TagModel.php index f8b0925e..dd821226 100644 --- a/src/Models/TagModel.php +++ b/src/Models/TagModel.php @@ -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; } diff --git a/src/Models/TokenModel.php b/src/Models/TokenModel.php index 6e13476b..b579ac36 100644 --- a/src/Models/TokenModel.php +++ b/src/Models/TokenModel.php @@ -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; } diff --git a/src/Models/UserModel.php b/src/Models/UserModel.php index 39f1f7c4..b9a5758e 100644 --- a/src/Models/UserModel.php +++ b/src/Models/UserModel.php @@ -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) diff --git a/tests/JobTests/ListCommentsJobTest.php b/tests/JobTests/ListCommentsJobTest.php index c94ad58a..9d59b8f1 100644 --- a/tests/JobTests/ListCommentsJobTest.php +++ b/tests/JobTests/ListCommentsJobTest.php @@ -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());