分类目录归档:PHP

laravel/lumen 数据库 和 Eloquent 浅析

一、 DatabaseServiceProvider 服务提供者

主要做了一下几件事:
– 初始化Model
– 注册Eloquent工厂
– 注册可队列化实体解析器实现
– 注册DB工厂
– 注册DB
– 注册DB连接

1、 初始化Model

主要清空初始化的类库和全局作用域。

2、 注册Eloquent工厂

作用暂未理解。
从代码中可以看到框架首先绑定了FakerGenerator::class数据伪造生成类,将其初始化到EloquentFactory中,并却传入了database_path('factories'),那么可以猜到大致作用为数据伪造器。

todo: 理解EloquentFactory

3、 注册可队列化实体解析器实现

用作暂时未理解。

todo: 理解QueueEntityResolver

4、 注册DB工厂

ConnectionFactory通过配置创建不同的的链接。
mysql为例,框架会通过MySqlConnector为我们创建一个数据库连接。MySqlConnector创建出来的其实就是PDO

另外,当我们开启主从分离的时候,ConnectionFactory还会根据相应的规则,创建读链接或者是写链接。

5、 注册DB

DatabaseManager负责管理ConnectionFactory创建出来的连接。
并且对外提供connection(创建)、purge(清除)、disconnect(断开)、reconnect(重连)方法,让外部进行统一调用。

6、注册DB连接

app('db.connection')后,返回一个有ConnectionFactory创建DatabaseManager负责管理的PDO连接。

Connection对外提供selectinsertupdatedeletecommitrollBackbeginTransaction等方法,提供对数据库的基本操作。

二、 Eloquent\ModelEloquent\BuilderQuery\BuilderSchema\Builder

Eloquent是框架提供的ORM

1、Eloquent\Model

Eloquent\Model是一个抽象类。模型必须继承它。Eloquent\Model类的主要作用,也是抽象类的主要作用是对子类所有功能的的一个抽象归纳。

主要抽象功能有:

  • 全局作用域管理,比如方法:addGlobalScopehasGlobalScopegetGlobalScopegetGlobalScopes等。
  • 事件管理,比如方法:observeflushEventListenersregisterModelEventgetObservableEventssetObservableEvents等。
  • 读写分离链接的主动选择,比如方法:ononWriteConnection
  • 关联关系管理,比如方法:hasOnemorphOnebelongsTomorphTohasManyhasManyThrough等。
  • 模型输入/输出数据管理,比如方法:guard系列方法(负责输入时过滤不想要的字段)、fill系列方法输入方法、getAttribute系列方法(负责输出时过滤敏感字段,格式化输出)
  • 数据操作方法,比如createupdatepushsavedestroydestroydelete等。这些方法提供了常用的数据库操作方法,并且在内部封装了事件机制。

个人感觉Eloquent\Model封装了比较多的东西,过于庞大,功能比较复杂,但是好处也是功能齐全,简便操作。可以更具自己要求是否开启Eloquent,为了简便开启Eloquent, 抑或是为了追求性能放弃Eloquent

2、Eloquent\Builder

Eloquent\Builder 在我看来才是真正的 Eloquent的核心, Eloquent\Model只是对其提供的方法进行封装和管理。

比如我们调用Model\User::first()方法,其实是通过Eloquent\Model__callStatic方法或者__call方法转发到了Eloquent\Builder上面。

我们需要注意到,通过Eloquent\Model每一次对数据的操作都会创建一个Eloquent\Builder。主要作用是一次请求封装在一个上下文中,我们可以很好的对其控制。好处有:

  1. 防止请求数据被污染
  2. 可以很好的管理内存

同理,http请求访问laravel\lumen框架,框架都会创建一个request也是为了如此。

还有一个细节需要注意的是Eloquent\Builder中的$passthru变量。Eloquent\Builder__call方法中,会判断如果请求放在存在于这个变量中的的时候,会返回Query\Builder,反之返回他自己Eloquent\Builder

3、 Query\Builder

Query\Builder 的作用就是存储本次数据库操作的基本操作。并且提供toSql方法,调用Query\Grammars\Grammar类来生成SQL语句。最后将SQL语句和绑定参数传给Connection类进行数据库操作。

Eloquent\Model在创建Eloquent\Builder的时候,会同时创建一个QueryBuilder,做一对一对于,所以QueryBuilder也可以很好的做好隔离工作。

4、 Schema\Builder

Schema\Builder是框架对数据库表操作的封装。

比如数据库的新增、修改、删除、索引、外键的管理等。

三、其他

1、 UML类图

laravel/lumen —— Artisan Console 命令行

1、 简介

laravellumen提供了artisan命令行接口,以便我们来进行命令行操作。

我们可以通过php artisan list来查看框架为我们提供了哪些接口。

也可以用help命令来查看,如何使用命令。

2、编写命令

laravel的话你可以直接使用命令行来创建命令。

你还可以使用--command参数来设置终端命令名。

这样app\Console\Commands目录下就多了SayHello这个类。

变量protected $signature = 'say:hello'; 就是刚刚设置的终端命令名。
变量protected $description = 'a test command, just say hello';是用来描述改名了作用的。

我们可以将我们想做的操作写入handle方法。

接下来我们将该命令注册到App\Console\Kernel中就可以执行了。

通过php artisan list命令可以查看到,say:hello已经注册成功。

执行命令

3、交互命令

3.1、 自定义参数

我们可以通过以下方式进行传参。

还可以让该参数可选可不选,以及定义默认的可选参数值:

使用argument方法可以来获取参数

通过php artisan say:hello cc来进行调用

还有一种传出方式,可以通过--方式来进行传参。

这种方式我们需要用option方法来接收参数。

如果--queue开关被传递,其值是true,否则其值是false

也可以将其改造成传值方式

简写方式

传数组

3.2、 输入提示

我们将命令改回say:hello不传参数形式。

输入提示分4种模式:

  • 询问
  • 密码
  • 确认
  • 选择

询问模式

不输入内容,自定会进行重试操作。

密码模式

密码模式输入字符不会再命令行中显示。但是需要开启exec的函数使用权。

确认模式

默认为no,只有输入yYyesYES才算通过

选择模式

第一种anticipate用户可以无视给出的提示,自己填写。

第二种choice,用户必须选择提示中的选项,否则失败。

3.3、 不同的输出样式

4、调用command

我们可以在routes.php中去调用命令。当然,这只是个例子,你可以在任何地方调用它。

laravel/lumen —— 面向接口编程

面向接口编程和面向对象编程并不是平级的,它并不是比面向对象编程更先进的一种独立的编程思想,而是附属于面向对象思想体系,属于其一部分。或者说,它是面向对象编程体系中的思想精髓之一。

多态

类有三大特性:封装继承多态

封装继承比较好理解,而多态比较抽象,不是很好解释。使用面向接口编程的思想就可以轻松简单的来解释。

多态顾名思义就是多种形态的意思。

以人和动物的例子来说明。人属于动物,可以走路、跑步、吃东西、睡觉、发声等。但是人区别于低级动物的地方是,人有思想。

使用代码来实现:

定义动物接口和人类接口。

人和猫两个类来实现。

猫饿了可以吃鱼。

但是人类高级一点,可以先思考,我要吃什么,怎么烹饪,创造工具,来提升我们生活水平。

这个时候我们可以说,动物有两种形态,一种是人,另外一种是猫。反过来也可以说人是动物,也是思想的高级生物。

怎么在代码上体现呢?

房子可以属于猫,或者人。拥有着可以在里面睡觉,使用不同的方式睡觉。

但是实验室,只有有思想能创造能发明的人类才能进入,而猫不行。

上面的例子可能有不妥的地方,但是也大致说明了,面向接口编程在多态中的应用。

契约

laravel/lumen框架中提供了契约这个概念。契约是指框架提供的一系列定义核心服务的接口。

比如,框架提供了Illuminate\Contracts\Mail\Mailer契约,那么我们可以通过实现这个契约来定制我们自己的发送邮件代码了。当然别人写的代码,由于他也实现了这个契约,我们可以直接拿过来使用。

laravel/lumen提供了一个Illuminate\Contracts\Config\Repository队列的契约。我们可以通过实现它来定制我们自己的配置操作类。

正常情况,我们都需要写一个package包,发布到packagist.org上去,然后通过composer下载到vendor中去。

但是为了方便起见,我就直接写在了app\Config目录下了。如题如何发布包到packagist.org上去可以参考我之前的博客创建自己的Composer包

我们需要实现Illuminate\Contracts\Config\Repository中所有的方法。

调用

laravel/lumen框架中,其实已经多这部分做了实现。上面只是一个简单的例子,用来说明契约的用法。

laravel/lumen提供的所有契约都可以在他的Github上找到。

服务容器和服务提供者

服务容器是一个用于管理类依赖和执行依赖注入的强大工具。他可以降低类之间的耦合,也可以方便我们去创建调用。

我们可以在bootstrap/app.php中去注册RedisConfig。然后使用app('redisconfig')方式来获取配置对象。

接下来调用与刚刚一样。

关于服务容器我们有3中绑定方式。

1、 单例

2、 普通

3、 绑定实例

当服务容器变多如何管理?还是第三方无法侵入我们的代码,如何提供服务容器?这时候可以使用服务提供者。

我们可以将服务提供者写在app/Providers目录下。然后将bootstrap/app.php中注册的容器搬入里面。最后在bootstrap/app.php注册服务提供者。

和之前一样可以调用。

门面

laravel/lumen的门面作为服务容器中的底层类的“静态代理”,相比于传统静态方法,在维护时能够提供更加易于测试、更加灵活的、简明且富有表现力的语法。

门面我们可以写入app\Facades文件夹下。

修改RedisConfigServiceProvider

然后我们就可以使用门面了。

laravel/lumen —— 软删除

在项目中,总是会碰到一些需要删除的数据。但是数据理论上是不能够物理删除(硬删除)的,只能进行逻辑删除(软删除)。

laravel/lumenEloquent ORM为我们提供了方便的软删除操作。

单表软删除

我们先创建user表。结构比较简单,三个字段:自增ID、用户名和密码。

并且为其插入一些数据。

正常情况下,我们进行删除操作。就是将数据重数据库中完全删除了。除了重二进制日志中进行回复,其他已无法恢复数据了。

接下来我们进行软删除配置:

1、 在Model中引入SoftDeletes

2、 为我们的数据增加几个字段created_atupdated_atdeleted_at

进入项目根目录执行命令,我们使用迁移来实现添加字段。

随后就会在database\migrations目录下找到2016_03_27_043709_alter_user_deleted_at这个文件。我们修改up方法如下。

随后执行php artisan migrate命令,数据库中字段就以添加。

3、 实现软删除

我们可以看到 deleted_at字段就会被填入删除时间点。

4、 查询

当我们去再去查询的时候,就会发现已经查询不到了。

如果说我们一定要查到被软删除的数据我们可以使用withTrashed

查询到后我们也可以将其恢复。

5、 原理

为什么引入SoftDeletes后就可以现象软删除呢?

当我们创建Eloquent对象的时候。会对引入的SoftDeletes Trait进行初始化。

__construct -> bootIfNotBooted -> boot -> bootTraits

bootTraits方法中会去进行初始化SoftDeletes Trait

SoftDeletes Trait中的bootSoftDeletes就是被初始化的方法。意思是添加一个软删除的作用于。

作用域的详情可以看到这里:http://laravelacademy.org/post/2995.html#ipt_kb_toc_2995_19

SoftDeletes Trait 主要作用的就是通过不同手段(重写方法、添加作用域等)来实现软删除,以及查询是自动添加deleted_at is null

多表软删除

我们再创建一个contentcomment表。并且重复上面的步骤添加上软删除。

形成了一个如下关系

接下来进行操作

1、 查询

我们可以被软删除的id=3content数据并没有查出来。

结合withTrashed来查询,就可以查出来被软删除的数据。

关联查询

软删除是Eloquent提供的功能,如果直接使用门面DB来操作或者使用leftJoin等操作是没办法自动实现的,需要我们手动来控制。

finite state machine

  • 状态模式
  • 状态间切换的控制
  • 基于事件

状态模式

状态模式(State Pattern) :允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

主要角色

  • 抽象状态角色: 定义一个接口,用以封装环境对象的一个特定的状态所对应的行为
  • 具体状态角色: 每一个具体状态类都实现了环境的一个状态所对应的行为
  • 环境角色: 定义客户端所感兴趣的接口,并且保留一个具体状态类的实例。这个具体状态类的实例给出此环境对象的现有状态

为什么要用状态模式

为了解耦各个状态间的工作,让人能够清晰的看懂某个状态下做些什么事情。

代码实现

接下来就是进行实践:

状态间切换的控制

状态切换

举个人类跑步的例子:

人的最大体力和当前体力为5,
需要跑步13KM,每1KM消耗1HP,
当体力不够是需要停下来休息,回复到最大体力后,继续跑,
跑完13KM,结束。

最后运行:

状态控制

虽然这个时候代码结构已经很清晰。但是还有一个问题就是无法控制状态之间的转换。

比如,征程流程准备->跑步->休息->跑步->结束,但是这个时候的代码可以跑步->准备之间切换。这个很显然违反的我们的规则。

这个时候就需要引入规律机制。

首先,增加一个简单的过程类

其次,Context变量中增加,过程对象

调用后,返回内容和上面一致。

但是当出现不允许调用情况是会抛出异常。

基于事件

我们还可以为进入状态,离开状态添加事件,以便代码更清晰,更容易解耦。

事件的底层其实就是观察模式。为了方便我就使用illuminate/events包。

1、为Context类添加event变量

2、设置事件

3、设置监听点以及调用

swoole —— process

  • 创建进程
  • 进程间通信
    • 管道
    • 队列
    • 信号
  • 其他
    • 执行系统命令
    • 回收
    • 守护进程
    • 命名进程
    • CPU亲和性

swoole_process

swoole的进程对比pcntl扩展进程的优势:

  • pcntl无法用在fpm/apache
  • pcntl没有提供进程间通信的功能
  • pcntl不支持重定向标准输入和输出
  • pcntl只提供了fork这样原始的接口,容易使用错误
  • swoole_process提供了比pcntl更强大的功能,更易用的API,使PHP在多进程编程方面更加轻松。

创建进程

原型:

参数:

  • function: 子进程创建成功后要执行的函数
  • redirect_stdin_stdout: 重定向子进程的标准输入和输出。 启用此选项后,在进程内echo将不是打印屏幕,而是写入到管道。读取键盘输入将变为从管道中读取数据。 默认为阻塞读取。
  • create_pipe: 是否创建管道,启用redirect_stdin_stdout后,此选项将忽略用户参数,强制为true 如果子进程内没有进程间通信,可以设置为false

返回值:

swoole_process对象,数据结构包括了管道描述符和回调函数。

进程间通信

管道

当创建进程时,进行了输入输出重定向,swoole就会帮我们自动创建管道。

这里运行程序并不会输出process: echo 1。因为输出意见被输入到管道中去了。

这里我们关闭输出输入重定向,并且开启管道。进行一个简单的案例说明:

队列

信号

父进程中注册信号(子进程被kill触发),自进程中注册信号(本身被kill触发)。

开启子进程后,父进程立即杀死子进程,触发子进程信号事件。子进程关闭后,触发父进程信号事件。

其他

执行系统命令

会输出 ps -ef执行后内容。

回收

子进程结束必须要执行wait进行回收,否则子进程会变成僵尸进程

守护进程

使当前进程脱变为一个守护进程。

命名进程

CPU亲和性

设置CPU亲和性,可以将进程绑定到特定的CPU核上。

事务嵌套解决方案

mysql官方并不支持事务嵌套,当第二个事务开始时候,会隐式的先调用commit提交之前的事务,而后在开启第二次的事务。

在正常开发中难免会出现失误嵌套的情况。如何解决呢?

laravel/lumen框架使用了mysqlSAVEPOINT方法来解决这一问题。

mysql savepoint

首先来看下mysql原生案例。

2次开启事务案例

savepoint 案例

上面例子中username=gg被回滚掉了,最终数据库中插入了username=ee,ff,hh。如果最后commit换成rollback所有数据将全部回滚。

laravel/lumen 实现

开启事务时检测当前transactions等级,如果等于1开启事务,如果大于1则创建相应levelsavepoint

commit操作时候,检测当前transactions等级,如果不等于1跳过操作,并且transactions等级减1。如果等于1,事务提交。

rollback操作,检测当前transactions等级,如果不等于1回滚到相应等级的savepoint,等于1这全部回滚。

Functional Programming

  • 函数式编程是什么
  • 函数式编程的特点
  • 函数式编程的意义
  • 函数式编程的几个技术

一、函数式编程是什么?

函数式编程就是编写纯函数,尽可能移除隐含的输入与输出,这样我们的代码就是仅描述输入与输出之间关系的代码。

1、 纯函数

Q: 什么是纯函数?
A: 该函数所有的输入都是显示的(无隐含输入),同样它的所有输出都是显示输出。

让我们来看2个例子:

示例1我们可以清晰的知道输入的是一个int类型,输出的也是一个int类型。对于这种我们称之为纯函数
示例2这个函数既没有输入也没有输出,然而很明显它依赖着什么,并且很显然也在做着什么。但是我们不知道。对于这种我们称之为不纯函数

为什么我们要写纯函数呢?因为不纯函数有很多副作用。详情可参考:
【译】什么是函数式编程?

二、 函数式编程的特点

1、 函数是”第一等公民”

所谓”第一等公民”(first class),指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。

举例来说,下面代码中的$print变量就是一个函数,可以作为另一个函数的参数。

2、 没有”副作用”

所谓”副作用”(side effect),指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。
函数式编程强调没有”副作用”,意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。

3、 不修改状态

上一点已经提到,函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。
在其他类型的语言中,变量往往用来保存”状态”(state)。不修改变量,意味着状态不能保存在变量中。函数式编程使用参数保存状态,最好的例子就是递归。下面的代码是一个将字符串逆序排列的函数,它演示了不同的参数如何决定了运算所处的”状态”。

4、 引用透明

引用透明(Referential transparency),指的是函数的运行不依赖于外部变量或”状态”,只依赖于输入的参数,任何时候只要参数相同,引用函数所得到的返回值总是相同的。
有了前面的第三点和第四点,这点是很显然的。其他类型的语言,函数的返回值往往与系统状态有关,不同的状态之下,返回值是不一样的。这就叫”引用不透明”,很不利于观察和理解程序的行为。

三、 函数式编程的意义

简单的说函数式编程有什么好处,我们为什么要去掌握它。

1、 代码简洁,开发快速

函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。

2、 接近自然语言,易于理解

函数式编程的自由度很高,可以写出很接近自然语言的代码。
用户函数式语言表达(1 + 2) * 3 - 4

对它进行变形,不难得到另一种写法:

3、 更方便的代码管理

函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。

4、易于”并发编程”

函数式编程不需要考虑”死锁”(deadlock),因为它不修改变量,所以根本不存在”锁”线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署”并发编程”(concurrency)。

四、 函数式编程的几个技术

1、 map、 reduce & filte

函数式编程中,我们不应该用循环迭代的方式,我们应该用更为高级的方法。

这样的代码很易读,因为,这样的代码是在描述要干什么,而不是怎么干。

2、 pipeline

详情参考我的另外一篇博客:pipeline

3、 recursing 递归

递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。

通常递归的效率低下,内存占用量比较大,这是它长期不能在业界推广的主要原因。

所以这里推荐使用尾递归。

递归和尾递归有何区别?

以斐波那契数列为例:

初看一下没有看出什么太大区别。但是执行效率却是天差地别。

递归函数在return的时候进行了计算f1($a - 1) + f1($a - 2)。这个时候计算机底层就会创建stack累积而后计算

这个曲线就代表内存占用大小的峰值,从左到右,达到顶峰,再从右到左收缩。

尾递归不会如此耗内存。

他的内存比较平稳。

下面是一个f1f2以及循环测试,展示他们各自的使用时间,可以看出他们之间的巨大差别。

4、 currying 柯里化

写一个数组连接成字符串的函数

现在我要其中所有数字加1, 然后在连接

然后结果所有数字乘以2, 再进行连接

是不是觉得很烦了?
如果需要再添加一个功能,是不是又要添加一个参数了?
是不是违背了函数式编程简洁、接近人类语言的好处了?

这个使用就需要用到柯里化了。

什么是柯里化?

柯里化是这样的一个转换过程,把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,如果其他的参数是必要的,返回接受余下的参数且返回结果的新函数。

这里有人就会说了,柯里化不是到最后变换成接受一个单一参数的函数吗?这里为什么还有2个参数?

其实如果将其转换为没想对象的来看他就是接收了一个参数:

当然还有其他非柯里化方式可以来实现简化如下:

5、 higher order function 高阶函数

所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。

6、 lazy evaluation 惰性求值

柯里化中我们的案例就又到了惰性求值。

7、 determinism 确定性

确定性就是只要输入参数一致,输出结果就是一致的。就像数学那样 f(x) = y ,这个函数无论在什么场景下,都会得到同样的结果。

参考以及转载自:
【译】什么是函数式编程?
阮一峰:函数式编程初探
左耳朵耗子:函数式编程
为什么要柯里化

swoole入门 —— C/S架构

一、什么是swoole

官方定义:特别霸气。
Swoole:重新定义PHP

官方介绍:

PHP语言的异步、并行、高性能网络通信框架,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。

它让PHP能够做出了WEB以外的更多事。

二、安装

swoole是一个php扩展。安装方式与其他扩展一样。

也可以用pecl进行安装: