Skip to content

Commit

Permalink
Use route model resolver to inject models (wire-elements#296)
Browse files Browse the repository at this point in the history
  • Loading branch information
wlhrtr authored Apr 7, 2023
1 parent ccd22de commit 6277f92
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 24 deletions.
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,23 @@ To open a modal you will need to emit an event. To open the `EditUser` modal for
```

## Passing parameters
To open the `EditUser` modal for a specific user we can pass the user id (notice the single quotes):
To open the `EditUser` modal for a specific user we can pass the user id:

```html
<!-- Outside of any Livewire component -->
<button onclick='Livewire.emit("openModal", "edit-user", {{ json_encode(["user" => $user->id]) }})'>Edit User</button>
<button onclick="Livewire.emit('openModal', 'edit-user', {{ json_encode(['user' => $user->id]) }})">Edit User</button>

<!-- Inside existing Livewire component -->
<button wire:click='$emit("openModal", "edit-user", {{ json_encode(["user" => $user->id]) }})'>Edit User</button>
<button wire:click="$emit('openModal', 'edit-user', {{ json_encode(['user' => $user->id]) }})">Edit User</button>

<!-- If you use a different primaryKey (e.g. email), adjust accordingly -->
<button wire:click='$emit("openModal", "edit-user", {{ json_encode(["user" => $user->email]) }})'>Edit User</button>
<button wire:click="$emit('openModal', 'edit-user', {{ json_encode(['user' => $user->email]) }})">Edit User</button>

<!-- Example of passing multiple parameters -->
<button wire:click='$emit("openModal", "edit-user", {{ json_encode([$user->id, $isAdmin]) }})'>Edit User</button>
<button wire:click="$emit('openModal', 'edit-user', {{ json_encode([$user->id, $isAdmin]) }})">Edit User</button>
```

The parameters are passed to the `mount` method on the modal component:
The parameters are injected into the modal component and the model will be automatically fetched from the database if the type is defined:

```php
<?php
Expand All @@ -113,13 +113,14 @@ use LivewireUI\Modal\ModalComponent;

class EditUser extends ModalComponent
{
// This will inject just the ID
// public int $user;

public User $user;

public function mount(User $user)
public function mount()
{
Gate::authorize('update', $user);

$this->user = $user;
Gate::authorize('update', $this->user);
}

public function render()
Expand All @@ -129,6 +130,8 @@ class EditUser extends ModalComponent
}
```

The parameters are also passed to the `mount` method on the modal component.

## Opening a child modal
From an existing modal you can use the exact same event and a child modal will be created:

Expand Down Expand Up @@ -160,16 +163,14 @@ class EditUser extends ModalComponent
{
public User $user;

public function mount(User $user)
public function mount()
{
Gate::authorize('update', $user);

$this->user = $user;
Gate::authorize('update', $this->user);
}

public function update()
{
Gate::authorize('update', $user);
Gate::authorize('update', $this->user);

$this->user->update($data);

Expand All @@ -188,7 +189,7 @@ If you don't want to go to the previous modal but close the entire modal compone
```php
public function update()
{
Gate::authorize('update', $user);
Gate::authorize('update', $this->user);

$this->user->update($data);

Expand All @@ -201,7 +202,7 @@ Often you will want to update other Livewire components when changes have been m
```php
public function update()
{
Gate::authorize('update', $user);
Gate::authorize('update', $this->user);

$this->user->update($data);

Expand Down
4 changes: 4 additions & 0 deletions phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
<directory suffix="Test.php">./tests</directory>
</testsuite>
</testsuites>
<php>
<server name="DB_CONNECTION" value="sqlite"/>
<server name="DB_DATABASE" value=":memory:"/>
</php>
</phpunit>
58 changes: 56 additions & 2 deletions src/Modal.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
namespace LivewireUI\Modal;

use Exception;
use Illuminate\Contracts\Routing\UrlRoutable;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection;
use Illuminate\Support\Reflector;
use Illuminate\View\View;
use Livewire\Component;
use ReflectionClass;
Expand Down Expand Up @@ -30,9 +34,14 @@ public function openModal($component, $componentAttributes = [], $modalAttribute
}

$id = md5($component . serialize($componentAttributes));

$componentAttributes = collect($componentAttributes)
->merge($this->resolveComponentProps($componentAttributes, new $componentClass()))
->all();

$this->components[$id] = [
'name' => $component,
'attributes' => $componentAttributes,
'name' => $component,
'attributes' => $componentAttributes,
'modalAttributes' => array_merge([
'closeOnClickAway' => $componentClass::closeModalOnClickAway(),
'closeOnEscape' => $componentClass::closeModalOnEscape(),
Expand All @@ -49,6 +58,51 @@ public function openModal($component, $componentAttributes = [], $modalAttribute
$this->emit('activeModalComponentChanged', $id);
}

public function resolveComponentProps(array $attributes, Component $component)
{
if (PHP_VERSION_ID < 70400) {
return;
}

return $this->getPublicPropertyTypes($component)
->intersectByKeys($attributes)
->map(function ($className, $propName) use ($attributes) {
$resolved = $this->resolveParameter($attributes, $propName, $className);

return $resolved;
});
}

protected function resolveParameter($attributes, $parameterName, $parameterClassName)
{
$parameterValue = $attributes[$parameterName];

if ($parameterValue instanceof UrlRoutable) {
return $parameterValue;
}

$instance = app()->make($parameterClassName);

if (! $model = $instance->resolveRouteBinding($parameterValue)) {
throw (new ModelNotFoundException())->setModel(get_class($instance), [$parameterValue]);
}

return $model;
}

public function getPublicPropertyTypes($component)
{
if (PHP_VERSION_ID < 70400) {
return new Collection();
}

return collect($component->getPublicPropertiesDefinedBySubClass())
->map(function ($value, $name) use ($component) {
return Reflector::getParameterClassName(new \ReflectionProperty($component, $name));
})
->filter();
}

public function destroyComponent($id): void
{
unset($this->components[$id]);
Expand Down
15 changes: 13 additions & 2 deletions tests/Components/DemoModal.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,25 @@
namespace LivewireUI\Modal\Tests\Components;

use LivewireUI\Modal\ModalComponent;
use LivewireUI\Modal\Tests\Models\TestUser;

class DemoModal extends ModalComponent
{
public TestUser $user;
public $number;
public $message;

public function mount(TestUser $user)
{
$this->user = $user;
}

public function render()
{
return <<<'blade'
return <<<blade
<div>
Hello
{$this->user->first_name} says:
{$this->message} + {$this->number}
</div>
blade;
}
Expand Down
24 changes: 21 additions & 3 deletions tests/LivewireModalTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,34 @@
use LivewireUI\Modal\Modal;
use LivewireUI\Modal\Tests\Components\DemoModal;
use LivewireUI\Modal\Tests\Components\InvalidModal;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Schema;
use LivewireUI\Modal\Tests\Models\TestUser;

use function PHPUnit\Framework\assertArrayNotHasKey;

class LivewireModalTest extends TestCase
{
use RefreshDatabase;

public function testOpenModalEventListener(): void
{
Schema::create('test_users', function ($table) {
$table->id('id');
$table->string('first_name');
$table->timestamps();
});

$user = TestUser::forceCreate([
'first_name' => 'Philo',
])->fresh();

// Demo modal component
Livewire::component('demo-modal', DemoModal::class);

// Event attributes
$component = 'demo-modal';
$componentAttributes = [ 'message' => 'Foobar' ];
$componentAttributes = [ 'user' => 1, 'number' => 42, 'message' => 'Hello World' ];
$modalAttributes = [ 'hello' => 'world', 'closeOnEscape' => true, 'maxWidth' => '2xl', 'maxWidthClass' => 'sm:max-w-md md:max-w-xl lg:max-w-2xl', 'closeOnClickAway' => true, 'closeOnEscapeIsForceful' => true, 'dispatchCloseEvent' => false, 'destroyOnClose' => false ];

// Demo modal unique identifier
Expand All @@ -30,14 +45,17 @@ public function testOpenModalEventListener(): void
->assertSet('components', [
$id => [
'name' => $component,
'attributes' => $componentAttributes,
// Swap the expected user id of 1 with the Eloquent model
'attributes' => array_merge($componentAttributes, ['user' => $user]),
'modalAttributes' => $modalAttributes,
],
])
// Verify component is set to active
->assertSet('activeComponent', $id)
// Verify event is emitted to client
->assertEmitted('activeModalComponentChanged', $id);
->assertEmitted('activeModalComponentChanged', $id)
// Verif if component attribute 'message' is visible
->assertSee(['Hello World', 'Philo', '42']);
}

public function testDestroyComponentEventListener(): void
Expand Down
7 changes: 7 additions & 0 deletions tests/Models/TestUser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace LivewireUI\Modal\Tests\Models;

class TestUser extends \Illuminate\Database\Eloquent\Model {

}

0 comments on commit 6277f92

Please sign in to comment.