一个使用Zend framework 1.9写的简单博客程序 - ZF Dream
一个使用Zend framework 1.9写的简单博客程序
我曾用Zend framework 1.8工作,现在用1.9。我做了一个简单的博客程序。我说的博客就是类似Wordpress的东西,可以发文章、加评论,现在还能从Picasa上抓相册。
我是一个Zend framework和MVC的新手,我知道里面有些错误,有一些是我图方便而产生的。同时我也是单元测试的新手,所以里面没有写什么测试数据代码。抱歉。我知道这个程序的缺点,数据库也不太好。但我还是把它发布了,希望能帮助一些人理解Zend_ACL、Zend_Auth、Zend_Form等。我在里面还加了一个图片集功能,用到了Zend_Gdata接口,可以从Picasa读取照片和相册,未来我考虑通过管理界面来增加照片,以及在管理界面做一些特色,如果我有时间的话。
我看过各式各样的博客程序,有些已经过时了,但有些仍然生机勃勃。我在用Zend Toolkit,因为相比图形界面,我更喜欢使用命令行。我提供了完整的Zend framework 1.9博客程序例的下载链接,大家可以在文末的链接处下载。
开发环境:
1)LAMP或WAMP或MAMP
2)配置好的Zendframework 1.9
3)我用eclipse、netbeans或zend studio i5编辑,如果你用zend studio i5,就不用看下面命令行的部分,许多东西已经在里面有了。
我希望你已经配置好了ZF,现在让我们用命令行来创建项目、控制器和动作。符号$代表命令行。(ZF Dream翻译)
这就创建了ZF 1.8+的目录结构。切换到项目所在目录,如cd <项目名称>。输入命令:
ProjectDirectory
ProjectProfileFile
ApplicationDirectory
ConfigsDirectory
ApplicationConfigFile
ControllersDirectory
ControllerFile
ActionMethod
ControllerFile
ModelsDirectory
ViewsDirectory
ViewScriptsDirectory
ViewControllerScriptsDirectory
ViewScriptFile
ViewControllerScriptsDirectory
ViewScriptFile
ViewHelpersDirectory
BootstrapFile
LibraryDirectory
PublicDirectory
PublicIndexFile
HtaccessFile
TestsDirectory
TestPHPUnitConfigFile
TestApplicationDirectory
TestApplicationBootstrapFile
TestLibraryDirectory
TestLibraryBootstrapFile
上面就是显示的目录结构。下面我们来创建控制器和动作:
Creating a controller at //var/www/blog/application/controllers/PostsController.php
Creating an index action method in controller posts
Creating a view script for the index action method at //var/www/blog/application/views/scripts/posts/index.phtml
Creating a controller test file at //var/www/blog/tests/application/controllers/PostsControllerTest.php
Updating project profile '//var/www/blog/.zfproject.xml'
Creating an action named view inside controller at //var/www/blog/application/controllers/PostsController.php
Updating project profile '//var/www/blog/.zfproject.xml'
Creating a view script for the view action method at //var/www/blog/application/views/scripts/posts/view.phtml
Updating project profile '//var/www/blog/.zfproject.xml'
Creating an action named edit inside controller at //var/www/blog/application/controllers/PostsController.php
Updating project profile '//var/www/blog/.zfproject.xml'
Creating a view script for the edit action method at //var/www/blog/application/views/scripts/posts/edit.phtml
Updating project profile '//var/www/blog/.zfproject.xml'
Creating an action named add inside controller at //var/www/blog/application/controllers/PostsController.php
Updating project profile '//var/www/blog/.zfproject.xml'
Creating a view script for the add action method at //var/www/blog/application/views/scripts/posts/add.phtml
Updating project profile '//var/www/blog/.zfproject.xml'
Creating an action named login inside controller at //var/www/blog/application/controllers/IndexController.php
Updating project profile '//var/www/blog/.zfproject.xml'
Creating a view script for the login action method at //var/www/blog/application/views/scripts/index/login.phtml
Updating project profile '//var/www/blog/.zfproject.xml'
Creating an action named logout inside controller at //var/www/blog/application/controllers/IndexController.php
Updating project profile '//var/www/blog/.zfproject.xml'
Creating a view script for the logout action method at //var/www/blog/application/views/scripts/index/logout.phtml
Updating project profile '//var/www/blog/.zfproject.xml'
建立数据库,并添加用户表、评论表和文章表。
`id` int(11) NOT NULL AUTO_INCREMENT,
`Post_id` int(11) NOT NULL,
`Description` varchar(300) NOT NULL,
`Name` varchar(200) NOT NULL,
`Email` varchar(250) NOT NULL,
`Webpage` varchar(200) NOT NULL,
`Postedon` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
`id` int(11) NOT NULL AUTO_INCREMENT,
`Title` varchar(250) NOT NULL,
`Description` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
`id` int(11) NOT NULL AUTO_INCREMENT,
`Username` varchar(200) NOT NULL,
`Password` varchar(250) NOT NULL,
`Role` varchar(10) NOT NULL,
`Name` varchar(200) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
在Bootstrap.php增加如下代码,这样你就可以象__autoload()一样自动加载类。(ZF Dream翻译)
protected function _initAutoload() { $moduleLoader = new Zend_Application_Module_Autoloader(array( 'namespace' => '', 'basePath' => APPLICATION_PATH)); return $moduleLoader; }
在application/configs/application.ini中加上布局和数据库连接信息。完整代码如下:
[production] phpSettings.display_startup_errors = 0 phpSettings.display_errors = 0 includePaths.library = APPLICATION_PATH "/../library" bootstrap.path = APPLICATION_PATH "/Bootstrap.php" bootstrap.class = "Bootstrap" resources.frontController.controllerDirectory = APPLICATION_PATH "/controllers" resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts" resources.view[] = resources.db.adapter = PDO_MYSQL resources.db.params.host = localhost resources.db.params.username = root resources.db.params.password = resources.db.params.dbname = hari_zendblog [staging : production] [testing : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1 [development : production] phpSettings.display_startup_errors = 1 phpSettings.display_errors = 1
编写布局文件:
application/layouts/scripts/layout.phtml
<?php echo $this->doctype() ?> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Bloggers turns to developers</title> <?php echo $this->headLink()->appendStylesheet('/quickstart/public/css/global.css') ?> <?php echo $this->headLink()->prependStylesheet($this->baseUrl().'/css/main.css'); ?> </head> <body> <div id="header" style="background-color: #EEEEEE; height: 30px;"> <div id="header-logo" style="float: left"> <b><a href="<?php echo $this->baseurl(); ?>">ZF Quickstart Application</a></b> <a href="<?php if( Zend_Auth::getInstance()->hasIdentity() ) { $str = "/index/logout"; $log = "Logout"; } else { $str = "/index/login"; $log = "Login"; } echo $this->baseurl().$str; ?>"><?php echo $log; ?></a> </div> </div> <div><?php echo $this->layout()->content ?></div> </body> </html>
你可以通过phpmyadmin或其他方式插入一些一些测试数据,这样前端就能显示出一些文章来。把下面的代码加到IndexController的indexAction中去:(ZF Dream翻译)
public function indexAction() { // action body $posts = new Model_DbTable_Posts(); $result = $posts->getPosts(); /* 下面是分页代码 */ $page = $this->_getParam('page',1); $paginator = Zend_Paginator::factory($result); $paginator->setItemCountPerPage(2); /* 每页的文章数。我写成2这样你可以容易得看到分页效果. */ $paginator->setCurrentPageNumber($page); $this->view->paginator = $paginator; }
以下是完整的IndexController.php代码:
<?php class IndexController extends Zend_Controller_Action { public function init() { /* Initialize action controller here */ } public function indexAction() { // action body $posts = new Model_DbTable_Posts(); $result = $posts->getPosts(); $page = $this->_getParam('page',1); $paginator = Zend_Paginator::factory($result); $paginator->setItemCountPerPage(2); $paginator->setCurrentPageNumber($page); $this->view->paginator = $paginator; } public function loginAction() { $loginForm = new Form_Login(); $redirect = $this->getRequest()->getParam('redirect', 'index/index'); $loginForm->setAttrib('redirect', $redirect ); $auth = Zend_Auth::getInstance(); if(Zend_Auth::getInstance()->hasIdentity()) { $this->_redirect('/index/hello'); } else if ($this->getRequest()->isPost()) { if ( $loginForm->isValid($this->getRequest()->getPost()) ) { $username = $this->getRequest()->getPost('username'); $pwd = $this->getRequest()->getPost('pass'); $authAdapter = new Model_AuthAdapter($username, $pwd); $result = $auth->authenticate($authAdapter); if(!$result->isValid()) { switch ($result->getCode()) { case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID: $this->view->error = 'user credentials not found'; } } else { //Successfully logged in $this->_redirect( $redirect ); } } } $this->view->loginForm = $loginForm; // $this->_redirect($redirectUrl); } public function logoutAction() { $auth = Zend_Auth::getInstance(); $auth->clearIdentity(); $this->_redirect('/'); } }
现在你需要创建一个文章表的连接,以便我们来做插入和更改操作。这部分内容写到application/Models/DbTable/Posts.php里去。
<?php class Model_DbTable_Posts extends Zend_Db_Table_Abstract { protected $_name = 'posts'; public function getPosts() { $orderby = array('id DESC'); $result = $this->fetchAll('1', $orderby ); return $result->toArray(); } public function getPost( $id ) { $id = (int)$id; $row = $this->fetchRow('id = ' . $id); if (!$row) { throw new Exception("Count not find row $id"); } return $row->toArray(); } /* * Add new posts */ public function savePost( $post ) { $data = array( 'Title'=> $post['Title'], 'Description'=> $post['Description']); $this->insert($data); } /* * Update old posts */ public function updatePost( $post ) { $data = array( 'id'=> $post['id'], 'Title'=> $post['Title'], 'Description'=> $post['Description']); $where = 'id = '.$post['id']; $this->update($data , $where ); } }
创建application/Models/DbTable/Users.php:
<?php class Model_DbTable_Users extends Zend_Db_Table_Abstract { protected $_name = 'users'; public function findCredentials($username, $pwd) { $select = $this->select()->where('username = ?', $username) ->where('password = ?', $this->hashPassword($pwd)); $row = $this->fetchRow($select); if($row) { return $row; } return false; } protected function hashPassword($pwd) { return md5($pwd); } }
创建application/Models/DbTable/Comments.php:
<?php class Model_DbTable_Comments extends Zend_Db_Table_Abstract { protected $_name = 'comments'; public function getComments( $postid ) { $result = $this->fetchAll( "post_id = '$postid'" ); return $result->toArray(); } public function saveComment( $commentForm ) { $data = array('post_id' => $commentForm['id'] , 'Description' => $commentForm['comment'], 'Name' => $commentForm['name'], 'Email' => $commentForm['email'], 'Webpage' => $commentForm['webpage'] ); $this->insert($data); } }
现在我们来建立登录表单/application/forms/Login.php:(ZF Dream翻译)
<?php class Form_Login extends Zend_Form { public function __construct() { parent::__construct($options); $this->setName('UserLogin'); $username = new Zend_Form_Element_Text('username'); $username->setLabel('User Name') ->setRequired(true) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $pass = new Zend_Form_Element_Password('pass'); $pass->setLabel('Password') ->setRequired(true) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $submit = new Zend_Form_Element_Submit('submit'); $redirect = new Zend_Form_Element_Hidden('redirect'); $submit->setAttrib('id', 'submitbutton'); $this->addElements( array ( $username, $pass, $submit)); } }
创建一个表单,用于发文章:
application/forms/posts.php
<?php class Form_Post extends Zend_Form { public function __construct() { parent::__construct($options); $this->setName('Posts'); $id = new Zend_Form_Element_Hidden('id'); $title = new Zend_Form_Element_Text('Title'); $title->setLabel('Title') ->setRequired(true) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $description = new Zend_Form_Element_Textarea('Description'); $description->setLabel('Description') ->setRequired(true) ->setAttrib('rows',20) ->setAttrib('cols',50) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $submit = new Zend_Form_Element_Submit('submit'); $submit->setAttrib('id', 'submitbutton'); $this->addElements( array( $id, $title, $description, $submit )); } }
创建一个表单,用于发评论:(ZF Dream翻译)
application/forms/Comments.php
<?php class Form_Comments extends Zend_Form { public function __construct() { $acl = new Model_Acl(); $identity = Zend_Auth::getInstance()->getIdentity(); /* * 检查是否允许发评论 */ if( Zend_Auth::getInstance()->hasIdentity() && $acl->isAllowed( $identity['role'] ,'comments','add') ) { parent::__construct($options); $this->setName('Comments'); $id = new Zend_Form_Element_Hidden('id'); $name = new Zend_Form_Element_Text('name'); $name->setLabel('Your Name') ->setRequired(true) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $email = new Zend_Form_Element_Text('email'); $email->setLabel('Email') ->setRequired(true) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $webpage = new Zend_Form_Element_Text('webpage'); $webpage->setLabel('Webpage') ->setRequired(true) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $comment = new Zend_Form_Element_Textarea('comment'); $comment->setLabel('Comments') ->setRequired(true) ->setAttrib('rows',7) ->setAttrib('cols',30) ->addFilter('StripTags') ->addFilter('StringTrim') ->addValidator('NotEmpty'); $submit = new Zend_Form_Element_Submit('submit'); $submit->setAttrib('id', 'submitbutton'); $this->addElements( array ($id, $name, $email, $webpage, $comment, $submit)); } } }
创建application/Models/Acl.php
<?php class Model_Acl extends Zend_Acl { public function __construct() { /* * 增加一个新角色"guest" * Guest能浏览所有内容 */ $this->addRole(new Zend_Acl_Role('guest')); /* * 增加user角色继承guest * Users能发评论 */ $this->addRole(new Zend_Acl_Role('user'), 'guest'); /* * 增加blogger角色继承ueser * Bloggers能发文章 */ $this->addRole(new Zend_Acl_Role('blogger'), 'user'); /* * 增加admin角色继承blogger * 拥有所有的权利 */ $this->addRole(new Zend_Acl_Role('admin'), 'blogger'); //增加posts资源 $this->add(new Zend_Acl_Resource('posts')); //增加edit资源,继承posts //$this->add(new Zend_Acl_Resource('edit'), 'posts'); //增加edit资源 //$this->add(new Zend_Acl_Resource('add'), 'posts'); //最后,我们让guest可以浏览所有的内容 $this->allow('guest', 'posts', 'view'); // Bloggers能发文章,修改文章 $this->allow('blogger', 'posts', 'edit'); $this->allow('blogger', 'posts', 'add'); } }
创建application/Models/AuthAdapter.php
<?php class Model_AuthAdapter implements Zend_Auth_Adapter_Interface { protected $username; protected $password; protected $user; public function __construct($username, $password) { $this->username = $username; $this->password = $password; $this->user = new Model_DbTable_Users(); } public function authenticate() { $match = $this->user->findCredentials($this->username, $this->password); //var_dump($match); if(!$match) { $result = new Zend_Auth_Result(Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID, null); } else { $user = current($match); $result = new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $user); } return $result; } }
创建application/views/helpers/BaseUrl.php
<?php class Zend_View_Helper_BaseUrl { function baseUrl() { $fc = Zend_Controller_Front::getInstance(); return $fc->getBaseUrl(); } } ?>
创建application/views/helpers/LinkTo.php
<?php class Zend_View_Helper_LinkTo { protected static $baseUrl = null; public function linkTo($path) { if (self::$baseUrl === null) { $request = Zend_Controller_Front::getInstance()->getRequest(); $root = '/' . trim($request->getBaseUrl(), '/'); if ($root == '/') { $root = ''; } self::$baseUrl = $root . '/'; } return self::$baseUrl . ltrim($path, '/'); } }
创建application/views/helpers/LoggedInUser.php
<?php class Zend_View_Helper_LoggedInUser { protected $_view; function setView($view) { $this->_view = $view; } function loggedInUser() { $auth = Zend_Auth::getInstance(); if($auth->hasIdentity()) { $logoutUrl = $this->_view->linkTo('auth/logout'); $user = $auth->getIdentity(); $username = $this->_view->escape(ucfirst($user->username)); $string = 'Logged in as ' . $username . ' | <a href="' . $logoutUrl . '">Log out</a>'; } else { $loginUrl = $this->_view->linkTo('auth/identify'); $string = '<a href="'. $loginUrl . '">Log in</a>'; } return $string; } }
用下面的内容来替换掉默认的application/views/scripts/index/index.phtml:
<?php if (count($this->paginator)): ?> <?php foreach ($this->paginator as $post ): ?> <div class="post"> <div class="title"><a href="<?php echo $this->baseUrl()."/posts/view/id/".$post['id']; ?>"><?php echo $this->escape($post['Title']); ?></a></div> <div class="description"><?php echo nl2br(substr( strip_tags( $post['Description']), 0 , 300)); ?></div> <div class="commentscount">Comments( count )</div> </div> <?php endforeach; ?> <?php endif; ?> <div class="pagination"><?php echo $this->paginationControl($this->paginator, 'Sliding', '/partials/my_pagination_control.phtml'); ?></div>
用下面的内容来替换application/views/scripts/index/login.phtml
<?php echo $this->errors; ?> <?php echo $this->loginForm; ?>
用下面的内容来替换application/views/scripts/index/logout.phtml
<div>Successfully Logged out</div>
创建application/views/scripts/partials/my_pagination_control.phtml(你可以在不同的目录里放占位符,我忘记替换了。)(ZF Dream翻译)
<?php if ($this->pageCount): ?> <div class="paginationControl"> <!-- 上一页链接 --> <?php if (isset($this->previous)): ?> <a href="<?php echo $this->url(array('page' => $this->previous)); ?>"> < Previous </a> | <?php else: ?> <span class="disabled">< Previous</span> | <?php endif; ?> <!-- 页码链接 --> <?php foreach ($this->pagesInRange as $page): ?> <?php if ($page != $this->current): ?> <a href="<?php echo $this->url(array('page' => $page)); ?>"> <?php echo $page; ?> </a> | <?php else: ?> <?php echo $page; ?> | <?php endif; ?> <?php endforeach; ?> <!-- 下一页链接 --> <?php if (isset($this->next)): ?> <a href="<?php echo $this->url(array('page' => $this->next)); ?>"> Next > </a> <?php else: ?> <span class="disabled">Next ></span> <?php endif; ?> </div> <?php endif; ?> <?php /* * Drop down paginaton example */ /* ?> <?php if ($this->pageCount): ?> <select id="paginationControl" size="1"> <?php foreach ($this->pagesInRange as $page): ?> <?php $selected = ($page == $this->current) ? ' selected="selected"' : ''; ?> <option value="<?php echo $this->url(array('page' => $page));?>"<?php echo $selected ?>> <?php echo $page; ?> </option> <?php endforeach; ?> </select> <?php endif; ?> <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype.js"> </script> <script type="text/javascript"> $('paginationControl').observe('change', function() { window.location = this.options[this.selectedIndex].value; }) </script> */?>
替换application/views/scripts/posts/add.phtml
<div><?php echo $this->postForm; ?></div>
替换application/views/scripts/posts/edit.phtml
<h2>Edit Post</h2> <div><?php echo $this->postForm; ?></div>
替换application/views/scripts/posts/view.phtml
<div class="title"><?php echo $this->post['Title']; ?></div> <div class="description"><?php echo $this->post['Description']; ?></div> <?php $acl = new Model_Acl(); $identity = Zend_Auth::getInstance()->getIdentity(); if( Zend_Auth::getInstance()->hasIdentity() && $acl->isAllowed( $identity['role'] ,'posts','edit') ) : ?> <div><a href="<?php echo $this->baseurl().$this->edit; ?>">Edit</a></div> <?php endif; ?> <div class="comments"> <?php if( count($this->comments) ) : ?> <?php foreach( $this->comments as $comment ) : ?> <div class="postedby"><a href="<?php echo $comment['Webpage']; ?>" ><?php echo $this->escape( $comment['Name'] ); ?></a> on <span><?php echo $this->escape( date( 'd-m-Y', strtotime($comment['Postedon']) ) ); ?></span></div> <div class="comment"><?php echo $this->escape( $comment['Description'] ); ?></div> <?php endforeach; ?> <?php else : ?> <div>No comments</div> <?php endif; ?> </div> <div class="newcomments"> <?php if( Zend_Auth::getInstance()->hasIdentity() ) : echo $this->commentsForm; else : ?>Login to Post comments<?php endif; ?> </div>
我希望我没漏掉什么。如果有什么错误请通知我。你可以从git获得一个工作副本。我参考了超过50个博客程序。学Zend Framework的同时我还做过一个自定义搜索引擎。我之前的文章里发过。
感谢各位,我希望你们喜欢我的小博客。如果有那里不清楚,请告诉我。你可以发邮件给我,当然最好就在下面发表评论,你可以从http://github.com/harikt/zendblog获得一份代码,而且要象http://zendcasts.com的Jon Lembord一般大言不惭地说:谢谢github和git提供这样精彩的东东。
原文:http://www.harikt.com/content/simple-blog-using-zend-framework-19
2010年8月16日 00:52
thx for u code.
2010年9月20日 00:21
有分页,有生成表单,就是没有多文件上传。。。。
2011年3月24日 17:16
请问你这个博客是zf的吗?
2011年4月20日 15:58
呵呵,很不错的例子