Skip to content

Add find or fail methods #421

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 18, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/NotFoundException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

namespace Yiisoft\ActiveRecord;

use Yiisoft\Db\Exception\Exception;

/**
* Represents an exception thrown when a record is not found.
*/
final class NotFoundException extends Exception
{
}
86 changes: 84 additions & 2 deletions src/Trait/RepositoryTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Yiisoft\ActiveRecord\ActiveQueryInterface;
use Yiisoft\ActiveRecord\ActiveRecordInterface;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\ActiveRecord\NotFoundException;

/**
* Trait to support static methods {@see find()}, {@see findOne()}, {@see findAll()}, {@see findBySql()} to find records.
Expand Down Expand Up @@ -43,6 +44,10 @@
* Returns an instance of {@see ActiveQueryInterface} instantiated by {@see ActiveRecordInterface::query()} method.
* If the `$condition` parameter is not null, it calls {@see ActiveQueryInterface::andWhere()} method.
* Do not to pass user input to this method, use {@see findByPk()} instead.
*
* @param array|ExpressionInterface|string|null $condition The condition to be applied to the query where clause.
* No condition is applied if `null` (by default).
* @param array $params The parameters to be bound to the SQL statement during execution.
*/
public static function find(array|string|ExpressionInterface|null $condition = null, array $params = []): ActiveQueryInterface
{
Expand Down Expand Up @@ -87,13 +92,38 @@
* $post = Post::findByPk($id);
* ```
*
* @param array|ExpressionInterface|string|null $condition The condition to be applied to the query where clause.
* Returns all records if `null` (by default).
* @param array $params The parameters to be bound to the SQL statement during execution.
*
* @return ActiveRecordInterface[]|array[] An array of ActiveRecord instance, or an empty array if nothing matches.
*/
public static function findAll(array|string|ExpressionInterface|null $condition = null, array $params = []): array
{
return static::find($condition, $params)->all();
}

/**
* Shortcut for {@see findAllOrFail()} method with throwing {@see NotFoundException} if no records found.
*
* ```php
* $customers = Customer::tryFindAll(['is_active' => true]);
* ```
*
* @param array|ExpressionInterface|string|null $condition The condition to be applied to the query where clause.
* Returns all records if `null` (by default).
* @param array $params The parameters to be bound to the SQL statement during execution.
*
* @throws NotFoundException
*
* @return ActiveRecordInterface[]|array[] An array of ActiveRecord instance, or throws {@see NotFoundException}
* if nothing matches.
*/
public static function findAllOrFail(array|string|ExpressionInterface|null $condition = null, array $params = []): array
{
return static::findAll($condition, $params) ?: throw new NotFoundException('No records found.');
}

/**
* Finds an ActiveRecord instance by the given primary key value.
* In the examples below, the `id` column is the primary key of the table.
Expand Down Expand Up @@ -123,17 +153,40 @@
* $customer = Customer::findByPk($id);
* }
* ```
*
* @param array|float|int|string $values The primary key value(s) to find the record.
*
* @return ActiveRecordInterface|array|null Instance matching the primary key value(s), or `null` if nothing matches.
*/
public static function findByPk(array|float|int|string $values): array|ActiveRecordInterface|null
{
return static::instantiate()->query()->findByPk($values);
}

/**
* Creates an {@see ActiveQuery} instance with a given SQL statement.
* Shortcut for {@see findByPk()} method with throwing {@see NotFoundException} if no records found.
*
* ```php
* $customer = Customer::findByPkOrFail(1);
* ```
*
* @param array|float|int|string $values The primary key value(s) to find the record.
*
* @throws NotFoundException
*
* @return ActiveRecordInterface|array|null Instance matching the primary key value(s),
* or throws {@see NotFoundException} if nothing matches.
*/
public static function findByPkOrFail(array|float|int|string $values): array|ActiveRecordInterface|null
{
return static::findByPk($values) ?? throw new NotFoundException('No records found.');
}

/**
* Creates an {@see ActiveQueryInterface} instance with a given SQL statement.
*
* Note: That because the SQL statement is already specified, calling more query modification methods
* (such as {@see where()}, {@see order()) on the created {@see ActiveQuery} instance will have no effect.
* (such as {@see where()}, {@see order()) on the created {@see ActiveQueryInterface} instance will have no effect.
*
* However, calling {@see with()}, {@see asArray()}, {@see indexBy()} or {@see resultCallback()} is still fine.
*
Expand All @@ -145,6 +198,8 @@
*
* @param string $sql The SQL statement to be executed.
* @param array $params The parameters to be bound to the SQL statement during execution.
*
* @return ActiveQueryInterface The newly created {@see ActiveQueryInterface} instance.
*/
public static function findBySql(string $sql, array $params = []): ActiveQueryInterface
{
Expand Down Expand Up @@ -183,6 +238,10 @@
* $post = Post::findByPk($id);
* ```
*
* @param array|ExpressionInterface|string|null $condition The condition to be applied to the query where clause.
* Returns the first record if `null` (by default).
* @param array $params The parameters to be bound to the SQL statement during execution.
*
* @return ActiveRecordInterface|array|null Instance matching the condition, or `null` if nothing matches.
*/
public static function findOne(
Expand All @@ -192,7 +251,30 @@
return static::find($condition, $params)->one();
}

/**
* Shortcut for {@see findOne()} method with throwing {@see NotFoundException} if no records found.
*
* ```php
* $customer = Customer::findOneOrFail(['id' => 1]);
* ```
*
* @param array|ExpressionInterface|string|null $condition The condition to be applied to the query where clause.
* Returns the first record if `null` (by default).
* @param array $params The parameters to be bound to the SQL statement during execution.
*
* @throws NotFoundException
*
* @return ActiveRecordInterface|array|null Instance matching the condition, or throws {@see NotFoundException}
* if nothing matches.
*/
public static function findOneOrFail(
array|string|ExpressionInterface|null $condition = null,
array $params = [],
): ActiveRecordInterface|array|null {
return static::findOne($condition, $params) ?? throw new NotFoundException('No records found.');
}

protected static function instantiate(): ActiveRecordInterface

Check warning on line 277 in src/Trait/RepositoryTrait.php

View workflow job for this annotation

GitHub Actions / PHP 8.4-ubuntu-latest

Escaped Mutant for Mutator "ProtectedVisibility": @@ @@ { return static::findOne($condition, $params) ?? throw new NotFoundException('No records found.'); } - protected static function instantiate(): ActiveRecordInterface + private static function instantiate(): ActiveRecordInterface { return new static(); } }
{
return new static();
}
Expand Down
46 changes: 46 additions & 0 deletions tests/RepositoryTraitTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Yiisoft\ActiveRecord\Tests;

use Yiisoft\ActiveRecord\ActiveQuery;
use Yiisoft\ActiveRecord\NotFoundException;
use Yiisoft\ActiveRecord\Tests\Stubs\ActiveRecord\Customer;

abstract class RepositoryTraitTest extends TestCase
Expand Down Expand Up @@ -45,6 +46,21 @@ public function testFindOne(): void
$this->assertNull($customer);
}

public function testFindOneOrFail(): void
{
$customerQuery = new ActiveQuery(new Customer());

$this->assertEquals(
$customerQuery->where(['id' => 1])->one(),
Customer::findOneOrFail(['id' => 1]),
);

$this->expectException(NotFoundException::class);
$this->expectExceptionMessage('No records found.');

Customer::findOneOrFail(['name' => 'user5']);
}

public function testFindAll(): void
{
$customerQuery = new ActiveQuery(new Customer());
Expand All @@ -63,6 +79,21 @@ public function testFindAll(): void
$this->assertCount(3, Customer::findAll(['id' => [1, 2, 3]]));
}

public function testFindAllOrFail(): void
{
$customerQuery = new ActiveQuery(new Customer());

$this->assertEquals(
$customerQuery->where(['id' => [1, 2, 3]])->all(),
Customer::findAllOrFail(['id' => [1, 2, 3]]),
);

$this->expectException(NotFoundException::class);
$this->expectExceptionMessage('No records found.');

Customer::findAllOrFail(['id' => 5]);
}

public function testFindByPk(): void
{
$customerQuery = new ActiveQuery(new Customer());
Expand All @@ -76,6 +107,21 @@ public function testFindByPk(): void
$this->assertNull($customer);
}

public function testFindByPkOrFail(): void
{
$customerQuery = new ActiveQuery(new Customer());

$this->assertEquals(
$customerQuery->where(['id' => 1])->one(),
Customer::findByPkOrFail(1),
);

$this->expectException(NotFoundException::class);
$this->expectExceptionMessage('No records found.');

Customer::findByPkOrFail(5);
}

public function testFindBySql(): void
{
$customerQuery = new ActiveQuery(new Customer());
Expand Down