laravel 5.6文檔 基礎(chǔ)目錄結(jié)構(gòu)參考
Introduction
Where does this class belong? This question is extremely common when building applications on a framework. Many developers ask this question because they have been told that "Model" means "Database". So, developers have their controllers that interact with HTTP, models which do something with the database, and views which contain their HTML. But, what about classes that send e-mail? What about classes that validate data? What about classes that call an API to gather information? In this chapter, we'll cover good application structure in the Laravel framework and break down some of the common mental roadblocks that hold developers back from good design.
這個(gè)類要寫(xiě)到哪兒?這是一個(gè)在用框架寫(xiě)應(yīng)用程序時(shí)十分常見(jiàn)的問(wèn)題。大量的開(kāi)發(fā)人員都有這個(gè)疑問(wèn)。他們被灌輸“Model”就是“Database”,在控制器里面處理HTTP請(qǐng)求,在模型里操作數(shù)據(jù)庫(kù),視圖里包含了要顯示的HTML。不過(guò),發(fā)送電子郵件的類要寫(xiě)到哪兒?數(shù)據(jù)驗(yàn)證的類要寫(xiě)到哪兒?調(diào)用外部API的類要寫(xiě)到哪兒?在這一章節(jié),我們將學(xué)習(xí)如何寫(xiě)結(jié)構(gòu)優(yōu)美的Laravel應(yīng)用,打破長(zhǎng)久以來(lái)掣肘開(kāi)發(fā)人員的普遍思維慣性這個(gè)攔路虎,最終做出好的設(shè)計(jì)。
MVC Is Killing You
The biggest roadblock towards developers achieving good design is a simple acronym: M-V-C. Models, views, and controllers have dominated web framework thinking for years, in part because of the popularity of Ruby on Rails. However, ask a developer to define "model". Usually, you'll hear a few mutters and the word "database". Supposedly, the model is the database. It's where all your database stuff goes, whatever that means. But, as you quickly learn, your application needs a lot more logic than just a simple database access class. It needs to do validation, call external services, send e-mails, and more.
為了做出好的程序設(shè)計(jì),最大的攔路虎就是一個(gè)簡(jiǎn)單的縮寫(xiě)詞:M-V-C。模型、視圖、控制器主宰了Web框架的思想已經(jīng)好多年了。這種思想的流行某種程度上是托了Ruby on Rails愈加流行的福。然而,如果你問(wèn)一個(gè)開(kāi)發(fā)人員“模型”的定義是什么。通常你會(huì)聽(tīng)到他嘟噥著什么“數(shù)據(jù)庫(kù)”之類的東西。這么說(shuō),模型就是數(shù)據(jù)庫(kù)了。不管這意味著什么,模型里包含了關(guān)于數(shù)據(jù)庫(kù)的一切。但是,你很快就會(huì)知道,你的應(yīng)用程序需要的不僅僅是一個(gè)簡(jiǎn)單的數(shù)據(jù)庫(kù)訪問(wèn)類。他需要更多的邏輯如:數(shù)據(jù)驗(yàn)證、調(diào)用外部服務(wù)、發(fā)送電子郵件,等等更多。
What Is A Model?
The word "model" has become so ambiguous that it has no meaning. By developing with a more specific vocabulary, it will be easier to separate our application into smaller, cleaner classes with a clearly defined responsiblity.
單詞"model"的含義太模糊了,很難說(shuō)明白準(zhǔn)確的含義。更具體來(lái)講,模型是用來(lái)將我們的應(yīng)用劃分成更小、更清晰的類,使得各代碼部分有著明確的權(quán)責(zé)。
So, what is the solution to this dilemma? Many developers start packing logic into their controllers. Once the controllers get large enough, they need to re-use business logic that is in other controllers. Instead of extracting the logic into another class, most developers mistakenly assume they need to call controllers from within other controllers. This pattern is typically called "HMVC". Unfortunately, this pattern often indicates poor application design, and controllers that are much too complicated.
所以怎么解決這個(gè)問(wèn)題(譯者注:上文中“更多的業(yè)務(wù)邏輯”)呢?很多開(kāi)發(fā)者開(kāi)始將業(yè)務(wù)邏輯包裝到控制器里面。當(dāng)控制器龐大到一定規(guī)模,他們將會(huì)需要重用業(yè)務(wù)邏輯。大部分開(kāi)發(fā)人員沒(méi)有將這些業(yè)務(wù)邏輯提取到別的類里面,而是錯(cuò)誤的臆想他們需要在控制器里面調(diào)用別的控制器。這種模式通常被稱為“HMVC”。不幸的是,這種模式通常也預(yù)示著糟糕的程序設(shè)計(jì),并且控制器已經(jīng)太復(fù)雜了。
HMVC (Usually) Indicates Poor Design
Feel the need to call controllers from other controllers? This is often indicative of poor application design and too much business logic in your controllers. Extract the logic into a third class that can be injected into any controller.
你覺(jué)得需要在控制器里面調(diào)用其他的控制器?這通常預(yù)示著糟糕的程序設(shè)計(jì)并且你的控制器里面業(yè)務(wù)邏輯太多了。把業(yè)務(wù)邏輯抽出來(lái)放到一個(gè)新的類里面,這樣你就可以在其他任何控制器里面調(diào)用了。
There is a better way to structure applications. We need to wash our minds clean of all we have been taught about models. In fact, let's just delete the model directory and start fresh!
有一種更好的程序結(jié)構(gòu)。但首先我們要忘掉以往我們被灌輸?shù)年P(guān)于“模型”的一切。干脆點(diǎn),讓我們直接刪掉model目錄,重新開(kāi)始吧!
Bye, Bye Models
Is your models directory deleted yet? If not, get it out of there! Let's create a folder within our app directory that is simply named after our application. For this discussion, let's call our application QuickBill, and we'll continue to use some of the interfaces and classes we've discussed before.
刪掉你的models目錄了么?還沒(méi)刪就趕緊刪了!我們將要在app目錄下創(chuàng)建個(gè)新的目錄,目錄名就以我們這個(gè)應(yīng)用的名字來(lái)命名,這次我們就叫QuickBill吧。在后續(xù)的討論中,我們?cè)谇懊鎸?xiě)的那些接口和類都會(huì)出現(xiàn)。
Remember The Context 注意使用場(chǎng)景
Remember, if you are building a very small Laravel application, throwing a few Eloquent models in the models directory perfectly fine. In this chapter, we're primarily concerned with discovering more "layered" architecture suitable to large and complex projects.
記住,如果你在寫(xiě)一個(gè)很小的Laravel應(yīng)用,那在models目錄下寫(xiě)幾個(gè)Eloquent模型其實(shí)挺合適的。但在本章節(jié),我們主要關(guān)注如何開(kāi)發(fā)更有合適“層次”架構(gòu)的大型復(fù)雜項(xiàng)目。
So, we should have an app/QuickBill directory, which is at the same level in the application directory structure as controllers and views. We can create several more directories within QuickBill. Let's create a Repositories directory and a Billing directory. Once these directories are established, remember to register them for PSR-0 auto-loading in your composer.json file:
這樣我們現(xiàn)在有了個(gè)app/QuickBill目錄,它和應(yīng)用目錄下的其他目錄如controllers還有views都是平級(jí)的。在QuickBill目錄下我們還可以創(chuàng)建幾個(gè)其他的目錄。我們來(lái)在里面創(chuàng)建個(gè)Repositories和Billing目錄。目錄都創(chuàng)建好以后,別忘了在composer.json文件里加入 PSR-0 的自動(dòng)載入機(jī)制:
"autoload": {
"psr-0": {
"QuickBill": "app/"
}
}
譯者注:psr-0 也可以改成 psr-4, "psr-4": { "QuickBill": "app/QuickBill" } psr-4 是比較新的建議標(biāo)準(zhǔn),和 psr-0 具體有什么區(qū)別請(qǐng)自行檢索。
For now, let's put our Eloquent classes at the root of the QuickBill directory. This will allow us to conveniently access them as QuickBill\User, QuickBill\Payment, etc. In the Repositories folder would belongs classes such as PaymentRepository and UserRepository, which would contain all of our data access functions such as getRecentPayments, and getRichestUser. The Billing directory would contain the classes and interfaces that work with third-party billing services like Stripe and Balanced. The folder structure would look something like this:
現(xiàn)在我們把繼承自 Eloquent 的模型類都放到QuickBill目錄下面。這樣我們就能很方便的以QuickBill\User, QuickBill\Payment的方式來(lái)使用它們。Repositories目錄屬于PaymentRepository 和UserRepository這種類,里面包含了所有對(duì)數(shù)據(jù)的訪問(wèn)功能比如getRecentPayments和getRichestUser。Billing目錄應(yīng)當(dāng)包含調(diào)用第三方支付服務(wù)(如Stripe和Balanced)的類。整個(gè)目錄結(jié)構(gòu)應(yīng)該類似這樣:
<!-- lang:php -->
// app
// QuickBill
// Repositories
-> UserRepository.php
-> PaymentRepository.php
// Billing
-> BillerInterface.php
-> StripeBiller.php
// Notifications
-> BillingNotifierInterface.php
-> SmsBillingNotifier.php
User.php
Payment.php
What About Validation
Where to perform validation often stumps developers. Consider placing validation methods on your "entity" classes (like User.php and Payment.php). Possible method name might be: validForCreationor hasValidDomain. Alternatively, you could create a UserValidator class within a Validationnamespace and inject that validator into your repository. Experiment with both approaches and see what you like best!
在哪兒進(jìn)行數(shù)據(jù)驗(yàn)證常常困擾著開(kāi)發(fā)人員。可以考慮將數(shù)據(jù)驗(yàn)證方法寫(xiě)進(jìn)你的“實(shí)體”類里面(好比User.php和Payment.php)。方法名可以設(shè)為validForCreation或hasValidDomain?;蛘吣阋部梢詫iT(mén)創(chuàng)建個(gè)驗(yàn)證器類UserValidator,放到Validation命名空間下,然后將這個(gè)驗(yàn)證器類注入到你的repository類里面。兩種方式你都可以試試,看哪個(gè)你更喜歡!
By just getting rid of the models directory, you can often break down mental roadblocks to good design, allowing you to create a directory structure that is more suitable for your application. Of course, each application you build will have some similarities, since each complex application will need a data access (repository) layer, several external service layers, etc.
擺脫了models目錄后,你通常就能克服心理障礙,實(shí)現(xiàn)好的設(shè)計(jì)。使得你能創(chuàng)建一個(gè)更合適的目錄結(jié)構(gòu)來(lái)為你的應(yīng)用服務(wù)。當(dāng)然,你建立的每一個(gè)應(yīng)用程序都會(huì)有一定的相似之處,因?yàn)槊總€(gè)復(fù)雜的應(yīng)用程序都需要一個(gè)數(shù)據(jù)訪問(wèn)(repository)層,一些外部服務(wù)層等等。
Don't Fear Directories
Don't be afraid to create more directories to organize your application. Always break your application into small components, each having a very focused responsibility. Thinking outside of the "model" box will help. For example, as we previously discussed, you could create a Repositories directory to hold all of your data access classes.
不要懼怕建立目錄來(lái)管理應(yīng)用。要常常將你的應(yīng)用切割成小組件,每一個(gè)組件都要有十分專注的職責(zé)。跳出“模型”的框框來(lái)思考。比如我們之前就說(shuō)過(guò),你可以創(chuàng)建個(gè)Repositories目錄來(lái)存放你所有的數(shù)據(jù)訪問(wèn)類。
It's All About The Layers 核心思想就是分層
As you may have noticed, a key to solid application design is simply separating responsibilities, or creating layers of responsibility. Controllers are responsible for receiving an HTTP request and calling the proper business layer classes. Your business / domain layer is your application. It contains the classes that retrieve data, validate data, process payments, send e-mail, and any other function of your application. In fact, your domain layer doesn't need to know about "the web" at all! The web is simply a transport mechanism to access your application, and knowledge of the web and HTTP need not go beyond the routing and controller layers. Good architecture can be challenging, but will yield large profits of sustainable, clear code.
你可能注意到,優(yōu)化應(yīng)用的設(shè)計(jì)結(jié)構(gòu)的關(guān)鍵就是責(zé)任劃分,或者說(shuō)是創(chuàng)建不同的責(zé)任層次??刂破髦回?fù)責(zé)接收和響應(yīng)HTTP請(qǐng)求然后調(diào)用合適的業(yè)務(wù)邏輯層的類。你的業(yè)務(wù)邏輯/領(lǐng)域邏輯層才是你真正的程序。你的程序包含了讀取數(shù)據(jù),驗(yàn)證數(shù)據(jù),執(zhí)行支付,發(fā)送電子郵件,還有你程序里任何其他的功能。事實(shí)上你的領(lǐng)域邏輯層不需要知道任何關(guān)于“網(wǎng)絡(luò)”的事情!網(wǎng)絡(luò)僅僅是個(gè)訪問(wèn)你程序的傳輸機(jī)制,關(guān)于網(wǎng)絡(luò)和HTTP請(qǐng)求的一切不應(yīng)該超出路由和控制器層。做出好的設(shè)計(jì)的確很有挑戰(zhàn)性,但好的設(shè)計(jì)也會(huì)帶來(lái)可持續(xù)發(fā)展的清晰的好代碼。
For example, instead of accessing the web request instance in a class, you could simply pass the web input from the controller. This simple change alone decouples your class from "the web", and the class can easily be tested without worrying about mocking a web request:
舉個(gè)例子。與其在你業(yè)務(wù)邏輯類里面直接獲取網(wǎng)絡(luò)請(qǐng)求,不如你直接把網(wǎng)絡(luò)請(qǐng)求從控制器傳給你的業(yè)務(wù)邏輯類。這個(gè)簡(jiǎn)單的改動(dòng)將你的業(yè)務(wù)邏輯類和“網(wǎng)絡(luò)”分離開(kāi)了,并且不必?fù)?dān)心怎么去模擬網(wǎng)絡(luò)請(qǐng)求,你的業(yè)務(wù)邏輯類就可以簡(jiǎn)單的測(cè)試了:
<!-- lang:php -->
class BillingController extends BaseController{
public function __construct(BillerInterface $biller)
{
$this->biller = $biller;
}
public function postCharge()
{
$this->biller->chargeAccount(Auth::user(), Input::get('amount'));
return View::make('charge.success');
}
}
Our chargeAccount method is now much easier to test, since we no longer have to use the Request or Input class inside of our BillingInterface implementation as we are simply passing the charged amount into the method as an integer.
現(xiàn)在chargeAccount 方法更容易測(cè)試了。 我們把Request和Input從BillingInterface里提出來(lái),然后在控制器里把方法需要的支付金額直接傳過(guò)去。
Separation of responsibilities is one of the keys to writing maintainable applications. Always be asking if a given class knows more than it should. You should frequently ask yourself: "Should this class care about X?" If answer is "no", extract the logic into another class that can be injected as a dependency.
編寫(xiě)擁有高可維護(hù)性應(yīng)用程序的關(guān)鍵之一,就是責(zé)任分割。要時(shí)常檢查一個(gè)類是否管得太寬。你要常常問(wèn)自己“這個(gè)類需不需要關(guān)心XXX呢?”如果答案是否定的,那么把這塊邏輯抽出來(lái)放到另一個(gè)類里面,然后用依賴注入的方式進(jìn)行處理。(譯者注:依賴注入的不同方式還記得么?調(diào)用方法傳參、構(gòu)造函數(shù)傳參、從IoC容器獲取等等。)
Single Reason To Change
A helpful method of determining whether a class has too many responsibilities is to examine your reason for changing code within that class. For example, should we need to change code within a Billerimplementation when tweaking our notification logic? Of course not. The Biller implementations are concerned with billing, and should only work with notification logic via a contract. Maintaining this mindset as you are working on your code will help you quickly identify areas of an application that can be improved.
如何判斷一個(gè)類是否管得太寬,有一個(gè)有用的方法就是檢查你為什么要改這塊兒代碼。舉個(gè)例子:當(dāng)我們想調(diào)整通知邏輯的時(shí)候,我們需要修改Biller的實(shí)現(xiàn)代碼么?當(dāng)然不需要,Biller的實(shí)現(xiàn)僅僅需要考慮支付,它與通知邏輯應(yīng)當(dāng)僅通過(guò)約定來(lái)進(jìn)行交互。使用這種思路過(guò)一遍代碼,會(huì)讓你很快找出應(yīng)用中需要改進(jìn)的地方。
Where To Put "Stuff"
When developing applications with Laravel, you may sometimes wonder where to put "stuff". For example, where should you put "helper" functions? Where should you put event listeners? Where should you put view composers? It may be surprising for you to know that the answer is: "wherever you want!" Laravel does not have many conventions regarding where things belong on the file system. However, as this answer is not often satisfactory, we'll explore some possible locations for such things before moving on.
當(dāng)用 Laravel 開(kāi)發(fā)應(yīng)用時(shí),你可能迷惑于應(yīng)該把各種“東西”都放在哪兒。比如,輔助函數(shù)要放在哪里?事件監(jiān)聽(tīng)器要放在哪里?視圖組件要放在哪里?答案可能出乎你的意料——“想放哪兒都行!”Laravel 并沒(méi)有很多在文件系統(tǒng)上的約定。不過(guò)這個(gè)答案的確不能讓人滿意,所以下面我們就這個(gè)問(wèn)題展開(kāi)討論,一起探索這些“東西”究竟可以放在哪兒。
Helper Functions 輔助函數(shù)
Laravel ships with a file full of helpers functions (support/helpers.php). You may wish to create a similar file containing helper functions relevant to your own application and conding style. A great place to include these functions are the "start" files. Within your start/global.php file, which is included on every request to the application, you may simply require your own helpers.php file:
Laravel 有一個(gè)文件(support/helpers.php)里面都是輔助函數(shù)。你或許希望創(chuàng)建一個(gè)類似的文件來(lái)存儲(chǔ)你自己的輔助函數(shù)?!皊tart”文件是個(gè)不錯(cuò)的入口,該文件會(huì)在應(yīng)用的每一次請(qǐng)求時(shí)被訪問(wèn)。在start/global.php里,你可以引入你自己寫(xiě)的helpers.php文件,就像這樣:
<!-- lang:php -->
// Within app/start/global.php
require_once __DIR__.'/../helpers.php';
//譯者注: 該helpers.php文件位于app目錄下,需要你自己創(chuàng)建。你想放到別的地方也可以。
Event Listeners
Since event listeners obviously do not belongs in the routes.php file, and can begin to clutter the "start" files, we need another location to place this code. A great option is a service provider. As we've learned, service providers are not strictly for registering bindings in the IoC container. They can be used to do all sorts of work. By grouping related event registrations inside of a service provider, the code stays neatly tucked away behind the scenes of your main application code. View composers, which are a type of event, may also be neatly grouped within service providers.
事件監(jiān)聽(tīng)器當(dāng)然不該放到routes.php文件里面,若直接放到“start”目錄下的文件里會(huì)比較亂,所以我們要找另外的地方來(lái)存放。服務(wù)提供者是個(gè)好地方。我們之前了解到,服務(wù)提供者可不僅僅是用來(lái)做依賴注入綁定,還可以干其他事兒??梢詫⑹录O(jiān)聽(tīng)器用服務(wù)提供者來(lái)管理起來(lái),讓代碼更整潔,不至于影響到你應(yīng)用的主要邏輯代碼。視圖組件其實(shí)和事件差不多,也可以類似的放到服務(wù)提供者里面。
For example, a service provider that registers events might look something like this:
例如使用服務(wù)提供者進(jìn)行事件注冊(cè)可以這樣:
<!-- lang:php -->
<?php namespace QuickBill\Providers;
use Illuminate\Support\ServiceProvider;
class BillingEventsProvider extends ServiceProvider{
public function boot()
{
Event::listen('billing.failed', function($bill)
{
// Handle failed billing event...
});
}
}
After creating the provider, we would simply add it our providers array within the app/config/app.phpconfiguration file.
創(chuàng)建好服務(wù)提供者后,就可以將它加入到app/config/app.php 配置文件的providers數(shù)組里。
Wear The Boot 注意啟動(dòng)流程
Remember, in the example above, we are using the boot method for a reason. The register method in a service provider is only intended for binding classes into container.
記住在上面的例子里面,我們?cè)赽oot方法里進(jìn)行編寫(xiě)是有原因的。register方法只能用來(lái)進(jìn)行依賴注入綁定。
Error Handlers
If your application has many customer error handlers, they may start to take over your "start" files. Again, like event handlers, these are best moved into a service provider. The service provider might be named something like QuickBillErrorProvider. Within the boot method of this provider you may register all of your custom error handlers. Again, keeps boilerplate code out of the main files of your application. An error handler provider would look something like this:
如果你的應(yīng)用里面有很多自定義的錯(cuò)誤處理方法,那你的“啟動(dòng)”文件可能會(huì)很臃腫。和剛才的事件監(jiān)聽(tīng)器一樣,錯(cuò)誤處理方法也最好放到服務(wù)提供者里面。這種服務(wù)提供者可以命名為像QuickBillErrorProvider這種。然后你在boot方法里想注冊(cè)多少錯(cuò)誤處理方法都可以了。重申一下精神:讓呆板的代碼離你應(yīng)用的業(yè)務(wù)邏輯越遠(yuǎn)越好。下方展示了這種服務(wù)提供者的一種可能的書(shū)寫(xiě)方法:
<!-- lang:php -->
<?php namespace QuickBill\Providers;
use App, Illuminate\Support\ServiceProvider;
class QuickBillErrorProvider extends ServiceProvider {
public function register()
{
//
}
public function boot()
{
App::error(function(BillingFailedException $e)
{
// Handle failed billing exceptions ...
});
}
}
The Small Solution 簡(jiǎn)便做法
Of course, if you only have one or two simple error handlers, keeping them in the "start" files is a reasonable and quick solution.
當(dāng)然如果你只有一兩條簡(jiǎn)單的錯(cuò)誤處理方法,那么都寫(xiě)在“啟動(dòng)”文件里面也是一種又快又好的簡(jiǎn)便做法。
The Rest
In general, classes may be neatly organized using a PSR-0 structure within a directory of your application. Imperative code such as event listeners, error handlers, and other "registeration" type operations may be placed inside of a service provider. With what we have learned so far, you should be able to make an educated decision on where to place a piece of code. But, don't be hesitate to experiment. The beauty of Laravel is that you can make conventions that work best for you. Discover a structure that works best for your applications, and make sure to share your insights with others!
通常只要遵循 PSR-0(譯者注:或 PSR-4)就可以保持類的整潔。命令式的代碼比如事件監(jiān)聽(tīng)器、錯(cuò)誤處理器還有其他“注冊(cè)”性質(zhì)的操作都可以放在服務(wù)提供者里面。對(duì)于什么代碼要放在什么地方這個(gè)問(wèn)題,結(jié)合你目前為止學(xué)到的知識(shí),應(yīng)當(dāng)可以給出一個(gè)有理有據(jù)的答案了。但永遠(yuǎn)不要害怕試驗(yàn)。Laravel 最美妙之處就是你可以做出最適合你自己的風(fēng)格。去探索和發(fā)現(xiàn)最適合你自己應(yīng)用的結(jié)構(gòu)吧,別忘了和他人分享你的見(jiàn)解!
For example, as you probably noticed above, you might create a Providers namespace for all of your application's custom service providers, creating a directory structure like so:
例如你可能注意到我們上面的例子,你可以創(chuàng)建個(gè)Providers的命名空間來(lái)存放你自己寫(xiě)的服務(wù)提供者,目錄就類似于這樣:
<!-- lang:php -->
// app
// QuickBill
// Billing
// Extensions
//Pagination
-> Environment.php
// Providers
-> EventPusherServiceProvider.php
// Repositories
User.php
Payment.php
Notice that in the example we have a Providers and an Extensions namespace. All of your application's service providers could be stored in the Providers directory in namespace, and the Extensionsnamespace is a convenient place to store extensions made to core framework classes.
看上面的例子我們有Providers和Extensions兩個(gè)命名空間(譯者注:分別對(duì)應(yīng)兩個(gè)同名目錄)。你自己寫(xiě)的服務(wù)提供者可以放到Providers命名空間下。那個(gè)Extensions命名空間可以用來(lái)存放你對(duì)框架核心進(jìn)行擴(kuò)展的類。