nginx、swoole高并发原理初探

一、阅前热身

为了更加形象的说明同步异步、阻塞非阻塞,我们以小明去买奶茶为例。

1、同步与异步

①同步与异步的理解

同步与异步的重点在消息通知的方式上,也就是调用结果通知的方式。

  • 同步当一个同步调用发出去后,调用者要一直等待调用结果的通知后,才能进行后续的执行

  • 异步:当一个异步调用发出去后,调用者不能立即得到调用结果的返回。

异步调用,要想获得结果,一般有两种方式:
1、主动轮询异步调用的结果;
2、被调用方通过callback来通知调用方调用结果。

②:生活实例

同步买奶茶:小明点单交钱,然后等着拿奶茶;异步买奶茶:小明点单交钱,店员给小明一个小票,等小明奶茶做好了,再来取。

异步买奶茶,小明要想知道奶茶是否做好了,有两种方式:
1、小明主动去问店员,一会就去问一下:“奶茶做好了吗?”…直到奶茶做好。
2、等奶茶做好了,店员喊一声:“小明,奶茶好了!”,然后小明去取奶茶。


2、阻塞与非阻塞

①阻塞与非阻塞的理解

阻塞与非阻塞的重点在于进/线程等待消息时候的行为,也就是在等待消息的时候,当前进/线程是挂起状态,还是非挂起状态。

  • 阻塞阻塞调用在发出去后,在消息返回之前,当前进/线程会被挂起,直到有消息返回,当前进/线程才会被激活.

  • 非阻塞非阻塞调用在发出去后,不会阻塞当前进/线程,而会立即返回。

②:生活实例

阻塞买奶茶:小明点单交钱,干等着拿奶茶,什么事都不做;非阻塞买奶茶:小明点单交钱,等着拿奶茶,等的过程中,时不时刷刷微博、朋友圈…


3、总结

通过上面的分析,我们可以得知:

同步与异步,重点在于消息通知的方式;阻塞与非阻塞,重点在于等消息时候的行为。

所以,就有了下面4种组合方式

  • 同步阻塞:小明在柜台干等着拿奶茶;

  • 同步非阻塞:小明在柜台边刷微博边等着拿奶茶;

  • 异步阻塞:小明拿着小票啥都不干,一直等着店员通知他拿奶茶;

  • 异步非阻塞:小明拿着小票,刷着微博,等着店员通知他拿奶茶。


二、Nginx如何处理高并发

1、Apache面对高并发,为什么很无力?

Apache处理一个请求是同步阻塞的模式。

每到达一个请求,Apache都会去fork一个子进程去处理这个请求,直到这个请求处理完毕。

面对低并发,这种模式没什么缺点,但是,面对高并发,就是这种模式的软肋了。

  • 1个客户端占用1个进程,那么,进程数量有多少,并发处理能力就有多少,但操作系统可以创建的进程数量是有限的。

  • 多进程就会有进程间的切换问题,而进程间的切换调度势必会造成CPU的额外消耗。当进程数量达到成千上万的时候,进程间的切换就占了CPU大部分的时间片,而真正进程的执行反而占了CPU的一小部分,这就得不偿失了。

下面,举例说明这2种场景是多进程模式的软肋:

  • 及时消息通知程序比如及时聊天程序,一台服务器可能要维持数十万的连接(典型的C10K问题),那么就要启动数十万的进程来维持。这显然不可能。

  • 调用外部Http接口时假设Apache启动100个进程来处理请求,每个请求消耗100ms,那么这100个进程能提供1000qps。

但是,在我们调用外部Http接口时,比如QQ登录、微博登录,耗时较长,假设一个请求消耗10s,也就是1个进程1s处理0.1个请求,那么100个进程只能达到10qps,这样的处理能力就未免太差了。

注:什么是C10K问题?网络服务在处理数以万计的客户端连接时,往往出现效率低下甚至完全瘫痪,这被称为C10K问题。(concurrent 10000 connection)

综上,我们可以看出,Apache是同步阻塞的多进程模式,面对高并发等一些场景,是很苍白的。


2、Nginx何以问鼎高并发?

传统的服务器模型就是这样,因为其同步阻塞的多进程模型,无法面对高并发。
那么,有没有一种方式,可以让我们在一个进程处理所有的并发I/O呢?
答案是有的,这就是I/O复用技术。

①、I/O复用是神马?

最初级的I/O复用

所谓的I/O复用,就是多个I/O可以复用一个进程。

上面说的同步阻塞的多进程模型不适合处理高并发,那么,我们再来考虑非阻塞的方式。

采用非阻塞的模式,当一个连接过来时,我们不阻塞住,这样一个进程可以同时处理多个连接了。

比如一个进程接受了10000个连接,这个进程每次从头到尾的问一遍这10000个连接:“有I/O事件没?有的话就交给我处理,没有的话我一会再来问一遍。”
然后进程就一直从头到尾问这10000个连接,如果这1000个连接都没有I/O事件,就会造成CPU的空转,并且效率也很低,不好不好。

升级版的I/O复用

上面虽然实现了基础版的I/O复用,但是效率太低了。于是伟大的程序猿们日思夜想的去解决这个问题…终于!

我们能不能引入一个代理,这个代理可以同时观察许多I/O流事件呢?

当没有I/O事件的时候,这个进程处于阻塞状态;当有I/O事件的时候,这个代理就去通知进程醒来?

于是,早期的程序猿们发明了两个代理—select、poll。

select、poll代理的原理是这样的:

当连接有I/O流事件产生的时候,就会去唤醒进程去处理。

但是进程并不知道是哪个连接产生的I/O流事件,于是进程就挨个去问:“请问是你有事要处理吗?”……问了99999遍,哦,原来是第100000个进程有事要处理。那么,前面这99999次就白问了,白白浪费宝贵的CPU时间片了!痛哉,惜哉…

注:select与poll原理是一样的,只不过select只能观察1024个连接,poll可以观察无限个连接。

上面看了,select、poll因为不知道哪个连接有I/O流事件要处理,性能也挺不好的。

那么,如果发明一个代理,每次能够知道哪个连接有了I/O流事件,不就可以避免无意义的空转了吗?

于是,超级无敌、闪闪发光的epoll被伟大的程序员发明出来了。

epoll代理的原理是这样的:

当连接有I/O流事件产生的时候,epoll就会去告诉进程哪个连接有I/O流事件产生,然后进程就去处理这个进程。

如此,多高效!

②、基于epoll的Nginx

有了epoll,理论上1个进程就可以无限数量的连接,而且无需轮询,真正解决了c10k的问题。

Nginx是基于epoll的,异步非阻塞的服务器程序。自然,Nginx能够轻松处理百万级的并发连接,也就无可厚非了。

三、swoole如何处理高并发以及异步I/O的实现

1、swoole介绍

swoole是PHP的一个扩展。
简单理解:swoole=异步I/O+网络通信
PHPer可以基于swoole去实现过去PHP无法实现的功能。
具体请参考swoole官网:swoole官网


2、swoole如何处理高并发

①Reactor模型介绍

IO复用异步非阻塞程序使用经典的Reactor模型,Reactor顾名思义就是反应堆的意思,它本身不处理任何数据收发。只是可以监视一个socket(也可以是管道、eventfd、信号)句柄的事件变化。

注:什么是句柄?句柄英文为handler,可以形象的比喻为锅柄、勺柄。也就是资源的唯一标识符、资源的ID。通过这个ID可以操作资源。

Reactor只是一个事件发生器,实际对socket句柄的操作,如connect/accept、send/recv、close是在callback中完成的。

②swoole的架构

swoole采用 多线程Reactor+多进程Worker

swoole的架构图如下:

swoole的处理连接流程图如下:

当请求到达时,swoole是这样处理的:

请求到达 Main Reactor        |
        |Main Reactor根据Reactor的情况,将请求注册给对应的Reactor
(每个Reactor都有epoll。用来监听客户端的变化)        |
        |客户端有变化时,交给worker来处理        |
        |worker处理完毕,通过进程间通信(比如管道、共享内存、消息队列)发给对应的reactor。        |
        |reactor将响应结果发给相应的连接        |
        |
    请求处理完成

因为reactor基于epoll,所以每个reactor可以处理无数个连接请求。 如此,swoole就轻松的处理了高并发。

3、swoole如何实现异步I/O

基于上面的Swoole结构图,我们看到swoole的worker进程有2种类型:
一种是 普通的worker进程,一种是 task worker进程。

worker进程是用来处理普通的耗时不是太长的请求;task worker进程用来处理耗时较长的请求,比如数据库的I/O操作。

我们以异步Mysql举例:

耗时较长的Mysql查询进入worker            |
            |worker通过管道将这个请求交给taskworker来处理            |
            |worker再去处理其他请求            |
            |task worker处理完毕后,处理结果通过管道返回给worker            |
            |worker 将结果返回给reactor            |
            |reactor将结果返回给请求方

如此,通过worker、task worker结合的方式,我们就实现了异步I/O。

四、参考文章

Nginx 多进程模型是如何实现高并发的?
PHP并发IO编程之路
epoll 或者 kqueue 的原理是什么?
IO 多路复用是什么意思?

centos php7安装redis拓展

wget https://codeload.github.com/phpredis/phpredis/zip/php7
unzip php7
cd phpredis-php7

这个地方要用php的phpize来生成configure

我试了几次都不成功 make失败

研究了一会发现linux上貌似默认执行的phpize是php5版本的 按路径调用php7的phpize就可以了

./configure
make && make install

修改php.ini配置 加上redis扩展 

//vim php.ini 添加 extension=redis.so
extension=redis.so

重启php-fpm 查看phpinfo就可以看到拓展已经安装成功了

blob.png

centos7 安装php7

折腾了半天搞死找不到软件包

image.png

解决方法:

yum  install epel-release  //扩展包更新包
yum  update //更新yum源
yum install libmcrypt libmcrypt-devel mcrypt mhash  就ok了

终于可以了

image.png

image.png

简单安装(通过yum)

  1. 安装epel-release

    rpm -ivh http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm
  2. 安装PHP7的rpm源

    rpm -Uvh https://mirror.webtatic.com/yum/el7/webtatic-release.rpm
  3. 安装PHP7

    yum install php70w

编译安装

  1. 下载php7

    wget -O php7.tar.gz http://cn2.php.net/get/php-7.0.4.tar.gz/from/this/mirror
  2. 解压php7

    tar -xvf php7.tar.gz
  3. 进入php目录

    cd php-7.0.4
  4. 安装依赖包

    # 直接复制下面一行(不包括本行)
    yum install libxml2 libxml2-devel openssl openssl-devel bzip2 bzip2-devel libcurl libcurl-devel libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel gmp gmp-devel libmcrypt libmcrypt-devel readline readline-devel libxslt libxslt-devel
  5. 编译配置(如果出现错误,基本都是上一步的依赖文件没有安装所致)
    嫌麻烦的可以从这一步起参考PHP官方安装说明:http://php.net/manual/zh/install.unix.nginx.php

    ./configure \
    --prefix=/usr/local/php \
    --with-config-file-path=/etc \
    --enable-fpm \
    --with-fpm-user=nginx  \
    --with-fpm-group=nginx \
    --enable-inline-optimization \
    --disable-debug \
    --disable-rpath \
    --enable-shared  \
    --enable-soap \
    --with-libxml-dir \
    --with-xmlrpc \
    --with-openssl \
    --with-mcrypt \
    --with-mhash \
    --with-pcre-regex \
    --with-sqlite3 \
    --with-zlib \
    --enable-bcmath \
    --with-iconv \
    --with-bz2 \
    --enable-calendar \
    --with-curl \
    --with-cdb \
    --enable-dom \
    --enable-exif \
    --enable-fileinfo \
    --enable-filter \
    --with-pcre-dir \
    --enable-ftp \
    --with-gd \
    --with-openssl-dir \
    --with-jpeg-dir \
    --with-png-dir \
    --with-zlib-dir  \
    --with-freetype-dir \
    --enable-gd-native-ttf \
    --enable-gd-jis-conv \
    --with-gettext \
    --with-gmp \
    --with-mhash \
    --enable-json \
    --enable-mbstring \
    --enable-mbregex \
    --enable-mbregex-backtrack \
    --with-libmbfl \
    --with-onig \
    --enable-pdo \
    --with-mysqli=mysqlnd \
    --with-pdo-mysql=mysqlnd \
    --with-zlib-dir \
    --with-pdo-sqlite \
    --with-readline \
    --enable-session \
    --enable-shmop \
    --enable-simplexml \
    --enable-sockets  \
    --enable-sysvmsg \
    --enable-sysvsem \
    --enable-sysvshm \
    --enable-wddx \
    --with-libxml-dir \
    --with-xsl \
    --enable-zip \
    --enable-mysqlnd-compression-support \
    --with-pear \
    --enable-opcache
  6. 正式安装

    make && make install
  7. 配置环境变量

    vi /etc/profile

    在末尾追加

    PATH=$PATH:/usr/local/php/bin
    export PATH

    执行命令使得改动立即生效

    source /etc/profile
  8. 配置php-fpm

    cp php.ini-production /etc/php.ini
    cp /usr/local/php/etc/php-fpm.conf.default /usr/local/php/etc/php-fpm.conf
    cp /usr/local/php/etc/php-fpm.d/www.conf.default /usr/local/php/etc/php-fpm.d/www.conf
    cp sapi/fpm/init.d.php-fpm /etc/init.d/php-fpm
    chmod +x /etc/init.d/php-fpm
  9. 启动php-fpm

    /etc/init.d/php-fpm start

10.在系统服务目录里创建php-fpm.service文件

vim /lib/systemd/system/php-fpm.service

写入以下内容(路径改成自己的)

[Unit]
Description=php-fpm
After=network.target
[Service]
Type=forking
ExecStart=/usr/local/php/sbin/php-fpm
PrivateTmp=true
[Install]
WantedBy=multi-user.target

先关闭nginx,php-fpm
使用以下命令开启

systemctl start nginx.service             #如果服务是开启状态,使用此命令会启动失败。
systemctl start php-fpm.service

开启成功,将服务加入开机自启

systemctl enable nginx.service                #注意后面不能跟空格
systemctl enable php-fpm.service

image.png

image.png

Laravel 5.0 之 表单验证类 (Form Requests)

让人头痛的表单验证

本文译自 Matt Stauffer 的 系列文章 .

只要你曾经在使用 Laravel 框架的过程中试图找到有关用户输入验证的最佳实践, 你就应该了解这是一个争论最多并且几乎没有达成共识的话题. 我们可以在控制器中进行验证, 可以在单独的一个服务层进行验证, 可以在模型中进行验证, 当然还可以在 Javascript 中进行验证 (这只是一个玩笑, 谁都知道不能只依赖于客户端的验证). 但是, 哪一种做法才是最佳的呢?

Laravel 5.0 新引入的表单请求 (Form Request) 特性提供了集规范性 (差不多就是 "最佳实践" 的意思) 和便捷性 (这是比之前任何一种选择都更强大也更便捷的方式) 于一体的, 在 Laravel 中执行数据检查和验证的新手段.

说明: 本文中使用新的 view() 辅助方法代替了旧版本中的 View::make() .

Form Requests 使表单验证不再让人头痛

Laravel 5.0 带来了 Form Requests, 这是一种特殊的类型, 用于在提交表单时进行数据的检查和验证. 每个 Form Request 类至少包含一个 rules() 方法, 这个方法返回一组验证规则. 除此之外还必须包含一个 authorize() 方法, 该方法返回一个布尔值, 代表是否允许用户执行本次请求.

Laravel 会在解析 POST 路由之前自动把用户输入的信息传递给相应的表单请求, 因此我们的所有验证逻辑都可以移到独立于控制器和模型之外的 FormRequest 对象中.

开始实践: 快速创建一个 Laravel 5.0 项目

如果你还没有创建好的 Laravel 5.0 项目, 用下面的命令创建一个:

$ composer create-project laravel/laravel myProject dev-develop --prefer-dist

1. 添加路由

// app/Http/routes.phpRoute::get('/', 'FriendsController@getAddFriend');Route::post('/', 'FriendsController@postAddFriend');

2. 创建控制器

//app/Http/Controllers/FriendsController:namespace App\Http\Controllers;use App\Http\Requests\FriendFormRequest;use Illuminate\Routing\Controller;use Response;use View;class FriendsController extends Controller{	public function getAddFriend()
	{		return view('friends.add');
	}	public function postAddFriend(FriendFormRequest $request)
	{		return Response::make('Friend added!');
	}
}

3. 创建视图

<html><body>
	@foreach ($errors->all() as $error)		<p class="error">{{ $error }}</p>
	@endforeach	<form method="post">
		<label>First name</label><input name="first_name"><br>
		<label>Email address</label><input name="email_address"><br>
		<input type="submit">
	</form></body></html>

4. 创建 FormRequest

// app/http/requests/FriendFormRequest.phpnamespace App\Http\Requests;use Illuminate\Foundation\Http\FormRequest;use Response;class FriendFormRequest extends FormRequest{	public function rules()
	{		return [			'first_name' => 'required',			'email_address' => 'required|email'
		];
	}	public function authorize()
	{		// 只允许登陆用户
		// 返回 \Auth::check();
		// 允许所有用户登入
		return true;
	}	// 可选: 重写基类方法
	public function forbiddenResponse()
	{		// 这个是可选的, 当认证失败时返回自定义的 HTTP 响应. 
		// (框架默认的行为是带着错误信息返回到起始页面)
		// 可以返回 Response 实例, 视图, 重定向或其它信息
		return Response::make('Permission denied foo!', 403);
	}	// 可选: 重写基类方法
	public function response()
	{		// 如果需要自定义在验证失败时的行为, 可以重写这个方法
		// 了解有关基类中这个方法的默认行为,可以查看:
		// https://github.com/laravel/framework/blob/master/src/Illuminate/Foundation/Http/FormRequest.php
	}
}

接下来, 用 php artisan serve 或者你自己喜欢的方式启动服务器. 提交表单, 你可以看到我们并没有往控制器中添加任何一行验证逻辑, 但是验证规则已经生效了.

其它用例

如果对 "新增" 和 "编辑" 有不同的规则, 或者根据不同的输入进行不同的验证, 要怎么办呢? 这里有几个可以参考的例子, 虽然还不能确定这些就是 "最佳实践":

采用分开的 form requests

Laravel 并没有规定你不能对 "新增" 和 "编辑" 操作采用不同的 form request 类. 所以你可以创建一个包含所有规则的 FriendFormRequest 作为基类, 然后把它扩展为 addFriendFormRequest 和 editFriendFormRequest 两个子类, 每个子类都可以实现各自的默认行为.

采用条件判断逻辑

rules() 作为一个方法而不是属性, 带来的好处就是你可以在方法中添加判断逻辑:

...class UserFormRequest extends FormRequest{
	...	protected $rules = [	
		'email_address' => 'required',		'password' => 'required|min:8',
	];	public function rules()
	{
		$rules = $this->rules;		// 根据不同的情况, 添加不同的验证规则
		if ($someTestVariableShowingThisIsLoginInsteadOfSignup)
		{
			$rules['password'] = 'min:8';
		}		return $rules;
	}
}

也可以在 authorize 方法中添加逻辑, 比如:

...class FriendFormRequest extends FormRequest{
	...	public function authorize()
	{		if ( ! Auth::check() )
		{			return false;
		}
		$thingBeingEdited = Thing::find(Input::get('thingId'));		// 如果是编辑操作, 或者当前用户不是对象创建者
		if ( ! $thingBeingEdited || $thingBeingEdited->owner != Auth::id()) {			return false;
		}		return true;
	}
}

自定义校验

除了上面的方式, 如果需要对验证逻辑进行更深入的控制, 可以重写提供校验对象实例的方法. 下面是一个简单的实例, 后续会专门写一篇文章来解释:

...class FriendFormRequest extends FormRequest{	public function validator(ValidationService $service)
	{
		$validator = $service->getValidator($this->input());		// 可选: 通过新的 ->after() 方法来进行自定义
		$validator->after(function() use ($validator)) {			// 在这里可以做更多更深入的校验
			$validator->errors()->add('field', 'new error);
		}
	}
}

ValidatesWhenResolved 接口

后续还会有一篇有关 ValidatesWhenResolved 接口的文章, 不过那篇文章重点讨论的是对方法/路由等的校验. IOC 何时提供什么东西, 这个在 Laravel 5.0 版已经分离出一个单独的接口. 官方文档: https://github.com/illuminate/contracts/blob/master/Validation/ValidatesWhenResolved.php

其它可自定义的参数:

$redirect : 校验失败时要重定向到的 URI. $redirectRoute : 校验失败时要重定向到的路由.$redirectAction : 校验失败时要重定向到的方法. $dontFlash : 重定向时不要传递的输入项的键 (默认值: ['password', 'password_confirmation']).

写在最后

通过文本可以看到, Form Requests 对于简化表单请求的数据校验是非常强大和方便的. 如果你阅读本文觉得还不够, 可以观看关于 Form Request 的 这个视频 .

本文写作时, Laravel 5.0 还未正式发布, 因此上述内容最终可能还会有修改, 或者作者遗漏了某些东西. 如果你有建议或者对文章内容的修正, 可以在 给译者发邮件 或者(在 Twitter 上直接联系原作者)

[http://twitter.com/stauffermatt].

Service Provider 测试实例

简单归纳一下

1、服务类继承契约接口(为了对类的定义加以约束)

2、创建服务提供者(Service Provider)

3、在服务提供者的register内绑定服务类

4、注册服务提供者到config

5、创建服务容器(Service Container

6、将服务提供者依赖注入服务容器

7、服务容器使用该服务

1、定义服务类

有了上一节有关服务容器的讲述,理解起服务提供者来很简单。我们这里先定义一个绑定到容器的测试类TestService,为了对类的定义加以约束,我们同时还定义一个契约接口TestContract。

定义TestContract如下:

<?php

namespace App\Contracts;

interface TestContract
{
    public function callMe($controller);
}

定义TestService如下:

<?php

namespace App\Services;

use App\Contracts\TestContract;

class TestService implements TestContract
{
    public function callMe($controller)
    {
        dd('Call Me From TestServiceProvider In '.$controller);
    }
}

2、创建服务提供者

接下来我们定义一个服务提供者TestServiceProvider用于注册该类到容器。创建服务提供者可以使用如下Artisan命令:

php artisan make:provider TestServiceProvider

该命令会在app/Providers目录下生成一个TestServiceProvider.php文件,我们编辑该文件内容如下:

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Services\TestService;

class TestServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {
        //
    }

    /**
     * Register the application services.
     *
     * @return void
     * @author LaravelAcademy.org
     */
    public function register()
    {
        //使用singleton绑定单例
        $this->app->singleton('test',function(){
            return new TestService();
        });

        //使用bind绑定实例到接口以便依赖注入
        $this->app->bind('App\Contracts\TestContract',function(){
            return new TestService();
        });
    }
}

可以看到我们使用了两种绑定方法,更多绑定方法参考服务容器文档。

3、注册服务提供者

定义完服务提供者类后,接下来我们需要将该服务提供者注册到应用中,很简单,只需将该类追加到配置文件config/app.php的providers数组中即可:

'providers' => [

    //其他服务提供者

    App\Providers\TestServiceProvider::class,
],

4、测试服务提供者

这样我们就可以在应用中使用该服务提供者了,为了测试该服务提供者我们首先使用Artisan命令创建一个资源控制器TestController:

php artisan make:controller TestController

然后在路由配置文件routes.php中定义路由:

Route::resource('test','TestController');

最后去TestController中编写测试代码:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

use App\Http\Requests;
use App\Http\Controllers\Controller;

use App;
use App\Contracts\TestContract;

class TestController extends Controller
{
    //依赖注入
    public function __construct(TestContract $test){
        $this->test = $test;
    }

    /**
     * Display a listing of the resource.
     *
     * @return Response
     * @author LaravelAcademy.org
     */
    public function index()
    {
        // $test = App::make('test');
        // $test->callMe('TestController');
        $this->test->callMe('TestController');
    }

    ...//其他控制器动作
}

然后我们去浏览器中访问http://laravel.app:8000/test

分别测试使用App::make和依赖注入解析绑定类调用callMe方法的输出,结果一样,都是:

"Call Me From TestServiceProvider In TestController"

好了,大功告成,是不是很简单?!

此外,Laravel服务提供者还支持延迟加载,具体可参考服务提供者文档

控制反转(DI) 依赖注入(IoC)

<?php
Class Fight{
    public function __construct($options){
        //...
    }
}

Class Force{
    public function __construct($options){
        //...
    }
}


Class Shot{
    public function __construct($options){
        //...
    }
}

//超能力工厂类
Class SuperModuleFactory{
    public function makeModule($moduleName, $options){
        switch($moduleName){
            case 'Fight':
                return new Fight($options[0], $options[1]);
            case 'Force':
                return new Force($options[0]);
            case 'Shot':
                return new Shot($options[0], $options[1], $options[2]);
            // case 'more': .......
            // case 'and more': .......
            // case 'and more': .......
            // case 'oh no! its too many!': .......
        }
    }
}

Class Superman{
    protected $power;

    public function __construct(array $modules)
    {
        //初始化工厂
        $factory = new SuperModuleFactory;

        //通过工厂提供的方法制造需要的模块
//        $this->power = $factory->makeModule('Fight',[9,100]);
//        $this->power = $factory->makeModule('Force',[50]);
//        $this->power = $factory->makeModule('Shot',[99,150,20]);

        /*
        $this->power = array(
            $factory->makeModule('Force',[45]),
            $factory->makeModule('Shot',[99,40,10])
        );
        */

        //通过工厂提供的方法制造需要的模块
        foreach($modules as $moduleName => $moduleOptions){
            $this->power[] = $factory->makeModule($moduleName, $moduleOptions);
        }

    }
}

//创建超人
$superman = new Superman([
    'Fight' => [9, 100],
    'Shot' => [99, 50, 2]
]);
//“超人”的创建不再依赖任何一个“超能力”的类,如果修改了或者增加了新的超能力只需要针对修改SuperModuleFactory即可。




?>

<?php
//当超能力急需拓展的时候,如果依赖超能力工厂就会在switch里堆很多东西,我们需要制定统一接口作为一种“契约”,这样无论是谁创建出的模组,都符合这样的接口,就可以被正常使用。
interface SuperModuleInterface{
    /**
     * 超能力激活方法
     *
     * 任何一个超能力都得有该方法,并拥有一个参数
     *@param array $target 针对目标,可以是一个或多个,自己或他人
     */
    public function activate(array $target);
}

/**
 * X-超能量
 */
class XPower implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}

/**
 * 终极炸弹 (就这么俗)
 */
class UltraBomb implements SuperModuleInterface{
    public function activate(array $target){
        // 这只是个例子。。具体自行脑补
    }
}

//提供的模组实例必须是一个SuperModuleInterface接口的实现
//正是由于超人的创造变得容易,一个超人也就不需要太多的超能力,我们可以创造多个超人,并分别注入需要的超能力模组即可。
//开头到现在提到的一系列依赖,只要不是由内部生产(比如初始化、构造函数 __construct 中通过工厂方法、自行手动 new 的),而是由外部以参数或其他形式注入的,都属于依赖注入(DI)
class Superman{
    protected $module;

    public function __construct(SuperModuleInterface $module)
    {
        $this->module = $module;
    }
}

//下面就是一个典型的依赖注入

//超能力模组
$superModule = new XPower;
//初始化一个超人,并注入一个超能力模组依赖
$superMan = new Superman($superModule);




//我们需要自动化 —— 最多一条指令,千军万马来相见
//工厂模式升华 -- IoC容器
class Container{
    protected $binds;
    protected $instance;

    public function bind($abstract, $concrete){
        if($concrete instanceof Closure){
            //instanceof Closure判断$concrete是否是一个闭包
            $this->binds[$abstract] = $concrete;
        }else{
            // instance 方法绑定一个已存在的对象实例到容器,这里不是绑定只是把concrete放到数组里
            $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('xpower', function($container) {
    return new XPower;
});

//同上
$container->bind('ultrabomb', function($container){
    return new UltraBomb;
});

//向该超级工厂添加超人的生产脚本
$container->bind('superman', function($container, $moduleName){
    return new Superman($container->make($moduleName));
});

//绑定后的XPower以及UltraBomb等超能力 使用$container->make()就可以返回实例
//$container->make('superman','xpower')可以实例化superman注入xpower的实例
//因为superman bind的时候闭包实现了对第二个参数(moduleName)的实例化
//所以就可以直接通过超级工厂类穿件superman的实例
//*********************** 华丽丽的分割线 *************************
//开始启动生产
$superman_1 = $container->make('superman','xpower');
$superman_1 = $container->make('superman','ultrabomb');
$superman_1 = $container->make('superman','xpower');
// ...随意添加


//要添加特异功能的参数我觉得应该修改代码如下,当然也需要修改一下前面的XPower类
$container->bind('xpower',function($container, $parameters){
    return new XPower($parameters);
    //然后XPower的构造函数接收参数做相应处理
});

$container->bind('superman', function($container, $moduleName, $parameters){
    return new Superman($container->make($moduleName, $parameters));
});

$superman_x = $container->make('superman','xpower',array(9,50,25));
//代码没有运行过不知道这样可不可行 只是一个思路


//实际上,真正的 IoC 容器更为高级。
//我们现在的例子中,还是需要手动提供超人所需要的模组参数,但真正的 IoC 容器会根据类的依赖需求,自动在注册、绑定的一堆实例中搜寻符合的依赖需求,并自动注入到构造函数参数中去。
//Laravel 框架的服务容器正是这么做的。实现这种功能其实理论上并不麻烦,但我并不会在本文中写出,因为……我懒得写。
?>

http://laravelacademy.org/post/769.html

laravel 学习笔记

这几天纯看文档稍微记点东西吧

路由 RESTful 资源控制器 Route::resource

动词 路径 行为 路由名称

GET /photo 索引 photo.index
GET /photo/create 创建 photo.create
POST /photo 保存 photo.store
GET /photo/{photo} 显示 photo.show
GET /photo/{photo}/edit 编辑 photo.edit
PUT/PATCH /photo/{photo} 更新 photo.update
DELETE /photo/{photo} 删除 photo.destroy


DI 依赖注入

Ioc依赖反转

其实这些概念在angular和之前看设计模式的时候都接触到过

http://www.jianshu.com/p/b7edc9b22b8a

http://blog.leokim.cn/2017/04/18/%E6%8E%A7%E5%88%B6%E5%8F%8D%E8%BD%ACdi-%E4%BE%9D%E8%B5%96%E6%B3%A8%E5%85%A5ioc/

Service Container, Service Provider,Contracts, Facade

Service Container就是被注入的类,作为容器,在构造函数内被注入,或者使用facade在顶部声明


Service Provider的bind方法

其实就是在Service Prodide里用bind方法来绑定别名,这个地方文档里的鬼例子看着让人着实迷惑

$this->app->bind('post', function ($app) {
 return new App\Models\Post;
});

绑定后就可以直接

$app->make('post')


singleton和bind的区别

它们两个都是返回一个类的实例,不同的是singleton是单例模式,而bind是每次返回一个新的实例。

https://segmentfault.com/a/1190000004388879


修改更新查找MySQL5.7.x的root用户的默认密码

最近新安装了wamp3.0.4里面附带的mysql已经升级到了5.7版本了。

MySQL5.7在性能方面有很大的提升。安装成功之后默认root的密码为空能登录。

但是正常情况下需要给root重新设置新的密码。

对于MySQL5.7版本来说和之前的5.6及以下版本的user表不一样了(user表里面没有了password这个字段了,mysql5.7 中保存密码的字段是 authentication_string)。

直接上命令行。

mysql5.7 中保存密码的字段是 authentication_string
 
//如果你用Navicat的话,先默认密码登录直接执行sql语句
update mysql.user set authentication_string=password("leokim.cn") where User="root" and Host="localhost";
 
flush privileges; 
 
 
//如果你用dos的话--进入到bin目录
cd D:\wamp\bin\mysql\mysql5.7.11\bin
 
//输入mysql 用户名和密码
mysql -u root -p
 
//新开DOS, 输入mysql,出现mysql> 命令标识符 
mysql>update mysql.user set authentication_string=password("www_dodobook_net") where user="root" and Host="localhost"; 
mysql>flush privileges; 
mysql>quit;

如果你忘了密码想找回密码的话,可以参考下面的方法

其实想要重置 5.7 的密码很简单,就一层窗户纸:
1、修改 /etc/my.cnf,在 [mysqld] 小节下添加一行:skip-grant-tables=1
这一行配置让 mysqld 启动时不对密码进行验证
2、重启 mysqld 服务:systemctl restart mysqld
3、使用 root 用户登录到 mysql:mysql -u root 
4、切换到mysql数据库,更新 user 表:
update user set authentication_string = password('root'), password_expired = 'N', password_last_changed = now() where user = 'root';
在之前的版本中,密码字段的字段名是 password,5.7版本改为了 authentication_string
5、退出 mysql,编辑 /etc/my.cnf 文件,删除 skip-grant-tables=1 的内容
6、重启 mysqld 服务,再用新密码登录即可.

另外,MySQL 5.7 在初始安装后(CentOS7 操作系统)会生成随机初始密码.

并在 /var/log/mysqld.log 中有记录,可以通过 cat 命令查看,找 password 关键字.

找到密码后,在本机以初始密码登录,并且(也只能)通过命令

alter user 'root'@'localhost' identified by 'root'

修改 root 用户的密码为 root,然后退出,重新以root用户和刚设置的密码进行登录即可。

 

也可以在安装的时候不生成随机密码,用 sudo mysqld –initialize-insecure

然后在自己设置密码 sudo /usr/bin/mysqladmin -uroot password 密码

如果你是之前的版本MySQL5.5 MySQL5.6的话请直接使用以下语句和方法

//先输入密码。默认为空就直接按Enter即可
mysql> use mysql
Database changed
mysql> update user set password=PASSWORD('leokim') where user='root';
Query OK, 3 rows affected (0.02 sec)
Rows matched: 3  Changed: 3  Warnings: 0
  
mysql> flush privileges;
Query OK, 0 rows affected (0.01 sec)
  
mysql> quit

php 获取月份最后一天

<?php
    echo date("t",strtotime('2016-02'));
?>

常用方法

<?php
echo strtotime("now"), "\n";
echo strtotime("10 September 2000"), "\n";
echo strtotime("+1 day"), "\n";
echo strtotime("+1 week"), "\n";
echo strtotime("+1 week 2 days 4 hours 2 seconds"), "\n";
echo strtotime("next Thursday"), "\n";
echo strtotime("last Monday"), "\n";
?>

php生成的csv, 无法完整显示带前导0的数字

PHP生成csv文件时,如果数字的第一位为0的话,显示时则不会显示第一个0

可以在php中做特殊处理,使得生成的csv在显示时能显示出第一个0

有两种方法可以实现:

1、给数字加上引号,并且在引号前面再加上个等号,如数字0555,,则生成时为="0555"

2、在数字前面加上制表符,如是数字0555,则生成时为\t0555

代码如下:

$arr    = array (  
    array ('111', 2222, '0555'),  
    array ('222', 3333, '0666')  
);  
$fp    = fopen('t.csv', 'w');  
foreach ($arr as $row) {  
    fputcsv($fp, array_map('h', $row));  
}  
fclose($fp);  
  
function h($v) {  
    if (($v{0} == '0') && is_numeric($v)) {  
        $v    = '="' . $v . '"';    //第一种方式  
        //$v   = "\t{$v}";  //第二种方式  
    }  
    return $v;  
}