Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

Maintain Slim PHP MVC Frameworks with a Layered Structure


h1>{{$user->first_name}} {{$user->last_name}}h1>
ul>
@foreach($user->posts as $post)
li>{{$post->title}}li>
@endforeach
ul>
It looks straightforward, but if I rename the first_name field, suddenly I have to change all views that use this model’s field, an error-prone process. The easiest way to avoid this conundrum is to use data transfer objects, or DTOs.

DATA TRANSFER OBJECTS

Data from the service layer needs to be wrapped into a simple immutable object—meaning it can’t be changed after it is created—so we don’t need any setters for a DTO. Furthermore, the DTO class should be independent and not extend any Active Record models. Careful, though—a business model is not always the same as an AR model.
Consider a grocery delivery application. Logically, a grocery store order needs to include delivery information, but in the database, we store orders and link them to a user, and the user is linked to a delivery address. In this case, there are multiple AR models, but the upper layers shouldn’t know about them. Our DTO class will contain not only the order but also delivery information and any other parts that are in line with a business model. If we change AR models related to this business model (for example, we move delivery information into the order table) we will change only field mapping in the DTO object, rather than changing your usage of AR model fields everywhere in the code.
By employing a DTO approach, we remove the temptation to change the Active Record model in the controller or in the view. Secondly, the DTO approach solves the problem of connectivity between the physical data storage and the logical representation of an abstract business model. If something needs to be changed on the database level, the changes will affect the DTO object rather than the controllers and views. Seeing a pattern?
Let’s take a look at a simple DTO:
//Example of simple DTO class. You can add any logic of conversion from an Active Record object to business model here 
class DTO
{

private $entity;

public static function make($model)
{

return new self($model);
}

public function __construct($model)
{

$this->entity = (object) $model->toArray();
}

public function __get($name)
{

return $this->entity->{$name};
}

}
Using our new DTO is just as straightforward:
//usage example
public function user (Request $request)
{

$user = $this->userService->getUserById($request->id);
$user = DTO::make($user);
return view('user.index', compact('user'));
}

VIEW DECORATORS

For separating view logic (like choosing a button’s color based on some status), it makes sense to use an additional layer of decorators. A decorator is a design pattern that allows embellishment of a core object by wrapping it with custom methods. It usually happens in the view with a somewhat special piece of logic.
While a DTO object can perform a decorator’s job, it really only works for common actions like date formatting. A DTO should represent a business model, whereas a decorator embellishes data with HTML for specific pages.
Let’s look at a snippet of a user profile status icon that doesn’t employ a decorator:
div class="status">
@if($user->status == \App\Models\User::STATUS_ONLINE)
label class="text-primary">Onlinelabel>
@else
label class="text-danger">Offlinelabel>
@endif
div>
div class="info"> {{date('F j, Y', strtotime($user->lastOnline))}} div>
While this example is straightforward, it’d be easy for a developer to get lost in more complicated logic. This is where a decorator comes in, to clean up the HTML’s readability. Let’s expand our status icon snippet into a full decorator class:
class UserProfileDecorator
{

private $entity;

public static function decorate($model)
{

return new self($model);
}

public function __construct($model)
{

$this->entity = $model;
}

public function __get($name)
{

$methodName = 'get' . $name;
if (method_exists(self::class, $methodName)) {
return $this->$methodName();
} else {
return $this->entity->{$name};
}
}

public function __call($name, $arguments)
{

return $this->entity->$name($arguments);
}

public function getStatus()
{

if($this->entity->status == \App\Models\User::STATUS_ONLINE) {
return '';
} else {
return '';
}
}

public function getLastOnline()
{

return date('F j, Y', strtotime($this->entity->lastOnline));
}
}
Using the decorator is easy:
public function user (Request $request)
{

$user = $this->userService->getUserById($request->id);
$user = DTO::make($user);
$user = UserProfileDecorator::decorate($user);
return view('user.index', compact('user'));
}
Now we can use model attributes in the view without any conditions and logic, and it’s much more readable:
div class="status"> {{$user->status}} div>    
div class="info"> {{$user->lastOnline}} div>
Decorators also can be combined:
public function user (Request $request)
{

$user = $this->userService->getUserById($request->id);
$user = DTO::make($user);
$user = UserDecorator::decorate($user);
$user = UserProfileDecorator::decorate($user);
return view('user.index', compact('user'));
}
Each decorator will do its job and decorate only its own part. This recursive embedding of several decorators allows for a dynamic combination of their features without introducing additional classes.

The Repository Layer

The repository layer works with the concrete implementation of data storage. It’s best to inject the repository through an interface for flexibility and easy replacement. If you change your data storage, you have to create a new repository that implements your repository interface, but at least you don’t have to change the other layers.
The repository plays the role of a query object: It gets data from the database and conducts the work of several Active Record models. Active Record models, in this context, play the role of single data model entities—any object in the system that you care to model and store information about. While each entity contains information, it doesn’t know how it appeared (if it was created or obtained from the database), or how to save and change its own state. The responsibility of the repository is to save and/or update an entity; this provides better separation of concerns by keeping management of entities in the repository and making entities simpler.
Here’s a straightforward example of a repository method that builds a query using knowledge about the database and Active Record relations:
public function getUsers()
{

return User::leftjoin('posts', function ($join) {
$join->on('posts.user_id', '=', 'user.id')
->where('posts.status', '=', Post::STATUS_APPROVED);
})
->leftjoin('orders', 'orders.user_id', '=', 'user.id')
->where('user.status', '=', User::STATUS_ACTIVE)
->where('orders.price', '>', 100)
->orderBy('orders.date')
->with('info')
->get();
}

Keeping Slim with Single Responsibility Layers

In a newly created application, you’ll only find folders for сontrollers, models, and views. Neither Yii nor Laravel add additional layers in their example application’s structure. Easy and intuitive, even for novices, the MVC structure simplifies work with the framework, but it is important to understand that their sample application is an example; it isn’t a standard or a style, and it doesn’t impose any rules about application architecture. By dividing tasks into separate, single responsibility layers, we get a flexible and extensible architecture that is easy to maintain. Remember:
  • Entities are single data models.
  • Repositories fetch and prepare data.
  • The service layer has only business logic.
  • Controllers communicate with all external sources like user input or a 3rd party service.
So if you start a complex project or a project that has a chance to grow in the future, consider a clear division of responsibilities into the controller, the service, and the repository layers.
By : Elvira Sheina, Uzbekistan at toptal.com


This post first appeared on Latest Technology News And Trends, please read the originial post: here

Share the post

Maintain Slim PHP MVC Frameworks with a Layered Structure

×

Subscribe to Latest Technology News And Trends

Get updates delivered right to your inbox!

Thank you for your subscription

×