百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Laravel的核心(七): 框架扩展(laravel框架最新版本)

ccwgpt 2024-09-23 04:32 28 浏览 0 评论

简介

为了方便你自定义框架的核心组件功能,甚至是完全替换它们,Laravel 提供了大量可以对应用进行扩展的地方。例如,哈希服务实现了 Illuminate\Contracts\Hashing\Hasher 契约,你可以按照自己应用的需求来重新实现它。你还可以继承 Request 对象类,添加自己用的顺手的方法。你甚至可以添加全新的用户认证、缓存和会话驱动!

Laravel 组件功能通常有两种扩展方式:在服务容器里面绑定新实现,或者通过采用工厂模式实现的 Manager 类注册一个自定义的扩展。在本章中,我们将探索扩展框架核心功能的不同方式,并检查都需要些什么代码。

管理类和工厂

Laravel 有多个 Manager 类用来管理基于驱动的组件的创建。这些组件包括缓存、会话、用户认证、队列组件等。管理类负责根据应用程序的配置来创建特定的驱动实例。例如,CacheManager 可以创建 APC、Memcached、Redis 以及其他不同的缓存驱动的实现。

同时,每个管理类都包含 extend 方法,该方法可用于将新的驱动解决方案注入到管理类中。下面我们将逐个介绍这些管理类,并向你展示如何将自定义的驱动注入它们。

了解你的管理类:请花点时间看看 Laravel 中每个 Manager 类的代码,比如 CacheManager 和 SessionManager。通过阅读这些代码能让你对Laravel 底层工作原理有更加全面的了解。所有管理类都继承自Illuminate\Support\Manager 基类,该基类每个管理类提供了一些有用的通用功能(适用于 Laravel 4,Laravel 5并非如此)。

缓存

要扩展 Laravel 的缓存服务,需要使用 CacheManager 里的 extend 方法,该方法用来绑定自定义的缓存驱动到管理类。在所有管理类中都是这个逻辑,所以扩展其他的管理类也是按照这个思路来。例如,我们想注册一个新的缓存驱动,名叫「mongo」,代码可以这样写:

Cache::extend('mongo', function($app) {
 // Return Illuminate\Cache\Repository instance...
});

extend 方法的第一个参数是自定义缓存驱动的名字。该名字对应 config/cache.php 配置文件中的 driver 配置项。第二个参数是一个会返回 Illuminate\Cache\Repository 实例的匿名函数,传入该匿名函数的 $app 参数是 Illuminate\Foundation\Application 的实例,即全局服务容器。

要创建自定义的缓存驱动,首先要实现 Illuminate\Contracts\Cache\Store 接口。所以,基于 MongoDB 实现的缓存驱动代码结构如下:

use Illuminate\Contracts\Cache\Store;
class MongoStore implements Store
{
 public function get($key)
 {
 // TODO: Implement get() method.
 }
 public function many(array $keys)
 {
 // TODO: Implement many() method.
 }
 public function put($key, $value, $minutes)
 {
 // TODO: Implement put() method.
 }
 public function putMany(array $values, $minutes)
 {
 // TODO: Implement putMany() method.
 }
 public function increment($key, $value = 1)
 {
 // TODO: Implement increment() method.
 }
 public function decrement($key, $value = 1)
 {
 // TODO: Implement decrement() method.
 }
 public function forever($key, $value)
 {
 // TODO: Implement forever() method.
 }
 public function forget($key)
 {
 // TODO: Implement forget() method.
 }
 public function flush()
 {
 // TODO: Implement flush() method.
 }
 public function getPrefix()
 {
 // TODO: Implement getPrefix() method.
 }
}

我们只需使用 MongoDB 连接来实现上面的每一个方法即可。一旦实现完毕,就可以像下面这样完成自定义驱动的注册

use Illuminate\Cache\Repository;
use Illuminate\Support\Facades\Cache;
Cache::extend('mongo', function($app)
{
 return new Repository(new MongoStore);
}

正如上面的例子所示,在创建自定义驱动的时候你可以直接使用 Illuminate\Cache\Repository 基类。通常你不需要创建自己的 Repository 类。

如果你不知道要把自定义的缓存驱动代码放到哪里,可以考虑将其放到扩展包然后发布到 Packagist 。或者,你也可以在应用的 app 目录下创建一个 Extensions 目录,然后将 MongoStore.php 放到该目录下。不过,Laravel 并没有对应用程序的目录结构做硬性规定,所以你完全可以按照自己喜欢的方式组织应用程序的代码。

如果你还为在哪里存放注册代码发愁,服务提供者是个不错的地方。我们之前就讲过,使用服务提供者来管理框架扩展代码是一个非常不错的方式,将相应代码放到服务提供者的 boot 方法即可。

会话(Session)

通过自定义会话驱动扩展 Laravel 功能和扩展缓存系统一样简单。和刚才一样,我们还是使用 extend 方法来注册自定义的代码:

Session::extend('mongo', function($app)
{
 // Return implementation of SessionHandlerInterface
});

注意,我们自定义的会话驱动需要实现 SessionHandlerInterface 接口。这个接口在 PHP 5.4 以上版本才有。但如果你用的是 PHP 5.3,也别担心,Laravel 会自动帮你定义这个接口的。该接口包含了一些需要我们实现的方法。基于 MongoDB 来实现的会话驱动的代码结构就像下面这样:

class MongoHandler implements \SessionHandlerInterface
{
 public function close()
 {
 // TODO: Implement close() method.
 }
 public function destroy($session_id)
 {
 // TODO: Implement destroy() method.
 }
 public function gc($maxlifetime)
 {
 // TODO: Implement gc() method.
 }
 public function open($save_path, $name)
 {
 // TODO: Implement open() method.
 }
 public function read($session_id)
 {
 // TODO: Implement read() method.
 }
 public function write($session_id, $session_data)
 {
 // TODO: Implement write() method.
 }
}

这些方法不像前面的 Illuminate\Contracts\Cache\Store 接口定义的那么容易理解。下面我们简单讲讲这些方法都是干什么的:

  • close 方法和 open 方法通常都不是必需的。对大部分驱动来说都不必实现。
  • destroy 方法会将与 $sessionId 相关联的数据从持久化存储系统中删除。
  • gc 方法会将所有存在时间超过参数 $lifetime 设定值的数据全都删除,该参数是一个 UNIX 时间戳。如果你使用的是诸如 Memcached 或Redis 这种自主管理过期数据的系统,那么该方法可以留空。
  • open 方法一般在基于文件的会话存储系统中才会用到。Laravel 自带的 file 会话驱动使用的就是 PHP 原生的基于文件的会话系统,你可能永远也不需要在这个方法里写东西,所以留空就好。另外这也是一个接口设计的反面教材(稍后我们会讨论这一点),因为 PHP 总是要求我们实现它,即使大部分时候不需要实现它。
  • read 方法会返回与给定 $sessionId 关联的会话数据的字符串版本。在你的会话驱动中,无论读取还是存储会话数据,都不需要做任何序列化和编码操作,因为 Laravel 已经替你做了。
  • write 方法会将给定的 $data 字符串关联到对应的 $sessionId,然后将其写入到一个持久化存储系统中,例如 MongoDB、数据库、Redis 等。

一旦 SessionHandlerInterface 被实现,我们就可以将其注册到会话管理器:

Session::extend('mongo', function($app) {
 return new MongoHandler;
});

注册完毕后,我们就可以在 config/session.php 配置文件里使用mongo 驱动了。

你要是写了个自定义的会话处理器,别忘了在 Packagist 上分享它!

用户认证

用户认证的扩展方式和缓存、会话的扩展方式一样,使用认证管理类上的 extend 方法就可以了:

Auth::extend('riak', function($app) {
 // Return implementation of Illuminate\Contracts\Auth\UserProvider
});

Illuminate\Contracts\Auth\UserProvider 接口的实现类负责从某个持久化存储系统(如 MySQL、Riak 等)中获取 Illuminate\Contracts\Auth\Authenticatable 接口的实现类实例。这两个接口使得 Laravel 的用户认证机制得以在不用关心用户数据如何存储以及使用何种类型表示用户的情况下继续工作。

下面我们来看看 Illuminate\Contracts\Auth\UserProvider 接口的代码:

<?php
namespace Illuminate\Contracts\Auth;
interface UserProvider
{
 /**
 * Retrieve a user by their unique identifier.
 *
 * @param mixed $identifier
 * @return \Illuminate\Contracts\Auth\Authenticatable|null
 */
 public function retrieveById($identifier);
 /**
 * Retrieve a user by their unique identifier and "remember me" token.
 *
 * @param mixed $identifier
 * @param string $token
 * @return \Illuminate\Contracts\Auth\Authenticatable|null
 */
 public function retrieveByToken($identifier, $token);
 /**
 * Update the "remember me" token for the given user in storage.
 *
 * @param \Illuminate\Contracts\Auth\Authenticatable $user
 * @param string $token
 * @return void
 */
 public function updateRememberToken(Authenticatable $user, $token);
 /**
 * Retrieve a user by the given credentials.
 *
 * @param array $credentials
 * @return \Illuminate\Contracts\Auth\Authenticatable|null
 */
 public function retrieveByCredentials(array $credentials);
 /**
 * Validate a user against the given credentials.
 *
 * @param \Illuminate\Contracts\Auth\Authenticatable $user
 * @param array $credentials
 * @return bool
 */
 public function validateCredentials(Authenticatable $user, array $credentials);
}

retrieveById 方法通常接受一个表示用户ID的数字键,比如 MySQL 数据库的自增 ID。该方法会返回与给定 ID 匹配的 Illuminate\Contracts\Auth\Authenticatable 的实现类实例,如 User 模型类实例。

当用户尝试登录到应用时,retrieveByCredentials 方法会接受传递给 Auth::attempt 方法的认证凭证数组。然后该方法会「查询」底层的持久化存储系统,来找到与给定凭证信息匹配的用户。通常,该方法会执行一个带有「where」条件的查询来匹配参数里的 $credentials['username']。该方法不应该尝试做任何密码验证

validateCredentials 方法会通过比较给定 $user 和$credentials 来认证用户。例如,该方法会比较 $user->getAuthPassword() 方法返回的字符串和 $credentials['password'] 经过 Hash::make 处理后的结果,如果相等,则认为认证通过,否则认证失败。

retrieveByToken 方法和 updateRememberToken 则用于在登录认证时实现「记住我」的功能,让用户在 Token 有效期内不用输入登录凭证即可自动登录。

现在,我们已经探索了 Illuminate\Contracts\Auth\UserProvider 接口的每一个方法,接下来,我们来看看 Illuminate\Contracts\Auth\Authenticatable 接口。别忘了,Authenticatable 接口实现的实例是通过是 UserProvider 实现实例的 retrieveById 和 retrieveByCredentials 方法返回的:

<?php
namespace Illuminate\Contracts\Auth;
interface Authenticatable
{
 /**
 * Get the name of the unique identifier for the user.
 *
 * @return string
 */
 public function getAuthIdentifierName();
 /**
 * Get the unique identifier for the user.
 *
 * @return mixed
 */
 public function getAuthIdentifier();
 /**
 * Get the password for the user.
 *
 * @return string
 */
 public function getAuthPassword();
 /**
 * Get the token value for the "remember me" session.
 *
 * @return string
 */
 public function getRememberToken();
 /**
 * Set the token value for the "remember me" session.
 *
 * @param string $value
 * @return void
 */
 public function setRememberToken($value);
 /**
 * Get the column name for the "remember me" token.
 *
 * @return string
 */
 public function getRememberTokenName();
}

这个接口很简单。getAuthIdentifier 方法返回用户的「主键」。如果在 MySQL 数据库中,就是自增主键了。getAuthPassword 方法返回经过散列处理的用户密码。getAuthIdentifierName 方法会返回用户的唯一标识,比如用户名或邮箱信息。其他几个方法都是和「记住我」功能相关的。

有了这个接口,用户认证系统就可以处理任何用户类,而不用关心该用户类使用了什么 ORM 框架或者存储抽象层。默认情况下,Laravel 已经在 app 目录下提供了实现 Authenticatable 接口的 User 类。所以你可以将这个类作为实现示例。

最后,当我们实现了 Illuminate\Contracts\Auth\UserProvider 接口后,就可以将对应扩展注册进 Auth 里面:

Auth::extend('riak', function($app) {
 return new RiakUserProvider($app['riak.connection']);
});

使用 extend 方法注册好驱动以后,你就可以在 config/auth.php 配置文件中切换到新的驱动了(对应配置项是 providers.users.driver)。

容器默认绑定

几乎所有 Laravel 框架自带的服务提供者都会绑定一些对象到服务容器里。你可以在 config/app.php 配置文件里找到服务提供者列表。如果你有时间的话,你应该大致过一遍每个服务提供者的源码。这么做的好处是你可以对每个服务提供者有更深的理解,明白它们都往框架里加了什么东西,以及对应的绑定到服务容器的键是什么,通过这些键我们就可以从容器中解析相应的服务。

学院君注:由于 Laravel 5.5 中新增了包自动发现功能,所以 config/app.php 配置文件的 providers 数组提供的服务服务者列表并不全,最全的列表在 bootstrap/cache/services.php 的 providers 数组中。

举个例子,AuthServiceProvider 向服务容器内绑定了一个 auth 键,通过这个键解析出来的服务是一个 Illuminate\Auth\AuthManager 的实例。你可以在自己的应用中通过覆盖这个服务容器绑定来轻松实现扩展并重写该类。例如,你可以创建一个继承自 AuthManager 类的子类:

namespace App\Extensions;
class MyAuthManager extends Illuminate\Auth\AuthManager 
{
 //
}

子类写好以后,你可以在服务提供者 AuthServiceProvider 的 boot 方法中覆盖默认的 auth:

public function boot()
{
 ...
 $this->app->singleton('auth', function ($app) {
 return new MyAuthManager; 
 });
}

这就是扩展绑定进容器的任意核心类的通用方法。基本上每一个核心类都以这种方式绑定进了容器,都可以被重写。还是那一句话,读一遍框架自带的服务提供者源码可以帮助你熟悉各种类是怎么绑定进容器的,都绑定到哪些键上。这是学习 Laravel 框架底层究竟如何运转的最佳实践。

请求

由于这是框架里面非常基础的部分,并且在请求生命周期中很早就被实例化,所以扩展 Request 类的方法与之前扩展其他服务的示例相比是有些不同的。

首先,还是要写个继承自 Illuminate\Http\Request 的子类:

namespace App\Extensions;
class CustomRequest extends Illuminate\Http\Request 
{
 // Custom, helpful methods here...
}

子类写好后,打开 bootstrap/app.php 文件。该文件是每次对应用发起请求时最早被载入的几个文件之一。该文件中执行的第一个动作是创建 Laravel 的 $app 实例:

$app = new Illuminate\Foundation\Application(
 realpath(__DIR__.'/../')
);

当新的应用实例创建后,它将会创建一个 Illuminate\Http\Request 的实例并且将其绑定到服务容器里,键名为 request。所以,我们需要找个方法来将一个自定义的类指定为「默认的」请求类,对不对?在 Laravel 4 中,应用实例上有一个 requestClass 方法,可以用来指定自定义的请求类,但是在 Laravel 5 中,没有这个类,所以我们需要自己来实现,打开 public/index.php,在

app = require_once __DIR__.'/../bootstrap/app.php';

这行代码之后添加如下代码覆盖默认的 request 指向:

$app->alias('request', \App\Extensions\CustomRequest::class);

然后还要修改

$request = Illuminate\Http\Request::capture()

这段代码为

$request = App\Extensions\CustomRequest::capture()

指定好自定义的请求类后,Laravel 会在任何创建 Request 实例的时候都使用这个自定义的类,以便你始终拥有自定义的请求类,即使在单元测试中也不例外!

举两个例子,怎么样写好代码

最经典的算法,献给正在面试道路上的你

相关推荐

团队管理“布阵术”:3招让你的团队战斗力爆表!

为何古代军队能够以一当十?为何现代企业有的团队高效似“特种部队”,有的却松散若“游击队”?**答案正隐匿于“布阵术”之中!**今时今日,让我们从古代兵法里萃取3个核心要义,助您塑造一支战斗力爆棚的...

知情人士回应字节大模型团队架构调整

【知情人士回应字节大模型团队架构调整】财联社2月21日电,针对原谷歌DeepMind副总裁吴永辉加入字节跳动后引发的团队调整问题,知情人士回应称:吴永辉博士主要负责AI基础研究探索工作,偏基础研究;A...

豆包大模型团队开源RLHF框架,训练吞吐量最高提升20倍

强化学习(RL)对大模型复杂推理能力提升有关键作用,但其复杂的计算流程对训练和部署也带来了巨大挑战。近日,字节跳动豆包大模型团队与香港大学联合提出HybridFlow。这是一个灵活高效的RL/RL...

创业团队如何设计股权架构及分配(创业团队如何设计股权架构及分配方案)

创业团队的股权架构设计,决定了公司在随后发展中呈现出的股权布局。如果最初的股权架构就存在先天不足,公司就很难顺利、稳定地成长起来。因此,创业之初,对股权设计应慎之又慎,避免留下巨大隐患和风险。两个人如...

消息称吴永辉入职后引发字节大模型团队架构大调整

2月21日,有消息称前谷歌大佬吴永辉加入字节跳动,并担任大模型团队Seed基础研究负责人后,引发了字节跳动大模型团队架构大调整。多名原本向朱文佳汇报的算法和技术负责人开始转向吴永辉汇报。简单来说,就是...

31页组织效能提升模型,经营管理团队搭建框架与权责定位

分享职场干货,提升能力!为职场精英打造个人知识体系,升职加薪!31页组织效能提升模型如何拿到分享的源文件:请您关注本头条号,然后私信本头条号“文米”2个字,按照操作流程,专人负责发送源文件给您。...

异形柱结构(异形柱结构技术规程)

下列关于混凝土异形柱结构设计的说法,其中何项正确?(A)混凝土异形柱框架结构可用于所有非抗震和抗震设防地区的一般居住建筑。(B)抗震设防烈度为6度时,对标准设防类(丙类)采用异形柱结构的建筑可不进行地...

职场干货:金字塔原理(金字塔原理实战篇)

金字塔原理的适用范围:金字塔原理适用于所有需要构建清晰逻辑框架的文章。第一篇:表达的逻辑。如何利用金字塔原理构建基本的金字塔结构受众(包括读者、听众、观众或学员)最容易理解的顺序:先了解主要的、抽象的...

底部剪力法(底部剪力法的基本原理)

某四层钢筋混凝土框架结构,计算简图如图1所示。抗震设防类别为丙类,抗震设防烈度为8度(0.2g),Ⅱ类场地,设计地震分组为第一组,第一自振周期T1=0.55s。一至四层的楼层侧向刚度依次为:K1=1...

结构等效重力荷载代表值(等效重力荷载系数)

某五层钢筋混凝土框架结构办公楼,房屋高度25.45m。抗震设防烈度8度,设防类别丙类,设计基本地震加速度0.2g,设计地震分组第二组,场地类别为Ⅱ类,混凝土强度等级C30。该结构平面和竖向均规则。假定...

体系结构已成昭告后世善莫大焉(体系构架是什么意思)

实践先行也理论已初步完成框架结构留余后人后世子孙俗话说前人栽树后人乘凉在夏商周大明大清民国共和前人栽树下吾之辈已完成结构体系又俗话说青出于蓝而胜于蓝各个时期任务不同吾辈探索框架结构体系经历有限肯定发展...

框架柱抗震构造要求(框架柱抗震设计)

某现浇钢筋混凝土框架-剪力墙结构高层办公楼,抗震设防烈度为8度(0.2g),场地类别为Ⅱ类,抗震等级:框架二级,剪力墙一级,混凝土强度等级:框架柱及剪力墙C50,框架梁及楼板C35,纵向钢筋及箍筋均采...

梁的刚度、挠度控制(钢梁挠度过大会引起什么原因)

某办公楼为现浇钢筋混凝土框架结构,r0=1.0,混凝土强度等级C35,纵向钢筋采用HRB400,箍筋采用HPB300。其二层(中间楼层)的局部平面图和次梁L-1的计算简图如图1~3(Z)所示,其中,K...

死要面子!有钱做大玻璃窗,却没有钱做“柱和梁”,不怕房塌吗?

活久见,有钱做2层落地大玻璃窗,却没有钱做“柱子和圈梁”,这样的农村自建房,安全吗?最近刷到个魔幻施工现场,如下图,这栋5开间的农村自建房,居然做了2个全景落地窗仔细观察,这2个落地窗还是飘窗,为了追...

不是承重墙,物业也不让拆?话说装修就一定要拆墙才行么

最近发现好多朋友装修时总想拆墙“爆改”空间,别以为只要避开承重墙就能随便砸!我家楼上邻居去年装修,拆了阳台矮墙想扩客厅,结果物业直接上门叫停。后来才知道,这种配重墙拆了会让阳台承重失衡,整栋楼都可能变...

取消回复欢迎 发表评论: