Skip to content

Commit da552f1

Browse files
committed
Merge branch 'master' into 3.2-merge
# Conflicts: # .github/workflows/test.yml # src/collection/tests/ArrTest.php # src/pipeline/composer.json # src/pipeline/tests/PipelineTest.php # src/support/src/Fluent.php # src/support/src/Traits/InteractsWithData.php # src/support/tests/FluentTest.php
2 parents a9299e5 + beffa3a commit da552f1

14 files changed

+593
-0
lines changed

src/Model/Builder.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -333,6 +333,18 @@ public function whereKeyNot($id)
333333
return $this->where($this->model->getQualifiedKeyName(), '!=', $id);
334334
}
335335

336+
/**
337+
* Exclude the given models from the query results.
338+
*/
339+
public function except(iterable|Model $models): static
340+
{
341+
return $this->whereKeyNot(
342+
$models instanceof Model
343+
? $models->getKey()
344+
: ModelCollection::wrap($models)->modelKeys()
345+
);
346+
}
347+
336348
/**
337349
* Add a basic where clause to the query.
338350
*
@@ -592,6 +604,18 @@ public function updateOrCreate(array $attributes, array $values = [])
592604
});
593605
}
594606

607+
/**
608+
* Create a record matching the attributes, or increment the existing record.
609+
*/
610+
public function incrementOrCreate(array $attributes, string $column = 'count', float|int $default = 1, float|int $step = 1, array $extra = []): Model
611+
{
612+
return tap($this->firstOrCreate($attributes, [$column => $default]), function ($instance) use ($column, $step, $extra) {
613+
if (! $instance->wasRecentlyCreated) {
614+
$instance->increment($column, $step, $extra);
615+
}
616+
});
617+
}
618+
595619
/**
596620
* Execute the query and get the first result or throw an exception.
597621
*

src/Model/Casts/ArrayObject.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Database\Model\Casts;
14+
15+
use ArrayObject as BaseArrayObject;
16+
use Hyperf\Collection\Collection;
17+
use Hyperf\Contract\Arrayable;
18+
use JsonSerializable;
19+
20+
/**
21+
* @template TKey of array-key
22+
* @template TItem
23+
* @extends BaseArrayObject<TKey, TItem>
24+
*/
25+
class ArrayObject extends BaseArrayObject implements Arrayable, JsonSerializable
26+
{
27+
/**
28+
* Get a collection containing the underlying array.
29+
*/
30+
public function collect(): Collection
31+
{
32+
return new Collection($this->getArrayCopy());
33+
}
34+
35+
/**
36+
* Get the instance as an array.
37+
*/
38+
public function toArray(): array
39+
{
40+
return $this->getArrayCopy();
41+
}
42+
43+
/**
44+
* Get the array that should be JSON serialized.
45+
*/
46+
public function jsonSerialize(): array
47+
{
48+
return $this->getArrayCopy();
49+
}
50+
}

src/Model/Casts/AsArrayObject.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Database\Model\Casts;
14+
15+
use Hyperf\Codec\Json;
16+
use Hyperf\Contract\Castable;
17+
use Hyperf\Contract\CastsAttributes;
18+
19+
class AsArrayObject implements Castable
20+
{
21+
/**
22+
* Get the caster class to use when casting from / to this cast target.
23+
* @return CastsAttributes<ArrayObject<array-key, mixed>, iterable>
24+
*/
25+
public static function castUsing(): CastsAttributes
26+
{
27+
return new class implements CastsAttributes {
28+
public function get($model, $key, $value, $attributes)
29+
{
30+
if (! isset($attributes[$key])) {
31+
return;
32+
}
33+
34+
$data = Json::decode($attributes[$key]);
35+
36+
return is_array($data) ? new ArrayObject($data, ArrayObject::ARRAY_AS_PROPS) : null;
37+
}
38+
39+
public function set($model, $key, $value, $attributes): array
40+
{
41+
return [$key => Json::encode($value)];
42+
}
43+
44+
public function serialize($model, string $key, $value, array $attributes)
45+
{
46+
return $value->getArrayCopy();
47+
}
48+
};
49+
}
50+
}

src/Model/Model.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,14 @@ public function setPerPage($perPage)
11971197
return $this;
11981198
}
11991199

1200+
/**
1201+
* Determine if the model is soft deletable.
1202+
*/
1203+
public static function isSoftDeletable(): bool
1204+
{
1205+
return in_array(SoftDeletes::class, class_uses_recursive(static::class));
1206+
}
1207+
12001208
/**
12011209
* Determine if the given attribute exists.
12021210
*/

src/Model/Relations/BelongsTo.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,16 @@ public function getRelation()
312312
return $this->relationName;
313313
}
314314

315+
/**
316+
* Alias of "dissociate" method.
317+
*
318+
* @return Model
319+
*/
320+
public function disassociate()
321+
{
322+
return $this->dissociate();
323+
}
324+
315325
/**
316326
* Gather the keys from an array of related models.
317327
*

src/Query/Builder.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,48 @@ public function orWhereJsonDoesntContain($column, $value)
14781478
return $this->whereJsonDoesntContain($column, $value, 'or');
14791479
}
14801480

1481+
/**
1482+
* Add a where between columns statement using a value to the query.
1483+
* @param array{Expression|string, Expression|string} $columns
1484+
*/
1485+
public function whereValueBetween(mixed $value, array $columns, string $boolean = 'and', bool $not = false): static
1486+
{
1487+
$type = 'valueBetween';
1488+
1489+
$this->wheres[] = compact('type', 'value', 'columns', 'boolean', 'not');
1490+
1491+
$this->addBinding($value, 'where');
1492+
1493+
return $this;
1494+
}
1495+
1496+
/**
1497+
* Add an or where between columns statement using a value to the query.
1498+
* @param array{Expression|string, Expression|string} $columns
1499+
*/
1500+
public function orWhereValueBetween(mixed $value, array $columns): static
1501+
{
1502+
return $this->whereValueBetween($value, $columns, 'or');
1503+
}
1504+
1505+
/**
1506+
* Add a where not between columns statement using a value to the query.
1507+
* @param array{Expression|string, Expression|string} $columns
1508+
*/
1509+
public function whereValueNotBetween(mixed $value, array $columns, string $boolean = 'and'): static
1510+
{
1511+
return $this->whereValueBetween($value, $columns, $boolean, true);
1512+
}
1513+
1514+
/**
1515+
* Add an or where not between columns statement using a value to the query.
1516+
* @param array{Expression|string, Expression|string} $columns
1517+
*/
1518+
public function orWhereValueNotBetween(mixed $value, array $columns): static
1519+
{
1520+
return $this->whereValueNotBetween($value, $columns, 'or');
1521+
}
1522+
14811523
/**
14821524
* Add a "where JSON overlaps" clause to the query.
14831525
*/

src/Query/Grammars/Grammar.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,20 @@ protected function compileColumns(Builder $query, $columns): ?string
457457
return $select . $this->columnize($columns);
458458
}
459459

460+
/**
461+
* Compile a "value between" where clause.
462+
*/
463+
protected function whereValueBetween(Builder $query, array $where): string
464+
{
465+
$between = $where['not'] ? 'not between' : 'between';
466+
467+
$min = $this->wrap(is_array($where['columns']) ? reset($where['columns']) : $where['columns'][0]);
468+
469+
$max = $this->wrap(is_array($where['columns']) ? end($where['columns']) : $where['columns'][1]);
470+
471+
return $this->parameter($where['value']) . ' ' . $between . ' ' . $min . ' and ' . $max;
472+
}
473+
460474
/**
461475
* Compile the "from" portion of the query.
462476
*

tests/DatabaseIntegrationTest.php

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,6 +264,53 @@ public function testWithWhereHasOnNestedSelfReferencingHasManyRelationship()
264264
$this->assertSame($results->first()->childPosts->pluck('childPosts')->flatten()->pluck('name')->unique()->toArray(), ['Child Post']);
265265
}
266266

267+
public function testIncrementOrCreate()
268+
{
269+
$user1 = ModelTestUser::incrementOrCreate(['email' => 'test@example.com'], 'value');
270+
$this->assertTrue($user1->wasRecentlyCreated);
271+
$this->assertSame('test@example.com', $user1->email);
272+
$this->assertEquals(1, $user1->value);
273+
274+
$user2 = ModelTestUser::incrementOrCreate(['email' => 'test@example.com'], 'value');
275+
$this->assertFalse($user2->wasRecentlyCreated);
276+
$this->assertEquals($user1->id, $user2->id);
277+
$this->assertEquals(2, $user2->value);
278+
279+
$user3 = ModelTestUser::incrementOrCreate(['email' => 'test2@example.com'], 'value', 10, 5);
280+
$this->assertTrue($user3->wasRecentlyCreated);
281+
$this->assertEquals(10, $user3->value);
282+
283+
$user4 = ModelTestUser::incrementOrCreate(['email' => 'test2@example.com'], 'value', 10, 5);
284+
$this->assertFalse($user4->wasRecentlyCreated);
285+
$this->assertEquals(15, $user4->value);
286+
287+
$user5 = ModelTestUser::incrementOrCreate(['email' => 'test3@example.com'], 'value', 1, 1, ['name' => 'Test User']);
288+
$this->assertTrue($user5->wasRecentlyCreated);
289+
$this->assertEquals(1, $user5->value);
290+
$this->assertNull($user5->name);
291+
292+
$user6 = ModelTestUser::incrementOrCreate(['email' => 'test3@example.com'], 'value', 1, 1, ['name' => 'Updated User']);
293+
$this->assertFalse($user6->wasRecentlyCreated);
294+
$this->assertEquals(2, $user6->value);
295+
$this->assertSame('Updated User', $user6->name);
296+
}
297+
298+
public function testIncrementOrCreateOnDifferentConnection()
299+
{
300+
$user1 = ModelTestUser::on('second_connection')->incrementOrCreate(['email' => 'test@example.com'], 'value');
301+
$this->assertTrue($user1->wasRecentlyCreated);
302+
$this->assertSame('second_connection', $user1->getConnectionName());
303+
$this->assertEquals(1, $user1->value);
304+
305+
$user2 = ModelTestUser::on('second_connection')->incrementOrCreate(['email' => 'test@example.com'], 'value');
306+
$this->assertFalse($user2->wasRecentlyCreated);
307+
$this->assertSame('second_connection', $user2->getConnectionName());
308+
$this->assertEquals(2, $user2->value);
309+
310+
$this->assertEquals(0, ModelTestUser::count());
311+
$this->assertEquals(1, ModelTestUser::on('second_connection')->count());
312+
}
313+
267314
protected function createSchema(): void
268315
{
269316
foreach (['default', 'second_connection'] as $connection) {

0 commit comments

Comments
 (0)