0%

牛b的人是不会废话的,只会默默的砸烂你的人生观,价值观,社会观。
You’ve probably cached some model data in the controller before, but I am going to show you a Laravel model caching technique that’s a little more granular using Active Record models. This is a technique I originally learned about on RailsCasts.

Using a unique cache key on the model, you can cache properties and associations on your models that are automatically updated (and the cache invalidated) when the model (or associated model) is updated. A side benefit is that accessing the cached data is more portable than caching data in the controller, because it’s on the model instead of within a single controller method.

Here’s the gist of the technique:

Let’s say you have an Article model that has many Comment models. Given the following Laravel blade template, you might retrieve the comment count like so on your /article/:id route:

1
<h3>$article->comments->count() {{ str_plural('Comment', $article->comments->count())</h3>

You could cache the comment count in the controller, but the controller can get pretty ugly when you have multiple one-off queries and data you need to cache. Using the controller, accessing the cached data isn’t very portable either.

We can build a template that will only hit the database when the article is updated, and any code that has access to the model can grab the cached value:

1
<h3>$article->cached_comments_count {{ str_plural('Comment', $article->cached_comments_count)</h3>

Using a model accessor, we will cache the comment count based on the last time the article was updated.

So how do we update the article’s updated_at column when a new comment is added or removed?

Enter the touch method.

Touching Models

Using the model’s touch() method, we can update an article’s updated_at column:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ php artisan tinker

>>> $article = \App\Article::first();
=> App\Article {#746
id: 1,
title: "Hello World",
body: "The Body",
created_at: "2018-01-11 05:16:51",
updated_at: "2018-01-11 05:51:07",
}
>>> $article->updated_at->timestamp
=> 1515649867
>>> $article->touch();
=> true
>>> $article->updated_at->timestamp
=> 1515650910

We can use the updated timestamp to invalidate a cache, but how can we touch the article’s updated_at field when we add or remove a comment?

It just so happens that Eloquent models have a property called $touches. Here’s what our comment model might look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace App;

use App\Article;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
protected $guarded = [];

protected $touches = ['article'];

public function article()
{
return $this->belongsTo(Article::class);
}
}

The $touches property is an array containing the association that will get “touched” when a comment is created, saved, or removed.

The Cached Attribute

Let’s go back to the $article->cached_comments_count accessor. The implementation might look like this on the App\Article model:

1
2
3
4
5
6
public function getCachedCommentsCountAttribute()
{
return Cache::remember($this->cacheKey() . ':comments_count', 15, function () {
return $this->comments->count();
});
}

We are caching the model for fifteen minutes using a unique cacheKey() method and simply returning the comment count inside the closure.

Note that we could also use the Cache::rememberForever() method and rely on our caching mechanism’s garbage collection to remove stale keys. I’ve set a timer so that the cache will be hit most of the time, with a fresh cache every fifteen minutes.

The cacheKey() method needs to make the model unique, and invalidate the cache when the model is updated. Here’s my cacheKey implementation:

1
2
3
4
5
6
7
8
9
10
public function cacheKey()
{
return sprintf(
"%s/%s-%s",
$this->getTable(),
$this->getKey(),
$this->updated_at->timestamp
);
}

The example output for the model’s cacheKey() method might return the following string representation:

1
articles/1-1515650910

The key is the name of the table, the model id, and the current updated_at timestamp. Once we touch the model, the timestamp will be updated, and our model cache will be invalidated appropriately.

Here’s the Article model if full:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php

namespace App;

use App\Comment;
use Illuminate\Support\Facades\Cache;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
public function cacheKey()
{
return sprintf(
"%s/%s-%s",
$this->getTable(),
$this->getKey(),
$this->updated_at->timestamp
);
}

public function comments()
{
return $this->hasMany(Comment::class);
}

public function getCachedCommentsCountAttribute()
{
return Cache::remember($this->cacheKey() . ':comments_count', 15, function () {
return $this->comments->count();
});
}
}

And the associated Comment model:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php

namespace App;

use App\Article;
use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
protected $guarded = [];

protected $touches = ['article'];

public function article()
{
return $this->belongsTo(Article::class);
}
}

What’s Next?

I’ve shown you how to cache a simple comment count, but what about caching all the comments?

1
2
3
4
5
6
public function getCachedCommentsAttribute()
{
return Cache::remember($this->cacheKey() . ':comments', 15, function () {
return $this->comments;
});
}

You might also choose to convert the comments to an array instead of serializing the models to only allow simple array access to the data on the frontend:

1
2
3
4
5
6
public function getCachedCommentsAttribute()
{
return Cache::remember($this->cacheKey() . ':comments', 15, function () {
return $this->comments->toArray();
});
}

Lastly, I defined the cacheKey() method on the Article model, but you would want to define this method via a trait called something like ProvidesModelCacheKey that you can use on multiple models or define the method on a base model that all our models extend. You might even want to use a contract (interface) for models that implement a cacheKey() method.

I hope you’ve found this simple technique useful!

https://laravel-news.com/laravel-model-caching

我来过,我战斗过,我不在乎结局。
Laravel中比较优雅的实现了MiddleWare. 主要用来层层递进的处理Request.

Pipeline对象

1
2
3
4
5
(new Pipeline())
->send($passable) // 待处理对象,要经过流水线的对象(比如request对象)
->through($middlewares) // 流水线上的各个环节(中间件)
->via('handle') // 指定每个环节的处理方法,比如 Authenticate::handle
->then($handler); // 尽头,最后的处理(返回时,则是源头)

then()方法

1
2
3
4
5
6
7
8
public function then(Closure $destination)
{
$pipeline = array_reduce(
array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
);

return $pipeline($this->passable);
}

$this->pipes 是我们的中间件数组:

1
2
3
4
5
6
7
8
9
[2018-01-24 08:55:58] local.INFO: array (
0 => 'App\\Http\\Middleware\\EncryptCookies',
1 => 'Illuminate\\Cookie\\Middleware\\AddQueuedCookiesToResponse',
2 => 'Illuminate\\Session\\Middleware\\StartSession',
3 => 'Illuminate\\View\\Middleware\\ShareErrorsFromSession',
4 => 'App\\Http\\Middleware\\VerifyCsrfToken',
5 => 'Illuminate\\Auth\\Middleware\\Authenticate',
6 => 'Illuminate\\Routing\\Middleware\\SubstituteBindings',
)

carry()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
protected function carry()
{
return function ($stack, $pipe) {
return function ($passable) use ($stack, $pipe) {
if ($pipe instanceof Closure) {
// If the pipe is an instance of a Closure, we will just call it directly but
// otherwise we'll resolve the pipes out of the container and call it with
// the appropriate method and arguments, returning the results back out.
return $pipe($passable, $stack);
} elseif (! is_object($pipe)) {
list($name, $parameters) = $this->parsePipeString($pipe);

// If the pipe is a string we will parse the string and resolve the class out
// of the dependency injection container. We can then build a callable and
// execute the pipe function giving in the parameters that are required.
$pipe = $this->getContainer()->make($name);

$parameters = array_merge([$passable, $stack], $parameters);
} else {
// If the pipe is already an object we'll just make a callable and pass it to
// the pipe as-is. There is no need to do any extra parsing and formatting
// since the object we're given was already a fully instantiated object.
$parameters = [$passable, $stack];
}

return $pipe->{$this->method}(...$parameters);
};
};
}

这里使用了array_reduce和闭包, 在闭包中判断, 如果是迭代就执行这个迭代.
$this->method 这个方法是handle, 也是每次我们需要自定义中间件时需要实现的方法.

简化说明

先看下面这段代码

1
2
3
4
5
6
7
8
9
$arr = ['AAAA'];

$res = array_reduce($arr, function($carry, $item){
return function () use ($carry, $item) {
if (is_null($carry)) {
return 'Carry IS NULL' . $item;
}
};
});

此时的数组长度为1,并且没有指定初始值, 故仅仅会迭代一次,返回一个闭包 use($carry = null, $item = ‘AAAA’),当我们执行($res())这个闭包时,得到的结果为Carry IS NULLAAAA

然后我们再看另外一段代码

1
2
3
4
5
6
7
8
9
10
11
12
$arr = ['AAAA', 'BBBB'];

$res = array_reduce($arr, function($carry, $item){
return function () use ($carry, $item) {
if (is_null($carry)) {
return 'Carry IS NULL' . $item;
}
if ($carry instanceof \Closure) {
return $carry() . $item;
}
};
});

我们新增了一个条件判断,若当前迭代的值是一个闭包,返回该闭包的执行结果。
当我们执行这个闭包时,满足$carry instanceof \Closure,得到结果Carry IS NULLAAAABBBB。

https://laravel-china.org/articles/5206/the-use-of-php-built-in-function-array-reduce-in-laravel

没想到,我已经这么老了.
PHP在用户自定义函数中支持可变数量的参数列表。在 `PHP 5.6` 及以上的版本中,由 ... 语法实现;在 PHP 5.5 及更早版本中,使用函数 `func_num_args()`,`func_get_arg()`,和 `func_get_args()` 。

代表全体参数

1
2
3
4
5
function sum(...$numbers) {
dump($numbers);
}

sum(1, 2, 3, 4);

这里的$numbers的结果就是一个数组;

解压一个数组

1
2
3
4
5
6
7
8
function add($a, $b) {
return $a + $b;
}

echo add(...[1, 2])."\n";

$a = [1, 2];
echo add(...$a);

只能用在参数的最后

1
2
3
4
5
6
7
8
9
function sum($header,...$numbers) {
$acc = 0;
foreach ($numbers as $n) {
$acc += $n;
}
return $header.$acc;
}

echo sum('结果是:', 1, 2, 3, 4);

打印结果: 结果是:10

PHP5.6之前的操作

1
2
3
4
5
6
7
8
9
function sum() {
$acc = 0;
foreach (func_get_args() as $n) {
$acc += $n;
}
return $acc;
}

echo sum(1, 2, 3, 4);

说明

在ES6中也有相似的用法…

http://php.net/manual/zh/functions.arguments.php

零落成泥碾作尘,只有香如故。

array_map

返回用户自定义函数作用后的数组
向array_map传入数组,出来的还是数组,而不是上面array_reduce()的一个值。所以,array_map()最简单的就是把 callback函数作用到每个数组的值上,最常见的场景就是 intval()、trim() 数组中的值,在一些框架的源码中也经常见到,比如:

1
2
3
$arr = array('2','3','4','5');
array_map('intval' , $arr);
array_map('htmlspecialchars' , $arr);

array_reduce

使用回调函数迭代地将数组简化为单一的值
使用array_reduce()替代foreach()循环最常用的一个业务场景也许就是数组求和,比如:

使用foreach

1
2
3
$arr = ['2','3','4','5'];
array_map('intval' , $arr);
array_map('htmlspecialchars' , $arr);

使用array_reduce

1
2
3
4
5
6
$nums = [1, 2, 3, 4, 6];
$sum = array_reduce($nums, function ($res, $num) {
return $res + $num;
});

dd($sum);

array_reduce带初始值

1
2
3
4
5
6
$arr = ['AAAA', 'BBBB', 'CCCC'];
$res = array_reduce($arr, function($carry, $item){
return $carry . $item;
}, '所有字符:');
dd($res);
// 所有字符:AAAABBBBCCCC

扩展array_filter

该函数把输入数组中的每个键值传给回调函数。如果回调函数返回 true,则把输入数组中的当前键值返回结果数组中。数组键名保持不变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function odd($var) {
return ($var % 2 == 1);
}

function even($var)
{
return ($var % 2 == 0);
}

$array1 = ["a"=>1, "b"=>2, "c"=>3, "d"=>4, "e"=>5];
$array2 = [6, 7, 8, 9, 10, 11, 12];

echo "Odd : ";
dump(array_filter($array1, "odd"));
echo "Even: ";
dump(array_filter($array2, "even"));

https://blog.tanteng.me/2015/07/array-map-reduce-foreach/

专注、极致、大道至简。

S.O.L.I.D 代表什么

  • S – 单一职责原则(Single responsibility)
  • O – 开放封闭原则(Open Close)
  • L – 里氏替换原则(Liskov Substitution)
  • I – 接口隔离原则(Interface Segregation)
  • D – 依赖倒置原则(Dependence Inversion)

单一职责原则

一个类应该有且只有一个去改变它的理由,这意味着一个类应该只有一项工作。
例如,假设我们有一些shape(形状),并且我们想求所有shape的面积的和。这很简单对吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Circle {
public $radius;

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

class Square {
public $length;

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

首先,我们创建shape类,让构造函数设置需要的参数。接下来,我们继续通过创建AreaCalculator类,然后编写求取所提供的shape面积之和的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class AreaCalculator {

protected $shapes;

public function __construct($shapes = array()) {
$this->shapes = $shapes;
}

public function sum() {
// logic to sum the areas
}

public function output() {
return implode('', array(
"<h1>",
"Sum of the areas of provided shapes: ",
$this->sum(),
"</h1>"
));
}
}

使用AreaCalculator类,我们简单地实例化类,同时传入一个shape数组,并在页面的底部显示输出。

1
2
3
4
5
6
7
8
9
$shapes = array(
new Circle(2),
new Square(5),
new Square(6)
);

$areas = new AreaCalculator($shapes);

echo $areas->output();

输出方法的问题在于,AreaCalculator处理了输出数据的逻辑。因此,如果用户想要以json或其他方式输出数据该怎么办?
所有的逻辑将由AreaCalculator类处理,这是违反单一职责原则(SRP)的;AreaCalculator类应该只对提供的shape进行面积求和,它不应该关心用户是需要json还是HTML。
因此,为了解决这个问题,你可以创建一个SumCalculatorOutputter类,使用这个来处理你所需要的逻辑,即对所提供的shape进行面积求和后如何显示。
SumCalculatorOutputter类按如下方式工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
$shapes = array(
new Circle(2),
new Square(5),
new Square(6)
);

$areas = new AreaCalculator($shapes);
$output = new SumCalculatorOutputter($areas);

echo $output->JSON();
echo $output->HAML();
echo $output->HTML();
echo $output->JADE();

开放封闭原则

对象或实体应该对扩展开放,对修改封闭。
这就意味着一个类应该无需修改类本身但却容易扩展。让我们看看AreaCalculator类,尤其是它的sum方法。

1
2
3
4
5
6
7
8
9
10
11
public function sum() {
foreach($this->shapes as $shape) {
if(is_a($shape, 'Square')) {
$area[] = pow($shape->length, 2);
} else if(is_a($shape, 'Circle')) {
$area[] = pi() * pow($shape->radius, 2);
}
}

return array_sum($area);
}

如果我们希望sum方法能够对更多的shape进行面积求和,我们会添加更多的If / else块,这违背了开放封闭原则。
能让这个sum方法做的更好的一种方式是,将计算每个shape面积的逻辑从sum方法中移出,将它附加到shape类上。

1
2
3
4
5
6
7
8
9
10
11
class Square {
public $length;

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

public function area() {
return pow($this->length, 2);
}
}

对Circle类应该做同样的事情,area方法应该添加。现在,计算任何所提的shape的面积的和的方法应该和如下简单:

1
2
3
4
5
6
7
public function sum() {
foreach($this->shapes as $shape) {
$area[] = $shape->area;
}

return array_sum($area);
}

现在我们可以创建另一个shape类,并在计算和时将其传递进来,这不会破坏我们的代码。然而,现在另一个问题出现了,我们怎么知道传递到AreaCalculator上的对象确实是一个shape,或者这个shape具有一个叫做area的方法?

对接口编程是S.O.L.I.D不可或缺的一部分,一个快速的例子是我们创建一个接口,让每个shape实现它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface ShapeInterface {
public function area();
}

class Circle implements ShapeInterface {
public $radius;

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

public function area() {
return pi() * pow($this->radius, 2);
}
}

在我们AreaCalculator的求和中,我们可以检查所提供的shape确实是ShapeInterface的实例,否则我们抛出一个异常:

1
2
3
4
5
6
7
8
9
10
11
12
public function sum() {
foreach($this->shapes as $shape) {
if(is_a($shape, 'ShapeInterface')) {
$area[] = $shape->area();
continue;
}

throw new AreaCalculatorInvalidShapeException;
}

return array_sum($area);
}

里氏替换原则

每一个子类或派生类应该可以替换它们基类或父类。

接口隔离原则

不应强迫客户端实现一个它用不上的接口,或是说客户端不应该被迫依赖它们不使用的方法。
我们知道也有立体shape,如果我们也想计算shape的体积,我们可以添加另一个合约到ShapeInterface:

1
2
3
4
interface ShapeInterface {
public function area();
public function volume();
}

任何我们创建的shape必须实现volume的方法,但是我们知道正方形是平面形状没有体积,所以这个接口将迫使正方形类实现一个它没有使用的方法。

接口隔离原则(ISP)不允许这样,你可以创建另一个名为SolidShapeInterface的接口,它有一个volume合约,对于立体形状比如立方体等等,可以实现这个接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface ShapeInterface {
public function area();
}

interface SolidShapeInterface {
public function volume();
}

class Cuboid implements ShapeInterface, SolidShapeInterface {
public function area() {
// calculate the surface area of the cuboid
}

public function volume() {
// calculate the volume of the cuboid
}
}

这是一个更好的方法,但小心一个陷阱,当这些接口做类型提示时,不要使用ShapeInterface或SolidShapeInterface。

你可以创建另一个接口,可以是ManageShapeInterface,平面和立体shape都可用,这样你可以很容易地看到它有一个管理shape的单一API。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface ManageShapeInterface {
public function calculate();
}

class Square implements ShapeInterface, ManageShapeInterface {
public function area() { /*Do stuff here*/ }

public function calculate() {
return $this->area();
}
}

class Cuboid implements ShapeInterface, SolidShapeInterface, ManageShapeInterface {
public function area() { /*Do stuff here*/ }
public function volume() { /*Do stuff here*/ }

public function calculate() {
return $this->area() + $this->volume();
}
}

现在AreaCalculator类中,我们可以轻易用calculate替代area调用,同时可以检查一个对象是ManageShapeInterface而不是ShapeInterface的实例。

依赖反转原则

实体必须依靠抽象而不是具体实现。它表示高层次的模块不应该依赖于低层次的模块,它们都应该依赖于抽象(接口)。

1
2
3
4
5
6
7
class PasswordReminder {
private $dbConnection;

public function __construct(MySQLConnection $dbConnection) {
$this->dbConnection = $dbConnection;
}
}

首先MySQLConnection是低层次模块,而PasswordReminder处于高层次,但根据S.O.L.I.D.中D的定义,即依赖抽象而不是具体实现,上面这段代码违反这一原则,PasswordReminder类被迫依赖于MySQLConnection类。

以后如果你改变数据库引擎,你还必须编辑PasswordReminder类,因此违反了开闭原则。

PasswordReminder类不应该关心你的应用程序使用什么数据库,为了解决这个问题我们又一次“对接口编程”,因为高层次和低层次模块应该依赖于抽象,我们可以创建一个接口:

1
2
3
interface DBConnectionInterface {
public function connect();
}

接口有一个connect方法,MySQLConnection类实现该接口,在PasswordReminder类的构造函数不使用MySQLConnection类,而是使用接口替换,不用管你的应用程序使用的是什么类型的数据库,PasswordReminder类可以很容易地连接到数据库,没有任何问题,且不违反OCP。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MySQLConnection implements DBConnectionInterface {
public function connect() {
return "Database connection";
}
}

class PasswordReminder {
private $dbConnection;

public function __construct(DBConnectionInterface $dbConnection) {
$this->dbConnection = $dbConnection;
}
}

根据上面的代码片段,你现在可以看到,高层次和低层次模块依赖于抽象。

http://blog.jobbole.com/86267/
https://juejin.im/entry/587f1c331b69e6005853ecfa

肉食者鄙,未能远谋。

如何让你的代码保持整洁

想要让你的代码整洁优雅,仅仅读完 uncle Bob 的《代码整洁之道》是不够的,还需要知识和持续的练习,你必须要学习原理,模式以及实践。也许这需要长时间的努力工作,但不妨你现在就开始行动。
无论你现在的代码多么的整洁,然而总有一些东西能你的代码变得更加整洁,这需要你去学习。
通过读专家的书或者帖子来学习,是一个非常有效的途径。你可以关注他们的 twitter,听他们的演讲,关注他们的 GitHub,学习他们写代码的方式以及结构。
除非你向你所在领域的专家不断学习,否则,你可能一直停留在工程师的级别。

保持你的函数短小精悍

整洁的代码不仅仅是编写简短的方法,编写的代码要清晰地传达意图。
当一个函数太长时,很可能表明做的太多了,读者会阅读中迷路。函数应该只做一件事。

1
2
3
4
5
6
7
8
9
10
if($order->contains($status){
//do something with order
}
function contains($status){
$order_statuses=['accepted','delivered','rejected','processed'];
if (in_array($status, $order_statuses)) {
return true;
}
return false;
}

我们可以通过重写函数,使它’contains’整洁为:

1
2
3
function contains($status){
return in_array($status, $this->config->statuses);
}

变量或函数的名字应该能够一眼看出它的作用

选择一个合适的函数名字有的时候可能很乏味,但是毫无疑问的是这值得你这样做!因为这使你在代码变更的时候不需要更新代码的注释!

1
2
$date =date('Y-m-d'); //Ofcourse, it's a date but too generic!
$orderCreationDate =date('Y-m-d'); //cleaner code

避免 if 和 switch 语句

个人认为,它(if 和 switch 语句)会让我花一点时间去理解。“你告诉我,我该怎么避免使用我最爱的语句之一——if & else?”事实证明,大部分条件语句可以简单的被分解成函数或类。这并不是说不让你使用 if 和 switch 语句,只是想说看情况使用!
这里有一个很好的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class BookOrder
{

public function process()
{
switch ($this->book->type) {
case 'audiocopy':
return $this->processAudioBook();
break;
case 'downloadablecopy':
return $this->processDownloadableCopy();
break;
case 'paperbookcopy':
return $this->processPaperBookCopy();
break;
default:

}
}

}

一个干净且更易于维护的书写方式应该是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
interface  IBookOrder {

public function process();
}
class AudioBookOrder implements IBookOrder :void {

public function process()
{
// TODO: Implement process() method.
}
}
class PaperBookOrder implements IBookOrder: void {

public function process()
{
// TODO: Implement process() method.
}
}
class DownloadableBookOrder implements IBookOrder: void {

public function process()
{
// TODO: Implement process() method.
}
}

避免心理地图式命名

干净的代码应该易于阅读,理解,不应该有猜测的余地。
下面的代码检查客户是否可以提取一定数量的钱。这很有效,但很混乱。

1
2
3
if($this->user->balance  > $amount && $this->user->activeLoan===0){
$this->user->balance -=$amount; // withdraw amount;
}

让我们把它弄的整洁一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if($this->hasNoActiveLoan() && $this->canWithdrawAmount($amount)){
$this->withdraw($amount);
}

public function hasNoActiveLoan(){
return $this->user->activeLoan===0;
}
public function canWithdrawAmount(float $amount){
return $this->user->balance > $amount;
}
public function withdraw(float $amount){
$this->user->balance -=$amount;

}

https://nikic.github.io/2011/12/27/Dont-be-STUPID-GRASP-SOLID.html
https://laravel-china.org/topics/7468/please-keep-your-php-code-neat-and-tidy

没人能嘲笑你的梦想,他们都是在嘲笑你的实力
若将商业逻辑都写在 controller,会造成 controller 肥大而难以维护,基于SOLID原则,我们应该使用 Service 模式辅助 controller,将相关的商业逻辑封装在不同的 service,方便中大型程序的维护。

问题的提出

商业逻辑中,常见的如 :

  • 牵涉到外部行为 : 如发送Email,使用外部API。
  • 使用PHP写的逻辑 : 如根据购买的件数,有不同的折扣。

若将商业逻辑写在 controller,会造成 controller 肥大,日后难以维护。

如发送Email,初学者常会在 controller 直接调用 Mail::queue():

1
2
3
4
5
6
7
8
public function store(Request $request)
{
Mail::queue('email.index', $request->all(), function (Message $message) {
$message->sender(env('MAIL_USERNAME'));
$message->subject(env('MAIL_SUBJECT'));
$message->to(env('MAIL_TO_ADDR'));
});
}

在中大型程序中,会有几个问题 :

  • 将牵涉到外部行为的商业逻辑写在 controller,造成 controller 的肥大难以维护。
  • 违反 SOLID 的单一职责原则 : 外部行为不应该写在 controller。
  • controller 直接相依于外部行为,使得我们无法对 controller 做单元测试。

比较好的方式是使用 service :

  • 将外部行为注入到 service。
  • 在 service 使用外部行为。
  • 将 service 注入到 controller。

问题的解决

EmailService.php

app/Services/EmailService.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
namespace App\Services;

use Illuminate\Mail\Mailer;
use Illuminate\Mail\Message;

class EmailService
{
/** @var Mailer */
private $mail;

/**
* EmailService constructor.
* @param Mailer $mail
*/
public function __construct(Mailer $mail)
{
$this->mail = $mail;
}

/**
* 发送Email
* @param array $request
*/
public function send(array $request)
{
$this->mail->queue('email.index', $request, function (Message $message) {
$message->sender(env('MAIL_USERNAME'));
$message->subject(env('MAIL_SUBJECT'));
$message->to(env('MAIL_TO_ADDR'));
});
}
}

将相依的Mailer注入到EmailService。

1
2
3
4
5
6
7
8
9
10
11
/** @var Mailer */
private $mail;

/**
* EmailService constructor.
* @param Mailer $mail
*/
public function __construct(Mailer $mail)
{
$this->mail = $mail;
}

将发送 Emai的商业逻辑写在send()。不是使用Mail facade,而是使用注入的$this->mail。

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 發送Email
*
* @param array $request
*/
public function send(array $request)
{
$this->mail->queue('email.index', $request, function (Message $message) {
$message->sender(env('MAIL_USERNAME'));
$message->subject(env('MAIL_SUBJECT'));
$message->to(env('MAIL_TO_ADDR'));
});
}

UserController.php

app/Http/Controllers/UserController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
namespace App\Http\Controllers;

use App\Http\Requests;
use Illuminate\Http\Request;
use MyBlog\Services\EmailService;

class UserController extends Controller
{
/** @var EmailService */
protected $emailService;

/**
* UserController constructor.
* @param EmailService $emailService
*/
public function __construct(EmailService $emailService)
{
$this->emailService = $emailService;
}

/**
* Store a newly created resource in storage.
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->emailService->send($request->all());
}
}

将相依的 EmailService 注入到 UserController

1
2
3
4
5
6
7
8
9
10
11
/** @var  EmailService */
protected $emailService;

/**
* UserController constructor.
* @param EmailService $emailService
*/
public function __construct(EmailService $emailService)
{
$this->emailService = $emailService;
}

从原本直接相依于 Mail facade,改成相依于注入的 EmailService

1
2
3
4
5
6
7
8
9
10
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$this->emailService->send($request->all());
}

实际的问题

问题提出

如根据购买的件数,有不同的折扣,初学者常会在 controller 直接写 if…else 逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public function store(Request $request)
{
$qty = $request->input('qty');

$price = 500;

if ($qty == 1) {
$discount = 1.0;
}
elseif ($qty == 2) {
$discount = 0.9;
}
elseif ($qty == 3) {
$discount = 0.8;
}
else {
$discount = 0.7;
}

$total = $price * $qty * $discount;

echo($total);
}

在中大型程序中,会有几个问题 :

  • 将 PHP 写的商业逻辑直接写在 controller,造成 controller 的肥大难以维护。
  • 违反 SOLID的 单一职责原则 : 商业逻辑不应该写在 controller。
  • 违反 SOLID的 单一职责原则 : 若未来想要改变折扣与加总的算法,都需要改到此 method,也就是说,此 method 同时包含了计算折扣与计算加总的职责,因此违反 SOLID 的单一职责原则。
  • 直接写在 controller 的逻辑无法被其他 controller 使用。

比较好的方式是使用 service:

  • 将相依物件注入到 service。
  • 在 service 写 PHP逻辑使用相依物件。
  • 将 service 注入到 controller。

OrderService.php

app/Services/OrderService.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace App\Services;

class OrderService
{
/**
* 計算折扣
* @param int $qty
* @return float
*/
public function getDiscount($qty)
{
if ($qty == 1) {
return 1.0;
} elseif ($qty == 2) {
return 0.9;
} elseif ($qty == 3) {
return 0.8;
} else {
return 0.7;
}
}

/**
* 計算最後價錢
* @param integer $qty
* @param float $discount
* @return float
*/
public function getTotal($qty, $discount)
{
return 500 * $qty * $discount;
}
}

OrderController.php

app/Http/Controllers/OrderController.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
namespace App\Http\Controllers;

use App\Http\Requests;
use App\MyBlog\Services\OrderService;
use Illuminate\Http\Request;

class OrderController extends Controller
{
/** @var OrderService */
protected $orderService;

/**
* OrderController constructor.
* @param OrderService $orderService
*/
public function __construct(OrderService $orderService)
{
$this->orderService = $orderService;
}

/**
* Store a newly created resource in storage.
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
$qty = $request->input('qty');

$discount = $this->orderService->getDiscount($qty);
$total = $this->orderService->getTotal($qty, $discount);

echo($total);
}
}

详细链接

  1. 如何使用Repository模式
  2. 如何使用Presenter模式
  3. 如何使用Service模式
  4. laravel的中大型架构

http://oomusou.io/laravel/laravel-service/
http://blog.iwanli.me/article/B8ApLAab.html

那一切都是种子,只有经过埋葬,才有生机。

简介

Redis是一种基于客户端-服务端模型以及请求/响应协议的TCP服务。这意味着通常情况下一个请求会遵循以下步骤:

  • 客户端向服务端发送一个查询请求,并监听Socket返回,通常是以阻塞模式,等待服务端响应。
  • 服务端处理命令,并将结果返回给客户端。

Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。从而节省网络传输时间, 这在一次需要执行大量命令时特别有用;

使用

phpredis 支持操作数组,同时操作多个 key,操作 redis 时,

1
$obj->get([]), $obj->del([])

而 predis 不支持,可以通过 pipeline 来执行

1
2
3
4
5
Redis::pipeline(function ($pipe) {
for ($i = 0; $i < 1000; $i++) {
$pipe->set("key:$i", $i);
}
});

其他

关于phpredis和predis的区别, 请参考:
Predis和Phpredis的区别

少年易老学难成,一寸光阴不可轻

定义

如果函数的最后一个命名参数以...为前缀,则它将成为一个数组,其中从0(包括)到theArgs.length(排除)的元素由传递给函数的实际参数提供。

剩余参数和 arguments对象的区别

  • 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
  • arguments对象不是一个真正的数组,而剩余参数是真正的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach或pop。
  • arguments对象还有一些附加的属性 (如callee属性)。

一般的求和函数

1
2
3
4
5
function sum() {
return Array.from(arguments).reduce((sum, no) => sum + no, 0);
}

console.log( sum(1,3,4));

使用剩余参数改写

1
2
3
4
5
function sum(...numbers) {
return numbers.reduce((prev, surr) => prev + surr, 0);
}

console.log( sum(1,3,4));

其他用法

1
2
3
4
5
6
const palyer = ['yangzie', 'male', 12, 13, 14, 15];
const [name, sex, ...scores] = palyer;

console.log(name);
console.log(sex);
console.log(scores);
1
2
3
4
5
function rate(rate, ...numbers) {
console.log(rate, numbers);
}

rate(0.98, 10,20,30);

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters

你不需要很厉害才能开始,但你需要开始才会很厉害。

问题原因

安装使用package control一直出现There are no packages available for installation,打开sublime text 的控制台,发现一直有以下错误:

1
2
Package Control: Error downloading channel. URL error [Errno 65] 
No route to host downloading https://packagecontrol.io/channel_v3.json.

应该GWF墙的就是这个文件,导致后续的安装失败,网上查了找到的方法就是改变安装channel_v3.json的配置文件。

解决办法

首先打开用户自定义配置文件win7

1
Preferences > Package Settings > Package Control > Settings - User

或者win10

1
Preferences > Settings

然后在最下方新增以下配置

1
2
3
4
"channels":
[
"http://cst.stu.126.net/u/json/cms/channel_v3.json"
],

问题既可得到解决;