1. 程式人生 > >用分層結構打造微 MVC 框架

用分層結構打造微 MVC 框架

To implement a layered structure, we need a dependency injection container, an object that knows how to instantiate and configure objects. You don’t need to create a class because the framework handles all the magic. Consider the following:

class SiteController extends \Illuminate\Routing\Controller
{
protected $userService; public function __construct(UserService $userService) { $this->userService = $userService; } public function showUserProfile(Request $request) { $user = $this->userService->getUser($request->id); return view('user.profile'
, compact('user')); } } class UserService { protected $userRepository; public function __construct(UserRepository $userRepository) { $this->userRepository = $userRepository; } public function getUser($id) { $user = $this->userRepository->getUserById($id
); $this->userRepository->logSession($user); return $user; } } class UserRepository { protected $userModel, $logModel; public function __construct(User $user, Log $log) { $this->userModel = $user; $this->logModel = $log; } public function getUserById($id) { return $this->userModel->findOrFail($id); } public function logSession($user) { $this->logModel->user = $user->id; $this->logModel->save(); } }

In the above example, UserService is injected into SiteControllerUserRepository is injected into UserService and the AR models User and Logs are injected into the UserRepository class. This container code is fairly straightforward, so let’s talk about the layers.

The Controller Layer   控制層

Modern MVC frameworks like Laravel and Yii take on many of the traditional controller challenges for you: Input validation and pre-filters are moved to another part of the application (In Laravel, it’s in what’s called middleware whereas, in Yii, it’s called behavior) while routing and HTTP verb rules are handled by the framework. This leaves a very narrow functionality for the programmer to code into a controller.

The essence of a controller is to get a request and deliver the results. A controller shouldn’t contain any application business logic; otherwise, it’s difficult to reuse code or change how the application communicates. If you need to create an API instead of rendering views, for example, and your controller doesn’t contain any logic, you just change the way you return your data and you’re good to go.

This thin controller layer often confuses programmers, and, since a controller is a default layer and the top-most entry point, many developers just keep adding new code to their controllers without any additional thinking about architecture. As a result, excessive responsibilities get added, responsibilities like:

  • Business logic (which it makes impossible to reuse business logic code).
  • Direct changes of model states (in which case any changes in the database would lead to tremendous changes everywhere in the code).
  • Model relation logic (such as complex queries, joining of multiple models; again, if something is changed in the database or in the relation logic, we would have to change it in all controllers).

Let’s consider an over-engineered controller example:

//A bad example of a controller
public function user(Request $request)
{
   $user = User::where('id', '=', $request->id)
   ->leftjoin('posts', function ($join) {
       $join->on('posts.user_id', '=', 'user.id')
           ->where('posts.status', '=', Post::STATUS_APPROVED);
   })
   ->first();
   if (!empty($user)) {
       $user->last_login = date('Y-m-d H:i:s');
   } else {
       $user = new User();
       $user->is_new = true;
       $user->save();
   }
   return view('user.index', compact('user'));
}

Why is this example bad? For numerous reasons:

  • It contains too much business logic.
  • It works with the Active Record directly, so if you change something in the database, like rename the last_login field, you have to change it in all controllers.
  • It knows about database relations, so if something changes in database we have to change it everywhere.
  • It’s not reusable, leading to code repetition.

A controller should be thin; really, all it should do is take a request and return results. Here’s a good example:

//A good example of a controller
public function user (Request $request)
{
 $user = $this->userService->getUserById($request->id);
 return view('user.index', compact('user'));
}

But where does all that other stuff go? It belongs in the service layer.

The Service Layer 服務層

The service layer is a layer of business logic. Here, and only here, information about business process flow and interaction between the business models should be situated. This is an abstract layer and it will be different for each application, but the general principle is independence from your data source (the responsibility of a controller) and data storage (the responsibility of a lower layer).

This is the stage with the most potential for growth problems. Often, an Active Record model is returned to a controller, and as a result, the view (or in the case of API response, the controller) must work with the model and be aware of its attributes and dependencies. This makes things messy; if you decide to change a relation or an attribute of an Active Record model, you have to change it everywhere in all your views and controllers.

Here’s a common example you might come across of an Active Record model being used in a view:

<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">Online</label>
   @else
       <label class="text-danger">Offline</label>
   @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 '<label class="text-primary">Online</label>';
       } else {
           return '<label class="text-danger">Offline</label>';
       }
   }

   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)
{
 
            
           

相關推薦

分層結構打造 MVC 框架

To implement a layered structure, we need a dependency injection container, an object that knows how to instantiate and configure objects. You don’t nee

原生 JDK 擼一個 MVC 框架

      前期準備 我這裡要寫的是一個迷你版的Spring MVC,我將在一個乾淨的web工程開始開發,不引入Spring,完全通過JDK來實現。 我們先來看一眼工程: 工程程式碼結構 第一,在annota

Spring MVC 框架結構介紹(二)

指定 let url 16px () isp -s 一個 ping Spring MVC框架結構    Spring MVC是圍繞DispatcherServlet設計的,DispatcherServlet向處理程序分發各種請求。處理程序[email prot

asp.net -mvc框架復習(9)-實現戶登錄控制器和視圖的編寫並調試

分享圖片 null admin img pac http tro .com sum 1.編寫控制器 三個步驟: 【1】獲取數據 【2】業務處理 【3】返回數據 using System;using System.Collections.Generic;using Syst

mvc原理:打造自己的MVC框架1.0

一、MVC的原理 從請求到服務端接受到請求中間這個過程經歷了哪些步驟: 第一步:請求被UrlRoutingModule部件攔截 第二步:封裝請求上下文HttpContext,成為HttpContextWrapper 第三步:根據當前的HttpContext,從Routes集合中得到與

【騰訊Bugly乾貨分享】打造信小程式”元件化開發框架

作者:Gcaufy 導語 Bugly 之前發了一篇關於微信小程式的開發經驗分享(點選閱讀),小夥伴們在公眾賬號後臺問了很多關於小程式開發方面的問題,精神哥在查閱相關內容的時候,發現了龔澄同學自己寫了一個小程式開發框架,真的怒贊,趕緊安利給大家

高效快捷簡便易的基於JSP的開源框架 MVC+ORM框架- YangMVC

專案地址 開發目的 @copyright 楊同峰 保留所有權利 本文可以轉載,但請保留版權資訊。 本人高校教師,帶著一門動態網站設計課程,前面講HTML+CSS+DIV,後面將JSP+JDBC+Struts+Hibernate+Spring

MVC系列——MVC原始碼學習:打造自己的MVC框架(四:瞭解神奇的檢視引擎)

前言:通過之前的三篇介紹,我們基本上完成了從請求發出到路由匹配、再到控制器的啟用,再到Action的執行這些個過程。今天還是趁熱打鐵,將我們的View也來完善下,也讓整個系列相對完整,博主不希望爛尾。對於這個系列,通過學習原始碼,博主也學到了很多東西,在此還是把博主知道的先發出來,供大家參考。 MVC原

MVC系列——MVC原始碼學習:打造自己的MVC框架(一:核心原理)

前言:最近一段時間在學習MVC原始碼,說實話,研讀原始碼真是一個痛苦的過程,好多晦澀的語法搞得人暈暈乎乎。這兩天算是理解了一小部分,這裡先記錄下來,也給需要的園友一個參考,奈何博主技術有限,如有理解不妥之處,還希望大家斧正,博主感激不盡! MVC原始碼學習系列文章目錄: 一、MVC原理解析  最

MVC系列——MVC原始碼學習:打造自己的MVC框架(二:附原始碼)

前言:上篇介紹了下 MVC5 的核心原理,整篇文章比較偏理論,所以相對比較枯燥。今天就來根據上篇的理論一步一步進行實踐,通過自己寫的一個簡易MVC框架逐步理解,相信通過這一篇的實踐,你會對MVC有一個更加清晰的認識。 MVC原始碼學習系列文章目錄: 這篇博主打算從零開始一步一步來加上MVC裡面用到

MVC系列——MVC原始碼學習:打造自己的MVC框架(三:自定義路由規則)

前言:上篇介紹了下自己的MVC框架前兩個版本,經過兩天的整理,版本三基本已經完成,今天還是發出來供大家參考和學習。雖然微軟的Routing功能已經非常強大,完全沒有必要再“重複造輪子”了,但博主還是覺得自己動手寫一遍印象要深刻許多,希望想深入學習MVC的童鞋自己動手寫寫。好了,廢話就此打住。 MVC原始

MVC框架模式&&三層結構&&SSM框架

MVC框架模式&&三層結構&&SSM框架 本文主要說明MVC框架模式和三層結構的區別和聯絡。SSM框架由於需要通過實際工程專案瞭解,所以暫時只給出定義。 定義

從零開始寫C# MVC框架之--- 專案結構

框架總分2個專案:Web開發專案、幫助類專案 (ZyCommon、Zy.Utilities)       1、ZyCommon,是Web開發專案結構。新建一個空解決方案,再建Data、Service、ZyWeb解決方案資料夾,把資料層、介面服務層、Web層區分開    

mvc框架下,怎樣cookie實現下次自動登入

登入時有個下次自動登入的checkBox。點了它下次就可以自動登入了 具體流程我都曉得,就是細節的地方有些搞不定。我只要解決3個問題: (1)登入時如果點了checkbox,則在本機產生一個cookie,用來儲存使用者名稱和密碼; (2)點選安全退出時,將cookie刪

1小時內打造你自己的PHP MVC框架

簡介 MVC框架在現在的開發中相當流行,不論你使用的是JAVA,C#,PHP或者IOS,你肯定都會選擇一款框架。雖然不能保證100%的開發語言都會使用框架,但是在PHP社群當中擁有最多數量的MVC框架。今天你或許還在使用Zend,明天你換了另一個專案也許就會轉投

從零開始寫C# MVC框架之--- autofac ioc 容器實現依賴注入

本章查找了一篇對依賴注入解釋得非常不錯的文章為基礎,再加上自己的理解,不然還真不好用語言的方式表達清楚,引用下面這位仁兄的文章 依賴注入產生的背景:         隨著面向物件分析與設計的發展,一個良好的設計,核心原則之一就是將變化隔離,使得變化部分發生變化時,不變部

Ueditor為Asp.net mvc打造視覺化HTML編輯器

在Asp.net WebForm時代,Web系統的HTML視覺化編輯器可以使用FTB一類的控制元件,直接引入工具箱拖拉即可實現。但是在Asp.net MVC時代,直接使用伺服器端控制元件是不符合MVC

junit在spring mvc框架中報錯:找不到service層中的bean

專案結構: 用junit寫的測試類DubboTest.java;spring用的配置檔案 applicationContext.xml spring mvc用的配置檔案 servlet-context

go服務框架go-micro深度學習 rpc方法調過程詳解

route head text Golan port 調用服務方法 eof 服務器 面試題 摘要: 上一篇帖子go微服務框架go-micro深度學習(三) Registry服務的註冊和發現詳細解釋了go-micro是如何做服務註冊和發現在,服務端註冊server信息,cli

2020年總結-學習過的技術搭建一個簡單的服務框架 + 原始碼

框架中使用的技術知識 工作快4年了,有時很迷茫,有時很有幹勁,學習了一些技術,也忘記了一些技術,即使對一些技術,瞭解的深度不夠,至少自己學習過使用過,那麼在面對問題時,不會顯得那麼無力,解決問題後,也能有更大的收穫。 NET Core基礎知識,EF CORE Code First,DB First