LicenseSyndicate This BlogPlanet PHPPEAR in March 2010 - Official Blog of the PEAR Group/PEAR President
Friday, March 19. 2010 A Simple Resource Injector for ZF Action Controllers - Matthew Weier O'Phinney Friday, March 19. 2010 Ada Lovelace Day 2010 is March 24th! - Maggie Nelson Friday, March 19. 2010 Memory usage in PHP - Paul Reinheimer Friday, March 19. 2010 WS-* for PHP - Rob Richards Friday, March 19. 2010 SabreDAV 1.0.9 released & CalDAV news - Evert Pot Friday, March 19. 2010 SimpleDNSd: new features & bugfix - Mark Karpeles Friday, March 19. 2010 Python. Good, bad, evil -2-: Native sets - Tobias Schlitt Thursday, March 18. 2010 The 15 Minute Rule Of Software Development - Brandon Savage Thursday, March 18. 2010 Zend Studio formatter for Zend Framework and ATK - TechPortal Thursday, March 18. 2010 StatisticsLast entry: 2010-02-23 23:27
405 entries written
1502 comments have been made
|
Wednesday, May 14. 2008
Posted by Pádraic Brady
in PHP General, PHP Security, Zend Framework
Comments (24) Trackbacks (0) Defined tags for this entry: application security, mvc, phing, php, php general, php security, zend framework Related entries by tags: PHP Framework Benchmarks: Entertaining But Ultimately Useless Is PHP A Worthy Template Language? Well, of course it is... The Mysteries Of Asynchronous Processing With PHP - Part 3: Implementation With Spawned Child Processes Using Simple Scripts Or Zend Framework The Mysteries Of Asynchronous Processing With PHP - Part 2: Making Zend Framework Applications CLI Accessible The Mysteries Of Asynchronous Processing With PHP - Part 1: Asynchronous Benefits, Task Identification and Implementation Methods View as PDF: This entry | This month | Full blog Example Zend Framework Blog Application Tutorial: Part 8: Creating and Editing Blog Entries with a dash of HTMLPurifier
There's nothing quite like having a functioning application emerge out of the controlled chaos we know as The Development Process. In Part 8 of the ongoing saga describing how to build a real world blog application using the Zend Framework we finally reach the point at which we concentrate on blog entries. At the end of this Part, we will be able to create and edit entries in preparation for Part 9 when we will explore displaying them to the world!
Previously: Example Zend Framework Blog Application Tutorial - Part 7: Authorisation with Zend_Acl and Revised Styling The reason displaying entries is not addressed here is simple. Display requires a lot of Zend_View work which is deserving of an article to itself before we go too far. A few of you have already noted in comments about our suspicious lack of View Helper usage So, on with the show already! Step 1: Adding an Entry Controller and Add Action TemplateThe first step to creating new entries will be writing an Admin_EntryController class (the prefix is needed since it's situated in the Admin Module) with an addAction() method and matching template.The template will utilise a new Zend_Form object for gathering the input used to create a new entry, as well as offering an editing View. We'll start with the Controller, added at /application/admin/controllers/EntryController.php:<?php class Admin_EntryController extends Zend_Controller_Action { public function addAction() {} public function listAction() {} public function editAction() {} public function deleteAction() {} } The EntryController needs four basic methods. We intend creating, editing and deleting entries, as well as listing all entries to an Author to select those options. We'll concentrate on the addAction() method first, so let's add an appropriate template at /application/admin/views/scripts/entry/add.phtml which has an old friend referred to:<p>Where I am not understood, it shall be concluded that something very useful and profound is couched underneath.<br /><em>- Jonathan Swift</em></p> <?php if($this->failedValidation): ?> <p class="error">Some problems were detected with the submitted form.</p> <?php endif; ?> <?php echo $this->entryForm ?> Let's accompany our blog entry forms with a little Jonathan Swift for inspiration To keep our styling synchronised with our intended form output, add the following to /public/css/style.css:textarea { width: 76%; height: 30em; } This should provide a decent styling for textareas for most browsers. Perhaps even Safari which I noticed recently is displaying some of my text fields poorly. Step 2: Assembling the Entry Form with Zend_FormWe've already had a fairly detailed look at Zend_Form back in Part 6 when we wrote a subclass to contain some standard and specific decorator arrays for form elements, and created our Login Form example. The same principles used there also apply here with very few changes. Once again we are using the shorter array-based syntax over multiple method calls. One of the differences to take note of is that I've used two new optionsattribs and value which no form developer could live without!We'll start with a new class called ZFBlog_Form_EntryAdd located at /library/ZFBlog/Form/EntryAdd.php:<?php class ZFBlog_Form_EntryAdd extends ZFBlog_Form { public function init() { $this->setAction('/admin/entry/add'); // Display Group #1 : Entry Data $this->addElement('text', 'title', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Title:', 'attribs' => array( 'maxlength' => 200, 'size' => 80 ), 'validators' => array( array('StringLength', false, array(3,200)) ), 'required' => true )); $this->addElement('text', 'date', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Date:', 'attribs' => array( 'maxlength' => 16, 'size' => 16 ), 'value' => Zend_Date::now()->toString('yyyy-MM-dd HH:mm'), 'validators' => array( array('Date', false, array('yyyy-MM-dd HH:mm', 'en')) ), 'required' => true )); $this->addElement('textarea', 'entrybody', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Entry Body:', 'required' => true )); $this->addElement('textarea', 'entrybodyextended', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Extended Body:' )); $this->addDisplayGroup( array('title','date','entrybody','entrybodyextended'), 'entrydata', array( 'disableLoadDefaultDecorators' => true, 'decorators' => $this->_standardGroupDecorator, 'legend' => 'Entry' ) ); // Display Group #2 : Submit $this->addElement('submit', 'submit', array( 'decorators' => $this->_buttonElementDecorator, 'label' => 'Save' )); $this->addDisplayGroup( array('submit'), 'entrydatasubmit', array( 'disableLoadDefaultDecorators' => true, 'decorators' => $this->_buttonGroupDecorator, 'class' => 'submit' ) ); } } I threw in a sprinkling of Zend_Date above to format the current date to a format compatible with a MySQL database. Zend_Date is also used in the background by the Zend_Validator_Date class which is why the date formatting is identical to both. Note that the formatting is not the PHP regular style used by the date() function, but instead uses ISO format specifiers. This offers a great deal of flexibility if you want something other than MySQL formatted dates. Finally note that there is no "required" flag for the "entrybodyextended" element since an extended body is optional. We're not quite done yet. Our two textareas, as discussed way back in Part 1, are being designed to accept HTML input since I really can't be bothered to play with custom formatting tags and such. This obviously raises the risk that I, another author, or someone who's guessed my password may, either by mistake or intent, add some Cross-Site Scripting (XSS) into the mix and introduce a devastating security exploit. We can't have that now! Step 3: Filtering Entries using a HTMLPurifier based Custom FilterWhat we will do now is attach a custom filter to the textareas containing our Entry data that cleans up any HTML input, removes XSS, and as a bonus converts any non-HTML input into formatted HTML. This, for example, covers our text paragraphs by wrapping them in<p> tags.My favourite library for achieving this is HTMLPurifier which I consider one of those much under utilised libraries in PHP. To my knowledge, there is nothing out there to beat its comprehensive feature list. Its finest feature is that it actually understands HTML across multiple standards. Input is tokenised, parsed, passed through a whitelist (the opposite of a detection blacklist), reformed as perfectly correct valid output, and then it can optionally wrap stuff like plain text in paragraphs. All for your preferred DTD. Either you're using HTMLPurifier, or you are using something second or third rate, and I have no qualms whatsoever in stating that as a fact. I cannot praise this library more highly. To start, you'll need to download a copy of HTMLPurifer 3.1.0rc1 (which is the latest release at the time of writing) and copy the contents of the package's /library directory into our blog application's /library. Since HTMLPurifer follows the PEAR Convention for class naming and file location, we need make no changes to our include_path. If you prefer, you can also install HTMLPurifer from its PEAR channel as described on the download page. Although it's a release candidate I haven't run into any problems using it other than my unexplainable habit of mispelling HTMLPurifier as HTMLPurifer which has led to a few frustrating hair pulling moments!HTMLPurifier's perfection is not without a performance cost. To improve performance it will utilise a HTML definition cache. Add a new base directory at /cache/htmlpurifier and grant permissions sufficient to let the webserver write files there. Don't get too caught up over performance as security isn't an area you want to needlessly play Scrooge with Let's introduce our custom filter classes. I'm saving them using a mirror directory structure of the Zend Framework as usual. Here a standard one I usually use with HTMLPurifier saved to /library/ZFBlog/Filter/HTMLPurifier.php:<?php class ZFBlog_Filter_HTMLPurifier implements Zend_Filter_Interface { protected $_htmlPurifier = null; public function __construct($options = null) { $config = null; if (!is_null($options)) { $config = HTMLPurifier_Config::createDefault(); foreach ($options as $option) { $config->set($option[0], $option[1], $option[2]); } } $this->_htmlPurifier = new HTMLPurifier($config); } public function filter($value) { return $this->_htmlPurifier->purify($value); } } HTMLPurifier options have three distinct elements we can pass to this filter as an array. We could stop here, passing filter options for every form, but this is a very general filter class whose sole purpose is to pass options into HTMLPurifier, so let's add a subclass of this specifically for HTML textual input with predefined options at /library/ZFBlog/Filter/HtmlBody.php:<?php class ZFBlog_Filter_HtmlBody extends ZFBlog_Filter_HTMLPurifier { public function __construct($newOptions = null) { $options = array( array('Cache', 'SerializerPath', Bootstrap::$root . '/cache/htmlpurifier' ), array('HTML', 'Doctype', 'XHTML 1.0 Strict'), array('HTML', 'Allowed', 'p,em,h1,h2,h3,h4,h5,strong,a[href],ul,ol,li,code,pre,' .'blockquote,img[src|alt|height|width],sub,sup' ), array('AutoFormat', 'Linkify', 'true'), array('AutoFormat', 'AutoParagraph', 'true') ); if (!is_null($newOptions)) { // I'll let HTMLPurifier overwrite original options // with new ones rather than filter them myself $options = array_merge($options, $newOptions); } parent::__construct($options); } } This new subclass passes specific options to HTMLPurifier. We provide the path to the cache directory created previously, inform the library our output should conform to XHTML 1.0 Strict, add a whitelist of allowed tags and attributes, and finally enable two optional formatting helpers to auto paragraph output (wrapped with <p> tags) and transform URLs into hyperlinks. If ZFBlog_Filter_HtmlBody needs further adjustment we can pass it options when attaching this filter to our form elements.This really is how HTMLPurifer works. By using sensible defaults, configuration before use is extremely simple. With our two custom filters in tow, we now need to make sure the Zend Framework can actually find them! We've done this previously actually when registering a custom decorator path with Zend_Form. Let's repeat the process. Here's an updated ZFBlog_Form class from /library/ZFBlog/Form.php:<?php class ZFBlog_Form extends Zend_Form { protected $_standardElementDecorator = array( 'ViewHelper', array('LabelError', array('escape'=>false)), array('HtmlTag', array('tag'=>'li')) ); protected $_buttonElementDecorator = array( 'ViewHelper' ); protected $_standardGroupDecorator = array( 'FormElements', array('HtmlTag', array('tag'=>'ol')), 'Fieldset' ); protected $_buttonGroupDecorator = array( 'FormElements', 'Fieldset' ); protected $_noElementDecorator = array( 'ViewHelper' ); public function __construct($options = null) { // Path setting for custom classes MUST ALWAYS be first! $this->addElementPrefixPath('ZFBlog_Form_Decorator', 'ZFBlog/Form/Decorator/', 'decorator'); $this->addElementPrefixPath('ZFBlog_Filter', 'ZFBlog/Filter/', 'filter'); $this->_setupTranslation(); parent::__construct($options); $this->setAttrib('accept-charset', 'UTF-8'); $this->setDecorators(array( 'FormElements', 'Form' )); } protected function _setupTranslation() { if (self::getDefaultTranslator()) { return; } $path = Bootstrap::$root . '/translate/forms.php'; $translate = new Zend_Translate('array', $path, 'en'); self::setDefaultTranslator($translate); } } Now Zend_Form can use our custom filters. The last thing we do is attach our new HtmlBody custom filter to our new form along with a few other filters for good measure: <?php class ZFBlog_Form_EntryAdd extends ZFBlog_Form { public function init() { $this->setAction('/admin/entry/add'); // Display Group #1 : Entry Data $this->addElement('text', 'title', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Title:', 'attribs' => array( 'maxlength' => 200, 'size' => 80 ), 'validators' => array( array('StringLength', false, array(3,200)) ), 'filters' => array('StringTrim'), 'required' => true )); $this->addElement('text', 'date', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Date:', 'attribs' => array( 'maxlength' => 16, 'size' => 16 ), 'value' => Zend_Date::now()->toString('yyyy-MM-dd HH:mm'), 'validators' => array( array('Date', false, array('yyyy-MM-dd HH:mm', 'en')) ), 'required' => true )); $this->addElement('textarea', 'entrybody', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Entry Body:', 'filters' => array('HtmlBody'), 'required' => true )); $this->addElement('textarea', 'entrybodyextended', array( 'decorators' => $this->_standardElementDecorator, 'label' => 'Extended Body:', 'filters' => array('HtmlBody') )); $this->addDisplayGroup( array('title','date','entrybody','entrybodyextended'), 'entrydata', array( 'disableLoadDefaultDecorators' => true, 'decorators' => $this->_standardGroupDecorator, 'legend' => 'New Entry' ) ); // Display Group #2 : Submit $this->addElement('submit', 'submit', array( 'decorators' => $this->_buttonElementDecorator, 'label' => 'Save' )); $this->addDisplayGroup( array('submit'), 'entrydatasubmit', array( 'disableLoadDefaultDecorators' => true, 'decorators' => $this->_buttonGroupDecorator, 'class' => 'submit' ) ); } } Let's get this form attached to a View now. Step 4: Output Form from Add Action TemplateLet's revise our EntryController to pass our sparkling new form to the addAction() method's associated template.<?php class Admin_EntryController extends Zend_Controller_Action { public function addAction() { $form = new ZFBlog_Form_EntryAdd; if (!$this->getRequest()->isPost()) { $this->view->entryForm = $form; return; } elseif (!$form->isValid($_POST)) { $this->view->failedValidation = true; $this->view->entryForm = $form; return; } } } Now, keeping in mind we should keep track of what error messages need to be cleaned up into a shorter message (as I noted previously, I like short error messages attached to element labels), let's add the Zend_Validate_Date default errors for replacement in our translation array in /translate/forms.php:<?php return array( Zend_Validate_NotEmpty::IS_EMPTY => 'Required', Zend_Validate_StringLength::TOO_SHORT => 'Minimum Length of %min%', Zend_Validate_StringLength::TOO_LONG => 'Maximum Length of %max%', Zend_Validate_Date::NOT_YYYY_MM_DD => 'Must use YYYY-MM-DD format', Zend_Validate_Date::INVALID => 'Not valid date', Zend_Validate_Date::FALSEFORMAT => 'Invalid date format', ); Let's give it a whirl! Open up http://zfblog/admin/entry/add in your browser (you may need to login as Joe Bloggs first with the username "joebloggs" and password "password" setup earlier) and marvel at the new form.We're still not done. If a valid form is submitted we're going to need to store it to the database! Step 5: Storing New Enties to the DatabaseWe've already put in place an Entries Model when we were setting up our database, so saving entries is just a simple addition to our EntryController.<?php class Admin_EntryController extends Zend_Controller_Action { public function addAction() { $form = new ZFBlog_Form_EntryAdd; if (!$this->getRequest()->isPost()) { $this->view->entryForm = $form; return; } elseif (!$form->isValid($_POST)) { $this->view->failedValidation = true; $this->view->entryForm = $form; return; } $values = $form->getValues(); $table = new Entries; $data = array( 'title' => $values['title'], 'date' => $values['date'], 'author' => Zend_Auth::getInstance()->getIdentity()->username, 'author_id' => Zend_Auth::getInstance()->getIdentity()->id, 'body' => $values['entrybody'], 'extended_body' => $values['entrybodyextended'] ); $table->insert($data); $this->view->entrySaved = true; } } I've left the class as is, but at some point I'll refactor this. One of the few worthwhile aesthetic goals of any source code is to keep methods as short as possible. Almost half the above method is mapping form names to database field names. If used a more direct convention, we could have saved reading space and had a shorter method. We now just need to add another of those persistently untidy confirmation messages to be cleaned up later to our Add Action's template <p>Where I am not understood, it shall be concluded that something very useful and profound is couched underneath.<br /><em>- Jonathan Swift</em></p> <?php if($this->failedValidation): ?> <p class="error">Some problems were detected with the submitted form.</p> <?php endif; ?> <?php if($this->entrySaved): ?> <p class="success">New entry has been saved.</p> <?php else: ?> <?php echo $this->entryForm ?> <?php endif; ?> Go ahead and try out the new entry addition. As noted in the introduction we'll cover the actual display of Entries in the next Part, so for now check out the results on the database using PhpMyAdmin or similar. You notice paragraphs are correctly wrapped with <p> and links linkified as HTMLPurifier makes it's presence felt. Tags and attributes that were not added to our whitelist will also be stripped.Step 6: Editing EntriesEditing entries is a fairly simple addition to our zoo. In fact, it's necessary. We already have in place a form class for inputting entries and it's only a small step from there to add in the old values for editing.The only hoop to jump through is reversing the two default formatting steps that HTMLPurifier performs before entries are saved to the database. This can be a confusing problem because many people, I suspect, assume populating Zend_Form with values will output those values exactly. Not so. If you have added Filters to a form object you have actually told Zend_Form that only values passing this filter are entirely valid, so guess what? Yes, populating data is filtered when added to a form object. Since here we filter all entry body data through HTMLPurifier, while the actual input from the user is not, we need to disable this filter before displaying the populated edit form. There is another strategy you could also use. Instead of applying the filter to the form, apply it instead within the Model using overridden save() and update() methods. I've elected not to do this since I quite like having the filtering as part of the form object itself. It doesn't change the fact that that hoop exists, but locating it in the Model could aid in reuse since it's now decoupled from a form object and managed closer to the database. But anyway, that's what Refactoring is for! So let's forge ahead. We'll start with implementing the listAction() for our Entry Controller so we can view a list of all entries for editing/deletion. For the moment this does not include paging. <?php class Admin_EntryController extends Zend_Controller_Action { public function addAction() { $form = new ZFBlog_Form_EntryAdd; if (!$this->getRequest()->isPost()) { $this->view->entryForm = $form; return; } elseif (!$form->isValid($_POST)) { $this->view->failedValidation = true; $this->view->entryForm = $form; return; } $values = $form->getValues(); $table = new Entries; $data = array( 'title' => $values['title'], 'date' => $values['date'], 'author' => Zend_Auth::getInstance()->getIdentity()->username, 'author_id' => Zend_Auth::getInstance()->getIdentity()->id, 'body' => $values['entrybody'], 'extended_body' => $values['entrybodyextended'] ); $table->insert($data); $this->view->entrySaved = true; } public function listAction() { $table = new Entries; // SELECT title, date, id FROM entries $rows = $table->fetchAll( $table->select()->from($table, array('title','date','id')) ); $this->view->entries = $rows->toArray(); } public function editAction() {} public function deleteAction() {} } The associated view simply iterates the array of entries over a foreach construct to replicate an entry list. /application/admin/views/scripts/entry/list.phtml<?php if (count($this->entries) > 0): ?> <ul class="entry-list"> <?php foreach($this->entries as $entry): ?> <li> <a href="/admin/entry/edit/id/<?php echo $entry['id'] ?>"> <?php echo $entry['title'] ?></a> - <a href="/admin/entry/edit/id/<?php echo $entry['id'] ?>">Edit</a> | <a href="/admin/entry/delete/id/<?php echo $entry['id'] ?>">Delete</a> </li> <?php endforeach; ?> </ul> <?php else: ?> <p class="error">There are no entries for this blog.</p> <?php endif; ?> It looks pretty darn ugly, but will fit for now. Logged in as an Author, you can view the list of existing entries assuming you've added a few test ones while testing our new add entry functionality. Each will have a link to edit or delete that entry. We'll cover the URL form I used shortly. Let's create a new form for editing entries. Luckily we have the Entry Add form, so all we need do is subclass this to make a few small adjustments! Add this form as /library/ZFBlog/Form/EntryEdit.php:<?php class ZFBlog_Form_EntryEdit extends ZFBlog_Form_EntryAdd { public function init() { parent::init(); $this->setAction('/admin/entry/edit'); // What entry id are we editing?! $this->addElement('hidden', 'id', array( 'decorators' => $this->_noElementDecorator, 'validators' => array( 'Digits' ), 'required' => true )); $this->getDisplayGroup('entrydata')->setLegend('Edit Entry'); $this->getDisplayGroup('entrydata')->addElement( $this->getElement('id') ); } } Isn't a little reuse worthwhile? Before displaying our Edit Form we need to populate it with the data we're editing. This involves capturing the entry from the database, reversing any HTMLPurifier filtering, disabling the Form's filter chain, and only then re-populating the form. We need to be careful the workflow only disables filters for form display - they should remain in force for any submitted edits. <?php class Admin_EntryController extends Zend_Controller_Action { public function addAction() { $form = new ZFBlog_Form_EntryAdd; if (!$this->getRequest()->isPost()) { $this->view->entryForm = $form; return; } elseif (!$form->isValid($_POST)) { $this->view->failedValidation = true; $this->view->entryForm = $form; return; } $values = $form->getValues(); $table = new Entries; $data = array( 'title' => $values['title'], 'date' => $values['date'], 'author' => Zend_Auth::getInstance()->getIdentity()->username, 'author_id' => Zend_Auth::getInstance()->getIdentity()->id, 'body' => $values['entrybody'], 'extended_body' => $values['entrybodyextended'] ); $table->insert($data); $this->view->entrySaved = true; } public function listAction() { $table = new Entries; $rows = $table->fetchAll( $table->select()->from($table, array('title','date','id')) ); $this->view->entries = $rows->toArray(); } public function editAction() { $form = new ZFBlog_Form_EntryEdit; if (!$this->getRequest()->isPost()) { $table = new Entries; $rowset = $table->find( $this->_getParam('id') ); if (count($rowset) == 0) { $this->view->failedFind = true; return; } $form->setElementFilters(array()); // disable all element filters $this->_repopulateForm($form, $rowset->current()); $this->view->entryForm = $form; return; } elseif (!$form->isValid($_POST)) { $this->view->failedValidation = true; $this->view->entryForm = $form; return; } $values = $form->getValues(); $table = new Entries; $data = array( 'title' => $values['title'], 'date' => $values['date'], 'body' => $values['entrybody'], 'extended_body' => $values['entrybodyextended'] ); $table->update($data, $table->getAdapter()->quoteInto('id = ?', $values['id']) ); $this->view->entrySaved = true; } public function deleteAction() { } protected function _repopulateForm($form, $entry) { $values = array( 'title' => $this->_reverseAutoFormat($entry->title), 'date' => $entry->date, 'entrybody' => $this->_reverseAutoFormat($entry->body), 'entrybodyextended' => $this->_reverseAutoFormat($entry->extended_body), 'id' => $this->_getParam('id') ); $form->populate($values); } protected function _reverseAutoFormat($string) { $string = preg_replace("/\<p\>/", '', $string); $string = preg_replace("/\<\/p\>/", "\n\n", $string); $string = preg_replace("/\<a[^>]*\>/", '', $string); $string = preg_replace("/\<\/a\>/", '', $string); $string = html_entity_decode($string, ENT_QUOTES, 'UTF-8'); return $string; } } We're knee deep in complexity now... To keep the editAction() method cleaner, I've refactored a few steps into protected helper methods. The workflow is quite straightforward. If no form is submitted, we populate a new form with reverse-filtered data so it's back in the same form we expect the author had originally inputted it. If we are dealing with a form submission, we simply check validity, and update the older entry on the database. The main tricky bit is that _reverseAutoFormat() method. I'm certainly not comfortable with it since it makes a lot of assumptions - the worst one being to entity decode the data. It's a reasonable default, but chances are there will be some input the user expressly encoded for a reason. The content between <code> tags springs immediately to mind. In the future I would seriously consider a class expressly for reverse filtering that uses something more flexible like the DOM.Here's the respective view template for our editAction(): <?php if($this->failedValidation): ?> <p class="error">Some problems were detected with the submitted form.</p> <?php endif; ?> <?php if($this->entrySaved): ?> <p class="success">Entry has been saved.</p> <?php elseif($this->failedFind): ?> <p class="error">The entry could not be found in the database.</p> <?php else: ?> <?php echo $this->entryForm ?> <?php endif; ?> The ever mounting feedback messages are becoming intolerable I hope? Fire ahead, and try out adding and editing entries. ConclusionBlog entries. You can't live without them, and despite their apparent simplicity they can be complex to program for. We're only at the start of a chain of work that will see us building a layer of entry data processing possibilities. The main ones covered here is creating, cleaning and editing. At some point I'll want to parse out coloured PHP syntax, perhaps insert some Microformatting. Maybe throw in more autoformatting. The strategies governing these and converting between presentation forms and user editable forms are well worth a careful examination.In the next Part to this series we'll select the simplest strategy of them all. We'll retain original input on the database, and simply post-process the entry for any such filtering. This will add heavily to page view performance but we do have options to limit that! Note: The source code for this entry is available to browse, or checkout with subversion, from http://svn.astrumfutura.org/zfblog/tags/Part8. The full source code for the entire application (as it exists thus far) from http://svn.astrumfutura.org/zfblog. Comments
Display comments as
(Linear | Threaded)
Will this entire tutorial be published as a single pdf document with a related .zip file with all the source code? It is awesome and it would be nice to have it in a beautiful form thanks;)
Hi,
After finishing all your previous posts I have a little problem with this one (before models) : the HTMLPurifier Here is the error message : Notice: Use of undefined constant HTMLPURIFIER_PREFIX - assumed 'HTMLPURIFIER_PREFIX' in F:\wamp\www\zfblog\library\HTMLPurifier\ConfigSchema.php on line 37 Warning: file_get_contents(HTMLPURIFIER_PREFIX/HTMLPurifier/ConfigSchema/schema.ser) [function.file-get-contents]: failed to open stream: No such file or directory in F:\wamp\www\zfblog\library\HTMLPurifier\ConfigSchema.php on line 37 Notice: Trying to get property of non-object in F:\wamp\www\zfblog\library\HTMLPurifier\Config.php on line 76 And a few more... Do you have an idea about the problem ? Thank you in advance for your time Jérôme
Hi Jérôme,
Double check the updated Bootstrap file. in the setupEnvironment() method there should be a line similar to define('HTMLPURIFIER_PREFIX', self::$root . '/library');. I'm guessing that's what's missing from your setup.
You are right, it's working now
I don't see that line in your tutorial, I certainly miss it somewhere. I will now be able to finish your last points. Again, thanks for your work
Is that normal that I had to add :
. ROOT_PATH . '/application/default/models' to my include_path to get access to my models, or is there an other way I'm missing?
Appears quite normal - Models need to be on the include path too. Path would be /application/models from this series though - we're not using module directory for "default".
That's what I thought, but I was wondering if an alternative exists like the ControllerPath we used for default and admin controllers. It should certainly avoid long seeks in the include paths.
But the models don't seem to be implemented or documented as the controllers are. I started your project with Zend Framework Plugin for Eclipse, that's why I have a default directory and a html directory instead of your public directory. I read in the reference guide that the default directory is used by default if nothing is specified, so I didn't get any trouble until the model's implementation. I thought it was a good practice to work a little differently from your structure to be sure I understand all the stuff and be able to adapt it from myself. There was so many different structures in the tutorials I read that I finally don't know the good one to use. I guess the Zend Framework Project created by Eclipse plugin should be a reference, so I start from that. What do you think about my approach ? Have a nice day, Jérôme
I don't think there is a all round standard approach just yet. Ralph Schindler and Wil I think are still working through the Zend_Build proposal which will set a standard. Usually I'd say the standard is /application/modules/default and /application/modules/admin for this series - I cut out the extra directory layers for simplicity's sake. Then you can use the setModuleDirectory() method on the Front Controller instance.
Hi,
First a million thanks for this great mega tutorial! Besides the excellent didactic I must say I love and admire your writing skills and style. I've been following to the risk and after fixing my bootstrap (just like Jérôme) I am getting a fatal error: Fatal error: Allowed memory size of 16777216 bytes exhausted (tried to allocate 75 bytes) in /home/lpanebr/mysites/zfblog/library/HTMLPurifier/ChildDef/Required.php on line 25 Any idea where that came from and how to fix? Thank you in advance for your time! Luciano
hi,
I've just posted this morning but about a fatal error but that is solved. Not shure what it was though, but had something to do with the permissions of the cache/htmlpurifier... Sorry and again, thank for the tutorial
No problem - I know the series has a few oddities. The revised versions are an exercise in purging those and addressing the problem people have met to date.
Happy you're enjoying the series!
Warning: Zend_Loader::include_once(Entries.php) [function.Zend-Loader-include-once]: failed to open stream: No such file or directory in Z:\home\blog.home.local\www\library\Zend\Loader.php on line 83
Warning: Zend_Loader::include_once() [function.include]: Failed opening 'Entries.php' for inclusion (include_path='Z:\home\blog.home.local\www/application;Z:\home\blog.home.local\www/library;.;/usr/local/php5/PEAR') in Z:\home\blog.home.local\www\library\Zend\Loader.php on line 83 Fatal error: Class 'Entries' not found in Z:\home\blog.home.local\www\application\admin\controllers\EntryController.php on line 17 Hi, Padriac. What do you think of this?)
I ran into the same problems here and tried several things to solve the issue permanently.
I'm currently using ZF 1.7.2 on my linux desktop with local webserver and so on. First I tried require_once on the model and it worked - but how to avoid this kind of glitch in the future? Seems that ZF can't find the models directory: what easier than adding it to the include dirs? Just add this in the top part of your bootstrap: $paths = array( APPLICATION_PATH . '/library', APPLICATION_PATH . '/application/models', ); set_include_path(implode(PATH_SEPARATOR, $paths)); don't mind my constant path: I prefer one constant over several dirname() calls Maybe this helps. Also symlinking ZF and htmlpurifier could help you greatly when playing with different versions.
Does your include path setup include the directory that file is located in?
Thank you very much for this tutorial. This is far better than what is offered on the official Zend Framework site. I am looking forward to the future revised sections after this one.
I have an error that I cannot seem to resolve as follows: Catchable fatal error: Argument 1 passed to Zend_Form_Element::setDecorators() must be an array, null given, called in "root"/htdocs/zend/library/Zend/Form/Element.php on line 322 and defined in "root"/htdocs/zend/library/Zend/Form/Element.php on line 1800 Any ideas???
Thanks for this tutorials!
maybe you forgot require_once '/path/to/htmlpurifier/library/HTMLPurifier.auto.php'; in the ZFBlog_Filter_HTMLPurifier class?
Hi...
I was a little confused, i am having an error while inserting the blog in Admin_EntryController, in $tables = new Entries... it can't find Entries???? may be it is due to path... please help me to sort out this problem... **** Fatal error: Class 'Entries' not found in C:\wamp\www\zfblog\application\admin\controllers\EntryController.php on line 21 **** THANKS....
when i add a element with /entry/add , two item are added on the database, so we see two entries in the /entry/list
normal?
As many people have already mentioned this really is a fantastic tutorial to follow and I am a keen follower. I have to admit though it was only by accident that I stumbled on the postings. I'm wondering if perhaps you're able to put a more obvious permanent link on the header or something to each series that you are writing, then maybe that could list the parts as well. However I understand that we are currently building a new blog application because as you mentioned this serendipity is a bit pants.
Thanks!
The tutorial is being reformatted (and expanded) as a book so it will its own permanent home in an easier to access format as HTML and PDF. All for free (funded by voluntary donations) I might add
Serendipity is showing its age on this site since the posts stretch back a few years and it's hard to maintain normalised formatting between years of plugin updates and ordering. A few things are broken here because if I fixed them the normal way it would break something elsewhere.
Fantastic news can't wait to see the new look tutorial.
Cheers!
Fantastic series of posts.
It might be useful to explain that the models directory needs to be added to the PATH. The path is added in the source code in index.php, although there is no bit in the instructions saying when it is added (index.php is created without this path in Part 3, but does not appear to get updated). Again, thanks for the great tutorial. Really comprehensive and well explained.
Awesome article as ever! Learning so much from your work, thanks!
I did have trouble relating to HTMLPurifier - errors were appearing complaining that it couldn't find a DOMDocument class. The problem was that my version of PHP didn't have php-xml installed. A quick 'yum install php-xml' fixed that right up and I'm on my way again! Hooray! |
Calendar
QuicksearchCommentspalPalani about PHP Framework Benchmarks: Entertaining But Ultimately Useless Thu, 18.03.2010 13:30 Very spot on. I like. Dreamsmaster about PHP Framework Benchmarks: Entertaining But Ultimately Useless Wed, 17.03.2010 00:05 Very nice post. Thx for your h ard work. hypnose about PHP Framework Benchmarks: Entertaining But Ultimately Useless Mon, 15.03.2010 19:37 You have some honest ideas her e. It looks like you have done a research on the issue and d iscovered.Anyway thanks [...] dofollow backlinks about PHP Framework Benchmarks: Entertaining But Ultimately Useless Sat, 13.03.2010 14:33 i agree Symfony 2 is outperfor mer now, Devin about PHP Framework Benchmarks: Entertaining But Ultimately Useless Thu, 11.03.2010 02:56 My favorite part of the articl e "For those with attention de ficit disorder". I've always w anted to know the benchm [...] Liberator Wedge about PHP Framework Benchmarks: Entertaining But Ultimately Useless Wed, 10.03.2010 18:00 Really an amazing feature John W. about PHP Framework Benchmarks: Entertaining But Ultimately Useless Fri, 05.03.2010 23:39 I'm a big fan of Symfony 2 as well, but I'm also curious as to how Zend Framework 2.0 will turn out! Thanks for th [...] Mikael Gramont about PHP Framework Benchmarks: Entertaining But Ultimately Useless Thu, 04.03.2010 17:34 Hi, I'm also interested in speeding up the overall view r endering, be it with or withou t Zend_Layout. I woul [...] Moaner about PHP Framework Benchmarks: Entertaining But Ultimately Useless Thu, 04.03.2010 07:36 This sounds like it could be a new website idea. Issue a cha llenge to developers to comple te a basic website with [...] Carlos Aguado about PHP Framework Benchmarks: Entertaining But Ultimately Useless Wed, 03.03.2010 06:51 Hi there! Small correction: After some tweeking here and some playing there, the perfor mance gain I see is of o [...] andreas about PHP Framework Benchmarks: Entertaining But Ultimately Useless Tue, 02.03.2010 17:21 Dear Sir, it is nice to see th e benchmark war continue since as you say "My fellow Zend Fr ameworkers, we cannot al [...] Pádraic Brady about PHP Framework Benchmarks: Entertaining But Ultimately Useless Sat, 27.02.2010 22:24 I don't use Zend_Layout either , but don't tell anyone Carlos Aguado about PHP Framework Benchmarks: Entertaining But Ultimately Useless Sat, 27.02.2010 16:51 Hi Padráic, Do you have a p ractical example of stopping u sing the ViewRenderer? I've be en playing a bit with it [...] chrisweb about PHP Framework Benchmarks: Entertaining But Ultimately Useless Fri, 26.02.2010 21:11 Padráic your the man Pádraic Brady about PHP Framework Benchmarks: Entertaining But Ultimately Useless Fri, 26.02.2010 18:32 You need to read the article a gain, I'm not criticising Symf ony 2 in any way, shape or for m. In fact, I compliment [...] CategoriesArchivesTop ReferrersShow tagged entries application security article astrum futura asynchronous processing atom bdd behavior-driven development behaviour-driven development book deep end dependency injection design patterns devnetwork docbook documentation eve online games htmlpurifier inversion of control irish php user group irishisms maugrim microformat mock objects model mutateme mutation testing mvc oauth openid openid and yadis pc gaming pear phing php php game development php games php general php security phpmock phpspec phpunit poka-yoke qgl quantum game library quantum star se rantings rss simpletest snarl solar empire surviving the deep end symfony tdd tutorial unit testing xp programming xrd xrds yadis yaml zend framework zf proposal zfstde |
|||||||||||||||||||||||||||||||||||||||||||||||||


