Skip to content

Commit

Permalink
add morph model binding tests
Browse files Browse the repository at this point in the history
  • Loading branch information
d8vjork committed May 23, 2024
1 parent 9ec6800 commit d651207
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 22 deletions.
12 changes: 9 additions & 3 deletions src/Attributes/BindModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public function getMorphModel(string $fromPropertyKey, array $properties, array
{
$morphTypePropertyKey = $this->getMorphPropertyTypeKey($fromPropertyKey);

$types = array_filter(explode(',', $properties[$morphTypePropertyKey] ?? ''));
$types = array_filter(array_map('trim', explode(',', $properties[$morphTypePropertyKey] ?? '')));

if (count($types) === 0) {
throw new Exception('Morph type must be specified to be able to bind a model from a morph.');
Expand All @@ -98,12 +98,18 @@ public function getMorphModel(string $fromPropertyKey, array $properties, array
);

if (count($modelModelClass) === 0 && count($propertyTypeClasses) > 0) {
$modelModelClass = array_filter($propertyTypeClasses, fn (string $class) => (new $class())->getMorphClass() === $type);
var_dump($propertyTypeClasses);
var_dump($morphMap);
var_dump($types);
$modelModelClass = array_filter(
$propertyTypeClasses,
fn (string $class) => in_array((new $class())->getMorphClass(), $types)

Check failure on line 106 in src/Attributes/BindModel.php

View workflow job for this annotation

GitHub Actions / PHPStan

Call to an undefined method object::getMorphClass().

Check failure on line 106 in src/Attributes/BindModel.php

View workflow job for this annotation

GitHub Actions / PHPStan

Call to an undefined method object::getMorphClass().
);

$modelModelClass = reset($modelModelClass);
}

if (count($modelModelClass) === 0) {
if (! $modelModelClass || count($modelModelClass) === 0) {
throw new Exception('Morph type not found on relation map or within types.');
}

Expand Down
3 changes: 2 additions & 1 deletion testbench.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
providers:
- OpenSoutheners\LaravelDto\ServiceProvider
- Workbench\App\Providers\WorkbenchServiceProvider

migrations:
- workbench/database/migrations
Expand All @@ -8,7 +9,7 @@ migrations:
# - Workbench\Database\Seeders\DatabaseSeeder

workbench:
start: '/'
start: "/"
install: true
discovers:
web: true
Expand Down
66 changes: 66 additions & 0 deletions tests/Integration/DataTransferObjectTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@
use Workbench\App\DataTransferObjects\UpdatePostData;
use Workbench\App\DataTransferObjects\UpdatePostWithDefaultData;
use Workbench\App\Enums\PostStatus;
use Workbench\App\Models\Film;
use Workbench\App\Models\Post;
use Workbench\App\Models\User;
use Workbench\Database\Factories\FilmFactory;
use Workbench\Database\Factories\PostFactory;
use Workbench\Database\Factories\TagFactory;

class DataTransferObjectTest extends TestCase
{
Expand Down Expand Up @@ -197,6 +200,69 @@ public function testDataTransferObjectWithDefaultValueAttributeGetsBoundWhenOneI
]);
}

public function testDataTransferObjectWithMorphsGetsModelsBoundOfEachTypeSent()
{
$user = User::create([
'email' => '[email protected]',
'password' => '1234',
'name' => 'Ruben',
]);

$this->actingAs($user);

$horrorTag = TagFactory::new()->create([
'name' => 'Horror',
'slug' => 'horror',
]);

$fooBarPost = PostFactory::new()->create([
'title' => 'Foo bar',
'slug' => 'foo-bar',
]);

$helloWorldPost = PostFactory::new()->create([
'title' => 'Hello world',
'slug' => 'hello-world',
]);

$myFilm = FilmFactory::new()->create([
'title' => 'My Film',
'slug' => 'my-film',
'year' => 1997
]);

$response = $this->patchJson('tags/1', [
'name' => 'Scary',
'taggable' => '1, 1, 2',
'taggable_type' => 'film, post',
]);

$response->assertSuccessful();

$response->assertJsonCount(3, 'data.taggable');

$response->assertJsonFragment([
"id" => 1,
"title" => "My Film",
"year" => "1997",
"about" => null
]);

$response->assertJsonFragment([
"id" => 1,
"title" => "Foo bar",
"slug" => "foo-bar",
"status" => "published"
]);

$response->assertJsonFragment([
"id" => 2,
"title" => "Hello world",
"slug" => "hello-world",
"status" => "published"
]);
}

public function testNestedDataTransferObjectsGetsTheNestedAsObjectInstance()
{
$this->markTestIncomplete('Need to create nested actions/DTOs');
Expand Down
10 changes: 8 additions & 2 deletions workbench/app/DataTransferObjects/UpdateTagData.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,28 @@
namespace Workbench\App\DataTransferObjects;

use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use OpenSoutheners\LaravelDto\Attributes\BindModel;
use OpenSoutheners\LaravelDto\Attributes\WithDefaultValue;
use OpenSoutheners\LaravelDto\Contracts\ValidatedDataTransferObject;
use OpenSoutheners\LaravelDto\DataTransferObject;
use Workbench\App\Http\Requests\TagUpdateFormRequest;
use Workbench\App\Models\Film;
use Workbench\App\Models\Post;
use Workbench\App\Models\Tag;
use Workbench\App\Models\User;

class UpdateTagData extends DataTransferObject implements ValidatedDataTransferObject
{
/**
* @param \Illuminate\Support\Collection<\Workbench\App\Models\Post|\Workbench\App\Models\Film> $taggable
*/
public function __construct(
#[BindModel]
public Tag $tag,
#[BindModel]
public ?Post $post,
#[BindModel([Post::class => 'slug', Film::class])]
public Collection $taggable,
public array $taggableType,
public string $name,
#[WithDefaultValue(Authenticatable::class)]
public User $authUser
Expand Down
2 changes: 2 additions & 0 deletions workbench/app/Http/Requests/TagUpdateFormRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public function rules()
{
return [
'name' => ['required', 'string'],
'taggable' => ['required', 'string'],
'taggable_type' => ['required', 'string'],
];
}
}
33 changes: 33 additions & 0 deletions workbench/app/Models/Film.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace Workbench\App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Film extends Model
{
use HasFactory;

/**
* The attributes that aren't mass assignable.
*
* @var array<string>|bool
*/
protected $guarded = [];

/**
* The attributes that should be visible in serialization.
*
* @var array<string>
*/
protected $visible = [
'id', 'title', 'year', 'about',
];

public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}
}
6 changes: 3 additions & 3 deletions workbench/app/Models/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Post extends Model
{
Expand All @@ -26,8 +26,8 @@ class Post extends Model
'id', 'title', 'slug', 'status', 'tags',
];

public function tags(): BelongsToMany
public function tags(): MorphToMany
{
return $this->belongsToMany(Tag::class);
return $this->morphToMany(Tag::class, 'taggable');
}
}
11 changes: 8 additions & 3 deletions workbench/app/Models/Tag.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class Tag extends Model
{
Expand All @@ -26,8 +26,13 @@ class Tag extends Model
'id', 'name', 'slug',
];

public function post(): BelongsTo
public function posts(): MorphToMany
{
return $this->belongsTo(Post::class);
return $this->morphedByMany(Post::class, 'taggable');
}

public function films(): MorphToMany
{
return $this->morphedByMany(Post::class, 'taggable');
}
}
9 changes: 8 additions & 1 deletion workbench/app/Providers/WorkbenchServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

namespace Workbench\App\Providers;

use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\ServiceProvider;
use Workbench\App\Models;

class WorkbenchServiceProvider extends ServiceProvider
{
Expand All @@ -19,6 +21,11 @@ public function register(): void
*/
public function boot(): void
{
//
Relation::enforceMorphMap([
'post' => Models\Post::class,
'tag' => Models\Tag::class,
'film' => Models\Film::class,
'user' => Models\User::class,
]);
}
}
36 changes: 36 additions & 0 deletions workbench/database/factories/FilmFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace Workbench\Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;
use Illuminate\Support\Str;
use Workbench\App\Models\Film;

/**
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\Workbench\App\Models\Film>
*/
class FilmFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
protected $model = Film::class;

/**
* Define the model's default state.
*
* @return array
*/
public function definition()
{
$name = $this->faker->unique()->words(2, true);

return [
'title' => $name,
'slug' => Str::slug($name),
'year' => $this->faker->year(),
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ public function up()
$table->timestamps();
});

Schema::create('post_tag', function (Blueprint $table) {
Schema::create('taggables', function (Blueprint $table) {
$table->id();
$table->foreignIdFor(Post::class);
$table->foreignIdFor(Tag::class);
$table->morphs('taggable');
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

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

return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('films', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->string('slug')->unique();
$table->string('year')->index();
$table->text('about')->nullable();

$table->timestamps();
});
}

/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('films');
}
};
8 changes: 1 addition & 7 deletions workbench/routes/api.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,8 @@
return response()->json($data->toArray());
})->middleware('api');

Route::patch('post/{post}/tags', function (UpdatePostWithTags $data) {
$tagsData = $data->tags->map(fn ($tag) => UpdateTagData::fromArray([
'post' => $data->post,
'tag' => $tag,
]));

Route::patch('tags/{tag}', function (UpdateTagData $data) {
return response()->json([
'tagsData' => $tagsData->toArray(),
'data' => $data->toArray(),
]);
})->middleware('api');

0 comments on commit d651207

Please sign in to comment.