apinto's avatar

Best Practice for a Helper Class

Hello, I'm planning the structure of a Lumen APP and I'm in doubt about the best practices (I'm quite new to Laravel/Lumen/MVC and still learning the best way to do things).

This is a very simple app that just logins (and saves cookies) to a external website and scraps the conte nt of the page (using Guzzle), after that the content is organized and shown in one view.

I understand that I need to create a route and assign a controller to that route, this controller should do all the logic in processing the data retrieved from the external website, however I feel that retrieving the data should be on another controller (maybe a Helper Controller?).

What is the best approach in this case?

Thank you!

0 likes
12 replies
gabrielbuzzi's avatar

Create a HelperServiceProvider

class HelperServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        require base_path('/app/Helpers/SomethingHelper.php');
    }
}

in config/app.php

App\Providers\HelperServiceProvider::class,

This way you just need to call the class in any controller, model or view.

1 like
MikeHopley's avatar
Level 17

I understand that I need to create a route and assign a controller to that route, this controller should do all the logic in processign the data retrieved from the external website, however I feel that retrieving the data should be on another controller (maybe a Helper Controller?).

I feel your thinking is too constrained by the MVC pattern. This is a common problem when you are learning about MVC (it's a problem I had). Most tutorials will give you the impression that everything is either a model, view, or controller. Often they also suggest that each "thing" is made from one model, one view, and one controller. That is highly misleading.

Here's how I think of it:

First you need a route. Your routes file(s) is like a "map" of your application. It shows you all the possible places visitors can go. Having this high-level overview is useful.

Each route sends you to a controller action. Think of controllers like "directing traffic". Often a single controller will handle traffic coming in from several routes.

Controllers should not be too concerned about the details of how something is done. It's good to keep your controllers short. You can achieve this by using other classes in your controllers.

Sometimes these classes are models, but often they would be something else. Often we call them "service classes". For example, you are retrieving data from an external website using Guzzle. You could create a ContentRetriever class that handles this task:

namespace App\Services

use GuzzleHttp\Client;

class ContentRetriever {

    public function __construct()
    {
        $this->client = new Client;
    }
    
    public function get($url)
    {
        // In reality this might be many lines of code

        return $this->$client->request('GET', $url);    
    }

    // More methods here...

}

...then you can just use this in your controller:

use App\Services\ContentRetriever;

class SomeController extends Controller {

    public function __construct(ContentRetriever $retriever)
    {
        $this->retriever = $retriever;
    }

    public function showContent($url)
    {
        $content = $this->retriever->get($url);

        return view('showContent', compact('content'));
    }

}

Notice that we've kept implementation details out of the controller. The controller uses the ContentRetriever service without needing to know how it works.

4 likes
MikeHopley's avatar

@gabrelbuzzi I don't see the benefit that you're getting from registering a service provider here. Why not just make a class and use it? Surely that is simpler?

1 like
apinto's avatar

@MikeHopley your answer was just amazing, THANK YOU! You made it very clear and simple, I couldn't have asked for anything better :)

Regarding @gabrelbuzzi awnser (that was also useful and Service Providers where the path I was planning to follow, Thank you); What would be the pros and cons of the Service Provider approach? I'm asking this not to question your suggestion but to learn :)

Thank you!

gabrielbuzzi's avatar

@MikeHopley I don't remember why, but i did this once in a project for a reason and since that i always do this way.

1 like
MikeHopley's avatar

Service providers allow you to register bindings in the application.

Let's say you have a class that requires several dependencies to work. Maybe these dependencies have their own dependencies too. Then every time you use it, you will need to call:

// First make the dependencies themselves
$profiler = new SystemProfiler(new System);
$organiser = new SprocketOrganiser(new Sprocket);
$manager = new PackageManager;

// Now we can make the actual Thing we want
$thing = new ComplexThing($profiler, $organiser, $manager); 

This could get pretty tedious. What's more, sometimes you might want to swap out a dependency for a different implementation.

Instead, you can register bindings in the IoC container. That sounds really technical, but it boils down to making it easier to create the object. Once your service provider is set up properly, the application knows how to make a new Thing:

$thing = App::make(ComplexThing::class);
3 likes
dhonions's avatar

I've just completed implementing my first Service Provider and Facades which is a revelation.

@MikeHopley Your explanation is more succinct than the laravel docs and even the Laracasts and I wish I'd found it a week ago when I was getting my head around the concepts. Once the penny drops Laravel makes an awful lot more sense - I guess that's the journey. Onwards :)

2 likes
MikeHopley's avatar

It's a pleasure to help. :)

I've found Laracasts and its community immensely helpful -- it's taken me from "spaghetti code" to "fairly competent". If I can pass on some of that, all the better.

1 like
MikeHopley's avatar

Oh, the other thing worth noting is that you don't have to use service providers to register your bindings. For example, you can stuff them all in /bootstrap/app.php if you want.

But using service providers can help keep things more organised, especially when you have a bunch of related bindings under one provider.

1 like
MikeHopley's avatar

I think my example was slightly wrong! The Laravel documentation says:

There is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed on how to build these objects, since it can automatically resolve these objects using reflection.

In other words, App::make(ComplexThing::class) should work without needing any bindings declared.

1 like
apinto's avatar

@MikeHopley you got yourself a fan :) It's beautiful to see someone taking such care on providing the best help they can.

:)

1 like
MikeHopley's avatar

Sometimes I forget how clever Laravel is. ;)

And usually you don't even need App::make, as Laravel will automatically resolve through type-hinting:

public function __construct(ComplexThing $thing)
{
    $this->thing = $thing;
}

...or in controllers, it's even wired up so you can use type-hinting in any method, not just the constructor.

Please or to participate in this conversation.