이 문서에서는 Zend Framework 개발 시작과 관련된 지식 포인트를 설명합니다. 참고할 수 있도록 모든 사람과 공유하세요. 자세한 내용은 다음과 같습니다.
Zend Framework가 출시되었습니다! 아직 개발 초기 단계이지만 이 튜토리얼에서는 사용 가능한 최고의 기능 중 일부를 강조하고 간단한 프로그램을 구축하는 과정을 안내합니다.
Zend는 커뮤니티에서 ZF를 최초로 출시했습니다. 동일한 아이디어를 바탕으로 이 튜토리얼은 ZF의 기존 기능을 보여주기 위해 작성되었습니다. 이 튜토리얼은 온라인에 게시되어 있으므로 ZF가 변경되면 최대한 효율적으로 업데이트할 것입니다.
요구사항
Zend Framework에는 PHP5가 필요합니다. 이 튜토리얼의 코드를 최대한 활용하려면 Apache 웹 서버도 필요합니다. 데모 프로그램(뉴스 관리 시스템)이 mod_rewrite를 사용하기 때문입니다.
이 튜토리얼의 코드는 무료로 다운로드할 수 있으므로 직접 사용해 볼 수 있습니다. Brain Buld 웹사이트(http://brainbulb.com/zend-framework-tutorial.tar.gz)에서 코드를 다운로드할 수 있습니다.
ZF 다운로드
이 튜토리얼을 시작하면 최신 버전의 ZF를 다운로드해야 합니다. 브라우저를 사용하여 tar.gz 또는 zip 파일을 수동으로 선택하여 http://framework.zend.com/download에서 다운로드하거나 다음 명령을 사용할 수 있습니다.
$ wget http://framework.zend.com/download/tgz $ tar -xvzf ZendFramework-0.1.2.tar.gz
팁: Zend는 다음을 수행할 계획입니다. 자체 PEAR 채널을 제공하여 다운로드를 단순화합니다.
미리보기를 다운로드한 후 라이브러리 디렉토리를 편리한 곳에 두세요. 이 튜토리얼에서는 간결한 디렉토리 구조를 갖기 위해 라이브러리 이름을 lib로 변경했습니다:
app/
views/
Controllers/
www/
.htaccess
index. php
lib/
www 디렉토리는 문서 루트 디렉토리이고, 컨트롤러 및 뷰 디렉토리는 나중에 사용할 빈 디렉토리이며, lib 디렉토리는 다운로드한 미리보기 버전에서 가져옵니다.
시작
첫 번째 소개하고 싶은 컴포넌트는 Zend_Controller입니다. 여러 면에서 이는 개발하는 프로그램의 기반을 제공하며 Zend Framework가 단순한 구성 요소 모음 이상이라는 점을 부분적으로 결정합니다. 그러나 사용하기 전에 얻은 모든 요청을 간단한 PHP 스크립트에 넣어야 합니다. 이 튜토리얼에서는 mod_rewrite를 사용합니다.
mod_rewrite를 사용하는 것은 그 자체로 예술이지만 운 좋게도 이 특별한 작업은 놀라울 정도로 쉽습니다. 일반적으로 mod_rewrite 또는 Apache 구성에 익숙하지 않은 경우 문서 루트에 .htaccess 파일을 만들고 다음 콘텐츠를 추가하세요.
RewriteEngine on RewriteRule !/.(js|ico|gif|jpg|png|css)$ index.php
팁: Zend_Controller의 TODO 프로젝트 중 하나는 mod_rewrite에 대한 종속성을 취소하는 것입니다. 예제의 미리보기를 제공하기 위해 이 튜토리얼에서는 mod_rewrite를 사용합니다.
이러한 내용을 httpd.conf에 직접 추가하는 경우 웹 서버를 다시 시작해야 합니다. 그러나 .htaccess 파일을 사용하는 경우에는 아무 작업도 수행할 필요가 없습니다. 특정 텍스트를 index.php에 넣고 /foo/bar와 같은 경로에 액세스하여 빠른 테스트를 수행할 수 있습니다. 도메인 이름이 example.org인 경우 http://example.org/foo/bar를 방문하세요.
또한 ZF 라이브러리의 경로를 include_path로 설정해야 합니다. php.ini에서 설정하거나 .htaccess 파일에 직접 다음 내용을 넣을 수 있습니다:
php_value include_path "/path/to/lib"
Zend
Zend 클래스에는 자주 사용되는 정적 메서드 모음이 포함되어 있습니다. 수동으로 추가해야 하는 유일한 클래스는 다음과 같습니다.
<?php include 'Zend.php'; ?>
Zend.php를 포함하면 모든 Zend 클래스 클래스 메서드가 포함됩니다. loadClass()를 사용하여 다른 클래스를 간단히 로드할 수 있습니다. 예를 들어 Zend_Controller_Front 클래스를 로드합니다.
<?php include 'Zend.php'; Zend::loadClass('Zend_Controller_Front'); ?>
include_path는 loadclass() 및 ZF의 구성과 디렉터리 구조를 이해할 수 있습니다. 다른 모든 클래스를 로드하는 데 사용합니다.
Zend_Controller
이 컨트롤러를 사용하는 것은 매우 직관적입니다. 사실 저는 이 튜토리얼을 작성할 때 광범위한 문서를 사용하지 않았습니다.
팁: 문서는 현재 http://framework.zend.com/manual/zend.controller.html에서 확인할 수 있습니다.
처음에는 Zend_Controller_Front라는 전면 컨트롤러를 사용했습니다. 작동 방식을 이해하려면 index.php 파일에 다음 코드를 배치하세요.
<?php include 'Zend.php'; Zend::loadClass('Zend_Controller_Front'); $controller = Zend_Controller_Front::getInstance(); $controller->setControllerDirectory('/path/to/controllers'); $controller->dispatch(); ?>
개체 링크를 선호하는 경우 다음 코드를 사용할 수 있습니다. 대신:
<?php include 'Zend.php'; Zend::loadClass('Zend_Controller_Front'); $controller = Zend_Controller_Front::getInstance() ->setControllerDirectory('/path/to/controllers') ->dispatch(); ?>
이제 /foo/bar에 액세스하면 오류가 발생합니다. 좋아요! 무슨 일이 일어나고 있는지 알려줍니다. 가장 큰 문제는 IndexController.php 파일을 찾을 수 없다는 것입니다.
이 파일을 만들기 전에 먼저 정부가 이러한 항목을 어떻게 정리하기를 원하는지 이해해야 합니다. ZF는 액세스 요청을 분할합니다. /foo/bar에 액세스하는 경우 foo는 컨트롤러이고 bar는 작업입니다. 기본값은 모두 인덱스입니다.
foo가 컨트롤러인 경우 ZF는 컨트롤러 디렉터리에서 FooController.php 파일을 검색합니다. 이 파일이 존재하지 않기 때문에 ZF는 IndexController.php로 대체됩니다. 검색된 결과가 없어 오류가 보고되었습니다.
다음으로, 컨트롤러 디렉토리에 IndexController.php 파일을 생성합니다(setControllerDirectory()로 설정 가능):
<?php Zend::loadClass('Zend_Controller_Action'); class IndexController extends Zend_Controller_Action { public function indexAction() { echo 'IndexController::indexAction()'; } } ?>
就如刚才说明的,IndexController类处理来自index controller或controller不存在的请求。indexAction()方法处理action为index的访问。要记住的是index是controller和action的默认值。如果你访问/,/index或/index/index,indexAction()方法就会被执行。 (最后面的斜杠并不会改变这个行为。) 而访问其他任何资源只会导致出错。
在继续做之前,还要在IndexController加上另外一个有用的类方法。不管什么时候访问一个不存在的控制器,都要调用noRouteAction()类方法。例如,在FooController.php不存在的条件下,访问/foo/bar就会执行noRouteAction()。但是访问/index/foo仍会出错,因为foo是action,而不是controller.
将noRouteAction()添加到IndexController.php:
<?php Zend::loadClass('Zend_Controller_Action'); class IndexController extends Zend_Controller_Action { public function indexAction() { echo 'IndexController::indexAction()'; } public function noRouteAction() { $this->_redirect('/'); } } ?>
例子中使用$this->_redirect('/')来描述执行noRouteAction()时,可能发生的行为。这会将对不存在controllers的访问重定向到根文档(首页)。
现在创建FooController.php:
<?php Zend::loadClass('Zend_Controller_Action'); class FooController extends Zend_Controller_Action { public function indexAction() { echo 'FooController::indexAction()'; } public function barAction() { echo 'FooController::barAction()'; } } ?>
如果你再次访问/foo/bar,你会发现执行了barAction(),因为bar是action。现在你不只支持了友好的URL,还可以只用几行代码就做得这么有条理。酷吧!
你也可以创建一个__call()类方法来处理像/foo/baz这样未定义的action。
<?php Zend::loadClass('Zend_Controller_Action'); class FooController extends Zend_Controller_Action { public function indexAction() { echo 'FooController::indexAction()'; } public function barAction() { echo 'FooController::barAction()'; } public function __call($action, $arguments) { echo 'FooController:__call()'; } } ?>
现在你只要几行代码就可以很好地处理用户的访问了,准备好继续。
Zend_View
Zend_View是一个用来帮助你组织好你的view逻辑的类。这对于模板-系统是不可知的,为了简单起见,本教程不使用模板。如果你喜欢的话,不妨用一下。
记住,现在所有的访问都是由front controller进行处理。因此应用框架已经存在了,另外也必须遵守它。为了展示Zend_View的一个基本应用,将IndexController.php修改如下:
<?php Zend::loadClass('Zend_Controller_Action'); Zend::loadClass('Zend_View'); class IndexController extends Zend_Controller_Action { public function indexAction() { $view = new Zend_View(); $view->setScriptPath('/path/to/views'); echo $view->render('example.php'); } public function noRouteAction() { $this->_redirect('/'); } } ?>
在views目录创建example.php文件:
<html> <head> <title>This Is an Example</title> </head> <body> <p>This is an example.</p> </body> </html>
现在,如果你访问自己网站的根资源,你会看到example.php的内容。这仍没什么用,但你要清楚你要在以一种结构和组织非常清楚的方式在开发网络应用。
为了让Zend_View的应用更清楚一点,,修改你的模板(example.php)包含以下内容:
<html> <head> <title><?php echo $this->escape($this->title); ?></title> </head> <body> <?php echo $this->escape($this->body); ?> </body> </html>
现在已经添加了两个功能。$this->escape()类方法用于所有的输出。即使你自己创建输出,就像这个例子一样。避开所有输出也是一个很好的习惯,它可以在默认情况下帮助你防止跨站脚本攻击(XSS)。
$this->title和$this->body属性用来展示动态数据。这些也可以在controller中定义,所以我们修改IndexController.php以指定它们:
<?php Zend::loadClass('Zend_Controller_Action'); Zend::loadClass('Zend_View'); class IndexController extends Zend_Controller_Action { public function indexAction() { $view = new Zend_View(); $view->setScriptPath('/path/to/views'); $view->title = 'Dynamic Title'; $view->body = 'This is a dynamic body.'; echo $view->render('example.php'); } public function noRouteAction() { $this->_redirect('/'); } } ?>
现在你再次访问根目录,应该就可以看到模板所使用的这些值了。因为你在模板中使用的$this就是在Zend_View范围内所执行的实例。
要记住example.php只是一个普通的PHP脚本,所以你完全可以做你想做的。只是应努力只在要求显示数据时才使用模板。你的controller (controller分发的模块)应处理你全部的业务逻辑。
在继续之前,我想做最后一个关于Zend_View的提示。在controller的每个类方法内初始化$view对象需要额外输入一些内容,而我们的主要目标是让快速开发网络应用更简单。如果所有模板都放在一个目录下,是否要在每个例子中都调用setScriptPath()也存在争议。
幸运的是,Zend类包含了一个寄存器来帮助减少工作量。你可以用register()方法把你的$view对象存储在寄存器:
<?php Zend::register('view', $view); ?>
用registry()方法进行检索:
<?php $view = Zend::registry('view'); ?>
基于这点,本教程使用寄存器。
Zend_InputFilter
本教程讨论的最后一个组件是Zend_InputFilter。这个类提供了一种简单而有效的输入过滤方法。你可以通过提供一组待过滤数据来进行初始化。
<?php $filterPost = new Zend_InputFilter($_POST); ?>
这会将($_POST)设置为NULL,所以就不能直接进入了。Zend_InputFilter提供了一个简单、集中的根据特定规则过滤数据的类方法集。例如,你可以用getAlpha()来获取$_POST['name']中的字母:
<?php /* $_POST['name'] = 'John123Doe'; */ $filterPost = new Zend_InputFilter($_POST); /* $_POST = NULL; */ $alphaName = $filterPost->getAlpha('name'); /* $alphaName = 'JohnDoe'; */ ?>
每一个类方法的参数都是对应要过滤的元素的关键词。对象(例子中的$filterPost)可以保护数据不被篡改,并能更好地控制对数据的操作及一致性。因此,当你操纵输入数据,应始终使用Zend_InputFilter。
提示:Zend_Filter提供与Zend_InputFilter方法一样的静态方法。
构建新闻管理系统
虽然预览版提供了许多组件(甚至许多已经被开发),我们已经讨论了构建一个简单程序所需要的全部组件。在这里,你会对ZF的基本结构和设计有更清楚的理解。
每个人开发的程序都会有所不同,而Zend Framework试图包容这些差异。同样,这个教程是根据我的喜好写的,请根据自己的偏好自行调整。
当我开发程序时,我会先做界面。这并不意味着我把时间都花在标签、样式表和图片上,而是我从一个用户的角度去考虑问题。因此我把程序看成是页面的集合,每一页都是一个独立的网址。这个新闻系统就是由以下网址组成的:
/
/add/news
/add/comment
/admin
/admin/approve
/view/{id}
你可以直接把这些网址和controller联系起来。IndexController列出新闻,AddController添加新闻和评论,AdminController处理一些如批准新闻之类的管理,ViewController特定新闻和对应评论的显示。
如果你的FooController.php还在,把它删除。修改IndexController.php,为业务逻辑以添加相应的action和一些注释:
<?php Zend::loadClass('Zend_Controller_Action'); class IndexController extends Zend_Controller_Action { public function indexAction() { /* List the news. */ } public function noRouteAction() { $this->_redirect('/'); } } ?>
接下来,创建AddController.php文件:
<?php Zend::loadClass('Zend_Controller_Action'); class AddController extends Zend_Controller_Action { function indexAction() { $this->_redirect('/'); } function commentAction() { /* Add a comment. */ } function newsAction() { /* Add news. */ } function __call($action, $arguments) { $this->_redirect('/'); } } ?>
记住AddController的indexAction()方法不能调用。当访问/add时会执行这个类方法。因为用户可以手工访问这个网址,这是有可能的,所以你要把用户重定向到主页、显示错误或你认为合适的行为。
接下来,创建AdminController.php文件:
<?php Zend::loadClass('Zend_Controller_Action'); class AdminController extends Zend_Controller_Action { function indexAction() { /* Display admin interface. */ } function approveAction() { /* Approve news. */ } function __call($action, $arguments) { $this->_redirect('/'); } } ?>
最后,创建ViewController.php文件:
<?php Zend::loadClass('Zend_Controller_Action'); class ViewController extends Zend_Controller_Action { function indexAction() { $this->_redirect('/'); } function __call($id, $arguments) { /* Display news and comments for $id. */ } } ?>
和AddController一样,index()方法不能调用,所以你可以使用你认为合适的action。ViewController和其它的有点不同,因为你不知道什么才是有效的action。为了支持像/view/23这样的网址,你要使用__call()来支持动态action。
数据库操作
因为Zend Framework的数据库组件还不稳定,而我希望这个演示可以做得简单一点。我使用了一个简单的类,用SQLite进行新闻条目和评论的存储和查询。
<?php class Database { private $_db; public function __construct($filename) { $this->_db = new SQLiteDatabase($filename); } public function addComment($name, $comment, $newsId) { $name = sqlite_escape_string($name); $comment = sqlite_escape_string($comment); $newsId = sqlite_escape_string($newsId); $sql = "INSERT INTO comments (name, comment, newsId) VALUES ('$name', '$comment', '$newsId')"; return $this->_db->query($sql); } public function addNews($title, $content) { $title = sqlite_escape_string($title); $content = sqlite_escape_string($content); $sql = "INSERT INTO news (title, content) VALUES ('$title', '$content')"; return $this->_db->query($sql); } public function approveNews($ids) { foreach ($ids as $id) { $id = sqlite_escape_string($id); $sql = "UPDATE news SET approval = 'T' WHERE id = '$id'"; if (!$this->_db->query($sql)) { return FALSE; } } return TRUE; } public function getComments($newsId) { $newsId = sqlite_escape_string($newsId); $sql = "SELECT name, comment FROM comments WHERE newsId = '$newsId'"; if ($result = $this->_db->query($sql)) { return $result->fetchAll(); } return FALSE; } public function getNews($id = 'ALL') { $id = sqlite_escape_string($id); switch ($id) { case 'ALL': $sql = "SELECT id, title FROM news WHERE approval = 'T'"; break; case 'NEW': $sql = "SELECT * FROM news WHERE approval != 'T'"; break; default: $sql = "SELECT * FROM news WHERE id = '$id'"; break; } if ($result = $this->_db->query($sql)) { if ($result->numRows() != 1) { return $result->fetchAll(); } else { return $result->fetch(); } } return FALSE; } } ?>
(你可以用自己的解决方案随意替换这个类。这里只是为你提供一个完整示例的介绍,并非建议要这么实现。)
这个类的构造器需要SQLite数据库的完整路径和文件名,你必须自己进行创建。
<?php $db = new SQLiteDatabase('/path/to/db.sqlite'); $db->query("CREATE TABLE news ( id INTEGER PRIMARY KEY, title VARCHAR(255), content TEXT, approval CHAR(1) DEFAULT 'F' )"); $db->query("CREATE TABLE comments ( id INTEGER PRIMARY KEY, name VARCHAR(255), comment TEXT, newsId INTEGER )"); ?>
你只需要做一次,以后直接给出Database类构造器的完整路径和文件名即可:
<?php $db = new Database('/path/to/db.sqlite'); ?>
整合
为了进行整合,在lib目录下创建Database.php,loadClass()就可以找到它。你的index.php文件现在就会初始化$view和$db并存储到寄存器。你也可以创建__autoload()函数自动加载你所需要的类:
<?php include 'Zend.php'; function __autoload($class) { Zend::loadClass($class); } $db = new Database('/path/to/db.sqlite'); Zend::register('db', $db); $view = new Zend_View; $view->setScriptPath('/path/to/views'); Zend::register('view', $view); $controller = Zend_Controller_Front::getInstance() ->setControllerDirectory('/path/to/controllers') ->dispatch(); ?>
接下来,在views目录创建一些简单的模板。index.php可以用来显示index视图:
<html> <head> <title>News</title> </head> <body> <h1>News</h1> <?php foreach ($this->news as $entry) { ?> <p> <a href="/view/<?php echo $this->escape($entry['id']); ?>"> <?php echo $this->escape($entry['title']); ?> </a> </p> <?php } ?> <h1>Add News</h1> <form action="/add/news" method="POST"> <p>Title:<br /><input type="text" name="title" /></p> <p>Content:<br /><textarea name="content"></textarea></p> <p><input type="submit" value="Add News" /></p> </form> </body> </html>
view.php模板可以用来显示选定的新闻条目:
<html> <head> <title> <?php echo $this->escape($this->news['title']); ?> </title> </head> <body> <h1> <?php echo $this->escape($this->news['title']); ?> </h1> <p> <?php echo $this->escape($this->news['content']); ?> </p> <h1>Comments</h1> <?php foreach ($this->comments as $comment) { ?> <p> <?php echo $this->escape($comment['name']); ?> writes: </p> <blockquote> <?php echo $this->escape($comment['comment']); ?> </blockquote> <?php } ?> <h1>Add a Comment</h1> <form action="/add/comment" method="POST"> <input type="hidden" name="newsId" value="<?php echo $this->escape($this->id); ?>" /> <p>Name:<br /><input type="text" name="name" /></p> <p>Comment:<br /><textarea name="comment"></textarea></p> <p><input type="submit" value="Add Comment" /></p> </form> </body> </html>
最后,admin.php模板可以用来批准新闻条目:
<html> <head> <title>News Admin</title> </head> <body> <form action="/admin/approve" method="POST"> <?php foreach ($this->news as $entry) { ?> <p> <input type="checkbox" name="ids[]" value="<?php echo $this->escape($entry['id']); ?>" /> <?php echo $this->escape($entry['title']); ?> <?php echo $this->escape($entry['content']); ?> </p> <?php } ?> <p> Password:<br /><input type="password" name="password" /> </p> <p><input type="submit" value="Approve" /></p> </form> </body> </html>
提示:为了保持简单,这个表单用密码作为验证机制。
使用到模板的地方,你只需要把注释替换成几行代码。如IndexController.php就变成下面这样:
<?php class IndexController extends Zend_Controller_Action { public function indexAction() { /* List the news. */ $db = Zend::registry('db'); $view = Zend::registry('view'); $view->news = $db->getNews(); echo $view->render('index.php'); } public function noRouteAction() { $this->_redirect('/'); } } ?>
因为条理比较清楚,这个程序首页的整个业务逻辑只有四行代码。AddController.php更复杂一点,它需要更多的代码:
<?php class AddController extends Zend_Controller_Action { function indexAction() { $this->_redirect('/'); } function commentAction() { /* Add a comment. */ $filterPost = new Zend_InputFilter($_POST); $db = Zend::registry('db'); $name = $filterPost->getAlpha('name'); $comment = $filterPost->noTags('comment'); $newsId = $filterPost->getDigits('newsId'); $db->addComment($name, $comment, $newsId); $this->_redirect("/view/$newsId"); } function newsAction() { /* Add news. */ $filterPost = new Zend_InputFilter($_POST); $db = Zend::registry('db'); $title = $filterPost->noTags('title'); $content = $filterPost->noTags('content'); $db->addNews($title, $content); $this->_redirect('/'); } function __call($action, $arguments) { $this->_redirect('/'); } } ?>
因为用户在提交表单后被重定向,这个controller不需要视图。
在AdminController.php,你要处理显示管理界面和批准新闻两个action:
<?php class AdminController extends Zend_Controller_Action { function indexAction() { /* Display admin interface. */ $db = Zend::registry('db'); $view = Zend::registry('view'); $view->news = $db->getNews('NEW'); echo $view->render('admin.php'); } function approveAction() { /* Approve news. */ $filterPost = new Zend_InputFilter($_POST); $db = Zend::registry('db'); if ($filterPost->getRaw('password') == 'mypass') { $db->approveNews($filterPost->getRaw('ids')); $this->_redirect('/'); } else { echo 'The password is incorrect.'; } } function __call($action, $arguments) { $this->_redirect('/'); } } ?>
最后是ViewController.php:
<?php class ViewController extends Zend_Controller_Action { function indexAction() { $this->_redirect('/'); } function __call($id, $arguments) { /* Display news and comments for $id. */ $id = Zend_Filter::getDigits($id); $db = Zend::registry('db'); $view = Zend::registry('view'); $view->news = $db->getNews($id); $view->comments = $db->getComments($id); $view->id = $id; echo $view->render('view.php'); } } ?>
虽然很简单,但我们还是提供了一个功能较全的新闻和评论程序。最好的地方是由于有较好的设计,增加功能变得很简单。而且随着Zend Framework越来越成熟,只会变得更好。
更多信息
这个教程只是讨论了ZF表面的一些功能,但现在也有一些其它的资源可供参考。在http://framework.zend.com/manual/有手册可以查询,Rob Allen在http://akrabat.com/zend-framework/介绍了一些他使用Zend Framework的经验,而Richard Thomas也在http://www.cyberlot.net/zendframenotes提供了一些有用的笔记。如果你有自己的想法,可以访问Zend Framework的新论坛:http://www.phparch.com/discuss/index.php/f/289//。
结束语
要对预览版进行评价是很容易的事,我在写这个教程时也遇到很多困难。总的来说,我想Zend Framework显示了承诺,加入的每个人都是想继续完善它。
이 기사가 Zend Framework 프레임워크를 기반으로 하는 모든 사람의 PHP 프로그래밍에 도움이 되기를 바랍니다.
Zend Framework 개발 입문 클래식 튜토리얼과 관련된 더 많은 기사를 보려면 PHP 중국어 웹사이트를 주목하세요!