这一章主要讲三个方面。php
设计模式、依赖管理和PHP的代码整洁。html
是 PHP 用来管理依赖(dependency)关系的工具。你能够在本身的项目中声明所依赖的外部工具库(libraries),Composer 会帮你安装这些依赖的库文件。前端
其实一门语言的将来,是要靠生态来决定的。而生态呢,又须要制定标准。node
在现代编程语言中,几乎都是共享库/包的方式来组合项目的。那么就须要一个权威的、被承认的标准包管理工具来管理这些包。python
世界上最大的包管理工具是npm,上面托管了几十万上百万个包。linux
每一门主流语言都须要包管理工具,JavaScript和node.js有npm,Java有Maven,Dart有pub,python有pip,go就比较混乱了,有好几个,目前最主流的应该是govendor。php的呢,就是composer。git
甚至能够这么理解,在某种程度上,composer是PHP的将来。github
解决了这个场景:你的项目依赖于若干个库,其中一些库依赖于其余库。你声明你的项目所依赖的东西,包管理器会找出哪一个版本的包须要安装,并将它们下载到你的项目中。golang
windows的安装至关简单。下载下来安装包后,一直点击下一步便可。npm
下载地址https://getcomposer.org/download/
安装完成后,使用composer --version
命令来查看是不是否安装成功。出现版本号即表明安装成功。
mac或linux的安装方式参见官方文档:https://docs.phpcomposer.com/00-intro.html
什么是镜像?为何使用镜像?
源服务器离咱们很远,或者咱们国家对相应的域名/地址进行了限制,致使没法链接或者网速很慢。
为了解决这种现象带来的问题。不少大公司都在本身的服务器上面搭建了一个和源服务器一摸同样的环境,再经过CDN和云存储来提供服务给咱们下载,而且每隔一段时间同步一下,好比10分钟,这就是镜像。
Packagist 中国全量镜像豆瓣搭建的pip镜像;阿里搭建的npm镜像cnpm;github的golang镜像;google在中国搭建的dart镜像,它们的目的都是解决以上问题。
Composer的镜像是由Packagist 中国全量镜像搭建的。
用法有两种。
系统全局配置
单项目配置
在命令行终端输入:
composer config -g repo.packagist composer https://packagist.phpcomposer.com
在项目根目录下输入:
composer config repo.packagist composer https://packagist.phpcomposer.com
这条命令会在当前项目的composer.json
文件的末尾自动添加镜像配置信息。由于咱们尚未项目的配置,因此这里暂时先不使用这种方式。
经过如下命令解除镜像,配置会重置为官方源。
$composer config -g --unset repos.packagist
composer的使用方式和npm极为类似,由于composer是受到了npm的启发创造出来的。
经过一个composer.json
文件来声明依赖关系,格式以下:
{ "require": { "monolog/monolog": "1.2.*" } }
composer.json文件所在的路径,就被认为是项目的根路径,若是你用过npm,这很好理解。
经过install命令安装项目依赖。
$ composer install
除了使用 install 命令外,咱们也可使用 require 命令快速的安装一个依赖而不须要手动在 composer.json 里添加依赖信息:
$ composer require monolog/monolog
# 更新全部依赖
$ composer update
# 更新指定的包
$ composer update monolog/monolog
# 更新指定的多个包
$ composer update monolog/monolog symfony/dependency-injection
# 还能够经过通配符匹配包
$ composer update monolog/monolog symfony/*
$ composer remove monolog/monolog
$ composer search monolog
# 列出全部已经安装的包
$ composer show
# 能够经过通配符进行筛选
$ composer show monolog/*
# 显示具体某个包的信息
$ composer show monolog/monolog
composer包的版本与npm是一致的。采用语义化版本控制,格式遵循semver 2.0规范。
版本号的格式由X.Y.Z
组成,X为主版本号,只有更新了不向下兼容的API时进行修改主版本号;Y为次版本号,当模块增长了向下兼容的功能时进行修改;Z为修订版本号,当模块进行了向下兼容的bug修改后进行修改。
版本的安装约束通常有5种。
1.精确锁定版本
1.1.1
2.范围版本
使用连字符 - 来指定版本范围。
有必定的操做符>,>=,<,<=,!=和逻辑操做符||
> = 1.一、>=1.2 <2.0、>=1.2 <2.0 || 3.1.一、1.1.1-2.2.2
3.通配符
1.*
等于 1.0.0 - 2.0.0
4.波浪号
定义最小版本,~1.2
等于>=1.2 <2.0.0
5.脱字符
容许升级版本到安全的版本。
^1.2.3
至关于>=1.2.3 <2.0.0
能够在这个网站搜索你想要的包:https://packagist.org/
wikipedia上给出的答案是:依赖注入是一种容许咱们从硬编码的依赖中解耦出来,从而在运行时或者编译时可以修改的软件设计模式。
看上去不怎么好理解。这里我给出我所理解的什么是依赖注入?的答案:
依赖注入的本质是按需加载,高度解耦。就这么简单。
依赖注入是一种设计模式,遵照依赖倒置原则。
依赖倒置原则主要有三个核心:
高层模块不该该依赖低层模块。两个都应该依赖抽象
抽象不该该依赖细节,细节应该依赖抽象
针对接口编程,不要针对实现编程
由于明天就要进入学习框架的阶段了,框架应该学什么?这个问题很差回答。我我的认为,学框架,得明白什么是框架。其实大多数面向对象语言的服务端框架都是MVC的模式。里面会充斥着各类设计模式。而此类框架设计的重中之重,应该是依赖注入。
其实这部分未必可以在实际中用到,若是你对依赖注入比较了解,或者不感兴趣,你能够选择不看这一部分。
明白了上面的概念,咱们来实际体验一下依赖注入。
假设有以下场景:
小明须要玩王者荣耀,但须要一部手机。
根据这一句话的业务,咱们能够抽象出,人类,手机,游戏。
这个业务的本质是什么?玩王者荣耀。
谁来玩?小明。
前置条件是?须要一部手机。
分析完毕,写出如下代码。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Human { public $name; public function __construct($name) { $this->name = $name; } public function play() { $samsung_s10 = new Phone('三星s10'); $wang_zhe_rong_yao = new Game('王者荣耀'); echo $this->name . '用' . $samsung_s10->getInfo() . '玩' . $wang_zhe_rong_yao->getInfo(); } } $ming = new Human('小明'); $ming->play();
这样就能够实现了业务。可是若是换了一个手机来玩别的游戏该怎么办呢?好比小明此次要使用iPhone xs来玩消消乐。
若是继续使用上面这种方式的话,就须要来动态改变Human类中的内容。此时若是咱们将手机和游戏在Human类的构造中注入进去,就能够比较好的解决这个问题。
思考完毕,修改代码。因为这里Phone和Game类都属于Human类一个行为的依赖,咱们能够将它们放置到另外一个php文件中,并建立一个名命空间 paly。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Human { public $name; public $phone; public $game; public function __construct($name, Phone $phone, Game $game) { $this->name = $name; $this->phone = $phone; $this->game = $game; } public function play() { echo $this->name . '用' . $this->phone->getInfo() . '玩' . $this->game->getInfo(); } } $iphone_xs = new Phone('iPhone xs'); $xiao_xiao_le = new Game('消消乐'); $ming = new Human('小明', $iphone_xs, $xiao_xiao_le); $ming->play();
这样解决了这个问题。
但同时又出现了新的问题,将上面的两个场景合起来, 业务场景就变成了这样:
小明用三星s10玩王者荣耀,接着用苹果xs玩消消乐。
若是继续使用上面这种方式的话,就须要再建立一个Human,传递不一样的参数进去,这样作并不合适。咱们须要有一个方法来注册不一样的手机和游戏,而且同时只须要一个小明的实例。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Human { public $name; public $phone; public $game; public function __construct($name) { $this->name = $name; } public function setPhone($phone) { $this->phone = $phone; } public function setGame($game) { $this->game = $game; } public function play() { echo $this->name . '用' . $this->phone->getInfo() . '玩' . $this->game->getInfo() . '<br/>'; } } $ming = new Human('小明'); $samsung_s10 = new Phone('三星s10'); $wang_zhe_rong_yao = new Game('王者荣耀'); $ming->setPhone($samsung_s10); $ming->setGame($wang_zhe_rong_yao); $ming->play(); $iphone_xs = new Phone('iPhone xs'); $xiao_xiao_le = new Game('消消乐'); $ming->setPhone($iphone_xs); $ming->setGame($xiao_xiao_le); $ming->play();
接下来,咱们的业务需求再继续细化,添加了时间和地点。好比:
小明今天中午在教室先用三星s10玩王者荣耀,下午用苹果xs玩消消乐。
咱们可能会写出这种代码:
// 伪代码。 $ming = new Human('小明'); $samsung_s10 = new Phone('三星s10'); $wang_zhe_rong_yao = new Game('王者荣耀'); $xx_time = new Time('xx'); $xx_location = new Location('xx'); $ming->setPhone($samsung_s10); $ming->setGame($wang_zhe_rong_yao); $ming->setTime($xx_time); $ming->setLoaction($xx_location); $ming->play(); $iphone_xs = new Phone('iPhone xs'); $xiao_xiao_le = new Game('消消乐'); $yy_time = new Time('yy'); $yy_location = new Location('yy'); $ming->setPhone($iphone_xs); $ming->setGame($xiao_xiao_le); $ming->setTime($yy_time); $ming->setLoaction($yy_location); $ming->play();
咱们发现,一旦业务逻辑所依赖的组件过多时,仍然会让程序难以维护。咱们须要消灭掉那些set。
咱们能够用工厂函数来对其进行一层封装。
<?php class Phone { public $phone; public function __construct($phone) { $this->phone = $phone; } public function getInfo() { return $this->phone; } } class Game { public $game_name; public function __construct($game_name) { $this->game_name = $game_name; } public function getInfo() { return $this->game_name; } } class Time { public $time; public function __construct($time) { $this->time = $time; } public function getInfo() { return $this->time; } } class Location { public $location; public function __construct($location) { $this->location = $location; } public function getInfo() { return $this->location; } } class Human { public $name; public $phone; public $game; public $time; public $location; public function __construct($name, $phone, $game, $time, $location) { $this->name = $name; $this->phone = $phone; $this->game = $game; $this->time = $time; $this->location = $location; } public static function factory() { $name = '小明'; $phone = new Phone('iphone xs'); $game = new Game('王者荣耀'); $time = new Time('上午'); $location = new Location('教室'); return new self($name, $phone, $game, $time, $location); } public function play() { echo $this->name . '' . '' . $this->time->getInfo() . '在' . $this->location->getInfo() . '用' . $this->phone->getInfo() . '玩' . $this->game->getInfo() . '<br/>'; } } $ming = Human::factory(); $ming->play();
但这又回到了最初的问题,在类的内部建立依赖。
咱们须要再次认真思考,到底怎么样才能更好的解决这个问题。
咱们能够尝试建立一个高级的注册抽象类,这个抽象类自己也是一个容器。
class DI { private $dep; public function set($key, $val) { array_push($this->dep, $key); $dep[$key] = $val; } public function get($key) { return array_search($this->dep, $key); } }
因为代码量太多,影响阅读,这里就再也不贴出来了。
咱们将Human类的构造函数参数改为di,而后Human类中每个依赖都拆分红一个方法,从di中获取。
这样DI容器类就成了一个桥梁,一个全局注册表,将复杂性隔离出去。代码耦合更低。
若是不须要某一个依赖,甚至能够不初始化。
一个完善的依赖注入框架,还会有其余特性,好比自动绑定、自动解析、注释解析器、延迟注入等。这些就比较复杂了,须要花时间去理解和探索,咱们到此为止,了解依赖注入是个什么东西,解决了什么事就能够了。
我写的示例代码有些重度设计,可是实际业务中的代码不会这么简单,手机内部确定会包含不少属性和方法,如电量、温度、信号等。游戏也会包含一些属性和方法,如加载、更新、版本、高性能模式、FPS、延迟等。但这些都不是重点,我这么作的最终目的是让咱们便于理解。
如今咱们明白了,使用依赖注入解决咱们的问题。不是在代码内部建立依赖关系,而是让其做为一个参数传递,这使得咱们的程序更容易维护,下降程序代码的耦合度,实现一种松耦合。
做为PHP语言自己内容的最后一部分,我认为应该就是学一下代码整洁之道了,代码的整洁程度,直接影响了项目的可读性和可维护性。要知道,除了极少数的极端项目之外,代码的可读性和可维护性是比性能还要重要的部分。
好的代码不止是计器能读懂,还得让人也读懂。
这里简单说一下几个注意点:
名命有意义。
同种类变量使用同一个名字。
使用易于搜索的变量名。
明确的名字比隐晦的名字更好。
不要添加不必的上下文。
参数保持简洁(2个之内),参数超过2个使用对象或者数组。
函数保持简洁,一个函数只作一件事。
函数只进行一层抽象,当超过一层抽象时,继续拆分功能。达到最高的可重用性和易用性。
删除重复代码。
经过对象赋值,设置默认值。
避免函数反作用。这个很重要,可以大大下降程序出现BUG的概率。
避免写全局函数。这样作的好处是防止污染命名空间。
封装条件语句。把判断写成函数。
避免消极条件。就是不要使用!来作判断。
避免条件声明。不要使用if。使用switch和case,一个函数只作一件事。
避免类型检查。不要给PHP代码写类型检测,作不到就是用类型声明。
移除僵尸代码。好比声明后从未调用的函数。
这一部分的内容,不少语言都是互通的,我也不给每一条写例子了,若是看了以上内容后仍有疑惑,你能够打开如下连接进行自学。里面包含了大量的对比示例。
https://github.com/yangweijie/clean-code-php
这一章经过学习依赖管理器Composer,了解了PHP项目是如何构建和组成的。以后学些了依赖注入,并将概念落实到代码上,对理解框架的设计原理会有所帮助。最后经过学习代码整洁之道,避免咱们写出质量不好的代码。
这一章为后续学习框架的使用及理解框架背后的运行原理提供了帮助。