From 22f8a408fa127818856f2e071c5e83a43ab672c0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 26 Nov 2015 23:45:04 +0000 Subject: [PATCH] Added indexes, Reduced queries on pages --- .gitignore | 3 +- app/Book.php | 10 -- app/Chapter.php | 3 +- app/Http/Controllers/BookController.php | 3 +- app/Http/Controllers/ChapterController.php | 3 +- app/Http/Controllers/PageController.php | 3 +- app/Page.php | 4 +- app/Repos/BookRepo.php | 24 +++ app/User.php | 33 ++-- composer.json | 3 +- composer.lock | 145 ++++++++++++++++-- config/app.php | 2 + database/factories/ModelFactory.php | 3 + .../2015_11_26_221857_add_entity_indexes.php | 89 +++++++++++ database/seeds/DummyContentSeeder.php | 29 ++++ resources/views/books/show.blade.php | 4 +- resources/views/chapters/show.blade.php | 2 +- resources/views/pages/show.blade.php | 2 +- .../views/pages/sidebar-tree-list.blade.php | 2 +- 19 files changed, 319 insertions(+), 48 deletions(-) create mode 100644 database/migrations/2015_11_26_221857_add_entity_indexes.php create mode 100644 database/seeds/DummyContentSeeder.php diff --git a/.gitignore b/.gitignore index 832184a61..ca7858438 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ Homestead.yaml /public/bower /public/build /storage/images -_ide_helper.php \ No newline at end of file +_ide_helper.php +/storage/debugbar \ No newline at end of file diff --git a/app/Book.php b/app/Book.php index 66ac883ef..de1841459 100644 --- a/app/Book.php +++ b/app/Book.php @@ -27,16 +27,6 @@ class Book extends Entity return $this->hasMany('BookStack\Chapter'); } - public function children() - { - $pages = $this->pages()->where('chapter_id', '=', 0)->get(); - $chapters = $this->chapters()->get(); - foreach($chapters as $chapter) { - $pages->push($chapter); - } - return $pages->sortBy('priority'); - } - public function getExcerpt($length = 100) { return strlen($this->description) > $length ? substr($this->description, 0, $length-3) . '...' : $this->description; diff --git a/app/Chapter.php b/app/Chapter.php index ce131f314..c3001b69b 100644 --- a/app/Chapter.php +++ b/app/Chapter.php @@ -18,7 +18,8 @@ class Chapter extends Entity public function getUrl() { - return '/books/' . $this->book->slug . '/chapter/' . $this->slug; + $bookSlug = isset($this->bookSlug) ? $this->bookSlug : $this->book->slug; + return '/books/' . $bookSlug. '/chapter/' . $this->slug; } public function getExcerpt($length = 100) diff --git a/app/Http/Controllers/BookController.php b/app/Http/Controllers/BookController.php index 682fc5415..c4173730d 100644 --- a/app/Http/Controllers/BookController.php +++ b/app/Http/Controllers/BookController.php @@ -89,7 +89,8 @@ class BookController extends Controller { $book = $this->bookRepo->getBySlug($slug); Views::add($book); - return view('books/show', ['book' => $book, 'current' => $book]); + $bookChildren = $this->bookRepo->getChildren($book); + return view('books/show', ['book' => $book, 'current' => $book, 'bookChildren' => $bookChildren]); } /** diff --git a/app/Http/Controllers/ChapterController.php b/app/Http/Controllers/ChapterController.php index 0aa1b6ee2..42ae11355 100644 --- a/app/Http/Controllers/ChapterController.php +++ b/app/Http/Controllers/ChapterController.php @@ -80,8 +80,9 @@ class ChapterController extends Controller { $book = $this->bookRepo->getBySlug($bookSlug); $chapter = $this->chapterRepo->getBySlug($chapterSlug, $book->id); + $sidebarTree = $this->bookRepo->getChildren($book); Views::add($chapter); - return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter]); + return view('chapters/show', ['book' => $book, 'chapter' => $chapter, 'current' => $chapter, 'sidebarTree' => $sidebarTree]); } /** diff --git a/app/Http/Controllers/PageController.php b/app/Http/Controllers/PageController.php index e061cea1b..d9d53178e 100644 --- a/app/Http/Controllers/PageController.php +++ b/app/Http/Controllers/PageController.php @@ -87,8 +87,9 @@ class PageController extends Controller { $book = $this->bookRepo->getBySlug($bookSlug); $page = $this->pageRepo->getBySlug($pageSlug, $book->id); + $sidebarTree = $this->bookRepo->getChildren($book); Views::add($page); - return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page]); + return view('pages/show', ['page' => $page, 'book' => $book, 'current' => $page, 'sidebarTree' => $sidebarTree]); } /** diff --git a/app/Page.php b/app/Page.php index 25bb22f8a..feedb1eae 100644 --- a/app/Page.php +++ b/app/Page.php @@ -40,7 +40,9 @@ class Page extends Entity public function getUrl() { - return '/books/' . $this->book->slug . '/page/' . $this->slug; + // TODO - Extract this and share with chapters + $bookSlug = $this->getAttribute('bookSlug') ? $this->getAttribute('bookSlug') : $this->book->slug; + return '/books/' . $bookSlug . '/page/' . $this->slug; } public function getExcerpt($length = 100) diff --git a/app/Repos/BookRepo.php b/app/Repos/BookRepo.php index b7189aaf0..112971a27 100644 --- a/app/Repos/BookRepo.php +++ b/app/Repos/BookRepo.php @@ -178,6 +178,30 @@ class BookRepo return $slug; } + /** + * Get all child objects of a book. + * Returns a sorted collection of Pages and Chapters. + * Loads the bookslug onto child elements to prevent access database access for getting the slug. + * @param Book $book + * @return mixed + */ + public function getChildren(Book $book) + { + $pages = $book->pages()->where('chapter_id', '=', 0)->get(); + $chapters = $book->chapters()->with('pages')->get(); + $children = $pages->merge($chapters); + $bookSlug = $book->slug; + $children->each(function ($child) use ($bookSlug) { + $child->setAttribute('bookSlug', $bookSlug); + if ($child->isA('chapter')) { + $child->pages->each(function ($page) use ($bookSlug) { + $page->setAttribute('bookSlug', $bookSlug); + }); + } + }); + return $children->sortBy('priority'); + } + /** * Get books by search term. * @param $term diff --git a/app/User.php b/app/User.php index ffe41229b..570789f37 100644 --- a/app/User.php +++ b/app/User.php @@ -33,6 +33,12 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ protected $hidden = ['password', 'remember_token']; + /** + * This holds the user's permissions when loaded. + * @var array + */ + protected $permissions; + /** * Returns a default guest user. */ @@ -40,7 +46,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon { return new static([ 'email' => 'guest', - 'name' => 'Guest' + 'name' => 'Guest' ]); } @@ -58,7 +64,19 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon public function getRoleAttribute() { - return $this->roles()->first(); + return $this->roles()->with('permissions')->first(); + } + + /** + * Loads the user's permissions from thier role. + */ + private function loadPermissions() + { + if (isset($this->permissions)) return; + $this->load('roles.permissions'); + $permissions = $this->roles[0]->permissions; + $permissionsArray = $permissions->pluck('name')->all(); + $this->permissions = $permissionsArray; } /** @@ -68,14 +86,11 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ public function can($permissionName) { - if($this->email == 'guest') { + if ($this->email == 'guest') { return false; } - $permissions = $this->role->permissions()->get(); - $permissionSearch = $permissions->search(function ($item, $key) use ($permissionName) { - return $item->name == $permissionName; - }); - return $permissionSearch !== false; + $this->loadPermissions(); + return array_search($permissionName, $this->permissions) !== false; } /** @@ -114,7 +129,7 @@ class User extends Model implements AuthenticatableContract, CanResetPasswordCon */ public function hasSocialAccount($socialDriver = false) { - if($socialDriver === false) { + if ($socialDriver === false) { return $this->socialAccounts()->count() > 0; } diff --git a/composer.json b/composer.json index 19bf5b08f..d64fc4b28 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,8 @@ "laravel/framework": "5.1.*", "intervention/image": "^2.3", "barryvdh/laravel-ide-helper": "^2.1", - "laravel/socialite": "^2.0" + "laravel/socialite": "^2.0", + "barryvdh/laravel-debugbar": "^2.0" }, "require-dev": { "fzaninotto/faker": "~1.4", diff --git a/composer.lock b/composer.lock index e847bcb4a..485ee353c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,63 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "7d7e80e9f1c13417c35195f79e41c038", + "hash": "c216d0bcb72b4f2008d7fa8067da2f77", + "content-hash": "3c421ae5b8e5c11792249142cb96ff25", "packages": [ + { + "name": "barryvdh/laravel-debugbar", + "version": "v2.0.6", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-debugbar.git", + "reference": "23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c", + "reference": "23672cbbe78278ff1fdb258caa0b1de7b5d0bc0c", + "shasum": "" + }, + "require": { + "illuminate/support": "~5.0.17|5.1.*", + "maximebf/debugbar": "~1.10.2", + "php": ">=5.4.0", + "symfony/finder": "~2.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\Debugbar\\": "src/" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "PHP Debugbar integration for Laravel", + "keywords": [ + "debug", + "debugbar", + "laravel", + "profiler", + "webprofiler" + ], + "time": "2015-09-09 11:39:27" + }, { "name": "barryvdh/laravel-ide-helper", "version": "v2.1.0", @@ -1078,6 +1133,62 @@ ], "time": "2015-08-22 09:49:14" }, + { + "name": "maximebf/debugbar", + "version": "v1.10.5", + "source": { + "type": "git", + "url": "https://github.com/maximebf/php-debugbar.git", + "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/30e53e8a28284b69dd223c9f5ee8957befd72636", + "reference": "30e53e8a28284b69dd223c9f5ee8957befd72636", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0", + "symfony/var-dumper": "~2.6" + }, + "require-dev": { + "phpunit/phpunit": "~4.0" + }, + "suggest": { + "kriswallsmith/assetic": "The best way to manage assets", + "monolog/monolog": "Log using Monolog", + "predis/predis": "Redis storage" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.10-dev" + } + }, + "autoload": { + "psr-0": { + "DebugBar": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Maxime Bouroumeau-Fuseau", + "email": "maxime.bouroumeau@gmail.com", + "homepage": "http://maximebf.com" + } + ], + "description": "Debug bar in the browser for php application", + "homepage": "https://github.com/maximebf/php-debugbar", + "keywords": [ + "debug" + ], + "time": "2015-10-19 20:35:12" + }, { "name": "monolog/monolog", "version": "1.16.0", @@ -1556,12 +1667,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/ClassLoader.git", + "url": "https://github.com/symfony/class-loader.git", "reference": "2fccbc544997340808801a7410cdcb96dd12edc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/2fccbc544997340808801a7410cdcb96dd12edc4", + "url": "https://api.github.com/repos/symfony/class-loader/zipball/2fccbc544997340808801a7410cdcb96dd12edc4", "reference": "2fccbc544997340808801a7410cdcb96dd12edc4", "shasum": "" }, @@ -1606,12 +1717,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/Console.git", + "url": "https://github.com/symfony/console.git", "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e", + "url": "https://api.github.com/repos/symfony/console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e", "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e", "shasum": "" }, @@ -1663,12 +1774,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/CssSelector.git", + "url": "https://github.com/symfony/css-selector.git", "reference": "0b5c07b516226b7dd32afbbc82fe547a469c5092" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/CssSelector/zipball/0b5c07b516226b7dd32afbbc82fe547a469c5092", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/0b5c07b516226b7dd32afbbc82fe547a469c5092", "reference": "0b5c07b516226b7dd32afbbc82fe547a469c5092", "shasum": "" }, @@ -1716,12 +1827,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/Debug.git", + "url": "https://github.com/symfony/debug.git", "reference": "9daa1bf9f7e615fa2fba30357e479a90141222e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/Debug/zipball/9daa1bf9f7e615fa2fba30357e479a90141222e3", + "url": "https://api.github.com/repos/symfony/debug/zipball/9daa1bf9f7e615fa2fba30357e479a90141222e3", "reference": "9daa1bf9f7e615fa2fba30357e479a90141222e3", "shasum": "" }, @@ -1776,12 +1887,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/DomCrawler.git", + "url": "https://github.com/symfony/dom-crawler.git", "reference": "9dabece63182e95c42b06967a0d929a5df78bc35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/9dabece63182e95c42b06967a0d929a5df78bc35", + "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/9dabece63182e95c42b06967a0d929a5df78bc35", "reference": "9dabece63182e95c42b06967a0d929a5df78bc35", "shasum": "" }, @@ -1829,12 +1940,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/EventDispatcher.git", + "url": "https://github.com/symfony/event-dispatcher.git", "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/9310b5f9a87ec2ea75d20fec0b0017c77c66dac3", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9310b5f9a87ec2ea75d20fec0b0017c77c66dac3", "reference": "9310b5f9a87ec2ea75d20fec0b0017c77c66dac3", "shasum": "" }, @@ -1936,12 +2047,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/HttpFoundation.git", + "url": "https://github.com/symfony/http-foundation.git", "reference": "863af6898081b34c65d42100c370b9f3c51b70ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/863af6898081b34c65d42100c370b9f3c51b70ca", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/863af6898081b34c65d42100c370b9f3c51b70ca", "reference": "863af6898081b34c65d42100c370b9f3c51b70ca", "shasum": "" }, @@ -1989,12 +2100,12 @@ "version": "v2.7.3", "source": { "type": "git", - "url": "https://github.com/symfony/HttpKernel.git", + "url": "https://github.com/symfony/http-kernel.git", "reference": "405d3e7a59ff7a28ec469441326a0ac79065ea98" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/405d3e7a59ff7a28ec469441326a0ac79065ea98", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/405d3e7a59ff7a28ec469441326a0ac79065ea98", "reference": "405d3e7a59ff7a28ec469441326a0ac79065ea98", "shasum": "" }, diff --git a/config/app.php b/config/app.php index 196a5c19b..aa7c0b561 100644 --- a/config/app.php +++ b/config/app.php @@ -143,6 +143,7 @@ return [ */ Intervention\Image\ImageServiceProvider::class, Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider::class, + Barryvdh\Debugbar\ServiceProvider::class, /* @@ -207,6 +208,7 @@ return [ */ 'ImageTool' => Intervention\Image\Facades\Image::class, + 'Debugbar' => Barryvdh\Debugbar\Facade::class, /** * Custom diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index 6a1ab049c..31c7a3716 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -23,6 +23,7 @@ $factory->define(BookStack\User::class, function ($faker) { $factory->define(BookStack\Book::class, function ($faker) { return [ 'name' => $faker->sentence, + 'slug' => str_random(10), 'description' => $faker->paragraph ]; }); @@ -30,6 +31,7 @@ $factory->define(BookStack\Book::class, function ($faker) { $factory->define(BookStack\Chapter::class, function ($faker) { return [ 'name' => $faker->sentence, + 'slug' => str_random(10), 'description' => $faker->paragraph ]; }); @@ -37,6 +39,7 @@ $factory->define(BookStack\Chapter::class, function ($faker) { $factory->define(BookStack\Page::class, function ($faker) { return [ 'name' => $faker->sentence, + 'slug' => str_random(10), 'html' => '

' . implode('

', $faker->paragraphs(5)) . '

' ]; }); diff --git a/database/migrations/2015_11_26_221857_add_entity_indexes.php b/database/migrations/2015_11_26_221857_add_entity_indexes.php new file mode 100644 index 000000000..4e68dd68e --- /dev/null +++ b/database/migrations/2015_11_26_221857_add_entity_indexes.php @@ -0,0 +1,89 @@ +index('slug'); + $table->index('created_by'); + $table->index('updated_by'); + }); + Schema::table('pages', function (Blueprint $table) { + $table->index('slug'); + $table->index('book_id'); + $table->index('chapter_id'); + $table->index('priority'); + $table->index('created_by'); + $table->index('updated_by'); + }); + Schema::table('page_revisions', function (Blueprint $table) { + $table->index('page_id'); + }); + Schema::table('chapters', function (Blueprint $table) { + $table->index('slug'); + $table->index('book_id'); + $table->index('priority'); + $table->index('created_by'); + $table->index('updated_by'); + }); + Schema::table('activities', function (Blueprint $table) { + $table->index('book_id'); + $table->index('user_id'); + $table->index('entity_id'); + }); + Schema::table('views', function (Blueprint $table) { + $table->index('user_id'); + $table->index('viewable_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('books', function (Blueprint $table) { + $table->dropIndex('slug'); + $table->dropIndex('created_by'); + $table->dropIndex('updated_by'); + }); + Schema::table('pages', function (Blueprint $table) { + $table->dropIndex('slug'); + $table->dropIndex('book_id'); + $table->dropIndex('chapter_id'); + $table->dropIndex('priority'); + $table->dropIndex('created_by'); + $table->dropIndex('updated_by'); + }); + Schema::table('page_revisions', function (Blueprint $table) { + $table->dropIndex('page_id'); + }); + Schema::table('chapters', function (Blueprint $table) { + $table->dropIndex('slug'); + $table->dropIndex('book_id'); + $table->dropIndex('priority'); + $table->dropIndex('created_by'); + $table->dropIndex('updated_by'); + }); + Schema::table('activities', function (Blueprint $table) { + $table->dropIndex('book_id'); + $table->dropIndex('user_id'); + $table->dropIndex('entity_id'); + }); + Schema::table('views', function (Blueprint $table) { + $table->dropIndex('user_id'); + $table->dropIndex('entity_id'); + }); + } +} diff --git a/database/seeds/DummyContentSeeder.php b/database/seeds/DummyContentSeeder.php new file mode 100644 index 000000000..d8ab91d22 --- /dev/null +++ b/database/seeds/DummyContentSeeder.php @@ -0,0 +1,29 @@ +create(); + $role = \BookStack\Role::where('name', '=', 'admin')->first(); + $user->attachRole($role); + + + $books = factory(BookStack\Book::class, 20)->create(['created_by' => $user->id, 'updated_by' => $user->id]) + ->each(function($book) use ($user) { + $chapters = factory(BookStack\Chapter::class, 5)->create(['created_by' => $user->id, 'updated_by' => $user->id]) + ->each(function($chapter) use ($user, $book){ + $pages = factory(\BookStack\Page::class, 10)->make(['created_by' => $user->id, 'updated_by' => $user->id, 'book_id' => $book->id]); + $chapter->pages()->saveMany($pages); + }); + $book->chapters()->saveMany($chapters); + }); + } +} diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 0485ccc15..1b6009504 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -37,8 +37,8 @@

- @if(count($book->children()) > 0) - @foreach($book->children() as $childElement) + @if(count($bookChildren) > 0) + @foreach($bookChildren as $childElement) @if($childElement->isA('chapter')) @include('chapters/list-item', ['chapter' => $childElement]) @else diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 60965d0f6..75ae6bf65 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -60,7 +60,7 @@

- @include('pages/sidebar-tree-list', ['book' => $book]) + @include('pages/sidebar-tree-list', ['book' => $book, 'sidebarTree' => $sidebarTree])
diff --git a/resources/views/pages/show.blade.php b/resources/views/pages/show.blade.php index 937d52be1..2a0fcc14d 100644 --- a/resources/views/pages/show.blade.php +++ b/resources/views/pages/show.blade.php @@ -60,7 +60,7 @@ diff --git a/resources/views/pages/sidebar-tree-list.blade.php b/resources/views/pages/sidebar-tree-list.blade.php index 9338cbab8..76d3a7614 100644 --- a/resources/views/pages/sidebar-tree-list.blade.php +++ b/resources/views/pages/sidebar-tree-list.blade.php @@ -3,7 +3,7 @@
Book Navigation