Laravel 使用 Composer 来管理项目依赖。因此,在使用 Laravel 之前,请确保你的机器已经安装了 Composer。
首先,通过使用 Composer 安装 Laravel 安装器:
composer global require laravel/installer
确保将 Composer’s system-wide vendor 目录放置在你的系统环境变量 $PATH 中,以便系统可以找到 Laravel 的可执行文件。该目录根据你的操作系统存在不同的位置中;一些常见的配置包括 :
macOS and GNU / Linux 发行版: $HOME/.config/composer/vendor/bin
- Windows: %USERPROFILE%\AppData\Roaming\Composer\vendor\bin
安装完成后, laravel new 命令会在你指定的目录创建一个全新的 Laravel 项目。例如, laravel new blog 将会创建一个名为 blog 的目录,并已安装好 Laravel 所有的依赖项:
laravel new blog
通过 Composer 创建项目
或者,你也可以在终端中运行 create-project 命令来安装 Laravel:
composer create-project --prefer-dist laravel/laravel blog "6.*"
本地开发环境
如果你在本地安装了 PHP, 并且你想使用 PHP 内置的服务器来为你的应用程序提供服务,则可以使用 Artisan 命令 serve 。该命令会在 http://localhost:8000上启动开发服务器:
php artisan serve
当然,最好还是选择 Homestead 和 Valet。
安装完 Laravel 之后,你应该配置你的 web 服务的文档目录指向 public 路径。该路径下的 index.php 文件作为进入应用的所有 HTTP 请求的前端控制器。
Laravel 框架的所有配置文件存放在 config 目录下。每个选项都有文档标注,便于通过文件查看并熟悉对你有用的选项。
在安装 Laravel 后,你可能需要配置一些权限。 storage 和 bootstrap/cache 目录在你的 web 服务下应该是可写的权限,否则 Laravel 将无法运行。如果你用的是 Homestead 虚拟机,这些权限应该已经设置好了。
安装好 Laravel 之后的下一步是设置你的应用密钥为随机字符串。如果你通过 Composer 或者 Laravel 安装器安装的,这个密钥已经通过 php artisan key:generate 命令为你设置好了。
通常,这个字符串应该是 32 个字符长度。这个密钥将会设置在 环境变量文件 .env 中。如果你还没有将 .env.example 文件重命名为 .env 那么你现在应当把此文件重命名。 如果应用密钥还没有设置,你的用户会话和其他的加密数据将会不安全
Laravel 几乎不需要除上面所说的其他什么配置了。你可以随心所欲的开始开发了!然而,你可能会想要再次查看 config/app.php 文件和它的注释说明。它包含一些你希望根据你应用来更改的选项,诸如: timezone 和 locale 。
你还可能想要配置 Laravel 的其他的一些组件,例如:
缓存
数据库
会话控制
Laravel 中包含了一个 public/.htaccess 文件,通常用于在资源路径中隐藏 index.php 的前端控制器。在用 Apache 为 Laravel 提供服务之前,确保启用了 mod_rewrite 模块 ,这样 .htaccess 文件才能被服务器解析。
如果 Laravel 附带的 .htaccess 文件不起作用,尝试下面的方法替代:
Options +FollowSymLinks -Indexes
RewriteEngine On
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
如果你使用 Nginx ,在你的站点配置中加入以下配置,所有的请求将会引导至 index.php 前端控制器:
location / {
try_files $uri $uri/ /index.php?$query_string;
}
当你使用 Homestead 或 Valet 时,优雅链接将会自动配置好。
Laravel 框架的所有配置文件都保存在 config 目录中。每个选项都有说明,你可随时查看这些文件并熟悉都有哪些配置选项可供你使用。
对于应用程序运行的环境来说,不同的环境有不同的配置通常是很有用的。 例如,你可能希望在本地使用的缓存驱动不同于生产服务器所使用的缓存驱动。
Laravel 利用 Vance Lucas 的 PHP 库 DotEnv 使得此项功能的实现变得非常简单。在新安装好的 Laravel 应用程序中,其根目录会包含一个 .env.example 文件。如果是通过 Composer 安装的 Laravel,该文件会自动更名为 .env。否则,需要你手动更改一下文件名。
你的 .env 文件不应该提交到应用程序的源代码控制系统中,因为每个使用你的应用程序的开发人员 / 服务器可能需要有一个不同的环境配置。此外,在入侵者获得你的源代码控制仓库的访问权的情况下,这会成为一个安全隐患,因为任何敏感的凭据都被暴露了。
如果是团队开发,则可能希望应用程序中仍包含 .env.example 文件。因为通过在示例配置文件中放置占位值,团队中的其他开发人员可以清楚地看到哪些环境变量是运行应用程序所必需的。你也可以创建一个 .env.testing 文件,当运行 PHPUnit 测试或以 --env=testing 为选项执行 Artisan 命令时,该文件将覆盖 .env 文件中的值。
Tip: .env 文件中的所有变量都可被外部环境变量(比如服务器级或系统级环境变量)所覆盖。
.env 文件中的所有变量都被解析为字符串,因此创建了一些保留值以允许你从 env() 函数中返回更多类型的变量:
.env 值 env() 值
true (bool) true
(true) (bool) true
false (bool) false
(false) (bool) false
empty (string) ‘’
(empty) (string) ‘’
null (null) null
(null) (null) null
如果你需要使用包含空格的值定义环境变量,可以通过将值括在双引号中来实现。
APP_NAME="我的 应用"
当应用程序收到请求时,.env 文件中列出的所有变量将被加载到 PHP 的超级全局变量 $_ENV 中。你可以使用 env 函数检索这些变量的值。事实上,如果你查看 Laravel 的配置文件,你就能注意到有数个选项已经使用了这个函数:
'debug' => env('APP_DEBUG', false),
传递给 env 函数的第二个值是「默认值」。如果给定的键不存在环境变量,则会使用该值。
应用程序当前所处环境是通过 .env 文件中的 APP_ENV 变量确定的。你可以通过 App facade 中的 environment 方法 来访问此值:
$environment = App::environment();
你还可以传递参数给 environment 方法,以检查当前的环境配置是否与给定值匹配。 如果与给定的任意值匹配,该方法将返回 true:
if (App::environment('local')) {
// 当前环境是 local
}
if (App::environment(['local', 'staging'])) {
// 当前的环境是 local 或 staging...
}
Tip:应用程序当前所处环境检测可以被 服务器级的 APP_ENV 环境变量 覆盖。这在为相同的应用程序配置不同的环境时是非常有用的,这样你可以在你的服务器配置中为给定的主机设置与其匹配的给定的环境。
当一个异常未被捕获并且 APP_DEBUG 环境变量为 true 时, 调试页面会显示所有的环境变量和内容。在某些情况下你可能想隐藏某些变量 。你可以通过设置 config/app.php 配置文件中的 debug_blacklist 选项来完成这个操作。
环境变量、服务器或者请求数据中都有一些变量是可用的。因此,你可能需要将 $_ENV 和 $_SERVER 的变量加入到黑名单中:
return [
// ...
'debug_blacklist' => [
'_ENV' => [
'APP_KEY',
'DB_PASSWORD',
],
'_SERVER' => [
'APP_KEY',
'DB_PASSWORD',
],
'_POST' => [
'password',
],
],
];
你可以轻松地在 应用程序的任何位置 使用 全局 config 函数来访问配置值 。配置值的访问可以 使用「点」语法 ,这其中 包含 了要访问的 文件 和 选项的名称 。 还可以指定默认值,如果配置选项不存在,则返回默认值 :
$value = config('app.timezone');
要在 运行时设置配置值 ,传递一个数组给 config 函数:
config(['app.timezone' => 'America/Chicago']);
为了给你的 应用程序提升速度 ,你应该 使用 Artisan 命令 config:cache 将 所有的配置文件缓存到单个文件中 。这会把你的应用程序中所有的配置选项合并成一个单一的文件,然后框架会快速加载这个文件。
通常来说,你应该把运行 php artisan config:cache 命令作为 生产环境部署常规工作的一部分 。这个命令 不应在本地开发环境下运行 ,因为配置选项在应用程序开发过程中是经常需要被更改的。
注意:如果在部署过程中执行 config:cache 命令,那你应该确保只从配置文件内部调用 env 函数。一旦配置被缓存,.env 文件将不再被加载,所有对 env 函数的调用都将返回 null
当应用程序处于 维护模式 时,所有对应用程序的请求都显示为一个自定义视图。这样 可以在更新或执行维护时轻松地「关闭」你的应用程序 。 维护模式检查包含在应用程序的默认中间件栈中。如果应用程序处于维护模式,则将 抛出一个状态码为 503 的 MaintenanceModeException 异常。
要 启用维护模式 ,只需执行下面的 Artisan 的 down 命令 :
php artisan down
你还可以向 down 命令提供 message 和 retry 选项。其中 message 选项的值可用于显示或记录自定义消息,而 retry 值可用于设置 HTTP 响应头中 Retry-After 的值:
php artisan down --message="Upgrading Database" --retry=60
即使在维护模式下,也可以使用命令 allow 选项允许特定的 IP 地址或网络访问应用程序:
php artisan down --allow=127.0.0.1 --allow=192.168.0.0/16
要 关闭维护模式 ,请使用 up 命令 :
php artisan up
Tip:你可以通过修改 resources/views/errors/503.blade.php 模板文件来自定义默认维护模式模板。
当应用程序处于维护模式时, 不会处理 队列任务 。而这些任务会在应用程序退出维护模式后再继续处理。
维护模式会 导致应用程序有数秒的停机(不响应)时间 ,因此你可以考虑使用像 Envoyer 这样的替代方案,以便与 Laravel 完成 零停机时间部署 。
默认的 Laravel 应用结构旨在为不同大小的应用提供一个很好的起点。当然,您可以随意组织您的应用程序。Laravel 对任何给定类的位置几乎没有任何限制,只要它们能被 Composer 自动加载。
当开始使用 Laravel 时,许多开发人员都因缺少 models 目录而感到困惑。然而,缺少这样的目录是故意的。我们发现「模型」含糊不清,因为不同的人对「模型」有不同的理解。一些开发者把应用的「模型」称为其所有业务逻辑的总体,而另一些人将「模型」称为与关系数据库交互的类。
出于这个原因,我们默认把 Eloquent 的模型放在 app 目录下,并允许开发人员将它们放在其他地方。
app 目录 包含应用程序的核心代码 。你应用中 几乎所有的类都应该放在这里 。稍后我们会更深入地了解这个目录的细节。
bootstrap 目录 包含引导框架的 app.php 文件 。该目录 还包含了一个 cache 目录 , cache 目录下 存放着框架生成的用来提升性能的文件 ,比如路由和服务缓存文件。
config 目录,顾名思义, 包含应用程序所有的配置文件 。我们鼓励你通读这些文件,以便帮助你熟悉所有可用的选项。
database 目录 包含数据填充和迁移文件以及模型工厂类 。你还可以把它作为 SQLite 数据库存放目录。
public 目录 包含了入口文件 index.php ,它是 进入应用程序 的 所有请求的入口点 。此目录 还包含了一些你的资源文件 (如图片、JavaScript 和 CSS)。
resources 目录 包含了视图和未编译的资源文件(如 LESS、SASS 或 JavaScript) 。此目录 还包含你所有的语言文件 。
routes 目录 包含了应用的所有路由定义 ,Laravel 默认包含了几个路由文件: web.php、api.php、 console.php 和 channels.php 。
web.php 文件 包含 RouteServiceProvider 放置在 web 中间件组中的路由 ,它提供会话状态、CSRF 防护和 cookie 加密。 如果你的应用不提供无状态的、RESTful 风格的 API,则所有的路由都应该在 web.php 文件中定义。
api.php 文件 包含 RouteServiceProvider 放置在 api 中间件组中的路由 ,它提供了频率限制。这些路由都是无状态的,所以 通过这些路由进入应用请求旨在通过令牌进行身份认证,并且不能访问会话状态。
console.php 文件是 定义所有基于控制台命令闭包函数的地方 。每个闭包函数都被绑定到一个命令实例并且允许和命令行 IO 方法进行简单的交互。尽管这些文件没有定义 HTTP 路由,但 它也将基于控制台的入口点(路由)定义到应用程序中 。
channels.php 用来 注册你的应用支持的所有的事件广播渠道的地方 。
storage 目录 包含编译后的 Blade 模板、session 会话生成的文件、缓存文件以及框架生成的其他文件 。这个目录 被细分成 app 、 framework 和 logs 三个子目录 。app 目录可以用来存储应用生成的任何文件。 framework 目录用来存储框架生成的文件和缓存。最后, logs 目录包含应用的日志文件。
storage/app/public 可以用来 存储用户生成的文件 ,比如 需要公开访问的用户头像 。你应该创建一个 public/storage 的软链接指向这个目录。你可以直接通过 php artisan storage:link 命令来创建此链接。
tests 目录 包含自动化测试文件 。在 PHPUnit 有现成的范例供你参考。每个 测试类 都应该以 Test 作为后缀 。你可以 使用 phpunit 或者 php vendor/bin/phpunit 命令来运行测试。
vendor 目录 包含你所有的 Composer 依赖包 。
你的大部分应用程序都位于 app 目录中. 默认情况下, 此目录的命名空间为 App,并通过 Composer 使用 PSR-4 自动加载标准 自动加载。
app 目录 包含额外的各种目录 ,比如: Console, Http, 和 Providers 。将 Console 和 Http 目录视为向应用程序的核心 提供API 。HTTP 协议和 CLI 都是与应用程序交互的机制, 但实际上并不包含应用程序逻辑。换句话说,它们是向你的应用程序发出命令的两种方式。 Console 包含所有的 Artisan 命令,而 Http 目录包含你的控制器,中间件和请求。
当你 使用 make Artisan 命令生成类 时,将在 app 目录下生成各种其它目录 . 因此, 例如, app/Jobs 目录直到你执行 make:job Artisan 命令去生成一个作业类之前将不存在.
Tip:许多类通过 Artisan 命令生成在 app 目录中。为了 查看可用的命令 ,在你的终端运行 php artisan list make 命令
Broadcasting 目录 包含应用程序的所有广播通道类 。这些类使用 make:channel 命令生成。此目录默认是不存在的,但是当你创建第一个通道时它将被创建。要了解更多的关于频道的信息,查看有关文档 事件广播 。
Console 目录 包含应用程序的所有自定义 Artisan 命令 。这些命令可以使用 make:command 命令生成。此目录也安置了 控制台内核 ,在其中你可以注册自定义的 Artisan 命令,并定义 计划任务 。
默认情况下这个目录是不存在的 ,但你可以通过 event:generate 和 make:event Artisan 命令去创建。Events 目录安置 事件类 。事件可用于提醒您的 应用程序 其它部分 已发生给定操作 ,提供了极大的灵活性和解耦。
Exceptions 目录 包含应用程序的异常处理 ,并且也是一个放置应用程序抛出任何异常的好地方。如果你想自定义异常的记录和渲染方式,你应该修改此目录中的 Handler 类。
Http 目录 包含你的控制器,中间件和表单请求 。 处理进入应用程序请求的所有逻辑 几乎都放置在此目录。
默认情况下这个目录是不存在的 ,但如果你执行 make:job Artisan 命令时,它将被创建出来。Jobs 目录安置应用程序的 队列任务 。Jobs 可由应用程序排队作业,也可以在当前请求的生命周期内同步运行 。在 当前请求期间同步运行 的 Jobs 有时会称为『 命令 』,因为它们是 命令模式 的一个实现。
默认情况下这个目录是不存在的 ,但如果你执行了 event:generate 或者 make:listener Artisan 命令时,它将会被创建出来。Listeners 目录 包含 事件 的处理类 。 事件侦听器 接收一个事件实例并执行逻辑以响应被触发的事件。例如,一个 UserRegistered 事件可能被 SendWelcomeEmail 侦听器处理。
默认情况下这个目录是不存在的 ,但如果你执行 make:mail Artisan 命令,它将被创建出来。Mail 目录 包含应用程序发送邮件的所有类 。邮件对象允许你去构建一个封装所有逻辑的邮件类,在这个类中可以使用 Mail::send 方法发送邮件。
默认情况下这个目录是不存在的 ,但如果你执行 make:notification Artisan 命令,它将被创建出来。Notifications 目录 包含应用程序的发送的所有『事务』通知 ,比如关于应用程序中发生的事件的简单通知。Laravel 的 通知功能 抽象了 通过各种驱动(如:电子邮件,Slack,SMS 或者存储在数据库中)去发送通知 。
默认情况下这个目录是不存在的 ,但如果你执行 make:policy Artisan 命令,它将被创建出来。Policies 目录 包含应用程序的授权策略类 。 策略用于确定一个用户是否对一个资源能否执行一个给定的操作 。有关更多信息,查看 授权文档。
Providers 目录 包含应用程序的所有 服务提供者 。服务提供者通过在服务容器中绑定服务引导应用程序,注册事件或者准备为应用程序即将到来的请求执行其它任何任务。
在一个新的 Laravel 应用中,此目录已经包含一些提供者。你可以根据需要随意将你自己的提供者添加到此目录中。
默认情况下这个目录是不存在的 ,但如果你执行 make:rule Artisan 命令,它将被创建出来。Rules 目录 包含应用程序的自定义验证规则对象 。 规则用于将复杂的验证逻辑封装在一个简单对象中 。关于更多信息,查看 验证文档。
当你准备部署你的 Laravel 应用到生产环境时,请确保几个重要的注意点以保证你的应用能尽可能高效的运行。以下我们将会覆盖几个重点来确保你的 Laravel 应用部署得当。
如果你想要部署你的应用到 Nginx 服务器上,你可能会用到下面这个配置文件作为一个开始来配置你的 Web 服务器。很有可能,这个文件需要根据你的服务器配置来做一些自定义的修改。如果你需要协助来管理你的服务器,可以考虑使用 Laravel Forge :
server {
listen 80;
server_name example.com;
root /example.com/public;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
index index.html index.htm index.php;
charset utf-8;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
error_page 404 /index.php;
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php7.2-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.(?!well-known).* {
deny all;
}
}
当你 准备往生产环境部署应用时 ,确保你 优化 了你的 Composer 类的自动加载映射 ,这样可以使 Composer 可以很快的找到正确的加载文件 去加载给定的类:
composer install --optimize-autoloader --no-dev
小提示:除了优化自动加载器,你 还应该确保在你的项目代码仓库中包含 了 composer.lock 这个文件。当你的项目代码中有 composer.lock 这个文件时,便可以更快的安装项目中需要的依赖项。
当你 将应用程序部署到生产环境时 ,你应当确保在你 部署过程中运行 config:cache Artisan 命令 :
php artisan config:cache
此命令 将所有 Laravel 的配置文件合并到一个缓存文件 ,这将极大地减少框架在加载配置值时必须对文件系统进行访问的次数。
注意:如果在你部署过程中执行 config:cache 命令,你应当确保你仅从你的配置文件中调用 env 函数。一旦配置被缓存,.env 文件将不被加载并且对 env 函数的所有调用将返回 null。
如果你想构建具有许多路由的大型应用程序,你应当确保在你部署的过程中运行 route:cache Artisan 命令:
php artisan route:cache
此命令 将为所有路由注册缩减到一个缓存文件中的单个方法调用 ,从而在注册数百个路由时提高了路由注册的性能。
注意:由于此功能使用 PHP 序列化,你仅能缓存专门使用基于控制器路由的应用程序路由。PHP 不能序列化闭包路由。
如果你还没有准备好管理自己的服务器配置,或者不熟悉配置对运行强大的 Laravel 应用程序所需的各种服务, Laravel Forge 是一个不错的选择。
Laravel Forge 能在各种基础设施提供商(如:DigitalOcean,Linode,AWS 等等)上创建服务器。另外,Forge 安装和管理构建强大 Laravel 应用程序所需的所有工具,比如:Nginx,MySQL,Redis,Memcached,Beanstalk 等等。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yWfnb6Yi-1689076777388)(http://flt-pan.58heshihu.com/blog/typecho/ljxofo6g.png)]
在「日常生活」中 使用任何工具时,如果理解了该工具的工作原理。那么用起来就会更加得心应手。应用开发也是如此,当你能真正懂得一个功能背后实现原理时,用起来会更加顺手,方便。
文档存在目的是为了让你更加清晰地了解 Laravel 框架是如何工作。通过框架进行全面了解,让一切都不再感觉很「神奇」。相信我,这有助于你更加清楚自己在做什么,对自己想做的事情更加胸有成竹。就算你不明白所有的术语,也不用因此失去信心!只要多一点尝试、学着如何运用,随着你浏览文档的其他部分,你的知识一定会因此增长。
Laravel 应用的 所有请求入口 都是 public/index.php 文件。而所有的请求都是经由你的 Web 服务器(Apache/Nginx)通过配置引导到这个文件。 index.php 文件代码并不多,但是,这里是加载框架其它部分的起点。
index.php 文件加载 Composer 生成的自动加载设置 ,然后从 bootstrap/app.php 脚本中检索 Laravel 应用程序的实例 。 Laravel 本身采取的 第一个动作是创建一个应用程序 / 服务容器 。
接下来, 根据进入应用程序的请求类型来将传入的请求发送到 HTTP 内核或控制台内核 。而这两个内核是用来作为所有请求都要通过的中心位置。 现在,我们先看看位于 app/Http/Kernel.php 中的 HTTP 内核。
HTTP 内核继承了 Illuminate\Foundation\Http\Kernel 类 ,该类定义了一个 bootstrappers 数组。 这个数组中的类在请求被执行前运行,这些 bootstrappers 配置了错误处理, 日志, 检测应用环境,以及其它在请求被处理前需要执行的任务。
HTTP 内核还 定义了所有请求被应用程序处理之前必须经过的 HTTP 中间件 ,这些中间件 处理 HTTP 会话 读写、判断应用是否处于维护模式、 验证 CSRF 令牌 等等。
HTTP 内核的 handle 方法签名相当简单:获取一个 Request ,返回一个 Response。 可以把该内核想象作一个代表整个应用的大黑盒子,输入 HTTP 请求,返回 HTTP 响应。
服务提供者
内核启动操作中最重要的便是你应用的 服务提供者 了。所有应用下的服务提供者均配置到了 config/app.php 配置文件中的 providers 数组中。 第一步,所有服务提供者的 register 方法会被调用,然后一旦所有服务提供者均注册后, boot 方法才被调用。
服务提供者给予框架开启多种多样的组件,像数据库,队列,验证器,以及路由组件。只要被启动服务提供者就可支配框架的所有功能,所以服务提供者也是 Laravel 整个引导周期最重要组成部分。
请求调度
一旦启动且所有服务提供者被注册,Request 会被递送给路由。路由将会调度请求,交给绑定的路由或控制器,也当然包括路由绑定的中间件。
聚焦服务提供者
服务提供者是 Laravel 真正意义的生命周期中的关键。应用实例一旦创建,服务提供者就被注册,然后请求被启动的应用接管。简单吧!
牢牢掌握服务提供者的构建和其对 Laravel 应用处理机制的原理是非常有价值的。当然,你的应用默认的服务提供会存放在 app/Providers 下面。
默认的, AppServiceProvider 是空白的。这个提供者是一个不错的位置,用于你添加应用自身的引导处理和服务容器绑定。当然,大型项目中,你可能希望创建数个粒度更精细的服务提供者。
Laravel 服务容器 是一个用于管理类的依赖和执行依赖注入的强大工具 。 依赖注入 这个花哨名词 实质上是指:类的依赖通过构造函数,或者某些情况下通过 「setter」 方法 「注入」到类中。
来看一个简单的例子:
namespace App\Http\Controllers;
use App\User;
use App\Repositories\UserRepository;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
/**
* 用户存储库的实现。
*
* @var UserRepository
*/
protected $users;
/**
* 创建新的控制器实例。
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
*显示指定用户的 profile。
*
* @param int $id
* @return Response
*/
public function show($id)
{
$user = $this->users->find($id);
return view('user.profile', ['user' => $user]);
}
}
在这个例子中, 控制器 UserController 需要从数据源获取 users。因此,我们要 注入 一个能够获取 users 的服务 。在当前上下文中,我们的 UserRepository 很可能是使用 Eloquent 从数据库中获取 user 信息。 然而,由于 repository 是被注入的,所以我们可以轻易地将其切换为另一个的实现。这种注入方式的便利之处还体现在当我们为应用编写测试时,我们还可以轻松地 “模拟” 或创建 UserRepository 的虚拟实现。
想要构建强大的大型应用,至关重要的一件事是:要深刻地理解 Laravel 服务容器。当然,为 Laravel 的核心代码做出贡献也一样。
几乎所有的服务容器绑定都会在 服务提供者 中注册,下面示例中的大多数将演示如何在该上下文(服务提供者)中使用容器。
Tip:如果某个容器不依赖于任何接口就没必要去绑定类在这个容器里。容器不需要指定如何构建这些对象,因为它可以使用反射来自动解析这些对象。
在服务提供器中,你总是可以通过 $this->app 属性访问容器 。我们可以通过容器的 bind 方法注册绑定 ,bind 方法的 第一个参数为要绑定的类/接口名 , 第二个参数是一个返回类实例的 Closure :
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
注意,我们接受容器本身作为解析器的参数。然后,我们可以使用容器来解析正在构建的对象的子依赖。
singleton 方法将类或接口绑定到只解析一次的容器中 。一旦单例绑定被解析,相同的对象实例会在随后的调用中返回到容器中:
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
你也可以使用 instance 方法将现有对象实例绑定到容器中 。给定的实例会始终在随后的调用中返回到容器中:
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\API', $api);
当你 有一个类不仅需要接受一个注入类,还需要注入一个基本值(比如整数) 。你可以使用 上下文绑定 来轻松 注入你的类需要的任何值 :
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
服务容器有 一个很强大的功能 ,就是 支持绑定接口到给定的实现 。例如,如果我们有个 EventPusher 接口 和一个 RedisEventPusher 实现。一旦我们 写完了 EventPusher 接口的 RedisEventPusher 实现,我们就可以在服务容器中注册它 ,像这样:
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
这么做相当于 告诉容器:当一个类需要实现 EventPusher 时,应该注入 RedisEventPusher 。现在我们就可以在构造函数或者任何其他通过服务容器注入依赖项的地方使用类型提示注入 EventPusher 接口:
use App\Contracts\EventPusher;
/**
* Create a new class instance.
*
* @param EventPusher $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
$this->pusher = $pusher;
}
有时你可能 有两个类使用了相同的接口,但你希望各自注入不同的实现 。例如, 有两个控制器可能依赖了 Illuminate\Contracts\Filesystem\Filesystem 契约. Laravel 提供了一个简单的,优雅的接口来定义这个行为:
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when([VideoController::class, UploadController::class])
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});
有时候,你可能需要 解析某个「分类」下的所有绑定 。 比如, 你可能正在构建一个报表的聚合器,它接收一个包含不同Report 接口实现的数组。注册Report实现之后,你可以使用tag方法给他们分配一个标签:
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
一旦服务被标记,你就可以通过 tagged方法轻松地解析它们:
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
extend 方法 可以修改已解析的服务 。比如,当一个服务被解析后,你可以添加额外的代码来修饰或者配置它。 extend 方法接受一个闭包,该闭包唯一的参数就是这个服务, 并返回修改过的服务:
$this->app->extend(Service::class, function ($service) {
return new DecoratedService($service);
});
make
方法你可以使用 ** make 方法从容器中解析出类实例** 。 make 方法 接收你想要解析的类或接口的名字 :
$api = $this->app->make('HelpSpot\API');
如果你的代码 处于无法访问 $app 变量的位置 ,则可用 全局辅助函数resolve 来解析:
$api = resolve('HelpSpot\API');
如果类依赖不能通过容器解析,你可以通过将它们作为关联数组作为 makeWith 方法的参数注入:
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
另外,并且更重要的是,你可以 简单地使用「类型提示」 的方式在类的构造函数中注入那些需要容器解析的依赖项 ,包括 控制器,事件监听器, 队列任务,中间件,等 。实际上,这才 是大多数对象应该被容器解析的方式 。
例如,你可以在控制器的构造函数中添加一个 repository 的类型提示,然后这个 repository 将会被自动解析并注入类中:
namespace App\Http\Controllers;
use App\Users\Repository as UserRepository;
class UserController extends Controller
{
/**
* The user repository instance.
*/
protected $users;
/**
* Create a new controller instance.
*
* @param UserRepository $users
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
/**
* Show the user with the given ID.
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}
}
服务容器每次解析对象会触发一个事件 ,你可以使用 resolving 方法监听这个事件 :
$this->app->resolving(function ($object, $app) {
// Called when container resolves object of any type...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// Called when container resolves objects of type "HelpSpot\API"...
});
正如你所看到的,被解析的对象将会被传入回调函数,这使得你能够在对象被传给调用者之前给它设置额外的属性。
Laravel 的服务容器实现了 PSR-11 接口。因此,你可以使用 PSR-11 容器 『接口类型提示』 来获取 Laravel 容器的实例:
use Psr\Container\ContainerInterface;
Route::get('/', function (ContainerInterface $container) {
$service = $container->get('Service');
//
});
如果无法解析给定的标识符,则将会引发异常。未绑定标识符时,会抛出 Psr\Container\NotFoundExceptionInterface 异常。如果标识符已绑定但无法解析,会抛出 Psr\Container\ContainerExceptionInterface 异常。
服务提供者是 所有 Laravel 应用程序的引导中心 。你的应用程序,以及 通过服务器引导的 Laravel 核心服务都是通过服务提供器引导。
但是,「 引导 」是什么意思呢? 通常,我们可以理解为 注册 ,比如注册服务容器绑定,事件监听器,中间件,甚至是路由。 服务提供者是配置应用程序的中心 。
当你打开 Laravel 的 config/app.php 文件时,你会看到 providers 数组。 数组中的内容是应用程序要加载的所有服务提供者的类。当然,其中有很多 「延迟」 提供者,他们并不会在每次请求的时候都加载,只有他们的服务实际被需要时才会加载。
本篇你将会学到如何编写自己的服务提供者,并将其注册到你的 Laravel 应用程序中
所有的服务提供者都会继承 Illuminate\Support\ServiceProvider 类。 大多服务提供者都包含一个 register 和一个boot 方法。在 register 方法中, 你只需要将服务绑定到服务容器。而不要尝试在 register 方法中注册任何监听器,路由,或者其他任何功能
使用 Artisan 命令行工具,通过 make:provider 命令可以生成一个新的提供者:
php artisan make:provider RiakServiceProvider
如上所述,在 register 方法中,你只需要将服务绑定到服务容器中。而不要尝试在 register 方法中注册任何监听器,路由,或者其他任何功能。否则,你可能会意外地使用到尚未加载的服务提供者提供的服务。
让我们来看一个基础的服务提供者。在任何服务提供者方法中,你总是通过 $app 属性来访问服务容器:
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider
{
/**
* 在服务容器里注册
*
* @return void
*/
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection(config('riak'));
});
}
}
这个服务提供者只是定义了一个 register 方法,并且使用这个方法在服务容器中定义了一个 Riak\Connection 接口。如果你不理解服务容器的工作原理,请查看其文档。
bindings
和 singletons
的特性
如果你的服务提供器注册了许多简单的绑定,你可能想 用 bindings 和 singletons 属性替代手动注册每个容器绑定。当服务提供器被框架加载时,将自动检查这些属性并注册相应的绑定
namespace App\Providers;
use App\Contracts\ServerProvider;
use App\Contracts\DowntimeNotifier;
use Illuminate\Support\ServiceProvider;
use App\Services\PingdomDowntimeNotifier;
use App\Services\DigitalOceanServerProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* 设定所有的容器绑定的对应关系
*
* @var array
*/
public $bindings = [
ServerProvider::class => DigitalOceanServerProvider::class,
];
/**
* 设定所有的单例模式容器绑定的对应关系
*
* @var array
*/
public $singletons = [
DowntimeNotifier::class => PingdomDowntimeNotifier::class,
ServerToolsProvider::class => ServerToolsProvider::class,
];
}
如果我们要在 服务提供者中注册 一个 视图合成器 该怎么做? 这就需要用到 boot 方法了。 该方法在所有服务提供者被注册以后才会被调用 , 这就是说我们可以在 其中访问框架已注册的所有其它服务 :
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* 启动所有的应用服务。
*
* @return void
*/
public function boot()
{
view()->composer('view', function () {
//
});
}
}
启动方法的依赖注入
你可以为服务提供者的 boot 方法设置类型提示。 服务容器 会自动注入你所需要的依赖:
use Illuminate\Contracts\Routing\ResponseFactory;
public function boot(ResponseFactory $response)
{
$response->macro('caps', function ($value) {
//
});
}
所有服务提供者都是通过配置文件 config/app.php 进行注册。该文件包含了一个列出所有服务提供者名字的 providers 数组,默认情况下,其中列出了所有核心服务提供者,这些服务提供者启动 Laravel 核心组件,比如邮件、队列、缓存等等。
要注册提供器,只需要将其添加到数组:
'providers' => [
// 其他服务提供者
App\Providers\ComposerServiceProvider::class,
],
如果你的服务提供者 只 在 服务容器 中注册,可以选择延迟加载该绑定直到注册绑定的服务真的需要时再加载,延迟加载这样的一个提供者将会提升应用的性能,因为它不会在每次请求时都从文件系统加载。
Laravel 编译并保存延迟服务提供者提供的所有服务的列表,以及其服务提供者类的名称。因此,只有当你在尝试解析其中一项服务时,Laravel 才会加载服务提供者。
要延迟加载提供者,需要实现 \Illuminate\Contracts\Support\DeferrableProvider 接口并置一个 provides 方法。这个 provides 方法返回该提供者注册的服务容器绑定:
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
use Illuminate\Contracts\Support\DeferrableProvider;
class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* 注册服务提供者。
*
* @return void
*/
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection($app['config']['riak']);
});
}
/**
* 获取由提供者提供的服务。
*
* @return array
*/
public function provides()
{
return [Connection::class];
}
}
Facades 为应用的 服务容器 提供了一个「静态」 接口。Laravel 自带了很多 Facades,可以访问绝大部分功能。Laravel Facades 实际是 服务容器中底层类的 「静态代理」 ,相对于传统静态方法,在使用时能够提供更加灵活、更加易于测试、更加优雅的语法。
所有的 Laravel Facades 都定义在 Illuminate\Support\Facades 命名空间下。所以,我们可以轻松的使用 Facade :
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
在 Laravel 文档中,有很多示例代码都会使用 Facades 来演示框架的各种功能。
Facades 有很多优点,它提供了简单,易记的语法,从而 无需手动注入或配置长长的类名 。此外,由于他们 对 PHP 静态方法的独特调用,使得测试起来非常容易 。
然而,在使用 Facades 时,有些地方需要 特别注意 。使用 Facades 时最主要的危险就是会 引起类作用范围的膨胀 。由于 Facades 使用起来非常简单并且不需要注入,就会使得我们不经意间在单个类中使用许多 Facades ,从而导致类变得越来越大。然而使用依赖注入的时候,使用的类越多,构造方法就会越长,在视觉上注意到这个类有些庞大了。因此在使用 Facades 的时候,要特别注意控制类的大小,让类的作用范围保持短小。
Tip:在开发与 Laravel 进行交互的第三方扩展包时,最好选择注入 Laravel 契约 而不使用 Facades 。因为扩展包是在 Laravel 之外构建,你无法使用 Laravel Facades 测试辅助函数
依赖注入 的主要 好处之一是能交换注入类的实现 。在 测试 的时候非常有用,因为你可以注入一个 mock 或者 stub,并断言 stub 上的各种方法。
通常,真正的静态方法是不可能 mock 或 stub 的。但是 Facades 使用动态方法对服务容器中解析出来的对象方法的调用进行了代理 ,我们也 可以像测试注入类实例一样测试 Facades 。比如,像下面的路由:
use Illuminate\Support\Facades\Cache;
Route::get('/cache', function () {
return Cache::get('key');
});
我们可以带上我们期望的参数编写下面的测试代码来验证 Cache::get 方法:
use Illuminate\Support\Facades\Cache;
/**
* 一个基础功能的测试用例。
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
除了 Facades,Laravel 还包含各种 『 辅助函数 』 来实现这些常用功能,比如 生成视图、触发事件、任务调度或者发送 HTTP 响应 。许多辅助函数都有与之对应的 Facades 。例如,下面这个 Facades 和辅助函数的作用是一样的:
return View::make('profile');
return view('profile');
Facade 和辅助函数之间没有实际的区别 。当你使用辅助函数时,你可以像测试相应的 Facade 那样进行测试。例如,下面的路由:
Route::get('/cache', function () {
return cache('key');
});
在底层实现, 辅助函数 cache 实际是 调用 Cache 这个 Facade 的 get 方法 。因此,尽管我们使用的是辅助函数,我们依然可以带上我们期望的参数编写下面的测试代码来验证该方法:
use Illuminate\Support\Facades\Cache;
/**
* 一个基础功能的测试用例。
*
* @return void
*/
public function testBasicExample()
{
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
$this->visit('/cache')
->see('value');
}
在 Laravel 应用中,Facade 就是 一个可以从容器访问对象的类 。其中核心的部件就是 Facade 类。不管是 Laravel 自带的 Facades,还是自定义的 Facades,都继承自 Illuminate\Support\Facades\Facade 类。
Facade 基类使用了 __callStatic() 魔术方法,直到对象从容器中被解析出来后,才会进行调用。在下面的例子中,调用了 Laravel 的缓存系统。通过浏览这段代码,可以假定在 Cache 类中调用了静态方法 get:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Cache;
class UserController extends Controller
{
/**
* 显示给定用户的信息。
*
* @param int $id
* @return Response
*/
public function showProfile($id)
{
$user = Cache::get('user:'.$id);
return view('profile', ['user' => $user]);
}
}
注意在上面这段代码中,我们『导入』了 Cache Facade。这个 Facade 作为访问 Illuminate\Contracts\Cache\Factory 接口底层实现的代理。我们使用 Facade 进行的任何调用都将传递给 Laravel 缓存服务的底层实例。
如果我们看一下 Illuminate\Support\Facades\Cache 这个类,你会发现类中根本没有 get 这个静态方法:
class Cache extends Facade
{
/**
* 获取组件的注册名称。
*
* @return string
*/
protected static function getFacadeAccessor() { return 'cache'; }
}
Cache Facade 继承了 Facade 类,并且定义了 getFacadeAccessor() 方法。这个方法的作用是返回服务容器绑定的名称。当用户调用 Cache Facade 中的任何静态方法时,Laravel 会从 服务容器 中解析 cache 绑定以及该对象运行所请求的方法(在这个例子中就是 get 方法)。
使用实时 Facades,你 可以将应用程序中的任何类视为 Facade 。为了说明这是如何使用的,我们来看看另一种方法。例如,假设我们的 Podcast 模型有一个 publish 方法。然而,为了发布 Podcast,我们需要注入一个 Publisher 实例:
namespace App;
use App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布 Podcast。
*
* @param Publisher $publisher
* @return void
*/
public function publish(Publisher $publisher)
{
$this->update(['publishing' => now()]);
$publisher->publish($this);
}
}
将发布者的实现注入到该方法中,我们可以轻松地测试这种方法,因为我们可以模拟注入的发布者。但是,它要求我们每次调用 publish 方法时都要传递一个发布者实例。使用实时的 Facades,我们可以保持同样的可测试性,而不需要显式地通过 Publisher 实例。要生成实时 Facade,请在导入类的名称空间中加上 Facades:
namespace App;
use Facades\App\Contracts\Publisher;
use Illuminate\Database\Eloquent\Model;
class Podcast extends Model
{
/**
* 发布 Podcast。
*
* @return void
*/
public function publish()
{
$this->update(['publishing' => now()]);
Publisher::publish($this);
}
}
当使用实时 Facade 时,发布者实现将通过使用 Facades 前缀后出现的接口或类名的部分来解决服务容器的问题。在测试时,我们可以使用 Laravel 的内置 facade 测试辅助函数来模拟这种方法调用:
namespace Tests\Feature;
use App\Podcast;
use Tests\TestCase;
use Facades\App\Contracts\Publisher;
use Illuminate\Foundation\Testing\RefreshDatabase;
class PodcastTest extends TestCase
{
use RefreshDatabase;
/**
* 一个测试演示。
*
* @return void
*/
public function test_podcast_can_be_published()
{
$podcast = factory(Podcast::class)->create();
Publisher::shouldReceive('publish')->once()->with($podcast);
$podcast->publish();
}
}
在下面你可以找到 每个 Facade 类及其对应的底层类 。这是一个查找给定 Facade 类 API 文档的工具。服务容器绑定 的关键信息也包含在内。
Facade Class 服务容器绑定
App Illuminate\Foundation\Application app
Artisan Illuminate\Contracts\Console\Kernel artisan
Auth Illuminate\Auth\AuthManager auth
Auth (Instance) Illuminate\Contracts\Auth\Guard auth.driver
Blade Illuminate\View\Compilers\BladeCompiler blade.compiler
Broadcast Illuminate\Contracts\Broadcasting\Factory
Broadcast (Instance) Illuminate\Contracts\Broadcasting\Broadcas…
Bus Illuminate\Contracts\Bus\Dispatcher
Cache Illuminate\Cache\CacheManager cache
Cache (Instance) Illuminate\Cache\Repository cache.store
Config Illuminate\Config\Repository config
Cookie Illuminate\Cookie\CookieJar cookie
Crypt Illuminate\Encryption\Encrypter encrypter
DB Illuminate\Database\DatabaseManager db
DB (Instance) Illuminate\Database\Connection db.connection
Event Illuminate\Events\Dispatcher events
File Illuminate\Filesystem\Filesystem files
Gate Illuminate\Contracts\Auth\Access\Gate
Hash Illuminate\Contracts\Hashing\Hasher hash
Lang Illuminate\Translation\Translator translator
Log Illuminate\Log\LogManager log
Mail Illuminate\Mail\Mailer mailer
Notification Illuminate\Notifications\ChannelManager
Password Illuminate\Auth\Passwords\PasswordBrokerMa… auth.password
Password (Instance) Illuminate\Auth\Passwords\PasswordBroker auth.password.broker
Queue Illuminate\Queue\QueueManager queue
Queue (Instance) Illuminate\Contracts\Queue\Queue queue.connection
Queue (Base Class) Illuminate\Queue\Queue
Redirect Illuminate\Routing\Redirector redirect
Redis Illuminate\Redis\RedisManager redis
Redis (Instance) Illuminate\Redis\Connections\Connection redis.connection
Request Illuminate\Http\Request request
Response Illuminate\Contracts\Routing\ResponseFacto…
Response (Instance) Illuminate\Http\Response
Route Illuminate\Routing\Router router
Schema Illuminate\Database\Schema\Builder
Session Illuminate\Session\SessionManager session
Session (Instance) Illuminate\Session\Store session.store
Storage Illuminate\Filesystem\FilesystemManager filesystem
Storage (Instance) Illuminate\Contracts\Filesystem\Filesystem filesystem.disk
URL Illuminate\Routing\UrlGenerator url
Validator Illuminate\Validation\Factory validator
Validator (Instance) Illuminate\Validation\Validator
View Illuminate\View\Factory view
View (Instance) Illuminate\View\View
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4OVXu7uN-1689076777389)(http://flt-pan.58heshihu.com/blog/typecho/ljy58ad1.png)]
Laravel 的契约是 一组接口,它们由框架提供并定义了核心服务 。 例如, Illuminate\Contracts\Queue\Queue 契约 定义了队列任务需要的方法,而 Illuminate\Contracts\Mail\Mailer 契约定义了发送邮件需要的方法。
每一个契约都有框架提供的相应的实现 。例如, Laravel 提供了对多种驱动的队列实现,和一个由SwiftMailer驱动的邮件实现。
所有 Laravel 契约都在 它们各自的 GitHub 仓库。这为所有可用的契约以及扩展包开发者们可能用到的单个、解耦的包,提供了一个快速参考入口。
Laravel 的 Facades 和辅助函数提供了一种简便方式来使用 Laravel 服务而无需用到类型提示,也可在服务容器外部解析契约。 多数情况下,每个 Facade 都有一个等效的契约 。
和 Facades (不须要在你类中的构造函数去引用依赖) 不同的是,契约允许你给自己的类定义明确的依赖 。一些开发者更喜欢依赖被明确地定义出来,所以更倾向于使用契约,而其他开发者则享受于 Facades 带来的方便。
Tip:在大多数应用中,无论你更喜欢 Facades 还是契约,都是没问题的。然而如果你在搭建扩展包,那你应该强烈考虑使用契约,因为他们更便于在包的上下文中做测试。
综上所述,使用契约还是 Facades 很大程度上取决于你个人或者团队的喜好。契约和 Facades 均可以用来构建健壮的、充分测试过的 Laravel 应用。只要你保持类的职责单一,你会发现使用契约和 Facades 的实际差别是非常小的。
然而,你也许仍有许多关于契约的问题。比方说,为啥都用接口 ?用接口不是更复杂吗?让我们在接下来的内容(「低耦合」与「简明性」)中,提炼出原因。
首先,让我们来看一些缓存实现的高耦合代码。假设有下面代码:
namespace App\Orders;
class Repository
{
/**
* 缓存实例
*/
protected $cache;
/**
* 创建一个新的仓库实例
*
* @param \SomePackage\Cache\Memcached $cache
* @return void
*/
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}
/**
* 根据 ID 获取订单
*
* @param int $id
* @return Order
*/
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
在这个类中,代码与给定的缓存实现形成高度耦合。它的高度耦合是因为我们依赖了一个扩展包中具体的缓存类。如果该扩展包的 API 变了,那么我们的代码也将必须做出修改。
同理,如果我们想要将底层的缓存技术 ( Memcached ) 替换成另一种缓存技术 ( Redis ),我们得再次修改我们的代码库。我们的代码库不应该对谁提供的数据或者数据是怎么提供的有太多了解。
我们可以通过依赖一个简单的与扩展包无关的接口来改进我们的代码,来替代之前的实现方式:
namespace App\Orders;
use Illuminate\Contracts\Cache\Repository as Cache;
class Repository
{
/**
* 缓存实例
*/
protected $cache;
/**
* 创建一个新的仓库实例.
*
* @param Cache $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}
现在的代码不与任何特定的扩展包耦合了,甚至与 Laravel 都是无关的。由于契约扩展包不包含任何实现和依赖,你可以轻松地为给定的契约编写替代实现的代码,从而可以在不修改任何缓存代码的情况下替换缓存的实现。
当 Laravel 的所有服务都在简单的接口中简洁地定义时,很容易就可以确定给定服务提供的功能。契约是框架功能的简洁文档。
此外,当您依赖简单的接口时,您的代码更易于理解和维护。您可以参考一个简单,干净的接口,而不是在大型复杂的类中跟踪哪些方法可用。
那么,你如何实现契约呢?它实际上非常简单。
Laravel中的许多类都通过服务容器解析, 包括控制器,事件监听器,中间件,队列任务,甚至路由闭包。因此,要获得契约的接口实现,你可以在正在解析的类的构造函数中「类型提示」接口。
例如,看看这个事件监听器:
namespace App\Listeners;
use App\User;
use App\Events\OrderWasPlaced;
use Illuminate\Contracts\Redis\Factory;
class CacheOrderInformation
{
/**
* Redis工厂实例
*/
protected $redis;
/**
* 创建新的事件处理程序实例
*
* @param Factory $redis
* @return void
*/
public function __construct(Factory $redis)
{
$this->redis = $redis;
}
/**
* 处理事件.
*
* @param OrderWasPlaced $event
* @return void
*/
public function handle(OrderWasPlaced $event)
{
//
}
}
解析事件侦听器后,服务容器将读取类的构造函数上的类型提示,并注入适当的值。要了解有关在服务容器中注册内容的更多信息,请查看其文档。
此表提供了 所有 Laravel 契约及其等效 facades 的快速参考 :
契约 参考 Facade
Illuminate\Contracts\Auth\Access\Authoriza…
Illuminate\Contracts\Auth\Access\Gate Gate
Illuminate\Contracts\Auth\Authenticatable
Illuminate\Contracts\Auth\CanResetPassword
Illuminate\Contracts\Auth\Factory Auth
Illuminate\Contracts\Auth\Guard Auth::guard()
Illuminate\Contracts\Auth\PasswordBroker Password::broker()
Illuminate\Contracts\Auth\PasswordBrokerFa… Password
Illuminate\Contracts\Auth\StatefulGuard
Illuminate\Contracts\Auth\SupportsBasicAut…
Illuminate\Contracts\Auth\UserProvider
Illuminate\Contracts\Bus\Dispatcher Bus
Illuminate\Contracts\Bus\QueueingDispatche… Bus::dispatchToQueue()
Illuminate\Contracts\Broadcasting\Factory Broadcast
Illuminate\Contracts\Broadcasting\Broadcas… Broadcast::connection()
Illuminate\Contracts\Broadcasting\ShouldBr…
Illuminate\Contracts\Broadcasting\ShouldBr…
Illuminate\Contracts\Cache\Factory Cache
Illuminate\Contracts\Cache\Lock
Illuminate\Contracts\Cache\LockProvider
Illuminate\Contracts\Cache\Repository Cache::driver()
Illuminate\Contracts\Cache\Store
Illuminate\Contracts\Config\Repository Config
Illuminate\Contracts\Console\Application
Illuminate\Contracts\Console\Kernel Artisan
Illuminate\Contracts\Container\Container App
Illuminate\Contracts\Cookie\Factory Cookie
Illuminate\Contracts\Cookie\QueueingFactor… Cookie::queue()
Illuminate\Contracts\Database\ModelIdentif…
Illuminate\Contracts\Debug\ExceptionHandle…
Illuminate\Contracts\Encryption\Encrypter Crypt
Illuminate\Contracts\Events\Dispatcher Event
Illuminate\Contracts\Filesystem\Cloud Storage::cloud()
Illuminate\Contracts\Filesystem\Factory Storage
Illuminate\Contracts\Filesystem\Filesystem Storage::disk()
Illuminate\Contracts\Foundation\Applicatio… App
Illuminate\Contracts\Hashing\Hasher Hash
Illuminate\Contracts\Http\Kernel
Illuminate\Contracts\Mail\MailQueue Mail::queue()
Illuminate\Contracts\Mail\Mailable
Illuminate\Contracts\Mail\Mailer Mail
Illuminate\Contracts\Notifications\Dispatc… Notification
Illuminate\Contracts\Notifications\Factory Notification
Illuminate\Contracts\Pagination\LengthAwar…
Illuminate\Contracts\Pagination\Paginator
Illuminate\Contracts\Pipeline\Hub
Illuminate\Contracts\Pipeline\Pipeline
Illuminate\Contracts\Queue\EntityResolver
Illuminate\Contracts\Queue\Factory Queue
Illuminate\Contracts\Queue\Job
Illuminate\Contracts\Queue\Monitor Queue
Illuminate\Contracts\Queue\Queue Queue::connection()
Illuminate\Contracts\Queue\QueueableCollec…
Illuminate\Contracts\Queue\QueueableEntity
Illuminate\Contracts\Queue\ShouldQueue
Illuminate\Contracts\Redis\Factory Redis
Illuminate\Contracts\Routing\BindingRegist… Route
Illuminate\Contracts\Routing\Registrar Route
Illuminate\Contracts\Routing\ResponseFacto… Response
Illuminate\Contracts\Routing\UrlGenerator URL
Illuminate\Contracts\Routing\UrlRoutable
Illuminate\Contracts\Session\Session Session::driver()
Illuminate\Contracts\Support\Arrayable
Illuminate\Contracts\Support\Htmlable
Illuminate\Contracts\Support\Jsonable
Illuminate\Contracts\Support\MessageBag
Illuminate\Contracts\Support\MessageProvid…
Illuminate\Contracts\Support\Renderable
Illuminate\Contracts\Support\Responsable
Illuminate\Contracts\Translation\Loader
Illuminate\Contracts\Translation\Translato… Lang
Illuminate\Contracts\Validation\Factory Validator
Illuminate\Contracts\Validation\ImplicitRu…
Illuminate\Contracts\Validation\Rule
Illuminate\Contracts\Validation\ValidatesW…
Illuminate\Contracts\Validation\Validator Validator::make()
Illuminate\Contracts\View\Engine
Illuminate\Contracts\View\Factory View
Illuminate\Contracts\View\View View::make()