Skip to content

Commit

Permalink
Merge pull request #3 from cesargb/residual
Browse files Browse the repository at this point in the history
add clean residuals
  • Loading branch information
cesargb authored Jan 18, 2019
2 parents e592b15 + 0dac8d3 commit 800e4b3
Show file tree
Hide file tree
Showing 14 changed files with 498 additions and 70 deletions.
28 changes: 28 additions & 0 deletions .php_cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

use PhpCsFixer\Finder;
use PhpCsFixer\Config;

$finder = Finder::create()
->exclude('build')
->exclude('vendor')
->in(__DIR__)
->ignoreDotFiles(true)
->ignoreVCS(true);

return Config::create()
->setRules([
'@PSR2' => true,
'array_syntax' => ['syntax' => 'short'],
'ordered_imports' => ['sort_algorithm' => 'length'],
'no_unused_imports' => true,
'no_useless_else' => true,
'no_useless_return' => true,
'no_superfluous_elseif' => true,
'no_unneeded_curly_braces' => true,
'phpdoc_order' => true,
'phpdoc_types_order' => true,
'align_multiline_comment' => true,
])
->setUsingCache(false)
->setFinder($finder);
4 changes: 3 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ php:
- 7.0
- 7.1
- 7.2
- 7.3

env:
matrix:
Expand All @@ -14,7 +15,8 @@ matrix:
exclude:
- php: 7.2
env: COMPOSER_FLAGS="--prefer-lowest"

- php: 7.3
env: COMPOSER_FLAGS="--prefer-lowest"

before_script:
- travis_retry composer self-update
Expand Down
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,55 @@ Use the trait `Cesargb\Database\Support\CascadeDelete` in your Elocuent Model an
namespace App;

use App\Tag;
use App\Option;
use Illuminate\Database\Eloquent\Model;
use Cesargb\Database\Support\CascadeDelete;

class Video extends Model
{
use CascadeDelete;

protected $cascadeDeleteMorph = ['tags'];
protected $cascadeDeleteMorph = ['tags', 'options'];

public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}

public function options()
{
return $this->morphMany(Option::class, 'optionable');
}
}
```

Now you can delete an `App\Video` record, and any associated `App\Tag` records
will be deleted.
Now you can delete an `App\Video` record, and any associated `App\Tag` and
`App\Options` records will be deleted.

## Delete Residuals

If you bulk delete a model with morphological relationships, you will have
residual data that has not been deleted.

To clean this waste you have the method `deleteMorphResidual`

Sample:

```php
Video::query()->delete();

$video = new Video;

$video->deleteMorphResidual();
```
### Command to remove all residuals

You can use Artisan command `morph:clean` to remove all residuals data from all
your Moldes that used the `Cesargb\Database\Support\CascadeDelete` trait.

```php
php artisan morph:clean
```

## Contributing

Expand Down
24 changes: 19 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,20 @@
"cascade",
"delete",
"polymorphic",
"relation"
"relation",
"residual"
],
"type": "library",
"require": {
"php": "^7.0",
"illuminate/database": "~5.5.0|~5.6.0|~5.7.0",
"illuminate/events": "~5.5.0|~5.6.0|~5.7.0"
"illuminate/database": "~5.5.34|~5.6.0|~5.7.0",
"illuminate/console": "~5.5.34|~5.6.0|~5.7.0",
"illuminate/support": "~5.5.34|~5.6.0|~5.7.0"
},
"require-dev": {
"phpunit/phpunit": "^6.0|^7.0",
"orchestra/testbench": "^3.5"
"orchestra/testbench": "^3.5.5",
"friendsofphp/php-cs-fixer": "^2.14"
},
"autoload": {
"psr-4": {
Expand All @@ -29,12 +32,23 @@
"Tests\\": "tests/"
}
},
"scripts": {
"test": "vendor/bin/phpunit --colors=always",
"fix": "vendor/bin/php-cs-fixer fix"
},
"license": "MIT",
"authors": [
{
"name": "Cesar Garcia",
"email": "[email protected]"
}
],
"minimum-stability": "stable"
"minimum-stability": "stable",
"extra": {
"laravel": {
"providers": [
"Cesargb\\Database\\Support\\CascadeDeleteServiceProvider"
]
}
}
}
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<logging>
<log type="tap" target="build/report.tap"/>
<log type="junit" target="build/report.junit.xml"/>
<log type="coverage-html" target="build/coverage" charset="UTF-8" yui="true" highlight="true"/>
<log type="coverage-html" target="build/coverage"/>
<log type="coverage-text" target="build/coverage.txt"/>
<log type="coverage-clover" target="build/logs/clover.xml"/>
</logging>
Expand Down
53 changes: 52 additions & 1 deletion src/CascadeDelete.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Cesargb\Database\Support;

use LogicException;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

Expand Down Expand Up @@ -32,8 +33,8 @@ protected static function bootCascadeDelete()
/**
* Fetch the valids cascading morphs deletes for this model.
*
* @return array
* @throws \LogicException
* @return array
*/
protected function getCascadeDeleteMorphValid()
{
Expand Down Expand Up @@ -74,4 +75,54 @@ protected function getCascadeDeleteMorph()
{
return (array) ($this->cascadeDeleteMorph ?? []);
}

public function deleteMorphResidual()
{
foreach ($this->getCascadeDeleteMorphValid() as $method) {
$relation = $this->$method();

if ($relation instanceof MorphMany) {
$relation_table = $relation->getRelated()->getTable();

$relation_type = $relation->getMorphType();

$relation_id = $relation->getForeignKeyName();
} elseif ($relation instanceof MorphToMany) {
$relation_table = $relation->getTable();

$relation_type = $relation->getMorphType();

$relation_id = $relation->getForeignPivotKeyName();
}

$parents = DB::table($relation_table)
->groupBy($relation_type)
->pluck($relation_type);

foreach ($parents as $parent) {
if (class_exists($parent)) {
$parentObject = new $parent;

DB::table($relation_table)
->where($relation_type, $parent)
->whereNotExists(function ($query) use (
$parentObject,
$relation_table,
$relation_type,
$relation_id
) {
$query->select(DB::raw(1))
->from($parentObject->getTable())
->whereRaw(
$parentObject->getTable().'.'.$parentObject->getKeyName().' = '.$relation_table.'.'.$relation_id
);
})->delete();
} else {
DB::table($relation_table)
->where($relation_type, $parent)
->delete();
}
}
}
}
}
31 changes: 31 additions & 0 deletions src/CascadeDeleteServiceProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

namespace Cesargb\Database\Support;

use Illuminate\Support\ServiceProvider;

class CascadeDeleteServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
if ($this->app->runningInConsole()) {
$this->commands([
MorphCleanCommand::class,
]);
}
}

/**
* Register the application services.
*
* @return void
*/
public function register()
{
}
}
35 changes: 35 additions & 0 deletions src/MorphCleanCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Cesargb\Database\Support;

use LogicException;
use Illuminate\Console\Command;

class MorphCleanCommand extends Command
{
protected $signature = 'morph:clean';

protected $description = 'Clean break relations morph';

public function handle()
{
foreach (get_declared_classes() as $class) {
if (array_key_exists(CascadeDelete::class, class_uses($class))) {
try {
(new $class)->deleteMorphResidual();

$this->info(sprintf(
'Clean class %s',
$class
));
} catch (LogicException $e) {
$this->error(sprintf(
'Error to delete residual of class %s: %s',
$class,
$e->getMessage()
));
}
}
}
}
}
64 changes: 64 additions & 0 deletions tests/CascadeDeleteElocuentTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

namespace Tests;

use Tests\Models\Photo;
use Tests\Models\Video;
use Illuminate\Support\Facades\DB;

class CascadeDeleteElocuentTest extends TestCase
{
public function test_it_can_delete_relations_with_morph_many()
{
$photo1 = Photo::with('options')->first();
$photo2 = Photo::with('options')->skip(1)->first();

$this->assertGreaterThan(0, count($photo1->options));
$this->assertGreaterThan(0, count($photo2->options));

Photo::first()->delete();

$this->assertEquals(
0,
DB::table('options')->where([
'optionable_type' => Photo::class,
'optionable_id' => $photo1->id,
])->count()
);

$this->assertEquals(
count($photo2->options),
DB::table('options')->where([
'optionable_type' => Photo::class,
'optionable_id' => $photo2->id,
])->count()
);
}

public function test_it_can_delete_relations_with_morph_to_many()
{
$video1 = Video::with('tags')->first();
$video2 = Video::with('tags')->skip(1)->first();

$this->assertGreaterThan(0, count($video1->tags));
$this->assertGreaterThan(0, count($video2->tags));

Video::first()->delete();

$this->assertEquals(
0,
DB::table('taggables')->where([
'taggable_type' => Video::class,
'taggable_id' => $video1->id,
])->count()
);

$this->assertEquals(
count($video2->tags),
DB::table('taggables')->where([
'taggable_type' => Video::class,
'taggable_id' => $video2->id,
])->count()
);
}
}
Loading

0 comments on commit 800e4b3

Please sign in to comment.