Skip to main content Skip to navigation

Guides: Hiker's Architecture

Before getting started with a project, we'd recommend getting used to the terminology and core-concepts that make Hiker so distinctive. As you may already know, we're not only providing a useful tool for flexible and modern user interfaces: Hiker's vocation is above all to define a powerful development pattern that will help keeping your projects maintainable and understandable.

When applied correctly, Hiker's methodology will reduce the gap between the application's business/user logic (UX) and its functional code architecture, as both will share a direct and clear connection.

Resources

In order to interact with database records (or even other data objects), Hiker uses Resource classes. These objects wrap the application's Laravel Model and make them usable in Hiker's UI by defining useful methods, each of them dictating how to display the Model's data correctly.

namespace App\Hiker\Resources\Posts;

use Hiker\Resource;

class Post extends Resource
{
    public static $resource = \App\Models\Post::class;

    // ...other methods
}

Generate new Resource classes using the following artisan command:

php artisan hiker:resource [resource classname]

Defining displayable resource labels

A Resource class is able to guess its labels from the classname itself. However, it is really easy to customize the resource's displayed name using the following static methods:

public static function singularLabel(): string
{
    return 'Blog post';
}

public static function pluralLabel(): string
{
    return 'Blog posts';
}

Defining a displayable icon

Why not adding a sweet little icon to the resource? It will automatically show up in the resource's search results, index pages, and so on, making the resource more recognizable for your end users.

use Hiker\Components\Icon\Icon;

public static function icon(): Icon
{
    return Icon::make('document');
}

A list of the default icons is available in the Icon component's documentation, but it is also possible to add your own.

Defining displayable base attributes

The created resource will be displayed in multiple views, such as search results or index tables. By defining resource attributes, you'll be able to prevent code redundancy for the display of popular values such as titles, names, statusses, and so on.

A resource's attributes are defined using the static getAvailableColumns method:

public static function getAvailableColumns(): array
{
    return [
        Attributes\Title::class,
        Attributes\Author::class,
        Attributes\PublishedAt::class,
        Attributes\CommentsCount::class,
    ];
}

Each of these attribute classes will typically be defined in the resource's Attributes directory. For convenience, generate them using their dedicated artisan command:

php artisan hiker:attribute [resource classname] [attribute classname]
Resource table columns

Since resource attributes can be used as table columns, the following methods add more configuration options for their usage in tables.

public static function getDefaultColumns(): array
{
    return ['title', 'author', 'published_at'];
}

public static function getDefaultOrderColumn(): string
{
    return 'published_at';
}

public static function getDefaultOrderDirection(): string
{
    return 'DESC';
}

Advanced resource configurations

Resource classes provide many other configuration possibilities! Take a look at the full Resource documentation for more information.

Flows & Roadmaps

The whole point of Hiker is to provide a robust and maintainable methodology when defining functional scenarios. These scenarios exist in every application, mostly represented by routes (like API endpoints) and controllers. When building complex features, the code that needs to be written in these controllers can quickly become very long, unreadable and therefore difficult to maintain. If you've ever been confronted to these unpleasant side-effects, you probably know it is considered good practice to apply the Action Pattern, even better when coupled to the Pipeline Pattern.

The Action Pattern's goal is to move all execution logic from the controller into a separate "Action" object. The Pipeline Pattern takes this concept even further by chaining multiple actions together, running one after another until the end result is achieved. For instance, let's say we need to generate an invoice PDF for an order: instead of writing all the required code directly in a controller, the Action Pattern would prefer to implement a GenerateInvoice action class, keeping the controller as clean as possible:

class InvoiceController
{
    public function download(InvoiceRequest $request)
    {
        $path = GenerateInvoice::execute($request)->path();

        return response()->download($path);
    }
}

There are many variations on how to implement the Action Pattern but they often end up in bloated action classes. In the example above, it is still unclear what steps are needed to generate the PDF file. This can quickly become problematic when a tiny change needs to be implemented, requiring a full understanding of the GenerateInvoice class.

That's where the Pipeline Pattern (and in our case, Hiker) comes to the rescue by considering these actions as scenarios, or, as we prefer to call them: "flows". A flow is a collection of nodes, each one representing a specific step in a more generic action's execution. The result of Hiker's Flows Pattern is a readable and understandable scheme of what happens during its execution, using an eloquent API. All the flows for a Resource are grouped in a FlowsRepository file.

In our Invoice case, it could look somthing like this:

use Hiker\Tracks\FlowsRepository;
use Hiker\Tracks\Roadmap;

class InvoiceFlows extends FlowsRepository
{
    public function create(): Roadmap
    {
        return roadmap()
            ->save(Nodes\SaveCustomerDetails::class)
            ->run(Nodes\FetchSuccesfullPayments::class)
            ->run(Nodes\ComputeTotals::class)
            ->run(Nodes\GeneratePDF::class)
            ->save(Nodes\MarkInvoiceAsDownloaded::class)
            ->download(Nodes\StreamPDF::class);
    }
}

From now on, our controller can execute this flow simply by running it:

use App\Hiker\Resources\Invoices\Invoice;

class InvoiceController
{
    public function download(InvoiceRequest $request)
    {
        $pdf = Invoice::flows()
            ->create($request)
            ->run();

        return response()->download($pdf);
    }
}

Moreover, when using Hiker's UI, you could call the flow directly from a component.

use App\Hiker\Resources\Invoices\Invoice;
use Hiker\Components\Btn\Btn;

Btn::make()
    ->label('Download invoice')
    ->flow(new Invoice(), 'create')
    ->data(['order' => $order->id]);

The main benefits of this approach are:

  • Whenever a change is requested, one should simply refer to the flow's roadmap and pinpoint the node that needs to be updated instead of reading the whole script.
  • It becomes really easy to onboard new team members, since all flows are simple to comprehend.
  • Clients or non-technical managers can be included in the conceptual process thanks to the use of understandable node classnames, creating an opportunity where Business logic can be easily translated into application functionalities.

Nodes

Each Node composing a flow/roadmap should be considered as a separate action performing a specific task in the flow's execution. These actions can be divided into 4 types, each of them represented by a few useful methods:

  • Executable nodes:
    • run
  • Data management nodes:
    • save
    • delete
  • Flow-control nodes:
    • branch
    • chain
  • Response nodes:
    • show
    • download
    • broadcast

Using the correct method for each node will enable useful automatic features in Hiker's UI, automatically responding to the flow's execution in real time. For instance, when running a save() node, the appropriate events will be broadcasted to the interface's front-end components, triggering out of the box live updates where needed.

Forms

Yet to be documented.

Views

Yet to be documented.