Skip to content

Commit

Permalink
Merge pull request #3 from jpkleemans/json-attributes
Browse files Browse the repository at this point in the history
Add support for JSON attributes
  • Loading branch information
jpkleemans authored Nov 21, 2020
2 parents 571e957 + 817c820 commit adfbd62
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 0 deletions.
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ Echo.channel('orders')
})
```

## JSON attributes
For attributes stored as JSON, you can use the `->` operator:

```php
protected $dispatchesEvents = [
'payment->status:completed' => PaymentCompleted::class,
];
```

## Accessors
For more complex state changes, you can use attributes defined by an <a href="https://laravel.com/docs/eloquent-mutators#defining-an-accessor" target="_blank">accessor</a>:

Expand Down
25 changes: 25 additions & 0 deletions src/AttributeEvents.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Kleemans;

use Illuminate\Support\Arr;
use Illuminate\Support\Str;

trait AttributeEvents
Expand Down Expand Up @@ -37,6 +38,18 @@ private function fireAttributeEvents(): void
}
}

// JSON attribute
elseif (Str::contains($attribute, '->')) {
[$attribute, $path] = explode('->', $attribute, 2);
$path = str_replace('->', '.', $path);

if (!$this->isDirtyNested($attribute, $path)) {
continue; // Not changed
}

$value = Arr::get($this->getAttribute($attribute), $path);
}

// Regular attribute
elseif (!$this->isDirty($attribute)) {
continue; // Not changed
Expand Down Expand Up @@ -85,6 +98,18 @@ public function isDirtyAccessor(string $attribute): bool
return $originalValue !== $currentValue;
}

public function isDirtyNested(string $attribute, string $path): bool
{
$originalValue = Arr::get($this->getOriginal($attribute), $path);
$currentValue = Arr::get($this->getAttribute($attribute), $path);

if ($currentValue === null) {
return false;
}

return $originalValue !== $currentValue;
}

/**
* @return array<string, string>
*/
Expand Down
97 changes: 97 additions & 0 deletions test/AttributeEventsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ class AttributeEventsTest extends TestCase
Fake\Events\OrderShippingCountryChanged::class,
Fake\Events\OrderPaid::class,
Fake\Events\OrderPaidWithCash::class,
Fake\Events\OrderMetaUpdated::class,
Fake\Events\PaypalPaymentDenied::class,
Fake\Events\InvoiceDownloaded::class,
];

public function setUp(): void
Expand Down Expand Up @@ -236,6 +239,8 @@ public function it_respects_withoutEvents()
});
}

// Accessors

/** @test */
public function it_dispatches_event_on_accessor_change()
{
Expand Down Expand Up @@ -283,6 +288,96 @@ public function it_dispatches_event_on_change_of_accessor_for_existing_attribute
$this->dispatcher->assertDispatched(Fake\Events\OrderPaidWithCash::class);
}

// JSON attributes

/** @test */
public function it_dispatches_event_when_updating_json_attribute()
{
$order = new Fake\Order();
$order->meta = ['gift_wrapping' => true];
$order->save();

$order->meta = ['gift_wrapping' => false];
$order->save();

$this->dispatcher->assertDispatched(Fake\Events\OrderMetaUpdated::class);
}

/** @test */
public function it_dispatches_event_when_updating_json_field()
{
$order = new Fake\Order();
$order->meta = ['paypal_status' => 'pending'];
$order->save();

$meta = $order->meta;
$meta['paypal_status'] = 'denied';
$order->meta = $meta;

$order->save();

$this->dispatcher->assertDispatched(Fake\Events\PaypalPaymentDenied::class);
}

/** @test */
public function it_works_with_json_changes_through_update_method()
{
$order = new Fake\Order();
$order->meta = ['paypal_status' => 'pending'];
$order->save();

$order->update(['meta->paypal_status' => 'denied']);

$this->dispatcher->assertDispatched(Fake\Events\PaypalPaymentDenied::class);
}

/** @test */
public function it_dispatches_event_when_adding_json_field()
{
$order = new Fake\Order();
$order->meta = ['gift_wrapping' => true];
$order->save();

$meta = $order->meta;
$meta['paypal_status'] = 'denied';
$order->meta = $meta;

$order->save();

$this->dispatcher->assertDispatched(Fake\Events\PaypalPaymentDenied::class);
}

/** @test */
public function it_does_not_dispatch_on_initial_value_of_json_attribute()
{
$order = new Fake\Order();
$order->meta = [
'gift_wrapping' => true,
'paypal_status' => 'denied',
];
$order->save();

$this->dispatcher->assertNotDispatched(Fake\Events\OrderMetaUpdated::class);
$this->dispatcher->assertNotDispatched(Fake\Events\PaypalPaymentDenied::class);
}

/** @test */
public function it_works_with_nested_json_fields()
{
$order = new Fake\Order();
$order->meta = ['invoice' => ['downloaded' => false]];
$order->save();

$meta = $order->meta;
$meta['invoice']['downloaded'] = true;
$order->meta = $meta;

$order->save();

$this->dispatcher->assertDispatched(Fake\Events\OrderMetaUpdated::class);
$this->dispatcher->assertDispatched(Fake\Events\InvoiceDownloaded::class);
}

// Setup methods

private function initEventDispatcher()
Expand Down Expand Up @@ -319,6 +414,7 @@ private function migrate()
$table->integer('discount_percentage');
$table->boolean('tax_free');
$table->string('payment_gateway');
$table->json('meta');
$table->timestamps();
});
}
Expand All @@ -336,6 +432,7 @@ private function seed()
'discount_percentage' => 0,
'tax_free' => false,
'payment_gateway' => 'credit_card',
'meta' => '{}',
]
]);
}
Expand Down
12 changes: 12 additions & 0 deletions test/Fake/Events/InvoiceDownloaded.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Kleemans\Test\Fake\Events;

use Kleemans\Test\Fake\Order;

class InvoiceDownloaded
{
public function __construct(Order $order)
{
}
}
12 changes: 12 additions & 0 deletions test/Fake/Events/OrderMetaUpdated.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Kleemans\Test\Fake\Events;

use Kleemans\Test\Fake\Order;

class OrderMetaUpdated
{
public function __construct(Order $order)
{
}
}
12 changes: 12 additions & 0 deletions test/Fake/Events/PaypalPaymentDenied.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Kleemans\Test\Fake\Events;

use Kleemans\Test\Fake\Order;

class PaypalPaymentDenied
{
public function __construct(Order $order)
{
}
}
5 changes: 5 additions & 0 deletions test/Fake/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class Order extends Model
'discount_percentage' => 0,
'tax_free' => false,
'payment_gateway' => 'credit_card',
'meta' => '{}',
];

protected $guarded = [];
Expand All @@ -27,6 +28,7 @@ class Order extends Model
'paid_amount' => 'float',
'discount_percentage' => 'integer',
'tax_free' => 'boolean',
'meta' => 'array',
];

protected $dispatchesEvents = [
Expand All @@ -44,6 +46,9 @@ class Order extends Model
'shipping_country:*' => Events\OrderShippingCountryChanged::class,
'is_paid:true' => Events\OrderPaid::class,
'payment_gateway:cash' => Events\OrderPaidWithCash::class,
'meta:*' => Events\OrderMetaUpdated::class,
'meta->paypal_status:denied' => Events\PaypalPaymentDenied::class,
'meta->invoice->downloaded:true' => Events\InvoiceDownloaded::class,
];

public function getShippingCountryAttribute(): string
Expand Down

0 comments on commit adfbd62

Please sign in to comment.