Repositories
PointArt's repository pattern mirrors Spring Data JPA. Extend Repository, declare abstract methods, and the framework generates the implementation at runtime.
Namespaces
PointStart\ORM — RepositoryPointStart\Attributes — Query
Defining a Repository
Place repository classes in app/repositories/. Declare the class as abstract and set $entityClass:
abstract class UserRepository extends Repository {
protected string $entityClass = User::class;
// Custom SQL via #[Query]
#[Query("SELECT * FROM users WHERE name = ? AND email = ?")]
abstract public function findByNameAndEmailRaw(string $name, string $email): array;
#[Query("SELECT COUNT(*) FROM users")]
abstract public function countAll(): int;
// Dynamic finder — no body needed
abstract public function findByName(string $name): array;
}
Abstract Required
Repository subclasses must be declared
abstract. The framework calls Repository::make() at runtime to generate a concrete implementation class (UserRepository__Impl) via eval().
Built-in Methods
Every repository inherits these methods from the base Repository class:
| Method | Description |
|---|---|
find($id) | Find entity by primary key |
findAll() | Return all entities |
save($entity) | Insert or update an entity |
delete($entity) | Delete an entity |
deleteById($id) | Delete by primary key |
#[Query] — Custom SQL
Use #[Query] to attach raw SQL to an abstract method. Parameters are bound positionally in method signature order:
#[Query("SELECT * FROM users WHERE name = ? AND email = ?")]
abstract public function findByNameAndEmailRaw(string $name, string $email): array;
#[Query("SELECT COUNT(*) FROM users")]
abstract public function countAll(): int;
| Parameter | Type | Required | Description |
|---|---|---|---|
queryString | string | Yes | Raw SQL to execute. Use ? for positional parameters |
The return type drives the result shape:
| Return Type | Behaviour |
|---|---|
array | Fetch all rows, map to entity instances |
int | Scalar fetch (e.g. COUNT(*)) |
void | Execute only (e.g. INSERT, UPDATE, DELETE) |
Dynamic Finders
Declare abstract methods and the framework generates the query from the method name — no implementation required:
| Method | Generated SQL |
|---|---|
findByName($n) | WHERE name = ? |
findByNameAndEmail($n, $e) | WHERE name = ? AND email = ? |
findByAgeGreaterThan($age) | WHERE age > ? |
findByNameOrderByEmail($n) | WHERE name = ? ORDER BY email |
findOneByEmail($e) | WHERE email = ? LIMIT 1 |
countByStatus($s) | SELECT COUNT(*) WHERE status = ? |
existsByEmail($e) | Returns bool |
deleteByStatus($s) | DELETE WHERE status = ? |
Operator Suffixes
Append these suffixes to field names in dynamic finders to control the comparison operator:
| Suffix | SQL Operator | Example |
|---|---|---|
| (none) | = ? | findByName($n) |
GreaterThan | > ? | findByAgeGreaterThan($age) |
LessThan | < ? | findByPriceLessThan($max) |
GreaterThanEqual | >= ? | findByScoreGreaterThanEqual($min) |
LessThanEqual | <= ? | findByStockLessThanEqual($threshold) |
Not | != ? | findByStatusNot($status) |
Like | LIKE ? | findByNameLike($pattern) |
IsNull | IS NULL | findByDeletedAtIsNull() (no arg) |
IsNotNull | IS NOT NULL | findByEmailIsNotNull() (no arg) |
Full Example
abstract class ProductRepository extends Repository {
protected string $entityClass = Product::class;
// Custom SQL
#[Query("SELECT * FROM products WHERE active = 1")]
abstract public function findAllActive(): array;
#[Query("SELECT * FROM products WHERE price <= ?")]
abstract public function findAffordable(float $maxPrice): array;
// Dynamic finders
abstract public function findByName(string $name): array;
abstract public function findByStockLessThan(int $threshold): array;
// Count and existence checks
abstract public function countByActive(bool $active): int;
abstract public function existsByName(string $name): bool;
// Delete
abstract public function deleteByActive(bool $active): void;
}
Next: Views →