Advanced Generic Services

This section assumes that you have a basic understanding how Generic Services work in Pionia. If you haven’t, you can check the Generic Services section first.

Introduction

Pionia Generic Services can be used to do more than just CRUD operations. This section is also still growing immensely as we discover more ways to use Generic Services. In this section, we will look at how to use Generic Services for more advanced operations.

Relationships

The first advanced operation we will look at is relationships. Pionia Generic Services can now query relationships directly.

Remember that Pionia as the framework does not have a built-in Model Layer. Therefore, generic services are the best way to interact with related data.

Properties and Methods

This feature is only available in all services that extend the Pionia\Services\GenericService class. But by default, this feature is not activated. You have to defines some or all of the following properties and methods to activate it.

$joins

The $joins property is an array defining each table we are connecting to and the relationship between them.

Remember, that we still need our base table to be defined in the $table property.

public string $table = 'products';

public array $joins = [
    'category' => ['id' => 'category_id']
];

The above implies that we have a products table and a category table. The products table has a category_id column that relates to the id column in the category table.

$joinTypes

For each table in the $joins property, we can define the type of join to use.

public array $joinTypes = [
    'category' => JoinType::INNER
];

If you had defined an alias on your table, you can define the $joinAlias property to use the alias in the query.

    public ?array $joinAliases = [
      'category' => 'cat',
    ];

This implies that the category table will be aliased as cat in the query. This has to be reflected in all queries that use the category table including in the $listColumns property.

public ?array $listColumns = [
        'product.id(id)',
        "product.name(product_name)",
        "cat.name(category_name)",
        "cat.created_at(category_created_at)",
        "product.created_at(product_created_at)",
        "active"
    ];

AS Clause

In the entire Porm including even in the above listColumns property, you can achieve the AS clause to alias columns by using the () syntax.

public ?array $listColumns = [
        'product.id(id)', // product.id AS id
        "product.name(product_name)", // product.name AS product_name
        "cat.name(category_name)", // cat.name AS category_name
        "cat.created_at(category_created_at)", // cat.created_at AS category_created_at
        "product.created_at(product_created_at)", // product.created_at AS product_created_at
        "active"
    ];

This is applicable to all use cases of Porm.

Creation

Even in cross relationships, you are only allowed to insert in the base table. This means saving across relationships is not yet supported.

Uploading Files

Pionia Generic Services have a built-in file upload feature. This is done by defining the $fileColumns property.

Imagining the following $createColumns:-

public ?array $createColumns = [
        'name',
        'active',
        'icon_image',
    ];

If you want the icon_image to be treated as a file upload, you can define the $fileColumns property as follows:-

public ?array $fileColumns = [
        'icon_image'
    ];

This will automatically upload the file and save the file path in the database.

By default, this behaviour uploads the file to the media directory in the root of your project. But you can change this in your settings.ini file by defining the uploads section.

[uploads]
media_dir=/media
media_url=/media
max_size=20000000

From the above, the media_dir is the directory where the files will be uploaded to. The media_url is the URL to access the files. The max_size is the maximum size of the file to be uploaded.

Custom upload handler

If you don’t want to upload files to the media directory, you can define a custom upload handler by overriding the handleUpload method.

This method receives the UploadedFile object as the first argument and the fileName as the second argument.

The fileName matches the parameter that was used to receive the file in your action, in the example above it would be icon_image.

    /**
     * Provides the default upload behaviour for the service.
     *
     * You can override this method in your service to provide custom upload behaviour.
     * @param UploadedFile $file The file to upload
     * @param string $fileName The name to save the file as
     * @throws Exception
     */
    public function handleUpload(UploadedFile $file, string $fileName): mixed
    {
        return $this->defaultUpload($file, $fileName);
    }

This is the core default implementation of the handleUpload method. Once this is defined in your service, all files will be uploaded using your custom handler.

The handleUpload method should return the path to the file to be saved in the database. Whatever it returns, we will attempt to save it in the database.

Querying Relationships

This is the part where generic services shine the most. The frontend now has access to all the related data. So they can even define the columns across relationships defined above to return from the db.

If the frontend want to withdraw from querying relationships back to querying the base table, they can define the dontRelate request parameter as true.

{
  // other parameters,
    "dontRelate": true
}

The frontend can also define the COLUMNS parameter to define the columns to return from the base table.

{
  // other parameters,
    "COLUMNS": ["id", "name"]
}

If your tables are aliased, you can use the alias in the COLUMNS parameter.

{
  // other parameters,
    "COLUMNS": ["id", "name", "cat.name"]
}

Also, the frontend can achieve the AS clause by using the () syntax.

{
  // other parameters,
    "COLUMNS": ["product.id(id)", "name", "cat.name(category_name)"] // product.id AS id, name, cat.name AS category_name
}

All the other functionalities of the Generic Services are still available in the advanced operations.

When switching from relationships back to querying the base table alone, Pionia takes care of converting the listColumns however, if you had aliased your tables, you need to remember how you named your pk_field as it might no longer be id.