Skip to main content
Version: 13.x

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.
    • SHOULD be named after the model they are associated with, followed by the Repository suffix. For instance, UserRepository.php.
  • 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.

note

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
note

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;
}
note

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 NameGuessed Relation Name
user-addressuserAddress
user_addressuserAddress
userAddressuserAddress
user-address-cityuserAddressCity
user_address_cityuserAddressCity
userAddressCityuserAddressCity