From a2384f4b3556a61c316acc5490ed0413a34bf107 Mon Sep 17 00:00:00 2001 From: James Collins Date: Sat, 29 Jul 2023 23:04:14 +1000 Subject: [PATCH] caching --- app/Conductors/ArticleConductor.php | 23 +++++--- app/Conductors/Conductor.php | 24 ++++++--- app/Conductors/EventConductor.php | 12 +++-- app/Conductors/MediaConductor.php | 7 ++- .../Controllers/Api/ArticleController.php | 6 +-- app/Http/Controllers/Api/EventController.php | 6 +-- app/Models/Attachment.php | 24 +++++---- app/Models/Gallery.php | 53 ++++++++++++++++++ app/Models/Media.php | 36 ++++++++++--- app/Models/User.php | 54 ++++++++++++++++--- app/Traits/HasAttachments.php | 45 ++++++++++++++-- app/Traits/HasGallery.php | 14 +++++ 12 files changed, 252 insertions(+), 52 deletions(-) diff --git a/app/Conductors/ArticleConductor.php b/app/Conductors/ArticleConductor.php index f97acb3..a1a10ea 100644 --- a/app/Conductors/ArticleConductor.php +++ b/app/Conductors/ArticleConductor.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\InvalidCastException; use Illuminate\Database\Eloquent\MissingAttributeException; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; use LogicException; class ArticleConductor extends Conductor @@ -123,8 +124,8 @@ class ArticleConductor extends Conductor */ public function includeAttachments(Model $model) { - return $model->attachments()->get()->map(function ($attachment) { - return MediaConductor::includeModel(request(), 'attachments', $attachment->media); + return $model->getAttachments()->map(function ($attachment) { + return MediaConductor::includeModel(request(), 'attachments', $attachment->getMedia()); }); } @@ -136,8 +137,8 @@ class ArticleConductor extends Conductor */ public function includeGallery(Model $model) { - return $model->gallery()->get()->map(function ($item) { - return MediaConductor::includeModel(request(), 'gallery', $item->media); + return $model->getGallery()->map(function ($item) { + return MediaConductor::includeModel(request(), 'gallery', $item->getMedia()); }); } @@ -149,7 +150,12 @@ class ArticleConductor extends Conductor */ public function includeUser(Model $model) { - return UserConductor::includeModel(request(), 'user', User::find($model['user_id'])); + $cacheKey = "user:{$model['user_id']}"; + $user = Cache::remember($cacheKey, now()->addDays(28), function () use ($model) { + return User::find($model['user_id']); + }); + + return UserConductor::includeModel(request(), 'user', $user); } /** @@ -160,6 +166,11 @@ class ArticleConductor extends Conductor */ public function transformHero(mixed $value): array|null { - return MediaConductor::includeModel(request(), 'hero', Media::find($value)); + $cacheKey = "media:{$value}"; + $media = Cache::remember($cacheKey, now()->addDays(28), function () use ($value) { + return Media::find($value); + }); + + return MediaConductor::includeModel(request(), 'hero', $media); } } diff --git a/app/Conductors/Conductor.php b/app/Conductors/Conductor.php index 01c034e..27d6b40 100644 --- a/app/Conductors/Conductor.php +++ b/app/Conductors/Conductor.php @@ -6,6 +6,7 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Str; class Conductor @@ -446,7 +447,7 @@ class Conductor return substr($field, 0, strpos($field, '.')); } return $field; - }, explode(',', $request->input('fields'))); + }, explode(',', $request->input('fields', ''))); if ($limitFields === null) { $limitFields = $fields; } else { @@ -531,6 +532,7 @@ class Conductor * @param Builder $query The custom query. * @param Request $request The request. * @param array|null $limitFields Limit the request to these fields. + * @return Builder */ public static function filterQuery(Builder $query, Request $request, array|null $limitFields = null): Builder { @@ -890,9 +892,11 @@ class Conductor * Run a scope query on the collection before anything else. * * @param Builder $builder The builder in use. + * @return void */ public function scope(Builder $builder): void { + // empty } /** @@ -903,12 +907,18 @@ class Conductor */ public function fields(Model $model): array { - $visibleFields = $model->getVisible(); - if (empty($visibleFields) === true) { - $visibleFields = $model->getConnection() - ->getSchemaBuilder() - ->getColumnListing($model->getTable()); - } + $visibleFields = Cache::remember("model:{$model->getTable()}:visible", now()->addDays(28), function () use ($model) { + $fields = $model->getVisible(); + if (empty($fields) === true) { + $fields = Cache::remember("schema:{$model->getTable()}:columns", now()->addDays(28), function () use ($model) { + return $model->getConnection() + ->getSchemaBuilder() + ->getColumnListing($model->getTable()); + }); + } + + return $fields; + }); $appends = $model->getAppends(); if (is_array($appends) === true) { diff --git a/app/Conductors/EventConductor.php b/app/Conductors/EventConductor.php index 1ed0756..1f3d442 100644 --- a/app/Conductors/EventConductor.php +++ b/app/Conductors/EventConductor.php @@ -7,6 +7,7 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\InvalidCastException; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Cache; class EventConductor extends Conductor { @@ -107,9 +108,9 @@ class EventConductor extends Conductor { $user = auth()->user(); - return $model->attachments()->get()->map(function ($attachment) use ($user) { + return $model->getAttachments()->map(function ($attachment) use ($user) { if ($attachment->private === false || ($user !== null && $user->hasPermission('admin/events') === true)) { - return MediaConductor::includeModel(request(), 'attachments', $attachment->media); + return MediaConductor::includeModel(request(), 'attachments', $attachment->getMedia()); } }); } @@ -122,6 +123,11 @@ class EventConductor extends Conductor */ public function transformHero(mixed $value): array|null { - return MediaConductor::includeModel(request(), 'hero', Media::find($value)); + $cacheKey = "media:{$value}"; + $media = Cache::remember($cacheKey, now()->addDays(28), function () use ($value) { + return Media::find($value); + }); + + return MediaConductor::includeModel(request(), 'hero', $media); } } diff --git a/app/Conductors/MediaConductor.php b/app/Conductors/MediaConductor.php index 9c1855a..f266445 100644 --- a/app/Conductors/MediaConductor.php +++ b/app/Conductors/MediaConductor.php @@ -5,6 +5,7 @@ namespace App\Conductors; use App\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Facades\Cache; class MediaConductor extends Conductor { @@ -143,6 +144,10 @@ class MediaConductor extends Conductor */ public function includeUser(Model $model) { - return UserConductor::includeModel(request(), 'user', User::find($model['user_id'])); + $user = Cache::remember("user:{$model['user_id']}", now()->addDays(28), function () use ($model) { + return User::find($model['user_id']); + }); + + return UserConductor::includeModel(request(), 'user', $user); } } diff --git a/app/Http/Controllers/Api/ArticleController.php b/app/Http/Controllers/Api/ArticleController.php index 53fedcc..dd98184 100644 --- a/app/Http/Controllers/Api/ArticleController.php +++ b/app/Http/Controllers/Api/ArticleController.php @@ -78,7 +78,7 @@ class ArticleController extends ApiController $article = Article::create($request->except(['attachments', 'gallery'])); if ($request->has('attachments') === true) { - $article->attachmentsAddMany($request->get('attachments')); + $article->addAttachments($request->get('attachments')); } if ($request->has('gallery') === true) { @@ -105,8 +105,8 @@ class ArticleController extends ApiController { if (ArticleConductor::updatable($article) === true) { if ($request->has('attachments') === true) { - $article->attachments()->delete(); - $article->attachmentsAddMany($request->get('attachments')); + $article->deleteAttachments(); + $article->addAttachments($request->get('attachments')); } if ($request->has('gallery') === true) { diff --git a/app/Http/Controllers/Api/EventController.php b/app/Http/Controllers/Api/EventController.php index 62e5adc..34644be 100644 --- a/app/Http/Controllers/Api/EventController.php +++ b/app/Http/Controllers/Api/EventController.php @@ -70,7 +70,7 @@ class EventController extends ApiController $event = Event::create($request->except(['attachments'])); if ($request->has('attachments') === true) { - $event->attachmentsAddMany($request->get('attachments')); + $event->addAttachments($request->get('attachments')); } return $this->respondAsResource( @@ -93,8 +93,8 @@ class EventController extends ApiController { if (EventConductor::updatable($event) === true) { if ($request->has('attachments') === true) { - $event->attachments()->delete(); - $event->attachmentsAddMany($request->get('attachments')); + $event->deleteAttachments(); + $event->addAttachments($request->get('attachments')); } $event->update($request->except(['attachments'])); diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index b273abc..7bf6574 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -5,7 +5,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Support\Facades\Cache; class Attachment extends Model { @@ -31,16 +31,6 @@ class Attachment extends Model ]; - /** - * Get attachments attachable - * - * @return MorphTo - */ - public function attachable(): MorphTo - { - return $this->morphTo(); - } - /** * Get the media for this attachment. * @@ -50,4 +40,16 @@ class Attachment extends Model { return $this->belongsTo(Media::class); } + + /** + * Get associated Media object. + * + * @return null|Media + */ + public function getMedia(): ?Media + { + return Cache::remember("attachment:{$this->id}:media", now()->addDays(28), function () { + return $this->media()->first(); + }); + } } diff --git a/app/Models/Gallery.php b/app/Models/Gallery.php index fb0aae6..835a6f2 100644 --- a/app/Models/Gallery.php +++ b/app/Models/Gallery.php @@ -3,10 +3,12 @@ namespace App\Models; use App\Traits\Uuids; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; +use Illuminate\Support\Facades\Cache; class Gallery extends Model { @@ -23,6 +25,26 @@ class Gallery extends Model ]; + /** + * Boot the model. + * + * @return void + */ + protected static function boot(): void + { + parent::boot(); + + $clearCache = function ($gallery) { + $cacheKeys = [ + "gallery:{$gallery->id}:media", + ]; + Cache::forget($cacheKeys); + }; + + static::saving($clearCache); + static::deleting($clearCache); + } + /** * Get gallery addendum model. * @@ -42,4 +64,35 @@ class Gallery extends Model { return $this->belongsTo(Media::class); } + + /** + * Get the media for this item. + * + * @return null|Media The media model. + */ + public function getMedia(): ?Media + { + $mediaId = '0'; + $media = null; + + if (Cache::has("gallery:{$this->id}:media") === true) { + $mediaId = Cache::get("gallery:{$this->id}:media"); + } else { + $media = $this->media()->first(); + if ($media === null) { + return null; + } + + $mediaId = $media->id; + Cache::put("gallery:{$this->id}:media", $mediaId, now()->addDays(28)); + } + + return Cache::remember("media:{$mediaId}", now()->addDays(28), function () use ($media) { + if ($media !== null) { + return $media; + } + + return $this->media()->first(); + }); + } } diff --git a/app/Models/Media.php b/app/Models/Media.php index 5089893..6d73956 100644 --- a/app/Models/Media.php +++ b/app/Models/Media.php @@ -14,6 +14,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Foundation\Bus\DispatchesJobs; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Queue; @@ -101,12 +102,23 @@ class Media extends Model /** * Model Boot + * + * @return void */ protected static function boot(): void { parent::boot(); - static::updating(function ($media) { + $clearCache = function ($media) { + $cacheKeys = [ + "media:{$media->id}", + ]; + Cache::forget($cacheKeys); + }; + + static::updating(function ($media) use ($clearCache) { + $clearCache($media); + if (array_key_exists('permission', $media->getChanges()) === true) { $origPermission = $media->getOriginal()['permission']; $newPermission = $media->permission; @@ -123,12 +135,12 @@ class Media extends Model } }); - static::deleting(function ($media) { + static::deleting(function ($media) use ($clearCache) { + $clearCache($media); $media->deleteFile(); }); } - /** * Get Type Variants. * @@ -163,6 +175,7 @@ class Media extends Model * Variants Set Mutator. * * @param mixed $value The value to mutate. + * @return void */ public function setVariantsAttribute(mixed $value): void { @@ -249,11 +262,10 @@ class Media extends Model return ''; } - - - /** * Delete file and associated files with the modal. + * + * @return void */ public function deleteFile(): void { @@ -275,7 +287,7 @@ class Media extends Model /** * Invalidate Cloudflare Cache. * - * @throws InvalidArgumentException Exception. + * @return void */ private function invalidateCFCache(): void { @@ -306,6 +318,8 @@ class Media extends Model /** * Get URL path + * + * @return string */ public function getUrlPath(): string { @@ -315,6 +329,8 @@ class Media extends Model /** * Return the file URL + * + * @return string */ public function getUrlAttribute(): string { @@ -327,6 +343,8 @@ class Media extends Model /** * Return the file owner + * + * @return BelongsTo */ public function user(): BelongsTo { @@ -337,6 +355,7 @@ class Media extends Model * Move files to new storage device. * * @param string $storage The storage ID to move to. + * @return void */ public function moveToStorage(string $storage): void { @@ -482,6 +501,8 @@ class Media extends Model /** * Get the server maximum upload size + * + * @return integer */ public static function getMaxUploadSize(): int { @@ -606,6 +627,7 @@ class Media extends Model * Sanitize fileName for upload * * @param string $fileName Filename to sanitize. + * @return string */ private static function sanitizeFilename(string $fileName): string { diff --git a/app/Models/User.php b/app/Models/User.php index 484e98c..557a3b2 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Cache; use Laravel\Sanctum\HasApiTokens; use OwenIt\Auditing\Contracts\Auditable; @@ -80,13 +81,37 @@ class User extends Authenticatable implements Auditable /** - * Get the list of files of the user + * Boot the model. * - * @return Illuminate\Database\Eloquent\Relations\HasMany + * @return void */ - public function permissions(): HasMany + protected static function boot(): void { - return $this->hasMany(Permission::class); + parent::boot(); + + $clearCache = function ($user) { + $cacheKeys = [ + "user:{$user->id}", + "user:{$user->id}:permissions", + ]; + Cache::forget($cacheKeys); + }; + + static::saving($clearCache); + static::deleting($clearCache); + } + + /** + * Get the list of permissions of the user + * + * @return Illuminate\Database\Eloquent\Collection + */ + public function permissions(): array + { + $cacheKey = "user:{$this->id}:permissions"; + return Cache::remember($cacheKey, now()->addDays(28), function () { + return $this->hasMany(Permission::class)->pluck('permission')->toArray(); + }); } /** @@ -96,7 +121,7 @@ class User extends Authenticatable implements Auditable */ public function getPermissionsAttribute(): array { - return $this->permissions()->pluck('permission')->toArray(); + return $this->permissions(); } /** @@ -107,7 +132,7 @@ class User extends Authenticatable implements Auditable */ public function hasPermission(string $permission): bool { - return ($this->permissions()->where('permission', $permission)->first() !== null); + return in_array($permission, $this->permissions()); } /** @@ -131,6 +156,9 @@ class User extends Authenticatable implements Auditable return $existingPermissions->contains('permission', $permission['permission']); }); + $cacheKey = "user:{$this->id}:permissions"; + Cache::forget($cacheKey); + return $this->permissions()->createMany($newPermissions->toArray()); } @@ -139,6 +167,7 @@ class User extends Authenticatable implements Auditable * Revoke permissions from the user * * @param string|array $permissions The permission(s) to revoke. + * @return integer */ public function revokePermission($permissions): int { @@ -146,6 +175,9 @@ class User extends Authenticatable implements Auditable $permissions = [$permissions]; } + $cacheKey = "user:{$this->id}:permissions"; + Cache::forget($cacheKey); + return $this->permissions() ->whereIn('permission', $permissions) ->delete(); @@ -153,6 +185,8 @@ class User extends Authenticatable implements Auditable /** * Get the list of files of the user + * + * @return HasMany */ public function media(): HasMany { @@ -161,6 +195,8 @@ class User extends Authenticatable implements Auditable /** * Get the list of files of the user + * + * @return HasMany */ public function articles(): HasMany { @@ -169,6 +205,8 @@ class User extends Authenticatable implements Auditable /** * Get associated user codes + * + * @return HasMany */ public function codes(): HasMany { @@ -177,6 +215,8 @@ class User extends Authenticatable implements Auditable /** * Get the list of logins of the user + * + * @return HasMany */ public function logins(): HasMany { @@ -185,6 +225,8 @@ class User extends Authenticatable implements Auditable /** * Get the events associated with the user. + * + * @return BelongsToMany */ public function events(): BelongsToMany { diff --git a/app/Traits/HasAttachments.php b/app/Traits/HasAttachments.php index e13eeec..a86a644 100644 --- a/app/Traits/HasAttachments.php +++ b/app/Traits/HasAttachments.php @@ -3,7 +3,9 @@ namespace App\Traits; use App\Models\Media; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Support\Facades\Cache; trait HasAttachments { @@ -15,7 +17,19 @@ trait HasAttachments protected static function bootHasAttachments(): void { static::deleting(function ($model) { - $model->attachments()->delete(); + $model->deleteAttachments(); + }); + } + + /** + * Get the attachments associated to this item. + * + * @return Collection + */ + public function getAttachments(): Collection + { + return Cache::remember($this->cacheKey(), now()->addDays(28), function () { + return $this->attachments()->get(); }); } @@ -27,14 +41,14 @@ trait HasAttachments * @param boolean $allowDuplicates Whether to allow duplicate media IDs or not. * @return void */ - public function attachmentsAddMany(array|string $ids, string $delimiter = ',', bool $allowDuplicates = false): void + public function addAttachments(array|string $ids, string $delimiter = ',', bool $allowDuplicates = false): void { if (is_array($ids) === false) { $ids = explode($delimiter, $ids); } $ids = array_map('trim', $ids); - $existingIds = $this->attachments()->pluck('media_id')->toArray(); + $existingIds = $this->attachmentsGet()->pluck('media_id')->toArray(); $attachmentItems = []; foreach ($ids as $id) { @@ -42,15 +56,26 @@ trait HasAttachments continue; } - $media = Media::find($id); - if ($media !== null) { + if (Media::where('id', $id)->exists() === true) { $attachmentItems[] = ['media_id' => $id]; } } + Cache::forget($this->cacheKey()); $this->attachments()->createMany($attachmentItems); } + /** + * Delete associated attachments. + * + * @return void + */ + public function deleteAttachments(): void + { + Cache::forget($this->cacheKey()); + $this->morphMany(\App\Models\Attachment::class, 'addendum')->delete(); + } + /** * Get the article's attachments. * @@ -60,4 +85,14 @@ trait HasAttachments { return $this->morphMany(\App\Models\Attachment::class, 'addendum'); } + + /** + * Return the attachment cache key. + * + * @return string + */ + private function cacheKey(): string + { + return "attachments:{$this->getTable()}:{$this->id}"; + } } diff --git a/app/Traits/HasGallery.php b/app/Traits/HasGallery.php index 62c9129..938bbea 100644 --- a/app/Traits/HasGallery.php +++ b/app/Traits/HasGallery.php @@ -3,7 +3,9 @@ namespace App\Traits; use App\Models\Media; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Relations\MorphMany; +use Illuminate\Support\Facades\Cache; trait HasGallery { @@ -60,4 +62,16 @@ trait HasGallery { return $this->morphMany(\App\Models\Gallery::class, 'addendum'); } + + public function getGallery(): Collection + { + $cacheKey = 'gallery:' . $this->getTable() . ':' . $this->id; + if (Cache::has($cacheKey) === true) { + return Cache::get($cacheKey); + } + + $gallery = $this->morphMany(\App\Models\Gallery::class, 'addendum')->get(); + Cache::put($cacheKey, $gallery, now()->addDays(14)); + return $gallery; + } }