├── figures ├── using-eloquent-1.png ├── using-eloquent-2.png ├── using-eloquent-3.png ├── using-eloquent-4.png ├── using-eloquent-5.png ├── using-eloquent-6.png ├── using-eloquent-7.png ├── using-query-builder-1.png ├── using-query-builder-10.png ├── using-query-builder-11.png ├── using-query-builder-12.png ├── using-query-builder-13.png ├── using-query-builder-2.png ├── using-query-builder-3.png ├── using-query-builder-4.png ├── using-query-builder-5.png ├── using-query-builder-6.png ├── using-query-builder-7.png ├── using-query-builder-8.png ├── using-query-builder-9.png └── introduction-to-query-builder-and-eloquent-1.png ├── README.md ├── from-apprentice-to-artisan.md ├── the-real-meaning-of-mass-assignment.md ├── five-ways-to-get-routing-parameters.md ├── introduction-to-inversion-of-control.md ├── introduction-to-query-builder-and-eloquent.md ├── avoid-trying-to-get-property-of-non-object-error.md ├── do-you-need-a-dependency-injection-container.md ├── valet-phpstorm-xdebug-phpunit-qcachegrind(mac).md ├── what-is-dependency-injection.md ├── using-eloquent.md ├── the-concept-of-laravel-service-provider.md ├── using-query-builder.md └── laravel-container-in-depth.md /figures/using-eloquent-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-eloquent-1.png -------------------------------------------------------------------------------- /figures/using-eloquent-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-eloquent-2.png -------------------------------------------------------------------------------- /figures/using-eloquent-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-eloquent-3.png -------------------------------------------------------------------------------- /figures/using-eloquent-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-eloquent-4.png -------------------------------------------------------------------------------- /figures/using-eloquent-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-eloquent-5.png -------------------------------------------------------------------------------- /figures/using-eloquent-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-eloquent-6.png -------------------------------------------------------------------------------- /figures/using-eloquent-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-eloquent-7.png -------------------------------------------------------------------------------- /figures/using-query-builder-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-1.png -------------------------------------------------------------------------------- /figures/using-query-builder-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-10.png -------------------------------------------------------------------------------- /figures/using-query-builder-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-11.png -------------------------------------------------------------------------------- /figures/using-query-builder-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-12.png -------------------------------------------------------------------------------- /figures/using-query-builder-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-13.png -------------------------------------------------------------------------------- /figures/using-query-builder-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-2.png -------------------------------------------------------------------------------- /figures/using-query-builder-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-3.png -------------------------------------------------------------------------------- /figures/using-query-builder-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-4.png -------------------------------------------------------------------------------- /figures/using-query-builder-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-5.png -------------------------------------------------------------------------------- /figures/using-query-builder-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-6.png -------------------------------------------------------------------------------- /figures/using-query-builder-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-7.png -------------------------------------------------------------------------------- /figures/using-query-builder-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-8.png -------------------------------------------------------------------------------- /figures/using-query-builder-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/using-query-builder-9.png -------------------------------------------------------------------------------- /figures/introduction-to-query-builder-and-eloquent-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seekerliu/laravel-tips/HEAD/figures/introduction-to-query-builder-and-eloquent-1.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Laravel Tips 【Laravel 微课堂】 2 | 1. [Laravel 获取 Route Parameters (路由参数) 的 5 种方法](./five-ways-to-get-routing-parameters.md) 3 | 2. [Laravel Mass-Assignment (批量赋值) 的真正含义](./the-real-meaning-of-mass-assignment.md) 4 | 3. [Laravel Dependency Injection (依赖注入) 概念详解](./what-is-dependency-injection.md) 5 | 4. [Laravel Container (容器) 概念详解 (上)](./do-you-need-a-dependency-injection-container.md) 6 | 5. [Laravel Container (容器) 深入理解 (下)](./laravel-container-in-depth.md) 7 | 6. [Laravel Service Provider 概念详解](./the-concept-of-laravel-service-provider.md) 8 | 7. [Laravel Inversion of Control (控制反转) 概念简介](./introduction-to-inversion-of-control.md) 9 | 8. [Laravel Query Builder & Eloquent ORM 介绍](./introduction-to-query-builder-and-eloquent.md) 10 | 9. [Laravel Query Builder 原理及用法](./using-query-builder.md) 11 | 10. [Laravel Eloquent 用法](./using-eloquent.md) 12 | 11. [Laravel - From Apprentice To Artisan, by Taylor Otwell](./from-apprentice-to-artisan.md) 13 | 12. [Laravel 避免 Trying to get property of non-object 错误的六种方法](./avoid-trying-to-get-property-of-non-object-error.md) 14 | 13. [Laravel 配置 valet, PHPStorm, xdebug, PHPUnit, qcachegrind(mac)环境](./valet-phpstorm-xdebug-phpunit-qcachegrind(mac).md) 15 | -------------------------------------------------------------------------------- /from-apprentice-to-artisan.md: -------------------------------------------------------------------------------- 1 | # Laravel - From Apprentice To Artisan, by Taylor Otwell. 2 | 3 | 《From Apprentice To Artisan》这本书是 Laravel 作者 Taylor Otwell 写的,里面详细介绍了 Laravel 框架涉及的各种软件理念和工具,如 DI、IoC、SOLID 等。 4 | 5 | 最近才发现这本书,本想着手翻译,才发现有大神在三年前就翻译好了,所以只贴个目录在这儿吧~ 6 | 7 | 英文正版链接:https://leanpub.com/laravel 8 | zgldh 大神翻译的中文版链接:https://my.oschina.net/zgldh/blog/389246 9 | huanghua581 大神制作的 Gitbook 版:https://huanghua581.github.io/FATA/ 10 | 11 | >目录 12 | 13 | * Dependency Injection 依赖注入 14 | * The Problem 遇到的问题 15 | * Build A Contract 建立约定 16 | * Take It further 更进一步 17 | * Too Much Java? 太像Java了? 18 | * The IoC Container 控制反转容器 19 | * Basic Binding 基础绑定 20 | * Reflective Resolution 反射解决方案 21 | * Interface As Contract 接口约定 22 | * Strong Typing & Water Fowl 强类型和小鸭子 23 | * A Contract Example 约定的范例 24 | * Interface & Team Development 接口与团队开发 25 | * Service Provider 服务提供者 26 | * As Bootstrapper 他是引导程序 27 | * As Organizer 作为管理工具 28 | * Booting Providers 服务提供者的启动过程 29 | * Providing The Core 核心也是服务提供者的模式 30 | * Application Structure 应用结构 31 | * Introduction 介绍 32 | * MVC Is Killing You MVC是慢性谋杀 33 | * Bye, Bye Models 再见,模型 34 | * It's All About The Layers 核心思想就是分层 35 | * Where To Put "Stuff" 东西都放哪儿? 36 | * Applied Architecture: Decoupling Handles 实用做法:解耦处理函数 37 | * Introduction 介绍 38 | * Decoupling Handlers 解耦处理函数 39 | * Other Handlers 其他处理函数 40 | * Extending The Framework 扩展框架 41 | * Introduction 介绍 42 | * Managers & Factories 管理者和工厂 43 | * Cache 缓存 44 | * Session 会话 45 | * Authentication 身份认证 46 | * IoC Based Extension 使用容器进行扩展 47 | * Request Extension 请求的扩展 48 | 49 | >以下是 SOLID 的讲解。 50 | 51 | * Single Responsibility Principle 单一职责原则 52 | * Introduction 介绍 53 | * In Action 实践 54 | * Open Closed Principle 开放封闭原则 55 | * Introduction 介绍 56 | * In Action 实践 57 | * Liskov Substitution Principle 里氏替换原则 58 | * Introduction 介绍 59 | * In Action 实践 60 | * Interface Segregation Principle 接口隔离原则 61 | * Introduction 介绍 62 | * In Action 实践 63 | * Dependency Inversion Principle 依赖反转原则 64 | * Introduction 介绍 65 | * In Action 实践 66 | -------------------------------------------------------------------------------- /the-real-meaning-of-mass-assignment.md: -------------------------------------------------------------------------------- 1 | # Laravel Mass-Assignment (批量赋值) 的真正含义 2 | 3 | > 初次遇到 `批量赋值` 的时候,很容易理解成 `批量添加多条数据`,实际并非如此。请看下面的例子。 4 | 5 | 假设用户表 `users` 结构如下,且通过 `is_admin` 字段值为 `1` 或 `0` 来判断用户是否为 `管理员`,其中 `is_admin` 字段默认值为 `0`: 6 | ```bash 7 | +----+-----------+------------------+----------+--------------------------------------------------------------+ 8 | | id | name | email | is_admin | password | 9 | +----+-----------+------------------+----------+--------------------------------------------------------------+ 10 | | 1 | seekerliu | me@seekerliu.com | 1 | $2y$10$RL6r.MwoJd.oOvKRYhUpmeQI6hUpoG/KgGNhA6X5JrRqfVbooCs92 | 11 | +----+-----------+------------------+----------+--------------------------------------------------------------+ 12 | ``` 13 | 正常情况下,我们通过这种方式新建一个 `普通` 用户: 14 | ```php 15 | public function store (Request $request) 16 | { 17 | $user = new \App\User; 18 | 19 | // 赋值 20 | $user->name = $request->name; 21 | $user->email = $request->email; 22 | $user->password = bcrypt($request->password); 23 | 24 | // 新建一个用户 25 | $user->save(); 26 | } 27 | ``` 28 | 29 | 为了方便,我们可以使用 `$request->all()` 获取用户提交的所有表单数据: 30 | ```php 31 | public function store (Request $request) 32 | { 33 | $user = new \App\User; 34 | 35 | // Mass-Assignment 批量赋值 36 | $data = $request->all(); 37 | 38 | // 新建一个用户 39 | $user->create($data); 40 | } 41 | ``` 42 | 这种情况下,如果用户提交正确的表单数据,例如: 43 | ```php 44 | ['name' => 'liu', 'email' => 'liu@seekerliu.com', 'password' => 'test'] 45 | ``` 46 | 会新建一个 `普通` 用户。 47 | 但只要用户在表单中伪造一个 `['is_admin' => 1]` 字段,就能新建一个 `管理员` 用户。 48 | 49 | #### 将多个字段同时传递给模型 `create()` 方法来新建一行,就是 `Mass-Assignment (批量赋值)` 。 50 | 51 | `Laravel` 提供了保护 `Mass-Assignment` 的方法,那就是在模型上定义 `fillable` 或 `guarded` 的属性,例如: 52 | ```php 53 | class User extend Model 54 | { 55 | protected $fillable = ['name', 'email', 'password']; 56 | } 57 | ``` 58 | 或: 59 | ```php 60 | class User extend Model 61 | { 62 | protected $guarded = ['is_admin']; 63 | } 64 | ``` 65 | 这样,在执行 `create()` 方法时,`Eloquent` 模型会先使用 `fill()` 方法对数据进行过滤,去掉 `$fillable` 以外的字段(白名单),或去掉 `$guarded` 中的字段(黑名单),来保证只获取预期的表单字段。 66 | 67 | 以上就是 `Laravel` 的 `Mass-Assignment` 。 68 | -------------------------------------------------------------------------------- /five-ways-to-get-routing-parameters.md: -------------------------------------------------------------------------------- 1 | # Laravel 获取 Route Parameters (路由参数) 的 5 种方法 2 | 3 | > Laravel 获取路由参数的方式有很多,并且有个小坑,汇总如下。 4 | 5 | ### 假设我们设置了一个路由参数: 6 | ```php 7 | /** 8 | * 定义路由参数名称分别为: param1,param2 9 | */ 10 | Route::get('/{param1}/{param2}', 'TestController@index'); 11 | ``` 12 | ### 现在我们访问 `http://test.dev/1/2` 13 | ### 在 `TestController` 中: 14 | ```php 15 | /** 16 | * 路由参数获取方法 17 | * 18 | * @param Illuminate\Http\Request $request 依赖注入 Request 实例,放在参数中什么位置都可以自动加载 19 | * @param mixed $arg2 要获取的路由参数 20 | * @param mixed $arg1 要获取的路由参数 21 | */ 22 | 23 | public function index(Request $request, $arg2, $arg1) 24 | { 25 | 26 | /** 27 | * 方法一:按照 URL 中路由参数先后顺序来获取 28 | * 注意:此种方式有个小坑,获取的值只与顺序有关,与名称无关 29 | */ 30 | echo $arg2; //结果为 1 ,因为 $arg2 在第一位,获取的是第一个路由参数 param1 的值 31 | echo $arg1; //结果为 2 ,因为 $arg1 在第二位,获取的是第二个路由参数 param2 的值 32 | 33 | /** 34 | * 方法二:按照路由参数名称来获取 35 | * 注意:此处名称是 Route 中定义的参数名,非上面方法中的参数名 36 | */ 37 | $request->route('param1'); //结果为 1 ,获取的是第一个路由参数 38 | $request->route('param2'); //结果为 2 ,获取的是第二个路由参数 39 | 40 | /** 41 | * 方法三:使用 request() 辅助函数来获取,效果同方法二 42 | */ 43 | request()->route('param1'); //结果为 1 ,如果不带路由参数名则返回当前的Route对象 44 | request()->route('param2'); //结果为 2 ,如果不带路由参数名则返回当前的Route对象 45 | 46 | /** 47 | * 方法四:使用 Route Facade 48 | */ 49 | \Route::input('param1'); //结果为 1 ,该方法必须带路由参数名 50 | \Route::input('param2'); //结果为 2 ,该方法必须带路由参数名 51 | 52 | /** 53 | * 方法五:使用 Illuminate\Http\Request 实例动态属性 54 | */ 55 | $request->param1; //结果为 1 ,Laravel 5.4+ 可用 56 | $request->param2; //结果为 2 ,Laravel 5.4+ 可用 57 | 58 | // 或者 59 | request()->param1; //结果为 1 ,Laravel 5.4+ 可用 60 | request()->param2; //结果为 2 ,Laravel 5.4+ 可用 61 | 62 | //或者 63 | request('param1'); //结果为 1 ,Laravel 5.4+ 可用 64 | request('param2'); //结果为 2 ,Laravel 5.4+ 可用 65 | 66 | /** 67 | * 注意:Laravel 在处理动态属性的优先级是,先从请求的数据(POST/GET)中查找,没有的话再到路由参数中找。 68 | * 例如:URL : http://test.dev/1/2?param1=a¶m2=b 69 | * $request->param1; request()->param1; request('param1'); //结果为 a 70 | * $request->param2; request()->param2; request('param2'); //结果为 b 71 | */ 72 | } 73 | ``` 74 | 以上就是 Laravel 获取路由参数的 5 种方法。 -------------------------------------------------------------------------------- /introduction-to-inversion-of-control.md: -------------------------------------------------------------------------------- 1 | # Laravel Inversion of Control (控制反转) 概念简介 2 | 3 | >本文内容部分摘自 Wikipedia - [Inversion of Control](https://zh.wikipedia.org/wiki/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%AC) . 4 | 5 | # 概述 6 | 7 | IoC (控制反转),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。 8 | 9 | ## 实现「控制反转」,有两种方式: 10 | * Dependency Injection (DI) - 依赖注入 11 | * Dependency Lookup - 依赖查找 12 | 13 | > 两者的区别在于,前者是被动的接收对象,在类实例创建过程中即创建了依赖的对象,通过类型或名称来判断将不同的对象注入到不同的属性中,而后者是主动索取相应类型的对象,获得依赖对象的时间也可以在代码中自由控制。 14 | 15 | ## 哪些方面的「控制」被「反转」了? 16 | 17 | 依赖对象的「获得」被反转了。 18 | 19 | ## 技能描述 20 | Class A 中用到了 Class B 的对象 b,一般情况下,需要在 A 的代码中显式的 new 一个 B 的对象。采用依赖注入技术之后,A 的代码只需要定义一个私有的B对象,不需要直接 new 来获得这个对象,而是通过相关的 `容器控制程序` 来将 B 对象在外部 new 出来并注入到 A 类里的引用中。而具体获取的方法、对象被获取时的状态由 `容器` 来指定。 21 | 22 | 假设我有一个 iPhone,我的 iPhone 依赖充电器才能充电。 23 | ```php 24 | class iPhone 25 | { 26 | // 电量 27 | private $power; 28 | 29 | // 充电 30 | public function charge() 31 | { 32 | } 33 | } 34 | ``` 35 | 36 | 我还有个苹果原装充电器: 37 | ```php 38 | class AppleCharger 39 | { 40 | public function charge() 41 | { 42 | return 100; 43 | } 44 | } 45 | ``` 46 | 47 | 在以前,iPhone 内部「控制」着只能用哪一款充电器: 48 | ```php 49 | class iPhone 50 | { 51 | // 电量 52 | private $power; 53 | 54 | // 充电,只能用原装的充电器 55 | public function charge() 56 | { 57 | $charger = new AppleCharger; 58 | $this->power = $charger->charge(); 59 | } 60 | } 61 | ``` 62 | 63 | ```php 64 | // 充电 65 | $iphone = new iPhone; 66 | $iphone->charge(); 67 | ``` 68 | 69 | 使用依赖注入之后,我来决定给 iPhone 用哪一款充电器: 70 | ```php 71 | class iPhone 72 | { 73 | private $power; 74 | private $charger; 75 | 76 | // 依赖注入充电器,╮(╯▽╰)╭哎算了只要是充电器就行 77 | public function __construct(Charger $charger) 78 | { 79 | $this->charger = $charger; 80 | } 81 | 82 | // 充电 83 | public function charge() 84 | { 85 | $this->power = $this->charger->charge(); 86 | } 87 | } 88 | ``` 89 | 90 | ```php 91 | interface Charger 92 | { 93 | public function charge(); 94 | } 95 | ``` 96 | 97 | ```php 98 | // Laravel 容器 99 | use Illuminate\Container\Container; 100 | $container = Container::getInstance(); 101 | 102 | // 给它一个原装充电器: 103 | $container->bind(Charger::class, AppleCharger::class); 104 | 105 | // 或者给它其它充电器 106 | $container->bind(Charger::class, OtherCharger::class); 107 | 108 | // 充电 109 | $iphone = $container->make(iPhone::class); 110 | $iphone->charger(); 111 | ``` 112 | 113 | 可见,使用依赖注入之后,控制权「反转」了,由外部来决定给它什么充电器 (依赖对象)。 114 | `Laravel` 管这个 `容器控制程序` 叫 `Service Container (服务容器)`,它来控制着各种依赖的获取方法。 115 | 116 | > 更多关于 Laravel 依赖注入、服务容器的知识可以参考以前的文章。 117 | 118 | # 扩展知识 119 | ### 依赖注入实现方式: 120 | * 基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。 121 | * 基于接口。实现特定接口以供外部容器注入所依赖类型的对象。 122 | * 基于 set 方法。实现特定属性的 public set 方法,来让外部容器调用传入所依赖类型的对象。 123 | * 基于注解。基于 Java 的注解功能,在私有变量前加 "@Autowired" 等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了 public 的 set 方法,但是因为没有真正的 set 方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为 set 方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)。 124 | 125 | ### 依赖查找: 126 | 依赖查找更加主动,在需要的时候通过调用框架提供的方法来获取对象,获取时需要提供相关的配置文件路径、key等信息来确定获取对象的状态。 -------------------------------------------------------------------------------- /introduction-to-query-builder-and-eloquent.md: -------------------------------------------------------------------------------- 1 | # Laravel Query Builder & Eloquent ORM 介绍 2 | 3 | >本文翻译自 [《Laravel - My first framework》](https://leanpub.com/laravel-first-framework/) 4 | 5 | `Laravel`拥有两个功能强大的功能来执行数据库操作: 6 | * Query Builder - 查询构造器 7 | * Eloquent ORM 8 | 9 | ![Database operations in Laravel](./figures/introduction-to-query-builder-and-eloquent-1.png) 10 | 11 | 我们可以单独使用这两个功能,或两者结合起来,以更灵活的进行数据库操作。下面来看看它们的区别。 12 | 13 | ## Query Builder 14 | 15 | `Laravel` 的 `Query Builder` 为执行数据库查询提供了一个干净简单的接口。它可以用来进行各种数据库操作,例如: 16 | 17 | * Retrieving records - 检索记录 18 | * Inserting new records - 插入记录 19 | * Deleting records - 删除记录 20 | * Updating records - 更新记录 21 | * Performing "Join" queries - 执行 JOIN 22 | * Executing raw SQL queries - 执行「原生」 SQL 语句 23 | * Filtering, grouping and sorting of records - 过滤、分组和排序记录 24 | 25 | 用 `DB` Facade 来使用 `Query Builder` 26 | 27 | ```php 28 | // 给 orders 表创建几条新记录 29 | DB::table('orders')->insert([ 30 | ['price' => 400, 'product' => 'Laptop'], 31 | ['price' => 200, 'product' => 'Smartphone'], 32 | ['price' => 50, 'product' => 'Accessory'], 33 | )); 34 | 35 | // 检索 orders 表中 所有 price 大于 100 的记录 36 | $orders = DB::table('orders') 37 | ->where('price', '>', 100) 38 | ->get(); 39 | 40 | // 获取 orders 表中 price 列的平均值 41 | $averagePrice = DB::table('orders')->avg('price'); 42 | 43 | // 查找 orders 表中所有 price 等于 50 的记录 44 | // 把他们 product 字段改为 Laptop 45 | // proce 字段改为 400 46 | DB::table('orders') 47 | ->where('price', 50) 48 | ->update(['product' => 'Laptop', 'price' => 400]); 49 | ``` 50 | 51 | `Query Builder` 是一个非常易于使用但很强大的与数据库进行交互的方式。 52 | 接下来看看 `Eloquent ORM`。 53 | 54 | ## Eloquent ORM 55 | `Eloquent` 是 `Laravel` 中对 `Active Recode pattern (领域模式)` 的实现。它通过 「模型」的概念来根数据表进行交互。 56 | 57 | > ORM, Active Record and Eloquent 58 | `ORM - Object/Relational Mapping (对象/关系映射)`,是随着面向对象的软件开发方法发展而产生的一种技术,用来把对象模型表示的对象映射到基于 SQL 的关系模型数据库结构中去。这样,我们在具体的操作实体对象的时候,就不需要再去和复杂的 SQL 语句打交道,只需简单的操作实体对象的属性和方法。ORM 技术是在对象和关系之间提供了一条桥梁,前台的对象型数据和数据库中的关系型的数据通过这个桥梁来相互转化。 59 | `Active Record` ,是一种领域模型模式,特点是一个「模型」类对应关系型数据库中的一个表,而「模型」类的一个实例对应表中的一行记录。 60 | 流行的Web开发框架,如 `CodeIgniter` ,`Ruby on Rails` 和 `Symfony` 都使用Active Record 模式来简化数据库操作。 而 `Eloquent` 就是 `Laravel` 自己的 `Active Record` 模式的实现。 61 | 62 | 要开始使用 `Eloquent`,只需要在 `app/config/database.php` 文件中配置数据库连接设置,并创建与数据库表相对应的「模型」。 `Eloquent` 的语法与普通的 PHP 代码没有太大的不同,所以很容易阅读。 63 | `Eloquent`提供许多先进的功能来管理和操作数据,其中最突出的是: 64 | 65 | * Working with data by the use of "models" - 通过「模型」来处理数据 66 | * Creating relationships between data - 创建表之间的关联关系 67 | * Querying related data - 查询数据 68 | * Conversion of data to JSON and arrays - 将数据转换为 `JSON` 或数组 69 | * Query optimization - 优化查询 70 | * Automatic timestamps - 自动时间戳 71 | 72 | `Eloquent` 涵盖了简单的数据插入,表格之间的数据关联,以及相关数据中的复杂过滤等多个方面。 73 | 例如: 74 | 75 | ```php 76 | // app/User.php 77 | class User extends Eloquent { 78 | public function orders() 79 | { 80 | return $this->hasMany('Order'); 81 | } 82 | } 83 | 84 | // app/models/Order.php 85 | class Order extends Eloquent {} 86 | 87 | // 在 users 表中创建一行 88 | $user = new User; 89 | $user->name = "John Doe"; 90 | $user->save(); 91 | 92 | // 在 orders 表中创建一行,并与 user 关联 93 | $order = new Order; 94 | $order->price = 100; 95 | $order->product = "TV"; 96 | 97 | $user->orders()->save($order); 98 | ``` 99 | 100 | 以上示例只是 `Query Builder` 和 `Eloquent ORM` 概念的简单介绍,下一篇中我将详细讲解 `Query Builder` 的用法。 -------------------------------------------------------------------------------- /avoid-trying-to-get-property-of-non-object-error.md: -------------------------------------------------------------------------------- 1 | # Laravel 避免 Trying to get property of non-object 错误的六种方法 2 | 3 | 在使用链式操作的时候,例如: 4 | 5 | ``` 6 | return $user->avatar->url; 7 | ``` 8 | 9 | 如果 `$user->avatar` 为 `null`,就会引起 `(E_ERROR) 10 | Trying to get property 'url' of non-object` 错误。 11 | 12 | ## 1. 常规方法是使用 `isset` 加以判断: 13 | ``` 14 | if(isset($user->avatar->url)) 15 | return $user->avatar->url; 16 | else 17 | return 'defaultUrl'; 18 | ``` 19 | 20 | 如果在 `blade` 模板的 `echo` 中,可以使用: 21 | 22 | ``` 23 | {{ $user->avatar->url or 'defaultUrl' }} 24 | ``` 25 | 26 | 上述代码会被 `Blade` 引擎解析为: 27 | 28 | ``` 29 | echo e(isset($user->avatar->url) ? $user->avatar->url : 'defaultUrl'); 30 | ``` 31 | 32 | > `Laravel 5.7` 已经取消了这个特性。详见:[https://github.com/laravel/framework/pull/23532](https://github.com/laravel/framework/pull/23532) 。感谢 [@jltxwesley](https://laravel-china.org/users/28596) 提醒。 33 | 34 | ## 2. `PHP7` 可以使用 `?? (NULL 合并操作符)` : 35 | ``` 36 | // 如果 $user->avatar->url 为 null, 返回 'defaultUrl' 37 | return $user->avatar->url ?? 'defaultUrl'; 38 | ``` 39 | 40 | ## 3. `Laravel 5.5` 及以上可以使用 `optional` 辅助函数: 41 | ``` 42 | /** 43 | * 如果给定的对象是 null , 那么属性和方法会简单地返回 null 而不是产生一个错误: 44 | */ 45 | return optional($user->avatar)->url; 46 | ``` 47 | 48 | 详见 [https://laravel-china.org/docs/laravel/5.7/helpers/1320#method-optional](https://laravel-china.org/docs/laravel/5.7/helpers/1320#method-optional) 49 | 50 | `Laravel 5.7` 中,`optional` 函数还可以接受 `匿名函数` 作为第二个参数: 51 | ``` 52 | /** 53 | * 如果第一个参数不为 null, 则调用闭包 54 | */ 55 | return optional(User::find($id), function ($user) { 56 | return new DummyUser; 57 | }); 58 | ``` 59 | 60 | 详见 [https://laravel.com/docs/5.7/helpers#method-optional](详见 https://laravel.com/docs/5.7/helpers#method-optional) 61 | 62 | ## 4. 使用 `object_get` 辅助函数 63 | ``` 64 | return object_get($user->avatar, 'url', 'default'); 65 | ``` 66 | 67 | 这个函数原意是用来以 `.` 语法来获取对象中的属性,例如: 68 | 69 | ``` 70 | return object_get($user, 'avatar.url', 'default'); 71 | ``` 72 | 73 | 也可以达到避免 `non-object` 错误的效果。 74 | 75 | ``` 76 | if (! function_exists('object_get')) { 77 | /** 78 | * Get an item from an object using "dot" notation. 79 | * 80 | * @param object $object 81 | * @param string $key 82 | * @param mixed $default 83 | * @return mixed 84 | */ 85 | function object_get($object, $key, $default = null) 86 | { 87 | if (is_null($key) || trim($key) == '') { 88 | return $object; 89 | } 90 | 91 | foreach (explode('.', $key) as $segment) { 92 | if (! is_object($object) || ! isset($object->{$segment})) { 93 | return value($default); 94 | } 95 | 96 | $object = $object->{$segment}; 97 | } 98 | 99 | return $object; 100 | } 101 | } 102 | ``` 103 | 104 | 详见 [https://github.com/laravel/framework/blob/master/src/Illuminate/Support/helpers.php#L673](https://github.com/laravel/framework/blob/master/src/Illuminate/Support/helpers.php#L673) 105 | 106 | > 感谢 [@lovecn](https://laravel-china.org/users/87) 提供姿势! 107 | 108 | 109 | ## 5. 使用 `data_get` 辅助函数 110 | ``` 111 | return data_get($user, 'avatar.url', 'default'); 112 | ``` 113 | 114 | 或 115 | 116 | ``` 117 | return data_get($user, ['avatar', 'url'], 'default'); 118 | ``` 119 | 120 | 以 `.` 语法来获取对象属性或数组元素。 121 | 122 | ``` 123 | if (! function_exists('data_get')) { 124 | /** 125 | * Get an item from an array or object using "dot" notation. 126 | * 127 | * @param mixed $target 128 | * @param string|array $key 129 | * @param mixed $default 130 | * @return mixed 131 | */ 132 | function data_get($target, $key, $default = null) 133 | { 134 | if (is_null($key)) { 135 | return $target; 136 | } 137 | $key = is_array($key) ? $key : explode('.', $key); 138 | while (! is_null($segment = array_shift($key))) { 139 | if ($segment === '*') { 140 | if ($target instanceof Collection) { 141 | $target = $target->all(); 142 | } elseif (! is_array($target)) { 143 | return value($default); 144 | } 145 | $result = []; 146 | foreach ($target as $item) { 147 | $result[] = data_get($item, $key); 148 | } 149 | return in_array('*', $key) ? Arr::collapse($result) : $result; 150 | } 151 | if (Arr::accessible($target) && Arr::exists($target, $segment)) { 152 | $target = $target[$segment]; 153 | } elseif (is_object($target) && isset($target->{$segment})) { 154 | $target = $target->{$segment}; 155 | } else { 156 | return value($default); 157 | } 158 | } 159 | return $target; 160 | } 161 | } 162 | ``` 163 | 164 | 详见 [https://github.com/laravel/framework/blob/master/src/Illuminate/Support/helpers.php#L450](https://github.com/laravel/framework/blob/master/src/Illuminate/Support/helpers.php#L450) 165 | 166 | > 感谢 [@Hachiko](https://laravel-china.org/users/22249) 提供姿势! 167 | 168 | ## 6.除此之外,还可以使用 `Null Object Pattern(空对象模式)`: 169 | [《點燈坊:如何實現 Null Object Pattern ?》](https://oomusou.io/design-pattern/nullobject/) 170 | 171 | 感谢群里大佬 @盒子 和 @Outshine 提供的姿势。:kissing: :kissing: :kissing: 172 | 173 | -------------------------------------------------------------------------------- /do-you-need-a-dependency-injection-container.md: -------------------------------------------------------------------------------- 1 | # Laravel Container (容器) 概念详解 (上) 2 | 3 | >本文翻译自 `Symfony` 作者 Fabien Potencier 的 [《Dependency Injection in general and the implementation of a Dependency Injection Container in PHP》](http://fabien.potencier.org/what-is-dependency-injection.html) 系列文章。 4 | 5 | * [Part 1: What is Dependency Injection?](http://fabien.potencier.org/article/11/what-is-dependency-injection) 6 | * [Part 2: Do you need a Dependency Injection Container?](http://fabien.potencier.org/article/12/do-you-need-a-dependency-injection-container) 7 | * [Part 3: Introduction to the Symfony Service Container](http://fabien.potencier.org/article/13/introduction-to-the-symfony-service-container) 8 | * [Part 4: Symfony Service Container: Using a Builder to create Services](http://fabien.potencier.org/article/14/symfony-service-container-using-a-builder-to-create-services) 9 | * [Part 5: Symfony Service Container: Using XML or YAML to describe Services](http://fabien.potencier.org/article/15/symfony-service-container-using-xml-or-yaml-to-describe-services) 10 | * [Part 6: The Need for Speed](http://fabien.potencier.org/article/16/symfony-service-container-the-need-for-speed) 11 | 12 | >专有名词翻译成中文后会变得不利于理解,后续文章中将改用括号+中文备注的形式。 13 | 14 | 15 | 上文我通过一些示例讲解了 `Dependency Injection` ,本文将接着介绍 `Dependency Injection Containers (容器)` 的概念。 16 | 17 | 首先记住这句话: 18 | 19 | >大多数时候,`Dependency Injection` 并不需要 `Container`。 20 | 21 | 只有当你需要管理一大堆具有很多依赖关系的不同对象时,`Container` 才会非常有用(例如框架中)。 22 | 23 | 上文书,创建 `User` 对象需要先创建 `SessionStorate` 对象。这里的有个瑕疵,创建对象时需要提前知道它所有的依赖项: 24 | ```php 25 | $storage = new SessionStorage('SESSION_ID'); 26 | $user = new User($storage); 27 | ``` 28 | 29 | 以 `Zend Framework` 中 `Zend_Mail` 库发送邮件过程为例: 30 | ```php 31 | $transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', [ 32 | 'auth' => 'login', 33 | 'username' => 'foo', 34 | 'password' => 'bar', 35 | 'ssl' => 'ssl', 36 | 'port' => 465, 37 | ]); 38 | 39 | $mailer = new Zend_Mail(); 40 | $mailer->setDefaultTransport($transport); 41 | ``` 42 | 43 | >请把这个例子看做一个大系统中的一小部分,因为这种简单的例子当然没必要用 `Container` 。 44 | 45 | `Dependency Injection Container` 是一个“知道如何实例化和配置对象”的对象(工厂模式的升华)。为了做到这点,它需要知道构造函数的参数、以及对象之间的关系。 46 | 47 | 下面是一个写死 `Zend_Mail` 的 `Container`: 48 | ```php 49 | class Container 50 | { 51 | public function getMailTransport() 52 | { 53 | return new Zend_Mail_Transport_Smtp('smtp.gmail.com', [ 54 | 'auth' => 'login', 55 | 'username' => 'foo', 56 | 'password' => 'bar', 57 | 'ssl' => 'ssl', 58 | 'port' => 465, 59 | ]); 60 | } 61 | 62 | public function getMailer() 63 | { 64 | $mailer = new Zend_Mail(); 65 | $mailer->setDefaultTransport($this->getMailTransport()); 66 | 67 | return $mailer; 68 | } 69 | } 70 | ``` 71 | 72 | 这个 `Container` 用起来就相当简单了: 73 | ```php 74 | $container = new Container(); 75 | $mailer = $container->getMailer(); 76 | ``` 77 | 78 | 我们只管向 `Container` 要 `mailer` 对象就行,完全不用管 `mailer` 怎么创建。创建 `mailer` 对象的“杂活”是嵌入在 `Container` 中的。 79 | `Container` 通过 `getMailTransport()` 方法,把 `Zend_Mail_Transport_Smtp` 这个依赖自动注入到了 `Zend_Mail` 中。 80 | 81 | 细心的网友可能已经发现,这里的 `Container` 把什么都写死了。我们可以完善一下: 82 | ```php 83 | class Container 84 | { 85 | protected $parameters = array(); 86 | 87 | public function __construct(array $parameters = []) 88 | { 89 | $this->parameters = $parameters; 90 | } 91 | 92 | public function getMailTransport() 93 | { 94 | return new Zend_Mail_Transport_Smtp('smtp.gmail.com', [ 95 | 'auth' => 'login', 96 | 'username' => $this->parameters['mailer.username'], 97 | 'password' => $this->parameters['mailer.password'], 98 | 'ssl' => 'ssl', 99 | 'port' => 465, 100 | ]); 101 | } 102 | 103 | public function getMailer() 104 | { 105 | $mailer = new Zend_Mail(); 106 | $mailer->setDefaultTransport($this->getMailTransport()); 107 | 108 | return $mailer; 109 | } 110 | } 111 | ``` 112 | 113 | 现在就可以随时更改 `username` 和 `password` 了: 114 | ```php 115 | $container = new Container([ 116 | 'mailer.username' => 'foo', 117 | 'mailer.password' => 'bar', 118 | ]); 119 | $mailer = $container->getMailer(); 120 | ``` 121 | 122 | 如果需要更改 `mailer` 类,把类名也当参数传入就行: 123 | ```php 124 | class Container 125 | { 126 | // ... 127 | 128 | public function getMailer() 129 | { 130 | $class = $this->parameters['mailer.class']; 131 | 132 | $mailer = new $class(); 133 | $mailer->setDefaultTransport($this->getMailTransport()); 134 | 135 | return $mailer; 136 | } 137 | } 138 | 139 | $container = new Container([ 140 | 'mailer.username' => 'foo', 141 | 'mailer.password' => 'bar', 142 | 'mailer.class' => 'Zend_Mail', 143 | ]); 144 | $mailer = $container->getMailer(); 145 | ``` 146 | 147 | 如果想每次获取同一个 `mailer` 实例,可以用 `单例模式`: 148 | ```php 149 | class Container 150 | { 151 | static protected $shared = []; 152 | 153 | // ... 154 | 155 | public function getMailer() 156 | { 157 | if (isset(self::$shared['mailer'])) 158 | { 159 | return self::$shared['mailer']; 160 | } 161 | 162 | $class = $this->parameters['mailer.class']; 163 | 164 | $mailer = new $class(); 165 | $mailer->setDefaultTransport($this->getMailTransport()); 166 | 167 | return self::$shared['mailer'] = $mailer; 168 | } 169 | } 170 | ``` 171 | 172 | 这就包含了 `Dependency Injection Containers` 的基本功能: 173 | * `Container` 管理对象实例化到配置的过程 174 | * 对象本身不知道自己是由 `Container` 管理的,对 `Container` 一无所知。 175 | 176 | 这就是为什么 `Container` 能够管理任何 PHP 对象。 对象使用 `DI` 来管理依赖关系非常好,但不是必须的。 177 | 178 | `Container` 很容易实现,但手工维护各种乱七八糟的对象还是很麻烦。下一章我将介绍 `Laravel` 中 `Container` 的实现方式。 179 | 180 | >作者下一章原文中讲的是 `Container` 在 `Symfony 2` 中的实现,我会把它换成 `Laravel`。 181 | 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /valet-phpstorm-xdebug-phpunit-qcachegrind(mac).md: -------------------------------------------------------------------------------- 1 | # Laravel 配置 valet, PHPStorm, xdebug, PHPUnit, qcachegrind(mac)环境 2 | 3 | * 可使用 `command+,` 快捷键打开 PHPStorm Preferences页面 4 | * 在 Preferences 页面可以直接搜索要配置的项目 5 | * 可使用 `control+alt+d` 快捷键打开 PHPStorm Debug 设置 6 | 7 | ## 一、配置单 php 文件的 Debug 8 | ``` 9 | 1、PHPStorm: Preferences > Languages & Frameworks > PHP 10 | > PHP language level: 7.2 11 | > CLI interpreter: PHP 7.2.4 12 | 13 | 2、PHPStorm: Run > Edit Configurations.. > + > PHP Script, 添加 php 文件。 14 | 或直接在要调试的 php 文件中,`control+alt+d` > xxx.php(PHP Script) 15 | ``` 16 | 17 | ## 二、配置 Laravel 的 Debug 18 | 1、修改 xdebug.ini 19 | ``` 20 | xdebug.remote_autostart=1 21 | xdebug.default_enable=1 22 | xdebug.remote_port=9001 ;xdebug的端口,默认为9000,我习惯用9001 23 | xdebug.remote_host=127.0.0.1 24 | xdebug.remote_connect_back=1 25 | xdebug.remote_enable=1 26 | xdebug.idekey=PHPSTORM 27 | ``` 28 | 29 | 如需开启性能分析,还需添加下列信息,开启后会严重拖慢 laravel 打开速度 30 | ``` 31 | zend_debugger.allow_hosts=127.0.0.1 32 | zend_debugger.expose_remotely=always 33 | zend_debugger.httpd_uid=-1 34 | xdebug.trace_format = 2 35 | xdebug.auto_trace = on 36 | xdebug.auto_profile = on ;打开性能分析 37 | xdebug.collect_params = on 38 | xdebug.collect_return = on 39 | xdebug.profiler_enable = 1 40 | xdebug.trace_output_dir = /Users/seekerliu/Documents/xdebug/trace ;trace生成的文件目录, 需配置成自己的 41 | xdebug.profiler_output_dir=/Users/seekerliu/Documents/xdebug ;性能分析生成的文件目录, 需配置成自己的 42 | xdebug.trace_output_name = trace.%c.%p 43 | xdebug.show_exception_trace = On ;开启异常跟踪 44 | xdebug.show_local_vars = 0 45 | 46 | xdebug.profiler_output_name=cachegrind.out.%s 47 | xdebug.dump.GET = * 48 | xdebug.dump.POST = * 49 | xdebug.dump.COOKIE = * 50 | xdebug.dump.SESSION = * 51 | xdebug.var_display_max_data = 4056 52 | xdebug.var_display_max_depth = 5 53 | ``` 54 | 55 | 2、 重启 php-fpm 56 | ``` 57 | ➜ valet restart 58 | 或 找到 php-fpm master progress 进程 pid (例如 2345) 后 59 | ➜ ps aux | grep php-fpm 60 | ➜ kill -USR2 2345 61 | ``` 62 | 63 | 3、配置 PhpStorm 64 | ``` 65 | 1) PHPStorm: Preferences > DBGp Proxy > IDE key: PHPSTROM > PORT:9001 66 | 67 | 2) Languages & Frameworks > PHP > Debug > Xdebug > Debug port: 9001 68 | 69 | 3) PHPStorm: Run > Edit Configurations.. > + > PHP Web Page 70 | > 填写 Name, Configuration: 选择 Server, 填写 Start URL。 71 | > 添加 Server 时,填写 Name, Host, Port, Debugger:Xdebug。 72 | > 设置 path mappings: √ Use path mappings, 73 | > 编辑 File/Directory(本地项目文件) 与 Absolute path on the server(服务器上的文件绝对路径) 的映射关系,例如: 74 | File/Directory: /Users/seekerliu/Documents/sxmsV2 75 | Absolute path on the server: /Users/seekerliu/Documents/sxmsV2 76 | ``` 77 | 78 | 4、对于 valet 项目,还需配置 valet 路径至 include path: 79 | ``` 80 | PHPStorm: Project Window(左边栏项目资源目录,可使用 command+1 快捷键打开) 81 | > Extrenal Libraries > Config PHP Include Paths 82 | > Add 83 | > /Users/seekerliu/.composer/vendor/laravel/valet (换成你自己的) 84 | ``` 85 | 86 | 5、监听来自页面的 xdebug 请求,即浏览器中打开网页后自动跳转到 PHPStorm 的断点上: 87 | 88 | 首先在 PHPStorm 中开启监听: 89 | ``` 90 | Run > Start Listening for PHP Debug Connections 91 | ``` 92 | 93 | 然后再 Web 端开启 XDEBUG_SESSION: 94 | 95 | 方法一: 96 | 97 | 在请求 URL 中添加 `XDEBUG_SESSION_START` 参数(可以为任意值, 例如:`XDEBUG_SESSION_START=1`), 98 | 99 | 代码中设置断点,然后访问URL: 100 | ``` 101 | http://website.test/?XDEBUG_SESSION_START=1 102 | ``` 103 | 104 | 即可进入设置过的断点。 105 | 106 | 方法二: 107 | 108 | `cookie` 中设置 `XDEBUG_SESSION`。使用方法一就会自动设置此 `cookie` 值,有效期 1小时。 109 | 110 | Laravel 项目中可以如下配置: 111 | ``` 112 | \App\Providers\AppServiceProvider.class.php 113 | 114 | public function register() 115 | { 116 | // 各类开发环境中使用的配置 117 | if ($this->app->environment() == 'local') { 118 | // 注册各类开发时用到的 package 119 | // $this->app->register('Seekerliu\ViewComposerGenerator\ServiceProvider'); 120 | 121 | // 开启 XDEBUG_SESSION 122 | // PHPStorm 中需执行:Run > Start Listening for PHP Debug Connections 123 | if(!request()->hasCookie('XDEBUG_SESSION')) { 124 | $cookie = \Cookie::forever('XDEBUG_SESSION', 1); 125 | \Cookie::queue($cookie); 126 | } 127 | } 128 | } 129 | ``` 130 | 131 | ## 三、配置 PHP Unit 132 | 1、安装phpunit 133 | ``` 134 | ➜ wget https://phar.phpunit.de/phpunit.phar 135 | ➜ chmod +x phpunit.phar 136 | ➜ php phpunit.phar --version 137 | >> PHPUnit 6.3.0 by Sebastian Bergmann and contributors. 138 | 139 | ➜ sudo mv phpunit.phar /usr/local/bin/phpunit 140 | ➜ phpunit --version 141 | >> PHPUnit 6.3.0 by Sebastian Bergmann and contributors. 142 | ``` 143 | 144 | 2、配置 `~/.bash_profile` 145 | ``` 146 | # PHP Unit 147 | t() { 148 | if [ -f vendor/bin/phpunit ]; then 149 | vendor/bin/phpunit "$@" 150 | else 151 | phpunit "$@" 152 | fi 153 | } 154 | ``` 155 | 156 | 这样在 Terminal 中可以使用 t 直接运行。 157 | ``` 158 | ➜ t --stderr 159 | ``` 160 | 161 | 3、配置 PHPStorm 中 phpunit 路径 162 | ``` 163 | PHPStorm: Preferences > Test Frameworks > + > PHP Unit Local 164 | > Path to phpunit.phar: /usr/local/bin/phpunit 165 | > Default configuration file: /Laravel项目目录/phpunit.xml 166 | > Default bootstrap file: /Laravel项目目录/vendor/autoload.php 167 | ``` 168 | 169 | 这样就可以在 PHPStorm 中对 Laravel Test 文件使用 `control+alt+d` 执行 PHPUnit 了。 170 | 171 | 四、安装qcachegrind(mac下的图形化性能分析软件) 172 | ``` 173 | ➜ brew install graphviz 174 | ➜ brew install qcachegrind 175 | ➜ qcachegrind 176 | ``` 177 | 还可以把 qcachegrind 添加到应用程序中: 178 | ``` 179 | ➜ cp -r /usr/local/Cellar/qcachegrind/17.04.1(版本号)/qcachegrind.app ~/Applications 180 | ``` 181 | 解决 Call Graph 无法使用的问题: 182 | ``` 183 | sudo ln -s /usr/local/bin/dot /usr/bin/dot 184 | ``` 185 | 186 | 以上命令会报错,因为mac系统有完整性保护,mac系统一些重要的系统目录禁止修改,即使是切换到root也不行。 187 | 188 | 这时,可暂时修改: 189 | ``` 190 | 1、关机后,开机的同时或者在听到开始音的同时,按住Command+R键 191 | 2、打开 Terminal 窗口输入命令:csrutil disable 即可;可以查看状态:csrutil status 192 | 3、重启电脑后再执行命令:sudo ln -s /usr/local/bin/dot /usr/bin/dot 193 | 4、重复 1 步骤,2 步骤命令替换为:csrutil enable 恢复默认的mac权限 194 | ``` 195 | -------------------------------------------------------------------------------- /what-is-dependency-injection.md: -------------------------------------------------------------------------------- 1 | # Laravel Dependency Injection (依赖注入) 概念详解 2 | 3 | >本文翻译自 `Symfony` 作者 Fabien Potencier 的 [《Dependency Injection in general and the implementation of a Dependency Injection Container in PHP》](http://fabien.potencier.org/what-is-dependency-injection.html) 系列文章。 4 | 5 | * [Part 1: What is Dependency Injection?](http://fabien.potencier.org/article/11/what-is-dependency-injection) 6 | * [Part 2: Do you need a Dependency Injection Container?](http://fabien.potencier.org/article/12/do-you-need-a-dependency-injection-container) 7 | * [Part 3: Introduction to the Symfony Service Container](http://fabien.potencier.org/article/13/introduction-to-the-symfony-service-container) 8 | * [Part 4: Symfony Service Container: Using a Builder to create Services](http://fabien.potencier.org/article/14/symfony-service-container-using-a-builder-to-create-services) 9 | * [Part 5: Symfony Service Container: Using XML or YAML to describe Services](http://fabien.potencier.org/article/15/symfony-service-container-using-xml-or-yaml-to-describe-services) 10 | * [Part 6: The Need for Speed](http://fabien.potencier.org/article/16/symfony-service-container-the-need-for-speed) 11 | 12 | 13 | `依赖注入` 设计模式非常简单,但又很难解释清楚。造成这个现象的主要原因是,别的介绍 `依赖注入` 的文章里太多废话,让人混淆。下面我将通过一些更适合 PHP 的例子来讲解它。 14 | 15 | HTTP 协议是无状态的,我们的 Web 应用程序如果需要在请求之间存储用户信息,可以通过 `COOKIE` 或 `SESSION` : 16 | 17 | ```php 18 | $_SESSION['language'] = 'fr'; 19 | ``` 20 | 21 | 上述代码中,我们将 `language` 存储在全局变量 `$_SESSION` 中,因此可以这样获取它: 22 | 23 | ```php 24 | $user_language = $_SESSION['language']; 25 | ``` 26 | 27 | 只有在 `OOP` 开发时中才会遇到 `依赖注入` ,因此假设我们有一个封装 `SESSION` 的 `SessionStorage` 类: 28 | 29 | ```php 30 | class SessionStorage 31 | { 32 | function __construct($cookieName='PHPSESSID') 33 | { 34 | session_name($cookieName); 35 | session_start(); 36 | } 37 | 38 | function set($key, $value) 39 | { 40 | $_SESSION[$key] = $value; 41 | } 42 | 43 | function get($key) 44 | { 45 | return $_SESSION[$key]; 46 | } 47 | 48 | // ... 49 | } 50 | ``` 51 | 52 | 以及一个更高层的 `User` 类: 53 | 54 | ```php 55 | class User 56 | { 57 | protected $storage; 58 | 59 | function __construct() 60 | { 61 | $this->storage = new SessionStorage(); 62 | } 63 | 64 | function setLanguage($language) 65 | { 66 | $this->storage->set('language', $language); 67 | } 68 | 69 | function getLanguage() 70 | { 71 | return $this->storage->get('language'); 72 | } 73 | 74 | // ... 75 | } 76 | ``` 77 | 78 | 这两个类很简单,并且用起来也很方便: 79 | 80 | ```php 81 | $user = new User(); 82 | $user->setLanguage('fr'); 83 | $user_language = $user->getLanguage(); 84 | ``` 85 | 86 | 这种方式看起来很完美,但是并不够灵活。比如:现在想修改会话的 `COOKIE` 名称(默认为 `PHPSESSID`) ,怎么办?这时有一大堆办法: 87 | 88 | * 把会话的 `COOKIE` 名称写死在 `User` 类中 `SessionStorage` 的构造函数中 (Hardcode): 89 | 90 | ```php 91 | class User 92 | { 93 | function __construct() 94 | { 95 | $this->storage = new SessionStorage('SESSION_ID'); 96 | } 97 | 98 | // ... 99 | } 100 | ``` 101 | 102 | * 或者在 `User` 类外面定义一个常量: 103 | 104 | ```php 105 | class User 106 | { 107 | function __construct() 108 | { 109 | $this->storage = new SessionStorage(SESSION_COOKIE_NAME); 110 | } 111 | 112 | // ... 113 | } 114 | 115 | define('SESSION_COOKIE_NAME', 'SESSION_ID'); 116 | ``` 117 | 118 | * 或者把会话的 `COOKIE` 名称作为 `User` 类构造函数的一个参数传进去: 119 | 120 | ```php 121 | class User 122 | { 123 | function __construct($cookieName) 124 | { 125 | $this->storage = new SessionStorage($cookieName); 126 | } 127 | 128 | // ... 129 | } 130 | 131 | $user = new User('SESSION_ID'); 132 | ``` 133 | 134 | * 或者给 `SessionStorage` 类加个选项数组: 135 | 136 | ```php 137 | class User 138 | { 139 | function __construct($storageOptions) 140 | { 141 | $this->storage = new SessionStorage($storageOptions['cookie_name']); 142 | } 143 | 144 | // ... 145 | } 146 | 147 | $user = new User(['cookie_name' => 'SESSION_ID']); 148 | ``` 149 | 150 | 上述方法都很糟糕: 151 | * 把会话的 `COOKIE` 名称写死的话,每次想再改名,都要修改 `User` 类 152 | * 使用常量的话,`User` 类的变化将取决于设置的常量 153 | * 使用参数或者选项数组看起来很灵活,但它把与 `User` 本身无关的东西掺杂在了构造函数中 154 | 155 | ### 通过构造函数,把一个外部的 `SessionStorage` 实例"注入"进 `User` 实例内部,而不是在 `User` 实例内部创建 `SessionStorage` 实例,就是 `依赖注入`。 156 | 157 | ```php 158 | class User 159 | { 160 | function __construct($storage) 161 | { 162 | $this->storage = $storage; 163 | } 164 | 165 | // ... 166 | } 167 | ``` 168 | 169 | 很清爽吧!只需先创建 `SessionStorage` 实例,再创建 `User` 实例: 170 | 171 | ```php 172 | $storage = new SessionStorage('SESSION_ID'); 173 | $user = new User($storage); 174 | ``` 175 | 176 | 用这个方法,配置 `SessionStorage` 很简单,给 `User` 替换 `$storage` 类型也很简单,都不需要去修改 `User` 类。这就实现了解耦。 177 | 178 | `依赖注入` 并不限于构造函数: 179 | 180 | * Constructor Injection: 181 | ```php 182 | class User 183 | { 184 | function __construct($storage) 185 | { 186 | $this->storage = $storage; 187 | } 188 | 189 | // ... 190 | } 191 | ``` 192 | 193 | * Setter Injection: 194 | ```php 195 | class User 196 | { 197 | function setSessionStorage($storage) 198 | { 199 | $this->storage = $storage; 200 | } 201 | 202 | // ... 203 | } 204 | ``` 205 | 206 | * Property Injection: 207 | ```php 208 | class User 209 | { 210 | public $sessionStorage; 211 | } 212 | 213 | $user->sessionStorage = $storage; 214 | ``` 215 | 216 | 作为经验, `Constructor 注入` 最适合必须的依赖关系,比如示例中的情况; `Setter 注入` 最适合可选依赖关系,比如缓存一个对象实例。 217 | 218 | 现在,大多数现代 PHP 框架都大量使用依赖注入来提供一组 `去耦` 但 `粘合` 的组件: 219 | 220 | ```php 221 | // symfony: A constructor injection example 222 | $dispatcher = new sfEventDispatcher(); 223 | $storage = new sfMySQLSessionStorage([ 224 | 'database' => 'session', 225 | 'db_table' => 'session', 226 | ]); 227 | $user = new sfUser($dispatcher, $storage, ['default_culture' => 'en']); 228 | 229 | // Zend Framework: A setter injection example 230 | $transport = new Zend_Mail_Transport_Smtp('smtp.gmail.com', [ 231 | 'auth' => 'login', 232 | 'username' => 'foo', 233 | 'password' => 'bar', 234 | 'ssl' => 'ssl', 235 | 'port' => 465, 236 | ]); 237 | 238 | $mailer = new Zend_Mail(); 239 | $mailer->setDefaultTransport($transport); 240 | ``` 241 | 242 | 如果你有兴趣了解更多有关 `依赖注入` 的信息,我强烈建议你阅读 [Martin Fowler 的介绍](http://www.martinfowler.com/articles/injection.html) 或 [Jeff Moore 的 PPT](http://www.procata.com/talks/phptek-may2007-dependency.pdf)。你还可以看看我去年关于 `依赖注入` 的 [PPT](http://fabien.potencier.org/talk/19/decouple-your-code-for-reusability-ipc-2008),其中我详细了介绍本文中所讨论的示例。 243 | 244 | 希望本文能让你更好的理解 `依赖注入`,在本系列的下一部分中,我将讨论 `依赖注入容器` (Dependency Injection Containers)。 245 | 246 | 247 | -------------------------------------------------------------------------------- /using-eloquent.md: -------------------------------------------------------------------------------- 1 | # Laravel Eloquent 用法 2 | 3 | >本文翻译自 [《Laravel - My first framework》](https://leanpub.com/laravel-first-framework/) 4 | 5 | # Eloquent 简介 6 | Eloquent 是 Laravel 提供的 ORM 实现。它将数据库抽象为对象,这些对象 (也称为「模型」) 被创建、更新或删除时,Eloquent 会在数据库中进行相应的更改。 7 | 8 | 下图显示了一个 Eloquent 的简单示例,通过创建一个新的 PHP 对象并为其属性分配值,在表 `users` 中插入一行: 9 | 10 | ![Using Eloquent to create a new record in the database](./figures/using-eloquent-1.png) 11 | 12 | Eloquent 通过创建数据之间的关系,简化了复杂数据库结构的工作。它内置了各种关联关系: 13 | 14 | * 将记录从一个表链接到另一个表的另一个记录(一对一关联) 15 | * 将记录从一个表链接到不同表中的多个记录(一对多关联) 16 | * 将一个表中的一些记录链接到另一个表中的记录数(多对多关联) 17 | * 一个表的记录可以动态链接到许多其他表(多态关联) 18 | * 更多 19 | 20 | 数据库中数据实体之间的这些内置关系帮助开发人员轻松的处理数据。 例如,下图显示了一个博客程序中用户、贴子、评论的数据实体之间关系: 21 | 22 | ![Example of relationships between the data in a blog](./figures/using-eloquent-2.png) 23 | 24 | Eloquent 使用「模型」的概念来表示数据库中的数据,接下来我们看一下它的概念。 25 | 26 | ## Model - 模型 27 | 像其他 ORM 实现一样,Eloquent 的「模型」是一个特殊的类,它表示数据库中的单个表。 使用「模型」可以以对象的方式来处理数据库中的数据。 28 | 29 | 当这些类 (模型) 使用 Eloquent 的功能时,Laravel 会自动进行数据库的 SQL 查询并返回结果: 30 | 31 | ![Interaction between Eloquent models and the database](./figures/using-eloquent-3.png) 32 | 33 | >将「模型」视为应用程序使用的对象或实体。 例如,如果应用程序是一个在线商店,那么它所使用的对象就是产品,订单,产品类别,用户等。如果应用程序是 SNS - 对象将是状态,照片,视频, 位置,用户之间的关系等。以这种方式表示实体可以大大简化数据库的结构,并使您能够在应用程序中使用 Eloquent。 34 | 35 | ## 定义模型时的约定 36 | 37 | 通过 Eloquent 的模型处理数据库表时,Laravel 主要有三个主要假设: 38 | 39 | * 表的主键是一个名为 `id` 的无符号自动递增整型。 40 | * 该表具有 `datetime` 类型的 `created_at` 和 `updated_at` 字段以存储创建时间、更新时间的时间戳。 41 | * 如果模型类为 `User.php` ,则表名应该称为 `users`,即以模型类的蛇形、复数形式名称来作为数据表的名称 42 | 43 | 例如: 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
模型名表名
Userusers
Postposts
Commentcomments
Personpeople
Indexindices
PostCategorypost_categories
74 | 75 | >定义模型名的时候,最好不要使用 Laravel 中已经用到的一些类名,例如:App, Auth, Cache, Config, Controller, Cookie, Crypt, Lang, Route, Event, Log, Schema, File, Mail, Session, Form, Password, View, Hash, Queue, Input, Request 等。 76 | 77 | # 创建并使用 Eloquent 78 | 每个模型类对应了数据库中的一个表,例如有一个博客程序中的贴子表: 79 | 80 | ![Defining a “Post” model abstracts the “posts” table as a PHP object](./figures/using-eloquent-4.png) 81 | 82 | 我们这样定义它: 83 | ```php 84 | // app/Post.php 85 | namespace App; 86 | 87 | use Illuminate\Database\Eloquent\Model; 88 | 89 | class Post extends Model {} 90 | ``` 91 | 92 | 接下来就可以通过它来进行数据库的增、删、改、查各种操作了。 93 | 94 | ## 使用模型 95 | Eloquent 提供了许多方法来操作数据库。 基本的方法如下: 96 | all() - 取回所有记录 97 | find($id) - 取回一条记录 98 | first() - 取回第一条记录 99 | save() - 将当前模型实例作为新记录添加到数据库中 100 | create($data) - 通过数据数组创建一条新记录 101 | delete() - 删除当前记录 102 | destroy($id) - 删除主键值为 $id 的记录 103 | 104 | 与 Query Builder 一样,Eloquent 的大部分方法可以进行链式操作。它们还可以与 Query Builder 交替使用。 105 | 106 | 假设我们有下面两个表: 107 | ![Table structure for posts and comments tables](./figures/using-eloquent-5.png) 108 | 109 | Eloquent 模型的属性表示模型对应的表的列,模型的属性表示列的值。例如,如果您想从 `posts` 表中检索 ID 为 1 的标题和内容,您可以访问 Post 模型的 `title` 和 `body` 属性,如下面的图所示: 110 | 111 | ![Model’s attributes correspond to the table’s columns](./figures/using-eloquent-6.png) 112 | 113 | ## Inserting records - 插入记录 114 | ```php 115 | try 116 | Route::get('posts/new', function(){ 117 | // Create a new instance of the Post model 118 | $post = new App\Post; 119 | // Assign values to model’s attributes 120 | $post->title = "My first post"; 121 | // Assign values to model’s attributes 122 | $post->body = "This post is created with Eloquent"; 123 | // Insert the record in the DB 124 | $post->save(); 125 | // Display the new record containing the blog post 126 | return $post; 127 | }); 128 | ``` 129 | 130 | Laravel 背后的执行过程如下: 131 | 132 | ![Creating a new record with Eloquent](./figures/using-eloquent-7.png) 133 | 134 | 这时就可以发现 Eloquent 与 Query Builder 的区别了: 135 | 136 | * 通过模型不需要指定记录将插入哪个表。 137 | * 通过使用对象而不是运行任何数据库操作来完成新记录的创建。 138 | 139 | ## Retrieving records - 检索记录 140 | Query Builder 中有 `find($id)` 和 `get()` 两个方法, 141 | Eloquent 另外提供了 `all()` 和 `first()` 两个方法。 142 | 143 | ### 使用 Query Builder 的 find() 取回单条记录 144 | ```php 145 | // Retrieve a record with primary key (id) equal to “2” 146 | $comment = Comment::find(2); 147 | 148 | // If there is a comment with ID “2”, The $comment variable will contain: 149 | /* 150 | object(Comment)#137 (20) { 151 | ["attributes":protected]=> array(5) { 152 | ["id"]=> 153 | string(1) "2" 154 | ["post_id"]=> 155 | string(1) "1" 156 | ["body"]=> 157 | string(17) "My second comment" 158 | ... 159 | } 160 | ... 161 | } 162 | */ 163 | ``` 164 | 165 | 这个相当于: 166 | ```php 167 | Comment::where('id',$id)->get(); 168 | ``` 169 | 170 | ### 使用 Eloquent 的 first() 取回单条记录 171 | `first()` 用来检索第一条记录: 172 | ```php 173 | Route::get('comments/first', function() { 174 | $comment = Comment::where('body', 'like', '%comment%')->first(); 175 | }); 176 | ``` 177 | 178 | ### 使用 all() 取回所有记录 179 | ```php 180 | Route::get('comments', function() { 181 | $comments = Comment::all(); 182 | }); 183 | ``` 184 | 185 | >注意:使用 all() 时,不管它前面有没有其它限定性的操作,都将返回全部记录。 186 | 187 | ### 使用 get() 取回多条记录 188 | `get()` 有以下功能: 189 | 190 | * 检索模型对应表的所有记录 191 | * 对查询结果进行过滤、分组、排序 192 | * 检索特定记录 193 | 194 | 取回所有记录: 195 | ```php 196 | Route::get('comments', function() { 197 | $comments = Comment::get(); 198 | }); 199 | ``` 200 | 201 | 过滤数据: 202 | ```php 203 | Route::get('published', function() { 204 | $posts = Post::where('title', 'like', '%Laravel%')->get(); 205 | }); 206 | ``` 207 | 208 | 指定检索的列: 209 | ```php 210 | Route::get('posts', function() { 211 | $posts = Post::get(['title','body']); 212 | }); 213 | 214 | /* 215 | object(Illuminate\Database\Eloquent\Collection)#149 (1) { 216 | ["items":protected]=> array(1) { 217 | [0]=> 218 | object(Post)#137 (20) { 219 | ... ["attributes":protected]=> array(2) { 220 | ["title"]=> 221 | string(13) "My first post" 222 | ["body"]=> 223 | string(34) "This post is created with Eloquent" 224 | } 225 | ... 226 | } 227 | } 228 | 229 | */ 230 | ``` 231 | 232 | ## Updating records - 更新记录 233 | 可以使用如下步骤来更新记录: 234 | 235 | 1. 通过在模型上使用 `find()`, `get()` 或 `first()` 来检索属于模型的记录 236 | 2. 为模型的属性分配新值 237 | 3. 在模型上调用 `save()` 来保存数据库中的更改 238 | 239 | ```php 240 | Route::get('posts/update', function() { 241 | $post = Post::find(1); 242 | 243 | $post->title = "Updated title"; 244 | 245 | $post->save(); 246 | }); 247 | ``` 248 | 249 | >注意:Eloquent 不允许一次性更新多条记录,如有需要可以使用 Query Builder 的 update() 方法。 250 | 251 | ## Deleting records - 删除记录 252 | 可以使用 `delete()` 删除当前模型实例的记录: 253 | ```php 254 | Route::get('posts/delete', function() { 255 | $post = Post::find(1); 256 | 257 | $post->delete(); 258 | }); 259 | ``` 260 | 261 | 也可以使用 `destory($id)` 删除指定 `$id` 的记录: 262 | ```php 263 | Route::get('posts/destory', function() { 264 | $post = Post::destory(1); 265 | }); 266 | ``` 267 | 268 | 有关 Eloquent 的更多知识请参考官方文档: 269 | 270 | * [Eloquent 入门](https://laravel.com/docs/5.5/eloquent) 271 | * [模型关联](https://laravel.com/docs/5.5/eloquent-relationships) 272 | -------------------------------------------------------------------------------- /the-concept-of-laravel-service-provider.md: -------------------------------------------------------------------------------- 1 | # Laravel Service Provider 概念详解 2 | 3 | 我们知道, `Container` 有很多种 「绑定」 的姿势,比如 `bind()` , `extend()` , `singleton()` , `instance()` 等等,那么 `Laravel` 中怎样「注册」这些「绑定」呢?那就是 `Service Provider`。 4 | 5 | 先看下 `Laravel` [文档](https://laravel.com/docs/5.5/providers)中这句话: 6 | 7 | > Service providers are the central place of all Laravel application bootstrapping. Your own application, as well as all of Laravel's core services are bootstrapped via service providers. 8 | 9 | `Service Providers (服务提供者)` 是 `Laravel` 「引导」过程的核心。这个「引导」过程可以理解成「电脑从按下开机按钮到完全进入桌面」这段时间系统干的事。 10 | 11 | ## 概览 12 | `Service Provider` 有两个重要的方法: 13 | ```php 14 | namespace App\Providers; 15 | use Illuminate\Support\ServiceProvider; 16 | 17 | class AppServiceProvider extends ServiceProvider 18 | { 19 | /** 20 | * 注册服务. 21 | * 22 | * @return void 23 | */ 24 | public function register() 25 | { 26 | // 27 | } 28 | 29 | /** 30 | * 引导服务。 31 | * 32 | * @return void 33 | */ 34 | public function boot() 35 | { 36 | // 37 | } 38 | } 39 | ``` 40 | 41 | `Laravel` 在「引导」过程中干了两件重要的事: 42 | 1. 通过 `Service Provider` 的 `register()` 方法注册「绑定」 43 | 2. 所有 `Servier Provider` 的 `register()` 都执行完之后,再通过它们 `boot()` 方法,干一些别的事。 44 | 45 | ## 过程分析 46 | 这个「先后顺序」可以在 `Laravel` 的启动过程中找到: 47 | 1. 首先,生成核心 `Container` : `$app` (实例化过程中还注册了一大堆基本的「绑定]) 48 | ```php 49 | // public/index.php 50 | /* 51 | |-------------------------------------------------------------------------- 52 | | Turn On The Lights 53 | |-------------------------------------------------------------------------- 54 | | 55 | */ 56 | $app = require_once __DIR__.'/../bootstrap/app.php'; 57 | ``` 58 | 59 | ```php 60 | // bootstrap/app.php 61 | $app = new Illuminate\Foundation\Application( 62 | realpath(__DIR__.'/../') 63 | ); 64 | ``` 65 | 66 | 2. 接下来注册 `Http\Kernel` , `Console\Kernel` , `Debug\ExecptionHandler` 三个「单例」绑定: 67 | ```php 68 | // bootstrap/app.php 69 | $app->singleton( 70 | Illuminate\Contracts\Http\Kernel::class, 71 | App\Http\Kernel::class 72 | ); 73 | 74 | $app->singleton( 75 | Illuminate\Contracts\Console\Kernel::class, 76 | App\Console\Kernel::class 77 | ); 78 | 79 | $app->singleton( 80 | Illuminate\Contracts\Debug\ExceptionHandler::class, 81 | App\Exceptions\Handler::class 82 | ); 83 | ``` 84 | 3. 然后「启动」应用: 85 | ```php 86 | // public/index.php 87 | 88 | /* 89 | |-------------------------------------------------------------------------- 90 | | Run The Application 91 | |-------------------------------------------------------------------------- 92 | | 93 | */ 94 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 95 | 96 | $response = $kernel->handle( 97 | $request = Illuminate\Http\Request::capture() 98 | ); 99 | ``` 100 | 4. 由于以前的「绑定」,`$kernel` 获取的其实是 `App\Http\Kernel` 类的实例,`App\Http\Kernel` 类又继承了 `Illuminate\Foundation\Http\Kernel` 类。 101 | 其 `handle()` 方法执行了 `sendRequestThroughRouter()` 方法: 102 | ```php 103 | // Illuminate\Foundation\Http 104 | class Kernel implements KernelContract 105 | { 106 | public function handle($request) 107 | try { 108 | //... 109 | $response = $this->sendRequestThroughRouter($request); 110 | 111 | } catch (Exception $e) { 112 | //... 113 | } 114 | } 115 | } 116 | ``` 117 | 5. 这个 `sendRequestThroughRouter()` 方法执行了一系列 `bootstrappers (引导器)`: 118 | ```php 119 | // Illuminate\Foundation\Http 120 | class Kernel implements KernelContract 121 | { 122 | protected function sendRequestThroughRouter($request) 123 | { 124 | //... 125 | 126 | // 按顺序执行每个 bootstrapper 127 | $this->bootstrap(); 128 | 129 | //... 130 | } 131 | } 132 | ``` 133 | 6. `bootstrap()` 方法中又调用了 `Illuminate\Foundation\Application` 类的 `bootstrapWith()` 方法: 134 | ```php 135 | // Illuminate\Foundation\Http 136 | class Kernel implements KernelContract 137 | { 138 | public function bootstrap() 139 | { 140 | if (! $this->app->hasBeenBootstrapped()) { 141 | $this->app->bootstrapWith($this->bootstrappers()); 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | ```php 148 | // Illuminate\Foundation\Http\Kernel 149 | class Kernel implements KernelContract 150 | { 151 | protected $bootstrappers = [ 152 | //... 153 | 154 | \Illuminate\Foundation\Bootstrap\RegisterProviders::class, // 注册 Providers 155 | \Illuminate\Foundation\Bootstrap\BootProviders::class, // 引导 Providers 156 | ]; 157 | 158 | protected function bootstrappers() 159 | { 160 | return $this->bootstrappers; 161 | } 162 | } 163 | ``` 164 | 165 | 这里就能看出来 `Service Provider` 中 `register` 和 `boot` 的「先后顺序了」。 166 | 167 | > 后续的执行过程暂时先不介绍了。 168 | 169 | 这就意味着,在 `Service Provider` `boot` 之前,已经把注册好了所有服务的「绑定」。因此, 在 `boot()` 方法中可以使用任何已注册的服务。 170 | 例如: 171 | ```php 172 | { 173 | namespace App\Providers; 174 | use Illuminate\Support\ServiceProvider; 175 | 176 | class ComposerServiceProvider extends ServiceProvider 177 | { 178 | public function boot() 179 | { 180 | // 这里使用 make() 方法,可以更直观 181 | $this->app->make('view')->composer('view', function () { 182 | // 183 | }); 184 | 185 | // 或者使用 view() 辅助函数 186 | view()->composer('view', function () { 187 | // 188 | }); 189 | } 190 | } 191 | ``` 192 | 193 | 如果了解 `Container` 「绑定」 和 `make` 的概念,应该很容易理解上面的过程。 194 | 195 | 剩下的无非就是: 196 | ### 创建 `Service Provier` 197 | ```bash 198 | php artisan make:provider MyServiceProvider 199 | ``` 200 | ### [注册绑定](https://laravel.com/docs/5.5/providers#writing-service-providers) 201 | ```php 202 | namespace App\Providers; 203 | use Illuminate\Support\ServiceProvider; 204 | 205 | class MyServiceProvider extends ServiceProvider 206 | { 207 | public function register() 208 | { 209 | $this->app->bind(MyInterface::class, MyClass::class); 210 | } 211 | } 212 | ``` 213 | ### [引导方法](https://laravel.com/docs/5.5/providers#writing-service-providers) 214 | ```php 215 | namespace App\Providers; 216 | use Illuminate\Support\ServiceProvider; 217 | use Illuminate\Contracts\Routing\ResponseFactory; 218 | 219 | class MyServiceProvider extends ServiceProvider 220 | { 221 | 222 | /** 223 | * boot() 方法中可以使用依赖注入。 224 | * 这是因为在 Illuminate\Foundation\Application 类中, 225 | * 通过 bootProvider() 方法中的 $this->call([$provider, 'boot']) 226 | * 来执行 Service Provider 的 boot() 方法 227 | * Container 的 call() 方法作用,可以参考上一篇文章 228 | */ 229 | public function boot(ResponseFactory $response) 230 | { 231 | $response->macro('caps', function ($value) { 232 | // 233 | }); 234 | } 235 | } 236 | ``` 237 | 238 | ### 在 `config/app.php` 中注册 `Service Provider` 239 | ```php 240 | 'providers' => [ 241 | 242 | /* 243 | * Laravel Framework Service Providers... 244 | */ 245 | 246 | /* 247 | * Package Service Providers... 248 | */ 249 | 250 | /* 251 | * Application Service Providers... 252 | */ 253 | App\Providers\MyServiceProvider::class, 254 | ], 255 | ``` 256 | 257 | > 注意:`Laravel 5.5` 之后有 [package discovery](https://laravel.com/docs/5.5/packages#package-discovery) 功能的 `package` 不需要在 `config/app.php` 中注册。 258 | 259 | ### [延时加载](https://laravel.com/docs/5.5/providers#deferred-providers) 260 | 按需加载,只有当 `Container` 「make」 `ServiceProvider` 类 `providers()`方法中返回的值时,才会加载此 `ServiceProvider`。 261 | 例如: 262 | ```php 263 | namespace Illuminate\Hashing; 264 | 265 | use Illuminate\Support\ServiceProvider; 266 | 267 | class HashServiceProvider extends ServiceProvider 268 | { 269 | /** 270 | * 如果延时加载,$defer 必须设置为 true 。 271 | * 272 | * @var bool 273 | */ 274 | protected $defer = true; 275 | 276 | /** 277 | * Register the service provider. 278 | * 279 | * @return void 280 | */ 281 | public function register() 282 | { 283 | $this->app->singleton('hash', function () { 284 | return new BcryptHasher; 285 | }); 286 | } 287 | 288 | /** 289 | * Get the services provided by the provider. 290 | * 291 | * @return array 292 | */ 293 | public function provides() 294 | { 295 | return ['hash']; 296 | } 297 | } 298 | ``` 299 | 当我们「make」时,才会注册 `HashServiceProvider`,即执行它的 `register()` 方法,进行 `hash` 的绑定: 300 | ```php 301 | class MyController extends Controller 302 | { 303 | public function test() 304 | { 305 | // 此时才会注册 `HashServiceProvider` 306 | $hash = $this->app->make('hash'); 307 | 308 | $hash->make('teststring'); 309 | 310 | // 或 311 | \Hash::make('teststring'); 312 | } 313 | } 314 | ``` 315 | 316 | 以上就是 `Laravel Service Provider` 概念的简单介绍,更深入的了解可以看看「點燈坊」的这篇文章: 317 | [http://oomusou.io/laravel/laravel-service-provider/](http://oomusou.io/laravel/laravel-service-provider/) 。 318 | -------------------------------------------------------------------------------- /using-query-builder.md: -------------------------------------------------------------------------------- 1 | # Laravel Query Builder 原理及用法 2 | 3 | >本文翻译自 [《Laravel - My first framework》](https://leanpub.com/laravel-first-framework/) 4 | 5 | 从 `CURD` 到 `排序` 和 `过滤`,`Query Builder` 提供了方便的操作符来处理数据库中的数据。这些操作符大多数可以组合在一起,以充分利用单个查询。 6 | 7 | `Laravel` 一般使用 `DB` facade 来进行数据库查询。当我们执行 `DB` 的「命令」(或者说「操作符」)时,`Query Builder` 会构建一个 SQL 查询,该查询将根据 `table()` 方法中指定的表执行查询。 8 | 9 | ![Executing database operations using Query Builder](./figures/using-query-builder-1.png) 10 | 11 | 该查询将使用 `app/config/database.php` 文件中指定的数据库连接执行。 查询执行的结果将返回:检索到的记录、布尔值或一个空结果集。 12 | 13 | 下表中是 `Query Builder` 的常用操作符: 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 |
操作符描述
insert(array(...))接收包含字段名和值的数组,插入数据至数据库
find($id)检索一个主键 id 等于给定参数的记录
update(array(...))接收含有字段名和值的数组,更新已存在的记录
delete()删除一条记录
get()返回一个 Illuminate\Support\Collection 结果,其中每个结果都是一个 PHP StdClass 对象的实例,实例中包含每行记录中的列名及其值
take($number)限制查询结果数量
45 | 46 | 接下来,将讲解 `Query Builder` 的各种操作。 47 | 48 | # CURD 49 | ## Inserting records - 插入 50 | `insert` 操作符将新行(记录)插入到现有表中。我们可以通过提供数据数组作为 `insert` 运算符的参数来指定要插入到表中的数据。 51 | 52 | 假设有一个 `orders` 表: 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
KeyColumnType
primaryidint (11), auto-incrementing
priceint (11)
productvarchar(255)
75 | 76 | ### 插入单行数据 77 | 把数据数组传给 `insert` 操作符,来告诉 `Query Builder` 插入新行: 78 | ```php 79 | DB::table('orders')->insert( 80 | [ 81 | 'price' => 200, // 设置 price 字段值 82 | 'product' => 'Console', // 设置 product 字段值 83 | ] 84 | ); 85 | ``` 86 | 87 | `Query Builder`将 `insert` 命令转换为特定于 `database.php` 配置文件中指定的数据库的 SQL 查询。 作为参数传递给 `insert` 命令的数据将以参数的形式放入 SQL 查询中。 然后,SQL 查询将在指定的表 "orders" 上执行,执行结果将返回给调用者。 88 | 下图说明了整个过程: 89 | 90 | ![Behind the scenes process of running “insert” operator](./figures/using-query-builder-2.png) 91 | 92 | >可以看到,Laravel 使用 PDO 来执行 SQL 语句。通过为数据添加占位符来使用准备好的语句可以增强 SQL 注入的保护性,并增加数据插入和更新的安全性。 93 | 94 | ### 插入多行数据 95 | `Query Builder` 的 `insert` 操作符同样可用于插入多行数据。 传递一个包含数组的数组可以插入任意数量的行: 96 | 97 | ```php 98 | DB::table('orders')->insert( 99 | [ 100 | ['price' => 400, 'product' => 'Laptop'], 101 | ['price' => 200, 'product' => 'Smartphone'], 102 | ['price' => 50, 'product' => 'Accessory'], 103 | ] 104 | ); 105 | ``` 106 | 107 | 此 `insert` 语句将创建三个新记录,Laravel 构建的 SQL 查询是: 108 | ``` 109 | insertinto`orders`(`price`,`product`)values(?,?),(?,?),(?,?) 110 | ``` 111 | 112 | >可以看到, Laravel 聪明地在一个查询中插入三行数据,而不是运行三个单独的查询。 113 | 114 | ## Retrieving records - 检索 115 | 116 | `Query Builder` 提供了多种从数据库获取数据的操作符,以灵活的适应许多不同的情况,例如: 117 | 118 | * 检索单个记录 119 | * 检索表中的所有记录 120 | * 仅检索表中所有记录的特定列 121 | * 检索表中有限数量的记录 122 | 123 | ### 检索单个记录 124 | 125 | 可以使用 `find` 操作符从表中检索单个记录。只需提供要检索的记录的主键的值作为 `find` 的参数,Laravel 将返回该记录作为对象。如果未找到该记录,则返回 `NULL`。 126 | 127 | ```php 128 | $order = DB::table('orders')->find(3); 129 | 130 | /* 131 | object(stdClass)#157 (3) { 132 | ["id"]=>string(1) "3" 133 | ["price"]=>string(3) "200" 134 | ["product"]=>string(10) "Smartphone" 135 | } 136 | */ 137 | ``` 138 | 139 | > 注意: `find` 操作符以 `id` 作为主键进行查询,如想使用别的主键,请使用其它操作符。 140 | 141 | Laravel 构建的 SQL 查询是: 142 | ``` 143 | select * from `orders` where `id` = ? limit 1 144 | ``` 145 | 146 | ### 检索表中的所有记录 147 | 要从表中检索所有记录,可以使用 `get` 操作符而不用任何参数。在指定的表上运行 `get` (前面没有别的操作符) 将会将该表中的所有记录作为对象数组返回。 148 | 149 | ```php 150 | $orders = DB::table('orders')->get(); 151 | 152 | /* 153 | array(4) { [0]=> 154 | object(stdClass)#157 (3) { 155 | ["id"]=>string(1) "1" 156 | ["price"]=>string(3) "200" 157 | ["product"]=>string(7) "Console" 158 | } 159 | 160 | ... 3 more rows returned as objects ... 161 | } 162 | */ 163 | ``` 164 | 165 | Laravel 构建的 SQL 查询是: 166 | ``` 167 | select * from `orders` 168 | ``` 169 | 170 | ### 检索仅包含特定列的所有记录 171 | 将所需的列名作为参数数组传递给 `get` 运算符,可获得表中所有记录的特定列。 172 | 173 | ```php 174 | $orders = DB::table('orders')->get(['id','price']); 175 | 176 | /* 177 | array(4) { [0]=> 178 | object(stdClass)#157 (2) { 179 | ["id"]=>string(1) "1" 180 | ["price"]=>string(3) "200" 181 | } 182 | ... 3 more rows returned as objects ... 183 | } 184 | */ 185 | ``` 186 | 187 | Laravel 构建的 SQL 查询是: 188 | ``` 189 | select `id`, `price` from `orders` 190 | ``` 191 | 192 | ### 检索表中有限数量的记录 193 | 要指定要从表中获取的最大记录数,可以使用 `take` 操作符,并将 `get` 附加到查询中。 194 | 195 | ```php 196 | $orders = DB::table('orders')->take(50)->get(); 197 | ``` 198 | 199 | $orders 数组中最多有 50 条数据。 200 | 201 | ## Updating records - 更新 202 | 203 | 使用 `Query Builder` 更新记录与创建新记录非常相似。要更新现有记录或一组记录的数据,可以将操作符 `update` 附加到查询中,并将一个新数据数组作为参数传递给它。同时可以使用查询链定位要更新的特定记录。 204 | 205 | ### 更新特定记录 206 | 207 | 使用 `where` 操作符来指定特定记录并更新: 208 | ```php 209 | DB::table('orders') 210 | ->where('price','>','50') 211 | ->update(['price' => 100]); 212 | ``` 213 | 214 | Laravel 构建的 SQL 查询是: 215 | ``` 216 | update `orders` set `price` = ? where `price` > ? 217 | ``` 218 | 219 | ### 更新所有记录 220 | 如果不限定条件直接使用 `update` ,将更新表中所有记录: 221 | 222 | ```php 223 | DB::table('orders')->update(['product'=>'Headphones']); 224 | ``` 225 | 226 | Laravel 构建的 SQL 查询是: 227 | ``` 228 | update `orders` set `product` = ? 229 | ``` 230 | 231 | ## Deleting records - 删除 232 | 使用 `Query Builder` 从表中删除记录遵循与更新记录相同的模式。 可以使用 `delete` 操作符删除与某些条件匹配的特定记录或删除所有记录。 233 | 234 | ### 删除特定记录 235 | 使用 `where` 操作符来指定要删除的特定记录: 236 | 237 | ```php 238 | DB::table('orders') 239 | ->where('product','=','Smartphone') 240 | ->delete(); 241 | ``` 242 | 243 | Laravel 构建的 SQL 查询是: 244 | ``` 245 | delete from `orders` where `product` = ? 246 | ``` 247 | 248 | # Filtering, sorting and grouping data - 过滤,排序和分组 249 | 在数据库应用程序中管理数据时,往往需要对哪些记录进行严格的控制。 这可能是要准确地获得应用程序规范要求的数据集,或者只删除符合某些条件的几条记录。 如果使用纯 SQL ,其中一些操作可能会变得非常复杂。 Laravel 的 `Query Builder` 允许过滤,排序和分组数据,同时保持清晰一致的语法,易于理解。 250 | 251 | 下表中是 `Query Builder` 的常用的过滤、排序和分组操作符: 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 |
操作符描述
where('column','comparator','value')检索符合条件的记录
orderBy('column','order')按指定的列和顺序排序记录(升序或降序)
groupBy('column')按列分组
270 | 271 | ## Query Chaining - 查询链 272 | 查询链接允许在单个查询中运行多个数据库操作。查询链将可以与数据执行的各种动作的顺序相互结合,以获得可以操作的特定结果。通过各种参数过滤、排序数据等等可以表示为对表中的数据执行的一系列操作: 273 | 274 | ![Concept of chaining actions together to get specific data from the database](./figures/using-query-builder-3.png) 275 | 276 | Laravel 允许根据需要将多个查询放在一起。查询链接可以显着减少编写的代码量来执行复杂的数据库操作。 例如,要对 `users` 表执行上述操作,可以将过滤和排序一起放入单个查询链,如图所示: 277 | 278 | ![Example of query chaining in action](./figures/using-query-builder-4.png) 279 | 280 | > 注意:可以使用查询链来执行多个操作,如排序、过滤、分组,以精确定位可以进一步检索、更新或删除的一组数据。 但不能在单个查询中将 insert/get/update/delete 操作混合在一起。 281 | 282 | ## Where 操作符 283 | `Query Builder` 的 `Where` 操作符提供了一个干净的接口,用于执行 SQL 的 WHERE 子句,并具有与之非常相似的语法。例如: 284 | 285 | * 选择符合特定标准的记录 286 | * 选择符合任一条件的记录 287 | * 选择具有特定值范围的列的记录 288 | * 选择列超出值范围的记录 289 | 290 | 使用 `where` 选择记录后,可以执行之前讨论过的任何操作:检索、更新或删除。 291 | 292 | ### 简单的 where 查询 293 | `where` 的查询由提供用于过滤数据的三个参数组成: 294 | 295 | * 用于比较的列名 296 | * 用于比较的运算符 297 | * 用于比较的值 298 | 299 | ![Syntax of using operator “where”](./figures/using-query-builder-5.png) 300 | 301 | > 如果仅将两个参数传递给 `where` 操作符, Laravel 会默认使用 `=` 进行比较,可以减少代码量。 302 | 303 | 下表是常用的 `where` 比较运算符: 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 |
运算符描述
=等于
<小于
>大于
<=小于等于
>=大于等于
<> 或!=不等于
like模糊查询
not like模糊查询
343 | 344 | 除了使用单个 `where` 操作符,还可可以链接多个 `where` 来进一步过滤结果。 Laravel 会在 SQL 语句中自动将 `AND` 链接在 `where` 操作符之间。 345 | 346 | ```php 347 | $users = DB::table('users') 348 | // Match users whose last_name column starts with “A” 349 | ->where('last_name', 'like','A%') 350 | // Match users whose age is less than 50 351 | ->where('age','<' ,50) 352 | // Retrieve the records as an array of objects 353 | ->get(); 354 | ``` 355 | 356 | Laravel 构建的 SQL 查询是: 357 | ``` 358 | select * from `users` where `last_name` like ? and `age` < ? 359 | ``` 360 | 361 | ### orWhere 362 | 363 | 通过使用 `orWhere` 操作符可以选择几个匹配至少一个条件的数据。它具有与 `where` 操作符完全相同的语法,并且必须将其附加到现有的 `where`操作符,以使其运行。 364 | 365 | ```php 366 | $orders = DB::table('orders') 367 | // Match orders that have been marked as processed 368 | ->where('processed', 1) 369 | // Match orders that have price lower than or equal to 250 370 | ->orWhere('price','<=' ,250) 371 | // Delete records that match either criterion 372 | ->delete(); 373 | ``` 374 | 375 | Laravel 构建的 SQL 查询是: 376 | ``` 377 | delete from `orders` where `processed` = ? or `price` <= ? 378 | ``` 379 | 380 | ### whereBetween 381 | `whereBetween` 方法用来验证字段的值介于两个值之间。它只需要两个参数,一个用于匹配的列和一个包含两个数值的数组,表示一个范围。 382 | 383 | ![Syntax of “whereBetween” operator](./figures/using-query-builder-6.png) 384 | 385 | ```php 386 | $users = DB::table('users') 387 | // Match users that have the value of “credit” column between 100 and 300 388 | ->whereBetween('credit', [100,300]) 389 | // Retrieve records as an array of objects 390 | ->get(); 391 | ``` 392 | 393 | Laravel 构建的 SQL 查询是: 394 | ``` 395 | select * from `users` where `credit` between ? and ? 396 | ``` 397 | 398 | ## orderBy - 排序 399 | `Query Builder` 的 `orderBy` 操作符提供了一种简单的方法来对从数据库检索的数据进行排序。 `orderBy` 类似于 SQL 中的 ORDER BY 子句。要通过一些列对一组数据进行排序,需要将两个参数传递给 `orderBy` :排序数据的列和排序方向(升序或降序)。 400 | 401 | ![Syntax of “orderBy” operator used to sort data](./figures/using-query-builder-7.png) 402 | 403 | 将 `orderBy` 操作符应用于由 `Query Builder` 的一个操作符检索的一组数据,将根据列名称和指定的方向对数据进行排序。 404 | 405 | ```php 406 | $products = DB::table('products') 407 | // Sort the products by their price in ascending order 408 | ->orderBy('price', 'asc') 409 | // Retrieve records as an array of objects 410 | ->get(); 411 | ``` 412 | 413 | 还可以使用查询链来适应更复杂的过滤和排序方案。 414 | ```php 415 | $products = DB::table('products') 416 | // Get products whose name contains letters “so” 417 | ->where('name','like','%so%') 418 | // Get products whose price is greater than 100 419 | ->where('price','>', 100) 420 | // Sort products by their price in ascending order 421 | ->orderBy('price', 'asc') 422 | // Retrieve products from the table as an array of objects 423 | ->get(); 424 | ``` 425 | 426 | 像 `where` 一样,`orderBy` 操作符是可链接的,可以组合多个 `orderBy` 以获取需要实现的排序结果。 427 | 428 | ## groupBy - 分组 429 | 可以使用类似于 SQL 中 GROUP BY 子句的 `groupBy` 操作符将记录组合在一起。 它只接受一个参数:用于对记录进行分组的列。 430 | 431 | ```php 432 | $products = DB::table('products') 433 | // Group products by the “name” column 434 | ->groupBy('name') 435 | // Retrieve products from the table as an array of objects 436 | ->get(); 437 | ``` 438 | 439 | ## JOIN - 联结 440 | Laravel 的 `Query Builder` 支持数据库所有类型的 Join 语句。 联结语句用于组合具有这些表共同值的多个表中的记录。 例如有两个表 `users` 和 `orders` ,其内容如图所示: 441 | 442 | ![Contents of two sample tables](./figures/using-query-builder-8.png) 443 | 444 | 虽然可以使用 Join 语句组合两个以上的表,但是我们将仅使用图中的两个表来演示可以在 `Query Builder` 中使用的 Join 语句类型。 445 | 446 | > 注意:如果联结的表具有相同名称的列,则应小心。 可以使用 `select()` 来代替重复的列。 447 | 448 | 449 | ### Inner Join - 内联结 450 | `Inner Join` 是一种简单而常见的 Join 类型。 它用于返回一个表中在另一个表中具有完全匹配条件的所有记录。 451 | 452 | > 可以使用查询链组合多个 `join` 操作符,来联结两个以上的表。 453 | 454 | ![Syntax of Inner Join](./figures/using-query-builder-9.png) 455 | 456 | ```php 457 | // Use table “users” as first table 458 | $usersOrders = DB::table('users') 459 | // Perform a Join with the “orders” table, checking for the presence of matching 460 | // “user_id” column in “orders” table and “id” column of the “user” table. 461 | ->join('orders', 'users.id', '=', 'orders.user_id') 462 | // Retrieve users from the table as an array of objects containing users and 463 | // products that each user has purchased 464 | ->get(); 465 | ``` 466 | 467 | Laravel 构建的 SQL 查询是: 468 | ``` 469 | select * from `users` inner join `orders` on `users`.`id` = `orders`.`user_id` 470 | ``` 471 | 472 | 下图显示了内联结为两组数据之间的阴影区域的结果。 473 | 474 | ![Result of running an Inner Join query between the “users” and “orders” tables](./figures/using-query-builder-10.png) 475 | 476 | ### Left Join - 左联结 477 | 左连接比内连接更具包容性,并具有类似的语法。 它生成一组在两个表之间匹配的记录,另外它返回从第一个表中有而其它表中没有匹配的所有记录。 语法的唯一区别是使用 `leftJoin` 操作符而不是 `join`。 478 | 479 | ![Syntax of Left Join](./figures/using-query-builder-11.png) 480 | 481 | ```php 482 | // Use table “users” as first table 483 | $usersOrders = DB::table('users') 484 | // Perform a Left Join with the “orders” table, checking for the presence of 485 | // matching “user_id” column in “orders” table and “id” column of the “user” table. 486 | ->leftJoin('orders', 'users.id', '=', 'orders.user_id') 487 | // Retrieve an array of objects containing records of “users” table that have 488 | // a corresponding record in the “orders” table and also all records in “users” 489 | // table that don’t have a match in the “orders” table 490 | ->get(); 491 | ``` 492 | 493 | Laravel 构建的 SQL 查询是: 494 | ``` 495 | select * from `users` left join `orders` on `users`.`id` = `orders`.`user_id` 496 | ``` 497 | 498 | 此查询将包含 `users` 中的所有行,而不管 `orders` 表中是否具有匹配的条目。结果列的值将与具有内联结的值相同,但是来自 `users` 表中的那些不在 `orders` 中匹配的行将返回 `id`,`item` 和 `user_id`列。 499 | 500 | ![Result of running a Left Join query between the “users” and “orders” tables](./figures/using-query-builder-12.png) 501 | 502 | ### 其它类型的联结 503 | Laravel 的 `Query Builder` 非常灵活,可以考虑连接查询的特殊情况,并允许执行数据库支持的所有类型的连接查询。 大多数SQL数据库引擎 (如 MySQL 和 SQLite) 支持 内左、右联结/外左、右联结,其他 SQL 引擎 (如 Postgres 和SQL Server) 还支持完全联结。 504 | 可以通过向 `join` 操作符提供第五个参数,指定要执行的 Join 查询类型,来执行数据库支持的任何类型的联结。 505 | 506 | ![Using fifth argument of “join” operator for custom type of Join query](./figures/using-query-builder-13.png) 507 | 508 | 下面显示了与 Laravel 支持的所有数据库引擎的 Join 类型: 509 | ```php 510 | // Right Join 511 | join('orders', 'users.id', '=', 'orders.user_id','right') // Right Outer Join 512 | join('orders', 'users.id', '=', 'orders.user_id','right outer') 513 | // Excluding Right Outer Join 514 | join('orders', 'users.id', '=', 'orders.user_id','right outer') ->where('orders.user_id',NULL) 515 | // Left Join 516 | join('orders', 'users.id', '=', 'orders.user_id','left') // Left Outer Join 517 | join('orders', 'users.id', '=', 'orders.user_id','left outer') 518 | // Excluding Left Outer Join 519 | join('orders', 'users.id', '=', 'orders.user_id','left outer') ->where('orders.user_id',NULL) 520 | // Cross join 521 | join('orders', 'users.id', '=', 'orders.user_id','cross') 522 | ``` 523 | 524 | 以上就是 `Laravel Query Builder` 的介绍,下一篇文章中将讲解 `Laravel Eloquent` 的用法。 525 | -------------------------------------------------------------------------------- /laravel-container-in-depth.md: -------------------------------------------------------------------------------- 1 | # Laravel Container (容器) 深入理解 (下) 2 | 3 | [TOC] 4 | 5 | >本文大部分翻译自 DAVE JAMES MILLER 的 [《Laravel’s Dependency Injection Container in Depth》](https://davejamesmiller.com/2017/06/15/laravel-illuminate-container-in-depth) 。 6 | 7 | 上文介绍了 `Dependency Injection Containers (容器)` 的基本概念,现在接着深入讲解 `Laravel` 的 `Container`。 8 | 9 | `Laravel` 中实现的 `Inversion of Control (IoC) / Dependency Injection (DI) Container` 非常强悍,但文档中很低调的没有细讲它。 10 | 11 | >本文中示例基于 `Laravel 5.5` ,其它版本差不多。 12 | 13 | # 准备工作 14 | ## 1.`Dependency Injection` 15 | 16 | 关于 `DI` 请看这篇 [《Laravel Dependency Injection (依赖注入) 概念详解》](https://github.com/seekerliu/laravel-tips/blob/master/do-you-need-a-dependency-injection-container.md),这里不再赘述。 17 | 18 | ## 2. 初识 `Container` 19 | `Laravel` 中有一大堆访问 `Container` 实例的姿势,比如最简单的: 20 | ```php 21 | $container = app(); 22 | ``` 23 | 但我们还是先关注下 `Container` 类本身。 24 | 25 | >`Laravel` 官方文档中一般使用 `$this->app` 代替 `$container`。它是 `Application` 类的实例,而 `Application` 类继承自 `Container` 类。 26 | 27 | ## 3. 在 `Laravel` 之外使用 `Illuminate\Container` 28 | 如果在 `Laravel` 之外 29 | ```bash 30 | mkdir container && cd container 31 | composer require illuminate/container 32 | ``` 33 | 34 | ```php 35 | // 新建一个 container.php,文件名随便取 36 | dependency = $dependency; 54 | } 55 | } 56 | ``` 57 | 58 | 接下来用 `Container` 的 `make` 方法来代替 `new MyClass`: 59 | ```php 60 | $instance = $container->make(MyClass::class); 61 | ``` 62 | 63 | `Container` 会自动实例化依赖的对象,所以它等同于: 64 | ```php 65 | $instance = new MyClass(new AnotherClass()); 66 | ``` 67 | 68 | 如果 `AnotherClass` 也有 `依赖`,那么 `Container` 会递归注入它所需的依赖。 69 | 70 | >`Container` 使用 [Reflection (反射)](http://php.net/manual/zh/book.reflection.php) 来找到并实例化构造函数参数中的那些类,实现起来并不复杂,以后的文章里再介绍。 71 | 72 | ### 实战 73 | 下面是 [PHP-DI 文档](http://php-di.org/doc/getting-started.html) 中的一个例子,它分离了「用户注册」和「发邮件」的过程: 74 | ```php 75 | class Mailer 76 | { 77 | public function mail($recipient, $content) 78 | { 79 | // Send an email to the recipient 80 | // ... 81 | } 82 | } 83 | ``` 84 | 85 | ```php 86 | class UserManager 87 | { 88 | private $mailer; 89 | 90 | public function __construct(Mailer $mailer) 91 | { 92 | $this->mailer = $mailer; 93 | } 94 | 95 | public function register($email, $password) 96 | { 97 | // 创建用户账户 98 | // ... 99 | 100 | // 给用户的邮箱发个 “hello" 邮件 101 | $this->mailer->mail($email, 'Hello and welcome!'); 102 | } 103 | } 104 | ``` 105 | 106 | ```php 107 | use Illuminate\Container\Container; 108 | 109 | $container = Container::getInstance(); 110 | 111 | $userManager = $container->make(UserManager::class); 112 | $userManager->register('dave@davejamesmiller.com', 'MySuperSecurePassword!'); 113 | ``` 114 | 115 | ## 技能W. Binding Interfaces to Implementations (绑定接口到实现) 116 | 用`Container` 可以轻松地写一个接口,然后在运行时实例化一个具体的实例。 首先定义接口: 117 | 118 | ```php 119 | interface MyInterface { /* ... */ } 120 | interface AnotherInterface { /* ... */ } 121 | ``` 122 | 123 | 然后声明实现这些接口的具体类。下面这个类不但实现了一个接口,还依赖了实现另一个接口的类实例: 124 | ```php 125 | class MyClass implements MyInterface 126 | { 127 | private $dependency; 128 | 129 | // 依赖了一个实现 AnotherInterface 接口的类的实例 130 | public function __construct(AnotherInterface $dependency) 131 | { 132 | $this->dependency = $dependency; 133 | } 134 | } 135 | ``` 136 | 137 | 现在用 `Container` 的 `bind()` 方法来让每个 `接口` 和实现它的类一一对应起来: 138 | ```php 139 | $container->bind(MyInterface::class, MyClass::class); 140 | $container->bind(AnotherInterface::class, AnotherClass::class); 141 | ``` 142 | 143 | 最后,用 `接口名` 而不是 `类名` 来传给 `make()`: 144 | ```php 145 | $instance = $container->make(MyInterface::class); 146 | ``` 147 | 148 | >注意:如果你忘记绑定它们,会导致一个 `Fatal Error`:"Uncaught ReflectionException: Class MyInterface does not exist"。 149 | 150 | ### 实战 151 | 下面是可封装的 `Cache` 层: 152 | ```php 153 | interface Cache 154 | { 155 | public function get($key); 156 | public function put($key, $value); 157 | } 158 | ``` 159 | 160 | ```php 161 | class Worker 162 | { 163 | private $cache; 164 | 165 | public function __construct(Cache $cache) 166 | { 167 | $this->cache = $cache; 168 | } 169 | 170 | public function result() 171 | { 172 | // 去缓存里查询 173 | $result = $this->cache->get('worker'); 174 | 175 | if ($result === null) { 176 | // 如果缓存里没有,就去别的地方查询,然后再放进缓存中 177 | $result = do_something_slow(); 178 | 179 | $this->cache->put('worker', $result); 180 | } 181 | 182 | return $result; 183 | } 184 | } 185 | ``` 186 | 187 | ```php 188 | use Illuminate\Container\Container; 189 | 190 | $container = Container::getInstance(); 191 | $container->bind(Cache::class, RedisCache::class); 192 | 193 | $result = $container->make(Worker::class)->result(); 194 | ``` 195 | 这里用 `Redis` 做缓存,如果改用其他缓存,只要把 `RedisCache` 换成别的就行了,easy! 196 | 197 | ## 技能E:Binding Abstract & Concret Classes (绑定抽象类和具体类): 198 | 199 | 绑定还可以用在抽象类: 200 | ```php 201 | $container->bind(MyAbstract::class, MyConcreteClass::class); 202 | ``` 203 | 204 | 或者继承的类中: 205 | ```php 206 | $container->bind(MySQLDatabase::class, CustomMySQLDatabase::class); 207 | ``` 208 | 209 | ## 技能R:自定义绑定 210 | 如果类需要一些附加的配置项,可以把 `bind()` 方法中的第二个参数换成 `Closure (闭包函数)`: 211 | ```php 212 | $container->bind(Database::class, function (Container $container) { 213 | return new MySQLDatabase(MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASS); 214 | }); 215 | ``` 216 | 217 | 闭包也可用于定制 `具体类` 的实例化方式: 218 | ```php 219 | $container->bind(GitHub\Client::class, function (Container $container) { 220 | $client = new GitHub\Client; 221 | $client->setEnterpriseUrl(GITHUB_HOST); 222 | return $client; 223 | }); 224 | ``` 225 | 226 | ## 技能T:Resolving Callbacks (回调) 227 | 可用 `resolveing()` 方法来注册一个 `callback (回调函数)`,而不是直接覆盖掉之前的 `绑定`。 这个函数会在绑定的类解析完成之后调用。 228 | ```php 229 | $container->resolving(GitHub\Client::class, function ($client, Container $container) { 230 | $client->setEnterpriseUrl(GITHUB_HOST); 231 | }); 232 | ``` 233 | 234 | 如果有一大堆 `callbacks`,他们全部都会被调用。对于 `接口` 和 `抽象类` 也可以这么用: 235 | ```php 236 | $container->resolving(Logger::class, function (Logger $logger) { 237 | $logger->setLevel('debug'); 238 | }); 239 | 240 | $container->resolving(FileLogger::class, function (FileLogger $logger) { 241 | $logger->setFilename('logs/debug.log'); 242 | }); 243 | 244 | $container->bind(Logger::class, FileLogger::class); 245 | 246 | $logger = $container->make(Logger::class); 247 | ``` 248 | 249 | 更 `diao` 的是,还可以注册成「什么类解析完之后都调用」: 250 | ```php 251 | $container->resolving(function ($object, Container $container) { 252 | // ... 253 | }); 254 | ``` 255 | 256 | 但这个估计只有 `logging` 和 `debugging` 才会用到。 257 | 258 | ## 技能Y:Extending a Class (扩展一个类) 259 | 使用 `extend()` 方法,可以封装一个类然后返回一个不同的对象 (装饰模式): 260 | ```php 261 | $container->extend(APIClient::class, function ($client, Container $container) { 262 | return new APIClientDecorator($client); 263 | }); 264 | ``` 265 | 266 | 注意:这两个类要实现相同的 `接口`,不然用类型提示的时候会出错: 267 | ```php 268 | interface Getable 269 | { 270 | public function get(); 271 | } 272 | ``` 273 | 274 | ```php 275 | class APIClient implements Getable 276 | { 277 | public function get() 278 | { 279 | return 'yes!'; 280 | } 281 | } 282 | ``` 283 | 284 | ```php 285 | class APIClientDecorator implements Getable 286 | { 287 | private $client; 288 | 289 | public function __construct(APIClient $client) 290 | { 291 | $this->client = $client; 292 | } 293 | 294 | public function get() 295 | { 296 | return 'no!'; 297 | } 298 | } 299 | ``` 300 | 301 | ```php 302 | class User 303 | { 304 | private $client; 305 | 306 | public function __construct(Getable $client) 307 | { 308 | $this->client = $client; 309 | } 310 | } 311 | ``` 312 | 313 | ```php 314 | $container->extend(APIClient::class, function ($client, Container $container) { 315 | return new APIClientDecorator($client); 316 | }); 317 | // 318 | $container->bind(Getable::class, APIClient::class); 319 | 320 | // 此时 $instance 的 $client 属性已经是 APIClentDecorator 类型了 321 | $instance = $container->make(User::class); 322 | ``` 323 | 324 | ## 技能U:单例 325 | 使用 `bind()` 方法绑定后,每次解析时都会新实例化一个对象(或重新调用闭包),如果想获取 `单例` ,则用 `singleton()` 方法代替 `bind()`: 326 | ```php 327 | $container->singleton(Cache::class, RedisCache::class); 328 | ``` 329 | 绑定单例 `闭包` 330 | ```php 331 | $container->singleton(Database::class, function (Container $container) { 332 | return new MySQLDatabase('localhost', 'testdb', 'user', 'pass'); 333 | }); 334 | ``` 335 | 绑定 `具体类` 的时候,不需要第二个参数: 336 | ```php 337 | $container->singleton(MySQLDatabase::class); 338 | ``` 339 | 340 | 在每种情况下,`单例` 对象将在第一次需要时创建,然后在后续重复使用。 341 | 如果你已经有一个 `实例` 并且想重复使用,可以用 `instance()` 方法。 `Laravel` 就是用这种方法来确保每次获取到的都是同一个 `Container` 实例: 342 | ```php 343 | $container->instance(Container::class, $container); 344 | ``` 345 | 346 | ## 技能I:Arbitrary Binding Names (任意绑定名称) 347 | `Container` 还可以绑定任意字符串而不是类/接口名称。但这种情况下不能使用类型提示,并且只能用 `make()` 来获取实例。 348 | ```php 349 | $container->bind('database', MySQLDatabase::class); 350 | $db = $container->make('database'); 351 | ``` 352 | 为了同时支持类/接口名称和短名称,可以使用 `alias()`: 353 | ```php 354 | $container->singleton(Cache::class, RedisCache::class); 355 | $container->alias(Cache::class, 'cache'); 356 | 357 | $cache1 = $container->make(Cache::class); 358 | $cache2 = $container->make('cache'); 359 | 360 | assert($cache1 === $cache2); 361 | ``` 362 | 363 | ## 技能O:保存任何值 364 | `Container` 还可以用来保存任何值,例如 `configuration` 数据: 365 | ```php 366 | $container->instance('database.name', 'testdb'); 367 | $db_name = $container->make('database.name'); 368 | ``` 369 | 它支持数组访问语法,这样用起来更自然: 370 | ```php 371 | $container['database.name'] = 'testdb'; 372 | $db_name = $container['database.name']; 373 | ``` 374 | >这是因为 `Container` 实现了 PHP 的 [ArrayAccess](http://php.net/manual/zh/class.arrayaccess.php) 接口。 375 | 376 | 当处理 `Closure` 绑定的时候,你会发现这个方式非常好用: 377 | ```php 378 | $container->singleton('database', function (Container $container) { 379 | return new MySQLDatabase( 380 | $container['database.host'], 381 | $container['database.name'], 382 | $container['database.user'], 383 | $container['database.pass'] 384 | ); 385 | }); 386 | ``` 387 | >`Laravel` 自己没有用这种方式来处理配置项,它使用了一个单独的 `Config` 类本身。 `PHP-DI` 用了。 388 | 389 | 数组访问语法还可以代替 `make()` 来实例化对象: 390 | ```php 391 | $db = $container['database']; 392 | ``` 393 | 394 | ## 技能P:Dependency Injection for Functions & Methods (给函数或方法注入依赖) 395 | 除了给构造函数注入依赖,`Laravel` 还可以往任意函数中注入: 396 | ```php 397 | function do_something(Cache $cache) { /* ... */ } 398 | $result = $container->call('do_something'); 399 | ``` 400 | 函数的附加参数可以作为索引或关联数组传递: 401 | ```php 402 | function show_product(Cache $cache, $id, $tab = 'details') { /* ... */ } 403 | 404 | // show_product($cache, 1) 405 | $container->call('show_product', [1]); 406 | $container->call('show_product', ['id' => 1]); 407 | 408 | // show_product($cache, 1, 'spec') 409 | $container->call('show_product', [1, 'spec']); 410 | $container->call('show_product', ['id' => 1, 'tab' => 'spec']); 411 | ``` 412 | 除此之外,`闭包`: 413 | ```php 414 | $closure = function (Cache $cache) { /* ... */ }; 415 | $container->call($closure); 416 | ``` 417 | `静态方法`: 418 | ```php 419 | class SomeClass 420 | { 421 | public static function staticMethod(Cache $cache) { /* ... */ } 422 | } 423 | ``` 424 | 425 | ```php 426 | $container->call(['SomeClass', 'staticMethod']); 427 | // or: 428 | $container->call('SomeClass::staticMethod'); 429 | ``` 430 | `实例的方法`: 431 | ```php 432 | class PostController 433 | { 434 | public function index(Cache $cache) { /* ... */ } 435 | public function show(Cache $cache, $id) { /* ... */ } 436 | } 437 | ``` 438 | 439 | ```php 440 | $controller = $container->make(PostController::class); 441 | 442 | $container->call([$controller, 'index']); 443 | $container->call([$controller, 'show'], ['id' => 1]); 444 | ``` 445 | 446 | 都可以注入。 447 | 448 | ## 技能A: 调用实例方法的快捷方式 449 | 使用 `ClassName@methodName` 语法可以快捷调用实例中的方法: 450 | ```php 451 | $container->call('PostController@index'); 452 | $container->call('PostController@show', ['id' => 4]); 453 | ``` 454 | 455 | 因为`Container` 被用来实例化类。意味着: 456 | 1. `依赖` 被注入进构造函数(或者方法); 457 | 2. 如果需要复用实例,可以定义为单例; 458 | 3. 可以用接口或任何名称来代替具体类。 459 | 460 | 所以这样调用也可以生效: 461 | ```php 462 | class PostController 463 | { 464 | public function __construct(Request $request) { /* ... */ } 465 | public function index(Cache $cache) { /* ... */ } 466 | } 467 | ``` 468 | 469 | ```php 470 | $container->singleton('post', PostController::class); 471 | $container->call('post@index'); 472 | ``` 473 | 最后,还可以传一个「默认方法」作为第三个参数。如果第一个参数是没有指定方法的类名称,则将调用默认方法。 `Laravel` 用这种方式来处理 [event handlers](https://laravel.com/docs/5.5/events#registering-events-and-listeners) : 474 | ```php 475 | $container->call(MyEventHandler::class, $parameters, 'handle'); 476 | 477 | // 相当于: 478 | $container->call('MyEventHandler@handle', $parameters); 479 | ``` 480 | 481 | ## 技能S:Method Call Bindings (方法调用绑定) 482 | `bindMethod()` 方法可用来覆盖方法,例如用来传递其他参数: 483 | ```php 484 | $container->bindMethod('PostController@index', function ($controller, $container) { 485 | $posts = get_posts(...); 486 | 487 | return $controller->index($posts); 488 | }); 489 | ``` 490 | 下面的方式都有效,调用闭包来代替调用原始的方法: 491 | ```php 492 | $container->call('PostController@index'); 493 | $container->call('PostController', [], 'index'); 494 | $container->call([new PostController, 'index']); 495 | ``` 496 | 497 | 但是,`call()` 的任何其他参数都不会传递到闭包中,因此不能使用它们。 498 | ```php 499 | $container->call('PostController@index', ['Not used :-(']); 500 | ``` 501 | >注意:这种方式不是 [Container 接口](https://github.com/laravel/framework/blob/5.5/src/Illuminate/Contracts/Container/Container.php) 的一部分,只有在它的实现类 [Container](https://github.com/laravel/framework/blob/5.4/src/Illuminate/Container/Container.php) 才有。在这个 [PR](https://github.com/laravel/framework/pull/16800)` 里可以看到它加了什么以及为什么参数被忽略。 502 | 503 | ## 技能D:Contextual Bindings (上下文绑定) 504 | 有时候你想在不同的地方给接口不同的实现。这里有 [Laravel 文档](https://laravel.com/docs/5.5/container#contextual-binding) 里的一个例子: 505 | ```php 506 | $container 507 | ->when(PhotoController::class) 508 | ->needs(Filesystem::class) 509 | ->give(LocalFilesystem::class); 510 | 511 | $container 512 | ->when(VideoController::class) 513 | ->needs(Filesystem::class) 514 | ->give(S3Filesystem::class); 515 | ``` 516 | 现在 `PhotoController` 和 `VideoController` 都依赖了 `Filesystem` 接口,但是收到了不同的实例。 517 | 518 | 可以像 `bind()` 那样,给 `give()` 传闭包: 519 | ```php 520 | ->when(VideoController::class) 521 | ->needs(Filesystem::class) 522 | ->give(function () { 523 | return Storage::disk('s3'); 524 | }); 525 | ``` 526 | 527 | 或者短名称: 528 | ```php 529 | $container->instance('s3', $s3Filesystem); 530 | 531 | $container 532 | ->when(VideoController::class) 533 | ->needs(Filesystem::class) 534 | ->give('s3'); 535 | ``` 536 | 537 | ## 技能F:Binding Parameters to Primitives (绑定初始数据) 538 | 当有一个类不仅需要接受一个注入类,还需要注入一个基本值(比如整数)。 539 | 还可以通过将变量名称 (而不是接口) 传递给 `needs()` 并将值传递给 `give()` 来注入需要的任何值 (字符串、整数等) : 540 | 541 | ```php 542 | $container 543 | ->when(MySQLDatabase::class) 544 | ->needs('$username') 545 | ->give(DB_USER); 546 | ``` 547 | 548 | 还可以使用闭包实现延时加载,只在需要的时候取回这个 `值` 。 549 | ```php 550 | $container 551 | ->when(MySQLDatabase::class) 552 | ->needs('$username') 553 | ->give(function () { 554 | return config('database.user'); 555 | }); 556 | ``` 557 | 558 | 这种情况下,不能传递类或命名的依赖关系(例如,give('database.user')),因为它将作为字面值返回。所以需要使用闭包: 559 | ```php 560 | $container 561 | ->when(MySQLDatabase::class) 562 | ->needs('$username') 563 | ->give(function (Container $container) { 564 | return $container['database.user']; 565 | }); 566 | ``` 567 | 568 | ## 技能G: Tagging (标记) 569 | `Container` 可以用来「标记」有关系的绑定: 570 | ```php 571 | $container->tag(MyPlugin::class, 'plugin'); 572 | $container->tag(AnotherPlugin::class, 'plugin'); 573 | ``` 574 | 575 | 这样会以数组的形式取回所有「标记」的实例: 576 | ```php 577 | foreach ($container->tagged('plugin') as $plugin) { 578 | $plugin->init(); 579 | } 580 | ``` 581 | 582 | `tag()` 方法的两个参数都可以接受数组: 583 | ```php 584 | $container->tag([MyPlugin::class, AnotherPlugin::class], 'plugin'); 585 | $container->tag(MyPlugin::class, ['plugin', 'plugin.admin']); 586 | ``` 587 | 588 | ## 技能H:Rebinding (重新绑定) 589 | >这个功能很少用到,可以跳过,仅供参考。 590 | 591 | 在绑定或实例被使用之后又发生了变化,将调用一个 `rebinding` 方法。 下例中, `Auth` 使用 `Session` 类后,`Session` 类将被替换,此时需要通知 `Auth` 类这个变动: 592 | ```php 593 | $container->singleton(Auth::class, function (Container $container) { 594 | $auth = new Auth; 595 | $auth->setSession($container->make(Session::class)); 596 | 597 | $container->rebinding(Session::class, function ($container, $session) use ($auth) { 598 | $auth->setSession($session); 599 | }); 600 | 601 | return $auth; 602 | }); 603 | 604 | $container->instance(Session::class, new Session(['username' => 'dave'])); 605 | $auth = $container->make(Auth::class); 606 | echo $auth->username(); // dave 607 | $container->instance(Session::class, new Session(['username' => 'danny'])); 608 | 609 | echo $auth->username(); // danny 610 | ``` 611 | 612 | `Rebinding` 的更多信息可以看这两个链接: 613 | [https://stackoverflow.com/questions/38974593/laravels-ioc-container-rebinding-abstract-types](https://stackoverflow.com/questions/38974593/laravels-ioc-container-rebinding-abstract-types) 614 | [https://code.tutsplus.com/tutorials/digging-in-to-laravels-ioc-container--cms-22167](https://code.tutsplus.com/tutorials/digging-in-to-laravels-ioc-container--cms-22167) 615 | 616 | 还有一个 `refresh()` 方法来处理这种模式: 617 | ```php 618 | $container->singleton(Auth::class, function (Container $container) { 619 | $auth = new Auth; 620 | $auth->setSession($container->make(Session::class)); 621 | 622 | $container->refresh(Session::class, $auth, 'setSession'); 623 | 624 | return $auth; 625 | }); 626 | ``` 627 | 它还返回现有的实例或绑定(如果有的话),所以可以这样做: 628 | ```php 629 | // This only works if you call singleton() or bind() on the class 630 | $container->singleton(Session::class); 631 | 632 | $container->singleton(Auth::class, function (Container $container) { 633 | $auth = new Auth; 634 | $auth->setSession($container->refresh(Session::class, $auth, 'setSession')); 635 | return $auth; 636 | }); 637 | ``` 638 | 639 | >注意:这种方式不是 [Container 接口](https://github.com/laravel/framework/blob/5.5/src/Illuminate/Contracts/Container/Container.php) 的一部分,只有在它的实现类 [Container](https://github.com/laravel/framework/blob/5.4/src/Illuminate/Container/Container.php) 才有。 640 | 641 | ## 技能J:Overriding Constructor Parameters (重写构造函数参数) 642 | `makeWith` 方法允许将附加参数传递给构造函数。它忽略任何现有的实例或单例,可以用于创建具有不同参数的类的多个实例,同时仍然注入依赖关系: 643 | ```php 644 | class Post 645 | { 646 | public function __construct(Database $db, int $id) { /* ... */ } 647 | } 648 | ``` 649 | 650 | ```php 651 | $post1 = $container->makeWith(Post::class, ['id' => 1]); 652 | $post2 = $container->makeWith(Post::class, ['id' => 2]); 653 | ``` 654 | 655 | >注意:`Laravel 5.3` 及以下使用 `make($class, $parameters)`。`Laravel 5.4` 中移除了此方法,但是在 `5.4.16` 以后又重新加回来了 `makeWith()` 。 656 | 657 | ## 技能K:其它 658 | 这涵盖了我认为有用的所有方法,但仅仅是简介,不然这篇文章就写不完了。。。 659 | 660 | ### bound() 661 | 如果一个类/名称已经被 `bind()` , `singleton()` ,`instance()` 或 `alias()` 绑定,那么 `bound()` 方法返回 `true`。 662 | ```php 663 | if (! $container->bound('database.user')) { 664 | // ... 665 | } 666 | ``` 667 | 还可以使用数组访问语法和 `isset()`: 668 | ```php 669 | if (! isset($container['database.user'])) { 670 | // ... 671 | } 672 | ``` 673 | 674 | 可以使用 `unset()` 来重置它,这会删除指定的绑定/实例/别名。 675 | ```php 676 | unset($container['database.user']); 677 | var_dump($container->bound('database.user')); // false 678 | ``` 679 | 680 | ### bindIf() 681 | 682 | `bindIf()` 和 `bind()` 功能类似,差别在于只有在现有绑定不存在的情况下才注册绑定。 它一般被用在 `package` 中注册一个可被用户重写的默认绑定。 683 | ```php 684 | $container->bindIf(Loader::class, FallbackLoader::class); 685 | ``` 686 | 不过并没有 `singletonIf()` 方法,只能用 `bindIf($abstract, $concrete, true)` 来实现。 687 | ```php 688 | $container->bindIf(Loader::class, FallbackLoader::class, true); 689 | ``` 690 | 它等同于: 691 | ```php 692 | if (! $container->bound(Loader::class)) { 693 | $container->singleton(Loader::class, FallbackLoader::class); 694 | } 695 | ``` 696 | 697 | ### resolved() 698 | 如果一个类已经被解析,`resolved()` 方法会返回 `true`。 699 | ```php 700 | var_dump($container->resolved(Database::class)); // false 701 | $container->make(Database::class); 702 | var_dump($container->resolved(Database::class)); // true 703 | ``` 704 | 705 | 我也不太确定他用在什么地方。。。 706 | 如果用过 `unset()` 之后它会被重置: 707 | ```php 708 | unset($container[Database::class]); 709 | var_dump($container->resolved(Database::class)); // false 710 | ``` 711 | 712 | ### factory() 713 | `factory()` 方法返回一个不需要参数并调用 `make()` 的闭包。 714 | ```php 715 | $dbFactory = $container->factory(Database::class); 716 | $db = $dbFactory(); 717 | ``` 718 | 719 | 这个东西我也不知道有什么用。。。 720 | 721 | ### wrap() 722 | `wrap` 方法包装一个闭包,以便在执行时依赖关系被注入。 它接受一个数组参数; 返回的闭包不带参数: 723 | 724 | ```php 725 | $cacheGetter = function (Cache $cache, $key) { 726 | return $cache->get($key); 727 | }; 728 | 729 | $usernameGetter = $container->wrap($cacheGetter, ['username']); 730 | 731 | $username = $usernameGetter(); 732 | ``` 733 | 734 | 我也不知道它有啥用,因为返回的闭包没带回参数。。。 735 | 736 | >注意:这个方法不是 [Container 接口](https://github.com/laravel/framework/blob/5.5/src/Illuminate/Contracts/Container/Container.php)` 的一部分,只有在它的实现类 [Container](https://github.com/laravel/framework/blob/5.4/src/Illuminate/Container/Container.php) 才有。 737 | 738 | ### afterResolving() 739 | `afterResolving()` 方法作用与 `resolving()` 完全相同,不同之处是 调用 「resolving」回调之后再调用 「afterResolving」回调。 740 | 不知道什么时候会用到它。。。 741 | 742 | # 最后再附几个 743 | 744 | `isShared()` – 确定一个给定的类型是一个 singleton/instance 745 | `isAlias()` – 确定给定的字符串是否是已注册的 `别名` 746 | `hasMethodBinding()` - 确定容器是否具有给定的 `method binding` 747 | `getBindings()` - 取回所有已注册绑定的原始数组 748 | `getAlias($abstract)` - 获取基础类/绑定名称的别名 749 | `forgetInstance($abstract)` - 清除单个实例对象 750 | `forgetInstances()` - 清除所有实例对象 751 | `flush()` - 清除所有绑定和实例,有效地重置容器 752 | `setInstance()` - 替换 `getInstance()` 使用的实例 (提示:使用 setInstance(null)来清除它,这样下一次它将生成一个新的实例) 753 | 754 | >注意:这些方法不是 [Container 接口](https://github.com/laravel/framework/blob/5.5/src/Illuminate/Contracts/Container/Container.php) 的一部分,只有在它的实现类 [Container](https://github.com/laravel/framework/blob/5.4/src/Illuminate/Container/Container.php) 才有。 755 | --------------------------------------------------------------------------------