docker 容器访问宿主机端口
运行在 docker 容器中的一个项目需要访问宿主机的某个端口,比如容器 A 中的项目访问宿主机 B,在 A 的项目中以下边的地址来访问即可: 1host.docker.internal 之前是可以运行的,但是今天不知道什么情况突然 500 了,重启电脑后,在容器中 ping 这个地址: 1234567PING host.docker.internal (192.168.65.2) 56(84) bytes of data.64 bytes from 192.168.65.2: icmp_seq=1 ttl=37 time=2.09 ms64 bytes from 192.168.65.2: icmp_seq=2 ttl=37 time=1.05 ms64 bytes from 192.168.65.2: icmp_seq=3 ttl=37 time=1.05 ms64 bytes from 192.168.65.2: icmp_seq=4 ttl=37 time=1.16 ms64 bytes from 192.168.65.2: icmp_seq=5 ttl=37 time=1.03 ms 可以看到实际访问的是 192.168.65.2 这个地址,那么在宿主机的 host 加上一行: 1192.168.65.2 host.docker.internal 再次访问,ok!做个记录,防止下次再忘。 安装ping不指定用户进入容器,然后执行: 12apt-get updateapt-get install iputils-ping
哈!
很久很久以前,有只自认为自己是狼的哈士奇,叫大哈。 后来,他找到了一个让他愿意把骨头让出来的人。 祝他做一只幸福的哈士奇,每天都有骨头吃。
卷积神经网络学习
卷积神经网络,计算机视觉应用几乎都在使用的一种深度学习模型。 一般用于训练数据集较小的问题,比如图像分类。 简介卷积神经网络是一种多层神经网络,主要由输入层,卷积层,激励函数,池化层和全连接层组成,可以通过一系列方法,成功将数据量庞大的图片识别问题不断降维,最终使其能够被训练。 输入层即数据的输入。 通过传入参数 input_shape=(28, 28, 1) 来设置网络接收张量的形状。 卷积层使用卷积核来进行特征提取和特征映射。 当我们输入的图像是 28 * 28 * 1 ,定义一个 3 * 3 的卷积核来对图像进行卷积操作(可以理解为一个滑动窗口,把卷积核与对应的图像像素做乘积然后求和),得到了 3 * 3 的卷积结果。 这个过程我们可以理解为我们使用一个过滤器(卷积核)来过滤图像的各个小区域,从而得到这些小区域的特征值。 激励层激励层主要对卷积层的输出进行一个非线性映射,因为卷积层的计算还是一种线性计算。 使用的激励函数一般是 ReLu。 卷积层和激励层一般合并在一起成为“卷积层”。 池化层其实就是下采样。一般在卷积层后边,通过池化来压缩卷积层输出的特征向量,使特征图变小,简化网络计算复杂度,同时改善结果。 全连接层在这里的作用由提取特征变成了分类。 实例化网络实例化一个简单的卷积神经网络模型。 1234567891011from keras import layersfrom keras import modelsmodel = models.Sequential()model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(64, (3, 3), activation='relu'))model.add(layers.MaxPooling2D((2, 2)))model.add(layers.Conv2D(64, (3, 3), activation='relu'))print(model.summary()) 运行结果如下: 123456789101112131415161718_________________________________________________________________Layer (type) Output Shape Param #=================================================================conv2d_1 (Conv2D) (None, 26, 26, 32) 320_________________________________________________________________max_pooling2d_1 (MaxPooling2 (None, 13, 13, 32) 0_________________________________________________________________conv2d_2 (Conv2D) (None, 11, 11, 64) 18496_________________________________________________________________max_pooling2d_2 (MaxPooling2 (None, 5, 5, 64) 0_________________________________________________________________conv2d_3 (Conv2D) (None, 3, 3, 64) 36928=================================================================Total params: 55,744Trainable params: 55,744Non-trainable params: 0_________________________________________________________________None 首先是网络层设置,这里是 5 层神经网络。 添加第一个卷积层,滤波器数量是 32,卷积窗口大小是 3 * 3,strides 指卷积沿高度和宽度的步幅,默认(1, 1),padding 是指卷积窗口滑动的方式,两个参数:默认 VALID 和 SAME,SAME 采用的是补全,即宽度不够时先补 0 再滑动,VALID 则直接丢弃多余的元素,激励函数选择 relu。因为是第一层,所以需要说明输入数据的 shape。 第一层 pooling(池化,下采样),将数据分辨率长宽各降低一半。 然后再继续添加,接下来把最后输出张量 (3, 3, 64)展平输入到一个全连接层。 添加全连接层分类器123model.add(layers.Flatten())model.add(layers.Dense(64, activation='relu'))model.add(layers.Dense(10, activation='softmax')) 继续打印,查看模型结构: 12345flatten_1 (Flatten) (None, 576) 0_________________________________________________________________dense_1 (Dense) (None, 64) 36928_________________________________________________________________dense_2 (Dense) (None, 10) 650 可以看到数据再丢入分类器之前被展平成了一维 (None, 576) 训练网络123456789101112131415161718from keras.datasets import mnistfrom keras.utils import to_categorical(train_images, train_labels), (test_images, test_labels) = mnist.load_data()train_images = train_images.reshape((60000, 28, 28, 1))train_images = train_images.astype('float32') / 255test_images = test_images.reshape((10000, 28, 28, 1))test_images = test_images.astype('float32') / 255train_labels = to_categorical(train_labels)test_labels = to_categorical(test_labels)model.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy'])model.fit(train_images, train_labels, epochs=5, batch_size=64)test_loss, test_acc = model.evaluate(test_images, test_labels)print(test_acc) 先通过张量变形格式化数据, 然后配置学习过程,开始训练。 最后打印结果可以看到精度为 0.9913 ,比之前的神经网络精度高了很多。
laravel 广播系统学习
看到广播系统,先想起了曾经虐过我的即时通讯。 虽然都是对 websocket 的应用,但是好像又有点区别,这里好好学习一下。 laravel 的广播与事件紧密相关,广播即对事件进行广播,因此在学习广播之前,要先阅读事件和监听器的相关文档。 配置老规矩,先来看配置文件 config/broadcasting.php 里边的配置选项: 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960<?phpreturn [ /* |-------------------------------------------------------------------------- | Default Broadcaster |-------------------------------------------------------------------------- | | This option controls the default broadcaster that will be used by the | framework when an event needs to be broadcast. You may set this to | any of the connections defined in the "connections" array below. | | Supported: "pusher", "redis", "log", "null" | */ 'default' => env('BROADCAST_DRIVER', 'null'), /* |-------------------------------------------------------------------------- | Broadcast Connections |-------------------------------------------------------------------------- | | Here you may define all of the broadcast connections that will be used | to broadcast events to other systems or over websockets. Samples of | each available type of connection are provided inside this array. | */ 'connections' => [ 'pusher' => [ 'driver' => 'pusher', 'key' => env('PUSHER_APP_KEY'), 'secret' => env('PUSHER_APP_SECRET'), 'app_id' => env('PUSHER_APP_ID'), 'options' => [ 'cluster' => env('PUSHER_APP_CLUSTER'), 'encrypted' => true, ], ], 'redis' => [ 'driver' => 'redis', 'connection' => 'default', ], 'log' => [ 'driver' => 'log', ], 'null' => [ 'driver' => 'null', ], ],]; 默认情况下,laravel 提供了以上几种开箱即用的广播驱动器程序。 env 配置文件中,默认的驱动为 log,意味着客户端不会受到任何信息,只是会把要广播的消息写入 log 文件中,跟学习目标不符,就先以 pusher 展开学习吧。 我们就以发布新文章后推送给所有用户为例。 前期准备开始之前,必须要先注册 App\Providers\BroadcastServiceProvider,在 config/app.php 配置文件中的 providers 数组中取消对提供者的注释。 注册: [ pusher ] 然后把相关参数配置到 .env 文件。 安装组件:12composer require pusher/pusher-php-servernpm install --save laravel-echo pusher-js 添加文章模块,包含 migrate,controller,model,view 和 router 等内容。 新建事件:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051php artisan make:event NewArticleNotificationEvent// 事件内容:<?phpnamespace App\Events;use Illuminate\Queue\SerializesModels;use Illuminate\Broadcasting\Channel;use Illuminate\Broadcasting\PrivateChannel;use Illuminate\Foundation\Events\Dispatchable;use Illuminate\Broadcasting\InteractsWithSockets;use Illuminate\Contracts\Broadcasting\ShouldBroadcast;use App\Article;class NewArticleNotificationEvent implements ShouldBroadcast{ use Dispatchable, InteractsWithSockets, SerializesModels; private $article; /** * Create a new event instance. * * @return void */ public function __construct(Article $article) { $this->article = $article; } public function broadcastWith() { return [ 'title' => $this->article->title, 'content' => $this->article->content, 'author' => $this->article->user->name ]; } /** * Get the channels the event should broadcast on. * * @return \Illuminate\Broadcasting\Channel|array */ public function broadcastOn() { return new Channel('articles'); }} 触发事件在保存文章的控制器中触发事件: 123$data = array_merge($request->only(['title', 'content']), ['uid' => Auth::id()]);$article = Article::create($data);broadcast(new NewArticleNotificationEvent($article)); 前端监听文章列表用了vue组件,在这个组件中进行事件监听。 1234567891011121314151617181920212223242526272829303132333435<template> <div class="container"> <table class="table table-striped"> <tr> <th>ID</th> <th>Author</th> <th>Title</th> <th>Content</th> <th>Created At</th> </tr> <tr v-for="article in articles"> <td>{{article.id}}</td> <td>{{article.user.name}}</td> <td>{{article.title}}</td> <td>{{article.content}}</td> <td>{{article.created_at}}</td> </tr> </table> </div></template><script>export default { props: ['articles'], created() { Echo.channel('articles').listen('NewArticleNotificationEvent', (article) => { console.log(article); }) }}</script><style scoped></style> 写好后要在命令行执行 npm run watch-poll 实时编译文件。 测试写篇文章测试一下: 注意事项 不需要创建 channel 路由 不需要开启队列监听 如果没反应请先强制刷新浏览器
laravel 测试模块学习
学习下 larave 内置的测试模块。 当你想把一些东西写到 print 语句或者调试表达式中时,别这么做,将其写成一个测试来代替。 –Martin Fowler 最开始看到的关于 laravel 测试的信息是借助模型工厂来生成测试数据,今天查完资料发现,这只是测试中的一小部分。 laravel 内置了 PHPUnit 来做测试,并且已经做好了配置文件,还提供了一些便利的辅助函数,可以更直观的测试程序。 在 laravel 的项目中,包含一个 tests 目录,这个目录又有两个子目录:Feature 和 Unit 分别用来做功能测试和单元测试。 功能测试用于测试较大区块的代码,包括若干组件之间的交互,甚至一个完整的 HTTP 请求。 单元测试用于小的 、相互隔离的代码。 配置可以使用默认的配置,也可以创建一个 .env.testing 文件,在运行测试或执行带有 --env=testing 开关的 Artisan 命令时覆盖 .env 文件中的环境变量。 创建 & 运行测试运行 artisan 生成测试用例: 12345// 在 Feature 目录下创建测试类php artisan make:test UserTest// 在 Unit 目录下创建测试类php artisan make:test UserTest --unit 先生成一个单元测试,然后改造一下: 1234$user = DB::table('users')->where('id',1)->first();$name = $user->name;$username = ucfirst($user->name);$this->assertEquals($username, $name); 把用户名首s字母大写,然后判断和原用户名是否相等。 运行结果如下: HTTP测试新建一个路由和方法: 1234567Route::get('/user-info/{id?}', 'HomeController@userInfo');public function userInfo(){ $id = request('id'); $user = User::where('id', $id)->first(); return $user;} 新建测试12345678910111213141516php artisan make:test HttpStatusTest$user = DB::table('users')->where('id',1)->first();$username = ucfirst($user->name);$response = $this->get('/user-info/1');$response->assertStatus(200)->assertJson([ 'id' => 1, 'name'=> 'wu', 'email'=> 'yf-wu@qq.com', 'email_verified_at'=>null, 'created_at'=> '2019-04-09 07:36:52', 'updated_at'=> '2019-04-09 07:36:52']); 运行测试发现接口需要登录,所以会被拦截: 所以需要先模拟用户登录,可以使用:Auth::loginUsingId(1); 使 id 为 1 的用户强制登录。 再次运行,结果 ok。 数据库测试数据库测试功能点更多,可以验证表中是否存在某条数据,也可以用来生成测试数据等。 123$this->assertDatabaseHas('users', [ 'email' => 'sally@example.com']); 也可以使用 assertDatabaseMissing 帮助程序断言数据库中不存在数据。 生成模型工厂运行命令生成模型工厂: 1php artisan make:factory PostFactory 在项目的 database\factories 目录中,已经预先生成了一个 UserFactory: 123456789$factory->define(User::class, function (Faker $faker) { return [ 'name' => $faker->name, 'email' => $faker->unique()->safeEmail, 'email_verified_at' => now(), 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 'remember_token' => Str::random(10), ];}); 改造之前的 TestUser 来测试一下: 123456public function testExample() { $user = factory(User::class)->create(['email' => 'm@m-finder.com']); $this->assertDatabaseHas('users', [ 'email' => 'm@m-finder.com' ]);} 状态ok。 更多操作还是要参考: [ PHPUnit ]
深度学习 -- 基于 keras 的手写数字识别示例
MNIST 是 keras 中一个入门级的计算机视觉数据集,这个数据集包含各种各样的手写数字图片,其中包含 60000 张训练图像和 10000 张测试图像。 是解决手写数字分类问题的经典数据集,可以看做是深度学习的 ‘Hello World’。 下载数据123from keras.datasets import mnist(train_images, train_labels), (test_images, test_labels) = mnist.load_data() 这两行代码需要先运行,下载数据集。 下载过程会失败,多试几次,不需要翻墙。 train_images 和 train_labels 为训练集, 分别为图片数据和标签数据。模型将从这些数据中进行学习。 然后在测试集: test_images 和 test_labels 上对模型进行测试。 构建网络12345from keras import modelsfrom keras import layersnetwork = models.Sequential()network.add(layers.Dense(512, activation='relu', input_shape=(28 * 28,)))network.add(layers.Dense(10, activation='softmax')) 神经网络的核心组件是层(layer),它是一种数据处理模块,可以将它看成数据过滤器。大多数深度学习都是将简单的层链接起来,从而实现渐进式的数据蒸馏(data distillation)。深度学习模型就像是数据处理的筛子,包含一系列越来越精细的数据过滤器(即层)。 编译123network.compile(optimizer='rmsprop', loss='categorical_crossentropy', metrics=['accuracy']) loss: 损失函数,网络如何衡量在训练数据上的性能,即网络如何朝着正确的方向前进。optimizer: 优化器,基于训练数据和损失函数来更新网络的机制metrucs: 监控指标,本例只关心精度,即正确分类的图像所占的比例。 准备图像数据1234train_images = train_images.reshape((60000, 28 * 28))train_images = train_images.astype('float32') / 255test_images = test_images.reshape((10000, 28 * 28))test_images = test_images.astype('float32') / 255 开始之前,我们需要转换数据格式,让网络能够处理。训练图像保存在一个uint8类型的数组中,其形状为 (60000, 28, 28),取值区间为 [0, 255]。我们需要将其转换成一个 float32数组,形状为 (60000, 28 * 28),取值范围为 0 ~ 1。 准备标签123from keras.utils import to_categoricaltrain_labels = to_categorical(train_labels)test_labels = to_categorical(test_labels) 开始训练1network.fit(train_images, train_labels, epochs=5, batch_size=128) 测试数据12test_loss, test_acc = network.evaluate(test_images, test_labels)print('test_acc:', test_acc) 测试集精度为97.98%,比训练集精度低不少。 训练精度和测试精度之间的这种差距是过拟合(overfit)造成的。 这些东西接下来再继续学习。
laravel 队列学习
学习下 laravel 的队列系统。 队列的目的是将耗时的任务延时处理,比如发送邮件,从而大幅度缩短 Web 请求和相应的时间。 常用的队列后台有: Beanstalk,Amazon SQS,Redis 等。 配置laravel 为多种队列服务做了统一的API,在配置文件 config/queue.php 中可以找到每种队列驱动的配置。 其中每种驱动都有一个默认的 queue 属性,用来存放使用时没有显示定义队列的任务。 12345// 分发到默认队列Job::dispatch();// 分发到 emails 队列Job::dispatch()->onQueue('emails'); 在项目的配置文件中,可以指定驱动,老版本中为 QUEUE_DRIVER,新版本中为QUEUE_CONNECTION , 驱动默认为 sync,这是一个本地的同步驱动,方便调试队列里的任务。 先以 redis 为例做一个邮件发送队列。 因为 laravel 的 redis 默认使用了 predis,所以先装下扩展: 1composer require 'predis/predis' 邮件配置,最后两项是手动添加的,否则会报错: 12345678MAIL_DRIVER=smtpMAIL_HOST=smtp.mxhichina.comMAIL_PORT=25MAIL_USERNAME=m@m-finder.comMAIL_PASSWORD=xxxxxxMAIL_ENCRYPTION=nullMAIL_FROM_NAME=M-finderMAIL_FROM_ADDRESS=m@m-finder.com 生成任务类命令行执行:php artisan make:job EmailJob,该命令会在 app/jobs 下自动创建文件。 在任务类中发送邮件: 12345678public function handle() { $email = $this->email; $content = '这是一封来自Laravel的队列测试邮件.'; Mail::raw($content, function ($message) use ($email) { $message->subject('[ 测试 ] 测试邮件SendMail - ' . date('Y-m-d H:i:s')); $message->to($email); });} 任务调度之前弄了登录事件和监听,就在监听里去触发吧。 1EmailJob::dispatch($guard->user)->onQueue('emails'); 开启队列1php artisan queue:work --tries=3 --timeout=30 --queue=emails 然后重新登录触发任务。可以看到邮箱已经有了提示: 邮件已经成功发出,接下来就可以在实际的需求中使用了。
laravel 事件系统学习
学习下 laravel 的事件系统。 Laravel 的事件提供了一个简单的观察者实现,能够订阅和监听应用中发生的各种事件。 先以一个登陆日志来作为例子开始事件的学习吧。 注册事件 && 监听器在 app\Providers\EventServiceProvider.php 中,添加以下内容: 12345678protected $listen = [ Registered::class => [ SendEmailVerificationNotification::class, ], 'App\Events\LoginEvent' => [ 'App\Listeners\LoginListener' ]]; 然后运行命令,生成事件和监听器: 1php artisan event:generate 运行结束后,事件和监听器都会被自动创建好。 在监听中打个 log: info(‘user login event’) 然后找个控制器触发事件。 event(new LoginEvent()); 可以看到 log 文件中有一条新纪录: [2019-03-27 08:16:21] local.INFO: user login event 走到这里,理论上已经可以做很多事情了,但是对于登录日志来说,这样处理并不完美,因为 larave 内置已经写好了登录事件,只需要去调用就可以了。 改造事件在身份验证处理过程中 Laravel 引发了多种事件 。可以在 EventServiceProvider 中附着这些事件的监听器。文档 1234567891011121314151617181920212223242526272829303132333435363738/** * 应用的事件监听器映射。 * * @var array */protected $listen = [ 'Illuminate\Auth\Events\Registered' => [ 'App\Listeners\LogRegisteredUser', ], 'Illuminate\Auth\Events\Attempting' => [ 'App\Listeners\LogAuthenticationAttempt', ], 'Illuminate\Auth\Events\Authenticated' => [ 'App\Listeners\LogAuthenticated', ], 'Illuminate\Auth\Events\Login' => [ 'App\Listeners\LogSuccessfulLogin', ], 'Illuminate\Auth\Events\Failed' => [ 'App\Listeners\LogFailedLogin', ], 'Illuminate\Auth\Events\Logout' => [ 'App\Listeners\LogSuccessfulLogout', ], 'Illuminate\Auth\Events\Lockout' => [ 'App\Listeners\LogLockout', ], 'Illuminate\Auth\Events\PasswordReset' => [ 'App\Listeners\LogPasswordReset', ],]; 这里就只使用登录事件。修改 EventServiceProvider: 123'Illuminate\Auth\Events\Login' => [ 'App\Listeners\LoginListener'] 再改改 listener: 1234public function handle($guard) { info('user login event', ['username'=>$guard->user->name]); dd($guard);} 触发事件退出帐号重新登录,可以看到以下内容: 可以再优化一下: 123456789public function handle($guard) { $log = [ 'name' => $guard->user->name, 'email'=> $guard->user->email, 'ip'=> \Request::getClientIp(true), 'datetime' => Carbon::now() ]; info('user login event', $log);} 这里可以选中用 log 存储还是用 mysql 存储。 事件的学习就是这些了,平时开发要经常使用避免遗忘。
laradock 中 php5.6 连接 mysql 报错解决
laradock 中 php 5.6 连接 mysql 报错的解决方法。 之前弄的是默认版本的 php 7.2 和 mysql 8,配置好以后一直都能正常使用。 最近因为需要切换 php 版本,突然发现切换后连接 mysql 时无法识别 host。 emmmm……,最开始还以为是 php 的锅,各种重新 build,无果,最后在 github 上找到别人发出来的解决办法: rm -rf ~/.laradock/data/mysql删除老版本的数据 docker-compose build mysql重新构建 mysql 进入 mysql 容器mysql -uroot -prootALTER USER root IDENTIFIED WITH mysql_native_password BY ‘root’;exit; 到这里也就可以了。 有这个问题是因为 8 和 5.7 的配置不一样,不清掉老数据的话会引起报错,一有请求去连接 mysql 马上就宕机。
Laravel 服务容器
在 Laravel 生命周期中,我们了解到框架运行过程中,会通过创建应用实例来完成很多事情,这个应用实例,也就是我们今天的主角,服务容器。 Laravel 的服务容器,是用于管理类的依赖和执行依赖注入的工具。 依赖注入 DI开始之前,需要我们先了解一下,什么是依赖注入。 简单来说,就是将类的依赖通过参数或其他方式注入。 比如: 12345678910111213141516171819202122232425262728293031323334interface Storage{ public function set($key, $value); public function get($key);}class SessionStorage implements Storage{ function __construct($cookieName='PHPSESSID'){ session_name($cookieName); session_start(); } function set($key, $value){ $_SESSION[$key] = $value; } function get($key){ return $_SESSION[$key]; }}class User{ private $storage; function __construct(Storage $storage){ $this->storage = $storage; } function setLanguage($language){ $this->storage->set('language', $language); }}$storage = new SessionStorage('SESSION_ID');$user = new User($storage); 依赖注入 并不局限于构造函数,也可以通过设值方法注入,或者类成员变量方式,通过构造函数注入适用于必要的依赖,设值注入适用于可选依赖,比如项目需要一个缓存功能的实现。在上面的例子中,我们如果需要改用 Redis 或者 MongoDB 来存储数据,只需要继承并实现 Storage 接口,然后在外部就可以很轻松的切换服务了。 依赖注入容器 IOC在实际的开发中,用上边的依赖注入方式还是很累的,所以,我们还需要了解一个新的概念,依赖注入容器,也可以叫控制反转。 简单来说,依赖注入容器就是将组件间的依赖关系由程序内部提到外部容器来管理,也就是将依赖的配置和使用分开,原本是程序控制执行流程,现在程序反倒成了被控制的对象,也就形成了控制反转。 通常用于管理大量依赖组件的实例。比如一个框架。 首先,我们可以定义一个容器: 123456789101112131415class Container{ public function getStorage() { return new SessionStorage(); } public function getUser() { return new User($this->getStorage()); }}// 更改实例化方式$container = new Container();$user = $container->getUser(); 在这个容器中,我们只需要调用容器 getUser 方法,既可以获取到 User 实例,并不需要关心它是怎么创建出来的。 但是,这个容器还存在一些问题,Storage 的实例化还是硬编码,如果要切换其他服务,只能通过改代码的方式。 对此,我们可以再次升级容器: 1234567891011121314151617181920212223242526272829303132333435363738394041class Container{ protected $binds; protected $instances; // 绑定 public function bind($abstract, $concrete) { // 判断是否为匿名函数 if ($concrete instanceof Closure) { $this->binds[$abstract] = $concrete; } else { $this->instances[$abstract] = $concrete; } } // 实例化 public function make($abstract, $parameters = []) { if (isset($this->instances[$abstract])) { return $this->instances[$abstract]; } array_unshift($parameters, $this); return call_user_func_array($this->binds[$abstract], $parameters); }}$container = new Container;$container->bind('Storage', function($container){ return new SessionStorage;});$container->bind('User',function($container,$module){ return new User($container->make($module));});$user = $container->make('User',['Storage']); 一个类似于 laravel 的服务容器就好了,当然 larave 的服务容器比这个要复杂的多。 总的来说,laravel 的服务容器有两大功能: 注册基础服务 管理需要实例化的类及其依赖 Laravel 服务容器的使用方法laravel 服务容器在使用时一般分为两个阶段:使用之前进行绑定(bind)完成将实现绑定到接口;使用时对通过接口解析(make)出服务。 laravel 内置多种不同的绑定方法以用于不同的使用场景: bind 简单绑定 singleton 单例绑定 instance 实例绑定 contextual-binding 上下文绑定 还有好几种,看文档吧 它们的最终目标是一致的:绑定接口到实现。 这样的好处是在项目的编码阶段建立起接口和实现的映射关系,到使用阶段通过抽象类(接口)解析出它的具体实现,这样就实现了项目中的解耦。 bindbind 方法的功能是将实现与接口进行绑定,然后在每次执行服务解析操作时,Laravel 容器都会重新创建实例对象。 例如: 123456789101112131415161718$this->app->bind( UserRepositoryInterface::class, UserRepository::class);class UserController extends Controller{ private $repository; function __construct(UserRepositoryInterface $userRepository) { $this->repository = $userRepository; } function users(){ return $this->repository->all(); }} 在服务提供者中,将 User 仓库的具体实现与接口进行绑定,使用时可以直接通过接口注入依赖。 singleton采用单例绑定时,仅在首次解析时创建实例,后续使用 make 进行解析服务操作都将直接获取这个已解析的对象,实现共享操作。 绑定处理类似 bind 绑定,只需将 bind 方法替换成 singleton 方法即可。 instance将已经创建的实例对象绑定到接口以供后续使用,这种使用场景类似于注册表。 比如用于存储用户模型: 12345678// 创建一个用户实例$artisan = new User();// 将实例绑定到服务容器App::instance('login-user', $artisan);// 获取用户实例$artisan = App::make('login-user'); contextual-binding主要用于一个接口多处实现,然后根据不同控制器去进行判断具体应该用哪个实现。 12345$this->app->when(PhotoController::class) ->needs(Filesystem::class) ->give(function () { return Storage::disk('local'); });
