Phalcon-入门指南-全-

Phalcon 入门指南(全)

原文:zh.annas-archive.org/md5/3f75b70a3a9a57e13c3d95e83f866056

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Phalcon 的开发者将开发最快、最有效的 PHP 框架作为他们的使命,并且他们很好地完成了这个使命。Phalcon 结合了 C 编程的速度、PHP 的简洁性和框架的结构,简化了 Web 应用开发。

Phalcon 入门是使用 Phalcon 开发 Web 应用的介绍。您将通过构建一个博客应用来学习,使用 Phalcon 开发者工具和网络工具快速构建应用的 CRUD 骨架,然后修改它以添加功能。

随着博客功能的增加,您将学习如何使用其他 Phalcon 功能,如 Volt 模板引擎、视图助手、PHQL、验证、加密、cookie、会话和事件管理。

本书涵盖内容

第一章, 安装 Phalcon,涵盖了在 Linux、Windows 或 Mac 上安装 Phalcon PHP 扩展,以及配置 PHP、Apache 或 Nginx。

第二章, 设置 Phalcon 项目,涵盖了使用 Phalcon 开发者工具或 Phalcon 网络工具手动设置 Phalcon 项目。

第三章, 使用 Phalcon 模型、视图和控制器,涵盖了 Phalcon 的 MVC 结构。

第四章, 在 Phalcon 中处理数据,深入探讨了 Phalcon 模型、PHQL、会话数据和过滤及清理数据。

第五章, 使用 Phalcon 特性,涵盖了 Phalcon 的更多特性,包括密码散列、控制用户访问、设置 cookie 以及使用视图部分、日志和视图助手。

本书面向对象

本书旨在为想要学习如何使用 Phalcon PHP 框架的 PHP 开发者编写。需要一些 PHP 知识,但不需要 MVC 框架的先验知识。

规范

在本书中,您将找到多种文本样式,以区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。

文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称如下所示:“因此,我们必须定位php.ini文件并对其进行编辑。”

代码块按如下方式设置:

phalconBlog/
  app/
    config/
    controllers/
    library/
    logs/
    models/
    plugins/
    views/
      index/
      layouts/
  public/
    css/
    files/
    img/
    js/
    temp/

任何命令行输入或输出都按如下方式编写:

sudo apt-get install git
sudo apt-get php5-devphp-mysqlgcc

新术语重要词汇以粗体显示。屏幕上显示的单词,例如在菜单或对话框中,在文本中显示如下:“因此,在您的浏览器窗口中搜索单词编译器。”

注意

警告或重要提示以如下框中显示。

小贴士

小技巧和技巧如下所示。

读者反馈

我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢什么或可能不喜欢什么。读者的反馈对我们开发您真正能从中获得最大价值的标题非常重要。

要发送一般反馈,只需发送一封电子邮件到mailto:feedback@packtpub.com,并在邮件主题中提及书籍标题。

如果您在某个主题上具有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南www.packtpub.com/authors

客户支持

现在您是 Packt 书籍的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大价值。

下载示例代码

您可以从您在www.packtpub.com的账户下载您购买的所有 Packt 书籍的示例代码文件。如果您在其他地方购买了这本书,您可以访问www.packtpub.com/support,注册以将文件直接通过电子邮件发送给您。您还可以通过访问 GitHub 页面eristoddle.github.io/phalconBlog/找到书中使用的所有代码。

错误清单

尽管我们已经尽一切努力确保我们内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现了错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何错误清单,请通过访问www.packtpub.com/support,选择您的书籍,点击错误提交表单链接,并输入您的错误详情来报告它们。一旦您的错误得到验证,您的提交将被接受,错误将被上传到我们的网站,或添加到该标题的错误清单中。

盗版

互联网上版权材料的盗版是所有媒体中持续存在的问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现我们作品的任何非法副本,无论形式如何,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。

请通过链接mailto:copyright@packtpub.com与我们联系,并提供涉嫌盗版材料的链接。

我们感谢您在保护我们的作者以及为我们提供有价值内容的能力方面提供的帮助。

问题

如果您在书的任何方面遇到问题,可以通过mailto:questions@packtpub.com与我们联系,我们将尽力解决。

第一章:安装 Phalcon

本章将指导您在所选平台上安装 Phalcon。安装过程根据您使用的操作系统略有不同。由于 Phalcon 是用 C 语言编写的 PHP 扩展,因此安装过程比下载 PHP 文件存档并将其上传到您的服务器要复杂一些。

但安装过程仍然应该少于一个小时。根据您的系统和您对 PHP 扩展的经验,可能只需几分钟。在本章结束时,您将拥有一个高性能的 PHP 框架,这将简化您开发应用程序的过程,并帮助您更快地将项目启动运行。

在本章中,您将学习如何:

  • 满足 Phalcon 的系统要求

  • 找到适合 Windows 机器使用的正确 Phalcon DLL

  • 在 Windows 机器上安装 Phalcon DLL

  • 在 Linux 上安装 Phalcon

  • 在 Mac 上安装 Phalcon

  • 在 FreeBSD 上安装 Phalcon

  • 编辑您的 php.ini 以使用 Phalcon

  • 为 Phalcon 配置 Apache

Phalcon 系统要求

当然,您需要一个 Web 服务器才能使用 Phalcon。Apache 是最常用的,但如果您有 Nginx 或 Cherokee Web 服务器,那也可以。如果您愿意,甚至可以使用 PHP 内置的 Web 服务器作为您的 Phalcon 开发服务器。为了本书的目的,我们假设您已经安装了 Apache。

此外,您还需要安装 PHP。虽然 Phalcon 可以与高于版本 5.3.1 的任何 PHP 版本一起工作,但建议您使用 PHP 5.3.11 或更高版本。5.3.11 之前的 PHP 版本仍然存在后来版本中已修复的安全漏洞和内存泄漏。

但这基本上就是您需要的。Phalcon 并不是特别挑剔。每个特定操作系统可能还有一些其他要求您需要满足,我们将在单独的安装过程中介绍它们。

在 Windows 上安装 Phalcon

在本节中,我们将指导您在 Windows 上安装 Phalcon。即使您不在 Windows 上安装扩展,也请阅读本节内容。其中许多步骤,如定位您的 php.ini,适用于所有操作系统。

Windows 版本的 Phalcon 扩展是以 DLL 的形式编译的。但是,您必须找到适合您 PHP 版本的正确 DLL。在 Windows 上安装 Phalcon 的第一个要求是使用 Windows Visual Studio 9.0 编译的 PHP 二进制文件。如果您使用的是其他版本的 Visual Studio,那么如果您想让 Phalcon 在您的系统上工作,您将不得不安装正确的 PHP 版本。

因此,让我们查看您的 PHP 安装,以确定您安装了哪个版本的 PHP。首先,您需要找到您的 Web 服务器的文档根目录。或者换句话说,您需要找到当您浏览到http://localhost时对应的文件夹,或者找到映射到http://yourdomain.com主页的您的服务器上的文件夹。我们将创建一个 PHP 文件,它会告诉我们如何选择正确的 PhalconPHP DLL 版本并将其放入该文件夹。

因此,打开您的文本编辑器或 IDE,创建一个新文件。将其命名为info.php。在该文件中,插入以下代码:

<?php
  phpinfo();
?>

将文件保存到您的文档根目录。您的文档根目录取决于您在机器上安装了哪个 Web 服务器堆栈。它可能是来自官方网站的 Apache,例如 XAMPP、Wamp 或 AMPPS,或者是 Apache、PHP 和 MySQL 堆栈的其他版本。您最好的选择是访问您下载所选 Apache 版本的网站,并阅读文档。

保存文件后,浏览到http://localhost/info.php,您应该看到如下所示的页面截图:

在 Windows 上安装 Phalcon

您会注意到页面顶部的 PHP 版本。我们将关注由点分隔的前两个数字。它们将是如前一个屏幕截图所示的5.3或 5.4。如果它们是 5.2 或更低版本,您需要更新您的 PHP 安装。

接下来,您需要确保您的 PHP 版本是用 Microsoft Visual Studio 9.0 编译的。因此,在浏览器窗口中搜索单词编译器。在大多数浏览器中打开搜索框的命令是Ctrl + F。它通常位于页面顶部,如下面的屏幕截图所示。如果您的 PHP 是用 Visual Studio 9.0 编译的,您应该看到MSVC9 (Visual C++) 2008,如下面的屏幕截图所示:

在 Windows 上安装 Phalcon

编译器下方,您将看到架构列表。它将显示x86x64。在之前的屏幕截图中,PHP 是为x86架构编译的。

接下来,我们需要查看您是否有线程安全的 PHP 版本。因此,在浏览器窗口中搜索“PHP 扩展构建”。在此旁边,您将看到您 PHP 安装的构建版本。如果您看到此数字末尾有 NTS,则说明您没有线程安全的 PHP 版本。

现在我们有了这些信息,我们可以前往phalconphp.com/en/download/windows

小贴士

下载示例代码

你可以从你购买的所有 Packt 书籍的账户中下载示例代码文件,账户地址为www.packtpub.com。如果你在其他地方购买了这本书,你可以访问www.packtpub.com/support并注册以直接将文件通过电子邮件发送给你。你还可以通过访问 GitHub 页面eristoddle.github.io/phalconBlog/找到书中使用的所有代码。

在这里,我们将看到如下所示的页面,列出了多个版本的 Phalcon PHP 扩展:

在 Windows 上安装 Phalcon

虽然看起来可能有些令人畏惧,但我们刚刚完成了研究,这将使我们能够选择正确的版本。我们知道我们是否有线程安全的版本。我们知道我们是否有 x86 或 x64 架构。我们还知道我们是否有 PHP 5.3 或 5.4。不要担心 PHP 版本中的最后一个数字。我们只需要下载适合我们配置的扩展。PHP 5.3.9 的 Phalcon 扩展将适用于所有 PHP 5.3 版本,而 PHP 5.4.0 的扩展将适用于所有 PHP 5.4 版本。

下载扩展后,解压它。在 zip 文件中将会有一个 DLL 文件。接下来,我们需要将这个 DLL 文件移动到包含其他 PHP 扩展的文件夹中。如果你安装了 XAMPP,这个文件夹很可能是c:\xampp\php\ext,而对于 Wamp,则是c:\wamp\bin\php_version_number\ext,其中php_version_number将被你安装的 PHP 版本号所替换。对于其他版本的 Web 服务器堆栈,你将需要访问文档以找到 PHP 扩展文件夹。

现在我们已经将扩展放置到需要的位置,但还没有完成。我们需要在 PHP 运行时加载这个扩展。因此,我们必须找到php.ini文件并编辑它。但是,你知道吗,我们可以使用我们的info.php文件来告诉我们这个文件的位置。所以,再次在浏览器中加载它并搜索已加载配置文件。在这个条目旁边列出了其位置,如下面的截图所示:

在 Windows 上安装 Phalcon

一旦找到这个文件,就用你的文本编辑器或 IDE 打开它,并滚动到文件的底部。这个文件可能相当长。在文件的底部,你将想要添加以下行来告诉 PHP 加载 Phalcon:

extension=php_phalcon.dll

保存文件。一些服务器堆栈,如 XAMPP,有桌面软件来停止和启动 Apache,而其他如来自官方网站的 Apache 则只有托盘图标来控制停止和启动服务器。或者,你也可以直接从命令行重启 Apache。现在重启 Apache,Phalcon 就安装完成了。但让我们检查一下。再次在浏览器中加载http://localh ost/info.php并搜索 Phalcon,你应该会看到一个如下所示的页面:

在 Windows 上安装 Phalcon

在 Linux 上安装 Phalcon

Linux 是最常见的用于服务器的操作系统。Phalcon 需要你拥有对服务器的 root 访问权限。如果你在一个共享服务器上,你将不会获得 root 访问权限,但你可能能够联系你的托管提供商为你安装 Phalcon。如果你有一个 VPS、云服务器或专用服务器,你很可能有 root 访问权限。如果没有,你可以要求你的托管提供商为你提供访问权限。

本书不涉及在每个 Linux 安装上安装 Phalcon 的过程,但 Phalcon 扩展的一些 Linux 版本有预构建版本。

对于 Debian Linux,你可以在以下链接找到仓库:

debrepo.frbit.com/

对于 Arch Linux,你可以访问以下链接下载 PKGBUILD:

aur.archlinux.org/packages.php?ID=61950

对于 OpenSUSE,你可以在以下链接找到软件包:

software.opensuse.org/package/php5-phalcon

对于所有其他版本的 Linux,我们将不得不自己编译 Phalcon。这并不难,如果你已经使用 Linux 一段时间了,你现在可能已经习惯了编译自己的软件。首先,我们必须确保我们安装了以下软件:

  • Git

  • GCC 编译器

  • PHP 开发资源

如果你还没有安装这些软件,你可以通过命令行安装它们。

对于 Ubuntu,命令行如下:

sudo apt-get install git
sudo apt-get php5-devphp-mysqlgcc

对于 Fedora、CentOS 和 RHEL,命令行如下:

sudo yum install git
sudo yum install php-develphp-mysqlnd ccc libtool

对于 OpenSUSE,命令行如下:

yast2 -igit
yast2 -iphp5-pear php5-develphp5-mysqlgcc

对于 Solaris,命令行如下:

pkginstall git
pkg install gcc-45 php-53 apache-php53

一旦满足所有要求,打开一个终端窗口并输入以下命令以下载 Git 仓库并编译 Phalcon 扩展:

git clone git://github.com/phalcon/phalcon.git
cd cphalcon/build
sudo ./install

在某些 Linux 安装中,编译可能会失败,因为缺少 libpcre3-dev。你可以通过使用你的 Linux 软件包管理器安装此软件包来修复此问题。

你刚刚编译了你的第一个 PHP 扩展。现在我们需要将扩展的引用添加到 php.ini 文件中。我们可以通过创建一个 info.php 文件,如本章中 在 Windows 上安装 Phalcon 部分所述,在浏览器中加载它,并在结果页面中搜索 Loaded Configuration File 来找到它。在你的文件系统中定位列出的文件,并将以下行添加到文件的底部:

extension=phalcon.so

现在,保存文件。如果您在 Debian 类型的 Linux 上安装 PHP 5.4 的 Phalcon,安装新 PHP 扩展的程序已更改。不再建议编辑主php.ini文件。在您找到php.ini文件的同一文件夹中,通常路径为/etc/apache5/,您将找到一个mods-available文件夹。您将在这个文件夹中创建一个仅用于 Phalcon 的自定义.ini文件,并将其命名为phalcon.ini。此文件将为空,因为我们没有为 Phalcon 添加任何自定义配置设置。然后,为了启用扩展,我们只需在命令行中运行以下命令:

php5enmod phalcon

现在,重启您的 Web 服务器。重启 Apache 的命令取决于 Linux 的类型。对于 Debian 和 Ubuntu 类型的 Linux,您可以输入以下命令:

sudo service apache2 restart
And for Red Hat, CentOS or Fedora, you would type:
sudo service httpd restart

在 Mac 上安装 Phalcon

要在 Mac 上安装 Phalcon,步骤基本上与在 Linux 上安装相同。您必须从源代码编译扩展。但首先有一些要求。您需要安装 Git、php5-devel 和带有命令行工具的 Xcode。Xcode 可以从官方 Mac 网站免费获取,但默认情况下并未安装。

此外,Xcode 的命令行工具默认情况下并未与 Xcode 一起安装。为了确保安装这些工具,打开 Xcode 的偏好设置面板,选择下载标签,然后点击位于命令行工具列表旁边的安装按钮。

要安装 Git,只需将您的网络浏览器指向git-scm.com/download/mac,下载文件,然后安装它。如果您系统上还没有安装 php5-devel,您也需要安装它。如何安装这取决于您安装的包管理器。Mac 上也有一些预构建的 php5-devel 二进制文件可用。在 Google 上搜索“install Mac php5-devel”应该会指引您正确的方向。

现在我们已经满足了所有要求,步骤与在 Linux 上安装 Phalcon 相同。首先,使用 Git 将源代码拉取到您的计算机上,并在终端中运行以下命令来构建扩展:

git clone git://github.com/phalcon/phalcon.git
cd cphalcon/build
sudo ./install

然后,使用本章“在 Windows 上安装 Phalcon”部分中提到的步骤来定位您的php.ini文件。一旦找到您的php.ini文件,打开它,并在文件的底部添加以下行:

extension=phalcon.so

接下来,重启 Apache,然后设置完成。

在 FreeBSD 上安装 Phalcon

如果您在 FreeBSD 上安装 Phalcon,您会很容易。Phalcon 有一个可用的端口。可以使用以下命令安装:

pkg_add -r phalcon

或者

export CFLAGS="-O2 -fno-delete-null-pointer-checks"cd/usr/ports/www/phalcon&& make install clean

可选的 Phalcon 依赖项

到目前为止,您应该已经安装了一个可工作的 Phalcon。但为了使用 Phalcon 的一些功能,您可能需要安装以下 PHP 扩展之一、一些或全部:

  • PDO

  • PDO/MySQL

  • PDO/PostgreSQL

  • PDO/SQLite

  • PDO/Oracle

  • mbstring

  • Mcrypt

  • openSSL

  • Mongo

只有在你打算使用它们提供的功能时,你才需要这些。就本书的目的而言,你需要确保你已经安装了 PDO、PDO/MySQL、Mcrypt 和 mbstring 扩展。

寻找帮助

Phalcon 一直在不断改进,但有时你可能会遇到标准安装过程可能不起作用或遇到其他错误的问题。本书的范围不包括在安装或使用 Phalcon 时可能发生的所有情况。为了找到这类问题的帮助,我建议查看以下三个地方:

摘要

在本章中,我们学习了如何为我们的操作系统选择正确的 Phalcon PHP 扩展版本。我们还学习了如何在多种系统上安装该扩展。我们编辑了php.ini文件,以便 Phalcon 现在将随我们的 PHP 安装一起加载,并成为语言的一个组成部分。

在下一章中,我们将开始开发我们的 Phalcon 博客,通过构建我们网站的骨架结构。

第二章:设置 Phalcon 项目

在我们开始编写博客代码之前,我们将先查看我们项目需要具备的基本框架。首先,我们将手动创建文件夹结构和必要的文件。完成这一艰难的过程后,我们将学习如何使用 Phalcon 开发者工具,通过几个简单的终端命令自动完成所有这些操作。我们的代码将在过程中不断变化和演变。这是因为随着我们深入框架,我们将重构旧代码以处理我们想要添加到博客应用程序中的新功能。

在本章中,我们将学习以下主题:

  • Phalcon 项目的文件夹结构

  • Phalcon .htaccess 文件

  • 如何使用.ini 文件

  • 如何创建 Phalcon 引导文件

  • 如何使用 Phalcon 开发者工具

文件夹结构

与许多 PHP 框架不同,Phalcon 不关心你的文件夹结构。你可以创建任何你想要的文件夹结构,但为了我们的博客教程,我们将使用一个基本的 MVC 结构网站。各种框架中文件夹结构的变体有很多。我们将从以下代码片段开始,它具有 Phalcon 开发者工具将在本章后面为我们生成的相同结构:

phalconBlog/
  app/
    config/
    controllers/
    library/
    logs/
    models/
    plugins/
    views/
      index/
      layouts/
  public/
    css/
    files/
    img/
    js/
    temp/

在我们的public文件夹中,我们只有一个可执行文件,那就是我们的引导文件index.php,我们将在本章后面学习它。其他所有内容都是浏览器加载的静态文件。在我们的app文件夹中,我们有modelsviewscontrollers文件夹。

有比这更小的 MVC 文件结构,也有包含templatepartialmodule文件夹的结构。随着你的项目变得更大、更复杂,你可能想要切换到多模块文件夹结构,关于这一点,你可以在docs.phalconphp.com/en/latest/reference/applications.html中阅读。对于像基本 API 这样的简单网站,你可能想要选择一个更小的结构,比如微应用结构。你可以在docs.phalconphp.com/en/latest/reference/tutorial-rest.html中找到一个微 MVC 结构的示例。

使用.htaccess 文件

我们将在应用程序中使用.htaccess文件,对于这一步骤,你需要确保你的 Apache 服务器上启用了mod_rewrite。在许多 Apache 安装中,这个模块默认安装并启用。如果你的服务器上没有启用mod_rewrite,你需要启用它,这可能涉及运行sudo a2enmod rewrite命令或在httpd.confapache2.conf文件中取消注释LoadModule mod_rewrite.so行。这取决于你的 Apache 安装。

对于 Nginx 配置,你可以阅读本章末尾的部分,并跳过使用.htaccess文件。

现在,我们需要告诉 Apache 如何为我们提供 Phalcon 项目。首先,我们必须隐藏我们的app文件夹,使其对公众不可见,并将所有访客重定向到我们的public文件夹。因此,我们需要为项目根文件夹phalconBlog创建一个.htaccess文件。所以,打开一个文本编辑器,创建一个新文件,并在文件中插入以下代码行:

<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteRule ^$ public/     [L]
  RewriteRule (.*) public/$1 [L]
</IfModule>

将此文件保存为phalconBlog文件夹中的.htaccess。现在,所有访问phalconBlog文件夹的访客都将被重定向到public文件夹。

当涉及到在 Apache 中提供我们的public文件夹时,这并不是唯一的选择。我们可以编辑为我们的项目提供虚拟主机的 Apache 配置文件,并将文档根设置为我们的public/目录。在这种情况下,我们就不需要这个第一个.htaccess文件,只需要我们即将创建的.htaccess文件。

Phalcon 内置了美丽的 URL。换句话说,URL 可以有更吸引人的结构;例如,www.blog.com/post/1 比较于 www.blog.com/?post=1 更好。由于每次访问都必须通过我们接下来将要学习的引导文件,我们还需要为public文件夹创建一个.htaccess文件。所以,在public文件夹中创建文件,并在文件中插入以下代码行:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{REQUEST_FILENAME} !-d
  RewriteCond %{REQUEST_FILENAME} !-f
  RewriteRule ^(.*)$ index.php?_url=/$1 [QSA,L]
</IfModule>

这个.htaccess文件告诉 Apache 如果public文件夹中存在文件或文件夹,则加载它;如果不存在,则将访客重定向到index.php文件。

引导文件

我们将所有请求重定向到的index.php文件是我们应用程序的基石。在这个文件中,我们加载我们设置的任何配置,并将 Phalcon 框架的各个部分组合成一个完整的应用程序。在这个步骤中,我们将从一个简单的引导文件开始,然后在下一个步骤中使用.ini文件使其更具可配置性。

处理异常

首先,我们想要确保我们能够捕获我们的应用程序可能抛给我们的任何异常。所以,打开你的文本编辑器或 IDE,创建一个index.php文件,并将其放置在public目录中。在index.php文件中,插入以下代码片段:

<?php
  try {
    //Our code
  } catch(\Phalcon\Exception $e) {
    echo "PhalconException: ", $e->getMessage();
  }
?>

你应该注意的第一件事是我们还没有在我们的应用程序中包含任何文件,但我们仍然可以引用 Phalcon 异常。Phalcon 是一个 PHP 扩展,现在它是 PHP 安装的一部分。没有必要包含任何 PHP 文件。我们使用 PHP 的 try-catch 结构来捕获 Phalcon 可能抛出的任何异常。

依赖注入

Phalcon 使用依赖注入容器来处理您在应用程序中可能需要使用的所有服务。每当我们的应用程序需要这些服务之一时,它会通过名称从容器中请求它。Phalcon 被设计为解耦的,这意味着您可以使用其所有或部分功能,而不是必须使用特定的基础服务集。这个依赖注入容器,或 DI,是我们选择的所有服务的粘合剂。

让我们创建一个 DI。我们将使用 Phalcon 的FactoryDefault DI。所以,在index.phptry括号中,插入以下代码片段:

  try {
    //Create a DI
    $di = new Phalcon\DI\FactoryDefault();

    //Set up our views
    $di->set('view''view', function(){
      $view = new \Phalcon\Mvc\View();
      $view->setViewsDir(__DIR__ . '../app/views/');
      return $view;
    });
  } catch(\Phalcon\Exception $e) {
    echo "PhalconException: ", $e->getMessage();
  }

因此,我们已经创建了我们的 DI。接下来,我们将注册我们的视图服务,告诉 Phalcon 在哪里可以找到我们的视图文件。现在,让我们设置我们的应用程序的模型和控制器文件夹。

自动加载器

现在,我们添加我们的自动加载器。

  //Create a DI
    $di = new Phalcon\DI\FactoryDefault();

    //Set up our views
    $di->set('view', function(){
      $view = new \Phalcon\Mvc\View();
      $view->setViewsDir(__DIR__ . '../app/views/');
      return $view;
    });

    //Our autoloaders
    $loader = new \Phalcon\Loader();
    $loader->registerDirs(array(
      __DIR__ . '../app/controllers/',
      __DIR__ . '../app/models/'
    ))->register();

这将告诉 Phalcon 在哪里可以找到我们的控制器和模型文件。目前,我们在我们的博客项目中只使用两个文件夹,但如果我们需要,可以使用 Phalcon Loader 加载更多文件夹。我们还创建了libraryplugin文件夹,我们将在以后使用。

初始化我们的应用程序

现在,我们只需添加几行代码来初始化我们的应用程序以处理请求。我们创建一个Phalcon\MVC\Application的实例,并将其命名为$application。然后,我们调用$application->handle->getContent()

<?php
  try {
    //Create a DI
    $di = new Phalcon\DI\FactoryDefault();

    //Set up our views
    $di->set('view', function(){
      $view = new \Phalcon\Mvc\View();
      $view->setViewsDir(__DIR__ . '../app/views/');
      return $view;
    });

    //Our autoloaders
    $loader = new \Phalcon\Loader();
    $loader->registerDirs(array(
      __DIR__ . '../app/controllers/',
      __DIR__ . '../app/models/'
    ))->register();

    //Initialize our application
    $application = new \Phalcon\Mvc\Application($di);
    echo $application->handle()->getContent();
  } catch(\Phalcon\Exception $e) {
    echo "PhalconException: ", $e->getMessage();
  }
?>

现在我们已经将所有内容硬编码到我们的引导文件中,我们将对其进行一些更改,使我们的应用程序更加灵活。此外,我们将使用经典的.ini文件作为我们应用程序的配置文件。

使用配置文件

使用配置文件为开发者提供了一个简单的地方来编辑网站上可能在不同环境中更改的各种设置。如果您将项目从本地主机移动到另一个服务器,您只需编辑此文件,应用程序就应该可以在您放置它的任何地方运行。

您可以使用标准的 PHP 数组来存储 Phalcon 配置、JSON 文件或.ini文件格式。我们将使用.ini文件以提高可读性,但 PHP 数组格式是 PHP 的本地格式,加载速度更快。在本书的后续部分,我们将向我们的文件添加更多设置,但到目前为止,我们的项目文件夹路径看起来是.ini文件的不错补充。我们很可能不需要更改这些设置,但这给了我们将来改变主意的选项。因此,您应该在博客项目的app文件夹中创建一个新的文件夹config,并在您的编辑器中创建一个新的文件config.ini,并将其保存在这个文件夹中。在这个文件中,添加以下代码行:

[phalcon]
controllersDir = "../app/controllers/"
modelsDir = "../app/models/"
viewsDir = "../app/views/"

现在,我们有一个孤零零的.ini文件。是时候在我们的引导文件中加载它了。所以,再次打开index.php文件。我们将进行一些更改。

Phalcon\Config 组件目前可以读取 .ini 或 PHP 数组类型的配置文件。对于我们的项目,一直是 .ini 类型。在你创建 DI 之后,你将加载 .ini 文件。

//Create a DI
$di = new Phalcon\DI\FactoryDefault();

//Load ini file
$config = new \Phalcon\Config\Adapter\Ini(__DIR__ . '../app/config/config.ini');
$di->set('config', $config);

现在,我们已经加载了配置文件,但它并没有做任何事情;它只是在占用空间。是时候用我们的配置数据替换硬编码的文件路径了。唯一不能替换的是 .ini 文件本身的存储位置。

因此,我们将从我们的索引文件中移除以下代码段:

//Set up our views
$di->set('view', function(){
  $view = new \Phalcon\Mvc\View();
  $view->setViewsDir(__DIR__ . '../app/views/');
  return $view;
});

//Our autoloaders
$loader = new \Phalcon\Loader();
$loader->registerDirs(array(
  __DIR__ .'../app/controllers/',
  __DIR__ .'../app/models/'
))->register();

将其更改为以下代码片段:

//Set up our views
$di->set('view', function() use ($config) {
  $view = new \Phalcon\Mvc\View();
  $view->setViewsDir($config->phalcon->viewsDir);
  return $view;
});

//Our autoloaders
$loader = new \Phalcon\Loader();
$loader->registerDirs(array(
  $config->phalcon->controllersDir,
  $config->phalcon->modelsDir
))->register();

我们 .ini 文件中的每个标题都成为我们的 $config 对象的子对象,每个标题下的设置成为该 Phalcon 子对象中的变量。现在我们有了 .ini 文件,我们可以选择几种部署方法来确保我们的应用程序可以在我们需要放置的每个环境中运行。我们可以为开发服务器创建一个 dev.ini 文件,为生产服务器创建一个 prod.ini 文件。然后我们的引导文件可以根据 Apache 环境变量加载一个 .ini 文件。或者,我们可以使用一个构建脚本,将正确的 .ini 文件上传到正确的环境中。

Phalcon 开发者工具

现在我们已经通过艰难的方式完成了所有工作,以了解 Phalcon 的工作方式,让我们尝试以快速、简单的方式进行同样的操作。Phalcon 开发者工具是一套工具,可以帮助你通过为你生成骨架代码来快速启动你的项目。我们将在接下来的章节中使用这些工具生成很多东西,然后审查创建的文件,以了解它们是如何工作的。

如果你不想安装开发者工具,也没有问题。我们手动创建的当前骨架经过一些修改后,将作为应用程序其余部分的基础。Phalcon 是一个非常灵活的框架。我们还将详细说明使用开发者工具生成的一切,以便你可以在需要时手动创建所有代码。但我建议你在可能的情况下使用开发者工具,然后用自定义代码来填补空白。这真的可以缩短你的开发时间。首先,让我们安装这些工具。

安装 Phalcon 开发者工具

你可以从官方 Git 仓库下载 Phalcon 开发者工具,该仓库位于 github.com/phalcon/phalcon-devtools。或者,你可以使用 Composer 将其作为库安装到你的项目中。我们在项目 app 文件夹中有一个 library 文件夹的原因是,我们可以存储我们可能在应用程序中需要的 PHP 库。Composer 是一个 PHP 包管理器,它只需几个命令就可以处理这些库。你可以在 getcomposer.org/ 上了解更多关于 Composer 的信息。

首先,在library文件夹中创建一个新的文件,命名为composer.json,在其中添加以下代码行,并保存文件:

{
    "require": {
        "phalcon/devtools": "dev-master"
    }
}

这是一个简单的.json文件,告诉 Composer 我们希望在项目中包含 Phalcon 开发者工具。如果您在计算机上安装了curl,请打开终端或 SSH 窗口,浏览到位于phalconBloglibrary文件夹,并运行以下命令:

curl -s http://getcomposer.org/installer | php

在 Linux 上,您很可能已经安装了curl。如果没有,您可以直接从getcomposer.org/download/下载composer.phar并将其放入您的lib文件夹。

接下来,我们告诉 Composer 在lib文件夹中添加 Phalcon 开发者工具。

php composer.phar install

如果您的系统PATH变量中包含 PHP 可执行文件的位置,这将适用于您。如果您在 Linux 上工作,并且 PHP 是通过包管理器安装的,那么这很可能是正确的。在 Windows 上,您可能需要找到 PHP 可执行文件并将其位置添加到您的路径中。

通过 Composer 下载 Phalcon 开发者工具后,您必须将路径phalconBlog/app/library/vendor/phalcon/devtools/phalcon.php添加到您的系统PATH变量中,以便 Phalcon 命令可以正常工作。

Phalcon 也可以通过 PEAR,PHP 包管理器进行安装。为了本书的目的,我们省略了安装开发者工具的细节。涵盖每个操作系统将超出本书的范围:

生成项目骨架

使用一条命令,我们将完成本章中已经做过的几乎所有事情。打开命令行界面,浏览到您想要生成项目的位置。您实际上可以删除我们之前创建的项目。您可以重新开始,或者在新位置构建新项目。现在,输入以下命令:

phalcon project phalconBlog –-use-config-ini --enable-webtools

突然之间,我们将有一个为我们构建的完整项目结构。您将注意到生成的项目和从头开始构建的项目之间有一些差异。我们将探讨其中的一些差异,看看有什么变化。

如果您没有使用 Phalcon 开发者工具,您可能需要将您的config.ini文件更改为这个版本。此配置文件将包含一些额外的设置,如下所示:

[database]
adapter  = Mysql
host     = localhost
username = root
password = root
dbname     = phalconblog

[application]
controllersDir = ../app/controllers/
modelsDir      = ../app/models/
viewsDir       = ../app/views/
pluginsDir     = ../app/plugins/
libraryDir     = ../app/library/
baseUri        = /phalconBlog/
cacheDir       = ../app/cache/

[models]
metadata.adapter = "Memory"

它添加了一组虚拟数据库配置。它还向我们的.ini文件的应用程序部分添加了几个更多文件夹。baseUri设置可能会给您带来一些问题。如果您创建的项目在本地主机的子目录中运行,生成的设置将有效。如果您直接浏览到 localhost 并看到以下消息,您将不得不更改此baseUri设置:

生成项目骨架

您可能不会注意到baseUri设置有任何问题,直到您开始在您的应用程序中包含 CSS 和 JavaScript 文件,而这些文件没有加载。所以,如果您的网站正在主目录中运行,将那一行改为以下代码:

baseUri        = /

您还会注意到,views文件夹中有些新文件,具有.volt扩展名。在app/viewsindex文件夹中有一个index.volt文件,在appviews文件夹中还有一个index.volt文件。让我们看看位于appviews文件夹中的文件。

<!DOCTYPE html>
<html>
  <head>
    <title>Phalcon PHP Framework</title>
  </head>
  <body>
    {{ content() }}
  </body>
</html>

此文件是您整个应用程序的主要布局。您应用程序提供的每个视图都将被此文件包裹。body 标签的内容将由视图提供。这就是{{ content() }}标签的作用。这是一个 Volt 模板标签。Volt 是一个内置在 Phalcon 中的模板引擎。我们将在第三章中了解更多关于它的内容,使用 Phalcon 模型、视图和控制器

当您浏览到您的 localhost 时,所提供的服务视图位于app/viewsindex文件夹中的index.volt文件。

<h1>Congratulations!</h1>

<p>You're now flying with Phalcon. Great things are about to happen!</p>

这只是一个静态的 HTML 页面,用来告知您一切工作正常。

您还会注意到,您的引导文件或位于publicindex.php文件有所不同。现在它看起来像以下代码片段:

<?php

error_reporting(E_ALL);

try {

  /**
   * Read the configuration
   */
  $config = new \Phalcon\Config\Adapter\Ini(__DIR__ . "/../app/config/config.ini");

  /**
   * Read auto-loader
   */
  include __DIR__ . "/../app/config/loader.php";

  /**
   * Read services
   */
  include __DIR__ . "/../app/config/services.php";

  /**
   * Handle the request
   */
  $application = new \Phalcon\Mvc\Application($di);

  echo $application->handle()->getContent();

} catch (\Exception $e) {
  echo $e->getMessage();
}

它仍然从相同的位置加载我们的.ini文件。然而,现在它还从我们的config文件夹中加载两个新文件,loader.phpservices.php。其余的文件大致相同。生成的版本只是将自动加载器和依赖注入容器拆分成单独的文件,并将它们作为配置文件包含进来,这确实更有意义。首先,让我们看看位于app/configloader.php文件:

<?php

$loader = new \Phalcon\Loader();

/**
 * We're a registering a set of directories taken from the configuration file
 */
$loader->registerDirs(
  array(
    $config->application->controllersDir,
    $config->application->modelsDir
  )
)->register();

这基本上是我们以前在旧的引导文件中使用的相同代码。现在,让我们看看位于app/configservices.php文件:

<?php
use Phalcon\DI\FactoryDefault,
  Phalcon\Mvc\View,
  Phalcon\Mvc\Url as UrlResolver,
  Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter,
  Phalcon\Mvc\View\Engine\Volt as VoltEngine,
  Phalcon\Mvc\Model\Metadata\Memory as MetaDataAdapter,
  Phalcon\Session\Adapter\Files as SessionAdapter;
/**
 * The FactoryDefault Dependency Injector automatically registers the right services providing a full stack framework
 */
$di = new FactoryDefault();
/**
 * The URL component is used to generate all kind of urls in the application
 */
$di->set('url', function() use ($config) {
  $url = new UrlResolver();
  $url->setBaseUri($config->application->baseUri);
  return $url;
}, true);
/**
 * Setting up the view component
 */
$di->set('view', function() use ($config) {

  $view = new View();

  $view->setViewsDir($config->application->viewsDir);

  $view->registerEngines(array(
    '.volt' => function($view, $di) use ($config) {

      $volt = new VoltEngine($view, $di);

      $volt->setOptions(array(
        'compiledPath' => $config->application->cacheDir,
        'compiledSeparator' => '_'
      ));

      return $volt;
    },
    '.phtml' => 'Phalcon\Mvc\View\Engine\Php'
  ));

  return $view;
}, true);
/**
 * Database connection is created based in the parameters defined in the configuration file
 */
$di->set('db', function() use ($config) {
  return new DbAdapter(array(
    'host' => $config->database->host,
    'username' => $config->database->username,
    'password' => $config->database->password,
    'dbname' => $config->database->dbname
  ));
});
/**
 * If the configuration specifies the use of a metadata adapter, use it or use memory otherwise
 */
$di->set('modelsMetadata', function() {
  return new MetaDataAdapter();
});
/**
 * Start the session the first time a component requests the session service
 */
$di->set('session', function() {
  $session = new SessionAdapter();
  $session->start();
  return $session;
});

在这个文件中肯定有比我们本章能涵盖的更多内容。然而,我们将在后面的章节中简要介绍这里添加的新服务,并最终添加一些自己的服务。你可以通过以下代码行看到我们设置视图目录的位置:$view->setViewsDir($config->application->viewsDir);。但现在,我们加载的服务肯定比我们旧的引导文件要多。现在,我们有一个 URL 解析器、一个模板引擎服务、一个数据库适配器和会话服务。这些将在本书的后面部分变得很重要。

可用命令

我们将在未来的章节中探索更多命令,以生成我们应用程序所需的文件,但这里有一些命令可以帮助你开始探索这些工具能为你做什么。

在你的命令行中键入 Phalcon 将会给出如下截图所示的输出:

可用命令

commands 命令实际上会打印出这个相同的可用命令列表。我们已经查看过项目命令。我们将在接下来的章节中探索其余的命令。

然而,你可能已经注意到,当我们使用 Phalcon 开发工具构建我们的项目时,我们使用了 –enable-webtools 命令。在为我们的应用程序编写代码时,我们有一个更简单的选项;我们可以在浏览器中直接生成代码。

网络界面

一旦我们使用命令行工具创建了一个项目,我们就可以在浏览器中完成 Phalcon 开发工具能做的几乎所有其他事情。导航到 http://localhost/phalconBlog/webtools.php,你应该会看到一个如下截图所示的页面:

网络界面

我们将在下一章中了解这个神奇工具的强大功能。

IDE 框架

Phalcon 开发工具的另一个特性是它们包含了 IDE 框架,用于你的 IDE 的代码提示和自动完成功能。因为 Phalcon 是用 C 编译的,所以你的 IDE 将无法索引实际框架的类和函数,例如,与用 PHP 编写的 Zend 框架相比。IDE 框架文件反映了 Phalcon 框架中的类和函数,以便 IDE 可以访问它们的名称和文档。

在我们项目位于 app/library/vendor/phalcondevtools 文件夹中,将存在一个 ide 文件夹,这个 ide 文件夹中有 Phalcon 的各种版本的框架。要使用我们 IDE 中的当前版本框架,我们只需将项目外部库的 1.2.4 文件夹的路径添加到我们的 IDE 中。这个过程的步骤会根据你使用的 IDE 而有所不同,但你可以在 phalconphp.com/en/download/stubs 找到将 Phalcon IDE 框架添加到 PhpStorm 的具体说明。

设置 Phalcon\Debug

可选步骤是向应用程序添加Phalcon\Debug组件。添加它只需要两行代码,并且在出现错误时,它将大大帮助您追踪错误的原因。

要添加Phalcon\Debug,我们将打开我们的引导文件,位于public目录下的index.php文件,并在代码的开头添加几行代码,移除 try-catch 结构,保留try部分的所有代码不变,并添加我们的调试器。

<?php

error_reporting(E_ALL);

$debug = new \Phalcon\Debug();
$debug->listen();

/**
 * Read the configuration
 */
$config = new \Phalcon\Config\Adapter\Ini(__DIR__ . "/../app/config/config.ini");

现在,当我们遇到异常时,而不是一个标准的丑陋错误,我们将看到一个类似于以下截图的页面:

设置 Phalcon\Debug

与标准的堆栈跟踪不同,我们展示的是异常出现的代码部分,以便检查可能的问题。我们还可以点击在跟踪中列出的 Phalcon 函数,以便跳转到官方的 Phalcon 文档页面。Phalcon\Debug有很多功能。您可以通过在应用程序中故意创建异常并检查输出,或者通过阅读更多关于调试器的信息来了解更多关于它们。docs.phalconphp.com/en/latest/reference/debug.html

Nginx 配置

同样,也可以在 Nginx 上运行 Phalcon。要在 Nginx 上运行我们的项目,我们将使用以下配置,并跳过使用.htaccess文件,因为在 Nginx 中它们没有用:

server {

    listen   80;
    server_name localhost;

    index index.php index.html index.htm;
    set $root_path '/var/www/phalcon/public';
    root $root_path;

    try_files $uri $uri/ @rewrite;

    location @rewrite {
        rewrite ^/(.*)$ /index.php?_url=/$1;
    }

    location ~ \.php {
        fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
        fastcgi_index /index.php;

        include /etc/nginx/fastcgi_params;

        fastcgi_split_path_info       ^(.+\.php)(/.+)$;
        fastcgi_param PATH_INFO       $fastcgi_path_info;
        fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }

    location ~* ^/(css|img|js|flv|swf|download)/(.+)$ {
        root $root_path;
    }

    location ~ /\.ht {
        deny all;
    }
}

您只需要编辑server_name变量,如果它不是localhost,以及将$root_path变量设置为项目中public文件夹的路径。有关为 Phalcon 项目配置 Nginx 的更多详细信息,请参阅docs.phalconphp.com/en/latest/reference/nginx.html,有关配置 Cherokee 网络服务器的信息,请参阅docs.phalconphp.com/en/latest/reference/cherokee.html

摘要

在本章中,我们学习了如何设置我们的骨架应用程序。我们创建了文件夹、.htaccess文件和我们的引导文件。然后,我们通过使用.ini文件使应用程序更加可配置。最后,我们使用 Phalcon 开发者工具使整个工作变得更快、更简单。

在下一章中,当我们学习 Phalcon 的模型、视图和控制器时,我们将在浏览器中最终开始服务页面。

第三章:使用 Phalcon 模型、视图和控制器

现在,我们将开始构建一个功能性的博客应用程序。使用 Phalcon 开发者工具,我们将生成我们的模型、控制器和视图,然后详细检查代码,以便我们可以在需要时自己编写它。我们还将了解 Phalcon 中的表单,这些表单是视图辅助工具的一部分,是我们可以在应用程序中重复使用的组件。这里没有必要重新发明轮子。我们还将查看内置在 Phalcon 中的 Volt 模板引擎。

在本章中,我们将涵盖以下主题:

  • Model-View-Controller 模式的基础

  • 创建 Phalcon 模型

  • 创建 Phalcon 控制器

  • 创建 Phalcon 视图

  • 使用 Volt 模板引擎

  • 使用 Phalcon 视图辅助工具

介绍 MVC

PHP 在这些年里发生了很大的变化。最初,它甚至不是面向对象的。但 PHP 3 中添加了这一点,并且从那时起在 PHP 版本中得到了改进。这些变化为基于 PHP 的框架铺平了道路。而且,这些框架中的大多数都使用 MVC 模式,这个模式由 Python 的 Django 和 Ruby 的 Ruby on Rails 共同推广,用于 Web 框架。让我们简要回顾 MVC 模式的每个部分。

视图

视图输出用户界面。这是它的唯一任务。有时,视图包括一个模板引擎,如 Smarty 或 Phalcon 自带的内置模板引擎 Volt。在我们的博客项目中,我们将使用 Volt。但事实是,由于你可以在同一文件中混合PHPHTML,PHP 本身就像一个模板引擎。

控制器

控制器是我们应用程序的总机操作员。控制器中的操作将参数传递给视图以显示并响应用户输入。当我们在我们应用程序中填写博客表单并点击保存按钮时,我们发布的数据将通过我们的控制器发送。当我们创建博客文章时,控制器使用其createAction函数在我们模型中创建一个新的文章实例。当完成这项工作后,它向我们的视图发送一条消息,说明我们的文章发布成功。

模型

我们应用程序的大部分逻辑应该位于模型中。它应该知道我们每篇博客文章都有一个发布它的用户,并且这两者之间是相关的。它还应该知道每篇博客文章可以有一个或多个标签。如果我们有一个连接到我们应用程序的数据库,模型应该处理对数据库的更改。当我们在我们应用程序中创建一篇新文章时,我们的控制器只需将我们在表单中发布的数据传递给正确的操作。我们的模型应该处理在数据库中创建新文章记录以及与发布该文章的用户或我们添加到其中的标签相关的任何记录。

创建数据库

在我们可以让 Phalcon 开发者工具为我们生成更多文件之前,我们需要一个数据库。项目骨架是容易的部分。我们现在需要生成模型、控制器和视图文件。所以,让我们创建我们的数据库。为了本书的目的,我将给出使用原始 SQL 查询的示例,您可以通过命令行或您选择的数据库工具执行它们,以及使用 phpMyAdmin 的示例,可以从www.phpmyadmin.net免费下载。这也要求您在机器上安装 MySQL,可以从www.mysql.com免费下载。

首先,我们需要我们的数据库。以下是我们项目所需的数据库创建命令:

CREATE DATABASE phalconblog;

在 phpMyAdmin 中,您只需点击数据库标签,在页面的创建新数据库部分输入phalconblog,然后点击创建按钮,如图所示:

创建数据库

现在,我们只需要创建我们的博客文章表格。我们稍后再处理其他表格。以下是我们博客posts表的 SQL 代码:

DROP TABLE IF EXISTS `posts`;
CREATE TABLE `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` text,
  `body` text,
  `excerpt` text,
  `published` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  `pinged` text,
  `to_ping` text,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

如果您正在使用 phpMyAdmin,浏览到您的phalconblog数据库,点击SQL标签,粘贴之前的 SQL 代码。然后点击执行按钮,您的表就准备好了。

创建数据库

我们现在有一个简单的表格来存储我们的博客文章。它有一个唯一的 ID,用于自动增加标题字段,以存储我们文章的标题,一个正文字段来存储正文,一个摘要字段来存储我们文章的摘要,用于主页和分类页面等,一个发布日期字段,一个更新日期字段,以及两个字段来存储我们文章的 ping 状态。

现在我们已经有一个表格了,我们可以使用 Phalcon 开发者工具为我们的博客文章创建一个模型文件。

创建模型

如果您的项目正在运行在http://localhost/phalconBlog/webtools.php,请在浏览器中打开它。如果不是,浏览到您项目的首页,并将/webtools.php添加到 URL 中。webtools.php文件位于引导文件相同的文件夹中。您应该在浏览器中看到一个类似于以下截图的页面:

创建模型

我们将为我们的posts表生成一个模型文件。Phalcon 开发者工具使用我们在Ini文件中设置的设置连接到我们的数据库,然后加载表名选择列表中的所有表名。因此,从选择列表中选择posts表,然后点击生成按钮。您还可以选择为您的类添加设置器和获取器,为相关表设置外键,并定义关系。您还可以强制网络工具生成模型文件,即使它已经存在。

另一个选项是使用命令行开发者工具。您需要打开一个终端实例,导航到您的phalconBlog项目文件夹,并输入以下命令:

phalcon model posts

在这里,posts是正在生成模型文件的表的名称。Phalcon 开发者工具的命令行也有选项。实际上,这里提供的选项比 Web 界面上的更多。您可以通过不输入表名来调用命令来列出所有这些选项,如下所示:

phalcon model

之前的命令应该会像以下截图所示打印出选项:

创建模型

如果不输入任何选项调用任何命令,都会打印出一个类似选项的列表。

一旦完成,我们应该在app文件夹中的models文件夹中找到Posts.php文件,并在其中找到以下代码:

<?php
class Posts extends \Phalcon\Mvc\Model
{
    /**
     *
     * @var integer
     */
    public $id;     
    /**
     *
     * @var string
     */
    public $title;     
    /**
     *
     * @var string
     */
    public $body;     
    /**
     *
     * @var string
     */
    public $excerpt;     
    /**
     *
     * @var string
     */
    public $published;     
    /**
     *
     * @var string
     */
    public $updated;     
    /**
     *
     * @var string
     */
    public $pinged;     
    /**
     *
     * @var string
     */
    public $to_ping;     
}

我们经历了所有这些,但并没有得到太多,但比手动输入所有这些变量要好。Phalcon 从我们的posts表中提取了列名,创建了一个Posts model类,并将这些名称转换为我们的类中的成员变量。这是一个开始。我们也可以使用 Web 工具创建控制器。

创建控制器

您也可以使用 Web 工具创建控制器。只需在 Phalcon Web 工具菜单中点击Controllers标签,然后在表单的Controller name字段中输入您的控制器名称,在我们的例子中是Posts,如下截图所示:

创建控制器

就像我们创建的模型一样,我们可以使用以下命令行工具来创建我们的控制器:

phalcon controller posts

这将生成一个位于app/controllers的新PostsController.php文件,如下代码所示:

<?php
class PostsController extends \Phalcon\Mvc\Controller
{
    public function indexAction()
    {

    }

这个文件内容不多,只是我们的PostsController类中有一个indexAction函数。但接下来事情会变得更有趣。让我们为我们的Posts模型构建 CRUD 函数。

创建 CRUD 脚手架

CRUD 代表创建、读取、更新和删除,这是我们的应用程序应该对博客文章记录执行的四个基本功能。Phalcon Web 工具也会帮助我们构建这些功能。在 Web 工具页面上点击Scaffold标签,您将看到一个如下截图所示的页面:

创建 CRUD 脚手架

Table name列表中选择posts,从Template engine列表中选择volt,并这次勾选Force,因为我们打算强制新文件覆盖我们刚刚生成的旧模型和控制器文件。点击Generate按钮,一些魔法应该会发生。

浏览到http://localhost/phalconBlog/posts,您将看到一个如下截图所示的页面:

创建 CRUD 脚手架

我们终于有一些可以使用的功能了。我们没有帖子,但我们可以创建一些。点击 创建帖子 链接,你会看到一个类似于我们刚才所在的页面。表单看起来几乎一样,但它会有一个 创建帖子 标题。填写 标题正文摘要 字段,然后点击 保存 按钮。表单将提交,你会得到一个消息,表明帖子已成功创建。

这将带你回到帖子索引页面。现在你应该能够搜索并找到你刚刚创建的帖子。如果你忘记了你的帖子内容,你可以点击 搜索 而不输入任何字段,你应该会看到一个类似于以下截图的页面:

创建 CRUD 框架

这不是一个非常漂亮或用户友好的博客应用程序。但它让我们开始了,这正是我们所需要的。下次我们开始一个 Phalcon 项目时,只需几分钟就可以完成这些步骤。现在我们将查看生成的代码,并在查看过程中对其进行修改,使其更像博客。

检查我们的 Posts 模型

如果你打开位于 app/modelPosts.php 文件,你可能会注意到自从我们第一次生成它以来,它并没有发生变化。即使我们有新的 CRUD 框架,它仍然只包含我们的 Posts 模型类和作为成员变量的数据库列名。在 PHP 端没有涉及任何 SQL 代码。

Phalcon\MVC\Model 路径是应用程序将使用到的所有模型的基础类。所有涉及的 SQL 代码都发生在 Phalcon PHP 扩展内部。有时我们不得不进行 SQL 查询,但到目前为止,我们已经为我们的 Posts 模型提供了 CRUD 功能、搜索功能和与其他模型关联的能力,而无需知道我们的应用程序将要使用什么数据库软件。如果我们决定不再使用 MySQL 而想使用 Oracle,我们可以修改位于 app/configservices.php 文件来使用另一个数据库适配器。只要我们的数据库表结构相同,我们的应用程序代码将保持不变。

我们的 Posts 模型目前以最少的代码完成了我们需要的所有功能,所以我们不会在本章中对其进行修改。现在让我们看看我们的控制器。

检查我们的 Posts 控制器

现在,让我们在我们的编辑器中打开位于 app/controllersPostsController.php 文件。这个文件中确实有很多内容。在我们的 PostsController 类中有七个以 actions 后缀的方法。这些方法中的每一个都处理请求并创建响应。每个方法对应于我们应用程序中的一个 URI:http://localhost/phalconBlog/[model]/[action]。当我们浏览到 http://www.localhost/phalconBlog/posts/ 时,默认情况下会调用 indexAction 函数。

让我们逐一查看这些操作,并在需要时进行修改。

创建

让我们看看 createAction 函数,它为我们保存新的博客帖子。

首先,我们应该稍微了解一下Phalcon\Mvc\Dispatcher。调度器是我们依赖注入容器中加载的服务之一。调度器接收请求对象并根据该对象的属性实例化一个控制器。当控制器被实例化时,它充当调度器事件的监听器。由于依赖注入容器中的所有内容都可以作为控制器的一个属性访问,因此我们可以在控制器中使用$this->dispatcher来访问调度器。

我们依赖注入容器中我们还应该了解的一个对象是请求对象,它存储有关 HTTP 请求的详细信息,并在调度器和控制器之间传递。代码如下:

public function createAction()
{

    if (!$this->request->isPost()) {
        return $this->dispatcher->forward(array(
            "controller" => "posts",
            "action" => "index"
        ));
    }

    $post = new Posts();

    $post->id = $this->request->getPost("id");
    $post->title = $this->request->getPost("title");
    $post->body = $this->request->getPost("body");
    $post->excerpt = $this->request->getPost("excerpt");
    $post->published = $this->request->getPost("published");
    $post->updated = $this->request->getPost("updated");
    $post->pinged = $this->request->getPost("pinged");
    $post->to_ping = $this->request->getPost("to_ping");

    if (!$post->save()) {
        foreach ($post->getMessages() as $message) {
            $this->flash->error($message);
        }
        return $this->dispatcher->forward(array(
            "controller" => "posts",
            "action" => "new"
        ));
    }

    $this->flash->success("post was created successfully");
    return $this->dispatcher->forward(array(
        "controller" => "posts",
        "action" => "index"
    ));

}

首先,我们的方法通过从我们的调度器容器中查询请求对象来检查请求是否为 POST。如果请求不是 POST,则调用$this->dispatcher->forward,我们的调度器将请求转发到我们文章控制器中的indexAction函数,在那里调度循环再次启动。

如果我们正在发布一篇新的博客文章,那么该方法将继续执行,并创建一个文章对象。我们可以通过$this->request->getPost()访问我们在表单中发布的资料,并且我们的代码将把请求对象中的每个参数分配给我们的$post实例的属性。在许多情况下,用一行代码替换这一系列赋值会更简单,如下所示:

$post->save($_POST);

但我们将在本书的后面部分在我们的控制器中设置一些这些变量,所以我们将它们保持原样。由于 Phalcon 在幕后处理我们数据库的所有细节,我们只需调用$this->post->save()将文章保存到数据库;不需要 SQL。

接下来,代码检查我们的文章是否已保存,如果没有,它使用 Phalcon 内嵌的闪存消息服务,通过$this->flash->error存储错误消息,同时应用程序将我们转发回表单。如果文章成功创建,我们将被转发到索引,并带有成功消息$this->flash->success。闪存消息服务为我们应用程序提供了向用户提供操作状态通知的能力。闪存消息服务中有四种消息类型:

  • 错误

  • 成功

  • 注意

  • 警告

如果我们想在视图服务我们的消息时为它们设置自定义类,我们可以在config目录下的services.php文件中添加以下代码:

$di->set('flash', function(){
    return new \Phalcon\Flash\Direct(array(
        'error' => 'alert alert-error',
        'success' => 'alert alert-success',
        'notice' => 'alert alert-info',
    ));
});

从那时起,我们应用程序提供的任何错误消息都将同时具有 alert 和 alert-error 类。Phalcon\Flash\DirectPhalcon\Flash的一个变体,它立即输出传递给它的消息。另一个变体是Phalcon\Flash\Session,它将消息暂时存储在用户的会话中,以便在下一个请求中输出。

搜索

现在让我们看看我们的searchAction函数:

public function searchAction()
{

    $numberPage = 1;
    if ($this->request->isPost()) {
        $query = Criteria::fromInput($this->di, "Posts", $_POST);
        $this->persistent->parameters = $query->getParams();
    } else {
        $numberPage = $this->request->getQuery("page", "int");
    }

    $parameters = $this->persistent->parameters;
    if (!is_array($parameters)) {
        $parameters = array();
    }
    $parameters["order"] = "id";

    $posts = Posts::find($parameters);
    if (count($posts) == 0) {
        $this->flash->notice("The search did not find any posts");
        return $this->dispatcher->forward(array(
            "controller" => "posts",
            "action" => "index"
        ));
    }

    $paginator = new Paginator(array(
        "data" => $posts,
        "limit"=> 10,
        "page" => $numberPage
    ));

    $this->view->page = $paginator->getPaginate();
}

在我们函数的开始处,我们设置一个$numberPage变量为1。这是因为我们使用我们在引导文件中加载到 Phalcon 服务容器中的 Phalcon 分页器。我们将使用这个变量来告诉分页器要加载哪一页的结果,如果没有page参数,那么我们希望加载结果的第一页。

之后,我们的代码检查是否有 POST 请求,如果有,我们使用Phalcon\Mvc\Model\Criteria中的fromInput函数将我们的帖子参数更改为一个我们可以用来查询我们的 Posts 模型的数组。我们通过在$this->persist中创建一个变量参数来在会话中持久化这些参数,$this->persist是一个在会话之间持久化变量的 Phalcon 类。这使得我们的代码能够在不向 URL 添加参数的情况下从搜索查询中加载第二页的结果。加载搜索结果第二页所需的唯一参数是page。如果没有帖子,我们将$numberPage变量设置为 GET 请求中的page参数,使用getQuery

然后,我们设置一个本地的$parameters变量来存储我们的持久化参数,我们将使用这个变量通过find函数查询我们的 Posts 模型,并将结果存储在$posts中。然后,代码检查是否有帖子结果,并设置一个闪存消息来告诉我们当我们的posts表中没有帖子时。

然后,我们终于到达了本节开头提到的分页器。我们通过将data参数设置为$posts变量,将limit参数设置为每页我们想要的记录数,在这个例子中是10,然后设置page参数为我们的$numberPage变量来创建它。然后,我们将我们的view对象的page变量设置为分页器返回的page对象,该对象包含分页博客文章的结果。

索引

我们当前的indexAction函数没有做太多。索引视图向用户显示一个搜索表单。我们将改变这一点,因为我们希望它显示我们最新的文章列表,如下面的代码所示:

<?php

use Phalcon\Mvc\Model\Criteria,
    Phalcon\Paginator\Adapter\Model as Paginator;

class PostsController extends ControllerBase
{

    /**
     * Index action
     */
    public function indexAction()
    {
        $this->persistent->parameters = null;
    }

我们将把它改为以下代码:

public function indexAction()
    {
        $numberPage = $this->request->getQuery("page", "int", 1);

        $posts = Posts::find();

        $paginator = new Paginator(array(
            "data" => $posts,
            "limit"=> 10,
            "page" => $numberPage
        ));

        $this->view->page = $paginator->getPaginate();
    }

你会注意到我们生成的PostsController类与我们本章开头从头开始编写的类有所不同。Phalcon 开发者工具为我们生成一个ControllerBase类,这样我们就可以向其中添加通用变量和功能。然后我们可以扩展这个类来创建我们应用程序的控制器。

在这里,我们使用了我们在searchAction函数中看到的部分功能。我们使用Phalcon\Http\RequestgetQuery函数设置分页器的页面编号。第一个page参数是我们想要的 GET 变量,第二个参数是我们期望此参数的类型,例如整数,第三个参数是默认值,我们将它设置为第一页的1。然后,我们实例化分页器并将view page变量设置为我们的分页器的结果。但我们使用Phalcon\MVC\Model的静态query函数以面向对象的方式执行我们的查询,按发布日期(我们尚未设置,但我们将这样做)对帖子进行排序。我们将在下一章中了解更多关于与 Phalcon 模型和数据库交互的内容。

新增

新的动作方法中实际上没有任何内容。动作仍然被调用,包含我们的新帖子表单的视图将被提供:

public function newAction()
{

}

编辑

我们将要检查的下一个方法是我们的editAction函数:

public function editAction($id)
{

    if (!$this->request->isPost()) {

        $post = Posts::findFirstByid($id);
        if (!$post) {
            $this->flash->error("post was not found");
            return $this->dispatcher->forward(array(
                "controller" => "posts",
                "action" => "index"
            ));
        }

        $this->view->id = $post->id;

        $this->tag->setDefault("id", $post->id);
        $this->tag->setDefault("title", $post->title);
        $this->tag->setDefault("body", $post->body);
        $this->tag->setDefault("excerpt", $post->excerpt);
        $this->tag->setDefault("published", $post->published);
        $this->tag->setDefault("updated", $post->updated);
        $this->tag->setDefault("pinged", $post->pinged);
        $this->tag->setDefault("to_ping", $post->to_ping);

    }
}

你会注意到这个函数中的$id参数。在动作参数之后的任何附加 URI 参数都是动作参数。它们按照路由传递它们的顺序分配。首先,我们的动作检查请求是否为 POST。如果不是,它跳过其余的功能并加载视图。如果是 POST,我们创建我们的帖子对象的一个实例,并使用它的findFirstByid方法将我们的博客帖子加载到$post变量中。我们检查我们的$post变量是否包含数据,然后设置我们视图的 ID 变量为博客帖子的 ID。如果我们愿意,我们也可以用以下循环替换 Phalcon 开发者工具为我们生成的这些多个赋值。

Phalcon\Tag是一个视图辅助类,它将为我们的应用程序生成 HTML。通过在我们的动作中调用$this->tag->setDefault,我们可以设置具有相同名称属性的表单元素的默认值。所以,通过调用$this->tag->setDefault("id", $post->id),我们将默认 ID 表单输入设置为我们的帖子实例的 ID。

保存

现在,让我们使用以下代码检查我们的saveAction函数:

public function saveAction()
{

    if (!$this->request->isPost()) {
        return $this->dispatcher->forward(array(
            "controller" => "posts",
            "action" => "index"
        ));
    }

    $id = $this->request->getPost("id");

    $post = Posts::findFirstByid($id);
    if (!$post) {
        $this->flash->error("post does not exist " . $id);
        return $this->dispatcher->forward(array(
            "controller" => "posts",
            "action" => "index"
        ));
    }

    $post->id = $this->request->getPost("id");
    $post->title = $this->request->getPost("title");
    $post->body = $this->request->getPost("body");
    $post->excerpt = $this->request->getPost("excerpt");
    $post->published = $this->request->getPost("published");
    $post->updated = $this->request->getPost("updated");
    $post->pinged = $this->request->getPost("pinged");
    $post->to_ping = $this->request->getPost("to_ping");

        if (!$post->save()) {
            foreach ($post->getMessages() as $message) {
                $this->flash->error($message);
            }
            $this->flash->error("post was not saved");
            $this->view->pick('posts/edit');
            $this->view->post = $post;
        } else {
            $this->flash->success("post was updated successfully");
            return $this->dispatcher->forward(
                array(
                    "controller" => "posts",
                    "action" => "index"
                )
            );
        }
}

这种方法与创建方法非常相似。当我们在保存正在编辑的帖子时,会调用这个方法。这也是一个地方,如果我们愿意,我们可以用循环来替换多个赋值行,就像我们在createAction函数中所做的那样。这个方法唯一的真正区别在于,当帖子没有成功保存时,我们使用$this->view->pick()选择我们想要显示的视图,然后我们将视图中的$post变量设置为刚刚创建的那个。这样,如果新的帖子保存失败,内容仍然会保存在浏览器中。如果我们简单地将它转发到editAction函数,它将再次通过id查找帖子并清除我们的更改。我们使用相同的视图,只是绕过了动作。

删除

以下是我们控制器中的deleteAction函数:

public function deleteAction($id)
{

    $post = Posts::findFirstByid($id);
    if (!$post) {
        $this->flash->error("post was not found");
        return $this->dispatcher->forward(array(
            "controller" => "posts",
            "action" => "index"
        ));
    }

    if (!$post->delete()) {

        foreach ($post->getMessages() as $message){
            $this->flash->error($message);
        }

        return $this->dispatcher->forward(array(
            "controller" => "posts",
            "action" => "search"
        ));
    }

    $this->flash->success("post was deleted successfully");
    return $this->dispatcher->forward(array(
        "controller" => "posts",
        "action" => "index"
    ));
}

这里并没有什么特别的事情发生。该方法尝试通过调用$post->delete来删除帖子,如果没有删除则给出错误消息,如果删除了则给出成功消息。

显示

我们将不得不在我们的控制器中添加一个动作来单独查看每个帖子。这将很容易,因为我们的editAction函数做了一些我们需要的showAction函数要做的事情。我们需要我们的控制器根据帖子 ID 将帖子实例传递给我们的视图。以下是我们的代码:

public function showAction($id)
    {

        if (!$this->request->isPost()) {

            $post = Posts::findFirstByid($id);
            if (!$post) {
                $this->flashSession->error("post was not found");
                $response = new \Phalcon\Http\Response();
                $response->setStatusCode(404, "Not Found");
                $response->redirect("posts/index");          
            }

        }

        $this->view->post = $post;
    }

这个方法实际上并没有太多内容。它接收我们的帖子 ID 并检查我们的帖子是否有效。如果是,我们将view变量的帖子设置为我们的$post对象,以便我们可以在视图中使用它。但是如果没有帖子,我们至少要设置一个 404 状态码。因此,我们创建了一个新的Phalcon\Http\Response实例并设置了状态码,然后使用相同的响应,我们将被重定向回帖子索引。请注意,我们不是使用$this->flash,而是使用$this->flashSession,因为我们使用的是真正的 HTTP 重定向而不是内部转发,所以我们需要在用户的会话中存储消息。

现在既然我们的控制器正在做我们想要它做的事情,让我们来看看我们的视图。

检查我们的帖子视图

好吧,我们没有在我们的模型上做任何手动编辑,我们只对我们的控制器做了一些修改。但是,我们将对我们的视图进行大量的修改。它们需要很多工作。因此,我们不会将生成的代码与最终代码比较太多。

幸运的是,当我们在应用中安装 Phalcon 网络工具时,它为我们安装了 Twitter Bootstrap。由于它已经存在,我们将使用它。由于这是一本关于 Phalcon 框架的书,而不是 Twitter Bootstrap,我不会过多解释我们用它做了什么。我们只是使用它。Twitter Bootstrap 是一个前端框架,它使样式化更容易。您可以在getbootstrap.com/上了解更多关于它的信息。

我们从第二章,设置 Phalcon 项目中了解到,我们应用中的每个请求都使用位于app/viewsindex.volt文件来包装从视图返回的内容。如果我们想在应用中全局添加 JavaScript 或 CSS,这就是我们想要包含它的地方。代码如下:

<!DOCTYPE html>
<html>
  <head>
    <title>Phalcon Blog</title>
    <link rel="stylesheet" href="/phalconBlog/css/bootstrap/bootstrap.min.css" type="text/css" />
        <link rel="stylesheet" href="/phalconBlog/css/bootstrap/bootstrap-responsive.min.css" type="text/css" />
  </head>
  <body>
      <div class="navbar">
            <div class="navbar-inner">
                <div class="container">
                    <a data-target=".nav-collapse" data-toggle="collapse" class="btn btn-navbar">
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                        <span class="icon-bar"></span>
                    </a>
                    <a href="/phalconBlog" class="brand">Phalcon Blog</a>
                    <div class="nav-collapse">
                        <ul class="nav">
                            <li>{{ link_to("posts/", "Posts") }}</li>
                            <li>{{ link_to("posts/search", "Advanced Search") }}</li>
                            <li>{{ link_to("posts/new", "Create posts") }}</li>
                            <li><a href="/phalconBlog/webtools.php?_url=/index">Webtools</a></li>
                        </ul>
                    </div>
                </div>
            </div>
        </div>
        <div id="content" class="container-fluid">
            <div class="row-fluid">
                <div class="span3">
                    <div class="well">
                        {{ form("posts/search", "method":"post", "autocomplete" : "off", "class" : "form-inline") }}
                            <div class="input-append">
                                {{ text_field("body", "class" : "input-medium") }}
                                {{ submit_button("Search", "class" : "btn") }}
                            </div>
                       {{ end_form() }}
                    </div>
                </div>
            <div class="span9 well">
            {{ content() }}
            </div>
            </div>
        </div>
    <script src="img/jquery.min.js" type="text/javascript"></script>
        <script src="img/bootstrap.min.js" type="text/javascript"></script>
  </body>
</html>

好吧,我们添加了很多标记来支持 Twitter Bootstrap。我们在页眉中包含了我们需要使用 Bootstrap 的css文件,并在页脚中包含了 JavaScript。在navbar div 中,我们有指向我们网站主要部分的链接。我们在侧边栏中有一个搜索表单,目前它只搜索正文字段,但我们将稍后更改它。然后,我们输出我们的内容。我们现在使用 Phalcon 自带的内置 Volt 模板引擎为我们的博客提供了一个响应式布局。

Volt 使用{{…}}将变量打印到模板中,使用{%…%}分配变量或执行循环。Volt 中的变量和函数调用与底层 PHP 代码非常相似,只是没有美元符号。在我们的页面主体部分,我们使用content(),这是一个代表$this->getContent()的模板标签。这个标签在这里用于主布局。但您也会在控制器布局以及动作视图中注意到它。每个视图的输出都通过层次结构中的下一个视图来提供,使用这个变量。

在这个视图层次结构中,有三个级别。动作视图仅在调用特定控制器的动作时渲染。控制器布局视图将在控制器的每个动作中显示。最后,主布局视图将在应用程序的每个控制器和动作中显示。

对于链接,我们使用link_to()标签,它代表Phalcon\TaglinkTo视图助手函数。第一个参数是我们应用程序中的路径,第二个参数是我们将用于链接的锚文本。

对于搜索表单,我们使用form()模板标签,它利用 Phalcon 的视图助手构建了我们表单的第一个 HTML 表单标签。为了关闭表单,我们使用 Volt 的end_form()模板标签。我们传递给这个标签的第一个参数是 action 属性。第二个参数是一组键值对,这些键值对将转换为 HTML 表单标签的属性。我们给我们的字段添加了input-medium类来应用 Twitter Bootstrap 样式。

对于搜索表单字段,我们再次使用另一个代表 Phalcon 视图助手的 Volt 标签。text_field()标签接受我们字段的名称作为其第一个参数。第二个参数再次是一个键值对,它转换为 HTML 标签属性。我们给我们的提交按钮添加了btn类。

Posts 布局

包裹 Posts 控制器中每个动作结果的布局位于app/views/layouts目录下的posts.volt文件中。这个文件中内容不多。如果我们想为我们的 Posts 模型使用的每个视图添加标记,我们就会在这里做。我们将从div中移除align="center"属性,使其看起来像以下代码:

<div>
    {{ content() }}
</div>

索引动作视图

索引动作视图位于app/views/posts目录下的index.volt文件中。这个页面最初包含一个搜索表单。但我们将控制器中的indexAction函数更改为列出最新的博客文章。我们将不得不对这个文件进行很多修改,这实际上将导致整体标记更少,如下所示:

{{ content() }}

{% if page.items is defined %}
{% for post in page.items %}
    <article>
        <h2>{{ post.title }}</h2>
        <div>{{ post.excerpt }}</div>
        <div>{{ link_to("posts/show/"~post.id, "Show") }}</div>
    </article>
{% endfor %}
<ul class="pager">
    <li>{{ link_to("posts/index", "First") }}</li>
    <li>{{ link_to("posts/index?page="~page.before, "Previous") }}</li>
    <li>{{ link_to("posts/index?page="~page.next, "Next") }}</li>
    <li>{{ link_to("posts/index?page="~page.last, "Last") }}</li>
    <li>{{ page.current~"/"~page.total_pages }}</li>
</ul>
{% endif %}

在 Volt 中,我们使用{%...%}来表示循环和控制结构。在这个视图中,我们将一切包裹在一个if标签中,该标签检查我们控制器中分页器生成的page对象是否包含任何项目。page对象中的结果存储在这个item变量中。如果有项目,我们就遍历它们,将post对象设置为找到的内容。我们可以使用点符号访问这个post对象中的变量。要打印帖子摘要,我们使用post.excerpt

最后,我们有了分页器的分页部分。除了项目外,我们的分页器对象还包含有关结果的信息。我们可以使用page.before来访问当前页的页码,page.next用于下一页的页码,past.last用于最后一页。在链接的末尾,我们打印当前页码和总页数。

搜索动作视图

现在,我们将查看位于app/views/posts目录下的search.volt文件中的搜索动作视图。代码如下:

{{ form("posts/search", "method":"post") }}
{{ content() }}

<div align="center">
    <h1>Search Posts</h1>
</div>

<table align="center">
    <tr>
        <td align="right">
            <label for="id">Id</label>
        </td>
        <td align="left">
            {{ text_field("id") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="title">Title</label>
        </td>
        <td align="left">
                {{ text_field("title") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="body">Body</label>
        </td>
        <td align="left">
                {{ text_field("body") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="excerpt">Excerpt</label>
        </td>
        <td align="left">
                {{ text_field("excerpt") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="published">Published</label>
        </td>
        <td align="left">
            {{ text_field("published", "size" : 30) }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="updated">Updated</label>
        </td>
        <td align="left">
            {{ text_field("updated", "size" : 30) }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="pinged">Pinged</label>
        </td>
        <td align="left">
                {{ text_field("pinged", "type" : "date") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="to_ping">To Of Ping</label>
        </td>
        <td align="left">
                {{ text_field("to_ping", "type" : "date") }}
        </td>
    </tr>

    <tr>
        <td></td>
        <td>{{ submit_button("Search", "class" : "btn") }}</td>
    </tr>
</table>

{{ end_form() }}

{% if page.items is defined %}
{% for post in page.items %}
    <article>
        <h2>{{ post.title }}</h2>
        <div>{{ post.excerpt }}</div>
        <div>{{ link_to("posts/show/"~post.id, "Show") }}</div>
    </article>
{% endfor %}
<ul class="pager">
    <li>{{ link_to("posts/search", "First") }}</li>
    <li>{{ link_to("posts/search?page="~page.before, "Previous") }}</li>
    <li>{{ link_to("posts/search?page="~page.next, "Next") }}</li>
    <li>{{ link_to("posts/search?page="~page.last, "Last") }}</li>
    <li>{{ page.current~"/"~page.total_pages }}</li>
</ul>
{% else %}
{{ form("posts/search", "method":"post", "autocomplete" : "off") }}
{% endif %}

在这里,我们看到了在其他视图中看到的大部分内容。表单有更多的字段,这样我们就可以搜索我们帖子中的每个字段。然后,我们有一个循环来打印搜索结果,类似于索引动作视图,最后,我们有我们的分页器来浏览结果。

编辑动作视图

接下来,我们有位于app/views/posts目录下的edit.volt文件中的编辑动作视图。

{{ form("posts/edit", "method":"post") }}

{{ content() }}

<div align="center">
    <h1>Edit Post</h1>
</div>

<table>
    <tr>
        <td align="right">
            <label for="title">Title</label>
        </td>
        <td align="left">
                {{ text_field("title") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="body">Body</label>
        </td>
        <td align="left">
                {{ text_area("body") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="excerpt">Excerpt</label>
        </td>
        <td align="left">
                {{ text_area("excerpt") }}
        </td>
    </tr>
    <tr>
        <td>{{ hidden_field("id") }}</td>
        <td>{{ submit_button("Save", "class" : "btn") }}</td>
    </tr>
</table>

</form>

在这里,我们再次有content标签和form标签,用于编辑我们帖子中需要编辑的所有字段。最后,我们有我们的提交按钮;我们将需要编辑帖子的一切。

新动作视图

最后但同样重要的是,位于app/views/posts目录下的new.volt文件中的新动作视图,它使用了我们在其他动作视图中介绍过的相同模板标签。我们视图的代码如下:

{{ form("posts/create", "method":"post") }}

{{ content() }}

<div align="center">
    <h1>Create Post</h1>
</div>

<table>
    <tr>
        <td align="right">
            <label for="title">Title</label>
        </td>
        <td align="left">
                {{ text_field("title", "type" : "date") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="body">Body</label>
        </td>
        <td align="left">
                {{ text_area("body", "type" : "date") }}
        </td>
    </tr>
    <tr>
        <td align="right">
            <label for="excerpt">Excerpt</label>
        </td>
        <td align="left">
                {{ text_area("excerpt", "type" : "date") }}
        </td>
    </tr>
    <tr>
        <td></td>
        <td>{{ submit_button("Save", "class" : "btn") }}</td>
    </tr>
</table>

</form>

摘要

在这一章中,我们为博客中的帖子工作在模型、视图和控制器。为此,我们使用 Phalcon 网络工具为我们生成 CRUD 脚手架。然后,我们修改了生成的代码,使其能够完成我们需要的任务。我们现在可以添加帖子。我们还了解了 Volt 模板引擎。

在下一章中,我们将创建更多模型,并查看 Phalcon 处理数据的各种方式,包括会话数据、模型之间的关系、数据过滤和清理、PHQL 以及 Phalcon 的对象-文档映射器。

第四章:在 Phalcon 中处理数据

除非你正在构建一个通过 API 检索所有数据的程序,否则你很可能需要在应用程序中有一个数据库。Phalcon 为你提供了几种从 PHP 访问数据的方法。在前一章中,我们没有直接与我们的 MySQL 数据库交互。我们可以创建博客文章并将它们存储在我们的数据库中,但 Phalcon 模型为我们做了所有的脏活。我们所做的只是将数据库连接详情添加到我们的配置文件中。

在我们开始使用更多可用的 Phalcon 功能之前,我们的模型可以处理一些更多功能。但是,即使是最简单的应用程序也会发展到需要独特的数据库查询的程度。然而,数据库并不是在应用程序中处理数据的唯一方式。

在本章中,你将学习以下主题:

  • 如何使用 Phalcon 模型的更高级功能

  • 如何在 Phalcon 应用程序中存储会话数据

  • 如何过滤和清理我们的数据

  • 如何将我们的 MySQL 数据库替换为另一个数据库系统

  • 如何使用 Phalcon 查询语言(PHQL)访问数据库

  • 如何使用 Phalcon 的对象-文档映射器(ODM)

  • 如何处理数据库迁移

添加模型

目前,我们的博客应用程序并没有做什么。我们可以创建和查看帖子,但仅此而已。我们需要考虑在我们的博客中还需要哪些其他表。

创建数据库表

我们将会有用户,并且每个用户都将能够写多篇帖子。因此,我们需要一个users表,并且我们需要在我们的posts表中添加一个users_id字段,以便每篇帖子都与一个用户相关联。以下是我们users表的 SQL 代码:

CREATE TABLE IF NOT EXISTS `phalconblog`.`users` (
  `id` INT(11) NOT NULL AUTO_INCREMENT ,
  `username` VARCHAR(16) NOT NULL ,
  `password` VARCHAR(255) NOT NULL ,
  `name` VARCHAR(255) NOT NULL ,
  `email` TEXT NOT NULL ,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`)
);

在你的 MySQL 数据库上运行这个 SQL 片段。如果你需要使用phpMyAdmin的帮助,请参考第三章,使用 Phalcon 模型、视图和控制器,了解如何执行此代码。现在,你有一个可以存储关于你的用户的最小数据的表,包括他们选择的用户名和密码、他们的姓名和他们的电子邮件地址。我们在username列上创建了一个唯一键,因为我们不希望有两个用户使用相同的用户名。

此外,一个没有评论的博客实际上并不算是一个真正的博客。我们希望访客能够对我们的博客应用进行评论。以下是我们需要在数据库上运行的 SQL 代码,以创建我们的评论空间:

CREATE TABLE IF NOT EXISTS `comments` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `post_id` int(11) NOT NULL,
  `body` text NOT NULL,
  `name` text NOT NULL,
  `email` text NOT NULL,
  `url` text NOT NULL,
  `submitted` datetime NOT NULL,
  `publish` tinyint(1) NOT NULL,
  `posts_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_comments_posts1` (`posts_id`)
);

我们需要将每个评论与特定的帖子相关联,所以我们有一个post_id字段。为了存储评论者的详细信息,我们有nameemailurl。我们有一个submitted日期,这样我们就可以对评论进行排序。此外,我们有一个publish字段,当评论提交时我们将它设置为0,当我们批准评论时设置为1

我们可能需要组织我们的帖子。我们有几种选择来做这件事。我们可以使用分类,每个帖子都必须适合特定的分类。我们的分类可以有子分类,或者我们可以使用标签,每个标签可以自由地应用于任何帖子,每个帖子也可以有多个标签。或者我们可以两者都做,就像大多数博客应用一样。对于我们的应用,我们将为了简单起见只使用标签。运行以下 SQL 代码来创建tags表:

CREATE TABLE IF NOT EXISTS `tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tag` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `tag` (`tag`)
);

这个表没有太多内容,只有一个idtag列。这是因为由于帖子与标签之间存在多对多关系,我们必须使用一个中间表来将我们的posts表与tags表相关联。然而,我们确实希望每个标签都是唯一的,所以我们把tag列设为唯一键。我们将把这个表称为post_tags,以下是你需要运行的 SQL 代码来创建它:

CREATE TABLE IF NOT EXISTS `post_tags` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `posts_id` int(11) NOT NULL,
  `tags_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `fk_post_tags_tags1` (`tags_id`),
  KEY `fk_post_tags_posts1` (`posts_id`)
);

这只是一个 ID 表。这个表中的每条记录都将有一个唯一的 ID,一个帖子 ID 和一个标签 ID。

现在,我们需要修改我们的posts表。它需要一个用户 ID。以下是我们需要运行的 SQL 代码来删除并重新创建我们的posts表:

DROP TABLE `posts`;
CREATE TABLE IF NOT EXISTS `posts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `users_id` int(11) NOT NULL,
  `title` text,
  `body` text,
  `excerpt` text,
  `published` datetime DEFAULT NULL,
  `updated` datetime DEFAULT NULL,
  `pinged` text,
  `to_ping` text,
  PRIMARY KEY (`id`),
  KEY `fk_posts_users` (`users_id`)
);

我们流程的最后一个步骤是在我们的表之间创建外键关系。运行以下 SQL 代码来向我们的数据库添加外键约束:

ALTER TABLE `comments`
  ADD CONSTRAINT `fk_comments_posts1` FOREIGN KEY (`posts_id`) REFERENCES `posts` (`id`);
ALTER TABLE `posts`
  ADD CONSTRAINT `fk_posts_users` FOREIGN KEY (`users_id`) REFERENCES `users` (`id`);
ALTER TABLE `post_tags`
  ADD CONSTRAINT `fk_post_tags_tags1` FOREIGN KEY (`tags_id`) REFERENCES `tags` (`id`),
  ADD CONSTRAINT `fk_post_tags_posts1` FOREIGN KEY (`posts_id`) REFERENCES `posts` (`id`);

以下截图表示显示我们数据库中关系的图:

创建数据库表

上述截图是从 MySQL Workbench 中的 EER 图工具生成的,您可以在www.mysql.com/products/workbench/免费下载此工具。这个工具对于设计数据库来说非常实用。我用拖放和填写空白的方式创建了所有这些表格和关系。

生成模型

现在,让我们使用 Phalcon 网络工具一次性创建所有我们的新模型。浏览到http://localhost/phalconBlog/webtools.php?_url=/models/index,你会看到以下屏幕:

生成模型

这次,我们将选择表名所有,并确保我们检查设置外键定义关系强制。这样,网络工具就会在我们的模型中设置必要的关系。这将在位于app目录下的models文件夹中创建以下文件:

  • Comments.php

  • PostTags.php

  • Tags.php

  • Users.php

在我们继续重构本章剩余部分的博客项目时,我们将查看这些文件。但是,为了更好地理解我们刚才所做的工作,让我们看看 Phalcon 是如何处理关系的。Phalcon 模型管理器处理模型之间的关系,并且这些关系必须在这些模型的initialize函数中设置。通过让 Phalcon 网络工具定义关系,我们可以简单地让它为我们生成initialize函数。

Users.php文件的initialize函数中,你会找到以下代码行:

$this->hasMany("id", "Posts", "users_id");

这行代码设置了应用中文章和用户之间的关系。一个用户将拥有多个文章。这是四种方法之一。其他三种方法如下:

  • hasOne()

  • belongsTo()

  • hasManyToMany()

第一个参数是本地模型中作为关系键的字段。第二个参数是被引用的模型。第三个参数是远程模型的键。由于文章属于用户,我们将在Posts模型的initialize函数中找到以下代码行:

$this->belongsTo("users_id", "Users", "id");

对于大部分情况,你可以忘记数据库中的关系和外键,只需将你的记录当作对象来处理。

在 Phalcon 中存储会话数据

首先,我们将赋予自己登录博客并存储用户信息到会话中的能力。我们将等到下一章再加密密码和设置用户权限。现在,我们只想在用户保存博客文章时保存用户 ID。

我们对模型进行了更改,但这并不意味着一切都会正常工作。浏览到http://localhost/phalconBlog/posts/create并尝试创建一个新的帖子。你将无法做到。相反,你将在浏览器中看到以下错误消息,因为我们刚刚创建了一个规则,要求我们的数据库中的每个帖子都必须有一个用户 ID:

在 Phalcon 中存储会话数据

另一个问题是为了有一个用户 ID,我们需要在users表中有一个用户来引用。因此,我们需要能够使用我们的应用程序向数据库中添加用户。所以,回到http://localhost/phalconBlog/webtools.php?_url=/scaffold并为你用户的表生成脚手架。我们将从中删除很多内容并更改一些东西,但同样,这是一个好的开始。

一旦你为你用户的表生成了脚手架,浏览到http://localhost/phalconBlog/users/new并创建你的用户,如下面的截图所示:

在 Phalcon 中存储会话数据

现在我们有了用户,让我们确保在创建新的博客文章时,用户 ID 与之相关联。我们将在用户的会话中设置一个user_id变量。首先,我们需要一个登录表单,并且我们可以将它放在indexAction函数的模板中,这样我们就可以在那里登录。打开位于app/views/usersindex.volt文件,这是使用 Phalcon Web 工具生成的,并修改它以看起来像以下代码片段:

{{ content() }}

{{ form("users/login", "method":"post", "autocomplete" : "off") }}

<h1>Users</h1>

<div>
    <label for="username">Username</label>
    {{ text_field("username", "size" : 30) }}
</div>

<div>
    <label for="password">Password</label>
    {{ password_field("password", "size" : 30) }}
</div>

{{ submit_button("Login", "class" : "btn") }}

{{ end_form() }}

我们剩下的是一个usernamepassword字段以及一个提交按钮。我们还修改了网络工具为我们构建的原始搜索表单,将其提交到一个我们控制器中尚未存在的动作,即loginAction函数。

现在,让我们在位于app/controllersUsersController.php文件中创建一个loginAction函数。我们将添加的方法是以下代码片段:

public function loginAction() {

    if ($this->request->isPost() && isset($_POST('username') && isset($_POST('password')) {
        $query = Criteria::fromInput($this->di, "Users", $_POST);
        $this->persistent->parameters = $query->getParams();
        $parameters = $this->persistent->parameters;
        if (!is_array($parameters)) {
            $parameters = array();
        }
        $users = Users::findFirst($parameters);
        if (count($users) == 1) {
            $user = $users->getFirst();
            $this->session->set("user_id", $user->id);
            $this->flash->success("Welcome " . $user->name);
        } else {
            $this->flash->error("Username and Password combination not found");
        }
    }
    return $this->dispatcher->forward(
        array(
            "controller" => "posts",
            "action" => "index"
        )
    );
}

这个方法很简单。它检查是否存在POST变量,如果存在,则将$parameters设置为POST变量。然后,我们将$users变量设置为 Phalcon 模型Find方法的结果。由于我们在数据库中将username列设置为唯一键,我们应该得到一条或没有记录。如果没有记录,我们设置一个闪存消息告诉用户他们输入的内容不正确。如果得到一条记录,我们调用$users->getFirst()从我们的查询中获取第一条也是唯一的结果。然后,我们将会话变量user_id设置为我们的$user对象中的id实例。我们在app/config中的services.php文件加载时启动了会话,因此它在这个控制器中可用。

在我们打开此文件时,我们还应该创建一个注销动作。这个方法甚至更容易编写。我们只需调用$this->session->remove($var),其中$var是我们想要从会话中删除的变量。以下是代码的示例:

public function logoutAction(){
        $this->session->remove("user_id");
        $this->flash->success("You have been logged out");
        return $this->dispatcher->forward(
            array(
                "controller" => "users",
                "action" => "index"
            )
        );
    }

我们将对这个文件进行最后一次修改。我们已经设置了indexAction函数的视图,以便在调用indexAction函数时显示登录表单。但是,如果用户已经登录,让我们显示用户列表。为此,我们将修改indexAction函数,使其看起来像以下代码片段:

public function indexAction() {
        $this->persistent->parameters = null;
        if ($this->session->has("user_id")) {
            return $this->dispatcher->forward(
                array(
                    "controller" => "users",
                    "action" => "search"
                )
            );
        }
    }

在这里,我们简单地检查会话中是否存在用户 ID,如果存在,则将用户转发到搜索控制器,该控制器在未提交搜索参数时显示所有用户的列表。

在我们设置此用户 ID 之前,我们仍然无法创建帖子。因此,打开位于app/controllersPostsController.php文件。我们只需在创建博客帖子时设置此 ID,因此我们只将修改createAction函数。在我们使用$post = new Posts()实例化新帖子之前,我们将添加以下代码片段:

if (!$this->session->has("user_id")) {
            $this->flash->error("Please log in to create a post");
            return $this->dispatcher->forward(
                array(
                    "controller" => "users",
                    "action" => "index"
                )
            );
        }

要检查会话中是否包含用户 ID,请将用户转发到登录页面。如果存在 ID,代码将创建新帖子,我们需要在post对象中添加以下代码行来添加我们的用户 ID:

$post->users_id = $this->session->get("user_id");

最后,我们需要一种注销的方法。我们已经创建了action方法,但没有方法可以访问它。因此,打开位于app/views/userssearch.volt文件,并在{{ content() }}标签之后添加以下代码片段:

{{ link_to("users/logout/", "Logout", "class" : "btn") }}

这将在用户列表顶部添加一个按钮,用于注销我们。现在,我们可以无任何问题地登录并创建帖子。

过滤和清理数据

为了防止未经授权的访问、SQL 注入和其他针对我们应用程序的恶意攻击,我们需要过滤和清理用户输入。我们可以使用Phalcon\Filter组件来为我们完成这项工作。此组件为 PHP 过滤器扩展提供了包装器。

保护您的应用程序超出了本书的范围,但我们可以看看当我们将用户字段之一命名为电子邮件时,Phalcon 开发工具已经为我们添加的一个过滤器。在位于app/controllerUsersControllers.php文件中,您会发现createAction函数有以下代码行:

$user->email = $this->request->getPost("email", "email");

过滤器对象是通过 Phalcon 请求对象访问的。第一个参数是我们正在访问的变量名,第二个可选参数是我们的过滤器。Phalcon 有以下内置过滤器:

名称 描述
string 移除标签
email 移除所有字符,除了字母、数字和特殊字符,如!、#、$、%、&、*、+、-、/、=、?、^、_、'、{、
int 移除所有字符,除了数字、点、加号和减号
float 移除所有字符,除了数字、点、加号和减号
alphanum 移除所有字符,除了 a-z、A-Z 和 0-9
striptags 应用strip_tags函数
trim 应用trim函数
lower 应用strtolower函数
upper 应用strtoupper函数

您也可以编写自己的过滤器。您可以在docs.phalconphp.com/en/latest/reference/filter.html#creating-your-own-filters了解更多相关信息。

重新审视 Phalcon 模型

我们还需要在我们的应用程序中修复的另一件事是,我们的日期没有任何意义。在我们的posts表中,我们发布了和更新了日期,但我们没有使用。让我们确保我们设置了这些日期。让我们打开位于app/modelPosts.php文件。我们将在我们的帖子模型initialize函数中添加更多代码,并且我们将使用日期字段的行为。首先,在第一个 PHP 标签之后添加以下代码行:

use Phalcon\Mvc\Model\Behavior\Timestampable;

然后,将以下代码行添加到该模型的initialize函数中:

        $this->addBehavior(new Timestampable(
            array(
                'beforeCreate' => array(
                    'field' => 'published',
                    'format' => 'Y-m-d H:i:s'
                )
            )
        ));
        $this->addBehavior(new Timestampable(
            array(
                'beforeUpdate' => array(
                    'field' => 'updated',
                    'format' => 'Y-m-d H:i:s'
                )
            )
        ));

我们可以使用Timestamable行为来为我们处理发布的和更新的日期设置。第一个参数是我们附加此行为的活动,下一个参数是字段,第三个是日期格式。因此,当创建新记录时,发布的日期将被设置,当更新时,更新的日期也将被设置,而无需在控制器中修改数据时手动设置日期。

现在,让我们给我们的帖子添加标签。首先,我们需要回到浏览器中的 Phalcon Web 工具或命令行中的 Phalcon Developer Tools,并生成tags表和post_tags表的模型。然后,我们重新生成我们的posts表的模型。确保选择设置外键定义关系。对于posts表,您将不得不设置强制选项。

打开您新生成的文件。您可以在位于app目录下的models文件夹中找到它们。在Posts.php文件的底部,您将找到以下新代码:

public function initialize() {
    $this->hasMany("id", "PostTags", "posts_id", NULL);
    $this->belongsTo("users_id", "Users", "id", array("foreignKey" => true));
  }

Tags.php文件的底部,您也将找到一个initialize函数:

public function initialize() {
        $this->hasMany("id", "PostTags", "tags_id", NULL);
    }

PostTags.php文件的底部,您将找到以下代码行:

public function initialize() {
        $this->belongsTo("posts_id", "Posts", "id");
        $this->belongsTo("tags_id", "Tags", "id");
    }

这些模型initialize函数中的代码基于我们在数据库中创建的外键将这些模型关联起来,并允许我们以对象的形式创建、读取、更新和删除我们的记录。然而,我们仍然需要编写一些代码来实际保存我们保存帖子时的标签。

接下来,让我们在Posts.php文件中创建一个新的方法。

public function addTags($tags) {
        foreach ($tags as $t) {
            $t = trim($t);
            $tag = Tags::findFirst(array("tag = '$t'"));
            if (!$tag) {
                $tag = new Tags();
                $tag->tag = $t;
                $tag->save();
            }
            $postTag = PostTags::findFirst(
                array(
                    "conditions" => "$this->id = ?1 AND tags_id = ?2",
                    "bind" => array(
                        1 => $this->id,
                        2 => $tag->id
                    )
                )
            );
            if (!$postTag) {
                $postTag = new PostTags();
                $postTag->posts_id = $this->id;
                $postTag->tags_id = $tag->id;
                $postTag->save();
            }
            unset($tag);
            unset($postTag);
        }

我们的功能接受一个标签数组作为第一个参数,以及一个帖子的 ID 作为第二个参数。在这里,我们遍历标签。使用findFirst,我们检查标签是否已经存在。在这个例子中,我们使用一个数组,它简单地保存了我们 SQL 查询的WHERE子句。如果存在标签,它将被加载到$tag变量中。如果没有,$tag将为 false,然后我们创建一个新的标签并保存它。接下来,我们检查post_tag表中是否有记录将当前标签与帖子 ID 相关联。如果有,我们加载它,如果没有,我们创建它并保存它。

我们已经处理了模型,所以让我们转到控制器。打开位于app/controllers目录下的PostsController.php文件。在createActionsaveAction函数中找到以下代码行:

$success = $post->save;

这是在帖子创建或编辑后被保存到数据库的点。现在,$post对象将附加一个 ID。在每个帖子之后,添加以下代码行:

  $tags = explode(",", $this->request->getPost("tags", "lower"));
  $post->addTags($tags);

在这里,我们假设当我们开始创建我们的表单时,我们将接受以逗号分隔的标签。因此,我们将这个标签字符串转换成一个数组,使用explode在将字符串设置为小写字母后进行,以确保更多的唯一性。然后,我们使用刚刚添加到我们的Post模型中的addTags方法来将标签保存到我们的数据库中。

现在,我们需要修改editAction函数,以便我们可以将标签发送到我们的编辑表单。在该函数中找到以下代码行:

$this->view->id = $post->id;

在它之后添加以下代码:

 $tagArray = array();
 foreach ($post->postTags as $postTag) {
       $tagArray[] = $postTag->tags->tag;
 }
$this->tag->setDefault("tags", implode(",", $tagArray));

现在我们只需要修改我们的帖子视图。位于app/view/post目录下的edit.voltnew.volt文件只需要在表格底部,在按钮标记之前添加一个用于标签的字段。

<tr>
        <td align="right">
            <label for="tags">Tags</label>
        </td>
        <td align="left">
                {{ text_field("tags") }}
        </td>
    </tr>

在位于app/view/post目录下的show.volt文件中找到以下代码行:

   {{ post.body }}

在之前的代码片段下方插入以下代码片段:

 <div>
    {% for posttag in post.postTags %}
        {{ posttag.tags.tag }},
    {% endfor %}
 </div>

现在,我们可以浏览到http://localhost/phalconBlog/posts/new并创建一个带有标签的帖子。

使用 PHQL

Phalcon 查询语言PHQL)是一种高级 SQL 方言,它为 Phalcon 支持的数据库系统标准化 SQL 查询。然而,这并不是 PHQL 的唯一特性。它还具有以下特性:

  • 它使用绑定参数来保护你的应用程序

  • 它将表视为模型,字段视为类属性

  • 它允许数据操作语句以防止数据丢失

  • 它只允许每次调用一个查询,以防止 SQL 注入

我们将在侧边栏的搜索表单中使用 PHQL。目前,它实际上相当无用。我们将将其简化,只搜索正文字段,以便事情变得简单。现在,我们将让它工作得更好。我们可以通过不仅搜索正文,还搜索标题和摘要,使这个搜索字段的价值得到提升。因此,我们需要编辑位于app/controllers目录下的PostsController.php文件中的searchAction函数。以下代码片段告诉我们如何编辑文件:

if ($this->request->isPost()) {
            //$query = Criteria::fromInput($this->di, "Posts", $_POST);
            //$this->persistent->parameters = $query->getParams();
            $this->persistent->parameters = $this->request->getPost();
        } else {
            $numberPage = $this->request->getQuery("page", "int");
        }

        $parameters = $this->persistent->parameters;
        if (!is_array($parameters)) {
            $parameters = array();
        }
        //$parameters["order"] = "id";
        $query = $parameters['body'];

        //$posts = Posts::find($parameters);
        $phql = "SELECT * FROM Posts WHERE body LIKE '%$query%' OR
            excerpt LIKE '%$query%' OR title LIKE '%$query%' ORDER BY id";
        $posts = $this->modelsManager->executeQuery($phql);

在之前的代码片段中已被注释掉的代码行是旧代码。代码的重要部分在末尾,我们在这里创建 PHQL 查询。注意,我们不是从表中选择,而是从实际模型中选择。然后,我们将$posts变量设置为$this->modelsManager->executeQuery()返回的结果。modelsManager是我们依赖注入容器中加载的服务。

PHQL 还可以帮助我们处理连接操作。由于我们已经定义了模型之间的关系,我们可以使用以下 PHQL 查询来获取带有用户名称的帖子列表:

SELECT Posts.title, Users.name FROM Posts JOIN Users ORDER BY Users.name

注意,我们不需要指定连接操作的条件。在这里,假设了一个INNER JOIN操作,但可以使用任何类型的连接。

如果你不想编写 PHQL 语句,可以使用一个查询构建器,它类似于其他 PHP 框架(如 Zend 和 Yii)中的查询构建器。我们可以用以下几行代码构建之前的查询,并执行它并返回记录:

$posts = $this->modelsManager->createBuilder()
    ->from('Posts')
    ->join('Users')
    ->orderBy('Users.name')
    ->getQuery()
    ->execute();

PHQL 是一种强大的语言,本书和应用程序的范围并不能真正体现其价值。要了解更多关于 PHQL 的知识,超出本书所能教授的内容,请访问docs.phalconphp.com/en/latest/reference/phql.html

在 Phalcon 中切换数据库

我们在我们的应用程序中使用了 MySQL 数据库。如果我们想在中途更改数据库软件,只要我们的新数据库中有相同的数据结构,这并不会太难。目前,Phalcon 支持以下关系型数据库后端:

我们的应用程序通过在app/config目录下的services.php文件中导入它来设置数据库适配器,如下所示:

use Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;

如果我们想使用不同的数据库系统,我们只需在这里包含不同的适配器。每个适配器处理其相应数据库的大部分专有特性。

Phalcon 的对象-文档映射器和 MongoDB

在 Phalcon 中,你不仅可以使用关系数据库,还可以使用 Phalcon 的对象-文档映射器ODM)连接到 MongoDB。Phalcon 的 ODM 提供了 CRUD 功能、验证、事件和其他有用的服务。

你可以通过以下代码片段将 MongoDB 数据库的连接添加到你的依赖注入容器中:

$di->set('mongo', function() {
    $mongo = new Mongo();
    return $mongo->selectDb("blog");}, true);

这将是一个连接到默认的 localhost MongoDB 实例的连接,该实例在默认端口上运行。

我们现在可以像在应用程序使用标准关系数据库时添加模型一样添加模型,但在这里,我们扩展了\Phalcon\Mvc\Collection

class Pages extends \Phalcon\Mvc\Collection{

}

真的,在你的模型文件中你只需要这些。这假设我们将使用名为pages的集合在我们的blog数据库中存储数据。这可能会有些令人困惑,但使用小写名称作为 MongoDB 集合的标准约定。这个类具有与 MongoDB PHP 扩展相同的函数。因此,如果我们想通过 ID 查找页面,我们将使用相同的函数名。

$page = Pages::findById("5087358f2d42b8c3d15ec4e2");

每个集合都是一个具有 MongoDB 记录灵活性的对象。一旦你有了page对象,就可以对其进行修改并再次保存。

$page->title = "New Title";

$page->adhoc = "Adhoc value just in this record";
$page->save();

你可以在docs.phalconphp.com/en/latest/reference/odm.html了解更多关于 ODM 的信息。

数据库迁移

Phalcon 开发者工具和 Phalcon Web 工具提供的一个另一个工具是管理不同软件环境之间的数据库更改。要生成数据库迁移,你需要在项目目录中运行以下命令:

phalcon migration generate

此命令将简单地从你的数据库中导出每个对象到迁移类中。在我们的应用程序中,这些类将存储在app目录下名为当前迁移版本的文件夹中。假设你刚刚更改了应用程序的本地数据模型,并为它创建了一个迁移。然后,你只需将迁移上传到你的开发服务器,并运行以下命令来执行此服务器上数据库的更改:

phalcon migration run

要列出可用的配置选项,请运行以下命令:

phalcon migration

要使用 Web 工具,我们只需在我们的当前应用程序中浏览到http://localhost/phalconBlog/webtools.php?_url=/migrations/index,我们就会看到以下屏幕:

迁移数据库

你可以在docs.phalconphp.com/en/latest/reference/migrations.html了解更多关于 Phalcon 数据库迁移的信息。

摘要

在本章中,我们通过添加模型之间的关系来稍微完善了我们的博客应用程序。我们现在可以登录,创建自己的帖子,这些帖子包含更新和创建的日期,并为我们帖子添加标签。在重构应用程序以执行这些操作的过程中,我们学习了在 Phalcon 中处理数据的各种方法。然而,我们的应用程序远未完成。

在下一章中,我们将填补我们应用程序中的一些空白,包括对帖子进行评论的能力、隐藏未登录访客的内容和功能、加密用户密码、生成 RSS 源,以及在我们创建帖子时 ping 网站。

第五章:使用 Phalcon 的特性

是时候给我们的博客添加一些特性,并在过程中了解更多关于 Phalcon 的功能了。为了使我们的博客达到预期效果,还有很多工作要做。用户的密码仍然是明文,任何人,无论是否是用户,都可以浏览到用户的页面并查看密码。因此,我们需要设置至少某种形式的安全性和用户访问控制。我们有一个评论表,但我们还没有任何评论的方式。而且,没有 RSS 源和 ping 到聚合器的功能,博客就不能称之为博客。

在本章中,我们将涵盖以下主题:

  • 如何使用 Phalcon 哈希密码

  • 如何使用视图辅助器和视图部分

  • 如何设置 cookies

  • 如何控制用户的访问

  • 如何在我们的应用程序中缓存数据

  • 如何使用 Phalcon 的事件管理器

  • 如何路由应用程序请求

使用 Phalcon 哈希密码

首先,让我们至少将密码哈希化。哈希化是一个将密码转换为固定长度的位字符串或哈希的过程,该哈希不能被逆向工程以检索密码,但输入密码的任何变化都会改变生成的哈希。因此,为了更安全的应用程序,我们将从用户输入的密码生成一个哈希,并将该值存储在数据库中。然后,当用户再次尝试登录时,我们将从输入的密码生成一个哈希,并将其与数据库中哈希值的值进行比较。

幸运的是,这很容易用 Phalcon 做到。安全组件会自动加载到 Phalcon 服务的容器中。所以,打开UsersController.php文件,找到createAction函数;然后,找到设置密码的行。

$user->password = $this->request->getPost("password");

然后,只需将其替换为以下两行代码:

$password = $this->request->getPost("password");
$user->password = $this->security->hash($password);

现在,当我们创建用户时,密码将被哈希并保存到数据库中。如果我们编辑,我们还想将密码再次作为哈希保存。因此,我们在saveAction函数中找到相同的代码行,并将其替换为前面的两行代码以哈希密码。

现在,我们只需要编辑我们的loginAction函数。我们的初始函数有点笨拙。所以,我们将用一些更流畅的东西来替换它。新的loginAction函数将如下代码片段所示:

public function loginAction() {

        if ($this->request->isPost()) {
            $username = $this->request->getPost('username');
            $password = $this->request->getPost('password');

            $user = Users::findFirstByUsername($username);
            if ($user && $this->security->checkHash($password, $user->password)) {
                $this->session->set("user_id", $user->id);
                $this->cookies->set('user_id', $user->id);
                $this->session->set('auth', array(
                    'id' => $user->id,
                    'name' => $user->name
                ));
                $this->flash->success("Welcome " . $user->name);
            }else{
                $this->flash->error("Username and Password combination not found");
            }
        }
        return $this->dispatcher->forward(
            array(
                "controller" => "posts",
                "action" => "index"
            )
        );
    }

我们不是在数据库中搜索用户,而是尝试使用findFirstByFieldname函数实例化用户,其中Fieldname可以替换为我们正在查询的数据库中的列。如果我们通过搜索用户名找到了用户,我们使用安全服务的checkHash函数来比较数据库中的哈希值与我们刚刚从输入的密码中生成的哈希值。如果匹配,我们就通过设置会话中的user_id变量和创建欢迎信息的过程。

Phalcon 安全组件使用 bcrypt 哈希算法,这为我们提供了高水平的安全性。该组件默认由 Phalcon 加载,但也可以手动配置以调整 bcrypt 算法的“缓慢”。你可以在docs.phalconphp.com/en/latest/reference/security.html了解更多关于此组件的信息。

使用 Phalcon 视图助手

我们还可以通过将表单中的密码字段更改为真实密码字段来添加一些安全性。因此,在 app/views 目录下的 users 文件夹中找到以下文件:

  • new.volt

  • index.volt

  • edit.volt

在代码中找到以下行:

{{ text_field("password", "size" : 30) }}

将前面的代码替换为以下行代码:

{{ password_field("password", "size" : 30) }}

现在,当在字段中输入时,密码将被隐藏。

正如我们在第三章中学习的,使用 Phalcon 模型、视图和控制器,Volt 模板引擎是一个非常薄的包装器,它围绕 PHP 代码包装,Phalcon 将其编译成实际的 PHP 代码。要在 Volt 中调用 Phalcon 标签助手,我们只需使用函数的非驼峰式版本。

使用动态标题标签

让我们用一些视图助手替换我们编写的部分 HTML 标记。一个强大的视图助手是文档标题助手。目前,我们在主布局中有一个硬编码的 title 标签,它给每个页面都赋予相同的标题。因此,打开位于 app/views 目录下的 index.volt 文件,并找到以下行代码:

<title>Phalcon Blog</title>

将前面的代码替换为以下行代码:

{{ get_title() }}

现在,唯一的问题是我们的页面没有标题,因为我们还没有设置它。保持我们博客的标题在每一页上可能是一个好主意。我们可以硬编码它,或者打开位于 app/controllers 目录下的 ControllerBase.php 文件,并将以下函数添加到 ControllerBase 类中:

public function initialize() {
        $this->tag->setTitle("Phalcon Blog");
}

现在,我们的全局标题又回来了。但我们要让每个博客文章的标题也出现在标题中。所以,如果我们有一个名为 Hello World 的博客文章,我们希望我们的标题是 Hello World: Phalcon Blog。这样做很容易。打开位于 app/controllers 目录下的 PostController.php 文件,并将以下行代码添加到 showAction 函数的末尾:

$this->tag->prependTitle($post->title . " - ");

如果你访问博客文章页面,博客文章的标题会显示在博客标题之前。现在,如果你想的话,你可以在每个控制器中的每个操作中添加自己的标题。

设置文档类型

通过使用 Phalcon 设置文档类型,你影响它生成的所有带有标签助手的标签,使它们符合你选择的标准。我们将使用 HTML5。将以下行代码添加到位于 app/controllers 目录下的 ControllerBase.php 文件中的 initialize 函数中:

$this->tag->setDoctype(\Phalcon\Tag::HTML5);

现在,文档类型将在每个控制器中全局设置。在 app/views 目录下的 index.html 文件顶部找到以下行代码:

<!DOCTYPE html>

将前面的代码替换为以下行代码:

{{ get_doctype() }}

现在,你可以确信 Phalcon 会输出你选择的任何标准的标签。要了解其他文档标准,你可以访问 docs.phalconphp.com/en/latest/reference/tags.html#document-type-of-content

使用视图助手添加 JavaScript 和 CSS

我们最初只是在主布局文件的头部分硬编码了 CSS 和 JavaScript 链接。目前这没问题,但如果我们移动项目,我们可能需要更改这些链接。Phalcon 提供了脚本和 CSS 标签助手,这将使我们的工作变得容易一些。我们可以用以下代码片段替换 app/views/index.phtml 文件头部的 CSS 链接:

{{ stylesheet_link("css/bootstrap/bootstrap.min.css") }}
{{ stylesheet_link("css/bootstrap/bootstrap-responsive.min.css") }}

我们使用以下代码行将 JavaScript 链接放在文件底部:

{{ javascript_include("js/jquery.min.js") }}
{{ javascript_include("js/bootstrap.min.js") }}

我们将在阅读本章的过程中探索更多的视图助手。要了解更多关于 Phalcon 标签助手的信息,请访问 docs.phalconphp.com/en/latest/api/Phalcon_Tag.html

设置 cookies

现在我们可以登录并拥有安全的密码,但每次浏览器关闭时,我们都需要再次登录。如果能够登录到我们的博客并至少保持几天登录状态会很好。是时候设置一个 cookie 了。

我们将在阅读本章的过程中改变我们做这件事的方式,但到目前为止,设置一个可以让我们保持登录状态的 cookie 非常简单。首先,我们需要设置一个加密密钥,因为我们在设置 cookie 之前和检索它时都需要解密。我们希望保持这种方式,但为了做到这一点,我们需要给它一个密钥。所以,去 randomkeygen.com/ 生成一个随机的数字字符串。然后,打开 app/config/config.ini 文件,并在应用程序部分添加你的密钥,命名为 encryptKey,如下所示:

[application]
controllersDir = /home/eristoddle/Dropbox/xampp/htdocs/phalconBlog/app/controllers/
modelsDir = /home/eristoddle/Dropbox/xampp/htdocs/phalconBlog/app/models/
viewsDir = /home/eristoddle/Dropbox/xampp/htdocs/phalconBlog/app/views/
pluginsDir = ../app/plugins/
libraryDir = ../app/library/
baseUri = /phalconBlog/
cacheDir = ../app/cache/
encryptKey = 2tx6]GD}532q4x_

然后,打开 app/config/services.php 文件,并在文件底部添加以下代码片段以设置加密密钥:

$di->set(
    'crypt', function () use ($config) {
        $crypt = new Phalcon\Crypt();
        $crypt->setKey($config->application->encryptKey);
        return $crypt;
    }
);

现在,我们的 cookies 已经准备好了。它们之前是可用的,但如果我们使用了它们,它们会抛出一个关于需要加密密钥的错误。现在,在 app/controllers 目录下找到 UsersController.php 文件中的 loginAction 函数,并添加以下代码行:

$this->cookies->set('user_id', $user->id);

在设置会话中的 user_id 变量后立即添加它:

$this->session->set("user_id", $user->id);

现在,打开 app/controllers/PostsController.php 文件,并找到检查会话中 user_id 变量的 createAction 函数部分。现在,我们将首先检查 cookies,然后如果找到 user_id cookie,就在会话中设置 user_id 变量:

if ($this->cookies->has('user_id')) {
            $this->session->set('user_id', $this->cookies->get('user_id'));
        }
        if ($this->session->has("user_id")) {
            return $this->dispatcher->forward(
                array(
                    "controller" => "users",
                    "action" => "search"
                )
            );
        }

关于将代码改造以添加 cookie 的所有工作就到这里了。要了解更多关于 Phalcon 中 cookie 管理的知识,请参阅docs.phalconphp.com/en/latest/reference/cookies.html

现在,我们应该能够注销并再次登录,关闭浏览器,仍然保持登录状态。然而,当涉及到控制用户时,这并不是我们真正想要的全部。我们希望能够无缝地控制他们访问的一切。目前,我们的应用程序存在漏洞,为了填补这些漏洞,我们需要复制并粘贴代码以在需要该功能的所有地方实现相同的user_id变量检查。幸运的是,我们不必这样做。Phalcon 提供了一个服务,帮助我们控制用户访问。

控制用户访问

访问控制列表ACL)使用角色来控制对资源的访问。Phalcon\Acl为我们提供了这个功能。有了它,我们可以将角色分配给我们博客上不同类型的访客,然后使用这些角色在每个控制器中的每个操作上设置权限。对于我们的目的,我们只将创建两个角色:用户和访客。用户将简单地是一个已登录的访客,访客或任何人。我们只将允许访客执行以下操作:

  • 查看索引页面:

    • 查看帖子索引页面

    • 对帖子进行评论

    • 当访客未登录时,查看用户索引页,这将是一个登录页面

  • 登录

登录用户可以做任何事情。为了在我们的应用程序中使用Phalcon\Acl,我们将创建一个简单的 Phalcon 插件。Phalcon 开发者工具已经添加了我们的plugins文件夹,并在config.ini文件中创建了pluginsDir设置。然而,我们仍然需要将plugins目录添加到我们的加载器中。打开位于app/configloader.php文件,并将plugins目录添加到您的加载器中。

$loader->registerDirs(
    array(
        $config->application->controllersDir,
        $config->application->modelsDir,
        $config->application->pluginsDir
    )
)->register();

我们想要创建的是一个插件,它将连接到 Phalcon 的事件管理器。使用事件管理器,我们可以将监听器附加到各种 Phalcon 事件。在app/plugins文件夹中创建一个名为Security.php的新文件,并在文件顶部添加以下代码片段:

<?php

use Phalcon\Events\Event,
    Phalcon\Mvc\User\Plugin,
    Phalcon\Mvc\Dispatcher,
    Phalcon\Acl;

class Security extends Plugin {

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

我们扩展了Phalcon\Plugin。我们的构造函数必须接受一个依赖注入器实例。然后,我们需要创建一个函数,将我们的 ACL 添加到我们的持久变量中。我们将称之为getAcl

    public function getAcl() {
        if (!isset($this->persistent->acl)) {

            $acl = new Phalcon\Acl\Adapter\Memory();
            $acl->setDefaultAction(Phalcon\Acl::DENY);

            $roles = array(
                'users' => new Phalcon\Acl\Role('Users'),
                'guests' => new Phalcon\Acl\Role('Guests')
            );
            foreach ($roles as $role) {
                $acl->addRole($role);
            }

            $private = array(
                'comments' => array('index', 'edit', 'delete', 'save'),
                'posts' => array('new', 'edit', 'save', 'create', 'delete'),
                'users' => array('search', 'new', 'edit', 'save', 'create', 'delete', 'logout')
            );
            foreach ($private as $resource => $actions) {
                $acl->addResource(new Phalcon\Acl\Resource($resource), $actions);
            }

            $public = array(
                'index' => array('index'),
                'posts' => array('index', 'search', 'show', 'comment'),
                'users' => array('login', 'index')
            );
            foreach ($public as $resource => $actions) {
                $acl->addResource(new Phalcon\Acl\Resource($resource), $actions);
            }

            foreach ($roles as $role) {
                foreach ($public as $resource => $actions) {
                    foreach ($actions as $action) {
                        $acl->allow($role->getName(), $resource, $action);
                    }
                }
            }

            foreach ($private as $resource => $actions) {
                foreach ($actions as $action) {
                    $acl->allow('Users', $resource, $action);
                }
            }

            $this->persistent->acl = $acl;
        }

        return $this->persistent->acl;
    }

首先,代码检查我们是否已经有了 ACL 实例。如果没有,我们创建一个。然后,我们将DefaultAction函数设置为出于安全原因拒绝访问。接下来的代码段创建了角色,即用户和访客,在我们的 ACL 中。然后,我们将我们想要设置为私有的所有资源加载到一个变量中,并将它们全部添加为资源。我们将对想要设置为公共的资源做同样的事情。然后,我们遍历角色,为角色和公共资源提供访问权限。然后,我们遍历私有资源,为用户提供访问权限,但不为访客提供。最后,我们将我们的新 ACL 设置为持久性并返回它。

此函数只是构建我们的 ACL 结构。我们仍然需要实际控制访问,我们将通过在调度之前触发一个函数来实现这一点。在文件底部添加以下函数:

    public function beforeDispatch(Event $event, Dispatcher $dispatcher) {

        $user = $this->session->get('user_id');
        if (!$user) {
            $role = 'Guests';
        } else {
            $role = 'Users';
        }

        $controller = $dispatcher->getControllerName();
        $action = $dispatcher->getActionName();
        $acl = $this->getAcl();

        $allowed = $acl->isAllowed($role, $controller, $action);
        if ($allowed != Acl::ALLOW) {
            $this->flash->error("You don't have access to this module");
            $dispatcher->forward(
                array(
                    'controller' => 'index',
                    'action' => 'index'
                )
            );
            return false;
        }

    }

}

此函数接受一个事件实例和一个调度程序实例。我们检查访问者是否已登录并设置他们的角色。然后,我们设置一个变量来存储我们的控制器名称和一个变量来存储操作名称。我们调用我们的getAcl函数来获取我们的访问控制列表(ACL),并且我们还调用$acl->isAllowed(),传递访问者的角色以及我们的控制器和操作名称。如果角色被允许访问当前资源,这将返回 true。如果用户不允许访问,我们将设置一个错误消息并将他们转发到我们博客的首页。

现在,我们需要连接到标准调度程序,将这个新的Security插件附加到我们的事件管理器,并将其设置为调度程序的事件管理器。打开位于app/configservices.php文件,并将以下代码片段添加到文件底部:

$di->set('dispatcher', function() use ($di) {
    $eventsManager = $di->getShared('eventsManager');
    $security = new Security($di);
    $eventsManager->attach('dispatch', $security);
    $dispatcher = new Phalcon\Mvc\Dispatcher();
    $dispatcher->setEventsManager($eventsManager);
    return $dispatcher;
});

注意

要了解更多关于使用Phalcon\Acl的信息,请访问docs.phalconphp.com/en/latest/reference/acl.html。要了解更多关于 Phalcon 的事件管理器的信息,请访问docs.phalconphp.com/en/latest/reference/events.html

为我们的应用程序添加最后修饰

Phalcon 开发者工具可以提供很多帮助,但让我们面对现实,你将为你的应用程序编写的绝大多数代码都是从零开始的。我们的博客还没有评论或源,所以它实际上根本算不上一个博客。添加这些功能将是一项手动工作,但 Phalcon 使这变得容易。

添加评论

现在,让我们给访问者提供在帖子上发表评论的能力。之前,我们需要用户访问控制,以便我们可以将评论管理限制为已登录用户。但现在,我们需要一个地方来做这件事。我们将构建一个非常简单的评论管理系统。我们可以使用 Phalcon 开发者工具或 Phalcon 网络工具为我们生成 CRUD 脚手架,但在这个案例中,我们几乎会移除比留下的更多生成的代码,但我们可以使用我们生成的其他控制器和视图作为指南。

对于评论,我们需要添加一个表单来对帖子进行评论,并显示动作视图,我们还需要在每个帖子下显示已发布的评论。我们还需要一个地方来列出所有评论,以及一个表单来编辑它们并将它们设置为发布。

评论已经与帖子相关联。我们只需修改几个地方,使这种关系变得可用。打开位于app/views/postsshow.volt文件。在文件底部,添加以下代码片段:

<div class="comments">
    <h3>Comments</h3>
    <div class="comment">
    {% for comments in post.comments if comments.publish == true %}
        <div>{{ comments.body }}</div>
        <div><a href="{{ comments.url }}">{{ comments.name }}</a></div>
    {% endfor %}
    </div>
    <h3>Leave a Comment</h3>
    <div>
        {{ form("posts/comment", "method":"post") }}
        {{ hidden_field("posts_id", "value" : post.id) }}
        <div>
            <label for="title">Comment</label>
            {{ text_area("body") }}
        </div>
        <div>
            <label for="title">Name</label>
            {{ text_field("name") }}
        </div>
        <div>
            <label for="title">Email</label>
            {{ text_field("email") }}
        </div>
        <div>
            <label for="title">Website</label>
            {{ text_field("url") }}
        </div>
        {{ submit_button("Comment", "class" : "btn") }}
        {{ end_form() }}
    </div>
</div>

在帖子下方,我们遍历与该帖子相关的评论,如果我们将它们设置为发布,则打印评论。下面是评论表单,确保我们有一个可以获取帖子 ID 的隐藏字段。我们将此表单提交到posts/comment,这意味着我们现在需要在Posts控制器中添加一个commentAction函数。打开位于app/controllersPostsController.php文件,并添加以下函数:

public function commentAction(){
        $comment = new Comments();
        $comment->posts_id = $this->request->getPost("posts_id");
        $comment->body = $this->request->getPost("body");
        $comment->name = $this->request->getPost("name");
        $comment->email = $this->request->getPost("email");
        $comment->url = $this->request->getPost("url");
        $comment->submitted = date("Y-m-d H:i:s");
        $comment->publish = 0;
        $comment->save();

        $this->flash->success("Your comment has been submitted.");

        return $this->dispatcher->forward(
            array(
                "controller" => "posts",
                "action" => "show",
                "params" => array($comment->posts_id)
            )
        );
}

这相当简单。我们从POST变量中创建一个Comments对象的实例,设置提交日期,并将发布值设置为 0,这在我们的数据库中将表示为 false。然后,我们设置一个成功消息,并将用户转发回Posts的显示动作,设置 ID 为刚刚评论的帖子的 ID。

现在,我们需要一种方式来管理我们的评论。首先,我们需要一个Comments控制器。因此,在app/controllers中创建一个CommentsController.php文件,并确保包含Paginator,因为我们将会使用它。

use Phalcon\Paginator\Adapter\Model as Paginator;

然后,我们需要我们的CommentsController类。

class CommentsController extends ControllerBase {
  //Actions will go here
}

CommentsController类内部,我们需要为索引、编辑、保存和删除操作创建动作。我们的indexAction函数返回所有评论的分页结果。

    public function indexAction() {
        $numberPage = $this->request->getQuery("page", "int", 1);

        $comments = Comments::query()
            ->order("submitted DESC")
            ->execute();

        $paginator = new Paginator(array(
            "data" => $comments,
            "limit" => 10,
            "page" => $numberPage
        ));

        $this->view->page = $paginator->getPaginate();
    }

我们的editAction函数通过将其设置为发布,为审核评论时使用的编辑表单准备单个评论。

    public function editAction($id) {

        if (!$this->request->isPost()) {

            $comment = Comments::findFirstByid($id);
            if (!$comment) {
                $this->flash->error("comment was not found");
                return $this->dispatcher->forward(
                    array(
                        "controller" => "comments",
                        "action" => "index"
                    )
                );
            }

            $this->view->id = $comment->id;

            $this->tag->setDefault("id", $comment->id);
            $this->tag->setDefault("body", $comment->body);
            $this->tag->setDefault("name", $comment->name);
            $this->tag->setDefault("email", $comment->email);
            $this->tag->setDefault("url", $comment->url);
            $this->tag->setDefault("submitted", $comment->submitted);
            $this->tag->setDefault("publish", $comment->publish);
            $this->tag->setDefault("posts_id", $comment->posts_id);

        }
    }

saveAction函数在我们审核后保存编辑过的评论。

    public function saveAction() {

        if (!$this->request->isPost()) {
            return $this->dispatcher->forward(
                array(
                    "controller" => "comments",
                    "action" => "index"
                )
            );
        }

        $id = $this->request->getPost("id");

        $comment = Comments::findFirstByid($id);
        if (!$comment) {
            $this->flash->error("comment does not exist " . $id);
            return $this->dispatcher->forward(
                array(
                    "controller" => "comments",
                    "action" => "index"
                )
            );
        }

        $comment->id = $this->request->getPost("id");
        $comment->body = $this->request->getPost("body");
        $comment->name = $this->request->getPost("name");
        $comment->email = $this->request->getPost("email", "email");
        $comment->url = $this->request->getPost("url");
        $comment->publish = $this->request->getPost("publish");

        if (!$comment->save()) {

            foreach ($comment->getMessages() as $message) {
                $this->flash->error($message);
            }

            return $this->dispatcher->forward(
                array(
                    "controller" => "comments",
                    "action" => "edit",
                    "params" => array($comment->id)
                )
            );
        }

        $this->flash->success("comment was updated successfully");
        return $this->dispatcher->forward(
            array(
                "controller" => "comments",
                "action" => "index"
            )
        );

    }

deleteAction函数删除我们的评论垃圾邮件。

    public function deleteAction($id) {

        $comment = Comments::findFirstByid($id);
        if (!$comment) {
            $this->flash->error("comment was not found");
            return $this->dispatcher->forward(
                array(
                    "controller" => "comments",
                    "action" => "index"
                )
            );
        }

        if (!$comment->delete()) {

            foreach ($comment->getMessages() as $message) {
                $this->flash->error($message);
            }

            return $this->dispatcher->forward(
                array(
                    "controller" => "comments",
                    "action" => "search"
                )
            );
        }

        $this->flash->success("comment was deleted successfully");
        return $this->dispatcher->forward(
            array(
                "controller" => "comments",
                "action" => "index"
            )
        );
    }

现在,我们只需要视图。首先,我们需要为我们的indexAction函数创建视图。因此,在app/viewscomments文件夹中创建一个名为index.volt的文件,并在其中插入以下代码片段:

{{ content() }}
<h1>Comments</h1>
<table class="browse" align="center">
    <thead>
        <tr>
            <th>Body</th>
            <th>Name</th>
            <th>Email</th>
            <th>Url</th>
            <th>Submitted</th>
            <th>Publish</th>
         </tr>
    </thead>
    <tbody>
    {% if page.items is defined %}
    {% for comment in page.items %}
        <tr>
            <td>{{ comment.body }}</td>
            <td>{{ comment.name }}</td>
            <td>{{ comment.email }}</td>
            <td>{{ comment.url }}</td>
            <td>{{ comment.submitted }}</td>
            <td>{{ comment.publish }}</td>
            <td>{{ link_to("comments/edit/"~comment.id, "Edit") }}</td>
            <td>{{ link_to("comments/delete/"~comment.id, "Delete") }}</td>
        </tr>
    {% endfor %}
    {% endif %}
    </tbody>
    <tbody><tr><td colspan="2" align="right">
        <table align="center">
            <tr>
                <td>{{ link_to("comments/search", "First") }}</td>
                <td>{{ link_to("comments/search?page="~page.before, "Previous") }}</td>
                <td>{{ link_to("comments/search?page="~page.next, "Next") }}</td>
                <td>{{ link_to("comments/search?page="~page.last, "Last") }}</td>
                <td>{{ page.current~"/"~page.total_pages }}</td>
            </tr>
        </table>
    </td></tr></tbody>
</table>

然后,当编辑我们的帖子时将使用的视图。将以下代码片段保存为edit.volt,位于app/views/comments

{{ content() }}
{{ link_to("comments", "Go Back") }}

<div align="center">
    <h1>Edit comments</h1>
</div>

<div>
    {{ form("comments/save", "method":"post") }}
        <label for="body">Body</label>{{ text_area("body") }}
        <label for="name">Name</label>{{ text_field("name") }}
        <label for="email">Email</label>{{ text_field("email") }}
        <label for="url">Url</label>{{ text_field("url") }}
        <label for="publish">Publish</label>
        {{ radio_field("publish", "value" : 1) }} Yes
        {{ radio_field("publish", "value" : 0) }} No
        {{ hidden_field("id") }}
        {{ submit_button("Save", "class" : "btn") }}
    {{ end_form() }}
</div>

现在,如果我们以用户身份登录,我们就可以对帖子进行评论和管理。

添加聚合源

一个博客如果没有一种方式来聚合我们的帖子,那就不是一个真正的博客。幸运的是,向我们的博客添加一个聚合源将会非常简单。让我们将我们的聚合源放在http://localhost/phalconBlog/posts/feed。这意味着我们需要在帖子控制器中添加一个新的feedAction函数。因此,打开位于app/controllersPostsController.php文件,并添加以下函数:

public function feedAction() {
    $posts = Posts::find(
        array(
            'order' => 'published DESC',
            'limit' => 10
        )
    );

    $rss_posts = array();
    foreach ($posts as $post){
        $post->rss_date = date("D, d M Y H:i:s O", strtotime($post->published));
        $rss_posts[] = $post;
    }
    $this->view->posts = $rss_posts;

    $this->view->setRenderLevel(Phalcon\Mvc\View::LEVEL_ACTION_VIEW);
}

这里,我们使用 Posts::find 仅检索按发布日期降序排列的 10 篇帖子。由于 RSS 源中的日期格式非常特定,并且不匹配 MySQL datetime 格式,我们遍历我们的记录,转换发布日期,并将此日期添加到每个 post 对象中。然后,我们将对象数组发送到视图。在函数的末尾,我们设置视图的渲染级别。在这种情况下,我们不希望我们的主布局或帖子布局渲染。我们只想渲染我们即将创建的专用模板,因此我们将我们的级别设置为 LEVEL_ACTION_VIEW。以下六个级别可用:

  • LEVEL_NO_RENDER:这不会渲染任何视图

  • LEVEL_ACTION_VIEW:这会渲染与操作关联的视图

  • LEVEL_BEFORE_TEMPLATE:这会在控制器布局之前生成视图

  • LEVEL_LAYOUT:这会生成控制器的布局

  • LEVEL_AFTER_TEMPLATE:这会在控制器布局之后生成视图

  • LEVEL_MAIN_LAYOUT:这会生成主布局

app/viewsposts 文件夹中创建一个新文件,命名为 feed.volt,并在 feed.volt 文件中插入以下代码片段:

{{'<?xml version="1.0" encoding="UTF-8" ?>'}}

<rss version="2.0">
    <channel>
        <title>{{ config.blog.title }}</title>
        <description>This is a demonstration of the Phalcon framework</description>
        <link>{{ config.blog.url }}</link>
        {% for post in posts %}
            <item>
                <title>{{ post.title|e }}</title>
                <description>{{ post.excerpt|e }}</description>
                <link>{{ config.blog.url }}{{ url("posts/show/"~post.id) }}</link>
                <guid>{{ config.blog.url }}{{ url("posts/show/"~post.id) }}</guid>
                <pubDate>{{ post.rss_date }}</pubDate>
            </item>
        {% endfor %}
    </channel>
</rss>

我们在 Volt 模板标签的顶部包裹 XML 标签,以隐藏 Volt 模板引擎中的 <?。我们添加一些必要的 RSS 元素,包括标题、描述和博客的链接。请注意,由于我们已经将配置加载到 DI 容器中,我们可以访问在那里为博客标题和 URL 创建的设置。下一步是遍历帖子。这部分与 index.volt 文件中的代码类似。但您可能会注意到标题和摘要后面的 |e。这是从 Volt 中调用的 Phalcon HTML 转义过滤器,以确保我们的源在任何可能存在于我们内容中的字符下都保持有效。我们正在生成 XML,并使用我们在 feedAction 函数中创建的 rss_date 属性。

我们需要执行的最后一个步骤是将 RSS 自动发现链接添加到我们页面的头部。因此,打开位于 app/viewsindex.volt 文件,并在头部标签之间添加以下代码片段:

{{ tag_html(
            "link",
            [
                "rel": "alternate",
                "type": "application/rss+xml",
                "title": "RSS Feed for Phalcon Blog",
                "href": config.application.baseUri~"posts/feed"
            ],
             true,
             true,
             true)
 }}

在这里,我们使用通用的 tagHtml() 标签辅助函数来为我们生成链接,在 Volt 中变为 tag_html()。第一个参数是标签的名称。第二个参数是标签属性的数组。请注意,href 参数使用了 baseUri 配置设置和 ~,这是 Volt 中用于连接字符串的字符。第三个参数是一个我们设置为 true 的布尔值,因为我们希望这是一个自闭合标签。我们将第四个参数设置为 true,因为我们只想生成起始标签。最后,我们将第五个参数设置为 true,因为我们希望在标签后生成一个换行符。

现在,我们可以开始向互联网的其余部分介绍我们的新博客。

发送更新 ping

为了 ping 像weblogs.com这样的网站并通知它们我们有一篇新文章,我们将进行一些重构。看起来我们在 HTML 标题标签中使用了我们博客的标题。我们还需要在将要编写的 ping 函数中使用它。因此,与其在两个必须编辑的地方硬编码标题,不如如果我们更改标题,创建一个配置设置会更好。所以,将以下代码片段添加到app/configconfig.ini文件:

[blog]
title = Phalcon Blog
url = http://localhost

由于我们现在将在控制器中使用这个设置,我们需要在那里访问它。为此,我们可以将其添加到依赖注入容器中。这可以通过将以下代码行添加到app/configservices.php文件的底部来完成:

$di->set('config', $config);

现在,我们可以打开位于app/controllersControllerBase.php文件,并使用我们的配置设置设置我们的标题标签。

$this->tag->setTitle($this->config->blog->title);

我们可以在PostsController.php文件中创建一个新的函数来为我们 ping 内容聚合网站。

private function sendPings(){
        $request = '<?xml version="1.0" encoding="iso-8859-1"?>
                    <methodCall>
                    <methodName>weblogUpdates.ping</methodName>
                    <params>
                     <param>
                      <value>
                       <string>'.$this->config->blog->title.'</string>
                      </value>
                     </param>
                     <param>
                      <value>
                       <string>'.$this->config->blog->url.$this->url->get('posts/feed').'</string>
                      </value>
                     </param>
                    </params>
                    </methodCall>';

        $ping_urls = array(
            'http://blogsearch.google.com/ping/RPC2',
            'http://rpc.weblogs.com/RPC2',
            'http://ping.blo.gs/'
        );
        foreach($ping_urls as $ping_url){
            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $ping_url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true );
            curl_setopt($ch, CURLOPT_POST, true );
            curl_setopt($ch, CURLOPT_POSTFIELDS, trim($request));
            $results = curl_exec($ch);
        }
        curl_close($ch);
    }

$request变量是我们将要发布到服务的 XML。现在我们已经将配置设置添加到我们的依赖注入容器中,我们可以使用$this->config->blog->title访问博客的标题设置,以及使用$this->config->blog->url访问 URL 设置。$ping_urls数组包含我们将要 ping 的服务 URL。如果您喜欢,可以向此数组添加更多服务。然后,我们使用 PHP curl进行 ping。

现在,在createAction函数中,找到以下代码行:

$this->flash->success("post was created successfully");

在前面的代码之前添加以下行:

$this->sendPings();

我们可以添加到这个函数中的另一个特性是记录我们 ping 的结果,这样我们就有了一种调试实际发生情况的方法,因为我们的函数不返回状态。

现在,是时候向配置文件的应用程序部分添加另一个变量了。

logsDir = ../app/logs/

现在,我们需要在依赖注入容器中添加一个服务来记录我们 ping 的结果。打开位于app/configservices.php文件,并添加以下代码行:

//Logging
$di->set(
    'pingLogger', function () use ($config){
        $logger = new \Phalcon\Logger\Adapter\File($config->application->logsDir.'ping.log');
        return $logger;
    }
);

这将使我们能够访问我们刚刚创建的sendPings函数中的pingLogger服务。因此,在设置完$result变量后,我们可以记录我们 ping 的 URL 以及我们收到的响应。我们检查结果是否为假,表示curl失败,然后更改$result变量以反映该错误。

$result = curl_exec($ch);
if($result === false){
       $result = "Curl Error";
}
$this->pingLogger->log($ping_url.PHP_EOL.$result);

使用视图部分

我们已经探讨了布局和级联视图。我们还有在 Phalcon 中使用部分视图的选项。使用部分模板允许我们在应用程序的各个部分重用相同的功能。我们可能想要使用部分视图的几个地方是导航栏和侧边栏。目前,我们的主布局做了很多事情。导航栏或侧边栏上没有太多内容。但随着我们添加更多功能,它们可能会变得更加复杂,将它们保存在单独的文件中将帮助我们组织和简化代码。

你可以在任何你选择的地方创建一个文件夹来存放你的部分视图。你甚至可以将它们放在位于appviews文件夹中。但是,为了使我们的模板更有组织性,让我们在位于appviews文件夹内创建一个名为partials的文件夹。现在,打开位于app/viewsindex.volt文件。我们将从这个文件中剪切侧边栏,并将其粘贴到一个名为sidebar.volt的新文件中,我们将将其保存在我们新的partials文件夹中。该文件的 内容应如下代码片段所示:

<div class="span3">
    <div class="well">
        {{ form("posts/search", "method":"post", "autocomplete" : "off", "class" : "form-inline") }}
            <div class="input-append">
                {{ text_field("body", "class" : "input-medium") }}
                {{ submit_button("Search", "class" : "btn") }}
            </div>
        {{ end_form() }}
    </div>
</div>

我们将在index.volt文件中用以下代码行替换它:

{{ partial("partials/sidebar") }}

这是我们在 Volt 中使用的代码。如果我们使用 PHP 模板,我们会使用<?php $this->partial("partials/sidebar"); ?>。请注意,我们不要在path参数中添加文件扩展名。确保对你的partials文件夹也这样做。添加扩展名会导致错误。

现在,你可以在位于app/viewsindex.phtml文件中,使用具有navbar类的div标签,将其粘贴到名为navbar.volt的文件中,将其保存在你的partials文件夹中,并用以下代码行替换div标签:

{{ partial("partials/navbar") }}

现在,不同的功能类型已在你应用中分离。你可能会在应用的其他地方找到可能想要使用部分视图的地方,例如在页眉或页脚中。要了解更多关于 Phalcon 的信息,请访问docs.phalconphp.com/en/latest/index.html

Phalcon 中的缓存

缓存可以加快并节省大量流量或执行大量密集型重复数据库查询的网站上的资源,但此功能只应在实际需要时实施。换句话说,我们当前的博客可能不需要缓存,但我们打算研究 Phalcon 中的缓存,并将缓存添加到我们博客的一部分,以便了解其工作原理。

设置缓存服务

在 Phalcon 中,缓存由两部分组成,一个处理缓存过期和转换的前端缓存,以及一个处理前端请求时的读取和写入的后端缓存。以下是一个缓存服务的示例。我们可以简单地将其添加到位于app/configservice.php文件中。

$di->set(
    'viewCache', function () use ($config) {

        //Cache for one day
        $frontCache = new \Phalcon\Cache\Frontend\Data(array(
            "lifetime" => 86400
        ));

        //Set file cache
        $cache = new Phalcon\Cache\Backend\File($frontCache, array(
            "cacheDir" => $config->application->cacheDir
        ));

        return $cache;
    }
);

到现在为止,我们已经很习惯于设置新的 Phalcon 服务。在这个新服务中,我们使用配置变量,以便它可以使用我们的缓存目录设置。我们给前端缓存设置了一天的生命周期,并将这个变量以及我们的缓存目录位置传递给后端缓存。

我们在这个服务中使用了基于文件的缓存。这不是最快的缓存机制,可能是最慢的,但它易于设置,且不需要其他软件。然而,你并不限于使用基于文件的 Phalcon 后端。只要你有所需的软件和 PHP 扩展安装,你就可以在 Phalcon 中使用以下任何一个作为后端缓存:

  • 文件

    • Memcached

    • APC

    • MongoDB

    • Xcache

Phalcon 也有多种前端缓存适配器。在我们的缓存服务代码中,我们指定了一个数据适配器,在保存数据之前对其进行序列化,但你也可以使用以下前端适配器之一:

  • 输出

  • JSON

  • IgBinary

  • Base64

使用 Phalcon 缓存

现在我们已经设置了缓存服务,我们可以在需要的地方使用它。首先,你需要一个缓存键。所有 Phalcon 缓存都使用键来存储和访问缓存数据。它需要对于那份数据是唯一的。在我们的应用程序中,为缓存生成键的好地方是在app/controllers文件夹中的ControllerBase.php文件中。我们可以在ControllerBase类中添加以下函数,以便所有控制器继承它:

public function createKey($controller, $action, $parameters = array())
{
        return urlencode($controller.$action.serialize($parameters));
}

此函数将为我们创建一个唯一的键。因此,现在我们通过打开位于app/controllersPostController.php文件并修改showAction函数来测试使用我们的缓存的功能。我们可以在函数的开始处添加以下代码行来访问我们创建的缓存服务:

$cache = $this->di->get("viewCache");

然后,通过执行以下代码行,你可以访问缓存内容或开始缓存:

$key = $this->createKey('posts', 'show', array($id));
$post = $cache->get($key);

然后,我们检查是否有内容,如果没有,我们就从数据库中获取它,并使用我们的键将其保存到缓存中。

if ($post === null) {
            $post = Posts::findFirstByid($id);
            $cache->save($key, $post);
}

函数的其余部分保持不变。

$this->tag->prependTitle($post->title . " - ");
$this->view->post = $post;

在浏览器中打开你博客的一篇文章并刷新页面。你将在app文件夹中的cache文件夹中找到缓存文件。在文件中,将存在序列化数据,代表$post对象。要了解更多关于在 Phalcon 中使用缓存的信息,请访问docs.phalconphp.com/en/latest/reference/cache.html

你也可以在模型级别缓存数据。机制是相同的。尝试通过键访问缓存的数据。检查是否存在数据。如果没有,执行数据库调用以检索数据并将其缓存。然后,返回结果。要了解 ORM 中的缓存,请访问docs.phalconphp.com/en/latest/reference/models-cache.html

Phalcon 中的路由

在我们查看 Phalcon 中的其他应用结构,特别是微应用之前,我们应该先看看路由。到目前为止,我们只是在我们的应用中有了路由;神奇地映射到我们的控制器和其中的动作,让 Phalcon 为我们处理所有的路由思考。这是因为我们正在使用的单 MVC 结构中,路由是在 MVC 模式下的。我们还有匹配模式的选择。你会注意到我们的博客应用有一个什么也不做的首页。它仍然有默认的 Phalcon 消息。我们网站的大部分内容目前位于http://localhost/phalconBlog/posts。嗯,为了修复这个问题,我们将使用一种黑客式的方法来学习 Phalcon 中的路由,那就是使用一个路由器。因此,我们需要向我们的依赖注入容器中添加另一个服务。打开位于app/configservices.php文件,并在底部添加以下代码行:

$di->set(
    'router', function () {
        $router = new Router();
        $router->add(
            "/", array(
                'controller' => 'posts',
                'action' => 'index',
            )
        );
        return $router;
    }
);

当我们向 MVC 路由器添加路由时,第一个参数是路径,第二个参数是任何数组,它将此路径映射到一个模块、控制器和动作。

在即将出现的微应用示例中,使用的是匹配模式的路由。要了解更多关于 Phalcon 中路由的信息,请访问docs.phalconphp.com/en/latest/reference/routing.html

Phalcon 的其他项目类型

我们在 Phalcon 中创建了一种类型的应用,一个单 MVC 应用。但是,这种应用结构并不适合所有类型的项目。现在我们将看看你可以使用 Phalcon 构建的几种其他类型的应用。当然,作为一个非常松散耦合的框架,Phalcon 并不限制你只使用这些结构,你可以根据自己的选择来组织项目。

多模块应用

随着应用规模的扩大,将代码组织到模块中可能变得容易。在项目中的app文件夹而不是一个apps文件夹下,你可以有多个文件夹,每个文件夹都有自己的模型、视图和控制器集合。在 MVC 模式下,路由已经设置好以处理模块。只需要几个额外的步骤。要了解更多关于 Phalcon 多模块应用的信息,请访问docs.phalconphp.com/en/latest/reference/applications.html#multi-module

微应用

对于比我们写的应用更简单的应用,使用微框架结构可能更有意义。在 Phalcon 中,微应用可以像以下代码片段那样简单:

<?php

$app = new Phalcon\Mvc\Micro();

$app->get(
    '/user/{name}', function ($name) {
        echo "<h1>Hi $name!</h1>";
    }
);

$app->get(
    '/api/user/{name}', function ($name) {
        echo json_encode(array("message" => "Hi ". $name));
    }
);

$app->handle();

这个应用程序没有太多内容。首先,我们创建了一个Phalcon\MVC\Micro的实例,然后我们定义了我们的路由。因此,当访问者访问应用程序的/user/Stephan页面时,他们会看到Hi Stephan。第二个路由展示了微应用程序用于 API 的一个完美用途。大多数简单的 API 不需要复杂的控制器。这个例子在这里是为了展示一个工作的 Phalcon 应用程序可以有多小。它使用匹配模式的路由。随着微应用程序规模的扩大,你可以使用它支持的更多功能,包括模型、重定向和服务。想了解更多关于 Phalcon 微应用程序的信息,请访问docs.phalconphp.com/en/latest/reference/micro.html

命令行应用程序

有时,你可能不需要一个完整的 Web 应用程序来完成工作。对于从命令行或 cron 运行的脚本,你只需要一个简单的结构来组织你代码的功能。Phalcon 命令行应用程序的结构可以从以下代码片段开始:

app/
  tasks/
  cli.php

启动文件是cli.php,它以与 Phalcon 中所有命令行应用程序启动时几乎相同的方式开始。

<?php

use Phalcon\DI\FactoryDefault\CLI as CliDI,
    Phalcon\CLI\Console as ConsoleApp;

//Using the CLI factory default services container
$di = new CliDI();

// Define the application path
defined('APPLICATION_PATH')
  || define('APPLICATION_PATH', realpath(dirname(__FILE__)));

//Register the autoloader
$loader = new \Phalcon\Loader();
$loader->registerDirs(
    array(
        APPLICATION_PATH . '/tasks'
    )
);
$loader->register();

// Load the config
$config = include APPLICATION_PATH . '/config/config.ini';
$di->set('config', $config);

//Create a console application
$console = new ConsoleApp();
$console->setDI($di);

//Process console arguments
$arguments = array();
$params = array();

foreach($argv as $k => $arg) {
    if($k == 1) {
        $arguments['task'] = $arg;
    } elseif($k == 2) {
        $arguments['action'] = $arg;
    } elseif($k >= 3) {
        $params[] = $arg;
    }
}
if(count($params) > 0) {
    $arguments['params'] = $params;
}

// define global constants for the current task and action
define('CURRENT_TASK', (isset($argv[1]) ? $argv[1] : null));
define('CURRENT_ACTION', (isset($argv[2]) ? $argv[2] : null));

try {
    // handle incoming arguments
    $console->handle($arguments);
}
catch (\Phalcon\Exception $e) {
    echo $e->getMessage();
    exit(255);
}

启动文件只需处理命令并选择正确的任务和操作。我们将任务存储在tasks文件夹中。命令行应用程序至少需要有一个mainTaskmainAction。因此,我们可以将此文件添加到tasks文件夹中,并将其命名为MainTask.php

<?php

class mainTask extends \Phalcon\CLI\Task
{

    public function mainAction() {
        echo "I am a task that doesn't do much";
    }

    public function anotherAction(array $params) {
        foreach($params as $p){
            echo "Hi, my name is ".$p.PHP_EOL;
        }
    }
}

执行以下命令行时,主任务的mainAction函数将被运行:

php app/cli.php

要调用特定的任务和操作,只需将任务作为第一个参数,将操作作为第二个参数,任何参数(tomdickharry)都可以由名为Executing的动作函数处理:

php app/cli.php main another tom dick harry

输出如下:

Hi, my name is tom
Hi, my name is dick
Hi, my name is harry

想要了解更多关于使用 Phalcon 创建命令行应用程序的信息,请访问docs.phalconphp.com/en/latest/reference/cli.html

摘要

在本章中,我们在为我们的博客应用程序添加功能的同时,更多地了解了我们可以使用 Phalcon 框架做什么。这并不是功能最丰富的博客,但我们展示了如何使用 Phalcon 标签助手设置文档类型并为我们的页面生成动态标题。我们还解释了如何在 Phalcon 中控制用户访问、散列密码和设置 cookie。然后,我们使用视图部分将我们的视图拆分成可重用的块。以防我们的博客有任何流量,我们在 Phalcon 中尝试了一些缓存。我们还学习了如何在 Phalcon 中使用路由。最后,我们学习了我们可以用于 Phalcon 应用程序的其他结构。想了解更多关于 Phalcon 的信息,请访问phalconphp.com

posted @ 2025-09-07 09:13  绝不原创的飞龙  阅读(6)  评论(0)    收藏  举报