├── CNAME ├── .gitignore ├── content ├── page │ ├── 2_The_IoC_Container.md │ ├── 4_Service_Provider.md │ ├── 1_Dependency_Injection.md │ ├── 3_Interface_As_Contract.md │ ├── 5_Application_Structure.md │ ├── 7_Extending_The_Framework.md │ ├── 9_Open_Closed_Principle.md │ ├── 10_Liskov_Substitution_Principle.md │ ├── 12_Dependency_Inversion_Principle.md │ ├── 11_Interface_Segregation_Principle.md │ ├── 8_Single_Responsibility_Principle.md │ ├── 6_Applied_Architecture_Decoupling_Handles.md │ ├── 8.1_Introduction.md │ ├── 10.1_Introduction.md │ ├── 6.1_Introduction.md │ ├── 5.1_Introduction.md │ ├── 9.1_Introduction.md │ ├── 12.1_Introduction.md │ ├── 7.1_Introduction.md │ ├── 7.2_Managers_&_Factories.md │ ├── 1.4_Too_Much_Java.md │ ├── 11.1_Introduction.md │ ├── 7.7_Request_Extension.md │ ├── 4.3_Booting_Providers.md │ ├── 4.4_Providing_The_Core.md │ ├── 5.2_MVC_Is_Killing_You.md │ ├── 6.3_Other_Handlers.md │ ├── 7.6_IoC_Based_Extension.md │ ├── 1.2_Build_A_Contract.md │ ├── 3.2_A_Contract_Example.md │ ├── 5.4_It's_All_About_The_Layers.md │ ├── 1.1_The_Problem.md │ ├── 1.3_Take_It_further.md │ ├── 7.3_Cache.md │ ├── 3.3_Interface_&_Team_Development.md │ ├── 7.4_Session.md │ ├── 4.1_As_Bootstrapper.md │ ├── 3.1_Strong_Typing_&_Water_Fowl.md │ ├── 7.5_Authentication.md │ ├── 5.3_Bye_Models.md │ ├── 4.2_As_Organizer.md │ ├── 6.2_Decoupling_Handlers.md │ ├── 2.2_Reflective_Resolution.md │ ├── 8.2_In_Action.md │ ├── 11.2_In_Action.md │ ├── 10.2_In_Action.md │ ├── 12.2_In_Action.md │ ├── 9.2_In_Action.md │ ├── 2.1_Basic_Binding.md │ └── 5.5_Where_To_Put_Stuff.md ├── README.md └── SUMMARY.md ├── scripts └── deploy-gh-pages.js ├── README.md └── package.json /CNAME: -------------------------------------------------------------------------------- 1 | fata.tuim.cn 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gh-pages 2 | node_modules 3 | -------------------------------------------------------------------------------- /content/page/2_The_IoC_Container.md: -------------------------------------------------------------------------------- 1 | # The IoC Container 控制反转容器 2 | -------------------------------------------------------------------------------- /content/page/4_Service_Provider.md: -------------------------------------------------------------------------------- 1 | # Service Providers 服务提供者 2 | -------------------------------------------------------------------------------- /content/page/1_Dependency_Injection.md: -------------------------------------------------------------------------------- 1 | # Dependency Injection 依赖注入 2 | -------------------------------------------------------------------------------- /content/page/3_Interface_As_Contract.md: -------------------------------------------------------------------------------- 1 | # Interface As Contract 接口约定 2 | -------------------------------------------------------------------------------- /content/page/5_Application_Structure.md: -------------------------------------------------------------------------------- 1 | # Application Structure 应用结构 2 | -------------------------------------------------------------------------------- /content/page/7_Extending_The_Framework.md: -------------------------------------------------------------------------------- 1 | # Extending The Framework 扩展框架 2 | -------------------------------------------------------------------------------- /content/page/9_Open_Closed_Principle.md: -------------------------------------------------------------------------------- 1 | # Open Closed Principle 开放封闭原则 2 | -------------------------------------------------------------------------------- /content/page/10_Liskov_Substitution_Principle.md: -------------------------------------------------------------------------------- 1 | # Liskov Substitution Principle 里氏替换原则 2 | -------------------------------------------------------------------------------- /content/page/12_Dependency_Inversion_Principle.md: -------------------------------------------------------------------------------- 1 | # Dependency Inversion Principle 依赖反转原则 2 | -------------------------------------------------------------------------------- /content/page/11_Interface_Segregation_Principle.md: -------------------------------------------------------------------------------- 1 | # Interface Segregation Principle 接口隔离原则 2 | -------------------------------------------------------------------------------- /content/page/8_Single_Responsibility_Principle.md: -------------------------------------------------------------------------------- 1 | # Single Responsibility Principle 单一职责原则 2 | -------------------------------------------------------------------------------- /content/page/6_Applied_Architecture_Decoupling_Handles.md: -------------------------------------------------------------------------------- 1 | # Applied Architecture: Decoupling Handlers 实用做法:解耦处理函数 2 | -------------------------------------------------------------------------------- /scripts/deploy-gh-pages.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ghpages = require('gh-pages'); 4 | 5 | main(); 6 | 7 | function main() { 8 | ghpages.publish('./gh-pages', console.error.bind(console)); 9 | } 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # From Apprentice To Artisan 2 | 3 | 本书详细介绍了 `Laravel` 框架涉及的各种软件理念和工具,如依赖注入、控制反转容器、面向接口编程等。 4 | 5 | 在线阅读地址:https://fata.laradoc.cn/ 6 | 7 | > 提示:科学上网访问效果更佳. 8 | 9 | Just for Fun! 10 | 11 | Happy Coding, Happy Life. 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fata", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "gitbook serve ./content ./gh-pages", 8 | "build": "gitbook build ./content ./gh-pages", 9 | "deploy": "node ./scripts/deploy-gh-pages.js", 10 | "publish": "npm run build && npm run deploy", 11 | "port": "lsof -i :35729" 12 | }, 13 | "author": "cooper", 14 | "license": "ISC" 15 | } 16 | -------------------------------------------------------------------------------- /content/page/8.1_Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 介绍 2 | 3 | The "SOLID" design principles, articulated by Robert "Uncle Bob" Martin, are five principles that provide a good foundation for sound application design. The five principles are: 4 | 5 | 罗伯特“鲍勃叔叔”马丁阐述了名为“坚实”的一些设计原则(译者注:看下面五个原则的首字母正是 SOLID )。这些都是制作完善的程序设计的优秀基础,一共有五个原则: 6 | 7 | * The Single Responsibility Principle 单一职责原则 8 | * The Open Closed Principle 开放封闭原则 9 | * The Liskov Substitution Principle 里氏替换原则 10 | * The Interface Segregation Principle 接口隔离原则 11 | * The Dependency Inversion Principle 依赖反转原则 12 | 13 | Let's explore each of these principles in more depth, and look at some code examples illustrating each principle. As we will see, each principle compliments the others, and if one principle falls, most, if not all, of the others do as well. 14 | 15 | 让我们深入探索一下,再看点代码样例来说明各个原则。我们将看到,每个原则之间都有联系。如果其中一个原则没有被遵循,那么其他大部分(可能不会是全部)的原则也会出问题。 16 | -------------------------------------------------------------------------------- /content/page/10.1_Introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 介绍 2 | 3 | Don't worry, the Liskov Substitution Principle is a lot easier to understand than it sounds. This principle states that you should be able to use any implementation of an abstraction in any place that accepts that abstraction. But, let's make this a little simpler. In plain English, the principle states that if a class uses an implementation of an interface, it must be able to use any implementation of that interface without requiring any modifications. 4 | 5 | 别担心,里氏替换原则读起来吓人学起来简单。该原则要求:一个抽象的任意一个实现,可以被用在任何需要该抽象的地方。读起来绕口,用普通人的话来解释一下。该原则规定:如果一个类使用了一个接口的一个实现类,那么该接口的任何其他实现类也可以被这里直接使用,不用做出任何修改。 6 | 7 | > ### Liskov Substitution Principle 里氏替换原则 8 | 9 | > This principle states that objects should be replaceable with instances of their sub-types without altering the correctness of that program. 10 | 11 | > 该原则规定对象应该可以被该对象子类的实例所替换,并且不会影响到程序的正确性。 12 | -------------------------------------------------------------------------------- /content/page/6.1_Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 介绍 2 | 3 | Now that we have discussed various aspects of sound application architecture using Laravel 4, Let's dig into some more specifics. In this chapter, we'll discuss tips for decoupling various handlers like queue and event handlers, as well as other "event-like" structures such as route filters. 4 | 5 | 我们已经讨论了用 Laravel 制作优美的程序架构的各个方面,让我们再深入一些细节。在本章,我们将讨论如何解耦各种处理函数:队列处理函数、事件处理函数,甚至其他“事件型”的结构如路由过滤器。 6 | 7 | > ### Don't Clog Your Transport Layer 不要堵塞传输层 8 | 9 | > Most "handlers" can be considered transport layer components. In other words, they receive calls through something like queue workers, a dispatched event, or an incoming request. Treat these handlers like controllers, and avoid clogging them up with the implementation details of your application. 10 | 11 | > 大部分的“处理函数”可以被当作传输层组件。也就是说,队列触发器、被触发的事件、或者外部发来的请求等都可能调用处理函数。可以把处理函数理解为控制器,避免在里面堆积太多具体业务逻辑实现。 12 | -------------------------------------------------------------------------------- /content/page/5.1_Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 介绍 2 | 3 | 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. 4 | 5 | 这个类要写到哪儿?这是一个在用框架写应用程序时十分常见的问题。大量的开发人员都有这个疑问。他们被灌输 “Model” 就是 “Database” ,在控制器里面处理 HTTP 请求,在模型里操作数据库,视图里包含了要显示的 HTML 。不过,发送电子邮件的类要写到哪儿?数据验证的类要写到哪儿?调用外部 API 的类要写到哪儿?在这一章节,我们将学习如何写结构优美的 Laravel 应用,打破长久以来掣肘开发人员的普遍思维惯性这个拦路虎,最终做出好的设计。 6 | -------------------------------------------------------------------------------- /content/page/9.1_Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 介绍 2 | 3 | Over the life time of an application, more time is spent adding to the existing codebase rather than constantly adding new features from scratch. As you are probably aware, this can be a tedious and frustrating process. Anytime you modify code, you risk introducing new bugs, or breaking old functionality completely. Ideally, we should be able to modify an existing codebase as quickly and easily as writing brand new code. If we correctly design our application according to the Open Closed principle, we can just do that! 4 | 5 | 在一个应用的生命周期里,大部分时间都花在了向现有代码库增加功能,而非一直从零开始写新功能。正像你所想的那样,这会是一个繁琐且令人痛苦的过程。当你修改代码的时候,你可能引入新的程序错误,或者将原来管用的功能搞坏掉。理想情况下,我们应该可以像写全新的代码一样,来快速且简单的修改现有的代码。只要采用开放封闭原则来正确的设计我们的应用程序,那么这是可以做到的! 6 | 7 | > ### Open Closed Principle 开放封闭原则 8 | 9 | > The Open Closed principle of SOLID design states that code is open for extension but closed for modification. 10 | 11 | > 开放封闭原则规定代码对扩展是开放的,对修改是封闭的。 12 | -------------------------------------------------------------------------------- /content/page/12.1_Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 介绍 2 | 3 | We have reached our final destination in our overview of the five SOLID design principles! The final principle is the Dependency Inversion principle, and it states that high-level code should not depend on low-level code. Instead, high-level code should depend on an abstraction layer that serves as a "middle-man" between the high and low-level code. A second aspect to the principle is that abstractions should not depend upon details, but rather details should depend upon abstractions. If this all sounds extremely confused, don't worry. We'll cover both aspects of this principle below. 4 | 5 | 在整个“坚实”原则概述的旅途中,我们到达最后一站了!最后的原则是依赖反转原则,它规定高等级的代码不应该依赖低等级的代码。首先,高等级的代码应该依赖着抽象层,抽象层就像是“中间人”一样,负责连接着高等级和低等级的代码。其次,抽象定义不应该依赖着具体实现,但具体实现应该依赖着抽象定义。如果这些东西让你极端困惑,别担心。接下来我们会将这两方面统统介绍给你。 6 | 7 | > ### Dependency Inversion Principle 依赖反转原则 8 | 9 | > This principle states that high-level code should not depend on low-level code, and that abstractions should not depend upon details. 10 | 11 | > 该原则要求高等级代码不应该依赖低等级代码,抽象定义不应该依赖具体实现。 12 | -------------------------------------------------------------------------------- /content/page/7.1_Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 介绍 2 | 3 | Laravel offers many extension points for you to customize the behavior of the framework's core components, or even replace them entirely. For example, the hashing facilites are defined by a HasherInterface contract, which you may implement based on your application's requirements. You may also extend the Request object, allowing you to add your own convenient "helper" methods. You may even add entirely new authentication, cache, and session drivers! 4 | 5 | 为了方便你自定义框架核心组件,Laravel 提供了大量可以扩展的地方。你甚至可以完全替换掉旧组件。例如:哈希器遵守了 HasherInterface 接口,你可以按照你自己应用的需求来重新实现。你也可以扩展 Request 对象,添加你自己用的顺手的 “helper” 方法。你甚至可以添加全新的身份认证、缓存和会话机制! 6 | 7 | Laravel components are generally extended in two ways: binding new implementations in the IoC container, or registering an extension with a Manager class, which are implementations of the "Factory" design pattern. In this chapter we'll explore the various methods of extending the framework and examine the necessary code. 8 | 9 | Laravel 组件通常有两种扩展方式:在 IoC 容器里面绑定新实现,或者用 Manager 类注册一个扩展,该扩展采用了工厂模式实现。 在本章中我们将探索不同的扩展方式并检查我们都需要些什么代码。 10 | 11 | > ### Methods Of Extension 扩展方式 12 | 13 | > Remember, Laravel components are typically extended in one of two ways: IoC bindings and the Manager classes. The manager classes serve as an implementation of the "factory" design pattern, and are responsible for instantiating driver based facilities such as cache and session. 14 | 15 | > 要记住 Laravel 通常有以下两种扩展方式:通过 IoC 绑定和通过 Manager 类(下文译作“管理类”)。其中管理类实现了工厂设计模式,负责组件的实例化。比如缓存和会话机制。 16 | -------------------------------------------------------------------------------- /content/page/7.2_Managers_&_Factories.md: -------------------------------------------------------------------------------- 1 | ## Manager & Factories 管理者和工厂 2 | 3 | Laravel has several Manager classes that manage the creation of driver-based components. These include the cache, session, authentication, and queue components. The manager class is responsible for creating a particular driver implementation based on the application's configuration. For example, the CacheManager class can create APC, Memcached, Native, and various other implementations of cache drivers. 4 | 5 | Laravel 有好多 Manager 类用来管理基于驱动的组件的生成过程。基于驱动的组件包括:缓存、会话、身份认证、队列组件等。管理类负责根据应用程序的配置,来生成特定的驱动实例。比如:CacheManager 可以创建 APC、Memcached、Native、还有其他不同的缓存驱动的实现。 6 | 7 | Each of these managers includes an extend method which may be used to easily inject new driver resolution functionality into the manager. We'll cover each of these managers below, with examples of how to inject custom driver support into each of them. 8 | 9 | 每个管理类都包含名为 extend 的方法,该方法可用于将新功能注入到管理类中。下面我们将逐个介绍管理类,为你展示如何注入自定义的驱动。 10 | 11 | > ### Learn About Your Managers 如何了解你的管理类 12 | 13 | > Take a moment to explore the various Manager classes that ship with Laravel, such as the CacheManager and SessionManager. Reading through these classes will give you a more thorough understanding of how Laravel works under the hood. All manager classes extend the Illuminate\Support\Manager base class, which provides some helpful, common functionality for each manager. 14 | 15 | > 请花点时间看看 Laravel 中各个 Manager 类的代码,比如 CacheManager 和 SessionManager 。通过阅读这些代码能让你对 Laravel 的管理类机制更加清楚透彻。所有的管理类都继承自 Illuminate\Support\Manager 基类,该基类为每一个管理类提供了一些有效且通用的功能。 16 | -------------------------------------------------------------------------------- /content/README.md: -------------------------------------------------------------------------------- 1 | # From Apprentice To Artisan 2 | 3 | 本书详细介绍了 Laravel 框架涉及的各种软件理念和工具,如依赖注入、控制反转容器、面向接口编程等。 4 | 5 | ## Author's Note 作者自序 6 | 7 | Since creating the Laravel framework, I have received numerous request for a book containing guidance on building well-architected, complex applications. As each application is unique, such a book requires that the counsel remains general, yet practical and easily applicable to a variety of projects. 8 | 9 | 自从编写了 Laravel 框架之后,我收到了大量让我写书的请求。想让我出一本关于如何建造具有良好架构的复杂应用的指南。由于每一个应用程序都是独特的,这就需要此书讲述的是通用且实用的理论,同时易于在各种项目中实施。 10 | 11 | So, we will begin by covering the foundational elements of dependency injection, then take an in-depth look at service providers and application structure, as well as an overview of the SOLID design principles. A strong knowledge of these topics will give you a firm foundation for all of your Laravel projects. 12 | 13 | 因此我们将从基础要素之一的依赖注入开始讲起,接着是深入了解服务提供者和应用程序结构, 以及“坚实”设计原则。这些主题的中心思想会给你的 Laravel 项目奠定坚实的理论基础。 14 | 15 | If you have any further questions about advanced architecture on Laravel, or wish to see something added to the book, please e-mail me. I plan to expand the book further based on community feedback, so your ideas matter! 16 | 17 | 如果你对 Laravel 上的高级架构有更进一步的问题的话,或者想在本书看到更多没讲到的东西,请给我发电子邮件。我打算基于社区的反馈来进一步扩展本书,所以你的意见很重要! 18 | 19 | Finally, thank you so much for being a part of the Laravel community. You have all helped make PHP development more enjoyable and exciting for thousands of people around the world. Code happy! 20 | 21 | 最后,十分感谢 Laravel 社区的每一个人。你们为世界上成千上万的 PHP 开发者做出了巨大的贡献,使得 PHP 开发变得更好玩更令人激动。祝编码快乐! 22 | 23 | Taylor Otwell 24 | 25 | © 著作权归作者所有 26 | 27 | --- 28 | -------------------------------------------------------------------------------- /content/page/1.4_Too_Much_Java.md: -------------------------------------------------------------------------------- 1 | ## Too Much Java? 太像 Java 了? 2 | 3 | A common criticism of use of interfaces in PHP is that it makes your code too much like "Java". What people mean is that it makes the code very verbose. You must define an interface and an implementation, which leads to a few extra key-strokes. 4 | 5 | 有人会说使用接口让 PHP 代码看上去太像 Java 了——即代码太罗嗦了——你必须定义接口然后实现它,要多按好多下键盘。 6 | 7 | For small, simple applications, this criticism is probably valid. Interfaces are often unnecessary in these applications, and it is "OK" to just couple yourself to an implementation you know won't change. There is no need to use interfaces if you are certain your implementation will not change. Architecture astronauts will tell you that you can "never be certain". But, let's face it, sometimes you are. 8 | 9 | 对于小而简单的应用来说,以上说法也对。接口通常是不必要的。将代码耦合到那些你认为不会改变的地方也是可以的。在你确信不会改变的地方就没有必要使用接口了。架构师说“不会改变的地方是不存在的”。不过话说回来,有时候的确不会改。 10 | 11 | Interfaces are very helpful in large applications, and the extra key-strokes pale in comparison to the flexibility and testability you will gain. The ability to quickly swap implementations of a contract will "wow" your manager, and allow you to write code that easily adapts to change. 12 | 13 | 在大型应用中接口是很有帮助的。和提升的代码灵活性、可测试性比起来,多敲键盘费的功夫就微不足道了。当你迅速的切换了代码实现的时候,你的经理一定会被你的神速吓一跳的。你也可以写出更适应变化的代码。 14 | 15 | So, in conclusion, keep in mind that this book presents a very "pure" architecture. If you need to scale it back for a small application, don't feel guilty. Remember, we're all trying to "code happy". If you're not enjoying what you're doing or you are programming out of guilt. step back and re-evaluate. 16 | 17 | 总而言之, 记住本书提倡“简单”架构。如果你在写小程序的时候无法遵守接口原则,别觉得不好意思。 要记住我们写代码是要快乐的写。如果你不喜欢写接口,那就先简单的写代码吧。日后再精进即可。 18 | -------------------------------------------------------------------------------- /content/page/11.1_Introduction.md: -------------------------------------------------------------------------------- 1 | ## Introduction 介绍 2 | 3 | The Interface Segregation principle states that no implementation of an interface should be forced to depend on methods it does not use. Have you ever had to implement methods of an interface that you did not need? If so, you probably created blank methods in your implementation. This is an example of being forced to use an interface that breaks the Interface Segregation principle. 4 | 5 | 接口隔离原则规定在实现接口的时候,不能强迫去实现没有用处的方法。你是否曾被迫去实现一些接口里你用不到的方法?如果答案是肯定的,那你可能创建了一个空方法放在那里。被迫去实现用不到的函数,这就是一个违背了接口隔离原则的例子。 6 | 7 | In practical terms, this principle demands that interfaces be granular and focused. Sound familiar? Remember, all five SOLID principles are related, such that by breaking one you often must break the others. When breaking the Interface Segregation principle, the Single Responsibility principle must also broken. 8 | 9 | 在实际操作中,该原则要求接口必须粒度很细,且专注于一个领域。听起来很耳熟?记住,所有五个“坚实”原则都是相关的,也就是说当打破一个原则时,你通常肯定打破了其他的原则。在这里当你违背了接口隔离原则后,肯定也违背了单一职责原则。 10 | 11 | Instead of having a "fat" interface containing methods not needed by all implementations, it is preferable to have several smaller interfaces that may be implemented individually as needed. By breaking fat interfaces into smaller, more focused contracts, consuming code can depend on the smaller interface, without creating dependencies on parts of the application it does not use. 12 | 13 | “臃肿”的接口,有着很多不是所有的实现类都需要的方法。与其写这样的接口,不如将其拆分成多个小巧的接口,里面的方法都是各自领域所需要的。这样将臃肿接口拆成小巧、功能集中的接口后,我们就可以使用小接口来编码,而不必为我们不需要的功能买单。 14 | 15 | > ### Interface Segregation Principle 接口隔离原则 16 | 17 | > This principle states that no implementation of an interface should be forced to depend on methods it does not use. 18 | 19 | > 该原则规定,一个接口的一个实现类,不应该去实现那些自己用不到的方法。如果需要,那就是接口设计有问题,违背了接口隔离原则。 20 | -------------------------------------------------------------------------------- /content/page/7.7_Request_Extension.md: -------------------------------------------------------------------------------- 1 | ## Request Extension 请求的扩展 2 | 3 | Because it is such a foundational piece of the framework and is instantiated very early in the request cycle, extending the Request class works a little differently than the previous examples. 4 | 5 | 由于这玩意儿是框架里面非常基础的部分,并且在请求流程中很早就被实例化,所以要扩展 Request 类的方法与之前相比是有些许不同的。 6 | 7 | First, extend the class like normal: 8 | 9 | 首先还是要写个子类: 10 | 11 | ``` 12 | namespace QuickBill\Extensions; 13 | class Request extends \Illuminate\Http\Request { 14 | // Custom, helpful methods here... 15 | } 16 | ``` 17 | 18 | Once you have extended the class, open the bootstrap/start.php file. This file is one of the very first files to be included on each request to your application. Note that the first action performed is the creation of the Laravel $app instance: 19 | 20 | 子类写好后,打开 bootstrap/start.php 文件。该文件是应用的请求流程中最早被载入的几个文件之一。要注意被执行的第一个动作是创建 Laravel 的 $app 实例: 21 | 22 | ``` 23 | $app = new \Illuminate\Foundation\Application; 24 | ``` 25 | 26 | When a new application instance is created, it will create a new Illuminate\Http\Request instance and bind it to the IoC container using the request key. So, we need a way to specify a custom class that should be used as the "default" request type, right? And, thankfully, the requestClass method on the application instance does just this! So, we can add this line at the very top of our bootstrap/start.php file: 27 | 28 | 当新的应用实例创建后,它将会创建一个 Illuminate\Http\Request 的实例并且将其绑定到 IoC 容器里,键名为 request。所以我们需要找个方法来将一个自定义的类指定为“默认的”请求类,对不对?而且幸运的是,应用实例有一个名为 requestClass 的方法就是用来干这事儿的!所以我们只需要在 bootstrap/start.php 文件最上面加一行: 29 | 30 | ``` 31 | use Illuminate\Foundation\Application; 32 | Application::requestClass('QuickBill\Extensions\Request'); 33 | ``` 34 | 35 | Once you have specified the custom request class, Laravel will use this class anytime it creates a Request instance, conveniently allowing you to always have an instance of your custom request class available, even in unit test! 36 | 37 | 一旦你指定了自定义的请求类,Laravel 将在任何时候都可以使用这个 Request 类的实例。并使你很方便的能随时访问到它,甚至单元测试也不例外! 38 | -------------------------------------------------------------------------------- /content/page/4.3_Booting_Providers.md: -------------------------------------------------------------------------------- 1 | ## Booting Providers 服务提供者的启动过程 2 | 3 | After all providers have been registered, they are "booted". This will fire the boot method on each provider. A common mistake when using service providers is attempting to use the services provided by another provider in the register method. Since, within the register method, we have no gurantee all other providers have been loaded, the service you are trying to use may not be available yet. So, service provider code that uses other services should always live in the boot method. The register method should only be used for, you guessed it, registering services with the container. 4 | 5 | 在所有服务提供者都注册以后,他们就进入了“启动”过程。该过程会触发每个服务提供者的 boot 方法。这里会发生一种常见的错误用法:在 register 方法里面调用其他的服务。由于在 register 方法里我们不能保证所有其他服务都已经被加载,所以在该方法里调用别的服务有可能会出错。所以如果你想在服务提供者里调用别的服务,请在 boot 方法里做这种事儿。register 方法只能进行容器注册。 6 | 7 | Within the boot method, you may do whatever you like: register event listeners, include a route file, register filters, or anything else you can imagine. Again, use the provider as organizational tools. Perhaps you wish to group some related event listeners together? Placing them in the boot method of a service provider would be a great approach! Or, you could include an "events" or "routes" PHP file: 8 | 9 | 在启动方法里面,你想做什么都可以:注册事件监听,引入路由文件,注册过滤器,或者其他你能想象到的事儿。再强调一下,要发挥服务提供者的管理功能。可能你想将相关的多个事件监听归为一组?将他们放到一个服务提供者的 boot 方法里,这会很管用的!或者你也可以引入单独的 “events”、“routes” PHP文件: 10 | 11 | ``` 12 | public function boot() 13 | { 14 | require_once __DIR__.'/events.php'; 15 | require_once __DIR__.'/routes.php'; 16 | } 17 | ``` 18 | 19 | Now that we've learned about dependency injection and a way to organize our projects around providers, we have a great foundation for building well-architected Laravel applications that are maintainable and testable. Next, we'll explore how Laravel itself uses providers, and how the framework works under the hood! 20 | 21 | 我们已经学习了依赖注入以及如何使用服务提供者来组织管理我们的项目。这样我们的 Laravel 应用就有了一个很好的基础,它结构优美并且易于维护和测试。接下来,我们将探索 Laravel 框架本身是如何使用服务提供者的,并且深究其原理! 22 | 23 | > ### Don't Box Yourself In 不要让条条框框限制你自己 24 | 25 | > Remember, don't assume that service providers are something that only packages use. Create your own to help organize your application's services. 26 | 27 | > 记住,服务提供者不仅仅是专业的软件包才能使用。 请大胆的使用它来组织管理你的应用服务吧。 28 | -------------------------------------------------------------------------------- /content/page/4.4_Providing_The_Core.md: -------------------------------------------------------------------------------- 1 | ## Providing The Core 核心也是服务提供者的模式 2 | 3 | By now you have probably noticed that your application already has many service providers registered in the app configuration file. Each of these service providers bootstraps a part of the framework core. For example, the MigrationServiceProvider bootstraps the various classes that run migrations, as well as the migration Artisan commands. The EventServiceProvider bootstraps and registers the event dispatcher class. Some of these providers are larger than others, but they each bootstrap a piece of the core. 4 | 5 | 你可能已经注意到,在 app 配置文件里面已经有了很多服务提供者。每一个都负责启动框架核心的一部分。比如 MigrationServiceProvider 负责启动数据库迁移的类,包括 Artisan 里面的命令。EventServiceProvide 负责启动和注册事件调度机制。不同的服务提供者有着不同的复杂度,但他们都负责启动核心的一部分。 6 | 7 | > ### Meet Your Providers 和服务提供者们见见面 8 | 9 | > One of the best way to improve your understanding of the Laravel core is to read the source for the core service providers. If you are familiar with the function of service providers and what each core service provider registers, you will have an incredibly better understanding of how Laravel works under the hood. 10 | 11 | > 理解 Laravel 核心的最好方法是去读它的核心服务源码。如果你对这些服务的源码、容器注册等都很熟悉,那么你对 Laravel 是如何工作的将会有十分深刻的理解。 12 | 13 | Most of the core providers are deferred, meaning they do not load on every request; however, some, such as the FilesystemServiceProvider and ExceptionServiceProvider load on every request to your application since they bootstrap very foundational piece of the framework. One might say that the core service providers and the application container are Laravel. They are what ties all of the various pieces of the framework together into a single, cohesive unit. These providers are the building blocks of the framework. 14 | 15 | 大部分的服务提供者是延迟加载的,意味着并非所有请求都会调用到他们;然而有一些很基础的服务是每一次请求都会被加载的,比如 FilesystemServiceProvide 和 ExceptionServiceProvider 。有人会说核心服务提供者和应用程序容器就是 Laravel 。Laravel 其实是将这么多不同部分联系起来,形成一个单一的、内聚的整体的这么一个机制。拿建筑来比喻,那些服务提供者就是框架的预制模块。 16 | 17 | As mentioned previously, if you wish to gain a deeper understanding of how the framework works, read the source code of the core service providers that ship with Laravel. By reading through these providers, you will gain a solid understanding of how the framework is put together, as well as what each provider offers to your application. Furthermore, you will be better prepared to contribute to Laravel itself! 18 | 19 | 正如之前提到的那样,如果你想更深的了解框架是如何运行的,请读 Lravel 的核心服务的源码吧。读过之后,你会对框架如何将各部分组合在一起、每一个服务是如何为你所用这些机制有更坚实的理解。此外,有了这些进一步的理解,你也可以为 Laravel 添砖加瓦! 20 | -------------------------------------------------------------------------------- /content/page/5.2_MVC_Is_Killing_You.md: -------------------------------------------------------------------------------- 1 | ## MVC Is Killing You MVC 是慢性谋杀 2 | 3 | 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. 4 | 5 | 为了做出好的程序设计,最大的拦路虎就是一个简单的缩写词:M-V-C。模型、视图、控制器主宰了Web框架的思想已经好多年了。这种思想的流行某种程度上是托了 Ruby on Rails 愈加流行的福。然而,如果你问一个开发人员“模型”的定义是什么。通常你会听到他嘟哝着什么“数据库”之类的东西。这么说,模型就是数据库了。不管这意味着什么,模型里包含了关于数据库的一切。但是,你很快就会知道,你的应用程序需要的不仅仅是一个简单的数据库访问类。他需要更多的逻辑如:数据验证、调用外部服务、发送电子邮件,等等更多。 6 | 7 | > ### What Is A Model? 模型是啥? 8 | 9 | > 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. 10 | 11 | > 单词 "model" 的含义太模糊了,很难说明白准确的含义。更具体来讲,模型是用来将我们的应用划分成更小、更清晰的类,使得各代码部分有着明确的权责。 12 | 13 | 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. 14 | 15 | 所以怎么解决这个问题(译者注:上文中“更多的业务逻辑”)呢?很多开发者开始将业务逻辑包装到控制器里面。当控制器庞大到一定规模,他们将会需要重用业务逻辑。大部分开发人员没有将这些业务逻辑提取到别的类里面,而是错误的臆想他们需要在控制器里面调用别的控制器。这种模式通常被称为 “HMVC” 。不幸的是,这种模式通常也预示着糟糕的程序设计,并且控制器已经太复杂了。 16 | 17 | > ### HMVC (Usually) Indicates Poor Design HMVC(通常)预示着糟糕的设计。 18 | 19 | > 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. 20 | 21 | > 你觉得需要在控制器里面调用其他的控制器?这通常预示着糟糕的程序设计并且你的控制器里面业务逻辑太多了。把业务逻辑抽出来放到一个新的类里面,这样你就可以在其他任何控制器里面调用了。 22 | 23 | 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! 24 | 25 | 有一种更好的程序结构。但首先我们要忘掉以往我们被灌输的关于“模型”的一切。干脆点,让我们直接删掉 model 目录,重新开始吧! 26 | -------------------------------------------------------------------------------- /content/page/6.3_Other_Handlers.md: -------------------------------------------------------------------------------- 1 | ## Other Handlers 其他处理函数 2 | 3 | We can improve many other types of "handlers" using this same approach to decoupling. By restricting all handlers to being simple translation layers, you can keep your heavy business logic neatly organized and decoupled from the rest of the framework. To drive the point home further, let's examine a route filter that verifies that the current user of our application is subscribed to our "premium" pricing tier. 4 | 5 | 使用类似的方式,我们可以改进和解耦很多其他类型的“处理函数”。将这些处理函数限制在转换层的状态,你可以将你庞大的业务逻辑和框架解耦,并保持整洁的代码结构。为了巩固这种思想,我们来看看一个路由过滤器。该过滤器用来验证当前用户是否是交过钱的高级用户套餐。 6 | 7 | ``` 8 | Route::filter('premium', function() 9 | { 10 | return Auth::user() && Auth::user()->plan == 'premium'; 11 | }); 12 | ``` 13 | 14 | On first glance, this route filter looks very innocent. What could possibly be wrong with a filter that is so small? However, even in this small filter, we are leaking implementation details of our application into the code. Notice that we are manually checking the value of the plan variable. We have tightly coupled the representation of "plans" in our business layer into our routing / transport layer. Now, if we change how the "premium" plan is represented in our database or user model, we will need to change this route filter! 15 | 16 | 猛一看这路由过滤器没什么问题啊。这么简单的过滤器能有什么错误?然而就是是这么小的过滤器,我们却将我们应用实现的细节暴露了出来。要注意我们在该过滤器里是写明了要检查plan变量。这使得将“套餐方案”在我们应用中的代表值(译者注:即plan变量的值)暴露在了路由/传输层里面。现在我们若想调整“高级套餐”在数据库或用户模型的代表值,我们竟然就需要改这个路由过滤器! 17 | 18 | Instead, let's make a very simple change: 19 | 20 | 让我们简单改一点儿: 21 | 22 | ``` 23 | Route::filter('premium', function() 24 | { 25 | return Auth::user() && Auth::user()->isPremium(); 26 | }); 27 | ``` 28 | 29 | A small change like this has great benefits and very little cost. By deferring the determination of whether a user is on the premium plan to the model, we have removed all implementation details from our route filter. Our filter is no longer responsible for knowing how to determine if a user is on the premium plan. Instead, it simply asks the User model. Now, if the representation of premium plans changes in the database, there is no need to update the route filter! 30 | 31 | 小小的改变就带来巨大的效果,并且代价也很小。我们将判断用户是否使用高级套餐的逻辑放在了用户模型里,这样就从路由过滤器里去掉了对套餐判断的实现细节。我们的过滤器不再需要知道具体怎么判断用户是不是高级套餐了,它只要简单的把这个问题交给用户模型。现在如果我们想调整高级套餐在数据库里的细节,也不必再去改动路由过滤器了! 32 | 33 | > ### Who Is Responsible? 谁负责? 34 | 35 | > Again we find ourselves exploring the concept of responsibility. Remember, always be considering a class' responsibility and knowledge. Avoid making your transport layer, such as handler, responsible for knowledge about your application and business logic. 36 | 37 | > 在这里我们又一次讨论了责任的概念。记住,始终保持一个类应该有什么样的责任,应该知道什么。避免在处理函数这种传输层直接编写太多你应用的业务逻辑。 38 | 39 | *译者注:本文多次出现 transport layer, translation layer,分别译作传输层和转换层。其实他们应当指代的同一种东西。* 40 | -------------------------------------------------------------------------------- /content/page/7.6_IoC_Based_Extension.md: -------------------------------------------------------------------------------- 1 | ## IoC Based Extension 使用容器进行扩展 2 | 3 | Almost every service provider included with the Laravel framework binds objects into the IoC container. You can find a list of your application's service providers in the app/config/app.php configuration file. As you have time, you should skim through each of these provider's source code. By doing so, you will gain a much better understanding of what each providers adds to the framework, as well as that keys are used to bind various services into the IoC container. 4 | 5 | Laravel 框架内几乎所有的服务提供者都会绑定一些对象到 IoC 容器里。你可以在 app/config/app.php 文件里找到服务提供者列表。如果你有时间的话,你应该大致过一遍每个服务提供者的源码。这么做你便可以对每个服务提供者有更深的理解,明白他们都往框架里加了什么东西,对应的什么键。那些键就用来联系着各种各样的服务。 6 | 7 | For example, the PaginationServiceProvider binds a paginator key into the IoC container, which resolves into Illuminate\Pagination\Environment instance. You can easily extend and override this class within your own application by overriding this IoC binding. For example, you could create a class that extend the base Environment: 8 | 9 | 举个例子,PaginationServiceProvider 向容器内绑定了一个 paginator 键,对应着一个 Illuminate\Pagination\Environment 的实例。你可以很容易的通过覆盖容器绑定来扩展重写该类。比如,你可以创建一个扩展自 Environment 类的子类: 10 | 11 | ``` 12 | namespace Snappy\Extensions\Pagination; 13 | class Environment extends \Illuminate\Pagination\Environment { 14 | // 15 | } 16 | ``` 17 | 18 | Once you have created your class extension, you may create a new SnappyPaginationProvider service provider class which overrides the paginator in its boot method: 19 | 20 | 子类写好以后,你可以再创建个新的 SnappyPaginationProvider 服务提供者来扩展其 boot 方法,在里面覆盖 paginator: 21 | 22 | ``` 23 | class SnappyPaginationProvider extends PaginationServiceProvider { 24 | public function boot() 25 | { 26 | App::bind('paginator', function() 27 | { 28 | return new Snappy\Extensions\Pagination\Environment; 29 | } 30 | 31 | parent::boot(); 32 | } 33 | } 34 | ``` 35 | 36 | Note that this class extends the PaginationServiceProvider, not the default ServiceProvider base class. Once you have extended the service provider, swap out the PaginationServiceProvider in your app/config/app.php configuration file with the name of your extended provider. 37 | 38 | 注意这里我们继承了 PaginationServiceProvider,而非默认的基类 ServiceProvider。扩展的服务提供者编写完毕后,就可以在 app/config/app.php 文件里将 PaginationServiceProvider 替换为你刚扩展的那个类了。 39 | 40 | This is the general method of extending any core class that is bound in the container. Essentially every core class is bound in the container in this fashion, and can be overridden. Again, reading through the included framework service providers will familiarize you with where various classes are bound into the container, and what keys they are bound by. This is a great way to learn more about how Laravel is put together. 41 | 42 | 这就是扩展绑定进容器的核心类的一般方法。基本上每一个核心类都以这种方式绑定进了容器,都可以被重写。还是那一句话,读一遍框架内的服务提供者源码吧。这有助于你熟悉各种类是怎么绑定进容器的,都绑定的是哪些键。这是学习 Laravel 框架到底如何运转的好方法。 43 | -------------------------------------------------------------------------------- /content/page/1.2_Build_A_Contract.md: -------------------------------------------------------------------------------- 1 | ## Build A Contract 建立约定 2 | 3 | First, we'll define an interface and a corresponding implementation: 4 | 5 | 首先我们定义一个接口,然后实现该接口。 6 | 7 | ``` 8 | interface UserRepositoryInterface 9 | { 10 | public function all(); 11 | } 12 | 13 | class DbUserRepository implements UserRepositoryInterface 14 | { 15 | public function all() 16 | { 17 | return User::all()->toArray(); 18 | } 19 | } 20 | ``` 21 | 22 | Next, we'll inject an implementation of this interface into our controller: 23 | 24 | 然后我们将该接口的实现注入我们的控制器。 25 | 26 | ``` 27 | class UserController extends BaseController 28 | { 29 | public function __construct(UserRepositoryInterface $users) 30 | { 31 | $this->users = $users; 32 | } 33 | 34 | public function getIndex() 35 | { 36 | $users=$this->users->all(); 37 | return View::make('users.index', compact('users')); 38 | } 39 | } 40 | ``` 41 | 42 | 43 | Now our controller is completely ignorant of where our user data is being stored. In this case, ignorance is bless! Our data could be coming from MySQL, MongoDB, or Redis. Our controller doesn't know the difference, nor should it care. Just by making this small change, we can test our web layer independent of our data layer, as well as easily switch our storage implementation. 44 | 45 | 现在我们的控制器就完全和数据层面无关了。在这里无知是福!我们的数据可能来自 MySQL,MongoDB 或者 Redis。我们的控制器不知道也不需要知道他们的区别。仅仅做出了这么小小的改变,我们就可以独立于数据层来测试 Web 层了,将来切换存储实现也会很容易。 46 | 47 | > ### Respect Boundaries 严守边界 48 | 49 | > Remember to respect responsibility boundaries. Controllers and routes serve as a mediator between HTTP and your application. When writing large applications, don't clutter them up with your domain logic. 50 | 51 | > 记得要保持清晰的责任边界。 控制器和路由是作为 HTTP 和你的应用程序之间的中间件来用的。当编写大型应用程序时,不要将你的领域逻辑混杂在其中(控制器、路由)。 52 | 53 | 54 | To solidify our understanding, let's write a quick test. First, we'll mock the repository and bind it to the application IoC container. Then, we'll ensure that the controller properly calls the repository: 55 | 56 | 为了巩固学到的知识,咱们来写一个测试案例。首先,我们要模拟一个资料库然后绑定到应用的 IoC 容器里。 然后,我们要保证控制器正确的调用了这个资料库: 57 | 58 | ``` 59 | public function testIndexActionBindsUsersFromRepository() 60 | { 61 | // Arrange... 62 | $repository = Mockery::mock('UserRepositoryInterface'); 63 | $repository->shouldReceive('all')->once()->andReturn(array('foo')); 64 | App::instance('UserRepositoryInterface', $repository); 65 | // Act... 66 | $response = $this->action('GET', 'UserController@getIndex'); 67 | 68 | // Assert... 69 | $this->assertResponseOk(); 70 | $this->assertViewHas('users', array('foo')); 71 | } 72 | ``` 73 | 74 | > ### Are you Mocking Me 你在模仿我么? 75 | 76 | > In this example, we used the `Mockery` mocking library. This library provides a clean, expressive interface for mocking your classes. Mockery can be easily insalled via Composer. 77 | 78 | > 在上面的例子里, 我们使用了名为 `Mockery` 的模仿库。 这个库提供了一套整洁且富有表达力的方法,用来模仿你写的类。 Mockery 可以通过 Composer 安装。 79 | -------------------------------------------------------------------------------- /content/SUMMARY.md: -------------------------------------------------------------------------------- 1 | 2 | * [Author's Note](README.md) 3 | 4 | * 1 Dependency Injection 依赖注入 5 | - [1.1 The Problem 遇到的问题](./page/1.1_The_Problem.md) 6 | - [1.2 Build A Contract 建立约定](./page/1.2_Build_A_Contract.md) 7 | - [1.3 Take It further 更进一步](./page/1.3_Take_It_further.md) 8 | - [1.4 Too Much Java? 太像Java了?](./page/1.4_Too_Much_Java.md) 9 | * 2 The IoC Container 控制反转容器 10 | - [2.1 Basic Binding 基础绑定](./page/2.1_Basic_Binding.md) 11 | - [2.2 Reflective Resolution 反射解决方案](./page/2.2_Reflective_Resolution.md) 12 | * 3 Interface As Contract 接口约定 13 | - [3.1 Strong Typing & Water Fowl 强类型和小鸭子](./page/3.1_Strong_Typing_&_Water_Fowl.md) 14 | - [3.2 A Contract Example 约定的范例](./page/3.2_A_Contract_Example.md) 15 | - [3.3 Interface & Team Development 接口与团队开发](./page/3.3_Interface_&_Team_Development.md) 16 | * 4 Service Provider 服务提供者 17 | - [4.1 As Bootstrapper 他是引导程序](./page/4.1_As_Bootstrapper.md) 18 | - [4.2 As Organizer 作为管理工具](./page/4.2_As_Organizer.md) 19 | - [4.3 Booting Providers 服务提供者的启动过程](./page/4.3_Booting_Providers.md) 20 | - [4.4 Providing The Core 核心也是服务提供者的模式](./page/4.4_Providing_The_Core.md) 21 | * 5 Application Structure 应用结构 22 | - [5.1 Introduction 介绍](./page/5.1_Introduction.md) 23 | - [5.2 MVC Is Killing You MVC是慢性谋杀](./page/5.2_MVC_Is_Killing_You.md) 24 | - [5.3 Bye, Bye Models 再见,模型](./page/5.3_Bye_Models.md) 25 | - [5.4 It's All About The Layers 核心思想就是分层](./page/5.4_It's_All_About_The_Layers.md) 26 | - [5.5 Where To Put "Stuff" 东西都放哪儿?](./page/5.5_Where_To_Put_Stuff.md) 27 | * 6 Applied Architecture: Decoupling Handles 实用做法:解耦处理函数 28 | - [6.1 Introduction 介绍](./page/6.1_Introduction.md) 29 | - [6.2 Decoupling Handlers 解耦处理函数](./page/6.2_Decoupling_Handlers.md) 30 | - [6.3 Other Handlers 其他处理函数](./page/6.3_Other_Handlers.md) 31 | * 7 Extending The Framework 扩展框架 32 | - [7.1 Introduction 介绍](./page/7.1_Introduction.md) 33 | - [7.2 Managers & Factories 管理者和工厂](./page/7.2_Managers_&_Factories.md) 34 | - [7.3 Cache 缓存](./page/7.3_Cache.md) 35 | - [7.4 Session 会话](./page/7.4_Session.md) 36 | - [7.5 Authentication 身份认证](./page/7.5_Authentication.md) 37 | - [7.6 IoC Based Extension 使用容器进行扩展](./page/7.6_IoC_Based_Extension.md) 38 | - [7.7 Request Extension 请求的扩展](./page/7.7_Request_Extension.md) 39 | * 8 Single Responsibility Principle 单一职责原则 40 | - [8.1 Introduction 介绍](./page/8.1_Introduction.md) 41 | - [8.2 In Action 实践](./page/8.2_In_Action.md) 42 | * 9 Open Closed Principle 开放封闭原则 43 | - [9.1 Introduction 介绍](./page/9.1_Introduction.md) 44 | - [9.2 In Action 实践](./page/9.2_In_Action.md) 45 | * 10 Liskov Substitution Principle 里氏替换原则 46 | - [10.1 Introduction 介绍](./page/10.1_Introduction.md) 47 | - [10.2 In Action 实践](./page/10.2_In_Action.md) 48 | * 11 Interface Segregation Principle 接口隔离原则 49 | - [11.1 Introduction 介绍](./page/11.1_Introduction.md) 50 | - [11.2 In Action 实践](./page/11.2_In_Action.md) 51 | * 12 Dependency Inversion Principle 依赖反转原则 52 | - [12.1 Introduction 介绍](./page/12.1_Introduction.md) 53 | - [12.2 In Action 实践](./page/12.2_In_Action.md) 54 | -------------------------------------------------------------------------------- /content/page/3.2_A_Contract_Example.md: -------------------------------------------------------------------------------- 1 | ## A Contract Example 约定的范例 2 | 3 | Interfaces are contracts. Interfaces do not contain any code, but simply define a set of methods that an object must implement. If an object implements an interface, we are guranteed that every method defined by the interface is valid and callable on that object. Since the contract gurantees the implementation of certain methods, type safety becomes more flexible via polymorphism. 4 | 5 | 接口就是约定。接口不包含任何代码实现,只是定义了一个对象应该实现的一系列方法。如果一个对象实现了一个接口,那么我们就能确信这个接口所定义的一系列方法都能在这个对象上使用。因为有约定保证了特定方法的实现标准,通过多态也能使类型安全的语言变得更灵活。 6 | 7 | > ###Polywhat? 多什么肽? 8 | 9 | > Polymorphism is a big word that essentially means an entity can have multiple forms. In the context of this book, we mean that an interface can have multiple implementations. For example, a UserRepositoryInterface could have a MySQL and a Redis implementation, and both implementations would qualify as a UserRepositoryInterface instance. 10 | 11 | > 多态含义很广,其本质上是说一个实体拥有多种形式。在本书中,我们讲多态是一个接口有着多种实现。比如 UserRepositoryInterface 可以有 MySQL 和 Redis 两种实现,每一种实现都是 UserRepositoryInterface 的一个实例。 12 | 13 | To illustrate the flexibility that interfaces introduce into strongly typed languages, let's write some simple code that books hotel rooms. Consider the following interface: 14 | 15 | 为了说明在强类型语言中接口的灵活性,咱们来写一个酒店客房预订的代码。考虑以下接口: 16 | 17 | ``` 18 | interface ProviderInterface{ 19 | public function getLowestPrice($location); 20 | public function book($location); 21 | } 22 | ``` 23 | 24 | When our user books a room, we'll want to log that in our system, so let's add a few methods to our User class: 25 | 26 | 当用户订房间时,我们需要将此事记录在系统里。所以在 User 类里面写点方法: 27 | 28 | ``` 29 | class User{ 30 | public function bookLocation(ProviderInterface $provider, $location) 31 | { 32 | $amountCharged = $provider->book($location); 33 | $this->logBookedLocation($location, $amountCharged); 34 | } 35 | } 36 | ``` 37 | 38 | Since we are type hinting the ProviderInterface, our User can safely assume that the book method will be available. This gives us the flexibility to re-use our bookLocation method regardless of the hotel provider the user prefers. Finally, let's write some code that harnesses this flexibility: 39 | 40 | 因为我们写出了 ProviderInterface 的类型提示,该 User 类的就可以放心大胆的认为 book 方法是可以调用的。这使得 bookLocation 方法有了重用性。当用户想要换一家酒店提供商时也就更灵活。最后咱们来写点代码来强化他的灵活性。 41 | 42 | ``` 43 | $location = 'Hilton, Dallas'; 44 | 45 | $cheapestProvider = $this->findCheapest($location, array( 46 | new PricelineProvider, 47 | new OrbitzProvider, 48 | )); 49 | 50 | $user->bookLocation($cheapestProvider, $location); 51 | ``` 52 | 53 | Wonderful! No matter what provider is the cheapest, we are able to simply pass it along to our User instance for booking. Since our User is simply asking for an object instances that abides by the ProviderInterface contract, our code will continue to work even if we add new provider implementations. 54 | 55 | 太棒了!不管哪家是最便宜的,我们都能够将他传入 User 对象来预订房间了。由于 User 对象只需要要有一个符合 ProviderInterface 约定的实例就可以预订房间,所以未来有更多的酒店供应商我们的代码也可以很好的工作。 56 | 57 | > ### Forget The Details 忘掉细节 58 | 59 | > Remember, interfaces don't actually do anything, They simply define a set of methods that an implementing class must have. 60 | 61 | > 记住,接口实际上不真正做任何事情。它只是简单的定义了类们必须实现的一系列方法。 62 | -------------------------------------------------------------------------------- /content/page/5.4_It's_All_About_The_Layers.md: -------------------------------------------------------------------------------- 1 | ## It's All About The Layers 核心思想就是分层 2 | 3 | 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. 4 | 5 | 你可能注意到,优化应用的设计结构的关键就是责任划分,或者说是创建不同的责任层次。控制器只负责接收和响应 HTTP 请求然后调用合适的业务逻辑层的类。你的业务逻辑/领域逻辑层才是你真正的程序。你的程序包含了读取数据,验证数据,执行支付,发送电子邮件,还有你程序里任何其他的功能。事实上你的领域逻辑层不需要知道任何关于“网络”的事情!网络仅仅是个访问你程序的传输机制,关于网络和 HTTP 请求的一切不应该超出路由和控制器层。做出好的设计的确很有挑战性,但好的设计也会带来可持续发展的清晰的好代码。 6 | 7 | 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: 8 | 9 | 举个例子。与其在你业务逻辑类里面直接获取网络请求,不如你直接把网络请求从控制器传给你的业务逻辑类。这个简单的改动将你的业务逻辑类和“网络”分离开了,并且不必担心怎么去模拟网络请求,你的业务逻辑类就可以简单的测试了: 10 | 11 | ``` 12 | class BillingController extends BaseController{ 13 | public function __construct(BillerInterface $biller) 14 | { 15 | $this->biller = $biller; 16 | } 17 | public function postCharge() 18 | { 19 | $this->biller->chargeAccount(Auth::user(), Input::get('amount')); 20 | return View::make('charge.success'); 21 | } 22 | } 23 | ``` 24 | 25 | 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. 26 | 27 | 现在 chargeAccount 方法更容易测试了。 我们把 Request 和 Input 从 BillingInterface 里提出来,然后在控制器里把方法需要的支付金额直接传过去。 28 | 29 | 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. 30 | 31 | 编写拥有高可维护性应用程序的关键之一,就是责任分割。要时常检查一个类是否管得太宽。你要常常问自己“这个类需不需要关心 XXX 呢?”如果答案是否定的,那么把这块逻辑抽出来放到另一个类里面,然后用依赖注入的方式进行处理。(译者注:依赖注入的不同方式还记得么?调用方法传参、构造函数传参、从 IoC 容器获取等等。) 32 | 33 | > ### Single Reason To Change 34 | 35 | > 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 Biller implementation 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. 36 | 37 | > 如何判断一个类是否管得太宽,有一个有用的方法就是检查你为什么要改这块儿代码。举个例子:当我们想调整通知逻辑的时候,我们需要修改 Biller 的实现代码么?当然不需要,Biller 的实现仅仅需要考虑支付,它与通知逻辑应当仅通过约定来进行交互。使用这种思路过一遍代码,会让你很快找出应用中需要改进的地方。 38 | -------------------------------------------------------------------------------- /content/page/1.1_The_Problem.md: -------------------------------------------------------------------------------- 1 | ## The Problem 遇到的问题 2 | 3 | The foundation of the Laravel framework is its powerful IoC container. To truly understand the framework, a strong grasp of the container is necessary. However, we shold note that an IoC container is simply a convenience mechanism for achieving a software design pattern: dependency injectionl. A container is not necessary to perform dependency injection, it simply makes the task easier. 4 | 5 | Laravel 框架的基础是一个功能强大的控制反转容器(IoC container)。 为了真正理解本框架,需要好好掌握该容器。但我们要搞清楚,控制反转容器只是一种用于方便实现“依赖注入”的工具。要实现依赖注入并不一定需要控制反转容器,只是用容器会更方便和容易一点儿。 6 | 7 | 8 | First, let's explore why dependency injection is beneficial. Consider the following class and method: 9 | 10 | 首先来看看我们为何要使用依赖注入,它能带来什么好处。 考虑下列代码: 11 | 12 | ``` 13 | class UserController extends BaseController{ 14 | public function getIndex() 15 | { 16 | $users= User::all(); 17 | return View::make('users.index', compact('users')); 18 | } 19 | } 20 | ``` 21 | 22 | While this code is concise, we are unable to test it without hitting an actual database. In other words, the Eloquent ORM is tightly coupled to out controller. We have no way to use or test this controller without also using the entire Eloquent ORM, including hitting a live database. This code also violates a software design principle commonly called separation of concerns. Simple put: our controller knows too much. Controllers do not need to know where data comes from, but only how to access it. The controller doesn't need to know that the data is available in MySQL, but only that it is available somewhere. 23 | 24 | 这段代码很简短,但我们要想测试这段代码的话就一定会和实际的数据库发生联系。也就是说, Eloquent ORM(译者注:Laravel 的数据库对象模型库)和该控制器有着紧耦合。如果不使用 Eloquent ORM,不连接到实际数据库,我们就没办法运行或者测试这段代码。这段代码同时也违背了“关注分离”这个软件设计原则。简单讲:这个控制器知道的太多了。 控制器不需要去了解数据是从哪儿来的,只要知道如何访问就行。控制器也不需要知道这数据是从 MySQL 或哪儿来的,只需要知道这数据目前是可用的。 25 | 26 | 27 | > ### Separation Of Concerns 关注分离 28 | 29 | > Every class should have a single responsibility, and that responsibility should be entirely encapsulated by the class. 30 | 31 | > 每一个类都应该有单独的职责,并且该职责应完全被这个类封装。(译者注:我认为就是不要让多个类负责同样的职责) 32 | 33 | 34 | So, it will be beneficial for us to decouple our web layer (controller) from our data access layer completely. This will allow us to migrate storage implementations easily, as well as make our code easier to test. Think of the "Web" as just a transport layer into your "real" application. 35 | 36 | 关注分离的好处就是能让 Web 控制器和数据访问解耦。这会使得实现存储迁移更容易,测试也会更容易。“Web” 就仅仅是为你真正的应用做数据的传输了。 37 | 38 | Imagine that your application is like a monitor with a variety of cable ports. You can access the monitor's functionality via HDMI, VGA, or DVI. Think of the Internet as just a cable into your application. The bulk of a monitor's functionality is independent of the cable. The cable is just a transport mechanism just like HTTP is a transport mechanism for your application. So, we don't want to clutter up our transport mechanism (the controller) with application logic. This will allow any transport layer, such as an API or mobile application, to access our application logic. 39 | 40 | 想象一下你有一个类似于监视器的程序,有着很多线缆接口(HDMI,VGA,DVI等等)。 你可以通过不同的接口访问不同的监视器。把 Internet 想象成另一个插进你程序线缆接口。大部分显示器的功能是与线缆接口互相独立的。线缆接口只是一 种传输机制就像 HTTP 是你程序的一种传输机制一样。所以我们不想把传输机制(控制器)和业务逻辑混在一起。这样的好处是很多其他的传输机制比如 API 调用、移动应用等都可以访问我们的业务逻辑。 41 | 42 | So, instead of coupling our controller to the Eloquent ORM, let's inject a repository class. 43 | 44 | 那么我们就别再将控制器和 Eloquent ORM 耦合在一起了。 咱们注入一个资料库类。 45 | -------------------------------------------------------------------------------- /content/page/1.3_Take_It_further.md: -------------------------------------------------------------------------------- 1 | ## Taking It Further 更进一步 2 | 3 | Let's consider another example to solidify our understanding. Perhaps we want to notify customers of charges to their account. We'll define two interfaces, or contracts. These contracts will gie us the flexibility to change out their implementations later. 4 | 5 | 让我们考虑另一个例子来巩固理解。 可能我们想要去提醒用户该交钱了。 我们会定义两个接口,或者约定。这些约定使我们在更改实际实现时更加灵活。 6 | 7 | ``` 8 | interface BillerInterface { 9 | public function bill(array $user, $amount); 10 | } 11 | 12 | interface BillingNotifierInterface { 13 | public function notify(array $user, $amount); 14 | } 15 | ``` 16 | 17 | Next we'll build an implementation of our BillerInterface contract: 18 | 19 | 接下来我们要写一个 BillerInterface 的实现: 20 | 21 | ``` 22 | class StripeBiller implements BillerInterface{ 23 | public function __construct(BillingNotifierInterface $notifier) 24 | { 25 | $this->notifier = $notifier; 26 | } 27 | public function bill(array $user, $amount) 28 | { 29 | // Bill the user via Stripe... 30 | $this->notifier->notify($user, $amount); 31 | } 32 | } 33 | ``` 34 | 35 | By separating the responsibilities of each class, we're now able to easily inject various notifier implementations into our billing class. For example, we could inject a SmsNotifier or an EmailNotifier. Our biller is no longer concerned with the implementation of notifyingg, but only the contract. As long as a class abides by its contract (interface), the biller will gladly accept it. Furthermore, not only do we get added flexibility, we can now test our biller in isolation from our notifiers by injecting a mock BillingNotifierInterface. 36 | 37 | 只要遵守了每个类的责任划分,我们很容易将不同的提示器(notifier)注入到账单类里面。 比如,我们可以注入一个 SmsNotifier 或者 EmailNotifier。账单类只要遵守了约定,就不用再考虑如何实现提示功能。只要是遵守约定(接口)的类, 账单类都能用。这不仅仅是方便了我们的开发,而且我们还可以通过模拟 BillingNotifierInterface 来进行无痛测试。 38 | 39 | > ### Be The Interface 使用接口 40 | 41 | > While writing interfaces might seem to a lot of extra work, they can actually make your development more rapid. Use interfaces to mock and test the entire back-end of your application before writing a single line of implementation! 42 | 43 | > 写接口可能看上去挺麻烦,但实际上能加速你的开发。你不用实现任何接口,就能使用模拟库来模拟你的接口,进而测试整个后台逻辑! 44 | 45 | So, how do we do dependency injection? It's simple: 46 | 47 | 那我们如何做依赖注入呢?很简单: 48 | 49 | ``` 50 | $biller = new StripeBiller(new SmsNotifier); 51 | ``` 52 | 53 | That's dependency injection. Instead of the biller being concerned with notifying users, we simply pass it a notifier. A change this simple can do amazing things for your applications. Your code immediately becomes more maintainable since class responsibilities are clearly delineated. Also, testability will skyrocket as you can easily inject mock dependencies to isolate the code under test. 54 | 55 | 这就是依赖注入。 biller 不需再考虑提醒用户的事儿,我们直接传给他一个提示器(notifier)。 这种微小的改动能使你的应用焕然一新。 你的代码马上就变得更容易维护, 因为明确指定了类的职责边界。 并且更容易测试,你只需使用模拟依赖即可。 56 | 57 | But what about IoC containers? Aren't they necessary to do dependency injection? Absolutely not! As we'll see in the following chapters, containers make dependency injection easier to manage, but they are not a requirement. By following the principles in this chapter, you can practice dependency injection in any of your projects, regardless of whether a container is avaliable to you. 58 | 59 | 那 IoC 容器呢? 难道依赖注入不需要 IoC 容器么?当然不需要!在接下来的章节里面你会了解到,容器使得依赖注入更易于管理,但是容器不是依赖注入所必须的。只要遵循本章提出的原则,你可以在你任何的项目里面实施依赖注入,而不必管该项目是否使用了容器。 60 | -------------------------------------------------------------------------------- /content/page/7.3_Cache.md: -------------------------------------------------------------------------------- 1 | ## Cache 缓存 2 | 3 | To extend the Laravel cache facility, we will use the extend method on the CacheManager, which is used to bind a custom driver resolver to the manager, and is common across all manager classes. For example, to register a new cache driver named "mongo", we would do the following: 4 | 5 | 要扩展 Laravel 的缓存机制,我们将使用 CacheManager 里的 extend 方法来绑定我们自定义的缓存驱动。扩展其他的管理类也是类似的。比如,我们想注册一个新的缓存驱动,名叫 “mongo”,代码可以这样写: 6 | 7 | ``` 8 | Cache::extend('mongo', function($app) 9 | { 10 | // Return Illuminate\Cache\Repository instance... 11 | }); 12 | ``` 13 | 14 | The first argument passed to the extend method is the name of the driver. This will correspond to your driver option in the app/config/cache.php configuration file. The second argument is a Closure that should return an Illuminate\Cache\Repository instance. The Closure will be passed an $app instance, which is an instance of Illuminate\Foundation\Application and an IoC container. 15 | 16 | extend 方法的第一个参数是你要定义的驱动的名字。该名字对应着 app/config/cache.php 配置文件中的 driver 项。第二个参数是一个匿名函数(闭包),该匿名函数有一个 $app 参数是 Illuminate\Foundation\Application 的实例也是一个 IoC 容器,该匿名函数要返回一个 Illuminate\Cache\Repository 的实例。 17 | 18 | To create our custom cache driver, we first need to implement the Illuminate\Cache\StoreInterface contract. So, our MongoDB cache implementation would look something like this: 19 | 20 | 要创建我们自己的缓存驱动,首先要实现 Illuminate\Cache\StoreInterface 接口。所以我们用 MongoDB 来实现的缓存驱动就可能看上去是这样: 21 | 22 | ``` 23 | class MongoStore implements Illuminate\Cache\StoreInterface { 24 | public function get($key) {} 25 | public function put($key, $value, $minutes) {} 26 | public function increment($key, $value = 1) {} 27 | public function decrement($key, $value = 1) {} 28 | public function forever($key, $value) {} 29 | public function forget($key) {} 30 | public function flush() {} 31 | } 32 | ``` 33 | 34 | We just need to implement each of these methods using a MongoDB connection. Once our implementation is complete, we can finish our custom driver registeration: 35 | 36 | 我们只需使用 MongoDB 链接来实现上面的每一个方法即可。一旦实现完毕,就可以照下面这样完成该驱动的注册: 37 | 38 | ``` 39 | use Illuminate\Cache\Repository; 40 | Cache::extend('mongo', function($app) 41 | { 42 | return new Repository(new MongoStore); 43 | } 44 | ``` 45 | 46 | As you can see in the example above, you may use the base Illuminate\Cache\Repository when creating custom cache drivers. These is typically no need to create your own repository class. 47 | 48 | 你可以像上面的例子那样来创建 Illuminate\Cache\Repository 的实例。也就是说通常你不需要创建你自己的仓库类(Repository)。 49 | 50 | If you're wondering where to put your custom cache driver code, consider making it available on Packagist! Or, you could create an Extensions namespace within your application's primary folder. For example, if the application is named Snappy, you could place the cache extension in app/Snappy/Extensions/MongoStore.php. However, keep in mind that Laravel doesn not have a rigid application structure and you are free to organize your application according to your preferences. 51 | 52 | 如果你不知道要把自定义的缓存驱动代码放到哪儿,可以考虑放到 Packagist 里!或者你也可以在你应用的主目录下创建一个 Extensions 目录。比如,你的应用叫做 Snappy ,你可以将缓存扩展代码放到 app/Snappy/Extensions/MongoStore.php 。不过请记住 Laravel 没有对应用程序的结构做硬性规定,所以你可以按任意你喜欢的方式组织你的代码。 53 | 54 | > ### Where To Extend 在哪儿调用Extend方法? 55 | 56 | > If you're ever wondering where to put a piece of code, always consider a service provider. As we've discussed, using a service provider to organize framework extensions is a great way to organize your code. 57 | 58 | > 如果你还发愁在哪儿放注册代码,先考虑放到服务提供者里吧。我们之前就讲过,使用服务提供者是一种非常棒的管理你应用代码的途径。 59 | -------------------------------------------------------------------------------- /content/page/3.3_Interface_&_Team_Development.md: -------------------------------------------------------------------------------- 1 | ## Interfaces & Team Development 接口与团队开发 2 | 3 | When you team is building a large application, pieces of the application progress at different speeds. For example, imagine one developer is working on the data layer, while another developer is working on the front-end and web/controller layer. The front-end developer wants to test his controllers, but the back-end is progressing slowly. However, what if the two developers can agree on an interface, or contract, that the back-end classes will abide by, such as following: 4 | 5 | 当你的团队在开发大型应用时,不同的部分有着不同的开发速度。比如一个开发人员在制作数据层,另一个开发人员在做前端和网站控制器层。前端开发者想测试他的控制器,不过后端开发较慢没法同步测试。那如果两个开发者能以接口的方式达成协议,后台开发的各种类都遵循这种协议,就像这样: 6 | 7 | ``` 8 | interface OrderRepositoryInterface { 9 | public function getMostRecent(User $user); 10 | } 11 | ``` 12 | 13 | Once the contract has been agreed upon, the front-end developer can test his controller, even if no real implementation of the contract has been written! This allows components of the application to be built at different speeds, while still allowing for proper unit tests to be written. Further, this approach allows entire implementations to change without breaking other, unrelated components. Remember, ignorance is bliss. We don't want our classes to know how a dependency does something, but only that it can. So, now that we have a contract defined, let's write our controller: 14 | 15 | 一旦建立了约定,就算约定还没实现,前端开发者也可以测试他的控制器了!这样应用中的不同组件就可以按不同的速度开发,并且单元测试也可以做。而且这种处理方法还可以使组件内部的改动不会影响到其他不相关组件。要记着无知是福。我们写的那些类们不用知道别的类如何实现的,只要知道它们能实现什么。这下咱们有了定义好的约定,再来写控制器: 16 | 17 | ``` 18 | class OrderController { 19 | public function __construct(OrderRepositoryInterface $orders) 20 | { 21 | $this->orders = $orders; 22 | } 23 | public function getRecent() 24 | { 25 | $recent = $this->orders->getMostRecent(Auth::user()); 26 | return View::make('orders.recent', compact('recent')); 27 | } 28 | } 29 | ``` 30 | 31 | The front-end developer could even write a "dummy" implementation of the interface, so the application's views can be populated with some fake data: 32 | 33 | 前端开发者甚至可以为这接口写个“假”实现,然后这个应用的视图就可以用假数据填充了: 34 | 35 | ``` 36 | class DummyOrderRepository implements OrderRepositoryInterface { 37 | public function getMostRecent(User $user) 38 | { 39 | return array('Order 1', 'Order 2', 'Order 3'); 40 | } 41 | } 42 | ``` 43 | 44 | Once the dummy implementation has been written, it can be bound in the IoC container so it is used throughout the application: 45 | 46 | 一旦假实现写好了,就可以被绑定到 IoC 容器里,然后整个程序都可以调用他了: 47 | 48 | ``` 49 | App::bind('OrderRepositoryInterface', 'DummyOrderRepository'); 50 | ``` 51 | 52 | Then, once a "real" implementaion, such as RedisOrderRepository, has been written by the back-end developer, the IoC binding can simply be switched to the new implementation, and the entire application will begin using orders that are stored in Redis. 53 | 54 | 接下来一旦后台开发者写完了真正的实现代码,比如叫 RedisOrderRepository。那么 IoC 容器就可以轻易的切换到真正的实现上。整个应用就会使用从 Redis 读出来的数据。 55 | 56 | > ### Interface As Schematic 接口就是大纲 57 | 58 | > Interfaces are helpful for developing a "skeleton" of defined functionality provided by your application. Use them during the design phase of a component to facilicate design discussion amongst your team. For example, define a BillingNotifierInterface and discuss its methods with your team. Use the interface to agree on a good API before you actually write any implementation code! 59 | 60 | > 接口在开发程序的“骨架”时非常有用。在设计组件时,使用接口进行设计和讨论都是对你的团队有益处的。比如定义一个 BillingNotifierInterface 然后讨论他有什么方法。在写任何实现代码前先用接口讨论好一套好的 API! 61 | -------------------------------------------------------------------------------- /content/page/7.4_Session.md: -------------------------------------------------------------------------------- 1 | ## Session 会话 2 | 3 | Extending Laravel with a custom session driver is just as easy as extending the cache system. Again, we will use the extend method to register our custom code: 4 | 5 | 扩展 Laravel 的会话机制和上文的缓存机制一样简单。和刚才一样,我们使用 extend 方法来注册自定义的代码: 6 | 7 | ``` 8 | Session::extend('mongo', function($app) 9 | { 10 | // Return implementation of SessionHandlerInterface 11 | }); 12 | ``` 13 | 14 | Note that our custom cache driver should implement the SessionHandlerInterface. This interface is included in the PHP 5.4+ core. If you are using PHP 5.3, the interface will be defined for you by Laravel so you have forward-compatibility. This interface contains just a few simple methods we need to implement. A stubbed MongoDB implementation would look something like this: 15 | 16 | 注意我们自定义的会话驱动(译者注:原味是 cache driver ,应该是笔误。正确应为 session driver )实现的是 SessionHandlerInterface 接口。这个接口在 PHP 5.4 以上版本才有。但如果你用的是 PHP 5.3 也别担心,Laravel 会自动帮你定义这个接口的。该接口要实现的方法不多也不难。我们用 MongoDB 来实现就像下面这样: 17 | 18 | ``` 19 | class MongoHandler implements SessionHandlerInterface { 20 | public function open($savePath, $sessionName) {} 21 | public function close() {} 22 | public function read($sessionId) {} 23 | public function write($sessionId, $data) {} 24 | public function destroy($sessionId) {} 25 | public function gc($lifetime) {} 26 | } 27 | ``` 28 | 29 | Since these methods are not as readily understandable as the cache StoreInterface, let's quickly cover what each of the methods do: 30 | 31 | 这些方法不像刚才的 StoreInterface 接口定义的那么容易理解。我们来挨个简单讲讲这些方法都是干啥的: 32 | 33 | * The open method would typically be used in file based session store system. Since Laravel ships with a native session driver that uses PHP's native file storage for sessions, you will almost never need to put anything in this method. You can leave it as an empty stub. It is simply a fact of poor interface design (which we'll discuss later) that PHP requires us to implement this method. 34 | * open 方法一般在基于文件的会话系统中才会用到。Laravel 已经自带了一个 native 的会话驱动,使用的就是 PHP 自带的基于文件的会话系统,你可能永远也不需要在这个方法里写东西。所以留空就好。另外这也是一个接口设计的反面教材(稍后我们会继续讨论这一点)。 35 | 36 | * The close method, like the open method, can also usually be disregarded. For most drivers, it is not needed. 37 | 38 | * close 方法和 open 方法通常都不是必需的。对大部分驱动来说都不必要实现。 39 | 40 | * The read method should return the string version of the session data associated with the given $sessionId. There is no need to do any serialization or other encoding when retrieving or storing session data in your driver, as Laravel will perform the serialization for you. 41 | 42 | * read 方法应该根据 $sessionId 参数来返回对应的会话数据的字符串形式。在你的会话驱动里,不论读写都不需要做任何数据序列化工作。因为 Laravel 会负责数据序列化的。 43 | 44 | * The write method should write the given $data string associated with the $sessionId to some persistent storage system, such as MongoDB, Dynamo, etc. 45 | 46 | * write 方法应该将 $sessionId 对应的 $data 字符串放置在一个持久化存储系统中。比如 MongoDB,Dynamo 等等。 47 | 48 | * The destroy method should remove the data associated with the $sessionId from persistent storage. 49 | 50 | * destroy 方法应该将 $sessionId 对应的数据从持久化存储系统中删除。 51 | 52 | * The gc method should destroy all session data that is older than the given $lifetime, which is a UNIX timestamp. For self-expiring systems like Memcached and Redis, this method may be left empty. 53 | 54 | * gc 方法应该将所有时间超过参数 $lifetime 的数据全都删除,该参数是一个 UNIX 时间戳。如果你使用的是类似 Memcached 或 Redis 这种有自主到期功能的存储系统,那该方法可以留空。 55 | 56 | Once the SessionHandlerInterface has been implemented, we are ready to register it with the Session manager: 57 | 58 | 一旦 SessionHandlerInterface 实现完毕,我们就可以将其注册进会话管理器: 59 | 60 | ``` 61 | Session::extend('mongo', function($app) 62 | { 63 | return new MongoHandler; 64 | }); 65 | ``` 66 | 67 | Once the session driver has been registered, we may use the mongo driver in our app/config/session.php configuration file. 68 | 69 | 注册完毕后,我们就可以在 app/config/session.php 配置文件里使用 mongo 驱动了。 70 | 71 | > ### Share Your Knowledge 分享你的知识 72 | 73 | > Remember, if you write a custom session handler, share it on Packagist! 74 | 75 | > 你要是写了个自定义的会话处理器,别忘了在Packagist上分享啊! 76 | -------------------------------------------------------------------------------- /content/page/4.1_As_Bootstrapper.md: -------------------------------------------------------------------------------- 1 | ## As Bootstrapper 他是引导程序 2 | 3 | A Laravel service provider is a class that registers IoC container bindings. In fact, Laravel ships with over a dozen service providers that manage the container bindings for the core framework components. Almost every component of the framework is registered with the container via a provider. A list of the providers currently being used by your application can be found in the providers array within the app/config/app.php configuration file. 4 | 5 | 一个 Laravel 服务提供者就是一个用来进行 IoC 绑定的类。事实上,Laravel 有好几十个服务提供者,用于管理框架核心组件的容器绑定。几乎框架里每一个组件的 IoC 绑定都是靠服务提供者来做的。你可以在 app/config/app.php 这个文件里查看目前有哪些服务提供者。 6 | 7 | A service provider must have at least one method: register. The register method is where the provide binds classes to the container. When a request enters your application and the framework is booting up, the register method is called on the providers listed in your configuration file. This happens very early in the application life-cycle so that all of the Laravel services will be available to you when your own bootstrap files, such as those in the start directory, are loaded. 8 | 9 | 一个服务提供者必须有一个 register 方法。你可以在这个方法里写 IoC 绑定。当一个请求发过来,程序框架刚启动时,所有在你配置文件里的服务提供者的 register 方法就会被调用。这在程序周期的很早的地方就会执行,所以在你自己的引导代码(比如那些在 start 目录里的文件)里所有的服务已经准备好了。 10 | 11 | > ### Register Vs. Boot 注册Vs引导代码 12 | 13 | > Never attemp to use services in the register method. This method is only for binding objects into the IoC container. All resolutions and interaction with the bound classes must be done inside the boot method. 14 | 15 | > 永远不要在 register 方法里面使用任何服务。该方法只是用来进行 IoC 绑定的地方。所有关于绑定类后续的判断、交互都要在 boot 方法里进行。 16 | 17 | Some third-party packages you install via Composer will ship with a service provider. The package's installation instructions will typically tell you to register their provider with your application by adding it to the providers array in your configuration file. Once you've registered the package's provider, it is ready for use. 18 | 19 | 你用 Composer 安装的一些第三方包也会有服务提供者。在第三方包的安装说明里一般都会告诉你要在 providers 数组里加上一行。一旦你加上了,那这个服务就算安装好了。 20 | 21 | > ### Package Providers包提供者 22 | 23 | > Not all third-party packages require a service provider. In fact, no package requires a service provider, as service providers generally just bootstrap components so they are ready for use immediately. They are simply a convenient place to organize bootstrap code and container bindings. 24 | 25 | > 不是所有的第三方包都需要服务提供者。事实上一个包并不需要服务提供者。因为服务提供者只是一个用来自动初始化服务组件的地方,一个方便管理引导代码和容器绑定的地方。 26 | 27 | ### Deferred Providers 延迟加载的服务提供者 28 | 29 | Not every provider that is listed in your providers configuration array will be instantiated on every request. This would be detrimental to performance, especially if the services the provider registers are not needed for the request. For example, the QueueServiceProvider services are not needed on every request, but only on requests where the queue is actually utilized. 30 | 31 | 并非在你配置文件中的 providers 数组里的所有提供者在每次请求都会被实例化。否则会对性能不利,尤其是这个服务的功能用不到的情况下。比如,QueueServiceProvider 服务就不是每次都用得到。 32 | 33 | In order to only instantiate the providers that are needed for a given request, Laravel generates a "service manifest" that is stored in the app/storage/meta directory. This manifest lists all of the service providers for the application, as well as the names of the container bindings they register. So, when the application asks the container for the queue container binding, Laravel knows that it needs to instantiate and run the QueueServiceProvider because it is listed as providing the queue service in the manifest. This allows the framework to lazy-load service providers for each request, greatly increasing performance. 34 | 35 | 为了达到只实例化需要的服务的提供者,Laravel 生成了“服务清单”并且储存在了 app/storage/meta 目录下。这份清单列出了应用里所有的服务提供者,包括容器绑定的名字也记录了。这样,当应用想让容器取出一个名为 queue 的绑定时,Laravel 知道需要先实例化并运行 QueueServiceProvider 因为在服务清单里记录着该服务提供者能提供 queue 的绑定。如此这般框架就能够延迟加载每个请求需要的服务了,性能大大提高。 36 | 37 | > ### Manifest Generation 如何生成服务清单 38 | 39 | > When you add a service provider to the providers array, Laravel will automatically regenerate the service manifest file during the next request to the application. 40 | 41 | > 当你在providers数组里新增一条,Laravel在下一次请求时就会自动重新生成服务清单。 42 | 43 | As you have time, take a look through the service manifest so you are aware of its contents. Understanding how this file is structured will be helpful if you ever need a debug a deferred service provider. 44 | 45 | 如果你有时间,去看看服务清单文件里面的内容。理解这个文件的结构有助于你对服务进行排错。 46 | -------------------------------------------------------------------------------- /content/page/3.1_Strong_Typing_&_Water_Fowl.md: -------------------------------------------------------------------------------- 1 | ## Strong Typing & Water Fowl 强类型和小鸭子 2 | 3 | In the previous chapters, we covered the basics of dependency injection: what it is; how it is accomplished; and several of benefits. The examples in the previous chapters also demonstrated the injection of interface into our classes, so before proceeding further, it will be beneficial to talk about interfaces in depth, as many PHP developers are not familiay with them. 4 | 5 | 在之前的章节里,涵盖了依赖注入的基础知识:什么是依赖注入;如何实现依赖注入;依赖注入有什么好处。之前章节里面的例子也模拟了将 interface 注入到 classes 里面的过程。在我们继续学习之前,有必要深入讲解一下接口,而这正是很多 PHP 开发者所不熟悉的。 6 | 7 | Before I was a PHP programmer, I was a .NET programmer. Do I love pain or what? In .NET, interfaces are everywhere. In fact, many interfaces are defined as part of the core .NET framework itself, and for good reason: many of the .NET languages, such as C# and VB.NET, are strongly typed. In general, you must define the type of object or primitive you will be passing into a method. For example, consider the following C# method: 8 | 9 | 在我成为 PHP 程序员之前,我是写 .NET 的。在 .NET 里可到处都是接口。事实上很多接口是定义在 .NET 框架核心中了,一个好的理由是:很多 .NET 语言比如 C# 和 VB.NET 都是强类型的。 也就是说,你在给一个函数传值,要么传原生类型对象,要么就必须给这个对象一个明确的类型定义。比如考虑以下 C# 方法: 10 | 11 | ``` 12 | public int BillUser(User user) 13 | { 14 | this.biller.bill(user.GetId(), this.amount) 15 | } 16 | ``` 17 | Note that we were forced to define not only what type of arguments we will be passing to the method, but also what the method itself will be returning. C# is encouraging type safety. We will not be allowed to pass anything other than an User object into out BillUser method 18 | 19 | 注意在这里, 我们不仅要定义传进去的参数是什么类型的,还要定义这个方法返回值是什么类型的。 C# 鼓励类型安全。除了指定的 User 对象,它不允许我们传递其他类型的对象到 BillUser 方法中。 20 | 21 | However, PHP is generally a duck typed language. In a duck typed language, an object's avaliable methods determine the way it may be used, rather than its inheritance from a class or implementation of an interface. Let's look at an example: 22 | 23 | 然而 PHP 是一种鸭子类型的语言。所谓鸭子类型的语言,一个对象可用的方法取决于使用方式,而非这个方法从哪儿继承或实现。来看个例子: 24 | 25 | ``` 26 | public function billUser($user) 27 | { 28 | $this->biller->bill($user->getId(), $this->amount); 29 | } 30 | ``` 31 | 32 | In PHP, we did not have to tell the method what type of argument to expect. In fact, we can pass any type, so long as the object responds to the getId method. This is an example of duck typing. If the object looks like a duck, quacks like a duck, it must be a duck. Or, in this case, if the object looks like a user, and acts like a user, it must be one. 33 | 34 | 在 PHP 里面,我们不必告诉一个方法需要什么类型的参数。实际上我们传递任何类型的对象都可以,只要这个对象能响应 getId 的调用。这里有个关于鸭子类型(下文译作:弱类型)的解释:如果一个东西看起来像个鸭子,叫声也像鸭子叫,那他就是个鸭子。 换言之在程序里,一个对象看上去是个 User ,方法响应也像个 User ,那他就是个 User 。 35 | 36 | But, does PHP have any strongly typed style features? Of course! PHP has a mixture of both strong and duck typed constructs. To illustrate this, let's re-write our billUser method: 37 | 38 | 不过 PHP 到底有没有任何强类型功能呢?当然有!PHP 混合了强类型和弱类型的结构。为了说明这点,咱们来重写一下 billUser 方法: 39 | 40 | ``` 41 | public function billUser(User $user) 42 | { 43 | $this->biller->bill($user->getId(), $amount); 44 | } 45 | ``` 46 | 47 | After adding the User type-hint to our method signature, we can now gurantee that all objects passed to billUser either are a User instance, or extend the User class. 48 | 49 | 给方法加上了加上了 User 类型提示后, 我们可以确信的说所有传入 billUser 方法的参数,都是 User 类或是继承自 User 类的一个实例。 50 | 51 | There are advangates and disadvantages to both typing systems. In strongly typed languages, the compiler can often provide you with through compile-time error checking, which is extremely helpful. Method inputs and outputs are also much more explicit in a strongly typed language. 52 | 53 | 强类型和弱类型各有优劣。在强类型语言中,编译器通常能提供编译时错误检查的功能,这功能可是非常有用的。方法的输入和输出也更加明确。 54 | 55 | As the same time, the explicit nature of strong typing can make it rigid. For example, dynamic methods such as whereEmailOrName that the Eloquent ORM offers would be impossible to implement in a strongly typed language like C#. Try to avoid heated discussions on which paradigm is better, and remember the advantages and disadvantages of each. It is not "wrong" to use the strong typing avaliable in PHP, nor is it wrong to use duck typing. What is wrong is exclusively using one or the other without considering the particular problem you are trying to solve. 56 | 57 | 与此同时,强类型的特性也使得程序僵化。比如 Eloquent ORM 中,类似 whereEmailOrName 的动态方法就不可能在 C# 这样的强类型语言里实现。我们不讨论强类型弱类型哪种更好,而是要记住他们分别的优劣之处。在 PHP 里面使用强类型标记不是错误,使用弱类型特性也不是错误。但是不加思索,不管实际情况去使用一种模式,这么固执的使用就是错的。 58 | -------------------------------------------------------------------------------- /content/page/7.5_Authentication.md: -------------------------------------------------------------------------------- 1 | ## Authentication 身份认证 2 | 3 | Authentication may be extended the same way as the cache and session facilities. Again, we will use the extend method we have become familiar with: 4 | 5 | 身份认证模块的扩展方式和缓存与会话的扩展方式一样:使用我们熟悉的 extend 方法就可以进行扩展: 6 | 7 | ``` 8 | Auth::extend('riak', function($app) 9 | { 10 | // Return implementation of Illuminate\Auth\UserProviderInterface 11 | }); 12 | ``` 13 | 14 | The UserProviderInterface implementations are only responsible for fetching a UserInterface implementation out of persistent storage system, such as MySQL, Riak, etc. These two interfaces allow the Laravel authentication mechanisms to continue functioning regardless of how the user data is stored or what type of class is used to represent it. 15 | 16 | 接口 UserProviderInterface 负责从各种持久化存储系统——如 MySQL,Riak 等——中获取数据,然后得到接口 UserInterface 的实现对象。有了这两个接口,Laravel 的身份认证机制就可以不用管用户数据是如何储存的、究竟哪个类来代表用户对象这种事儿,从而继续专注于身份认证本身的实现。 17 | 18 | Let's take a look at the UserProviderInterface: 19 | 20 | 咱们来看一看 UserProviderInterface 接口的代码: 21 | 22 | ``` 23 | interface UserProviderInterface { 24 | public function retrieveById($identifier); 25 | public function retrieveByCredentials(array $credentials); 26 | public function validateCredentials(UserInterface $user, array $credentials); 27 | } 28 | ``` 29 | 30 | The retrieveById function typically receives a numeric key representing the user, such as an auto-incrementing ID from a MySQL database. The UserInterface implementation matching the ID should be retrieved and returned by the method. 31 | 32 | 方法 retrieveById 通常接受一个数字参数用来表示一个用户,比如 MySQL 数据库的自增 ID。该方法要找到匹配该 ID 的 UserInterface 的实现对象,并且将该对象返回。 33 | 34 | The retrieveByCredentials method receives the array of credentials passed to the Auth::attempt method when attempting to sign into an application. The method should then "query" the underlying persistent storage for the user matching those credentials. Typically, this method will run a query with a "where" condition on $credentials['username']. This method should not attempt to do any password validation or authentication. 35 | 36 | retrieveByCredentials 方法接受一个参数作为登录帐号。该参数是在尝试登录系统时从 Auth::attempt 方法传来的。那么该方法应该“查询”底层的持久化存储系统,来找到那些匹配到该帐号的用户。通常该方法会执行一个带有 “where” 条件的查询来匹配参数里的 $credentials['username']。该方法不应该做任何密码验证。 37 | 38 | The validateCredentials method should compare the given $user with the $credentials to authenticate the user. For example, this method might compare the $user->getAuthPassword(); string to a Hash::make of $credentials['password']. 39 | 40 | validateCredentials 方法会通过比较 $user 参数和 $credentials 参数来检测用户是否通过认证。比如,该方法会调用 $user->getAuthPassword(); 方法,将得到的字符串与 $credentials['password'] 经过 Hash::make 处理后的结果进行比对。 41 | 42 | Now that we have explored each of the methods on the UserProviderInterface, let's take a look at the UserInterface. Remember, the provider should return implementations of this interface from the retrieveById and retrieveByCredentials methods: 43 | 44 | 现在我们探索了 UserProviderInterface 接口的每一个方法,接下来咱们看一看 UserInterface 接口。别忘了 UserInterface 的实例应当是 retrieveById 和 retrieveByCredentials 方法的返回值: 45 | 46 | ``` 47 | interface UserInterface { 48 | public function getAuthIdentifier(); 49 | public function getAuthPassword(); 50 | } 51 | ``` 52 | 53 | This interface is simple. The getAuthIdentifier method should return the "primary key" of the user. In a MySQL back-end, again, this would be the auto-incrementing primary key. The getAuthPassword should return the user's hashed password. This interface allows the authentication system to work with any User class, regardless of what ORM or storage abstraction layer you are using. By default, Laravel includes a User class in the app/models directory which implements this interface, so you may consult this class for an implementation example. 54 | 55 | 这个接口很简单。 getAuthIdentifier 方法应当返回用户的“主键”。就像刚才提到的,在 MySQL 中可能就是自增主键了。getAuthPassword 方法应当返回经过散列处理的用户密码。有了这个接口,身份认证系统就可以不用关心用户类到底使用了什么ORM或者什么存储方式。Laravel 已经在 app/models 目录下,包含了一个默认的 User 类且实现了该接口。所以你可以参考这个类当例子。 56 | 57 | Finally, once we have implemented the UserProviderInterface, we can ready to register our extension with the Auth facade: 58 | 59 | 当我们最后实现了 UserProviderInterface 接口后,我们可以将该扩展注册进Auth里面: 60 | 61 | ``` 62 | Auth::extend('riak', function($app) 63 | { 64 | return new RiakUserProvider($app['riak.connection']); 65 | }); 66 | ``` 67 | 68 | After you have registered the driver with the extend method, you switch to the new driver in your app/config/auth.php configuration file. 69 | 70 | 使用 extend 方法注册好驱动以后,你就可以在 app/config/auth.php 配置文件里面切换到新的驱动了。 71 | -------------------------------------------------------------------------------- /content/page/5.3_Bye_Models.md: -------------------------------------------------------------------------------- 1 | ## Bye, Bye Models 再见,模型 2 | 3 | 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. 4 | 5 | 删掉你的 models 目录了么?还没删就赶紧删了!我们将要在 app 目录下创建个新的目录,目录名就以我们这个应用的名字来命名,这次我们就叫 QuickBill 吧。在后续的讨论中,我们在前面写的那些接口和类都会出现。 6 | 7 | > ### Remember The Context 注意使用场景 8 | 9 | > 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. 10 | 11 | > 记住,如果你在写一个很小的 Laravel 应用,那在 models 目录下写几个 Eloquent 模型其实挺合适的。但在本章节,我们主要关注如何开发更有合适“层次”架构的大型复杂项目。 12 | 13 | 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: 14 | 15 | 这样我们现在有了个 app/QuickBill 目录,它和应用目录下的其他目录如 controllers 还有 views 都是平级的。在 QuickBill 目录下我们还可以创建几个其他的目录。我们来在里面创建个 Repositories 和 Billing 目录。目录都创建好以后,别忘了在 composer.json 文件里加入PSR-0的自动载入机制: 16 | 17 | ``` 18 | "autoload": { 19 | "psr-0": { 20 | "QuickBill": "app/" 21 | } 22 | } 23 | ``` 24 | 25 | > 译者注:psr-0 也可以改成 psr-4, "psr-4": { "QuickBill\": "app/QuickBill" } psr-4 是比较新的建议标准,和 psr-0 具体有什么区别请自行检索。 26 | 27 | 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: 28 | 29 | 现在我们把继承自 Eloquent 的模型类都放到 QuickBill 目录下面。这样我们就能很方便的以 QuickBill\User, QuickBill\Payment 的方式来使用它们。Repositories 目录属于 PaymentRepository 和 UserRepository 这种类,里面包含了所有对数据的访问功能比如 getRecentPayments 和 getRichestUser 。Billing 目录应当包含调用第三方支付服务(如 Stripe 和 Balanced )的类。整个目录结构应该类似这样: 30 | 31 | ``` 32 | // app 33 | // QuickBill 34 | // Repositories 35 | -> UserRepository.php 36 | -> PaymentRepository.php 37 | // Billing 38 | -> BillerInterface.php 39 | -> StripeBiller.php 40 | // Notifications 41 | -> BillingNotifierInterface.php 42 | -> SmsBillingNotifier.php 43 | User.php 44 | Payment.php 45 | ``` 46 | 47 | > ### What About Validation 数据验证怎么办? 48 | 49 | > 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: validForCreation or hasValidDomain. Alternatively, you could create a UserValidator class within a Validation namespace and inject that validator into your repository. Experiment with both approaches and see what you like best! 50 | 51 | > 在哪儿进行数据验证常常困扰着开发人员。可以考虑将数据验证方法写进你的“实体”类里面(好比 User.php 和 Payment.php )。方法名可以设为 validForCreation 或 hasValidDomain 。或者你也可以专门创建个验证器类 UserValidator,放到 Validation 命名空间下,然后将这个验证器类注入到你的 repository 类里面。两种方式你都可以试试,看哪个你更喜欢! 52 | 53 | 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. 54 | 55 | 摆脱了 models 目录后,你通常就能克服心理障碍,实现好的设计。使得你能创建一个更合适的目录结构来为你的应用服务。当然,你建立的每一个应用程序都会有一定的相似之处,因为每个复杂的应用程序都需要一个数据访问(repository)层,一些外部服务层等等。 56 | 57 | > ### Don't Fear Directories 别害怕目录 58 | 59 | > 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. 60 | 61 | > 不要惧怕建立目录来管理应用。要常常将你的应用切割成小组件,每一个组件都要有十分专注的职责。跳出“模型”的框框来思考。比如我们之前就说过,你可以创建个 Repositories 目录来存放你所有的数据访问类。 62 | -------------------------------------------------------------------------------- /content/page/4.2_As_Organizer.md: -------------------------------------------------------------------------------- 1 | ## As Organizer 作为管理工具 2 | 3 | One of the keys to building a well architected Laravel application is learning to use service providers as an organizational tool. When you are registering many classes with the IoC container, all of those bindings can start to clutter your app/start files. Instead of doing container registerations in those files, create service providers that register related services. 4 | 5 | 想制作一个结构优美的 Laravel 应用的话,就要去学习如何用服务提供者来管理代码。当你在注册 IoC 绑定的时候,所有代码都杂乱的塞进了app/start 路径下的文件里。 别再这样做了,使用服务提供者来注册这些吧。 6 | 7 | > ### Get It Started 万物之初 8 | 9 | > Your application's "start" files are all stored in the app/start directory. These are "bootstrap" files that are loaded based on the type of the request entering your application. Start files named after an environment are loaded after the global start.php file. Finally, the artisan.php start file is loaded when any console command is executed. 10 | 11 | > 你应用的“启动”文件都储存在 app/start 目录下。根据不同的请求入口,系统会载入不同的启动文件。在全局的 start.php 文件加载后,系统会根据执行环境的不同来加载不同的启动文件。 此外,在执行命令行程序时,artisan.php 文件会被载入。 12 | 13 | Let's explore an example. Perhaps our application is using Pusher to push messages to clients via WebSockets. In order to decouple ourselves from Pusher, it will be benificial to create an EventPusherInterface, and a PusherEventPusher implementation. This will allow us to easily change WebSocket providers down the road as requirements change or our application grows. 14 | 15 | 咱们来考虑这个例子。也许我们的应用正在使用 Pusher 来为客户推送消息。为了将我们的应用和 Pusher 解耦,我们要定义 EventPusherInterface 接口和对应的实现类 PusherEventPusher 。这样在需求变化或应用改进时,我们就可以随时轻松的改变推送服务提供商。 16 | 17 | ``` 18 | interface EventPusherInterface{ 19 | public function push($message, array $data = array()); 20 | } 21 | 22 | class PusherEventPusher implements EventPusherInterface{ 23 | public function __construct(PusherSdk $pusher) 24 | { 25 | $this->pusher = $pusher; 26 | } 27 | public function push($message, array $data = array()) 28 | { 29 | // Push message via the Pusher SDK... 30 | } 31 | } 32 | ``` 33 | 34 | Next, let's create an EventPusherServiceProvider: 35 | 36 | 接下来我们创建一个 EventPusherServiceProvider: 37 | 38 | ``` 39 | use Illuminate\Support\ServiceProvider; 40 | 41 | class EventPusherServiceProvider extends ServiceProvider { 42 | public function register() 43 | { 44 | $this->app->singleton('PusherSdk', function() 45 | { 46 | return new PusherSdk('app-key', 'secret-key'); 47 | } 48 | 49 | $this->app->singleton('EventPusherInterface', 'PusherEventPusher'); 50 | } 51 | } 52 | ``` 53 | 54 | Great! Now we have a clean abstraction over event pushing, as well as a convenient place to register this, and other related bindings, in the container. Finally, we just need to add the EventPusherServiceProvider to our providers array in the app/config/app.php configuration file. Now we are ready to inject the EventPusherInterface into any controller or class within our application. 55 | 56 | 很好! 我们对事件推送进行了清晰的抽象,同时我们也有了一个很不错的地方进行注册、绑定其他相关的东西到容器里。最后一步只需要将 EventPusherServiceProvider 写入 app/config/app.php 文件内的 providers 数组里就可以了。现在这个应用里的 EventPusherInterface 已经被绑定到了正确的实现类上。 57 | 58 | > ### Should You Singleton? 要使用单例么? 59 | 60 | > You will need to evaluate whether to bind classes with bind or singleton. If you only want one instance of the class to be created per request cycle, use singleton. Otherwise, use bind. 61 | 62 | > 用不用单例可以这样来考虑:如果在一次请求周期中该类只需要有一个实例,就使用singleton;否则就使用bind。 63 | 64 | Note that a service provider has an $app instance available via the base ServiceProvider class. This is a full Illuminate\Foundation\Application instance, which inherits from the Container class, so we can call all of the IoC container methods we are used to. If you preffer to use the App facade inside the service provider, you may do that as well: 65 | ``` 66 | App::singleton('EventPusherInterface', 'PusherEventPusher'); 67 | ``` 68 | Of course, service providers are not limited to registering certain kinds of services. We could use them to register our cloud file storage services, database access services, a custom view engine such as Twig, etc. They are simply bootstrapping and organizational tools for your application. Nothing more. 69 | 70 | 当然服务提供者的功能不仅仅局限于消息推送。像是云存储、数据库访问、自定义的视图引擎比如 Twig 等等都可以用这种模式来设置。服务提供者就是你的应用里的启动代码和管理工具,没什么神奇的。 71 | 72 | So, don't be scared to create your own service providers. They are not something that should be strictly limited to distributed packages, but rather are great organizational tools for your own applications. Be creative and use them to bootstrap your various application components. 73 | 74 | 所以大胆的去创建你自己的服务提供者。并不是你非要发布个什么软件包才需要服务提供者,他们只是非常好的管理代码的工具。使用它们的力量去管理好应用中的各个组件吧。 75 | -------------------------------------------------------------------------------- /content/page/6.2_Decoupling_Handlers.md: -------------------------------------------------------------------------------- 1 | ## Decoupling Handlers 解耦处理函数 2 | 3 | To get started, let's jump right into an example. Consider a queue handler that sends an SMS message to a user. After sending the message, the handler logs that message so we can keep a history of all SMS messages we have sent to that user. The code might look something like this: 4 | 5 | 接下来我们看一个例子。考虑有一个队列处理函数用来给用户发送手机短信。信息发送后,处理函数还要记录消息日志来保存给用户发送的消息历史。代码应该看起来是这样: 6 | 7 | ``` 8 | class SendSMS{ 9 | public function fire($job, $data) 10 | { 11 | $twilio = new Twilio_SMS($apiKey); 12 | $twilio->sendTextMessage(array( 13 | 'to'=> $data['user']['phone_number'], 14 | 'message'=> $data['message'], 15 | )); 16 | $user = User::find($data['user']['id']); 17 | $user->messages()->create(array( 18 | 'to'=> $data['user']['phone_number'], 19 | 'message'=> $data['message'], 20 | )); 21 | $job->delete(); 22 | } 23 | } 24 | ``` 25 | 26 | Just by examining this class, you can probably spot several problems. First, it is hard to test. The Twilio_SMS class is instantiated inside of the fire method, meaning we will not be able to inject a mock service. Secondly, we are using Eloquent directly in the handler, thus creating a second testing problem as we will have to hit a real database to test this code. Finally, we are unable to send SMS messages outside of the queue. All of our SMS sending logic is tightly coupled to the Laravel queue. 27 | 28 | 简单审查下这个类,你可能会发现一些问题。首先,它难以测试。在 fire 方法里直接使用了 Twilio_SMS 类,意味着我们没法注入一个模拟的服务(译者注:即一旦测试则必须发送一条真实的短信)。第二,我们直接使用了 Eloquent,导致在测试时肯定会对数据库造成影响。第三,我们没法在队列外面发送短信,想在队列外面发还要重写一遍代码。也就是说我们的短信发送逻辑和 Laravel 的队列耦合太多了。 29 | 30 | By extracting this logic into a separate "service" class, we can decouple our application's SMS sending logic from Laravel's queue. This will allow us to send SMS messages from anywhere in our application. While we are decoupling this process from the queue, we will also refactor it to be more testable. 31 | 32 | 将里面的逻辑抽出成为一个单独的“服务”类,我们即可将短信发送逻辑和 Laravel 的队列解耦。这样我们就可以在应用的任何位置发送短信了。我们将其解耦的过程,也令其变得更易于测试。 33 | 34 | So, let's examine an alternative: 35 | 36 | 那么我们来稍微改一改: 37 | 38 | ``` 39 | class User extends Eloquent { 40 | /** 41 | * Send the User an SMS message 42 | * 43 | * @param SmsCourierInterface $courier 44 | * @param string $message 45 | * @return SmsMessage 46 | */ 47 | public function sendSmsMessage(SmsCourierInterface $courier, $message) 48 | { 49 | $courier->sendMessage($this->phone_number, $message); 50 | return $this->sms()->create(array( 51 | 'to'=> $this->phone_number, 52 | 'message'=> $message, 53 | )); 54 | } 55 | } 56 | ``` 57 | 58 | In this refactored example, we have extracted the SMS sending logic into the User model. We are also injecting a SmsCourierInterface implementation into the method, allowing us to better test that aspect of the process. Now that we have refactored this logic, let's re-write our queue handler: 59 | 60 | 在本重构的例子中,我们将短信发送逻辑抽出到 User 模型里。同时我们将 SmsCourierInterface 的实现注入到该方法里,这样我们可以更容易对该方法进行测试。现在我们已经重构了短信发送逻辑,让我们再重写队列处理函数: 61 | 62 | ``` 63 | class SendSMS { 64 | public function __construct(UserRepository $users, SmsCourierInterface $courier) 65 | { 66 | $this->users = $users; 67 | $this->courier = $courier; 68 | } 69 | public function fire($job, $data) 70 | { 71 | $user = $this->users->find($data['user']['id']); 72 | $user->sendSmsMessage($this->courier, $data['message']); 73 | $job->delete(); 74 | } 75 | } 76 | ``` 77 | 78 | As you can see in this refactored example, our queue handler is now much lighter. It essentially serves as a translation layer between the queue and your real application logic. That is great! It means that we can easily send SMS message s outside of the queue context. Finally, let's write some tests for our SMS sending logic: 79 | 80 | 你可以看到我们重构了代码,使得队列处理函数更轻量化了。它本质上变成了队列系统和你真正的业务逻辑之间的转换层。这可是很了不起!这意味着我们可以很轻松的脱离队列系统来发送短信息。最后,让我们为短信发送逻辑写一些测试代码: 81 | 82 | ``` 83 | class SmsTest extends PHPUnit_Framework_TestCase { 84 | public function testUserCanBeSentSmsMessages() 85 | { 86 | /** 87 | * Arrage ... 88 | */ 89 | $user = Mockery::mock('User[sms]'); 90 | $relation = Mockery::mock('StdClass'); 91 | $courier = Mockery::mock('SmsCourierInterface'); 92 | 93 | $user->shouldReceive('sms')->once()->andReturn($relation); 94 | 95 | $relation->shouldReceive('create')->once()->with(array( 96 | 'to' => '555-555-5555', 97 | 'message' => 'Test', 98 | )); 99 | 100 | $courier->shouldReceive('sendMessage')->once()->with( 101 | '555-555-5555', 'Test' 102 | ); 103 | 104 | /** 105 | * Act ... 106 | */ 107 | $user->sms_number = '555-555-5555'; //译者注: 应当为 phone_number 108 | $user->sendMessage($courier, 'Test'); 109 | } 110 | } 111 | ``` 112 | -------------------------------------------------------------------------------- /content/page/2.2_Reflective_Resolution.md: -------------------------------------------------------------------------------- 1 | ## Reflect Resolution 反射解决方案 2 | 3 | One of the most powerful features of the Laravel container is its ability to automatically resolve dependencies via reflection. Reflection is the ability to inspect a classes and methods. For example, the PHP ReflectionClass class allows you to inspect the method avaliable on a given class. The PHP method method_exists is also a form of reflection. To play with PHP's reflection class, try the followring code on one of your classes: 4 | 5 | 用反射来自动处理依赖是 Laravel 容器的一个最强大的特性。反射是一种运行时探测类和方法的能力。比如,PHP 的 ReflectionClass 可以探测一个类的方法。method_exists 某种意义上说也是一种反射。我们来把玩一下 PHP 的反射类,试试下面的代码吧( StripeBiller 换成你自己定义好的类): 6 | 7 | ``` 8 | $reflection = new ReflectionClass('StripeBiller'); 9 | var_dump($reflection->getMethods()); 10 | var_dump($reflection->getConstants()); 11 | ``` 12 | 13 | By leveraging this powerful feature of PHP, the Laravel IoC container can do some interesting things! For instance, consider the following class: 14 | 15 | 依靠这个强大的 PHP 特性, Laravel 的 IoC 容器可以实现很有趣的功能!考虑接下来这个类: 16 | 17 | ``` 18 | class UserController extends BaseController 19 | { 20 | public function __construct(StripBiller $biller) 21 | { 22 | $this->biller = $biller; 23 | } 24 | } 25 | ``` 26 | 27 | Note that the controller is type-hinting the StripBiller class. We are able to retrieve this type-hint using reflection. When the Laravel container does not have a resolver for a class explictity bound, it will try to resolve the class via reflection. The flow looks like this: 28 | 29 | 注意这个控制器的构造函数暗示着有一个 StripBiller 类型的参数。使用反射就可以检测到这种类型暗示。当 Laravel 的容器无法解决一个类型的明显绑定时,容器会试着使用反射来解决。程序流程类似于这样的: 30 | 31 | 1. Do I have a resolver for StripBiller? 32 | 33 | 已经有一个 StripBiller 的绑定了么? 34 | 35 | 2. No resolver? Reflect into StripBiller to determin if it has dependencies. 36 | 37 | 没绑定?那用反射来探测一下 StripBiller 吧。看看他都需要什么依赖。 38 | 39 | 3. Resolve any dependencies needed by StripBiller (recursive). 40 | 41 | 解决 StripBiller 需要的所有依赖(递归处理) 42 | 43 | 4. Instantiate new StripBiller instance via ReflectionClass->newInstanceArgs(). 44 | 45 | 使用 ReflectionClass->newInstanceArgs() 来实例化 StripBiller 46 | 47 | As you can see, the container is doing a lot of heavy lifting for you, which saves you from having to write resolves for every single one of your classes. This is one of the most powerful and unique features of the Laravel container, and having a strong grasp of this capability is very beneficial when building large Laravel applications. 48 | 49 | 如你所见, 容器替我们做了好多重活,这能帮你省去写大量绑定的麻烦。这就是Laravel容器最强大也是最独特的特性。熟练掌握这种能力对构建大型Laravel应用是十分有益的。 50 | 51 | Now, let's modify our controller a bit. What if it looks like this? 52 | 53 | 下面我们修改一下控制器, 改成这样会发生什么事儿呢? 54 | 55 | ``` 56 | class UserController extends BaseController 57 | { 58 | public function __construct(BillerInterface $biller) 59 | { 60 | $this->biller = $biller; 61 | } 62 | } 63 | ``` 64 | 65 | Assuming we have not explicitly bound a resolver for BillerInterface, how will the container know what class to inject? Remember, interface can't be instantiated since they are just contracts. Without us giving the container any more information, it will be unable to instantiate this dependency. We need to specify a class that should be used as the default implementation of this interface, and we may do so via the bind method: 66 | 67 | 假设我们没有为 BillerInterface 做任何绑定, 容器该怎么知道要注入什么类呢?要知道,interface 不能被实例化,因为它只是个约定。如果我们不提供更多信息的话,容器是无法实例化这个依赖的。我们需要明确指出哪个类要实现这个接口,这就需要用到 bind 方法: 68 | 69 | ``` 70 | App::bind('BillerInterface','StripBiller'); 71 | ``` 72 | 73 | Here, we are passing a string instead of a Closure, and this string tells the container to always use the StripBiller class anytime it needs an implementation of the BillerInterface interface. Again, we're gaining the ability to switch implementations of services with a simple one-line change to our container binding. For example, if we need to switch to Balanced Payments as our billing provider, we simply write a new BalancedBiller implementation of BillerInterface, and change our container binding: 74 | 75 | 这里我们只传了一个字符串进去,而不是一个匿名函数。 这个字符串告诉容器总是使用 StripBiller 来作为 BillerInterface 的实现类。 此外我们也获得了只改一行代码即可轻松改变实现的能力。比如,假设我们需要切换到 Balanced Payments 作为我们的支付提供商,我们只需要新写一个 BalancedBiller 来实现 BillerInterface 接口,然后这样修改容器代码: 76 | 77 | ``` 78 | App::bind('BillerInterface', 'BalancedBiller'); 79 | ``` 80 | 81 | Automatically, our new implementation will be used throughout out application! 82 | 83 | 我们的应用程序就装载上了的新支付实现代码了! 84 | 85 | When binding implementations to interfaces, you may also use the singleton method so the container only instantiates one instance of the class per request cycle: 86 | 87 | 你也可以使用 singleton 方法来实现单例模式。 88 | 89 | ``` 90 | App::singleton('BillerInterface', 'StripBiller'); 91 | ``` 92 | 93 | > ### Master The Container 掌握容器 94 | 95 | > Want to learn even more about the container? Read through its source! The container is only one class: Illuminate\Container\Container. Read over the source code to gain a deeper understanding of how the container works under the hood. 96 | 97 | > 想了解更多关于容器的知识?去读源码!容器只有一个类 Illuminate\Container\Container , 读完了你就对容器有更深的认识了。 98 | -------------------------------------------------------------------------------- /content/page/8.2_In_Action.md: -------------------------------------------------------------------------------- 1 | ## In Action 实践 2 | 3 | The Single Responsibility Principle states that a class should have one, and only one, reason to change. In other words, a class' scope and responsibility should be narrowly focused. As we have said before, ignorance is bliss when it comes to class responsibilities. A class should do its job, and should not be affected by changes to any of its dependencies. 4 | 5 | 单一职责原则规定一个类有且仅有一个理由使其改变。换句话说,一个类的功能边界和职责应当是十分狭窄且集中的。我们之前就提到过,在类的职责问题上,无知是福。一个类应当做它该做的事儿,并且不应当被它的依赖的任何变化所影响到。 6 | 7 | Consider the following class: 8 | 9 | 考虑下列类: 10 | 11 | ``` 12 | class OrderProcessor { 13 | public function __construct(BillerInterface $biller) 14 | { 15 | $this->biller = $biller; 16 | } 17 | public function process(Order $order) 18 | { 19 | $recent = $this->getRecentOrderCount($order); 20 | if($recent > 0) 21 | { 22 | throw new Exception('Duplicate order likely.'); 23 | } 24 | 25 | $this->biller->bill($order->account->id, $order->amount); 26 | 27 | DB::table('orders')->insert(array( 28 | 'account' => $order->account->id, 29 | 'amount' => $order->amount, 30 | 'created_at'=> Carbon::now() 31 | )); 32 | } 33 | protected function getRecentOrderCount(Order $order) 34 | { 35 | $timestamp = Carbon::now()->subMinutes(5); 36 | return DB::table('orders')->where('account', $order->account->id) 37 | ->where('created_at', '>=', $timestamps) 38 | ->count(); 39 | } 40 | } 41 | ``` 42 | 43 | What are the responsibilities of the class above? Obviously, its name implies that it is responsible for processing orders. But, based on the getRecentOrderCount method, we can also see that it is responsible for examining an account's order history in the database in order to detect duplicated orders. This extra validation responsibility means that we must change our order processor when our data store changes, as well as when our order validation rules change. 44 | 45 | 上面这个类的职责是什么?很显然顾名思义,它是用来处理订单的。不过由于 getRecentOrderCount 这个方法的存在,这个类就有了在数据库中审查某帐号订单历史来看有没有重复订单的职责。这个额外的验证职责意味着当我们的存储方式改变或当订单验证规则改变时,我们的这个订单处理器也要跟着改变。 46 | 47 | We should extract this responsibility into another class, such as an OrderRepository: 48 | 49 | 我们必须将这个职责抽离出来放到另外的类里面,比如放到 OrderRepository: 50 | 51 | ``` 52 | class OrderRepository { 53 | public function getRecentOrderCount(Account $account) 54 | { 55 | $timestamp = Carbon::now()->subMinutes(5); 56 | return DB::table('orders')->where('account', $account->id) 57 | ->where('created_at', '>=', $timestamp) 58 | ->count(); 59 | } 60 | 61 | public function logOrder(Order $order) 62 | { 63 | DB::table('orders')->insert(array( 64 | 'account' => $order->account->id, 65 | 'amount' => $order->amount, 66 | 'created_at'=> Carbon::now() 67 | )); 68 | } 69 | } 70 | ``` 71 | 72 | Then, we can inject our repository into the OrderProcessor, alleviating it of the responsibility of researching an account's order history: 73 | 74 | 然后我们可以将我们的资料库(译者注:OrderRepository )注入到 OrderProcessor 里,帮后者承担起对账户订单历史的处理责任: 75 | 76 | ``` 77 | class OrderProcessor { 78 | public function __construct(BillerInterface $biller, OrderRepository $orders) 79 | { 80 | $this->biller = $biller; 81 | $this->orders = $orders; 82 | } 83 | 84 | public function process(Order $order) 85 | { 86 | $recent = $this->orders->getRecentOrderCount($order->account); 87 | 88 | if($recent > 0) 89 | { 90 | throw new Exception('Duplicate order likely.'); 91 | } 92 | 93 | $this->biller->bill($order->account->id, $order->amount); 94 | 95 | $this->orders->logOrder($order); 96 | } 97 | } 98 | ``` 99 | 100 | Now that we have abstracted our order data gathering responsibilities, we no longer have to change our OrderProcessor when the method of retrieving and logging orders changes. Our class responsibilities are more focused and narrow, providing for cleaner, more expressive code, and a more maintainable application. 101 | 102 | 现在我们提取出了收集订单数据的责任,当读取和写入订单的方式改变时,我们不再需要修改 OrderProcessor 这个类了。我们的类的职责更加的专注和精确,这提供了一个更干净、更有表现力的代码,同时也是更容易维护的代码。 103 | 104 | Keep in mind, the Single Responsibility Principle isn't just about less line of code, it's about writing classes that have a narrow responsibility, and a cohesive set of available methods, so make sure that all of the methods in a class are aligned with the overall responsibility of that class. After building a library of small, clear classes with well defined responsibilities, our code will be more decoupled, easier to test, and friendlier to change. 105 | 106 | 请记住,单一职责原则的关键不仅仅是让函数变短,而是写出职责更精确更高内聚的类,所以要确保类里面所有的方法都属于该类的职责之下的。在建立一个小巧、清晰且职责明确的类库以后,我们的代码会更加解耦,更容易测试,并且更易于更改。 107 | -------------------------------------------------------------------------------- /content/page/11.2_In_Action.md: -------------------------------------------------------------------------------- 1 | ## In Action 实践 2 | 3 | To illustrate this principle, let's consider an example session handing library. In fact, we will consider PHP's own SessionHandlerInterface. Here are the methods defined by this interface, which is included with PHP beginning in version 5.4: 4 | 5 | 为了说明该原则,我们来思考一个关于会话处理的类库。实际上我们将要考察PHP自己的SessionHandlerInterface。下面是该接口定义的方法,他们是从PHP 5.4版才开始有的: 6 | 7 | ``` 8 | interface SessionHandlerInterface { 9 | public function close(); 10 | public function destroy($sessionId); 11 | public function gc($maxLifetime); 12 | public function open($savePath, $name); 13 | public function read($sesssionId); 14 | public function write($sessionId, $sessionData); 15 | } 16 | ``` 17 | 18 | Now that you are familiar with the methods defined by this interface, consider an implementation using Memcached. Would a Memcached implementation of this interface define functionality for each of these methods? Not only do we not need to implement all of these methods, we don't need half of them! 19 | 20 | 现在我们知道接口里面都是什么方法了,我们打算用 Memcached 来实现它。Memcached 需要实现这个接口里的所有方法么?不,里面一半的方法对于 Memcached 来说都是不需要实现的! 21 | 22 | Since Memcached will automatically expire the values it contains, we do not need to implement the gc method of the interface, nor do we need to implement the open or close methods of the interface. So, we are forced to define "stubs" for these methods in our implementation that are just empty methods. To correct this problem, let's start by defining a smaller, more focused interface for session garbage collection: 23 | 24 | 因为 Memcached 会自动清除存储的过期数据,我们不需要实现 gc 方法。我们也不需要实现 open 和 close 方法。所以我们被迫去写空方法来站着位子。为了解决在这个问题,我们来定义一个小巧的专门用来垃圾回收的接口: 25 | 26 | ``` 27 | interface GarbageCollectorInterface { 28 | public function gc($maxLifetime); 29 | } 30 | ``` 31 | 32 | Now that we have a smaller interface, any consuming code can depend on this focused contract, which defines a very narrow set of functionality and does not create a dependency on the entire session handler. 33 | 34 | 现在我们有了一个小巧的接口,功能单一而专注。需要垃圾清理的只用依赖这个接口即可,而不必去依赖整个会话处理。 35 | 36 | To further understand the principle, let's reinforce our knowledge with another example. Imagine we have a Contact Eloquent class that is defined like so: 37 | 38 | 为了更深入理解该原则,我们用另一个例子来强化理解。想象我们有一个名为Contact的Eloquent类,定义成这样: 39 | 40 | ``` 41 | class Contact extends Eloquent { 42 | public function getNameAttribute() 43 | { 44 | return $this->attributes['name']; 45 | } 46 | public function getEmailAttribute() 47 | { 48 | return $this->attributes['email']; 49 | } 50 | } 51 | ``` 52 | 53 | Now, let's assume that our application also employs a PasswordReminder class that is responsible for sending password reminder e-mails to users of the application. Below is a possible definition of the PasswordReminder class: 54 | 55 | 现在我们再假设我们应用里还有一个叫 PasswordReminder 的类来负责给用户发送密码找回邮件。下面是 PasswordReminder 的定义方式的一种: 56 | 57 | ``` 58 | class PasswordReminder { 59 | public function remind(Contact $contact, $view) 60 | { 61 | // Send password reminder e-mail... 62 | } 63 | } 64 | ``` 65 | 66 | As you probably noticed, our PassswordReminder is dependent upon the Contact class, which in turns means it is dependent on the Eloquent ORM. It is neither desirable or necessary to couple the password reminder system to a specific ORM implementation. By breaking the dependency, we can freely change our back-end storage mechanism or ORM without affecting the password reminder component of the application. Again, by breaking one of the SOLID principles, we have given a consuming class too much knowledge about the rest of the application. 67 | 68 | 你可能注意到了,PasswordReminder 依赖着 Contact 类,也就是依赖着 Eloquent ORM。 对于一个密码找回系统来说,依赖着一个特定的 ORM 实在是没必要,也是不可取的。切断对该 ORM 的依赖,我们就可以自由的改变我们后台存储机制或者说 ORM,同时不会影响到我们的密码找回组件。重申一遍,违背了“坚实”原则的任何一条,就意味着有个类它知道的太多了。 69 | 70 | To break the dependency, let's create a RemindableInterface. In fact, such an interface is included with Laravel, and is implemented by the User model by default: 71 | 72 | 要切断这种依赖,我们来创建一个 RemindableInterface 接口。事实上 Laravel 已经有了这个接口,并且默认由 User 模型实现了该接口: 73 | 74 | ``` 75 | interface RemindableInterface { 76 | public function getReminderEmail(); 77 | } 78 | ``` 79 | 80 | Once the interface has been created, we can implement it on our model: 81 | 82 | 一旦接口定义好了,我们就可以在模型上实现它: 83 | 84 | ``` 85 | class Contact extends Eloquent implements RemindableInterface { 86 | public function getReminderEmail() 87 | { 88 | return $this->email; 89 | } 90 | } 91 | ``` 92 | 93 | Finally, we can depend on this smaller, more focused interface in the PasswordReminder: 94 | 95 | 最终我们可以在 PasswordReminder 里面依赖这样一个小巧且专注的接口了: 96 | 97 | ``` 98 | class PasswordReminder { 99 | public function remind(RemindableInterface $remindable, $view) 100 | { 101 | // Send password reminder e-mail... 102 | } 103 | } 104 | ``` 105 | 106 | By making this simple change, we have removed unnecessary dependencies from the password reminder component and made it flexible enough to use any class from any ORM, so long as that class implements the new RemindableInterface. This is exactly how the Laravel password reminder component remains database and ORM agnostic! 107 | 108 | 通过这小小的改动,我们已经移除了密码找回组件里不必要的依赖,并且使它足够灵活能使用任何实现了 RemindableInterface 的类或 ORM。这其实正是 Laravel 的密码找回组件如何保持与数据库 ORM 无关的秘诀! 109 | 110 | > ### Knowledge Is Power 知识就是力量 111 | 112 | > Again we have discovered the pitfalls of giving a class too much knowledge about application implementation details. By paying careful attention to how much knowledge we are giving a class, we can abide by all of the SOLID principles. 113 | 114 | > 我们再次发现了一个使类知道太多东西的陷阱。通过小心留意是否让一个类知道了太多,我们就可以遵守所有的“坚实”原则。 115 | -------------------------------------------------------------------------------- /content/page/10.2_In_Action.md: -------------------------------------------------------------------------------- 1 | ## In Action 实践 2 | 3 | To illustrate this principle, let's continue to use our OrderProcessor example from the previous chapter. Take a look at this method: 4 | 5 | 为了说明该原则,我们继续编写上一章节的OrderProcessor。看下面的方法: 6 | 7 | ``` 8 | public function process(Order $order) 9 | { 10 | // Validate order... 11 | $this->orders->logOrder($order); 12 | } 13 | ``` 14 | 15 | Note that after the Order is validated, we log the order using the OrderReporsitoryInterface implementation. Let's assume that when our order processing business was young, we stored all of our orders in CSV format on the file system. Our only OrderRepositoryInterface implementation was a CsvOrderRepository. Now, as our order rate grows, we want to use a relational database to store them. So, let's look at a possible implementation of our new repository: 16 | 17 | 注意当我们的 Order 通过了验证,就被 OrderRepositoryInterface 的实现对象存储起来了。假设当我们的业务刚起步时,我们将订单存储在 CSV 格式的文件系统中。我们的 OrderRepositoryInterface 的实现类是 CsvOrderRepository。现在,随着我们订单增多,我们想用一个关系数据库来存储订单。那么我们来看看新的订单资料库类该怎么编写吧: 18 | 19 | ``` 20 | class DatabaseOrderRepository implements OrderRepositoryInterface { 21 | protected $connection; 22 | public function connect($username, $password) 23 | { 24 | $this->connection = new DatabaseConnection($username, $password); 25 | } 26 | 27 | public function logOrder(Order $order) 28 | { 29 | $this->connection->run('insert into orders values (?, ?)', array( 30 | $order->id, $order->amount 31 | )); 32 | } 33 | } 34 | ``` 35 | 36 | Now, let's examine how we would have to consume this implementation: 37 | 38 | 现在我们来研究如何使用这个实现类: 39 | 40 | ``` 41 | public function process(Order $order) 42 | { 43 | // Validate order... 44 | 45 | if($this->repository instanceof DatabaseOrderRepository) 46 | { 47 | $this->repository->connect('root', 'password'); 48 | } 49 | $this->repository->logOrder($order); 50 | } 51 | ``` 52 | 53 | Notice that we are forced to check if our OrderRepositoryInterface is a database implementation from within our consuming processor class. If it is, we must connect to the database. This may not seem like a problem in a very small application, but what if the OrderRepositoryInterface is consumed by dozens of other classes? We would be forced to implement this "bootstrap" code in every consumer. This will be a headache to maintain and is prone to bugs, and if we forgot to update a single consumer our application will break. 54 | 55 | 注意在这段代码中,我们必须在资料库外部检查 OrderRepositoryInterface 的实例对象是不是用数据库实现的。如果是的话,则必须先连接数据库。在很小的应用中这可能不算什么问题,但如果 OrderRepositoryInterface 被几十个类调用呢?我们可能就要把这段“启动”代码在每一个调用的地方复制一遍又一遍。这让人非常头疼难以维护,非常容易出错误。一旦我们忘了将所有调用的地方进行同步修改,那程序恐怕就会出问题。 56 | 57 | The example above clearly breaks the Liskov Substitution Principle. We were unable to inject an implementation of our interface without changing the consumer to call the connect method. So, now that we have identified the problem, let's fix it. Here is our new DatabaseOrderRepository implementation: 58 | 59 | 很明显,上面的例子没有遵循里氏替换原则。如果不附加“启动”代码来调用 connect 方法,则这段代码就没法用。好了,我们已经找到问题所在,咱们修好他。下面就是新的 DatabaseOrderRepository: 60 | 61 | ``` 62 | class DatabaseOrderRepository implements OrderRepositoryInterface { 63 | protected $connector; 64 | public function __construct(DatabaseConnector $connector) 65 | { 66 | $this->connector = $connector; 67 | } 68 | public function connect() 69 | { 70 | return $this->connector->bootConnection(); 71 | } 72 | public function logOrder(Order $order) 73 | { 74 | $connection = $this->connect(); 75 | $connection->run('insert into orders values (?, ?)', array( 76 | $order->id, $order->amount 77 | )); 78 | } 79 | } 80 | ``` 81 | 82 | Now our DatabaseOrderRepository is managing the connection to the database, and we can remove our "bootstrap" code from the consuming OrderProcessor: 83 | 84 | 现在 DatabaseOrderRepository 掌管了数据库连接,我们可以把“启动”代码从 OrderProcessor 移除了: 85 | 86 | ``` 87 | public function process(Order $order) 88 | { 89 | // Validate order... 90 | 91 | $this->repository->logOrder($order); 92 | } 93 | ``` 94 | 95 | With this modification, we can now use our CsvOrderRepository or DatabaseOrderRepository without modifying the OrderProcessor consumer. Our code adheres to the Liskov Substitution Principle! Take note that many of the architecture concepts we have discussed are related to knowledge. Specifically, the knowledge a class has of its "surrounding", such as the peripheral code and dependencies that help a class do its job. As you work towards a robust application architecture, limiting class knowledge will be a recurring and important theme. 96 | 97 | 这样一改,我们就可以想用 CsvOrderRepository 也行,想用 DatabaseOrderRepository 也行,不用改 OrderProcessor 一行代码。我们的代码终于实现了里氏替换原则!要注意,我们讨论过的许多架构概念都和知识相关。具体讲,知识就是一个类和它所具有的周边领域,比如用来帮助类完成任务的外围代码和依赖。当你要制作一个容错性强大的应用架构时,限制类的知识是一种常用且重要的手段。 98 | 99 | Also note the consequence of violating the Liskov Substitution principle with regards to the other principle we have covered. By breaking this principle, the Open Closed principle must also be broken, as, if the consumer must check for instances of various child classes, this consumer must be changed each time there is a new child class. 100 | 101 | 还要注意如果不遵守里氏替换原则,那后果可能会影响到我们之前已经讨论过的其他原则。不遵守里氏替换原则,那么开放封闭原则一定也会被打破。因为,如果调用者必须检查实例属于哪个子类的,那一旦有个新的子类,调用者就得做出改变。(译者注:这就违背了对修改封闭的原则。) 102 | 103 | > ### Watch For Leaks 小心遗漏 104 | 105 | > You have probably noticed that this principle is closely related to the avoidance of "leaky abstractions", which were discussed in the previous chapter. Our database repository's leaky abstraction was our first clue that the Liskov Substitution Principle was being broken. Keep an eye out for those leaks! 106 | 107 | > 你可能注意到这个原则和上一章节提到的“抽象的漏洞”密切相关。我们的数据库资料库的抽象漏洞就是没有遵守里氏替换原则的第一迹象。要留意那些漏洞! 108 | -------------------------------------------------------------------------------- /content/page/12.2_In_Action.md: -------------------------------------------------------------------------------- 1 | ## In Action 实践 2 | 3 | If you have already read prior chapters of this book, you already have a good grasp of the Dependency Inversion principle! To illustrate the principle, let's consider the following class: 4 | 5 | 如果你已经读过了本书前面几个章节,你就已经很好掌握了依赖反转原则!为了说明本原则,让我们考虑下面这个类: 6 | 7 | ``` 8 | class Authenticator { 9 | public function __construct(DatabaseConnection $db) 10 | { 11 | $this->db = $db; 12 | } 13 | public function findUser($id) 14 | { 15 | return $this->db->exec('select * from users where id = ?', array($id)); 16 | } 17 | public function authenticate($credentials) 18 | { 19 | // Authenticate the user... 20 | } 21 | } 22 | ``` 23 | 24 | As you might have guessed, the Authenticator class is responsible for finding and authenticating users. Let's examine the constructor of this class. You will see that we are type-hinting a DatabaseConnection instance. So, we're tightly coupling our authenticator to the database, and essentially saying that users will always only be provided out of a relational SQL database. Furthermore, our high-level code (the Authenticator) is directly depending on low-level code (the DatabaseConnection). 25 | 26 | 你可能猜到了,Authenticator 就是用来查找和验证用户的。继续研究它的构造函数。我们发现它使用了类型提示,要求传入一个 DatabaseConnection 对象,所以该验证类和数据库被紧密的联系在一起。而且基本上讲,这个数据库还只能是关系数据库。从而可知,我们的高级代码(Authenticator)直接的依赖着低级代码(DatabaseConnection)。 27 | 28 | First, let's discuss "high-level" and "low-level" code. Low-level code implements basic operations like reading files from a disk, or interaction with a database. High-level code encapsulates complex logic and relies on the low-level code to function, but should not be directly coupled to it. Instead, the high-level code should depend on an abstraction that sits on top of the low-level code, such as an interface. Not only that, but the low-level code should also depend upon an abstraction. So, let's write an interface that we can use within our Authenticator: 29 | 30 | 首先我们来谈谈“高级代码”和“低级代码”。低级代码用于实现基本的操作,比如从磁盘读文件,操作数据库等。高级代码用于封装复杂的逻辑,它们依靠低级代码来达到功能目的,但不能直接和低级代码耦合在一起。取而代之的是高级代码应该依赖着低级代码的顶层抽象,比如接口。不仅如此,低级代码也应当依赖着抽象。 所以我们来写个 Authenticator 可以用的接口: 31 | 32 | ``` 33 | interface UserProviderInterface { 34 | public function find($id); 35 | public function findByUsername($username); 36 | } 37 | ``` 38 | 39 | Next let's inject an implementation of this interface into our Authenticator: 40 | 41 | 接下来我们将该接口注入到 Authenticator 里面: 42 | 43 | ``` 44 | class Authenticator { 45 | public function __construct(UserProviderInterface $users, HasherInterface $hash) 46 | { 47 | $this->hash = $hash; 48 | $this->users = $users; 49 | } 50 | public function findUser($id) 51 | { 52 | return $this->users->find($id); 53 | } 54 | public function authenticate($credentials) 55 | { 56 | $user = $this->users->findByUsername($credentials['username']); 57 | return $this->hash->make($credentials['password']) == $user->password; 58 | } 59 | } 60 | ``` 61 | 62 | After making these changes, our Authenticator now relies on two high-level abstractions: UserProviderInterface and HasherInterface. We are free to inject any implementation of these interfaces into the Authenticator. For example, if our users are now stored in Redis, we can write a RedisUserProvider which implements the UserProviderInterface contract. The Authenticator is no longer depending directly on low-level storage operations. 63 | 64 | 做了这些小改动后,Authenticator 现在依赖于两个高级抽象:UserProviderInterface 和 HasherInterface。我们可以向 Authenticator 自由的注入这俩接口的任何实现类。比如,如果我们的用户存储在 Redis 里面,我们只需写一个 RedisUserProvider 来实现 UserProviderInterface 接口即可。Authenticator 不再依赖着具体的低级别的存储操作了。 65 | 66 | Furthermore, our low-level code now depends on the high-level UserProviderInterface abstraction, since it implements the interface itself: 67 | 68 | 此外,由于我们的低级别代码实现了 UserProviderInterface 接口,则我们说该低级代码依赖着这个接口。 69 | 70 | ``` 71 | class RedisUserProvider implements UserProviderInterface { 72 | public function __construct(RedisConnection $redis) 73 | { 74 | $this->redis = $redis; 75 | } 76 | public function find($id) 77 | { 78 | $this->redis->get('users:'.$id); 79 | } 80 | public function findByUsername($username) 81 | { 82 | $id = $this->redis->get('user:id:'.$username); 83 | return $this->find($id); 84 | } 85 | } 86 | 87 | ``` 88 | 89 | > ### Inverted Thinking 反转的思维 90 | 91 | > Applying this principle inverts the way many developers design applications. Instead of coupling high-level code directly to low-level code in a "top-down" fashion, this principle states that both high and low-level code depend upon a high-level abstraction. 92 | 93 | > 贯彻这一原则会反转好多开发者设计应用的方式。不再将高级代码直接和低级代码以“自上而下”的方式耦合在一起,这个原则提出无论高级还是低级代码都要依赖于一个高层次的抽象。 94 | 95 | Before we "inverted" the dependencies of our Authenticator, it could not be used with anything other than a database storage system. If we changed storage system, our Authenticator would also have to be modified, in violation of the Open Closed principle. Again, as we have seen before, multiple principles usually stand or fall together. 96 | 97 | 在我们没有反转 Authenticator 的依赖之前,它除了使用数据库存储系统别无选择。如果我们改变了存储系统,Authenticator 也需要被修改,这就违背了开放封闭原则。我们又一次看到,这些设计原则通常一荣俱荣一损俱损。 98 | 99 | After forcing the Authenticator to depend upon an abstraction over the storage layer, we can use it with any storage system that implements our UserProviderInterface contract without any modifications to the Authenticator itself. The conventional dependency chain has been inverted and our code is much more flexible and welcoming of change! 100 | 101 | 通过强制让 Authenticator 依赖着一个存储抽象层,我们就可以使用任何实现了 UserProviderInterface 接口的存储系统,且不用对 Authenticator 本身做任何修改。传统的依赖关系链已经被反转了,代码变得更灵活,更加无惧变化! 102 | -------------------------------------------------------------------------------- /content/page/9.2_In_Action.md: -------------------------------------------------------------------------------- 1 | ## In Action 实践 2 | 3 | To demonstrate the Open Closed principle, let's continue working with our OrderProcessor from the previous chapter. Consider the following section of the process method: 4 | 5 | 为了演示开放封闭原则,我们来继续编写上一章节的 OrderProcecssor。考虑下面的 process 方法: 6 | 7 | ``` 8 | $recent = $this->orders->getRecentOrderCount($order->account); 9 | 10 | if($recent > 0) 11 | { 12 | throw new Exception('Duplicate order likely.'); 13 | } 14 | ``` 15 | 16 | This code is quite readable, and even easy to test since we are properly using dependency injection. However, what if our business rules regarding order validation change? What if we get new rules? In fact, what if, as our business grows, we get many new rules? Our process method will quickly grow into a beast of spaghetti code that is hard to maintain. Because this code must be changed each time our business rules change, it is open for modification and violates the Open Closed principle. Remember, we want our code to be open for extension, not modification. 17 | 18 | 这段代码可读性很高,且因为我们使用了依赖注入,变得很容易测试。然而,如果我们判断订单的规则改变了呢?如果我们又有新的规则了呢?更进一步,如果随着我们的业务发展,要增加一大堆新规则呢?那我们的 process 方法会很快变成一坨难以维护的浆糊。因为这段代码必须随着每次业务逻辑的改变而跟着改变,它对修改是开放的,这违反了开放封闭原则。记住,我们希望代码对扩展开放,而不是修改。 19 | 20 | Instead of performing all of our order validation directly inside the process method, let's define a new interface: OrderValidator: 21 | 22 | 不必再把订单验证直接写在 process 方法里面,我们来定义一个新的接口:OrderValidator: 23 | 24 | ``` 25 | interface OrderValidatorInterface { 26 | public function validate(Order $order); 27 | } 28 | ``` 29 | 30 | Next, let's define an implementation that protects against duplicate orders: 31 | 32 | 下一步我们来定义一个实现接口的类,来预防重复订单: 33 | 34 | ``` 35 | class RecentOrderValidator implements OrderValidatorInterface { 36 | public function __construct(OrderRepository $orders) 37 | { 38 | $this->orders = $orders; 39 | } 40 | public function validate(Order $order) 41 | { 42 | $recent = $this->orders->getRecentOrderCount($order->account); 43 | if($recent > 0) 44 | { 45 | throw new Exception('Duplicate order likely.'); 46 | } 47 | } 48 | } 49 | ``` 50 | 51 | Great! Now we have a small, testable encapsulation of a single business rule. Let's create another implementation that verifies the account is not suspended: 52 | 53 | 很好!我们封装了一个小巧的、可测试的单一业务逻辑。咱们来再创建一个来验证账号是否停用吧: 54 | 55 | ``` 56 | class SuspendedAccountValidator implements OrderValidatorInterface { 57 | public function validate(Order $order) 58 | { 59 | if($order->account->isSuspended()) 60 | { 61 | throw new Exception("Suspended accounts may not order."); 62 | } 63 | } 64 | } 65 | ``` 66 | 67 | Now that we have two different implementations of our OrderValidatorInterface, let's use them within our OrderProcessor. We'll simply inject an array of validators into the processor instance, which will allow us to easily add and remove validation rules as our codebase evolves. 68 | 69 | 现在我们有两个不同的类实现了OrderValidatorInterface接口。咱们将在OrderProcessor里面使用它们。我们只需简单的将一个验证器数组注入进订单处理器实例中。这将使我们以后修改代码时能轻松的添加和删除验证器规则。 70 | 71 | ``` 72 | class OrderProcessor { 73 | public function __construct(BillerInterface $biller, OrderRepository $orders, array $validators = array()) 74 | { 75 | $this->biller = $bller; 76 | $this->orders = $orders; 77 | $this->validators = $validators; 78 | } 79 | } 80 | ``` 81 | 82 | Next, we can simply loop through the validators in the process method: 83 | 84 | 然后我们只要在 process 方法里面循环这个验证器数组即可: 85 | 86 | ``` 87 | public function process(Order $order) 88 | { 89 | foreach($this->validators as $validator) 90 | { 91 | $validator->validate($order); 92 | } 93 | 94 | // Process valid order... 95 | } 96 | ``` 97 | 98 | Finally, we will register our OrderProcessor class in the application IoC container: 99 | 100 | 最后我们在 IoC 容器里面注册 OrderProcessor 类: 101 | 102 | ``` 103 | App::bind('OrderProcessor', function() 104 | { 105 | return new OrderProcessor( 106 | App::make('BillerInterface'), 107 | App::make('OrderRepository'), 108 | array( 109 | App::make('RecentOrderValidator'), 110 | App::make('SuspendedAccountValidator') 111 | ) 112 | ); 113 | }); 114 | ``` 115 | 116 | With these few changes, which took only minimal effort to build from our existing codebase, we can now add and remove new validation rules without modifying a single line of existing code. Each new validation rule is simply a new implementation of the OrderValidatorInterface, and is registered with the IoC container. Instead of unit testing a large, unwieldy process method, we can now test each validation rule in isolation. Our code is now open for extension, but closed for modification. 117 | 118 | 在现有代码里付出些小努力,做一些小改动之后,我们现在可以添加删除新的验证规则而不必修改任何一行现有代码了。每一个新的验证规则就是对 OrderValidatorInterface 的一个实现类,然后注册进IoC容器里。不必再为那个又大又笨的 process 方法做单元测试了,我们现在可以单独测试每一个验证规则。现在,我们的代码对扩展是开放的,对修改是封闭的。 119 | 120 | > ### Leaky Abstractions 抽象的漏洞 121 | 122 | > Watch out for dependencies that leak implementation details. An implementation change in a dependency should not require any changes by its consumer. When changes to the consumer are required, it is said that the dependency is "leaking" implementation details. When your abstractions are leaky, the Open Closed principle has likely been broken. 123 | 124 | > 小心那些缺少实现细节的依赖(译者注:比如上面的 RecentOrderValidator )。当一个依赖的实现需要改变时,不应该要求它的调用者做任何修改。当需要调用者进行修改时,这就意味着该依赖遗漏了一些实现的细节。当你的抽象有漏洞的话,开放封闭原则就不管用了。 125 | 126 | Before processing further, remember that this principle is not a law. It does not state that every piece of code in your application must be "pluggable". For instance, a small application that retrieves a few records out of a MySQL database does not warrant a strict adherence to every design principle you can imagine. Do not blindly apply a given design principle out of guilt as you will likely create an over-designed, cumbersome system. Keep in mind that many of these design principles were created to address common architectural problems in large, robust applications. That being said, don't use this paragraph as an excuse to be lazy! 127 | 128 | 在我们继续学习前,要记住这些原则不是法律。这不是说你应用中每一块代码都应该是“热插拔”式的。例如,一个仅仅从 MySQL 检索几条记录的小应用程序,不值得去严格遵守每一条你想到的设计原则。不要盲目的应用设计原则,那样你会造出一个“过度设计”的繁琐的系统。记住这些设计原则是用来解决通用的架构问题,制造大型容错能力强的应用。我就这么一说,你可别把它当作懒惰的借口! 129 | -------------------------------------------------------------------------------- /content/page/2.1_Basic_Binding.md: -------------------------------------------------------------------------------- 1 | ## Basic Binding 基础绑定 2 | 3 | Now that we've learned about dependency injection, let's explore inversion of control containers. IoC containers make managing your class dependencies much more convenient, and Laravel ships with a very powerful container. The IoC container is the certral piece of the Laravel framework, and it is what allows all of the framework's jcomponents jto work together. In fact, the Laravel Application class extends the Container class! 4 | 5 | 我们已经学习了依赖注入,接下来咱们一起来探索“控制反转容器”(IoC)。 IoC 容器可以使你更容易管理依赖注入,Laravel 框架拥有一个很强大的IoC容器。Laravel 的核心就是这个 IoC 容器,这个 IoC 容器使得框架各个组件能很好的在一起工作。事实上 Laravel 的 Application 类就是继承自 Container 类! 6 | 7 | > ### IoC Container 控制反转容器 8 | 9 | > Inversion of control containers make dependency injection more convenient. How to resolve a given class or interface is defined once in the container, which manages resolving and injecting those objects throughout your application. 10 | 11 | > 控制反转容器使得依赖注入更方便。当一个类或接口在容器里定义以后,如何处理它们——如何在应用中管理、注入这些对象? 12 | 13 | In a Laravel application, the IoC container can be accessed via the App facade. The container has a variety of methods, but we'll start with the most basic. Let's continue to use our BillerInterface and BillingNotifierInterface from the previous chapter, and assume that our application is using Stripe to process payments. We can bind the Stripe implementation of the interface to the container like this: 14 | 15 | 在 Laravel 应用里,你可以通过 App 来访问控制反转容器。容器有很多方法,不过我们从最基础的开始。让我们继续使用上一章写的 BillerInterface 和 BillingNotifierInterface,且假设我们使用了 Stripe 来进行支付操作。我们可以将 Stripe 的支付实现绑定到容器里,就像这样: 16 | 17 | ``` 18 | App::bind('BillerInterface', function() 19 | { 20 | return new StripeBiller(App::make('BillingNotifierInterface')); 21 | }); 22 | ``` 23 | 24 | Notice that within our BillingInterface resolver, we also resolve a BillingNotifierInterface implementation. Let's define that binding as well: 25 | 26 | 注意在我们处理 BillingInterface 时,我们额外需要一个 BillingNotifierInterface 的实现,也就是再来一个 bind: 27 | 28 | ``` 29 | App::bind('BillingNotifierInterface', function() 30 | { 31 | return new EmailBillingNotifier; 32 | }); 33 | ``` 34 | 35 | So, as you can see, the container is a place to store Closures that resolve various classes. Once a class has been registered with the container, we can easily resolve it from anywhere in our application. We can even resolve other container bindings within a resolver. 36 | 37 | 如你所见, 这个容器就是个用来存储各种绑定的地方(译者注:这么理解简单点。再扯匿名函数、闭包就扯远了。)。一旦一个类在容器里绑定了以后,我们可以很容易的在应用的任何位置调用它。我们甚至可以在 bind 函数内写另外的 bind。 38 | 39 | > ### Have Acne? 40 | 41 | > The Laravel IoC container is a drop-in replacement for the Pimple IoC container by Fabien Potencier. So, if you're already using Pimple on a project, feel free to upgrade to the Illuminate Container component for a few more features! 42 | 43 | > Laravel 框架的 Illuminate 容器和另一个名为 Pimple 的 IoC 容器是可替换的。所以如果你之前用的是 Pimple,你尽可以大胆的升级为 Illuminate Container,后者还有更多新功能! 44 | 45 | Once we're using the container, we can switch interface implementations with a single line change. For example, consider the following: 46 | 47 | 一旦我们使用了容器,切换接口的实现就是一行代码的事儿。 比如考虑以下代码: 48 | 49 | ``` 50 | class UserController extends BaseController{ 51 | 52 | public function __construct(BillerInterface $biller) 53 | { 54 | $this->biller = $biller; 55 | } 56 | } 57 | ``` 58 | 59 | When this controller is instantiated via the IoC container, the StripeBiller, which includes the EmailBillingNotifier, will be injected into the instance. Now, if we want to change our notifier implementation, we can simply change the binding to this: 60 | 61 | 当这个控制器通被容器实例化后,包含着 EmailBillingNotifier 的 StripeBiller 会被注入到这个控制器中(译者注:见上文的两个 bind )。如果我们现在想要换一种提示方式,我们可以简单的将代码改为这样: 62 | 63 | ``` 64 | App::bind('BillingNotifierInterface', function() 65 | { 66 | return new SmsBillingNotifier; 67 | }); 68 | ``` 69 | 70 | Now, it doesn't matter where the notifier is resolved in our application, we will now always get the SmsBillingNotifier implementation. Utilizing this architecture, our application can be rapidly shifted to new implementations of various services. 71 | 72 | 现在不管在应用的哪里需要一个提示器,我们总会得到SmsBillingNotifier的对象。利用这种结构,我们的应用可以在不同的实现方式之间快速切换。 73 | 74 | Being able to change implementations of an interface with a single line is amazingly powerful. For example, imagine we want to change our SMS service from a legacy provider to Twilio. We can develop a new Twilio implementation of the notifier and swap our binding. If we have problems with the transition to Twilio, we can quickly change back to the legacy provider by making a single IoC binding change. As you can see, the benefits of using dependency injection go beyond what is immediately obvious. Can you think of more benefits for using dependency injection and an IoC container? 75 | 76 | 只改一行就能切换代码实现,这可是很厉害的能力。比如我们想把短信服务从原来的提供商替换为 Twilio。我们可以开发一个新的 Twilio 的提示器类(译者注:当然要继承自 BillingNotifierInterface)然后修改绑定语句。如果 Twilio 有任何闪失,我们只需修改一行代码就可以快速的切换回原来的短信提供商。看到了吧,依赖注入的好处多得很呢。你能再想出几个使用依赖注入和控制反转容器的好处么? 77 | 78 | Sometimes you may wish to resolve only one instance of a given class throughout your entire application. This can be achieved via the singleton method on the container class: 79 | 80 | 想在应用中只实例化某类一次?没问题,使用 singleton 方法吧: 81 | 82 | ``` 83 | App::singleton('BillingNotifierInterface', function() 84 | { 85 | return new SmsBillingNotifier; 86 | }); 87 | ``` 88 | 89 | Now, once the container has resolved the billing notifier once, it will continue to use that same instance for all subsequent requests for that interface. 90 | 91 | 这样只要这个容器生成了这个提示器对象一次, 在接下来的生成请求中容器都只会提供这同样的一个对象。 92 | 93 | The instance method on the container is similar to singleton, however, you are able to pass an already existing object instance. The instance you give to the container will be used each time the container needs an instance of that class: 94 | 95 | 容器的 instance 方法和 singleton 方法很类似,区别是 instance 可以绑定一个已经存在的对象。然后容器每次返回的都是这个对象了。 96 | 97 | ``` 98 | $notifier = new SmsBillingNotifier; 99 | App::instance('BillingNotifierInterface', $notifier); 100 | ``` 101 | 102 | Now that we're familiar with basic container resolution using Closures, let's dig into its most powerful feature: the ability to resolve class via reflection. 103 | 104 | 现在我们熟悉了容器的基础用法,让我们深入发掘它更强大的功能:依靠反射来处理类和接口。 105 | 106 | > ### Stand Alone Container 容器独立运行 107 | 108 | > Working on a project that isn't built on Laravel? You can still utilize Laravel's IoC container by installing the illuminate/container package via Composer! 109 | 110 | > 你的项目没有使用 Laravel ?但你依然可以使用 Laravel 的 IoC 容器!只要用 Composer 安装了 illuminate/container 包就可以了。 111 | -------------------------------------------------------------------------------- /content/page/5.5_Where_To_Put_Stuff.md: -------------------------------------------------------------------------------- 1 | ## Where To Put "Stuff" 东西都放哪儿? 2 | 3 | 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. 4 | 5 | 当用 Laravel 开发应用时,你可能迷惑于应该把各种“东西”都放在哪儿。比如,辅助函数要放在哪里?事件监听器要放在哪里?视图组件要放在哪里?答案可能出乎你的意料——“想放哪儿都行!”Laravel并没有很多在文件系统上的约定。不过这个答案的确不能让人满意,所以下面我们就这个问题展开探索,探索这些“东西”究竟可以放在哪儿。 6 | 7 | ### Helper Functions 辅助函数 8 | 9 | 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: 10 | 11 | Laravel 有一个文件 (support/helpers.php) 里面都是辅助函数。你或许希望创建一个类似的文件来存储你自己的辅助函数。“start” 文件是个不错的入口,该文件会在应用的每一次请求时被访问。在 start/global.php 里,你可以引入你自己写的 helpers.php 文件,就像这样: 12 | 13 | ``` 14 | // Within app/start/global.php 15 | 16 | require_once __DIR__.'/../helpers.php'; 17 | //译者注: 该helpers.php文件位于app目录下,需要你自己创建。你想放到别的地方也可以。 18 | ``` 19 | 20 | ### Event Listeners 事件监听器 21 | 22 | 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. 23 | 24 | 事件监听器当然不该放到 routes.php 文件里面,若直接放到 “start” 目录下的文件里会比较乱,所以我们要找另外的地方来存放。服务提供者是个好地方。我们之前了解到,服务提供者可不仅仅是用来做依赖注入绑定,还可以干其他事儿。可以将事件监听器用服务提供者来管理起来,让代码更整洁,不至于影响到你应用的主要逻辑代码。视图组件其实和事件差不多,也可以类似的放到服务提供者里面。 25 | 26 | For example, a service provider that registers events might look something like this: 27 | 28 | 例如使用服务提供者进行事件注册可以这样: 29 | 30 | ``` 31 | ### Wear The Boot注意启动流程 49 | 50 | > 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. 51 | 52 | > 记住在上面的例子里面,我们在 boot 方法里进行编写是有原因的。register 方法只能用来进行依赖注入绑定。 53 | 54 | ### Error Handlers 错误处理 55 | 56 | 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: 57 | 58 | 如果你的应用里面有很多自定义的错误处理方法,那你的“启动”文件可能会很臃肿。和刚才的事件监听器一样,错误处理方法也最好放到服务提供者里面。这种服务提供者可以命名为像 QuickBillErrorProvider 这种。然后你在 boot 方法里想注册多少错误处理方法都可以了。重申一下精神:让呆板的代码离你应用的业务逻辑越远越好。下方展示了这种服务提供者的一种可能的书写方法: 59 | 60 | ``` 61 | ### The Small Solution 简便做法 80 | 81 | > Of course, if you only have one or two simple error handlers, keeping them in the "start" files is a reasonable and quick solution. 82 | 83 | > 当然如果你只有一两条简单的错误处理方法,那么都写在“启动”文件里面也是一种又快又好的简便做法。 84 | 85 | ### The Rest 其他 86 | 87 | 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! 88 | 89 | 通常只要遵循 PSR-0(译者注:或 PSR-4 )就可以保持类的整洁。命令式的代码比如事件监听器、错误处理器还有其他“注册”性质的操作都可以放在服务提供者里面。对于什么代码要放在什么地方这个问题,结合你目前为止学到的知识,应当可以给出一个有理有据的答案了。但永远不要害怕试验。Laravel 最美妙之处就是你可以做出最适合你自己的风格。去探索和发现最适合你自己应用的结构吧,别忘了和他人分享你的见解! 90 | 91 | 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: 92 | 93 | 例如你可能注意到我们上面的例子,你可以创建个 Providers 的命名空间来存放你自己写的服务提供者,目录就类似于这样: 94 | 95 | ``` 96 | // app 97 | // QuickBill 98 | // Billing 99 | // Extensions 100 | //Pagination 101 | -> Environment.php 102 | // Providers 103 | -> EventPusherServiceProvider.php 104 | // Repositories 105 | User.php 106 | Payment.php 107 | ``` 108 | 109 | 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 Extensions namespace is a convenient place to store extensions made to core framework classes. 110 | 111 | 看上面的例子我们有 Providers 和 Extensions 两个命名空间(译者注:分别对应两个同名目录)。你自己写的服务提供者可以放到 Providers 命名空间下。那个 Extensions 命名空间可以用来存放你对框架核心进行扩展的类。 112 | --------------------------------------------------------------------------------