Skip to content

Commit

Permalink
Merge pull request #34 from michaeldyrynda/pr-13
Browse files Browse the repository at this point in the history
Add many to many support
  • Loading branch information
michaeldyrynda authored Sep 6, 2019
2 parents dfb4216 + 820370f commit a5a16c7
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 31 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/vendor/
composer.lock
24 changes: 24 additions & 0 deletions src/CascadeSoftDeleteException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Iatstuti\Database\Support;

use Exception;
use Illuminate\Support\Str;

class CascadeSoftDeleteException extends Exception
{
public static function softDeleteNotImplemented($class)
{
return new static(sprintf('%s does not implement Illuminate\Database\Eloquent\SoftDeletes', $class));
}


public static function invalidRelationships($relationships)
{
return new static(sprintf(
'%s [%s] must exist and return an object of type Illuminate\Database\Eloquent\Relations\Relation',
Str::plural('Relationship', count($relationships)),
join(', ', $relationships)
));
}
}
76 changes: 50 additions & 26 deletions src/CascadeSoftDeletes.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use LogicException;
use Illuminate\Support\Str;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\Relation;

trait CascadeSoftDeletes
Expand All @@ -20,36 +21,59 @@ trait CascadeSoftDeletes
protected static function bootCascadeSoftDeletes()
{
static::deleting(function ($model) {
if (! $model->implementsSoftDeletes()) {
throw new LogicException(sprintf(
'%s does not implement Illuminate\Database\Eloquent\SoftDeletes',
get_called_class()
));
}

if ($invalidCascadingRelationships = $model->hasInvalidCascadingRelationships()) {
throw new LogicException(sprintf(
'%s [%s] must exist and return an object of type Illuminate\Database\Eloquent\Relations\Relation',
Str::plural('Relationship', count($invalidCascadingRelationships)),
join(', ', $invalidCascadingRelationships)
));
}

$delete = $model->forceDeleting ? 'forceDelete' : 'delete';

foreach ($model->getActiveCascadingDeletes() as $relationship) {
if ($model->{$relationship} instanceof Model) {
$model->{$relationship}->{$delete}();
} else {
foreach ($model->{$relationship} as $child) {
$child->{$delete}();
}
}
}
$model->validateCascadingSoftDelete();

$model->runCascadingDeletes();
});
}


/**
* Validate that the calling model is correctly setup for cascading soft deletes.
*
* @throws \Iatstuti\Database\Support\CascadeSoftDeleteException
*/
protected function validateCascadingSoftDelete()
{
if (! $this->implementsSoftDeletes()) {
throw CascadeSoftDeleteException::softDeleteNotImplemented(get_called_class());
}

if ($invalidCascadingRelationships = $this->hasInvalidCascadingRelationships()) {
throw CascadeSoftDeleteException::invalidRelationships($invalidCascadingRelationships);
}
}


/**
* Run the cascading soft delete for this model.
*
* @return void
*/
protected function runCascadingDeletes()
{
foreach ($this->getActiveCascadingDeletes() as $relationship) {
$this->cascadeSoftDeletes($relationship);
}
}


/**
* Cascade delete the given relationship on the given mode.
*
* @param string $relationship
* @return return
*/
protected function cascadeSoftDeletes($relationship)
{
$delete = $this->forceDeleting ? 'forceDelete' : 'delete';

foreach ($this->{$relationship}()->get() as $model) {
$model->pivot ? $model->pivot->{$delete}() : $model->{$delete}();
}
}


/**
* Determine if the current model implements soft deletes.
*
Expand Down
57 changes: 53 additions & 4 deletions tests/CascadeSoftDeletesIntegrationTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use PHPUnit\Framework\TestCase;
use Illuminate\Events\Dispatcher;
use Iatstuti\Database\Support\CascadeSoftDeleteException;
use Illuminate\Container\Container;
use Illuminate\Database\Capsule\Manager;

Expand Down Expand Up @@ -49,6 +50,17 @@ public static function setupBeforeClass(): void
$table->string('label');
$table->timestamps();
});

$manager->schema()->create('authors__post_types', function ($table) {

$table->increments('id');
$table->integer('author_id');
$table->integer('posttype_id');
$table->timestamps();

$table->foreign('author_id')->references('id')->on('author');
$table->foreign('posttype_id')->references('id')->on('post_types');
});
}


Expand All @@ -67,6 +79,23 @@ public function it_cascades_deletes_when_deleting_a_parent_model()
$this->assertCount(0, Tests\Entities\Comment::where('post_id', $post->id)->get());
}

/** @test */
public function it_cascades_deletes_entries_from_pivot_table()
{
$author = Tests\Entities\Author::create(['name' => 'ManyToManyTestAuthor']);

$this->attachPostTypesToAuthor($author);
$this->assertCount(2, $author->posttypes);

$author->delete();

$pivotEntries = Manager::table('authors__post_types')
->where('author_id', $author->id)
->get();

$this->assertCount(0, $pivotEntries);
}

/** @test */
public function it_cascades_deletes_when_force_deleting_a_parent_model()
{
Expand All @@ -88,7 +117,7 @@ public function it_cascades_deletes_when_force_deleting_a_parent_model()
*/
public function it_takes_exception_to_models_that_do_not_implement_soft_deletes()
{
$this->expectException(LogicException::class);
$this->expectException(CascadeSoftDeleteException::class);
$this->expectExceptionMessage('Tests\Entities\NonSoftDeletingPost does not implement Illuminate\Database\Eloquent\SoftDeletes');

$post = Tests\Entities\NonSoftDeletingPost::create([
Expand All @@ -106,7 +135,7 @@ public function it_takes_exception_to_models_that_do_not_implement_soft_deletes(
*/
public function it_takes_exception_to_models_trying_to_cascade_deletes_on_invalid_relationships()
{
$this->expectException(LogicException::class);
$this->expectException(CascadeSoftDeleteException::class);
$this->expectExceptionMessage('Relationships [invalidRelationship, anotherInvalidRelationship] must exist and return an object of type Illuminate\Database\Eloquent\Relations\Relation');

$post = Tests\Entities\InvalidRelationshipPost::create([
Expand All @@ -131,7 +160,7 @@ public function it_ensures_that_no_deletes_are_performed_if_there_are_invalid_re

try {
$post->delete();
} catch (LogicException $e) {
} catch (CascadeSoftDeleteException $e) {
$this->assertNotNull(Tests\Entities\InvalidRelationshipPost::find($post->id));
$this->assertCount(3, Tests\Entities\Comment::where('post_id', $post->id)->get());
}
Expand All @@ -156,10 +185,11 @@ public function it_can_accept_cascade_deletes_as_a_single_string()

/**
* @test
*/
public function it_handles_situations_where_the_relationship_method_does_not_exist()
{
$this->expectException(LogicException::class);
$this->expectException(CascadeSoftDeleteException::class);
$this->expectExceptionMessage('Relationship [comments] must exist and return an object of type Illuminate\Database\Eloquent\Relations\Relation');

$post = Tests\Entities\PostWithMissingRelationshipMethod::create([
Expand Down Expand Up @@ -226,6 +256,25 @@ public function it_cascades_a_has_one_relationship()
$this->assertCount(0, Tests\Entities\PostType::where('id', $type->id)->get());
}

/**
* Attach some post types to the given author.
*
* @return void
*/
public function attachPostTypesToAuthor($author)
{
$author->posttypes()->saveMany([

Tests\Entities\PostType::create([
'label' => 'First Post Type',
]),

Tests\Entities\PostType::create([
'label' => 'Second Post Type',
]),
]);
}

/**
* Attach some dummy posts (w/ comments) to the given author.
*
Expand Down
7 changes: 6 additions & 1 deletion tests/Entities/Author.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,17 @@ class Author extends Model

public $dates = ['deleted_at'];

protected $cascadeDeletes = ['posts'];
protected $cascadeDeletes = ['posts', 'posttypes'];

protected $fillable = ['name'];

public function posts()
{
return $this->hasMany('Tests\Entities\Post');
}

public function posttypes()
{
return $this->belongsToMany('Tests\Entities\PostType', 'authors__post_types', 'author_id', 'posttype_id');
}
}
5 changes: 5 additions & 0 deletions tests/Entities/PostType.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ public function post()
{
return $this->belongsTo('Test\Entities\Post');
}

public function authors()
{
return $this->belongsToMany('Tests\Entities\Author', 'authors__post_types', 'posttype_id', 'author_id');
}
}

0 comments on commit a5a16c7

Please sign in to comment.