Repositories
Apiato provides a powerful repository pattern implementation based on the L5 Repository package.
To prevent overlap with the L5 Repository documentation, this page exclusively delves into Apiato distinct features and configurations, offering only a limited set of examples. To learn more about the L5 Repository package, please refer to its documentation.
Repositories play a crucial role in software architecture by separating and abstracting the data layer from the business logic. They serve as an essential architectural pattern to promote code separation, flexibility, and maintainability in software development. Repositories help create clean and organized codebases by abstracting the intricacies of data access and manipulation from the core business logic.
To generate new repositories,
you may use the apiato:make:repository
interactive command:
php artisan apiato:make:repository
You can also generate a model and its repository at the same time by using the apiato:make:model
interactive command:
php artisan apiato:make:model
Rules
- All Repositories:
- MUST be placed in the
app/Containers/{section}/{container}/Data/Repositories
directory. - MUST extend the
App\Ship\Parents\Repositories\Repository
class.- The parent extension SHOULD be aliased as
ParentRepository
.
- The parent extension SHOULD be aliased as
- SHOULD be named after the model they are associated with, followed by the
Repository
suffix. For instance,UserRepository.php
.
- MUST be placed in the
- A Model MUST always get accessed through its Repository.
- Every Model MUST have a Repository.
Folder Structure
app
└── Containers
└── Section
└── Container
└── Data
└── Repositories
├── UserRepository.php
└── ...
Code Example
use App\Ship\Parents\Repositories\Repository as ParentRepository;
class UserRepository extends ParentRepository
{
protected $fieldSearchable = [
'id' => '=',
'name' => 'like',
'email' => '=',
'email_verified_at' => '=',
'created_at' => '=',
];
}
Configuration
All the configuration options for this feature are located in the app/Ship/Configs/repository.php
config file.
Binding Models
Once you have created a repository, you need to associate it with its corresponding model. You can either let Apiato automatically discover the model or manually bind the model to the repository.
Model Discovery
By default, Apiato automatically discovers and associates your repositories with their corresponding models as long as the model and repository follow standard Apiato naming conventions.
However, if these conventions do not apply to your particular application, you may configure the model discovery logic via the Apiato Configuration class.
Manual Model Binding
If you prefer to manually bind the model to the repository,
you can do so by implementing the model
method in your repository class.
This method should return the fully qualified class name of the model.
use App\Ship\Parents\Repositories\Repository as ParentRepository;
class DemoRepository extends ParentRepository
{
public function model(): string
{
return AnotherDemo::class;
}
}
Pagination
Pagination is automatically applied when you use the paginate
method with a model repository:
{
$this->userRepository->paginate();
}
To move between pages of results, you can use the page
query parameter like this:
api.apiato.test/v1/users?page=200
You can use the page
parameter in any GET
request that lists records.
Whenever a request returns paginated results, you'll find information about the pagination in the meta section of the response, which tells you things like the total number of records, the number on the current page, and more.
"data": [...],
"meta": {
"pagination": {
"total": 2000,
"count": 30,
"per_page": 10,
"current_page": 20,
"total_pages": 200,
"links": []
}
}
Limiting Results Per Page
You can control the number of results displayed on a single page using the limit
parameter.
By default, the pagination limit is set to 10.
If you don't specify the ?limit=
parameter in your request, the response will contain 10 records per page.
You can adjust the default pagination limit in the .env
file:
PAGINATION_LIMIT_DEFAULT=10
For instance, if you want to receive 100 resources per page, add the ?limit=100
query parameter to your request:
api.apiato.test/v1/users?limit=100
This will return 100 resources within a single page of results.
You can also combine the limit
and page
query parameters to access the next 100 resources:
api.apiato.test/v1/users?limit=100&page=2
Maximum Pagination Limit
You can also set the maximum number of resources
that can be returned in a single page by setting the $maxPaginationLimit
property in your repository class.
For example, to set the maximum number of resources to 20, you can do the following:
protected $maxPaginationLimit = 20;
Disabling Pagination
You can allow clients to request all data that matches their criteria and disable pagination,
which can be applied either project-wide or on a per-repository basis.
This enables a request to retrieve all matching data by specifying limit=0
.
To retrieve all matching entities in a single page, you can use the following query parameter:
api.apiato.test/v1/users?limit=0
Project-Wide
To allow disabling pagination for the entire project, set PAGINATION_SKIP=true
in the .env
file.
Per Repository
If you wish to allow disabling pagination for a specific repository,
set the $allowDisablePagination
property in your repository class.
Per-repository configurations take precedence and override the global settings.
RequestCriteria
RequestCriteria is a standard Criteria implementation that enables filters to be applied to the repository based on parameters sent in the request. It allows for dynamic searches, data filtering, and query customization.
To use RequestCriteria, you need to apply it to the repository.
Apiato provides two methods, addRequestCriteria
and removeRequestCriteria
, which are available on all repositories.
Here's an example of how to use RequestCriteria:
use App\Containers\AppSection\User\Data\Repositories\UserRepository;
use App\Ship\Parents\Tasks\Task as ParentTask;
class ListUsersTask extends ParentTask
{
public function __construct(
protected readonly UserRepository $repository
) {
}
public function run(): mixed
{
// $this->repository->removeRequestCriteria();
return $this->repository->addRequestCriteria()->paginate();
}
}
It's important to note
that using the removeRequestCriteria
method is only relevant if you have previously applied RequestCriteria
.
If it hasn't been applied, there is no need to remove it,
as RequestCriteria is not automatically applied unless explicitly used.
Sorting and Ordering
You can use the orderBy
and sortedBy
parameters to sort the data in the response.
The sortedBy
parameter is typically employed in conjunction with the orderBy
parameter.
By default, the orderBy
parameter sorts the data in Ascending order.
To sort the data in Descending order, you can include sortedBy=desc
.
For example:
?orderBy=id&sortedBy=asc
?orderBy=created_at&sortedBy=desc
?orderBy=name&sortedBy=asc
You can use the following values for the sortedBy
parameter:
asc
for Ascending.desc
for Descending.
Searching
There is much more to searching than what is covered here. Please refer to the L5 Repository documentation for more details.
When the RequestCriteria is enabled for a specific route,
you can use the search
parameter in GET
HTTP requests on that route to perform searches.
To make the search feature work, you need to specify which fields from the model should be searchable.
In your repository,
set the fieldSearchable
field with the names of the fields
you want to make searchable or specify a relation to the fields.
Here's an example:
protected $fieldSearchable = [
'name',
'email',
'product.name'
];
You can also set the type of condition that will be used to perform the query. The default condition is "=":
protected $fieldSearchable = [
'name' => 'like',
'email', // Default Condition "="
'your_field' => 'condition'
];
You can use the search
parameter in various ways:
?search=John
?search=name:John
?search=name:John%20Doe
Replace spaces with
%20
in the URL (e.g.,search=keyword%20here
).
Searching with Hashed IDs
If you have Hash ID enabled and want to search a hashed field,
like a user ID, you need to instruct the RequestCriteria
to decode it before performing the search.
For example, if you have a search query like this:
?search=id:XbPW7awNkzl83LD6;parent_id:aYOxlpzRMwrX3gD7;some_hashed_id:NxOpZowo9GmjKqdR
You should update your addRequestCriteria
method as follows:
$this->repository->addRequestCriteria(['id', 'parent_id', 'some_hashed_id'])->all();
Caching
L5 Repository supports caching of queries.
This feature is disabled
by default.
You can enable caching in the app/Ship/Configs/repository.php
config or the .env
file.
'cache' => [
'enabled' => true,
],
ELOQUENT_QUERY_CACHE=true
As per the L5 Repository documentation,
you need to implement the CacheableRepository
interface
and use the CacheableRepositoryTrait
trait in your repository class to enable caching.
However, Apiato already implements these two requirements in the parent Repository
class,
Skipping Cache
You can skip the cache for a specific request by adding the ?skipCache=true
query parameter to the request URL.
?skipCache=true
It's not recommended to skip the cache, but it's useful for testing purposes.
Eager Loading Includes
By default, Apiato automatically eager-loads the relationships specified by the include query parameter. This preloads the requested related data to optimize performance.
You can disable this feature by overriding the shouldEagerLoadIncludes
method in your repository class.
public function shouldEagerLoadIncludes(): bool
{
return false;
}
Apiato does not automatically eager-load the default includes.
Considerations
The include
parameter must correspond to a valid relationship on the model.
Apiato will automatically infer the correct relation name from the specified include
.
If the include
parameter does not match any relationship on the model,
Apiato will not throw an error and the relationship will not be loaded.
Here are some examples of how the include
parameter is mapped to the relationship name:
Include Name | Guessed Relation Name |
---|---|
user-address | userAddress |
user_address | userAddress |
userAddress | userAddress |
user-address-city | userAddressCity |
user_address_city | userAddressCity |
userAddressCity | userAddressCity |