Last Updated: September 30, 2021
·
14.93K
· psycatham

Alternatives to HMVC with Laravel

HMVC is a real effective design pattern and has been widely used, however among Laravel’s community, this design pattern is considered somehow a “bad practice”.

In his book From Apprentice to Artisan, Taylor Otwell – Laravel Framework Author- suggests:

HMVC is usually an indicator of poor design

Also, In his chapter Application Structure, Taylor was speaking about how

"MVC is killing you"

and how we should not make mere conventions 'holy' to the point of never making our own way through them and breaking them.

and then right after

Say bye bye to Models!

No wonder Laravel 5 comes with no models folder.

What Taylor was suggesting in this chapter is an invitation to think outside of the box, to think outside of the usual MVC design pattern, so maybe you will have a better, elegant and easy to maintain code.

No wonder Laravel 5 comes with no 'models' folder.

So, I thought it is worthwile to entertain the ideas and suggestions Taylor was trying to communicate.

MVC, HMVC encourage Fat controllers.


Usually, following a MVC design pattern, many developers end up with what's called Fat Controllers.

Fat controllers are simply controllers that have too much business logic in them, many lines of bulky code.

Unless, you are one of the few who created the services folder, You will still suffer from these fat classes.

Solution : SoC, Seperation of Concerns

Taylor did not leave this unsolved, actually he suggested
SoC as a solution.

Great, but how do we use this?

====
<br>
Having said ByeBye! to models, and trying to think outside the box, Taylor Otwell has proposed a way in his book, that implies to create an interface which is implemented by a repository that fetches data that is relative to the object class you want.

In a more concrete fashion of explanation, Using 'SoC' will similar to something like the way he has suggested himself.

I really bought into this idea, and implemented it, and it made my controllers less fat.

however

There wasn't a way I could find in the internet to replace HMVC...

So, I came up with what I decided to call Multi-layering.

Multi-layering is a way of decoupling and making modular your application's code in analogy to the n-tiers of web development theory.

It's nothing new.

Let me explain,

Multilayering describes two main layers, the DATA-layer and the HTTP-Layer...

And each of these two has three parts.

These two interact with each other through junction points where they explicitly connect, and provide a boxed and proctected wayof fetching data from the database, of adding business logic to your controllers, and of presenting it to the outside world.

The DATA-layer divides into three parts :

  • Contracts :
  • Objects
  • Repositories

While the HTTP-Layer divides into three parts:

  • Motors
  • Traits
  • Controllers

<br><br><br>

The DATA Layer.

<br>

DATA-layer is nothing but the implementation of what Taylor Otwell has presented as suggestions in the chapter I mentioned up above.

*
In case you have not read through the link I have provided, go and do so, however I will still proceed with a similar way of explanation, and use bits of code to make my ideas clearer and more concrete.
*

Let's start with a brief definition of the three parts of this layer.

  • Contracts :

Contracts are interfaces that our repositories will implement

  • Objects:

Objects are the good old models, the classes that describe an object respective to a table in the database.

  • Repositories:

Repositories are classes that box the data retrieving from the database, they use an object class and implement an interface


Now, let's contreticize our talk with some examples.

We will emulate an example of blog application.
In this application we want to describe what a post is and use it.

Let's start with the object classes.

 //this is the object class.

 class Post extends Eloquent
    {

    protected $attributes = ['title',  'content'];

    protected $table = 'posts';

    public function someRelationship()
    {
       this->hasMany('SomeObjectClass');
    }
}

<br><br>
Next, the contracts.

//This the interface 

interface PostsInterface
{

  public function getAll();
  public function getAllAsc();
  public function getOldest();
  public function getNewest();

}

<br>
Now, the repositories, where all the magic happens.

Basically, this is where you will hide code from the rest of the application.

This should implement a certain contract between you and the rest of the application.

A contract, like the one we have written up above.

Using the contract and the objectClass we made, our repository will look someway like this:

class PostsRepositories implements PostsInterface
{

  public function getAll()
  {
    return Post::all();
  }

}

This should do it for the DATA-layer.

<br><br><br>

The HTTP Layer.

<br>

Let's also define the three parts of the this layer.

  • Motors:

Motors are the junction point between the datalayer and the httplayer, a motor is a class that injects a repository and uses a trait of actions, so it can fetch data from the database and adds any business logic to it and present it to the world if it needs to.

Later on, a motor gets injected in a controller, so that the controller can fetch data through this very motor, also call any action this motor describes/defines.

  • Traits:

A trait is a trait of actions that a controller will have through his injected motor.

  • Controllers

*Our good old controllers, but this time, injecting a motor. *

Honestly, these are all just fancy terms for the good old notion of modular code, we simply separate parts of our code in an organized way to make long term maintenance less of a nuance and save more time So don't get worked out over them.

*Paying respect to them is but a convenience to facilitate teamwork. So please do so. *

I'll start this part by shedding light on Motors.

Basically, To each Repository we will need to fetch, there should be a motor.

Each motor extends from a base class Motor.

Here is what is :

abstract class Motor 
{
 protected $repository;
 protected $views;

 use GlobalCRUDtrait;
}

So this is what will be shared between all motors, a basic CRUD trait, so we avoid duplicating code for basic CRUD actions. Also, three attributes, one that is the repository itself ,and the second that is views.

So, motors are where the repositories are injected, and are what gets injected into the controllers.

You'll make good sense of this as you go with this article.

Now, Let's implement this Motor.

Let's create a PostsMotor that interacts with Posts in the DATA layer.

//PostsRepoInterface is just an allias for PostsRepository

use PostsRepoInterface;

class PostsMotor extends Motor
{
  public function __construct(PostsRepoInterface $myRepo)
  {
     $this->repository = $myRepo;
     $this->views = 'posts';
 }

}

Okay, in here, we have injected the repository into our motor, and we are good to.

Please see that our motor is using GlobalCRUDtrait, that'll be something like this.

trait GlobalCRUDtrait
{
 public function index()
{
 $all = $this->repository->getAll();
 return view($this->views.'.index', ['all' => $all]);
}

public function show($id)
{
  $instance = $this->repository->show();
  return view($this->view.'.show', ['instance' => $instance]);

   //etc

}

And now we are ready to put it all together.

the Controller !

First,
we'll tweak the abstract controller class like this first :

abstract class Controller
{
 protected $motors = array();
}

So we'll be able to inject our motors properly.

As you can see, $motors is just an array, that'll be associative, where we'll store each motor injected.

And then we'll go to create our wanted controller.

class PostsController extends Controller
{
 public function __construct(PostsMotor $posts)
{
 $this->motors['posts'] = $posts;
}

  public function getPostsIndex()
  {
    return $this->motors['posts']->index();
  }

}

Tadaaaaa!

This way, not only have we completely decoupled the back end from the front end from the business logic, but we have made ourselves capable of escaping the HMVC way..

How?

Simply, add to your controller another motor, and use it however you want!

No need to call any controller inside of another , simply call the specific action you want.

For instance, in the HMVC, you wanted to call UsersController inside of PostsController.

Now, all you will do is create one controller, inject the two motors inside of it, and make the call you want.

Concretion

class mySingleController extends Controller
{

 public function __construct(UsersMotor $users, PostsMotor $posts)
  {
   $this->motors['users'] = $users;
   $this->motors['posts'] = $posts;
 }

  public function getUsers()
  {
   return $this->motors['users']->getAll();
  }

   public function getPosts()
   {
    return $this->motors['posts']->getAll();
    }

   }

And we are done.

Not only have we completely decoupled the layer, our code has became completely elegant.

The controllers are far from being fat, and they are quite fit and maintainable.

Also, this way, front end developers won't bother much about the underlaying code as much as they did.

The Workflow.

Remember, we have used a common CRUD trait between all motors, where we call the each repo's right methods, and return the appropriate views to each.

This will noticeably increase your workflow, specially if you have so many repositories to deal with.

All you have to do then is create a motor that inherits from the base Motor class, and make the views for it, and you are good to go! call them however you want from your motor-implement controller!

This is highly configurable, as for adding new actions to a specific motor, all you have to do is go to your motor and start describing the new action you want, and call it the regular way I mentioned above.

But, Why bother with all of these?

The real question is, why not?

This is only a way to organize code and make possible the long term maintenance, as well as produce elegant and quite easy-to-debug code.

Clear design patterns, or architectures, organization and code elegance should never be a second priority to any developer, thus, feel the need to throw laziness out of the window and do an organized, clear well done job.

Happy coding everyone.

Update: Package for Artisan Commands

So I thought, since the whole point is to speed up the workflow efficiently, I decided to make a little package to generate all of the stuff mentioned up above..


Links:

Have a fresh tip? Share with Coderwall community!

Post
Post a tip