四、依赖项管理
依赖关系管理是一个系统,用于轻松管理(安装、使用、更新、卸载)项目中的库依赖关系。声明中的关键词是容易。很长一段时间,PHP 中的依赖管理实际上是不存在的。
当然,PEAR 从 1999 年就已经存在了,但是它不适合在应用中提供简单的依赖管理。它用于服务器范围内的全局安装包(想想apt-get
或yum
),任何使用过 PEAR 的 XML 结构来创建包的人都可以证明它缺乏易用性。这就是 Composer 及其配套 Packagist 发挥作用的地方。
作曲家和包装家
Composer 是一个命令行工具,它是为 PHP 应用中的简单依赖管理而创建的。您用一个简单的 JSON 文件格式定义应用的依赖项,Composer 将为您安装和更新这些包。它的灵感来自 npm 和 Bundler,它们分别是 Node.js 和 Ruby 的包管理器,并借鉴了它们的许多特性和概念。
安装作曲者
安装 Composer 有两种不同的方式。您可以将它本地安装到您的项目中,或者作为系统范围的可执行文件全局安装。如果你只是第一次下载它或者没有权限在系统范围内安装它,那么本地安装就可以了。但是,最好的方法是全局安装它,这样您就有了一个安装的 Composer 版本,并且可以用于同一服务器上的所有项目,而不必维护不同的安装和版本。
在本地
要在本地安装 Composer,只需在项目目录中运行以下命令:
$ curl -sS https://getcomposer.org/installer | php
一旦安装程序运行,它会将composer.phar
二进制文件下载到您的项目中。然后,您可以通过运行php composer.phar
来执行它。
世界上
要将 Composer 作为系统范围的可执行文件安装,首先要下载composer.phar
,然后将它移动到您在类 Unix 系统(Linux/OS X 等)上的路径中的某个位置。):
$ curl -sS https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
您现在可以通过在命令行上键入composer
来执行 Composer。
Note
如果您使用的是 Windows 系统,那么您可以通过访问getcomposer.org
下载 Windows Composer 安装程序,将其全局安装到您的系统上。
包装设计师
Packagist 是默认的 Composer 存储库。它是 PHP 软件包的公共集合,可以通过 Composer 安装。您可以通过访问packagist.org
来访问 Packagist,在这里您可以轻松地搜索可用的软件包。通过引用版本和包名,Composer 知道从哪里下载您在项目中指定的代码。截至撰写本文时,它包含了超过 67,000 个注册包,近 314,000 个版本,并且自 2012 年 4 月以来已经安装了超过 10 亿个包!
除了作为开发者寻找他们希望安装的包的信息的可搜索资源之外,包作者可以很容易地将他们的项目提交给 Packagist,这样其他人也可以将 Composer 用于该项目。
一旦您的软件包成功注册到 Packagist,您就可以在您的 Bitbucket 或 GitHub 帐户中启用服务挂钩,并在您推送至远程存储库时立即更新您的软件包。
使用作曲家
在项目中开始使用 Composer 的唯一要求是安装 Composer 并创建项目的composer.json
文件。该文件列出了您的项目依赖项,以及其他可能的元数据。
composer.json 文件
在最基本的形式中,唯一需要的是使用require
键。对于我们的例子,我们将安装 PHPUnit 框架。我们将像这样创建项目的composer.json
文件:
{
"require": {
"phpunit/phpunit": "4.8.4"
}
}
现在我们将使用 Composer 安装这个框架,如下所示:
$ composer install
运行该命令后,我们将看到 Composer 的输出,它下载并安装 PHPUnit 所需的所有依赖项,如下所示:
$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
- Installing sebastian/version (1.0.6)
Loading from cache
- Installing sebastian/global-state (1.0.0)
Loading from cache
- Installing sebastian/recursion-context (1.0.1)
Downloading: 100%
Composer 完成安装后,您会看到它生成了一个锁文件和自动加载文件,这些文件是将 PHPUnit 自动加载到您的应用中所需要的。您还将拥有一个名为vendor
的新文件夹,其中包含 Composer 刚刚安装的所有软件包。
安装附加软件包
现在,当您需要将额外的包安装到您的应用中时,您只需将新需求的条目添加到您的composer.json
文件中,并再次运行composer install
。或者,您可以使用composer require
命令,它将把条目添加到您的composer.json
文件中,并在一个命令中安装它。例如,我们的应用需求之一可能是发送电子邮件,我们希望使用流行的 SwiftMailer 库。为此,我们首先在packagist.com
上查找 SwiftMailer 并找到包名,然后运行以下命令:
$ composer require swiftmailer/swiftmailer
Using version ⁵.4 for swiftmailer/swiftmailer
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing swiftmailer/swiftmailer (v5.4.1)
Downloading: 100%
Writing lock file
Generating autoload files
从输出中,我们可以看到它更新了我们的composer.json
文件,安装了 SwiftMailer 库包,更新了锁文件,并生成了新的自动加载文件。如果您查看composer.json
文件,您现在会看到它被更新以反映 SwiftMailer:
{
"require": {
"phpunit/phpunit": "4.8.4",
"swiftmailer/swiftmailer": "⁵.4"
}
}
对于文件composer.json
中的新条目,您可能注意到的一件事是 SwiftMailer —
的版本号,它包括脱字符(^)操作符。我们将在几个部分中更深入地研究 Composer 版本,但现在这相当于告诉未来的composer update
命令,我们总是想要最新的稳定版本,即>=5.4 < 6.0
。如果我们想更具体地指定一个版本,就像 PHPUnit 例子中那样,那么我们可以有选择地将一个版本传递给composer require
命令,就像这样:
$ composer require swiftmailer/swiftmailer 5.4
移除包
Composer 使向您的应用添加新库变得容易,并且它使删除不再需要的包变得同样容易。有两种不同的方法可以完成—
,要么从你的composer.json
文件中手动删除包声明并运行composer update
,要么利用composer
remove
命令。
如果我们决定删除之前安装的 PHPUnit 库,我们可以用这个命令来完成:
$ composer remove phpunit/phpunit
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Removing phpunit/phpunit (4.8.4)
Writing lock file
Generating autoload files
需求与需求开发
Composer 提供了两种不同的请求包的方法。第一种方法是使用require
声明,正如前面所有的例子所示。第二种是通过使用require-dev
声明。您应该总是使用require
,除非某个包只需要用于您的应用的开发,而不需要用于在生产中运行应用。一个主要的例子是单元测试库,比如 PHPUnit。例如,现在让我们将 PHPUnit 重新添加到我们的应用中,但是这次指定它只在使用require-dev
的开发中需要:
$ composer require phpunit/phpunit --dev
Using version ⁴.8 for phpunit/phpunit
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
- Installing phpunit/phpunit (4.8.8)
Downloading: 100%
phpunit/phpunit suggests installing phpunit/php-invoker (∼1.1)
Writing lock file
Generating autoload files
现在,如果您查看composer.json
文件,您会看到phpunit
已经被添加回来,但是这次是在require-dev
声明下:
{
"require": {
"swiftmailer/swiftmailer": "⁵.4"
},
"require-dev": {
"phpunit/phpunit": "⁴.8"
}
}
编写器锁定文件
Composer 锁定文件是 Composer 已安装到您的项目中的确切版本的列表。这会将您的项目锁定在这些特定的版本中。将这个锁文件和composer.json
文件一起添加到您的 Git 存储库中是很重要的,这样任何从事您的项目的人都将使用相同的包版本。这一点也很重要,因为如果您在您的试运行或生产环境中使用任何类型的部署方案,您可以利用composer install
来确保这些环境已经安装了与您开发应用所针对的完全相同的软件包版本。这也确保了将来对这些库的更新可以在您的应用开发中完成,提交到您的存储库中,并准确地分发到这些其他环境中。这也消除了存储 Composer 已经安装在源代码存储库中的所有各种包的需要。
Note
因为您现在使用两个 Composer 文件来跟踪依赖文件和版本,所以没有必要将vendor
文件夹提交并维护到您的 Git 存储库中。您应该将vendor
目录添加到您的.gitignore
文件中。
半自动的
当使用 Composer 时,它会生成一个自动加载文件vendor/autoload.php
,用于自动加载您用 Composer 安装的所有库和包。只需将这个自动加载文件包含在您的应用中(确保到vendor/autoload.php
文件的正确路径),所有已安装的包都将对您可用。
require __DIR__ . '/../vendor/autoload.php';
现在,如果我们想使用我们之前安装的 SwiftMailer 库,我们可以简单地调用它:
// Create the SwiftMailer Transport
$transport = Swift_MailTransport::newInstance();
// Create a Mailer instance with our Transport
$mailer = Swift_Mailer::newInstance($transport);
// Create our message
$message = Swift_Message::newInstance('Learning Composer')
->setFrom(array('john@doemain.tld' => 'John Doe'))
->setTo(array('jane@doemain.tld' => 'Jane Doe'))
->setBody('Composer is wonderful!');
// Send our message!
$result = $mailer->send($message);
附加自动装载
除了自动加载所有已安装的 Composer 库包之外,您还可以将 Composer 自动加载器用于您自己的应用代码。为此,使用composer.json
文件中的“自动加载”字段。
例如,如果您将自己的应用代码存储在一个名为src
的文件夹中,您可以将以下条目添加到您的composer.json
文件中:
{
"autoload": {
"psr-4": { "MyApplication\\": "src/" }
},
"require": {
"swiftmailer/swiftmailer": "⁵.4"
}
}
这告诉 Composer 为“MyApplication”名称空间注册一个 PSR-4(PHP-FIG 自动加载标准)自动加载器。现在,要让 Composer 更新vendor/autoload.php
,您需要运行dump-autoload
命令:
$ composer dump-autoload
Generating autoload files
除了 PSR-4 自动加载,Composer 还支持 PSR-0 自动加载、类别映射生成和文件包含作为有效的自动加载方法。然而,PSR-4 是使用 Composer 自动加载的推荐方法,因为它易于使用。
自动装载机优化
虽然开发环境不需要,但是强烈建议在生成用于生产的 Composer 自动加载程序时,使用内置的自动加载程序优化器。您的应用性能提升 30%并不罕见,尤其是当您的应用在 Composer 自动加载文件上花费大量时间时。
有两种不同的方法来生成优化的自动加载器。第一种是使用带有-o
参数的dump-autoload
命令:
$ composer dump-autoload -o
Generating optimized autoload files
例如,可以将此命令设置为在试运行和生产环境部署中运行,以便在开发中使用标准自动加载程序,而在测试和生产中使用优化版本。
除了通过dump-autoload
命令生成优化的自动加载程序之外,您还可以在您的composer.json
文件中指定它,以便您总是生成优化的版本。这是通过使用config
指令完成的:
{
"autoload": {
"psr-4": {
"MyApplication\\": "src/"
}
},
"require": {
"swiftmailer/swiftmailer": "⁵.4"
},
"require-dev": {
"phpunit/phpunit": "⁴.8"
},
"config": {
"optimize-autoloader": true
}
}
包版本
Composer 在定义应用中安装的给定软件包的版本时提供了很大的灵活性。本质上,你可以将定义分为三类:基本约束、下一个有意义的发布和稳定性。
基本限制
确切的
使用基本约束,您可以告诉 Composer 通过只指定数字来安装一个精确的版本,比如1.2.4
。这将确保您的应用总是使用这个确切的版本,不管composer update
运行了多少次。
"require": {
"vendor/packagea": "1.5.4"
}
范围
Composer 允许使用比较运算符来指定应用的有效版本范围。有效的运算符是>、> =、AND 和OR
逻辑。用空格或逗号分隔范围用于表示AND
,用双管||表示OR
。以下是一些有效的例子:
"require": {
"vendor/packagea": ">1.5",
"vendor/packageb": ">2.0 <3.0",
"vendor/packagec": ">2.0,<3.0",
"vendor/packaged": ">1.0 <1.5 || >= 1.7"
}
通配符
除了特定的版本和范围之外,还可以通过在版本声明中使用通配符来指定版本号模式。例如,如果我们想要一个包的 4.2 分支的任何子版本,它将被指定为:
"require": {
"vendor/packagea": "4.2.*"
}
范围连字符
另一种指定范围的方法是使用连字符。使用连字符表示法时,连字符右侧的部分版本号被视为通配符。因此,考虑下面的例子:
"require": {
"vendor/packagea": "1.5 – 2.0",
"vendor/packageb": "2.0 – 2.1.0"
}
本例中的packagea
相当于>=1.5 <2.1
。由于右侧的版本号被视为通配符,Composer 将其视为2.0.*
。
本例中的packageb
相当于>=2.0 <=2.1.
0
。
下一个重要版本
Composer 中有两个不同的操作符可以用来定义版本限制,直到给定软件包的下一个重要发行版。
波浪号
使用波浪号操作符∼
您可以定义一个您希望应用使用的最低版本标记,同时保护您不必更新到软件包的下一个重要版本(例如,下一个 x.0 版本)。考虑以下示例:
"require": {
"vendor/packagea": "∼2.5"
}
该声明与指定>= 2.5 but <3.0
相同。您也可以通过将您的需求定义为以下内容,在子版本级别对此进行定义:
"require": {
"vendor/packagea": "∼2.5.2"
}
该声明与指定>= 2.5.2 but < 2.6.0
相同。
脱字号
脱字符操作符^
的工作方式与波浪号操作符非常相似,只是略有不同。它应该总是通过坚持更接近语义版本来允许不间断的更新。考虑以下示例:
"require": {
"vendor/packagea": "².5.2"
}
该声明与指定>= 2.5.2 but <3.0
相同。如您所见,这与波浪号略有不同,波浪号会阻止它更新到 2.6.0。最后,关于小于 1.0 的包,插入符号提供了一点额外的安全性,不允许如此大范围的版本更新:
"require": {
"vendor/packagea": "∼0.5"
}
该声明与指定>= 0.5.0 but <0.6.0
相同。
稳定性
当试图理解 Composer 将要安装的软件包的稳定性时,Composer 文档会变得相当混乱。阅读 Composer 文档的“版本”部分会让您认为 Composer 可能会仅仅根据您在指定版本时使用的约束来随机选择一个包的开发版本。
虽然这在技术上是正确的,但只有当您在composer.json
文件中指定最小稳定性为dev
时,这才适用。默认情况下,Composer 将始终选择稳定包,除非您使用require
部分下的-dev
后缀明确告诉它,或者您已经将最小稳定性配置定义为dev
。
更新包
到目前为止,我们已经介绍了如何使用 Composer 安装和删除软件包,以及如何指定您的应用所依赖的软件包的版本和稳定性。您将使用 Composer 进行的最后一个主要操作是更新您现有的库。这是使用composer
update
命令执行的:
$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
Nothing to install or update
Generating autoload files
默认情况下,运行composer update
时,会执行一些动作。首先,如果您对您的composer.json
文件做了任何手动更改来添加或删除一个包,它将会处理这些并安装或删除给定的包。此外,如果您的任何软件包版本没有锁定到一个确切的版本,它将寻找任何更新,并根据您的版本规范安装它们。最后,它将重新生成自动加载文件和锁定文件,并完成其操作。
有许多选项可以传递给composer update
。例如,通过传递--dry-run
,您可以看到 Composer 在实际执行之前会做什么。你可以选择通过--no-dev
,这将导致它跳过更新或安装任何在require-dev
声明下定义的包。您也可以定义您希望它更新的特定包,而不更新您的composer.json
文件中定义的所有包。您可以通过向它传递一个或多个包来实现这一点,例如:
$ composer update swiftmailer/swiftmailer guzzlehttp/guzzle
全局安装软件包
Composer 可以用于全局管理和安装包,类似于 PEAR。这对于全局安装某些实用程序,甚至对于维护 Composer 本身的全局安装更新都很有用。
例如,如果我们想要更新 Composer 的全局版本,我们将运行以下命令:
$ sudo composer self-update
如果我们想安装一个像 PHPUnit 这样的实用程序,我们可以使用这样的命令:
$ composer global require phpunit/phpunit
Changed current directory to /home/vagrant/.config/composer
You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug
Using version ⁵.2 for phpunit/phpunit
./composer.json has been updated
Loading composer repositories with package information
Updating dependencies (including require-dev)
...
Writing lock file
Generating autoload files
执行该命令时,请注意紧随其后的一行:Changed current directory to
。这告诉您它将在/home/vagrant/.config/composer/vendor/
下安装 PHPUnit 及其依赖项。我们当前登录的用户是“流浪者”这就是它选择这个目录的原因。为了便于执行,您需要将该目录添加到全局路径中。如果您的主目录中有一个.bashrc
或.bash_profile
文件,请相应地调整以下命令。在我的例子中,我有一个.bashrc
文件,所以我将使用它:
$ cd ∼/
$ echo 'export PATH="$PATH:$HOME/.config/composer/vendor/bin"' >> ∼/.bashrc
现在,通过注销并重新登录或使用source
命令,重新加载以获取路径更改:
$ source .bashrc
现在可以执行 phpunit 了:
$ phpunit --version
PHPUnit 5.2.9 by Sebastian Bergmann and contributors.
梨和梨
正如在这一章的介绍中提到的,PEAR(PHP 扩展和应用库)曾经是唯一一个试图创建一个分布式系统的方法,用来在你的 PHP 应用中提供库。然而,PEAR 在许多不同的领域都有不足,这为创建更好的依赖管理工具铺平了道路,比如 Composer。
PEAR 确实取得了一些巨大的成功,过去是,现在仍然被许多不同的库和包使用,并且仍然是 PECL 用来安装 PHP 扩展的系统。过去几年 PEAR2 和 Pyrus 的创建旨在解决 PEAR 的一些缺点,但是它们没有看到 Composer 一直享有的牵引力和广泛的社区采用和开发。因此,在撰写本文时,PEAR2 和 Pyrus 已经处于 alpha 状态超过四年了。
还有人用梨吗?
这个问题的答案—
在我看来,在其他开发者看来,并且基于 Pear 下载统计页面—
上可用的当前下载统计数据,既有是也有否。Pear 的 PHP7 兼容版本自 2015 年 10 月首次发布以来,截至本文撰写之时,已有超过 650,000 次下载。有无数的旧 PHP 应用仍然依赖于各种 Pear 包,因此它仍然在使用这些包。我相信,基于我们的日常开发,以及 Packagist 上可用库数量的不断增长和大量开源平台转向使用和支持 Composer (Zend Framework 2、Symfony Framework、Drupal 8、Magento 2 等)。),Pear 作为库管理器和在应用中安装依赖项的使用正在迅速减少。
耦合逻辑
尽管总体上 Pear 的使用在减少,PHP 扩展社区库,更广为人知的名字是 PECL,今天仍然非常活跃。它是 PHP 扩展的公共存储库,通常用于安装开发所需的库。PECL 使用 Pear 来安装它的库,这在查看pecl
命令的源代码时是显而易见的:
#!/bin/sh
# first find which PHP binary to use
if test "x$PHP_PEAR_PHP_BIN" != "x"; then
PHP="$PHP_PEAR_PHP_BIN"
else
if test "/usr/bin/php" = '@'php_bin'@'; then
PHP=php
else
PHP="/usr/bin/php"
fi
fi
# then look for the right pear include dir
if test "x$PHP_PEAR_INSTALL_DIR" != "x"; then
INCDIR=$PHP_PEAR_INSTALL_DIR
INCARG="-d include_path=$PHP_PEAR_INSTALL_DIR"
else
if test "/usr/share/php" = '@'php_dir'@'; then
INCDIR=`dirname $0`
INCARG=""
else
INCDIR="/usr/share/php"
INCARG="-d include_path=/usr/share/php"
fi
fi
exec $PHP -C -n -q $INCARG -d date.timezone=UTC -d output_buffering=1 -d variables_order=EGPCS -d safe_mode=0 -d register_argc_argv="On" $INCDIR/peclcmd.php "$@"
现在,让我们看看在pecl
命令的最后一行中引用的peclcmd.php
的源代码:
<?php
/**
* PEAR, the PHP Extension and Application Repository
*
* Command line interface
*
* PHP versions 4 and 5
*
* @category pear
* @package PEAR
* @author Stig Bakken <ssb@php.net>
* @author Tomas V.V.Cox <cox@idecnet.com>
* @copyright 1997-2009 The Authors
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @link http://pear.php.net/package/PEAR
*/
/**
* @nodep Gtk
*/
//the space is needed for Windows include paths with trailing backslash
// http://pear.php.net/bugs/bug.php?id=19482
if ('/usr/share/php ' != '@'.'include_path'.'@ ') {
ini_set('include_path', trim('/usr/share/php '). PATH_SEPARATOR .get_include_path());
$raw = false;
} else {
// this is a raw, uninstalled pear, either a cvs checkout or php distro
$raw = true;
}
define('PEAR_RUNTYPE', 'pecl');
require_once 'pearcmd.php';
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* indent-tabs-mode: nil
* mode: php
* End:
*/
// vim600:syn=php
?>
正如我们在这里看到的,pecl
命令只不过是对pear
命令的包装。每次你使用pecl
安装一个新的 PHP 扩展或者更新一个现有的pecl
扩展的时候,pear
都会被使用。正因为如此,它使得下载、编译和安装所需的 PHP 扩展变得非常容易。例如,如果我们想安装 APC 用户域扩展APCu
,我们只需执行以下命令:
$ sudo pecl install apcu
downloading apcu-5.1.3.tgz ...
Starting to download apcu-5.1.3.tgz (108,422 bytes)
.........................done: 108,422 bytes
39 source files, building
running: phpize
Configuring for:
PHP Api Version: 20151012
Zend Module Api No: 20151012
Zend Extension Api No: 320151012
...
install ok: channel://pecl.php.net/apcu-5.1.3
就这么简单,PECL 和 PEAR,连同phpize
,已经下载、编译并安装了APCu
扩展。
Note
如果您使用的 PHP 版本可以从您的操作系统的存储库中获得(yum
/ apt-get
),那么您可以首先检查是否已经有一个 PECL 扩展可以直接从存储库中安装。这不需要使用 PECL。
我应该用梨还是梨?
纯粹基于 Pyrus 的当前开发状态和活动,以及 Composer 和 Packagist 软件包的许多好处和可用性,我认为 PEAR 或 Pyrus 不再是当今新开发中使用的最佳选择。
由于 PEAR 的全局安装特性及其提供的管理,它仍然是一个有用的工具,在某些情况下,它仍然是为某些实用程序安装系统范围的依赖项的唯一工具。让我们看一个使用 PEAR 在开发机器上安装 PHP CodeSniffer 工具的例子。
使用 PEAR 安装全局实用程序
$ sudo pear install PHP_CodeSniffer
downloading PHP_CodeSniffer-2.5.1.tgz ...
Starting to download PHP_CodeSniffer-2.5.1.tgz (484,780 bytes)
....................done: 484,780 bytes
install ok: channel://pear.php.net/PHP_CodeSniffer-2.5.1
PHP 代码嗅探器现在可以立即供您使用。您可以测试它是否像这样工作:
$ phpcs --version
PHP_CodeSniffer version 2.5.1 (stable) by Squiz (http://www.squiz.net)
如果在尝试运行前面的命令时收到警告,比如PHP Warning: include_once(PHP/CodeSniffer/CLI.php): failed to open stream: No such file or directory
,这意味着 PHP CLI 的php.ini
不存在,或者没有将 PEAR 安装路径添加到php
路径中。您可以通过以下步骤进行检查,首先检查安装在您系统上的pear
的include
路径:
$ pear config-get php_dir
/usr/lib/php/pear
现在检查您的 PHP CLI 正在使用哪个配置文件:
$ php --ini
Configuration File (php.ini) Path: /etc/php/7.0/cli
Loaded Configuration File: /etc/php/7.0/cli/php.ini
这将给出比前面的代码更多的输出,但是您想要寻找Loaded Configuration File
行。
现在,采用前面列出的配置文件的路径,检查 PHP include
路径:
$ php -c /etc/php/7.0/cli/php.ini -r 'echo get_include_path()."\n";'
.:/usr/share/php:
因此,如您所见,include
路径不包括pear include
路径。为了解决这个问题,我们将打开php.ini
并将其添加到include_path
指令中,如下所示:
include_path = ".:/usr/share/php:/usr/lib/php/pear"
如果您尝试再次执行phpcs
命令,它现在会执行,因为它知道从哪里包含文件。
摘要
在这一章中,我们介绍了 Composer 和 Packagist,以及如何一起使用它们来管理应用的依赖关系。我们从头到尾介绍了立即使用 Composer 所需的一切,以及在管理应用的依赖关系时使用 Composer 的各种日常交互。我们还了解了 PEAR 及其在当今 PHP 开发中的作用。
我希望您在阅读完这一章后,对 Composer 的使用和它对您的应用开发的影响有一个非常清晰的了解,并且您现在有资源和能力马上使用它。
五、框架
即使你对 PHP 非常陌生,你也可能已经偶然发现了一些 PHP 框架。Symfony、Zend、Laravel、Yii 和 CakePHP 只是你可以使用的一些流行的选择。
当我在 1999 年第一次开始用 PHP 开发时,这些选项都不可用。那时候,PHP 应用是逻辑、HTML、JavaScript、SQL 查询以及更多分散在成百上千个文件中的东西的混合体。几年后,一些 PHP 开发框架在 2005-2006 年间开始成形,其中一些至今仍然存在并蓬勃发展(例如 Symfony 和 Zend Framework)。
诚然,我最初抵制使用这些新框架之一的想法。此时,我已经开始为我的站点和应用开发某种类型的已定义结构,因为我试图模仿 MVC 风格的结构,并将某种类型的分离和组织引入到我周围正在开发的疯狂中。我不想丢掉它,去学习一些对 PHP 来说非常新的东西。然而,随着这些框架开始成熟,社区支持开始建立,我意识到我没有一个好的理由继续坚持下去。我一头扎进了这些流行的框架中,并且从未后悔过这个决定。
为什么要使用框架?
这是我经常被那些还没有和一个人一起工作的人问到的问题。一个框架有什么特别之处,以至于我应该使用它,而不是自己做独立的 PHP 开发?好处有很多:
- 所有站点和应用都熟悉的定义好的结构
- 一个为框架代码库的改进做出贡献的社区,可以回答之前已经问过和回答过的问题(堆栈溢出,有人吗?)
- 一套预先开发的功能,您不必为每个应用重新开发
- 您可以使用模块、库和插件来立即添加附加功能
- 更好的可测试性,有可能与 PHPUnit 集成进行单元测试
- 与 ORM 的现有集成
- 在应用中预先建立设计模式的使用。通常,使用一个框架意味着你不得不至少在某种程度上遵循它的范例,这可以产生更好的结构和组织的代码
- 可重用和可维护的代码目的
关于 PHP 和框架的更多信息,请参考phpframeworks.com
。
Note
框架不是任何 PHP 开发的必需品。但是,它们是一个非常有价值的工具,可以帮助您构建更好的应用。
那么,框架是什么样子的呢?让我们深入研究一些广受欢迎和社区支持的框架。对于每个框架示例,我们将了解:
- 安装是多么容易
- 框架的总体结构
- 如何让一个简单的动作和控制器工作
- 如何进行简单的数据库调用并显示结果
Zend 框架 2
我们将从查看 Zend Framework 2 开始。Zend Framework 2,俗称 ZF2,是由 Zend Technologies 创建的第二代企业级框架。Zend Technologies 是由 Andi Gutmans 和 Zeev Suraski 创办的公司,自从拉斯马斯·勒德尔夫最初创建 PHP 以来,他们为 PHP 的发展做出了很大贡献。ZF2 标榜自己是模块化的、安全的、可扩展的、高性能的、企业就绪的,并得到了一个庞大而活跃的社区基础的支持。正如其模块化一样,ZF2 依赖于 Composer,由许多组件组成,所有组件都可以通过 Packagist 获得。
安装 ZF2
安装和运行 Zend Framework 2 非常简单快速。
Note
由于框架实现的 PHP 5.3+中的新特性,以及许多组件的主要重写,ZF2 与 ZF1 不向后兼容。
对于这个例子,我们将使用 Composer 安装 ZF2 框架应用,它可以在 GitHub ( https://github.com/zendframework/ZendSkeletonApplication
)上获得:
$ composer create-project -n -sdev zendframework/skeleton-application zf2
该命令将在名为zf2
的文件夹中安装 ZF2 框架应用。当它完成时,你应该在新创建的zf2
文件夹中看到一些目录(图 5-1 )。
图 5-1。
Directories in the newly created zf2 folder following installation of the ZF2 skeleton framework application
每个文件夹都有特定的用途,并作为任何 ZF2 应用的基础结构。这些文件夹的功能如下:
config
–所有的全局应用配置文件都可以在这个目录中找到。诸如定义应用中的模块、数据库配置、日志配置和应用缓存配置都包含在这里。data
–该文件夹是存储应用数据的地方。缓存文件、日志文件和其他类似的文件都存储在这里。module
–module
文件夹是您的所有应用逻辑所在的位置。在这里,您可以找到组成您的应用的所有各种模块。我们很快就会看到module
文件夹的结构。- public——
public
文件夹是你的应用的网络根目录。在您的 web 服务器配置中,文档根目录将设置为该文件夹。这里存储了所有面向公众的资产,如 JavaScript、CSS、图像、字体等。 vendor
–该文件夹包含您在应用中安装的所有第三方模块。这是通过 Composer 安装的任何东西在应用中的默认位置。
您刚刚安装的 ZF2 框架应用附带了一个方便的浮动文件,可以让您快速启动运行框架应用的虚拟机。正如我们在第二章中提到的,要启动并运行它,你只需要运行:
$ vagrant up
安装完成后,您应该会在浏览器中看到应用的框架(图 5-2 )。
图 5-2。
The ZF2 skeleton framework displayed in a browser
这个例子的目的是探索在每个框架中定义一个控制器和动作并执行一个简单的数据库查询是多么容易。ZF2 框架应用已经为我们定义了一个控制器和动作,这就是你在图 5-2 中看到的欢迎页面。当我们解构module
目录布局时,让我们看看组成这个显示的代码。
组件
ZF2 中的模块是您在应用中划分功能组的地方。ZF2 是围绕模块化系统的概念构建的。例如,如果您的应用具有面向用户的一面、一个管理员和一组批处理进程,那么每一个都可以被分离到它们自己的模块中。对于我们的例子,如果你展开module
目录,你会看到默认的应用模块包含在框架应用中(图 5-3 )。
图 5-3。
The default Application module contained in the skeleton app
这是 ZF2 模块的默认结构;每个文件夹都包含应用的重要部分,如下所示:
config
–这里是放置模块特定配置的地方。定义了诸如路线、控制器和视图模板之类的东西。- 这是你的模块的翻译文件所在的地方。框架应用使用 ZendI18n 模块,并使用
.po
文件来提供文本翻译。 - 这是绝大多数模块代码所在的地方。该文件夹包含构成模块的控制器、窗体、服务和其他应用逻辑。
view
–view
文件夹包含您的应用视图,即 MVC 中的“V”。默认情况下,ZF2 使用.phtml
文件,这是应用表示层的纯 PHP 模板方法。Module.php
-这个文件包含了module
类,这是 ZF2 为了实例化你的模块所期望的唯一的东西。在本模块中,您可以执行注册监听器、附加配置、自动加载器等活动。
控制器
控制器将应用动作与视图联系起来。框架应用包含使控制器和动作工作所需的最低限度:
class IndexController extends AbstractActionController
{
public function indexAction()
{
return new ViewModel();
}
}
ZF2 中控制器类和文件的命名约定分别是ControllerNameController
和ControllerNameController.php
。ControllerName
部分必须以大写字母开头。控制器中定义的每个动作都是定义为actionNameAction
的公共方法。动作必须以小写字母开头。这遵循类和方法的命名约定的 PSR-1 标准。
查看前面示例中的indexAction
方法,包含的唯一代码是返回ViewModel()
的实例化。ZF2 中的ViewModel
负责为您的应用设置和调用合适的视图模板,以及设置视图变量和某些您可用的选项。默认情况下,ViewModel()
将使用与您的动作同名的视图。
以下是控制器和视图模型的简单示例:
use Zend\View\Model\ViewModel
;
数据库ˌ资料库
对于我们的示例,我们将创建一个简单的表,其中包含用户的姓名和电子邮件地址,然后检索它并将其显示在我们的视图中。尽管 ZF2 支持强大的对象关系映射器(ORM ),比如 Doctrine,但我们将使用 ZF2 中可用的数据库提取层,称为 Zend\Db。
首先,让我们创建简单的数据库表,并用一些简单的数据填充它:
CREATE TABLE user (
id int(11) NOT NULL auto_increment,
name varchar(100) NOT NULL,
email varchar(100) NOT NULL,
PRIMARY KEY (id)
);
INSERT INTO user VALUES
(null,'Bob Jones','bob.jones@example.tld'),
(null,'Sally Hendrix','sallyh@example.tld'),
(null,'Carl Davidson','cdavidson@example.tld');
凭据配置
为了能够连接到我们的新数据库,我们需要向 ZF2 提供数据库名称、类型、用户名和密码的配置信息。这是在名为global.php
和local.php
的两个文件的全局config
文件夹中完成的。在这里,我们还将配置ServiceManager
,我们将使用它将所有东西连接在一起,然后使它对我们的应用可用:
global.php
return array(
'db' => [
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=app;host=localhost',
'driver_options' => [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''
],
],
'service_manager' => [
'factories' => [
'Zend\Db\Adapter\Adapter'
=> 'Zend\Db\Adapter\AdapterServiceFactory',
],
],
);
local.php
return array(
'db' => [
'username' => 'YOUR_DB_USERNAME_HERE',
'password' => 'YOUR_DB_USERNAME_PASSWORD_HERE',
],
);
模型
接下来,我们将创建我们的模型层。本例的模型层将包含一个非常简单的表(实体)表示和另一个与 Zend\Db TableGateway
交互的类,该类将执行我们的选择查询。
首先,我们的实体,它位于src/Application/Model/Entity/User.php:
下
namespace Application\Model\Entity;
class User
{
public $id;
public $name;
public $email;
public function exchangeArray($data)
{
$this->id = (!empty($data['id'])) ? $data['id'] : null;
$this->name = (!empty($data['name'])) ? $data['name'] : null;
$this->email = (!empty($data['email'])) ? $data['email'] : null;
}
}
这使用了exchangeArray
,您可能还记得它是 PHP 标准库(SPL)的一部分,将传递给它的数据映射到构成我们的表的三个方法。
接下来,在src/Application/Model/User.php
下找到与 Zend TableGateway
交互的类:
namespace Application\Model;
use Zend\Db\TableGateway\TableGateway;
class User
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function fetchAll()
{
$results = $this->tableGateway->select();
return $results;
}
}
服务经理
我们使用 ZF2 服务管理器来允许我们的新实体作为服务在我们的控制器中被调用。我们通过向Module.php
添加代码来做到这一点:
public function getServiceConfig()
{
return array(
'factories' => array(
'Application\Model\User' => function($sm) {
$tableGateway = $sm->get('UserTableGateway');
$table = new User($tableGateway);
return $table;
},
'UserTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new UserEntity());
return new TableGateway('user', $dbAdapter, null, $resultSetPrototype);
},
),
);
}
视角
ZF2 中的视图名称和文件夹结构通常遵循模块名称空间名称、控制器名称和动作。
Note
视图层的组件可能是变量、容器、视图模型、呈现器等。
以下是我们在 skeleton 应用中使用的示例代码:
view (folder containing views)
- application (Namespace name)
- index (controller name)
- index.phtml (Action name)
ZF2 中的视图模板采用纯 PHP 方法,而不是单独的模板语言,并且默认使用.phtml
扩展名。如果您查看示例应用中的index.phtml
文件,您会注意到 HTML 和简单 PHP 的混合。
查询和显示
最后一步是将我们的新users
表作为服务加载,查询整个表,并显示结果。我们将首先实例化ServiceLocator
,它用于在我们的应用中查找和加载服务。然后我们让它专门加载我们的User
类并返回实例化的对象。看这里:
<?php
namespace Application\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class IndexController extends AbstractActionController
{
protected $user;
public function indexAction()
{
if (!$this->user) {
$sm = $this->getServiceLocator();
$this->user = $sm->get('Application\Model\User');
}
$users = $this->user->fetchAll();
return new ViewModel([
'users' => $users,
]);
}
}
在这段代码中,我们获得了 ZF2 服务定位器的一个实例,并使用get
方法来检索我们的User
模型。接下来,我们查询这个表,并使用 ZF2 ViewModel
对象将结果传递给我们的模板。
在我们看来,我们将添加一个新的 div 和 table,并在我们的操作中使用ViewModel
迭代传递的结果:
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Users</h3>
</div>
<div class="panel-body">
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
</tr>
</thead>
<tbody
<?php foreach ($users as $album): ?>
<tr>
<td><?php echo $this->escapeHtml($album->name); ?></td>
<td><?php echo $this->escapeHtml($album->email); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
现在,当我们再次加载我们的页面时,我们将看到刚才查询的表的结果通过我们的视图模板显示出来(图 5-4 )。
Figure 5-4.
第二号交响曲
我们要看的下一个框架是 Symfony,特别是 Symfony 2 (SF2)。Symfony 的历史和 Zend Framework 一样长,是另一个坚实、可靠、面向企业的框架,由一个庞大、充满活力的社区提供支持。Symfony 由 SensioLabs 支持,由杨奇煜·波登西耶于 2004 年末创建,旨在为 Sensio 更快地创建网站。在创建它后不久,他决定开源它,11 年后,我们在这里有一个数千人的社区支持一个伟大的框架。
Note
截至发稿时,Symfony 3 刚刚发布。本教程重点介绍 Symfony 2 和当前可用的 Symfony 演示应用。
安装 SF2
虽然有几种安装 Symfony 2 (SF2)的方法,但目前的最佳做法是使用 Symfony 安装程序。要使用安装程序,只需运行适合您的操作系统的命令。
Linux 和 OS X
$ sudo curl -LsS https://symfony.com/installer -o /usr/local/bin/symfony
$ sudo chmod a+x /usr/local/bin/symphony
这将创建一个全局 symfony 命令,可以在系统中的任何地方执行。
Windows 操作系统
转到您的项目目录并执行以下命令:
c:\> php -r "readfile('https://symfony.com/installer');" > symphony
安装演示
一旦安装了 Symfony 安装程序,我们就可以安装 Symfony 演示应用了。这个应用将提供 SF2 中控制器、动作和数据库查询的功能演示。要安装此演示,只需键入:
$ symfony demo
Tip
本书提供的示例代码包含一个类似于我们的 ZF2 项目提供的用于 Symfony 演示的浮动文件。
应用目录结构
一旦安装成功,你应该在新创建的symphony
_demo
文件夹中看到一些目录(图 5-5 )。
图 5-5。
Directories in the newly created symphony_demo folder
就像 ZF2 一样,每个文件夹都有特定的用途;它们是任何 SF2 应用的基础结构。这些文件夹的功能如下:
- app–这是 Symfony 的核心文件夹,因为它包含所有配置、日志、缓存文件、AppKernel 和自动加载程序,还可以包含其他关键数据,如视图和翻译文件。
- bin–该文件夹是存储应用数据的地方。缓存文件、日志文件和其他类似的文件都存储在这里。
- src—
module
文件夹是您所有应用逻辑的所在。Symfony 应用逻辑被划分成“包”在这里,您将拥有组成您的应用的所有不同的包。这与我们看到的 ZF2 的模块文件夹非常相似。我们很快就会看到一个包的文件夹结构。 - vendor–该文件夹包含您在应用中安装的所有第三方模块。这是通过 Composer 安装的任何东西在应用中的默认位置。
- web——
web
文件夹是您的应用的 web 根目录,就像public
文件夹是 ZF2 的根目录一样。这是您的 web 服务器配置将设置为文档根的内容。这里存储了所有面向公众的资产,如 JavaScript、CSS、图像、字体等。
安装并运行演示程序后,您应该会在浏览器中看到演示应用(图 5-6 )。
图 5-6。
The demo application running in a browser
SF2 演示应用已经为我们定义了工作控制器、动作和数据库查询,如果您单击欢迎页面上的 Browse Application 按钮,就会看到这个示例应用。当我们解构 bundle 和 app 目录布局时,让我们来看看组成这个功能的代码。
束
就像 ZF2 中的模块一样,SF2 中的包是您在应用中划分功能组的地方。
以我们的演示应用为例,您看到的主要应用包含在AppBundle
包中。允许您在每个演示应用页面上查看源代码的功能包含在一个单独的包中,CodeExplorerBundle
。对于我们的例子,如果你展开AppBundle
目录,你会看到组成演示应用的许多目录(图 5-7 )。
图 5-7。
The expanded AppBundle directory showing the directories that make up the demo app
因为这是一个完整的演示应用,所以在这个包中还设置了许多其他组件。就本例而言,我们将只关注几个关键部分,如下所示:
- 控制器——这是一个包中包含的所有控制器所在的位置。
- entity——它在 MVC (model)中主要充当“M ”,因为它包含所有将数据库映射到代码的数据库实体。
AppBundle.php
–与 ZF2module.php
类似,这个文件包含AppBundle
类,它将包中包含的代码转换成功能 Symfony 代码。
您可能会注意到,这个包中没有我们的视图模板。虽然你的应用视图可以放在你的包—
中,而且在过去,根据定义的 Symfony 最佳实践,这是保存它们—
的正常位置,但是最好将它们放在app/Resources/views
目录的app
文件夹中。
控制器
正如我们在 ZF2 中所探讨的,控制器将应用动作与视图联系起来。演示应用包含几个控制器,但是在这个例子中我们只关注BlogController
和indexAction
:
public function indexAction($page)
{
$query = $this->getDoctrine()->getRepository('AppBundle:Post')->queryLatest();
$paginator = $this->get('knp_paginator');
$posts = $paginator->paginate($query, $page, Post::NUM_ITEMS);
$posts->setUsedRoute('blog_index_paginated');
return $this->render('blog/index.html.twig', array('posts' => $posts));
}
与 ZF2 一样,SF2 控制器命名约定遵循控制器文件名和类名的 StudlyCaps,每个动作都在 camelCase 中定义,同样遵循 PSR-1 标准。
SF2 使用render
方法来定义和呈现视图模板,并将任何数据传递给模板进行解释和处理。从前面的代码中我们可以看到,这个操作正在呈现名为index.html.twig
的视图模板,它位于 blog 目录中。
数据库ˌ资料库
Symfony 2 不包括我们之前看到的数据库抽象层,比如 Zend\Db。默认情况下,SF2 被配置为使用 Doctrine,这是一个强大的对象关系映射器(ORM)库。虽然我们为 ZF2 示例创建了一个模型(实体)层,但是 Symfony 演示应用中已经存在一些实体。我们的indexAction
正在调用的博文示例的实体位于AppBundle/Entity
目录中,名为Post.php
。
除了提供实体之外,我们的演示应用还利用了数据库表post
的存储库。Doctrine 中的存储库允许您定义在数据库上执行定制查询的方法。在我们的indexAction
中,它调用了PostRepository
和queryLatest()
方法。让我们来看看组成这个方法的代码:
public function queryLatest()
{
return $this->getEntityManager()
->createQuery('
SELECT p
FROM AppBundle:Post p
WHERE p.publishedAt <= :now
ORDER BY p.publishedAt DESC
')
->setParameter('now', new \DateTime())
;
}
这种方法利用了教条查询语言(DQL ),非常类似于常规 SQL。它在语法上等效于以下 SQL:
SELECT p.*
FROM post p
WHERE p.published_at <= NOW()
ORDER BY p.published_at DESC
这个特定的代码返回一个 Doctrine 实体管理器对象,该对象包含按发布日期降序排列的文章数据。indexAction
通过以下代码进行查询:
$query = $this->getDoctrine()->getRepository('AppBundle:Post')->queryLatest();
这段代码获取教条对象,加载位于AppBundle
中的Post
存储库,最后调用前面的queryLatest()
方法。然后,这将被移交给演示应用用来提供结果分页的另一个库,最后,应用通过使用以下代码行将post
数据变量传递给 Twig 模板:
return $this->render('blog/index.html.twig', array('posts' => $posts));
Tip
ZF2 也可以被配置为作为 ORM 使用 Doctrine,而不是使用 Zend 数据库抽象库。
视角
SF2 中的视图名称和文件夹结构通常遵循控制器名称和动作。对于我们从演示应用中检查的示例代码,如下所示:
view (folder containing views)
- blog (controller name)
- index.html.twig (Action name)
默认情况下,Symfony 使用 Twig 模板引擎。Twig 是一种轻量级但功能强大的模板语言,它使用简单的语法并将模板解析为纯 PHP 文件。
Note
虽然 Symfony 支持纯 PHP 模板,就像 Zend Framework 2 一样,但人们认为 Twig 将成为 Symfony Framework 3 唯一官方支持的模板引擎。
显示结果
我们的演示应用的最后一步是处理 Symfony render
方法传入的数据,并将其显示在博客索引视图模板中。让我们来看看模板中处理这个问题的代码块:
{% for post in posts %}
<article class="post">
<h2>
<a href="{{ path('blog_post', { slug: post.slug }) }}">
{{ post.title }}
</a>
</h2>
{{ post.summary|md2html }}
</article>
{% else %}
<div class="well">{{ 'post.no_posts_found'|trans }}</div>
{% endfor %}
这段代码使用了标准 PHP foreach
的 Twig 等价物,就像我们在 ZF2 示例中使用的一样。然而,如果通过else
语句在post
变量中没有可用的数据,Twig for
方法有一个自动处理的约定。
拉维尔 5 号
我们要看的最后一个 PHP 框架是 Laravel。Laravel 是较新的框架之一,但它在 PHP 社区中迅速流行起来,因为它干净、快速且易于使用。
Laravel 最重要的特性之一是它非常可配置、可扩展,是一个非常有用的刀片模板引擎。Laravel 是由 Taylor Otwell 创建的,旨在为一个名为 CodeIgniter 的过时 PHP 框架提供一种高级替代方案。Laravel 的第一个测试版是在 2011 年 6 月。
安装 Laravel 5
正如其他框架一样,有几种方法可以安装 Laravel。推荐的方式是通过 Composer。出于本练习的目的,我们将使用 Laravel quickstart 项目和 Laravel Homestead 游民箱。Homestead 是一个完全配置好的流浪者盒子,带有 PHP7 和运行 Laravel 所需的所有系统要求。
首先,克隆快速启动项目:
$ git clone https://github.com/laravel/quickstart-basic laravel
现在安装所有的依赖项:
$ cd laravel
$ composer install
接下来,安装宅基地。这将为您提供工具来生成运行 Laravel Homestead 框的流浪者文件:
$ composer require laravel/homestead --dev
$ php vendor/bin/homestead make
现在调出新框:
$ vagrant up
最后,ssh 进入 new box 并运行数据库迁移脚本,为 Laravel quickstart 应用安装示例数据库:
$ vagrant ssh
$ cd laravel
$ php artisan migrate
使用Homestead.yaml
配置文件中生成的设置构建家园框。如果您打开这个文件,您将看到为这个新的流浪者虚拟机定义的 IP。在这种情况下,默认设置为 192.168.10.10。如果您在浏览器中加载该网站,您应该会看到快速启动应用页面(图 5-8 )。
图 5-8。
The quickstart app page displayed in a browser
应用目录结构
如果您查看安装 Laravel quickstart 项目的目录,您会看到组成 Laravel 应用的各种文件夹(图 5-9 )。
图 5-9。
The folders that comprise a Laravel application
与我们研究的其他框架一样,每个目录都存储了应用的一个特定部分。以下是三个最重要的目录:
- app——这是所有代码存在的地方。
- bootstrap——它主要充当 MVC(模型)中的“M ”,因为它包含所有将数据库映射到代码的数据库实体。
- config——类似于 ZF2
module.php
文件,这个文件包含了AppBundle
类,它将包中包含的代码转换成功能 Symfony 代码。
应用逻辑
与 Zend Framework 或 Symfony 不同,在基本的 Laravel 应用中,没有通过模块或捆绑包的方式进行划分。通过在 Laravel 应用文件夹下创建单独的文件夹,并相应地命名底层代码,可以实现有点类似的方法,但这与其他框架不同,不是必需的。
控制器和路线
在 Laravel 中,有两种方法可以提供 MVC 应用的控制器层。最简单的方法是使用app/Http/routes.php
文件并声明一个匿名函数。这是我们正在研究的 quickstart 应用所采用的方法,如下所示:
Route::get('/', function () {
return view('tasks', [
'tasks' => Task::orderBy('created_at', 'asc')->get()
]);
});
如果您想将它移到一个控制器中,您可以将您的控制器添加到app/Http/Controllers
目录中,并在routes.php
文件中定义控制器:
namespace App\Http\Controllers;
use App\User;
use App\Http\Controllers\Controller;
class TaskController extends Controller
{
public function tasks()
{
$tasks = Task::orderBy('created_at', 'asc')->get();
return view('tasks', ['tasks' => $tasks]);
}
}
现在我们在routes.php
中定义路线:
Route::get('/', TaskController@tasks);
这条路径告诉 Laravel 使用IndexController
并执行tasks
方法。
数据库ˌ资料库
Laravel 在基础安装中包含了自己的基于 ActiveRecord 的对象关系映射(ORM)库实现。雄辩被吹捧为简单易用。与前面的例子不同,每个数据库都用一个模型类表示,而不是使用实体。正如您可以从包含的Task
模型中看到的,所需的代码非常少:
namespace App;
use Illuminate\Database\Eloquent\Model;
class Task extends Model
{
//
}
默认情况下,Laravel 将尝试使用模型类名的复数小写版本作为它所代表的数据库表名。在本例中,应该是tasks
。它还期望每个表都有一个名为id
的主键列以及两个名为created_at
和updated_at
的时间戳列。当视图中的所有任务都被检索到时,这些将被使用。
视角
Laravel 中的所有视图都存储在resources/views
目录下。视图模板可以任意排列,例如在主views
目录下的子目录中。Laravel 提供了使用纯 PHP 模板或 Blade 的能力,Blade 是 Laravel 创建的一种模板语言。快速启动应用的视图是用 Blade 编写的,可以在resources/views/tasks.blade.php
下找到。
显示结果
让我们再来看看在routes.php
文件中声明的匿名函数:
return view('tasks', [
'tasks' => Task::orderBy('created_at', 'asc')->get()
]);
这将使用带有 concertive 的get()
方法来检索由必需的created_at
列排序的所有任务。这相当于运行以下 SQL 语句:
SELECT * FROM tasks ORDER BY created_at ASC
最后,通过调用view()
方法并将数据传递给它,代码将数据交给模板使用。如果我们看一下模板,我们可以看到 Blade 语法检查变量$tasks,
中是否有任何结果,如果有,它用一个foreach
循环遍历它们:
<!-- Current Tasks -->
@if (count($tasks) > 0)
<div class="panel panel-default">
<div class="panel-heading">
Current Tasks
</div>
<div class="panel-body">
<table class="table table-striped task-table">
<thead>
<th>Task</th>
<th> </th>
</thead>
<tbody>
@foreach ($tasks as $task)
<tr>
<td class="table-text">
<div>{{ $task->name }}</div>
</td>
<!-- Task Delete Button -->
<td>
<form action="/task/{{ $task->id }}" method="POST">
{{ csrf_field() }}
{{ method_field('DELETE') }}
<button type="submit" class="btn btn-danger">
<i class="fa fa-btn fa-trash"></i>Delete
</button>
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@endif
微观框架
当您希望框架提供的结构和开发速度时,微框架是一种替代方案,但它比传统的完整框架提供的“花哨”和开销更少。
PHP 有许多不同的微框架。以下是一些目前流行的选择:
- silex——这是 Sensio Labs 的一个微框架,基于几个不同的 Symfony 组件。
- lumen——这是 Laravel 的一个微观框架,基于 Laravel 的一些基础。
- slim——这被认为是目前最小最快的 PHP 微框架之一。
- 这是一个开源的 PHP 全栈框架,是作为 C 扩展编写的。
- Yii -这是一个开源的、面向对象的、基于组件的 MVC PHP 框架。
何时使用微框架
关于什么时候使用微框架,什么时候使用完整框架,没有硬性规定。这完全是个人决定,可能会因项目而异。顾名思义,微框架通常被认为是用于小型项目的,但是没有什么可以阻止你将它用于任何规模的项目。与任何框架一样,您需要仔细权衡所构建内容的范围、大小和功能,并在此基础上做出决定。通读任何给定框架的文档和特性,无论它是微观的还是完整的,都会让您对该框架能为您提供什么有重要的了解。
使用微框架
那么用微框架开发是什么样子的呢?让我们深入研究一下一个非常简单的“hello world”示例,它使用了我之前列出的三个框架。在每个例子中,我们只需安装框架并定义一个简单的路由和控制器来打印“hello world”。
Tip
本书提供的示例代码包含一个基本的浮动文件,该文件提供了一个简单的 VM 来运行微框架示例。
Silex
要开始使用 Silex,我们首先要安装它。使用 Composer 是简单且值得推荐的方法。为此,让我们创建一个需要 Silex 的composer.json
文件:
{
"require": {
"silex/silex": "∼1.3"
}
}
现在,我们运行composer install
:
$ composer install
Loading composer repositories with package information
Installing dependencies (including require-dev)
...
Writing lock file
Generating autoload files
就这样—
Silex 现在已经设置好了,可以使用了。要创建这个极其简单的示例,我们只需创建一个包含 Silex autoloader 的文件,定义路由,执行 Silex,并显示一个简单的 HTML 响应。让我们来看看实现这一点所需的代码:
<?php
require_once __DIR__.'/../vendor/autoload.php';
// Initialize Silex
$app = new Silex\Application();
// Define a route and anonymous function for our "controller"
$app->get('/hello-world', function () {
return '<h1>Hello World!</h1>';
});
$app->run();
现在,如果我们在浏览器中访问/hello-world
,我们会看到“Hello World!”输出到屏幕上。当然,Silex 比简单的路由和 HTML 响应更强大。Silex 提供了 Symfony 为您提供的许多其他功能和服务,包括:
- Twig 服务提供商,因此您也可以利用 Silex 中 Twig 模板的功能。
- 动态路由
- 使用教条的数据库交互
- 表单、验证和会话处理
- 日志、PHPUnit 集成等等
流明
名单上的下一个是卢蒙。我们可以通过首先安装 Lumen 安装程序或者使用composer create-project
来安装 Lumen。对于我们的例子,我们将使用composer create-project
并在 Laravel Homestead 流浪者盒子上运行这个例子,就像我们在前面的 Laravel 例子中所做的那样:
$ composer create-project --prefer-dist laravel/lumen lumen
Installing laravel/lumen (v5.2.1)
...
Writing lock file
Generating autoload files
现在,安装了 Lumen 之后,让我们添加 Homestead 流浪者配置:
$ composer require laravel/homestead --dev
$ php vendor/bin/homestead make
我们现在可以启动我们的流浪者盒子,并测试我们简单的“hello world”示例。正如 Laravel 一样,我们在app/Http/routes.php
下定义路线。创建我们的示例的语法看起来几乎与 Silex 中的完全一样:
<?php
// Define our hello world route
$app->get('/hello-world', function () {
return '<h1>Hello World!</h1>';
});
正如您在这里可能注意到的,这个例子和 Silex 例子之间最大的区别是没有包含和执行 Silex 的run
方法的自动加载器。这是因为这一切都发生在public/index.php
下定义的前端控制器中:
<?php
$app = require __DIR__.'/../bootstrap/app.php';
$app->run();
如您所见,这与 Silex 微框架的结构几乎完全相同。这两者之间的主要区别是框架的底层组件。如果您已经偏好 Symfony 或 Laravel,这将有助于您在这两个框架之间做出更容易的决定。
微小的
我们名单上的最后一个是斯利姆。要安装 Slim,我们将使用 Composer,就像我们使用 Silex 一样。我们将创建一个composer.json
文件,然后运行composer install
:
{
"require": {
"slim/slim": "³.0"
}
}
$ composer install
Loading composer repositories with package information
...
Writing lock file
Generating autoload files
Now that Slim is installed, we'll define a single file to which we'll pass our request. This will define the route as well as an anonymous function to perform the "hello world" response:
<?php
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
// Include Slim autoloader
require_once __DIR__.'/../vendor/autoload.php';
// Initialize Slim
$app = new Slim\App();
// Define a route and anonymous function to serve as a controller
$app->get('/hello-world', function (Request $request, Response $response) {
$response->getBody()->write("Hello World!");
return $response;
});
$app->run();
正如您在这里看到的,这也非常类似于 Silex。我们包含了 Slim autoloader,初始化 Slim,定义我们的路线并传入所需的Request
和Response
对象,返回我们的文本,最后执行 Slim。
如果您浏览 Slim 的源代码和文档,您会很快注意到它肯定比 Silex 或 Lumen 要轻得多。它确实提供了一些附加功能,比如在你的应用中使用 Twig 模板,但是你会注意到它缺少其他一些现成的默认功能,比如数据库交互。尽管在选择要使用的微框架时应该考虑到这一点,但应该注意的是,由于 Slim 使用了 Composer 以及 Composer 提供的模块化,您可以快速且相当容易地利用 ORM,如 Doctrine 或 Laravel 的口才。
摘要
在这一章中,我们讨论了使用 PHP 开发框架给你带来的好处,以及如何快速地使用一些最流行的完整框架和微框架。尽管我们只是触及了使用框架的表面,但希望你现在对它们是如何工作的有了更好的理解,并且能够马上开始在你的项目中使用。