Skip to content

Commit efcc4d9

Browse files
committed
Merge branch 'main' into feature/observed-by-attribute
2 parents af4f69e + 74248e7 commit efcc4d9

File tree

28 files changed

+1680
-45
lines changed

28 files changed

+1680
-45
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ composer.lock
66
.phpunit.result.cache
77
!tests/Foundation/fixtures/hyperf1/composer.lock
88
tests/Http/fixtures
9+
.env

src/broadcasting/src/BroadcastManager.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
use Pusher\Pusher;
3535

3636
/**
37-
* @mixin \Hypervel\Broadcasting\Contracts\Broadcaster
37+
* @mixin \Hypervel\Broadcasting\Broadcasters\Broadcaster
3838
*/
3939
class BroadcastManager implements BroadcastingFactoryContract
4040
{

src/broadcasting/src/BroadcastPoolProxy.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,21 @@
66

77
use Hyperf\HttpServer\Contract\RequestInterface;
88
use Hypervel\Broadcasting\Contracts\Broadcaster;
9+
use Hypervel\Broadcasting\Contracts\HasBroadcastChannel;
910
use Hypervel\ObjectPool\PoolProxy;
1011

1112
class BroadcastPoolProxy extends PoolProxy implements Broadcaster
1213
{
14+
/**
15+
* Register a channel authenticator.
16+
*/
17+
public function channel(HasBroadcastChannel|string $channel, callable|string $callback, array $options = []): static
18+
{
19+
$this->__call(__FUNCTION__, func_get_args());
20+
21+
return $this;
22+
}
23+
1324
public function auth(RequestInterface $request): mixed
1425
{
1526
return $this->__call(__FUNCTION__, func_get_args());

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ public function getCasts(): array
6464
return static::$castsCache[static::class] = array_merge([$this->getKeyName() => $this->getKeyType()], $this->casts, $this->casts());
6565
}
6666

67-
return static::$castsCache[static::class] = $this->casts;
67+
return static::$castsCache[static::class] = array_merge($this->casts, $this->casts());
6868
}
6969

7070
/**

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

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,27 @@
66

77
use Hyperf\Stringable\Str;
88

9+
/**
10+
* Provides ULID primary key support for Eloquent models.
11+
*/
912
trait HasUlids
1013
{
1114
/**
12-
* Add a unique identifier to the model before it is created.
15+
* Boot the trait.
1316
*/
14-
public function creating(): void
17+
public static function bootHasUlids(): void
1518
{
16-
foreach ($this->uniqueIds() as $column) {
17-
if (empty($this->{$column})) {
18-
$this->{$column} = $this->newUniqueId();
19+
static::registerCallback('creating', function (self $model): void {
20+
foreach ($model->uniqueIds() as $column) {
21+
if (empty($model->{$column})) {
22+
$model->{$column} = $model->newUniqueId();
23+
}
1924
}
20-
}
25+
});
2126
}
2227

2328
/**
24-
* Generate a new UUID for the model.
29+
* Generate a new ULID for the model.
2530
*/
2631
public function newUniqueId(): string
2732
{

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,23 @@
66

77
use Hyperf\Stringable\Str;
88

9+
/**
10+
* Provides UUID primary key support for Eloquent models.
11+
*/
912
trait HasUuids
1013
{
1114
/**
12-
* Add a unique identifier to the model before it is created.
15+
* Boot the trait.
1316
*/
14-
public function creating(): void
17+
public static function bootHasUuids(): void
1518
{
16-
foreach ($this->uniqueIds() as $column) {
17-
if (empty($this->{$column})) {
18-
$this->{$column} = $this->newUniqueId();
19+
static::registerCallback('creating', function (self $model): void {
20+
foreach ($model->uniqueIds() as $column) {
21+
if (empty($model->{$column})) {
22+
$model->{$column} = $model->newUniqueId();
23+
}
1924
}
20-
}
25+
});
2126
}
2227

2328
/**

src/core/src/Database/Eloquent/Relations/BelongsToMany.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use Closure;
88
use Hyperf\Database\Model\Relations\BelongsToMany as BaseBelongsToMany;
9+
use Hypervel\Database\Eloquent\Relations\Concerns\InteractsWithPivotTable;
910
use Hypervel\Database\Eloquent\Relations\Concerns\WithoutAddConstraints;
1011
use Hypervel\Database\Eloquent\Relations\Contracts\Relation as RelationContract;
1112

@@ -44,6 +45,7 @@
4445
*/
4546
class BelongsToMany extends BaseBelongsToMany implements RelationContract
4647
{
48+
use InteractsWithPivotTable;
4749
use WithoutAddConstraints;
4850

4951
/**
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Hypervel\Database\Eloquent\Relations\Concerns;
6+
7+
use Hyperf\Collection\Collection;
8+
use Hyperf\Database\Model\Relations\Pivot;
9+
10+
/**
11+
* Overrides Hyperf's InteractsWithPivotTable to support pivot model events.
12+
*
13+
* When a custom pivot class is specified via `->using()`, operations like
14+
* attach/detach/update use model methods (save/delete) instead of raw queries,
15+
* enabling model events (creating, created, deleting, deleted, etc.) to fire.
16+
*
17+
* Without `->using()`, the parent's performant bulk query behavior is preserved.
18+
*/
19+
trait InteractsWithPivotTable
20+
{
21+
/**
22+
* Attach a model to the parent.
23+
*
24+
* @param mixed $id
25+
* @param bool $touch
26+
*/
27+
public function attach($id, array $attributes = [], $touch = true)
28+
{
29+
if ($this->using) {
30+
$this->attachUsingCustomClass($id, $attributes);
31+
} else {
32+
parent::attach($id, $attributes, $touch);
33+
34+
return;
35+
}
36+
37+
if ($touch) {
38+
$this->touchIfTouching();
39+
}
40+
}
41+
42+
/**
43+
* Attach a model to the parent using a custom class.
44+
*
45+
* @param mixed $ids
46+
*/
47+
protected function attachUsingCustomClass($ids, array $attributes)
48+
{
49+
$records = $this->formatAttachRecords(
50+
$this->parseIds($ids),
51+
$attributes
52+
);
53+
54+
foreach ($records as $record) {
55+
$this->newPivot($record, false)->save();
56+
}
57+
}
58+
59+
/**
60+
* Detach models from the relationship.
61+
*
62+
* @param mixed $ids
63+
* @param bool $touch
64+
*/
65+
public function detach($ids = null, $touch = true)
66+
{
67+
if ($this->using) {
68+
$results = $this->detachUsingCustomClass($ids);
69+
} else {
70+
return parent::detach($ids, $touch);
71+
}
72+
73+
if ($touch) {
74+
$this->touchIfTouching();
75+
}
76+
77+
return $results;
78+
}
79+
80+
/**
81+
* Detach models from the relationship using a custom class.
82+
*
83+
* @param mixed $ids
84+
* @return int
85+
*/
86+
protected function detachUsingCustomClass($ids)
87+
{
88+
$results = 0;
89+
90+
$pivots = $this->getCurrentlyAttachedPivots($ids);
91+
92+
foreach ($pivots as $pivot) {
93+
$results += $pivot->delete();
94+
}
95+
96+
return $results;
97+
}
98+
99+
/**
100+
* Update an existing pivot record on the table.
101+
*
102+
* @param mixed $id
103+
* @param bool $touch
104+
*/
105+
public function updateExistingPivot($id, array $attributes, $touch = true)
106+
{
107+
if ($this->using) {
108+
return $this->updateExistingPivotUsingCustomClass($id, $attributes, $touch);
109+
}
110+
111+
return parent::updateExistingPivot($id, $attributes, $touch);
112+
}
113+
114+
/**
115+
* Update an existing pivot record on the table via a custom class.
116+
*
117+
* @param mixed $id
118+
* @return int
119+
*/
120+
protected function updateExistingPivotUsingCustomClass($id, array $attributes, bool $touch)
121+
{
122+
$pivot = $this->getCurrentlyAttachedPivots($id)->first();
123+
124+
$updated = $pivot ? $pivot->fill($attributes)->isDirty() : false;
125+
126+
if ($updated) {
127+
$pivot->save();
128+
}
129+
130+
if ($touch) {
131+
$this->touchIfTouching();
132+
}
133+
134+
return (int) $updated;
135+
}
136+
137+
/**
138+
* Get the pivot models that are currently attached.
139+
*
140+
* @param mixed $ids
141+
*/
142+
protected function getCurrentlyAttachedPivots($ids = null): Collection
143+
{
144+
$query = $this->newPivotQuery();
145+
146+
if ($ids !== null) {
147+
$query->whereIn($this->relatedPivotKey, $this->parseIds($ids));
148+
}
149+
150+
return $query->get()->map(function ($record) {
151+
/** @var class-string<Pivot> $class */
152+
$class = $this->using ?: Pivot::class;
153+
154+
return $class::fromRawAttributes($this->parent, (array) $record, $this->getTable(), true)
155+
->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey);
156+
});
157+
}
158+
159+
/**
160+
* Create a new pivot model instance.
161+
*
162+
* Overrides parent to include pivotValues in the attributes.
163+
*
164+
* @param bool $exists
165+
*/
166+
public function newPivot(array $attributes = [], $exists = false)
167+
{
168+
$attributes = array_merge(
169+
array_column($this->pivotValues, 'value', 'column'),
170+
$attributes
171+
);
172+
173+
/** @var Pivot $pivot */
174+
$pivot = $this->related->newPivot(
175+
$this->parent,
176+
$attributes,
177+
$this->table,
178+
$exists,
179+
$this->using
180+
);
181+
182+
return $pivot->setPivotKeys($this->foreignPivotKey, $this->relatedPivotKey);
183+
}
184+
}

src/core/src/Database/Eloquent/Relations/MorphPivot.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,46 @@
44

55
namespace Hypervel\Database\Eloquent\Relations;
66

7-
use Hyperf\Database\Model\Relations\MorphPivot as BaseMorphPivot;
7+
use Hyperf\DbConnection\Model\Relations\MorphPivot as BaseMorphPivot;
8+
use Hypervel\Database\Eloquent\Concerns\HasAttributes;
9+
use Hypervel\Database\Eloquent\Concerns\HasCallbacks;
810
use Hypervel\Database\Eloquent\Concerns\HasObservers;
911

1012
class MorphPivot extends BaseMorphPivot
1113
{
14+
use HasAttributes;
15+
use HasCallbacks;
1216
use HasObservers;
17+
18+
/**
19+
* Delete the pivot model record from the database.
20+
*
21+
* Overrides parent to fire deleting/deleted events even for composite key pivots,
22+
* while maintaining the morph type constraint.
23+
*/
24+
public function delete(): mixed
25+
{
26+
// If pivot has a primary key, use parent's delete which fires events
27+
if (isset($this->attributes[$this->getKeyName()])) {
28+
return parent::delete();
29+
}
30+
31+
// For composite key pivots, manually fire events around the raw delete
32+
if ($this->fireModelEvent('deleting') === false) {
33+
return 0;
34+
}
35+
36+
$query = $this->getDeleteQuery();
37+
38+
// Add morph type constraint (from Hyperf's MorphPivot::delete())
39+
$query->where($this->morphType, $this->morphClass);
40+
41+
$result = $query->delete();
42+
43+
$this->exists = false;
44+
45+
$this->fireModelEvent('deleted');
46+
47+
return $result;
48+
}
1349
}

0 commit comments

Comments
 (0)