Skip to content

Commit

Permalink
Merge pull request #6 from plank/1-contentable-polymorphic
Browse files Browse the repository at this point in the history
Implement basic contentable functionality
  • Loading branch information
m-triassi authored Nov 8, 2023
2 parents f4f4f7a + 087eb87 commit acc4c64
Show file tree
Hide file tree
Showing 20 changed files with 540 additions and 14 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
}
],
"require": {
"php": "^8.1",
"php": "^8.2",
"spatie/laravel-package-tools": "^1.14.0",
"illuminate/contracts": "^10.0"
},
Expand Down
5 changes: 5 additions & 0 deletions config/contentable.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
<?php

return [

'model' => \Plank\Contentable\Models\Content::class,
'cache' => [
'ttl' => 10800,
],
'layouts' => [
'model' => \Plank\Contentable\Models\Layout::class,
],
Expand Down
25 changes: 25 additions & 0 deletions database/migrations/create_contents_table.php.stub
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::create('contents', function (Blueprint $table) {
$table->id();
$table->string('identifier')->nullable();
$table->morphs('contentable');
$table->morphs('renderable');
$table->integer('order');
$table->timestamps();
});
}

public function down()
{
Schema::dropIfExists('contents');
}
};
66 changes: 66 additions & 0 deletions src/Concerns/CanRender.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

namespace Plank\Contentable\Concerns;

use Illuminate\Database\Eloquent\Relations\MorphOne;
use Illuminate\Database\Eloquent\SoftDeletes;
use Plank\Contentable\Contracts\Contentable;
use Plank\Contentable\Contracts\Renderable;

trait CanRender
{
public static function bootCanRender()
{
static::updated(function (Renderable $renderable) {
$renderable->contentable()?->clearCache();
});

static::deleted(function (Renderable $renderable) {
$renderable->contentable()?->clearCache();
});

if (in_array(SoftDeletes::class, class_uses_recursive(static::class))) {
static::restored(function (Renderable $renderable) {
$renderable->contentable()?->clearCache();
});
}
}

public function content(): MorphOne
{
$contentModel = config('contentable.model');

return $this->morphOne($contentModel, 'renderable');
}

public function contentable(): ?Contentable
{
return $this->content?->contentable;
}

/**
* Encodes the renderable fields as JSON to be passed to the front-end.
*/
public function renderJson($fields = []): string
{
return json_encode($this->only($fields ?: $this->renderableFields()));
}

/**
* Accessor for fields that are set to be rendered when serializing to
*
* @return string[]
*/
public function renderableFields(): array
{
return property_exists($this, 'renderableFields') ? $this->renderableFields : ['title', 'body'];
}

public function formatKeys(): array
{
return [
'renderable_type' => static::class,
'renderable_id' => $this->getKey(),
];
}
}
64 changes: 64 additions & 0 deletions src/Concerns/HasContent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Plank\Contentable\Concerns;

use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Support\Facades\Cache;

trait HasContent
{
public function contents(): MorphMany
{
$contentModel = config('contentable.model');

return $this->morphMany($contentModel, 'contentable');
}

public function renderHtml(): string
{
if (Cache::has("contentable.html.{$this->getKey()}")) {
return Cache::get("contentable.html.{$this->getKey()}");
}

$output = '';
foreach ($this->contents as $content) {
$output .= $content->renderable->renderHtml()."\n";
}

Cache::put("contentable.html.{$this->getKey()}", $output, config('contentable.cache.ttl'));

return $output;
}

public function renderJson(): string
{
if (Cache::has("contentable.json.{$this->getKey()}")) {
return Cache::get("contentable.json.{$this->getKey()}");
}

$output = [];

foreach ($this->contents as $content) {
$output[] = $content->renderable->renderJson();
}

$output = json_encode($output);

Cache::put("contentable.json.{$this->id}", $output, config('contentable.cache.ttl'));

return $output;
}

public function clearCache($key = null): void
{
$key = $key ?? $this->getKey();

if (Cache::has("contentable.html.{$key}")) {
Cache::delete("contentable.html.{$key}");
}

if (Cache::has("contentable.json.{$key}")) {
Cache::delete("contentable.json.{$key}");
}
}
}
7 changes: 0 additions & 7 deletions src/Contentable.php

This file was deleted.

6 changes: 1 addition & 5 deletions src/ContentableServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,11 @@ class ContentableServiceProvider extends PackageServiceProvider
{
public function configurePackage(Package $package): void
{
/*
* This class is a Package Service Provider
*
* More info: https://github.com/spatie/laravel-package-tools
*/
$package
->name('contentable')
->hasConfigFile()
->hasMigrations([
'create_contents_table',
'create_layouts_table',
]);
}
Expand Down
12 changes: 12 additions & 0 deletions src/Contracts/Content.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Plank\Contentable\Contracts;

use Illuminate\Database\Eloquent\Relations\MorphTo;

interface Content
{
public function renderable(): MorphTo;

public function contentable(): MorphTo;
}
19 changes: 19 additions & 0 deletions src/Contracts/Contentable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Plank\Contentable\Contracts;

use Illuminate\Database\Eloquent\Relations\MorphMany;

/**
* @property string $render_order_column
*/
interface Contentable
{
public function contents(): MorphMany;

public function renderHtml(): string;

public function renderJson(): string;

public function clearCache(): void;
}
18 changes: 18 additions & 0 deletions src/Contracts/Renderable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Plank\Contentable\Contracts;

use Illuminate\Database\Eloquent\Relations\MorphOne;

interface Renderable
{
public function renderHtml(): string;

public function renderJson(): string;

public function renderableFields(): array;

public function content(): MorphOne;

public function contentable(): ?Contentable;
}
49 changes: 49 additions & 0 deletions src/Models/Content.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace Plank\Contentable\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Plank\Contentable\Contracts\Content as ContentInterface;

class Content extends Model implements ContentInterface
{
protected $fillable = [
'contentable_id',
'contentable_type',
'renderable_id',
'renderable_type',
'identifier',
'order',
];

protected static function boot()
{
static::saved(function (ContentInterface $content) {
$content->contentable->clearCache();
});

parent::boot();
}

public function renderable(): MorphTo
{
return $this->morphTo();
}

public function contentable(): MorphTo
{
return $this->morphTo();
}

public function scopeInRenderableOrder(Builder $query): void
{
$query->orderBy($this->renderOrderColumnField());
}

public function renderOrderColumnField(): string
{
return $this->render_order_column ?? $this->getKeyName();
}
}
44 changes: 44 additions & 0 deletions tests/Feature/AttachTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php

use Plank\Contentable\Tests\Helper\Models\FakeModule;
use Plank\Contentable\Tests\Helper\Models\Page;

it('can format keys as expected for easy creation', function () {
$renderable = FakeModule::factory()->create();

$expected = [
'renderable_type' => FakeModule::class,
'renderable_id' => $renderable->id,
];

expect($renderable->formatKeys())->toEqual($expected);
});

it('can attach new renderables to a piece of content', function () {
$page = Page::factory()->create();
$renderable = FakeModule::factory()->create();

$identifier = 'Fake Identifier';
$attached = $page->contents()->create(array_merge($renderable->formatKeys(), ['identifier' => $identifier]))->first();

expect($page->contents)
->toHaveCount(1);

$module = $page->contents->first();
expect($module->identifier)->toEqual($identifier);
expect($module->renderable)->toEqual($renderable->fresh());

expect($attached->renderable_id)->toEqual($renderable->id);
expect($attached->renderable_type)->toEqual($renderable::class);
expect($attached->contentable_id)->toEqual($page->id);
expect($attached->contentable_type)->toEqual($page::class);
});

it('can have many renderables attached in one go using collection high order functions', function () {
$page = Page::factory()->create();
$renderables = FakeModule::factory(10)->create();

$attached = $page->contents()->createMany($renderables->map->formatKeys());

expect($attached->count())->toEqual(10);
});
Loading

0 comments on commit acc4c64

Please sign in to comment.