Skip to content

Commit

Permalink
Merge pull request #3 from plank/queued-deletes
Browse files Browse the repository at this point in the history
Syncing & Queued Deletes
  • Loading branch information
kfriars committed Mar 20, 2024
2 parents d2f120e + 16a64c7 commit 5e67eb6
Show file tree
Hide file tree
Showing 16 changed files with 455 additions and 4 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

- [Kurt Friars](https://github.com/kfriars)
- [Massimo Triassi](https://github.com/m-triassi)
- [Andrew Hanichkovsky](https://github.com/a-drew)
- [All Contributors](../../contributors)

 
Expand Down
1 change: 1 addition & 0 deletions config/publisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
'draft' => 'draft',
'workflow' => 'status',
'has_been_published' => 'has_been_published',
'should_delete' => 'should_delete',
],
'urls' => [
'rewrite' => true,
Expand Down
3 changes: 3 additions & 0 deletions src/Commands/PublisherMigrations.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ protected function getMigrationContent(Model&Publishable $model)
$draftColumn = config()->get('publisher.columns.draft', 'draft');
$workflowColumn = config()->get('publisher.columns.workflow', 'status');
$unpublishedState = $model::workflow()::unpublished()->value;
$shouldDeleteColumn = config()->get('publisher.columns.should_delete', 'should_delete');

return <<<EOT
<?php
Expand All @@ -66,6 +67,7 @@ public function up(): void
\$table->json('$draftColumn')->nullable();
\$table->string('$workflowColumn')->default('$unpublishedState');
\$table->boolean('$hasBeenPublishedColumn')->default(false);
\$table->boolean('$shouldDeleteColumn')->default(false);
});
}
Expand All @@ -75,6 +77,7 @@ public function down(): void
\$table->dropColumn('$workflowColumn');
\$table->dropColumn('$draftColumn');
\$table->dropColumn('$hasBeenPublishedColumn');
\$table->dropColumn('$shouldDeleteColumn');
});
}
};
Expand Down
2 changes: 2 additions & 0 deletions src/Concerns/HasPublishableAttributes.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ protected function excludedFromDraftByDefault(): array
$this->workflowColumn(),
$this->draftColumn(),
$this->hasBeenPublishedColumn(),
$this->shouldDeleteColumn(),
$this->dependsOnPublishableForeignKey(),
]);

if (in_array(SoftDeletes::class, class_uses_recursive($this))) {
Expand Down
16 changes: 14 additions & 2 deletions src/Concerns/IsPublishable.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,21 @@ trait IsPublishable
{
use FiresPublishingEvents;
use HasPublishableAttributes;
use SyncsPublishing;

public function initializeIsPublishable()
{
$this->mergePublishableCasts();

$this->{$this->workflowColumn()} ??= static::workflow()::unpublished();
$this->{$this->hasBeenPublishedColumn()} ??= false;
$this->{$this->shouldDeleteColumn()} ??= false;

$this->makeHidden($this->draftColumn());
$this->makeHidden([
$this->draftColumn(),
$this->hasBeenPublishedColumn(),
$this->shouldDeleteColumn(),
]);
}

protected function mergePublishableCasts(): void
Expand All @@ -36,6 +42,7 @@ protected function mergePublishableCasts(): void
$this->workflowColumn() => Status::class,
$this->draftColumn() => 'json',
$this->hasBeenPublishedColumn() => 'boolean',
$this->shouldDeleteColumn() => 'boolean',
]);
}

Expand Down Expand Up @@ -111,6 +118,11 @@ public function hasBeenPublishedColumn(): string
return config()->get('publisher.columns.has_been_published', 'has_been_published');
}

public function shouldDeleteColumn(): string
{
return config()->get('publisher.columns.should_delete', 'should_delete');
}

/**
* @return class-string<PublishingStatus>
*/
Expand Down Expand Up @@ -180,6 +192,6 @@ public function wasUndrafted(): bool

public function hasEverBeenPublished(): bool
{
return $this->attributes[$this->hasBeenPublishedColumn()] === true;
return $this->{$this->hasBeenPublishedColumn()};
}
}
17 changes: 17 additions & 0 deletions src/Concerns/QueriesPublishableModels.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,23 @@ public function onlyDraft(): Builder&PublisherQueries
->whereNot($this->model->workflowColumn(), $this->model::workflow()::published());
}

public function withoutQueuedDeletes(): Builder&PublisherQueries
{
return $this->withoutGlobalScope(PublisherScope::class)
->where($this->model->shouldDeleteColumn(), false);
}

public function withQueuedDeletes(): Builder&PublisherQueries
{
return $this->withoutGlobalScope(PublisherScope::class);
}

public function onlyQueuedDeletes(): Builder&PublisherQueries
{
return $this->withoutGlobalScope(PublisherScope::class)
->where($this->model->shouldDeleteColumn(), true);
}

public function where($column, $operator = null, $value = null, $boolean = 'and')
{
if ($column instanceof Closure && is_null($operator)) {
Expand Down
141 changes: 141 additions & 0 deletions src/Concerns/SyncsPublishing.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
<?php

namespace Plank\Publisher\Concerns;

use Illuminate\Contracts\Support\Arrayable;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Collection;
use Plank\Publisher\Contracts\Publishable;

/**
* @mixin FiresPublishingEvents
* @mixin Publishable
*/
trait SyncsPublishing
{
public static function bootSyncsPublishing()
{
static::drafting(function (Publishable&Model $model) {
$model->syncPublishingToDependents();
});

static::undrafting(function (Publishable&Model $model) {
$model->syncPublishingToDependents();
});

static::deleting(function (Publishable&Model $model) {
return $model->queueForDelete();
});
}

public function syncPublishingToDependents(): void
{
$this->publishingDependents()
->map(fn (string $relation) => $this->nestedPluck($relation))
->flatten()
->each(fn (Publishable&Model $model) => $model->syncPublishingFrom($this));
}

public function publishingDependents(): Collection
{
if (property_exists($this, 'publishingDependents')) {
return Collection::make($this->publishingDependents);
}

return Collection::make();
}

public function syncPublishingFrom(Publishable&Model $from): void
{
$this->setRelation($this->dependendsOnPublishableRelation(), $from);

$this->{$this->workflowColumn()} = $from->{$this->workflowColumn()};
$this->save();

if ($from->isPublished() && $this->{$this->shouldDeleteColumn()}) {
$this->delete();
}
}

public function queueForDelete(): ?bool
{
$parent = $this->dependendsOnPublishable();

if ($parent === null || $parent->isPublished()) {
return null;
}

$this->{$this->shouldDeleteColumn()} = true;
$this->save();

return false;
}

public function dependendsOnPublishable(): (Publishable&Model)|null
{
if ($this->dependendsOnPublishableRelation() === null) {
return null;
}

return $this->{$this->dependendsOnPublishableRelation()};
}

public function dependendsOnPublishableRelation(): ?string
{
if (property_exists($this, 'dependendsOnPublishable')) {
return $this->dependendsOnPublishable;
}

return null;
}

public function dependsOnPublishableForeignKey(): ?string
{
if ($relation = $this->dependendsOnPublishableRelation()) {
$relation = $this->{$relation}();
}

if ($relation instanceof BelongsTo) {
return $relation->getForeignKeyName();
}

return null;
}

/**
* @return Collection<Publishable&Model>
*/
protected function nestedPluck(string $relation): Collection
{
$this->loadMissing($relation);

$models = [$this];

$relations = explode('.', $relation);

while ($part = array_shift($relations)) {
$results = [];

foreach ($models as $model) {
$related = $model->{$part};

if ($related instanceof Model) {
$results[] = $related;

continue;
}

if ($related instanceof Arrayable) {
foreach ($related as $item) {
$results[] = $item;
}
}
}

$models = $results;
}

return Collection::make($models);
}
}
47 changes: 47 additions & 0 deletions src/Contracts/Publishable.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace Plank\Publisher\Contracts;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;

interface Publishable extends PublishableAttributes, PublishableEvents
{
/**
Expand All @@ -19,6 +22,11 @@ public function workflowColumn(): string;
*/
public function hasBeenPublishedColumn(): string;

/**
* Get the name of the column that stores if the model should be deleted
*/
public function shouldDeleteColumn(): string;

/**
* Get the Workflow State Enum
*
Expand Down Expand Up @@ -75,4 +83,43 @@ public function wasDrafted(): bool;
* Determine if the Model was recently saved as draft
*/
public function wasUndrafted(): bool;

/**
* Sync the publishing state to dependents
*/
public function syncPublishingToDependents(): void;

/**
* Sync the publishing state from another model
*/
public function syncPublishingFrom(Publishable&Model $from): void;

/**
* Get a Collection of of dot-notation relations that should be synced
* with this Model's publishing/visibility state.
*
* @return Collection<string>
*/
public function publishingDependents(): Collection;

/**
* Queue the model for deletion if it's owner is not published
*/
public function queueForDelete(): ?bool;

/**
* Get the Model that this Model depends on for publishing/visibility
*/
public function dependendsOnPublishable(): (Publishable&Model)|null;

/**
* Get the Model that this Model depends on for publishing/visibility
*/
public function dependendsOnPublishableRelation(): ?string;

/**
* Get the Model the foreign key that this Model depends on for
* publishing/visibility
*/
public function dependsOnPublishableForeignKey(): ?string;
}
15 changes: 15 additions & 0 deletions src/Contracts/PublisherQueries.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,19 @@ public function onlyPublished(): Builder&PublisherQueries;
* Scope the query to models that are not in the published state
*/
public function onlyDraft(): Builder&PublisherQueries;

/**
* Scope the query to models that are not queued for deletion
*/
public function withoutQueuedDeletes(): Builder&PublisherQueries;

/**
* Scope the query to models that are queued for deletion
*/
public function onlyQueuedDeletes(): Builder&PublisherQueries;

/**
* Scope the query to models that are either queued for deletion or not
*/
public function withQueuedDeletes(): Builder&PublisherQueries;
}

0 comments on commit 5e67eb6

Please sign in to comment.