Skip to content

Commit ea8ade3

Browse files
authored
Merge pull request #326 from binaryfire/feature/observed-by-attribute
feat: Add `#[ObservedBy]` attribute support
2 parents 74248e7 + efcc4d9 commit ea8ade3

File tree

3 files changed

+531
-0
lines changed

3 files changed

+531
-0
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\Database\Eloquent\Attributes;
6+
7+
use Attribute;
8+
9+
/**
10+
* Declare observers for a model using an attribute.
11+
*
12+
* When placed on a model class, the specified observer(s) will be automatically
13+
* registered when the model boots. Attributes on parent classes are inherited
14+
* by child classes.
15+
*
16+
* @example
17+
* ```php
18+
* #[ObservedBy(UserObserver::class)]
19+
* class User extends Model {}
20+
*
21+
* #[ObservedBy([AuditObserver::class, CacheObserver::class])]
22+
* class Post extends Model {}
23+
* ```
24+
*/
25+
#[Attribute(Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
26+
class ObservedBy
27+
{
28+
/**
29+
* Create a new attribute instance.
30+
*
31+
* @param class-string|class-string[] $classes
32+
*/
33+
public function __construct(
34+
public array|string $classes
35+
) {
36+
}
37+
}

src/core/src/Database/Eloquent/Concerns/HasObservers.php

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,77 @@
55
namespace Hypervel\Database\Eloquent\Concerns;
66

77
use Hyperf\Collection\Arr;
8+
use Hyperf\Collection\Collection;
9+
use Hyperf\Database\Model\Model as HyperfModel;
810
use Hypervel\Context\ApplicationContext;
11+
use Hypervel\Database\Eloquent\Attributes\ObservedBy;
912
use Hypervel\Database\Eloquent\ObserverManager;
13+
use ReflectionAttribute;
14+
use ReflectionClass;
1015
use RuntimeException;
1116

1217
trait HasObservers
1318
{
19+
/**
20+
* Boot the has observers trait for a model.
21+
*
22+
* Automatically registers any observers declared via the ObservedBy attribute.
23+
*/
24+
public static function bootHasObservers(): void
25+
{
26+
$observers = static::resolveObserveAttributes();
27+
28+
if (! empty($observers)) {
29+
static::observe($observers);
30+
}
31+
}
32+
33+
/**
34+
* Resolve the observer class names from the ObservedBy attributes.
35+
*
36+
* Collects ObservedBy attributes from parent classes, traits, and the
37+
* current class itself, merging them together. The order is:
38+
* parent class observers -> trait observers -> class observers.
39+
*
40+
* @return array<int, class-string>
41+
*/
42+
public static function resolveObserveAttributes(): array
43+
{
44+
$reflectionClass = new ReflectionClass(static::class);
45+
46+
$parentClass = get_parent_class(static::class);
47+
$hasParentWithTrait = $parentClass
48+
&& $parentClass !== HyperfModel::class
49+
&& method_exists($parentClass, 'resolveObserveAttributes');
50+
51+
// Collect attributes from traits, then from the class itself
52+
$attributes = new Collection();
53+
54+
foreach ($reflectionClass->getTraits() as $trait) {
55+
foreach ($trait->getAttributes(ObservedBy::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
56+
$attributes->push($attribute);
57+
}
58+
}
59+
60+
foreach ($reflectionClass->getAttributes(ObservedBy::class, ReflectionAttribute::IS_INSTANCEOF) as $attribute) {
61+
$attributes->push($attribute);
62+
}
63+
64+
// Process all collected attributes
65+
$observers = $attributes
66+
->map(fn (ReflectionAttribute $attribute) => $attribute->getArguments())
67+
->flatten();
68+
69+
// Prepend parent's observers if applicable
70+
return $observers
71+
->when($hasParentWithTrait, function (Collection $attrs) use ($parentClass) {
72+
/** @var class-string $parentClass */
73+
return (new Collection($parentClass::resolveObserveAttributes()))
74+
->merge($attrs);
75+
})
76+
->all();
77+
}
78+
1479
/**
1580
* Register observers with the model.
1681
*

0 commit comments

Comments
 (0)