|
BlogTutorial
IntroductionThis wiki page will provide a tutorial showing how to use the sierra-php framework to quickly create a blogging application. Links
Facts & StatsRequired System Features
Code Facts
InstructionsBefore beginning this tutorial, you will need to follow the instructions provided in the Install wiki page for installing the framework and setting up an application. Once you have the application shell set up (mine is named sierraphp-blog), you may proceed with these step by step instructions: Application Database AccessIn order to create our blog we are first going to need to set up a database. For this tutorial, I'll be using MySQL, however, I'll also provide configuration examples for both PostgreSQL and SQLite. The important thing in this step is to create a database and user with full CRUD access as well as the ability to create, drop and alter tables and indexes (not applicable for SQLite). Once you have created the database and user access, you will need to configure the application to use this database by adding the following XML to [app]/etc/app-config.xml (replace values in brackets with your own values): <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE app-config PUBLIC "-//SIERRA//DTD APP CONFIG//EN" "http://sierra-php.googlecode.com/svn/trunk/etc/app-config.dtd"> <app-config error-log-file="blog.log"> ... <db key="blog" name="blog" type="[mysql OR pgsql OR sqlite]" user="[user]" password="[password]" /> ... </app-config> Blog ModelNow comes the fun part, creating the model for our blog application. The model is an xml description of the data entities used in our application. In order to add our model, we need to add a use-model entry in [app]/etc/app-config.xml: <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE app-config PUBLIC "-//SIERRA//DTD APP CONFIG//EN" "http://sierra-php.googlecode.com/svn/trunk/etc/app-config.dtd"> <app-config error-log-file="blog.log"> ... <use-entity-model key="blog" path="blog-model.xml"/> ... </app-config> This tells sierra-php that you wish to use the model described in the xml file [app]/etc/blog-model.xml. So, we now must now create that file which should initially look like this: <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE entity-model PUBLIC "-//SIERRA//DTD ENTITY MODEL//EN" "http://sierra-php.googlecode.com/svn/trunk/lib/model/entity-model.dtd"> <entity-model db="blog" sync-schema="1" vo-suffix=""> </entity-model> This tells sierra-php that our model should use the database blog (<db key="blog" above) for this model and to automatically synchronize the schema for this model sync-schema="1". Localizationsierra-php provides built-in localization and in a sense forces you to write your applications in an easily localizable and well structured manner through use of properties files. Hence, all language strings are stored in these properties files located in [app]/etc/l10n. When a user accesses your website or application using a web browser, sierra-php will evaluate their locale preferences provided in the request headers and select the closest matching properties file according to those preferences. The naming convention for properties files is app_[2 character language code]_[2 character country code].properties. Both language code and country code are optional (hence the default properties file is [app]/etc/l10n/app.properties. So, for now, create that file and keep it open as we will use it frequently as we define the model. Database SynchronizationAutomatic database synchronization means that sierra-php will automatically create and alter tables and indexes in the blog database for this model. BlogPost EntityOk, so now you should have 2 files open, [app]/etc/blog-model.xml and [app]/etc/l10n/app.properties and we will begin implementing the application model by defining the BlogPost entity. An entity is defined in the model using the entity XML element where an entity is a data structure with 2 or more attributes (a primary key and at least 1 additional attribute). Our BlogPost entity will have the following attributes:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE entity-model PUBLIC "-//SIERRA//DTD ENTITY MODEL//EN"
"http://sierra-php.googlecode.com/svn/trunk/lib/model/entity-model.dtd">
<entity-model db="blog" sync-schema="1" vo-suffix="">
<entity key="BlogPost" primary-key="postId">
<attribute key="postId" sequence="1" />
<attribute key="post" depends="required" />
<attribute key="posted" default="new SRA_GregorianDate()" read-only="1" type="time" />
<attribute key="title" depends="required" max-length="64" />
</entity>
<msg key="maxLength" resource="error.maxLength" />
<msg key="required" resource="error.required" />
</entity-model>This might look a bit complicated, so let me explain what it all means. First of all, I defined an entity named BlogPost where the primary key is the attribute named postId. The postId attribute, is a sequence, meaning it will be created in the database as an auto-incrementing column (using the native database support for that type - auto_increment in mysql or serial in postgres for example). The post attribute is mandatory (depends="required"). The posted attribute uses the time data type (both data and time), read-only (because it shouldn't be changed after the post is created), and the default value is new SRA_GregorianDate() which is just PHP code for the current time when the post is created. And finally, the title attribute is also mandtory and additionally limited to a maximum of 64 characters. There are also 2 msg attributes which are used to define the error message that will be displayed to the user if a required attribute (post or title) is not specified (key="required"), or if a title is over 64 character long (key="maxLength"). Ok, so now we just need to define some strings in our properties file [app]/etc/l10n/app.properties. Here are the strings I added: sierraphp-blog=sierra-php Blog
BlogPost=Post
BlogPost.id=Post ID
BlogPost.id-help=The unique identifier for this blog post
BlogPost.post=Post
BlogPost.post-help=The blog post content
BlogPost.posted=Post Time
BlogPost.posted-help=The date and time when this post was created
BlogPost.title=Title
BlogPost.title-help=The blog post title
error.maxLength=The max length of {$attr} is {$maxLength} characters
error.required={$attr} is requiredSo what's up with the -help strings? Well, an attribute can have 2 strings associated with it, one is the attribute name and the other is a more descriptive explanation of what that attribute is. We will use both of these strings in our views and they are also used by sierra-php to construct the web services API documentation both of which will be discussed below. You may also be wondering what {$attr} and {$maxLength} are in the error message strings. These are dynamic keys that will automatically be replaced with the attribute name and max-length constraint defined in the model. TestingNow we are ready to begin testing our entity model. To do so, we'll use the sierra-php console which allows us to run PHP code for our application through a bash-like console. To start up the console, just type in the following from an SSH shell: /[path]/[to]/sierra/bin/sra-console.php [app id] (substitute bracketed values with your own). The first time you start the console, there may be a slight lag as the system creates the database schema and generates the PHP source files for your model. Once you see the Welcome to the SIERRA PHP Console message you should be ready to go. For now, just exit out of the console so we can verify a few things: exit. Then log into your database and verify that a table blog_post was created for our BlogPost entity and that it has the correct columns. If it does not, check the error log [path]/[to]/sierra/log/blog.log and you should see an error message (most commonly it will be related to database permissions or a login failure) and make the appropriate corrections. If you do have to fix database user problems, re-save the blog-model.xml file and then re-launch the console to have sierra-php attempt to rebuild your model. Once you have verified that you database was synchronized, we are ready to test some simple CRUD logic. To do so, fire the sierra-php console back up. It may also be handy to have a database client application open in another window so you can verify the CRUD operations as we complete them. This process will also familiarize you a bit with a few sierra-php model-related features. First of all, you need to understand that when sierra-php generates your model, it creates 2 PHP source files for each entity. The first source file will be named BlogPost.php which is a PHP class providing all of the data, getters/setters, validation, localization, views and more for that entity (Value or Transfer Object design pattern). The second source file will be named BlogPostDAO.php which is a PHP class provided a means of performing all of the CRUD logic for that entity (Data Access Object design pattern). As we test the CRUD login, we will be using both of those classes extensively. CreateOk, so first lets create a couple of test posts. To do so, enter the following PHP code in the sierra-php console: $dao =& SRA_DaoFactory::getDao('BlogPost');
$post1 = new BlogPost();
$post1->setTitle('My First Blog Post');
$post1->setPost("Well, I'm going to give this a shot. I hope it works!");
$dao->insert($post1);
$post2 = new BlogPost();
$post2->setTitle('My Second Blog Post');
$post2->setPost("Here we go again, hopefully this second blog post will work also");
$dao->insert($post2);Now exit out of the terminal and look at the contents of the blog_post table in your database, you should have 2 entries corresponding with these two blog posts we just created. If you do not, check the error log [path]/[to]/sierra/log/blog.log. Let me explain how this worked:
You didn't need to write any SQL statements to perform these inserts because all of that is provided automatically by the generated DAO. RetrieveNow that we have created a couple of blog posts, we need to verify that we can retrieve them from the database. Again, we will do this using BlogPostDAO. Here is the code to retrieve all posts: $dao =& SRA_DaoFactory::getDao('BlogPost');
$posts =& $dao->findAll();And to retrieve a single post (where the primary key id=1): $dao =& SRA_DaoFactory::getDao('BlogPost');
$post1 =& $dao->findByPk(1);BlogPostDAO also provides some other "finder" methods: findByConstraints, findByPks, findByQuery, findBySqlConstraints, findEqual and findLike. For more information on how to use these "finder" methods, open the file [app]/lib/model/BlogPostDAO.php and review the API documentation provided for those methods. UpdateNow that we have retrieved some blog posts, lets try to make some changes to them: $dao =& SRA_DaoFactory::getDao('BlogPost');
$post1 =& $dao->findByPk(1);
$post1->setTitle('New title for my first post');
$post1->setPost("I'm going to change my post content to test out the update functionality");
$dao->update($post1);You can now verify that the changes to the first blog post were updated in the database. DeleteFinally, lets test out deleting a blog post from the database: $dao =& SRA_DaoFactory::getDao('BlogPost');
$post1 =& $dao->findByPk(1);
$dao->delete($post1);Verify that the first blog post was deleted from the database. Create - another approachSo, the create section above, showed you how to create a new blog post using BlogPostDAO explicitly. Another, possibly easier way to perform CUD logic for an entity is to use the insert, update and delete provided in the entity class itself. For example, the following code will create post1 the same as the Create section above (note that entities classes are not automatically included, so you must include them manually): include_once('model/BlogPost.php');
$post1 = new BlogPost();
$post1->setTitle('My First Blog Post');
$post1->setPost("Well, I'm going to give this a shot. I hope it works!");
$post1->insert();ValidationYou may recall that we added some validation constraints to the BlogPost entity including that post title and post are mandatory, and that title cannot be more than 64 characters in length. So lets test out how this validation works. To do so, lets try to create a BlogPost where 1 or more of these constraints is not met: include_once('model/BlogPost.php');
$post1 = new BlogPost();
$post1->setTitle('This is a really really really really really really really long title');
$post1->validate();
print_r($post1->validateErrors);
$post1->insert();When you run this code, you should notice that the return valid for the validate method invocation returned FALSE, meaning validation failed, and the print_r($post1->validateErrors) displayed the localized strings for the validation errors indexed by attribute name. Additionally, you should note that even though we invoked $post1->insert(), that post was not inserted into the database because of those validation errors. When sierra-php generated BlogPostDAO the insert and update methods were implemented to verify successful validation prior to performing that corresponding database logic. Entity Dirty TrackingThe class BlogPost also keeps track of dirty attributes, so when update is invoked, only the dirty attributes will be included in the UPDATE SQL query that is executed. Let's verify this with one of our blog posts: $dao =& SRA_DaoFactory::getDao('BlogPost');
$post2 =& $dao->findByPk(2);
$post2->isDirty('title');
$post2->setTitle('New title for my second post');
$post2->isDirty('title');
$post2->isDirty('post');
$dao->update($post2);So, you can see from this code that the BlogPost instance kept track of the dirty state of it's title and post attributes and when update was invoked the SQL query was did not include post: UPDATE blog_post SET title="New title for my second post" WHERE id=2. Entity and Attribute StringsSo, how do we get access to those attribute strings in [app]/etc/l10n/app.properties you are probably wondering... easy, here is some code you can run: include_once('model/BlogPost.php');
$post = new BlogPost();
$post->getEntityLabel();
$post->getPostLabel();
$post->getPostHelpContent();
$post->getTitleLabel();
$post->getTitleHelpContent();Creating the Web ApplicationSo now that we have our model up and running and a basic understanding of the functionality provided by the sierrra-php framework, lets set up a web application to display and manage our blog. Blog Home PageThe blog homepage will simply display all of the posts. First we'll need to create a PHP script which will act as the "controller" for this page. We'll name it [app]/www/html/index.php and it will contain the following code (substitute bracketed strings with your own values): <?php
include_once('/[path]/[to]/sierra/lib/core/SRA_Controller.php');
SRA_Controller::init('[app id]');
$dao =& SRA_DaoFactory::getDao('BlogPost');
$posts =& $dao->findAll();
$tpl =& SRA_Controller::getAppTemplate();
$tpl->assignByRef('posts', $posts);
$tpl->display('index.tpl');
?>The first couple of lines include the sierra-php controller and initialize the application. These two lines are required on all PHP scripts for this application. Everything after that up to $tpl should look pretty familiar... but you are probably wondering what $tpl is. sierra-php provides built-in support for the smarty template engine for rendering web pages. The reason for this is because smarty provides some nice display related functionality and also forces you to keep your code clean and organized by not putting business logic into your PHP web scripts. So, the $tpl code first obtains a reference to the application template engine, then assign the posts (by reference) to the template engine so they can be referenced as a smarty variable in the template, and then displays the template [app]/www/tpl/index.tpl. Now, before we can view our blog home page, we must create the smarty template [app]/www/tpl/index.tpl. Here is an example: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{$Locale->getId()}" xml:lang="{$Locale->getId()}">
<head>
<title>{$Controller->getAppName()}</title>
</head>
<body>
<h1>{$Controller->getAppName()}</h1>
{foreach from=$posts item=post}
{$post->render('html')}
{/foreach}
</body>
</html>$Controller is a reference to a sierra/lib/core/SRA_Controller.php instance and $Controller->getAppName() returns the name of the current application (the name is set using the app name in app.properties: sierraphp-blog=sierra-php Blog Next we need to add a view to the BlogPost entity in blog-model.xml: <entity-model>
<entity key="BlogPost" primary-key="postId">
...
<attribute key="title" depends="required" max-length="64" />
<view key="html" template="views/blog-post.tpl" />
</entity>
...
</entity-model>and finally, create a smarty template for that view in [app]/www/tpl/views/blog-post.tpl (you will need to create the views directory first) with the following content: <hr />
<h2>{$entity->getTitle()}</h2>
<p>
<strong>{$entity->getPosted(0, 1)}</strong><br />
{$entity->getPost()}
</p>It is completely understandable if you are a little lost at this point. However, lets look at the results first... open a browser and point it to the base URL for you application. If your setup is correct, you should see a very ugly page displaying the 2 test posts you created previously in the Testing section. If not, check the error log [path]/[to]/sierra/log/blog.log. Ok, now on to the explanations... what is this about an entity view and how is used? In short... both entities and attributes can have 0-to-many. For example, you might have 1 view to display an entity and 1 to render the HTML form so the user can edit it. For now, we have created an 1 view for BlogPost named html which is used to display the blog post in html format. Second, you may be wondering what the 0, 1 parameters are for the posted attribute. Because posted is a time value, it is represented as a sierra/lib/SRA_GregorianDate object and not a text value like title and post. So in order to display it in a web page, we'll need to format. That is what the 1 parameter tells the BlogPost::getPosted method to do. Alternatively, you could replace the 1, with any PHP date format string. The 1 just tells it to use the default time formatting for the application which can be configured in app-config.xml. Add Blog PostNow that our home page is working, we are ready to create another page that lets use add new blog posts. First lets create the "controller" script: [app]/www/html/post.php: <?php
include_once('/var/www/sierra/lib/core/SRA_Controller.php');
SRA_Controller::init('[app id]');
$dao =& SRA_DaoFactory::getDao('BlogPost');
$post = BlogPost::newInstanceFromForm();
// if the form has been submitted, validate the blog post, insert and redirect
// the user to the homepage
if (count($_POST) && $post->validate() && $post->insert()) {
header('Location: /');
}
$tpl =& SRA_Controller::getAppTemplate();
$tpl->assignByRef('post', $post);
$tpl->display('post.tpl');
?>The only thing new on this script is the use of BlogPost:: newInstanceFromForm. What doest this method do? Basically, it just creates a new BlogPost instance using the values in the $_GET and $_POST PHP super globals. So, essentially if a form has been submitted with form elements named title and/or post, whatever the value specified in those form fields will automatically be set to that new BlogPost instance. This script also uses a new smarty template [app]/www/tpl/post.tpl: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{$Locale->getId()}" xml:lang="{$Locale->getId()}">
<head>
<title>{$resources->getString('blog.post.title')}</title>
</head>
<body>
<h1>{$resources->getString('blog.post.title')}</h1>
{if $post->validateErrors}
<font style="color:red">
<ul>
{foreach from=$post->validateErrors item=error}
<li>{$error}</li>
{/foreach}
</ul>
</font>
{/if}
<form action="" method="post">
{$post->render('form')}
<input type="submit" />
</form>
</body>
</html>Ok, it may look complicated, but it really is not. Basically, it is just rendering an html page for the new blog post form including displaying any validation errors (if present) and wrapping the BlogPost form view with a form and adding a submit button. One thing additional thing you'll need to do for this template is add 1 strings for blog.post.title in [app]/etc/l10n/app.properties (all of the strings in app.properties are accessible using the $resources smarty template variable and the getString method - $resources->getString('blog.post.title') in this example): # blog strings blog.post.title=Create Blog Post Now on to the BlogPost form view. This one is going to require some major changes to etc/blog-model.xml: <?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE entity-model PUBLIC "-//SIERRA//DTD ENTITY MODEL//EN"
"http://sierra-php.googlecode.com/svn/trunk/lib/model/entity-model.dtd">
<entity-model db="blog" sync-schema="1" vo-suffix="">
<entity key="BlogPost" primary-key="postId">
<attribute key="postId" sequence="1">
<view key="input" extends="input-hidden" />
</attribute>
<attribute key="post" depends="required">
<view key="input" extends="input-textarea" />
</attribute>
<attribute key="posted" default="new SRA_GregorianDate()" type="time" />
<attribute key="title" depends="required" max-length="64">
<view key="input" extends="input-text" />
</attribute>
<view key="form" template="views/blog-post-form.tpl" />
<view key="html" template="views/blog-post.tpl" />
</entity>
<global-views>
<view key="input-hidden" template="model/sra-attr.tpl">
<param id="value" type="tpl" value="model/sra-form-input.tpl" />
<param id="type" type="input-attrs" value="hidden" />
</view>
<view key="input-text" template="model/sra-attr.tpl">
<param id="value" type="tpl" value="model/sra-form-input.tpl" />
</view>
<view key="input-textarea" template="model/sra-attr.tpl">
<param id="useTextArea" value="1" />
<param id="value" type="tpl" value="model/sra-form-input.tpl" />
</view>
</global-views>
<msg key="maxLength" resource="error.maxLength" />
<msg key="required" resource="error.required" />
</entity-model>Ok, again this might look confusing, but it really isn't that complex... I'll walk you through the changes. First, we added a new view for BlogPost: <view key="form" template="views/blog-post-form.tpl" /> which is a smarty template used to render the form that will be used to create or update blog posts: <label>{$entity->getTitleLabel()}</label>: {$entity->renderAttribute('title', 'input')}
<p>{$entity->getTitleHelpContent()}</p>
<hr />
<label>{$entity->getPostLabel()}</label>: {$entity->renderAttribute('post', 'input')}
<p>{$entity->getPostHelpContent()}</p>
{if $entity->recordExists}{$entity->renderAttribute('postId', 'input')}{/if}Now you see how we will use those attribute strings in [app]/etc/l10n/app.properties. Also, you'll notice the renderAttribute invocations, which are similar to render used in post.tpl but specific to attribute views which we will discuss below. Also, you'll notice the use of $entity->recordExists which will only be true for blog entries that already exist (because this same form will be used to both insert and update blog posts). So, now on to the most complex and verbose part of this puzzle, the attribute views. You'll notice in blog-model.xml that 3 attribute views were added for the postId, title, and post attributes all of which are named input and use the funny extend notation. So what does that mean? While, you will also notice that big section of XML for global-views with 3 views with the same names (key=) as the extend values for the attribute views. So, what extends means, is that the view extends the values from another view. Why would you want to do this you might wonder... while, views be somewhat complicated and extremely repetitive... so by extending views, you can significantly reduce the amount of xml for defining your views. global-views can be extended by any other views throughout the model. You don't need to fully understand the xml in the global views... just copy and paste. They essentially, use some pre-built templates provided by sierra-php used to make displaying data and form fields easier. Now we just need to make a link to [app]/www/tpl/index.tpl to post.php: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{$Locale->getId()}" xml:lang="{$Locale->getId()}">
<head>
<title>{$Controller->getAppName()}</title>
</head>
<body>
<h1>{$Controller->getAppName()}</h1>
{foreach from=$posts item=post}
{$post->render('html')}
{/foreach}
<p><a href="/post.php">{$resources->getString('blog.post.title')}</a></p>
</body>
</html>So, now you are ready to start testing this bad boy. Point your browser to /post.php and have at it... try leaving one or both fields blank and entering a title that is longer than 64 characters to test out the validation error messages. When you create a post successfully, you should be redirected to the home page where your post will be illogically placed at the bottom of the page. Edit Blog PostNow that we can add blog posts, we may also want to be able to edit them. So, lets add a new "controller" script [app]/www/html/edit.php: <?php
include_once('/var/www/sierra/lib/core/SRA_Controller.php');
SRA_Controller::init('[app id]');
$dao =& SRA_DaoFactory::getDao('BlogPost');
$post = count($_POST) ? BlogPost::newInstanceFromForm() : $dao->findByPk($_GET['postId']);
// if the form has been submitted, validate the blog post, insert and redirect
// the user to the homepage
if (count($_POST) && $post->validate() && $post->update()) {
header('Location: /');
}
$tpl =& SRA_Controller::getAppTemplate();
$tpl->assignByRef('post', $post);
$tpl->display('edit.tpl');
?>This controller script is very similar to post.php with the exception that it looks up the existing post using the URI parameter postId the first time it is displayed. Next we must add a new template [app]/www/tpl/edit.tpl: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{$Locale->getId()}" xml:lang="{$Locale->getId()}">
<head>
<title>{$resources->getString('blog.edit.title')}</title>
</head>
<body>
<h1>{$resources->getString('blog.edit.title')}</h1>
{if $post->validateErrors}
<font style="color:red">
<ul>
{foreach from=$post->validateErrors item=error}
<li>{$error}</li>
{/foreach}
</ul>
</font>
{/if}
<form action="" method="post">
{$post->render('form')}
<input type="submit" />
</form>
</body>
</html>As you can see, pretty much identical to post.tpl with the exception that the header and title is different. Now we just need to add a new string to [app]/etc/l10n/app.properties: # blog strings blog.edit.title=Edit Blog Post blog.post.title=Create Blog Post That's it... the edit functionality was much easier than post because we were able to reuse much of the code including the views. But, how to we let the user's access the edit page, we should not expect them to know to type in /edit.php?postId=N.... easy, we'll just add a link in the html view ([app]/www/tpl/views/blog-post.tpl): <hr />
<h2>{$entity->getTitle()}</h2>
<p>
<strong>{$entity->getPosted(0, 1)}</strong> <a href="/edit.php?postId={$entity->getPostId()}">{$resources->getString('text.edit')}</a><br />
{$entity->getPost()}
</p>And, finally, add the text.edit string in [app]/etc/l10n/app.properties: blog.edit.title=Edit Blog Post blog.post.title=Create Blog Post text.edit=Edit Delete Blog PostNow that users can view, create and update blog posts, we now just need to add the ability for them to delete. This will be relatively easy compared to the previous tasks. First we need to create the "controller" script: [app]/www/html/delete.php: <?php
include_once('/var/www/sierra/lib/core/SRA_Controller.php');
SRA_Controller::init('[app id]');
$dao =& SRA_DaoFactory::getDao('BlogPost');
if (count($_GET) && BlogPost::isValid($post =& $dao->findByPk($_GET['postId']))) {
$post->delete();
}
header('Location: /');
?>The only thing that might not be familiar in the script is the use of BlogPost::isValid($post). The isValid method is a generated static method added to every entity class. It returns TRUE if if the $post parameter is a valid BlogPost. Now we just need to add a link in the html view to the delete script ([app]/www/tpl/views/blog-post.tpl): <hr />
<h2>{$entity->getTitle()}</h2>
<p>
<strong>{$entity->getPosted(0, 1)}</strong>
<a href="/edit.php?postId={$entity->getPostId()}">{$resources->getString('text.edit')}</a> |
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteConfirm')}')) document.location.replace('/delete.php?postId={$entity->getPostId()}')">{$resources->getString('text.delete')}</a>
<br />
{$entity->getPost()}
</p>and a few new strings to [app]/etc/l10n/app.properties: # blog strings blog.deleteConfirm=Are you sure you want to delete this post? blog.edit.title=Edit Blog Post blog.post.title=Create Blog Post text.delete=Delete text.edit=Edit Now refresh your home page view and test one of the delete links. Everything should be working. Web ServicesNow that we have our model defined and a web application set up, we may find that some users want to plug our blog into their existing software via web services. No problem... sierra-php provides almost instant SOAP access to our model with just one additional line of XML <ws key="blogPostService" create="1" delete="1" update="1" /> and specify the ws-gateway in entity-model: <entity-model db="blog" sync-schema="1" vo-suffix="" ws-gateway-rewrite="1" ws-gateway-uri="/ws">
<entity key="BlogPost" primary-key="postId">
...
<view key="html" template="views/blog-post.tpl" />
<ws key="blogPostService" create="1" delete="1" update="1" />
</entity>
</entity-model>That's all... so now, how do we enable access to these new web services, well in order to make it easy we need to make a few changes to the apache configuration. First, you'll need to add an apache alias, if you have your application configured as a VirtualHost this might look something like this: <VirtualHost *:80> DocumentRoot /var/www/sierra/app/[app id]/www/html ServerName blog.sierra-php.org Alias /sra-ws-gateway.php "/var/www/sierra/lib/model/sra-ws-gateway.php" </VirtualHost> Next, we'll need to enable .htaccess overrides in our DocumentRoot using something like this: <Directory "/var/www/sierra/app/[app id]/www/html"> AllowOverride All </Directory> And, finally, we'll need to add a .htaccess file to the DocumentRoot containing the following: RewriteEngine On
RewriteRule ^ws/(.*)/(.*)/(.*)/(.*)/(.*) /sra-ws-gateway.php?ws1=$1&ws2=$2&ws3=$3&ws4=$4&ws5=$5&%{QUERY_STRING}
RewriteRule ^ws/(.*)/(.*)/(.*)/(.*) /sra-ws-gateway.php?ws1=$1&ws2=$2&ws3=$3&ws4=$4&%{QUERY_STRING}
RewriteRule ^ws/(.*)/(.*)/(.*) /sra-ws-gateway.php?ws1=$1&ws2=$2&ws3=$3&%{QUERY_STRING}
RewriteRule ^ws/(.*)/(.*) /sra-ws-gateway.php?ws1=$1&ws2=$2&%{QUERY_STRING}
RewriteRule ^ws/(.*) /sra-ws-gateway.php?ws1=$1&%{QUERY_STRING}
RewriteRule ^ws /sra-ws-gateway.php?%{QUERY_STRING}Now we should be able to access our web services API documentation including the WSDL at /ws/[app id]/api. Here are a few sample REST web service calls to get you started:
You can also view and test the API on the demo site http://blog.sierra-php.org/ws/sierraphp-blog/api CommentsSo far our blog is pretty useless... in fact it really isn't even a blogging system at all, because there is no way for people to add comments to posts. So, lets delve a bit deeper into our entity model and create a new BlogComment entity in order to allow users to do so. First we must determine what data will constitute a comment... there will be the comment itself of course, along with a timestamp representing when it was posted and maybe a name and email address of the person adding the comment. So, that should give us enough information to add our new entity: <entity key="BlogPostComment" primary-key="commentId">
<attribute key="commentId" sequence="1" />
<attribute key="comment" depends="required" />
<attribute key="email" depends="email required" max-length="64" />
<attribute key="name" depends="required" max-length="16" />
<attribute key="post" column="post_id" depends="required" type="BlogPost" />
<attribute key="posted" default="new SRA_GregorianDate()" read-only="1" type="time" />
</entity>This should all look somewhat familiar. The only addition is the email validators... notice we applied 2 validators to that attribute. The built-in email validator validates the email string is properly formatted (i.e. string@string.[com|net|org|etc]. However, since this is a new validator we haven't used before, we must also add a localized error message for it. To do so, first we add a new msg element to the bottom of the model: <msg key="email" resource="error.email" /> And then, in our handy properties file (which we never close), we add a new string: error.email={$attr} is a not properly formatted email addressFinally, we need to add our entity strings. Here are mine: BlogPostComment=Comment BlogPostComment.commentId=Comment ID BlogPostComment.commentId-help=The unique identifier for this comment BlogPostComment.comment=Comment BlogPostComment.comment-help=The comment being made BlogPostComment.email=Email Address BlogPostComment.email-help=The email address of the comment author BlogPostComment.name=Name BlogPostComment.name-help=The name of the comment author BlogPostComment.posted=Post Time BlogPostComment.posted-help=The date and time when this comment was submitted That's it... now refresh your browser and verify that the new table blog_post_comment was created in the database. Relational AttributesSo, you may be wondering now how are comments actually going to be associated to posts the way it is set up now... and the answer is there is no relationship currently between the two, we need to create one in order for it to exist. So, we need to go back to our BlogPost entity now and create a new attribute named comments: <entity key="BlogPost" primary-key="postId">
...
<attribute key="comments" cardinality="0..*" on-delete-cascade="1" on-remove-delete="1" type="BlogPostComment" />
...
</entity>A little more complex than our scalar one-to-one attributes right? Well yes, but not overly complicated. Lets discuss what each of the attributes means here:
Now that we have defined the relationship between a post and comments, we may also chose to do so between comments and posts if there will be some usefulness in doing so. We'll go ahead and do it as well just to explain how it would work. Basically, we'll just need to add another attribute to our BlogPostComment entity like this: <entity key="BlogPostComment" primary-key="commentId">
...
<attribute key="post" column="post_id" depends="required" type="BlogPost" />
...
</entity>You'll notice that there is no cardinality used for this attribute. This is because a comment can only belong to 1 post. The column attribute just tells sierra-php to use the database column named post_id (instead of the default column named post) because that is the name of the column that the post to comment relationship will implicitly use and we certainly want them both to use the same column. Now, of course we need to add our new attribute strings: BlogPost.comments=Comments BlogPost.comments-help=The comments submitted for this blog post BlogPostComment.post=Post BlogPostComment.post-help=The blog post this comment was submitted for Now, save your model, refresh your browser, and take a look at the blog_post_comment table. You'll notice a new column named post_id of type int. This column will be used for the foreign key in the blog_post table so the relationship between posts and comments can be determined by sierra-php and any other program that uses the database. So, now we are ready to test this new functionality out from our handy sierra-php console /[path]/[to]/sierra/bin/sra-console.php [app id]: $dao =& SRA_DaoFactory::getDao('BlogPost');
$post =& $dao->findByPk(2);
// first way to add a comment using the comment to post relationship
$comment1 = new BlogPostComment();
$comment1->setComment('this post really sucks! but thanks for writing it');
$comment1->setEmail('john@doe.com');
$comment1->setName('John Doe');
$comment1->setPost($post);
$comment1->insert();
// second way to add a comment using the post to comment relationship
$comment2 = new BlogPostComment();
$comment2->setComment('great post, I really like it');
$comment2->setEmail('jane@doe.com');
$comment2->setName('Jane Doe');
$comment2->setPost($post);
$post->addComments($comment2);
$post->update();Now check your database and verify that 2 comment records were added. Notice that we used both relationships to add these comments. First we added the comment using BlogPostComment::insert, and second we did the same using BlogPost::update. Both are valid methods for adding comments since we have defined the circular relationship. However, if you use the latter, you are still required to setPost because post is a required attribute for BlogPostComment. That's it... everything appears to be working ok in our model for comments now. We won't do full CRUD testing since we have already done that previously. Instead, lets go back to our web application and add the ability to submit post comments. Adding Comments to our Blogging Web AppYou should already have a good feel for what this is going to involve... views and controllers. First let's add an html view for our BlogPostComment entity: <entity key="BlogPostComment" primary-key="commentId">
...
<view key="html" template="views/blog-post-comment.tpl" />
</entity>And create the template views/blog-post-comment.tpl: <p>
<strong><a href="mailto:{$entity->getEmail()}">{$entity->getName()}</a> - {$entity->getPosted(0, 1)}</strong>
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteCommentConfirm')}')) document.location.replace('/delete-comment.php?commentId={$entity->getCommentId()}')">{$resources->getString('text.delete')}</a>
<br />
{$entity->getComment()}
</p>And now, let's modify views/blog-post.tpl to also display any comments: <hr />
<h2>{$entity->getTitle()}</h2>
<p>
<strong>{$entity->getPosted(0, 1)}</strong>
<a href="/edit.php?postId={$entity->getPostId()}">{$resources->getString('text.edit')}</a> |
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteConfirm')}')) document.location.replace('/delete.php?postId={$entity->getPostId()}')">{$resources->getString('text.delete')}</a>
<br />
{$entity->getPost()}
<h3>{$entity->getCommentsLabel()}</h3>
{if $entity->getComments()}
{foreach from=$entity->getComments() item=comment}
{$comment->render('html')}
{/foreach}
{else}
{$resources->getString('blog.noComments')}
{/if}
</p>And finally, we need to add one additional string to app.properties: blog.noComments=There are no comments for this post Now refresh the home page for your blog, and the comments we added during testing should show up under one of the posts. Those that do not have comments, should have a message indicating such. Submit CommentNow we need to add a means for users to submit new comments for our posts. To do so, lets first add a "Submit Comment" link to views/blog-post.tpl: <hr />
<h2>{$entity->getTitle()}</h2>
<p>
<strong>{$entity->getPosted(0, 1)}</strong>
<a href="/edit.php?postId={$entity->getPostId()}">{$resources->getString('text.edit')}</a> |
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteConfirm')}')) document.location.replace('/delete.php?postId={$entity->getPostId()}')">{$resources->getString('text.delete')}</a>
<br />
{$entity->getPost()}
<h3>{$entity->getCommentsLabel()}</h3>
<p><a href="/add-comment.php?post={$entity->getPostId()}">{$resources->getString('blog.submitComment')}</a></p>
{if $entity->getComments()}
{foreach from=$entity->getComments() item=comment}
{$comment->render('html')}
{/foreach}
{else}
{$resources->getString('blog.noComments')}
{/if}
</p>And a new string in app.properties: blog.submitComment=Submit New Comment Refresh your page now and you should see the link. Now comes the hard part, creating a controller and view for the comment form. First lets define the attribute views and entity view form: <entity key="BlogPostComment" primary-key="commentId">
<attribute key="commentId" sequence="1">
<view key="input" extends="input-hidden" />
</attribute>
<attribute key="comment" depends="required">
<view key="input" extends="input-textarea" />
</attribute>
<attribute key="email" depends="email required" max-length="64">
<view key="input" extends="input-text" />
</attribute>
<attribute key="name" depends="required" max-length="16">
<view key="input" extends="input-text" />
</attribute>
<attribute key="post" column="post_id" depends="required" type="BlogPost">
<view key="input" extends="input-hidden" />
</attribute>
<attribute key="posted" default="new SRA_GregorianDate()" read-only="1" type="time" />
<view key="form" template="views/blog-post-comment-form.tpl" />
<view key="html" template="views/blog-post-comment.tpl" />
</entity>And now, lets create the template views/blog-post-comment-form.tpl: <label>{$entity->getNameLabel()}</label>: {$entity->renderAttribute('name', 'input')}
<p>{$entity->getNameHelpContent()}</p>
<label>{$entity->getEmailLabel()}</label>: {$entity->renderAttribute('email', 'input')}
<p>{$entity->getEmailHelpContent()}</p>
<hr />
<label>{$entity->getCommentLabel()}</label>: {$entity->renderAttribute('comment', 'input')}
<p>{$entity->getCommentHelpContent()}</p>
{$entity->renderAttribute('post', 'input')}
{if $entity->recordExists}{$entity->renderAttribute('commentId', 'input')}{/if}And now we need our controller add-comment.php: <?php
include_once('/var/www/sierra/lib/core/SRA_Controller.php');
SRA_Controller::init([app id]);
include_once('model/BlogPostComment.php');
$comment = BlogPostComment::newInstanceFromForm();
$comment->setPost($_GET['post']);
// if the form has been submitted, validate the blog post, insert and redirect
// the user to the homepage
if (count($_POST) && $comment->validate() && $comment->insert()) {
header('Location: /');
}
$tpl =& SRA_Controller::getAppTemplate();
$tpl->assignByRef('comment', $comment);
$tpl->display('comment.tpl');
?>Notice that the post attribute for the comment is being set explicitly using the postId provided in the URI (post=[postId]). This is because unless you specify otherwise, BlogPostComment::newInstanceFromForm only looks at the POST headers for attributes to use to initialize the comment. And finally, we need the wrapper template comment.tpl: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{$Locale->getId()}" xml:lang="{$Locale->getId()}">
<head>
<title>{$resources->getString('blog.submitComment')}</title>
</head>
<body>
<h1>{$resources->getString('blog.submitComment')}</h1>
{if $comment->validateErrors}
<font style="color:red">
<ul>
{foreach from=$comment->validateErrors item=error}
<li>{$error}</li>
{/foreach}
</ul>
</font>
{/if}
<form action="" method="post">
{$comment->render('form')}
<input type="submit" />
</form>
</body>
</html>Pretty easy, and very similar to the form we already created for adding a new post. Now reload your browser and test. Everything should work. Delete CommentWe'll skip edit comment (since comment editing is not a comment blogging software feature anyway) and move straight into delete. As you may have already guessed, this is going to be very similar to our delete post. First let's create a delete link in views/blog-post-comment.tpl: <h4><a href="mailto:{$entity->getEmail()}">{$entity->getName()}</a></h4>
<p>
<strong>{$entity->getPosted(0, 1)}</strong>
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteCommentConfirm')}')) document.location.replace('/delete-comment.php?commentId={$entity->getCommentId()}')">{$resources->getString('text.delete')}</a>
<br />
{$entity->getComment()}
</p>Next, add our confirm string to app.properties: blog.deleteCommentConfirm=Are you sure you want to delete this comment? And finally, add our delete comment controller delete-comment.php: <?php
include_once('/var/www/sierra/lib/core/SRA_Controller.php');
SRA_Controller::init([app id]);
$dao =& SRA_DaoFactory::getDao('BlogPostComment');
if (count($_GET) && BlogPostComment::isValid($comment =& $dao->findByPk($_GET['commentId']))) {
$comment->delete();
}
header('Location: /');
?>Refresh and test it all out. We have now added commenting functionality to our blogging system. Post and Comment OrderingSo currently our blogging app is displaying posts from oldest to newest, and that is just wrong. So, lets change how we retrieve our posts the ordering is from newest to oldest instead. To accomplish this, open index.php and lets replace $dao->findAll() with another DAO finder method named findByQuery: <?php
include_once('/var/www/sierra/lib/core/SRA_Controller.php');
SRA_Controller::init([app id]);
$dao =& SRA_DaoFactory::getDao('BlogPost');
$posts =& $dao->findByQuery('SELECT post_id FROM blog_post ORDER BY posted DESC');
$tpl =& SRA_Controller::getAppTemplate();
$tpl->assignByRef('posts', $posts);
$tpl->display('index.tpl');
?>Pretty quick and easy change. Now lets do the same for comments. This will be even easier because sierra-php provides an attribute configuration parameter for defining ordering: <attribute key="comments" cardinality="0..*" on-delete-cascade="1" on-remove-delete="1" order-by="posted DESC" type="BlogPostComment" /> Refresh, and everything should be ordered correctly now from newest to oldest for both posts and comments. AuthenticationOk, great... now we have a somewhat functional blogging system that the entire world is free to use. After spending all that time writing your posts you don't want just anyone to be able to delete them right? So how do we deal with this issue? Is it going to require a lot of changes to our code and/or model? NO, it won't. In fact, with sierra-php's built-in authentication module it will actually be very easy. We won't even have to create a login page, because sierra-php uses the standard browser supported HTTP authentication headers. So let's get started and see how this works. First of all, you should understand how authentication works in sierra-php. In sierra-php authentication is implemented declaratively... meaning in your application's configuration file (etc/app-config.xml), NOT in the code of your PHP script. In app-config.xml you define which authenticator(s) you want to use (sierra-php currently supports database, LDAP and Linux OS user authentication), and then restrict access to sensitive web accessible PHP scripts by associating them to 1 or more authenticators. You can even chain multiple authenticators together (for example, first verify user is valid in LDAP directory, and then verify that user also exists in my database). Additionally, you can impose site-wide authentication if there are no publicly accessible scripts and you can even restrict sierra-php console access using the same authenticators (instead of starting right up, the console will first prompt for a user and password). Authentication is one of the very nice and very useful features of sierra-php and will greatly reduce your codebase when access restrictions are necessary in your application. Configuring AuthenticationNow that you understand at a high level how authentication works, lets get right to it and add some authentication to our blogging system. In order to do so, we must first decide where we will store and access the users... the supported datastores currently are database, LDAP or Linux OS. For our blogging system we'll go with the database option. So now we need to create an entity in our model for users. At a minimum this entity will need to have username and password attributes. Here is my entity: <entity key="BlogAdmin" primary-key="adminId">
<attribute key="adminId" sequence="1" />
<attribute key="name" depends="required" max-length="16" />
<attribute key="password" depends="required" min-length="4" max-length="255" set-function="md5" set-only="1" />
<attribute key="userName" depends="required" max-length="16" />
</entity>We also need to add a new msg to the model for the mini-length constraint on the password attribute: <msg key="minLength" resource="error.minLength" /> So what's new here? The password attribute has some new configurations. First, set-function defines the name of a database function that should be used when this attribute is stored (using an insert or update query). So, this means we will be encrypting the password using MySQL's md5 encryption function. Second, set-only signifies that this attribute can ONLY be set, it can not be "gotten" for lack of a better word. This is because once the password is encrypted and stored in the database, there is no way for sierra-php to decrypt it. Therefore, if you retrieve a BlogAdmin object out of the database using sierra-php, the password attribute will always be NULL. Finally, min-length defines the minimum allowed number of characters for password. We're going to make our blogging system passwords super secure with 4 character minimum passwords. And here are my app.properties strings: BlogAdmin=Blog Administrator
BlogAdmin.adminId=Admin Id
BlogAdmin.adminId-help=The unique identifier for this administrator
BlogAdmin.name=Name
BlogAdmin.name-help=The administrator's name
BlogAdmin.password=Password
BlogAdmin.password-help=The administrator's password
BlogAdmin.userName=Username
BlogAdmin.userName-help=The administrator's user name
error.minLength=The min length of {$attr} is {$minLength} charactersNow we need to create a blog admin user. We won't waste time creating a web-based user management tool. Instead, we'll just use the handy sierra-php console to do the job: SRA_DaoFactory::getDao('BlogAdmin');
$admin = new BlogAdmin();
$admin->setName('JoJo');
$admin->setUserName('admin');
$admin->setPassword('admin');
$admin->insert();That's it, now we have our first blog administrator user, and now we can move on to actually restricting access to some of our controller scripts. Open the application configuration [app]/etc/app-config.xml and we'll knock this out fast. First we need to define an authenticator: <authenticator key="blog" logout-fwd-uri="/" logout-get-var="logout" resource="text.login" sys-err-tpl="error.tpl" tpl-var="user" type="SRA_DbAuthenticator">
<param id="db" value="blog" />
<param id="table" value="blog_admin" />
<param id="user-col" value="user_name" />
<param id="pswd-col" value="password" />
<param id="pswd-fun" value="md5" />
<param id="name" type="attrs" value="name" />
</authenticator>Here is what all of this means: General Authenticator Configurations
Database Authenticator Configurations These include all of the param id="..." values.
Now I need to create my sys-err-tpl ([app]/www/tpl/error.tpl): <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{$Locale->getId()}" xml:lang="{$Locale->getId()}">
<head>
<title>{$Controller->getAppName()}</title>
</head>
<body>
{$resources->getString('error.sys')}
</body>
</html>and add a couple of app.properties strings: error.sys=A system error has occurred text.login=You need a username and password to do this! Ok, that was the hard part, now we can restrict access to some of our controller scripts. Lets restrict access to the following scripts: delete-comment.php, delete.php, edit.php, post.php and sra-ws-gateway.php (the web services controller). To do so, we simply add this one line to app-config.xml: <restrict-access authenticators="blog" match="delete-comment.php|delete.php|edit.php|post.php|sra-ws-gateway.php" /> So now our full app-config.xml file looks like this: <?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE app-config PUBLIC "-//SIERRA//DTD APP CONFIG//EN"
"http://sierra-php.googlecode.com/svn/trunk/etc/app-config.dtd">
<app-config error-log-file="blog.log">
<authenticator key="blog" logout-fwd-uri="/" logout-get-var="logout" resource="text.login" sys-err-tpl="error.tpl" tpl-var="user" type="SRA_DbAuthenticator">
<param id="db" value="blog" />
<param id="table" value="blog_admin" />
<param id="user-col" value="user_name" />
<param id="pswd-col" value="password" />
<param id="pswd-fun" value="md5" />
<param id="name" type="attrs" value="name" />
</authenticator>
<db key="blog" name="sierraphp_blog" user="sierraphp" password="sierraphp" />
<restrict-access authenticators="blog" match="delete-comment.php|delete.php|edit.php|post.php|sra-ws-gateway.php" />
<use-entity-model key="blog" path="blog-model.xml"/>
</app-config>Now, lets refresh the browser and test this out. First click on the "Create Blog Post" link. You should get hit up to login. If you did, proceed to test access to all of the other scripts, otherwise, check your error log. Ok, so now lets try logging out. Point your browser to /post.php/?logout=1 (note: the logout has to occur on one of the restricted access scripts). This should then redirect you back to the homepage. Now click on one of the restricted access links again. Note... you are now re-prompted to login. There is a lot of tricky stuff happening behind the scenes in order to make this work due to browser authentication credential caching... but rest assured it does work and with sierra-php you will never have to write another login screen again! Blog Post AuthorWe talked about the tpl-var authenticator hash previously and how it will allow use to access user attributes on authenticated pages. So let's put that to some good use. Fire up the model again and let's add a new attribute to BlogPost named author: <attribute key="author" max-length="16" /> and our properties file entry: BlogPost.author=Author BlogPost.author-help=The name of the blog post author Now, open blog-post-form.tpl and make this line to the end: <input name="author" type="hidden" value="{$user.name}" />And, open blog-post.tpl and make this change to the <strong>{$entity->getPosted(0, 1)}</strong> line: <strong>{if $entity->getAuthor()}{$entity->getAuthor()} - {/if}{$entity->getPosted(0, 1)}</strong>And finally create a new post... and there it is, your name should be to the left of the post time. How did this happen? We took advantage of the name value in the user tpl-var hash provided to us completely free of charge by the database authenticator. Spanish TranslationNow we get to really see the advantage of breaking out all of our strings into app.properties. We will translate that file into spanish using the Yahoo Babel Fish translation service. The first step is to create a spanish version of app.properties, so open that file up and save a copy as app_es.properties using UTF-8 encoding. Now, go to the translation service using a browser and paste in a few strings at a time for translation. Then copy them back into the spanish properties files. This is what I ended up with: sierraphp-blog=sierra-php Blog
BlogAdmin=Administrador del blog
BlogAdmin.adminId=Identificación del Admin
BlogAdmin.adminId-help=El identificador único para este administrador
BlogAdmin.name=Nombre
BlogAdmin.name-help=El nombre del administrador
BlogAdmin.password=Contraseña
BlogAdmin.password-help=La contraseña del administrador
BlogAdmin.userName=Nombre de usuario
BlogAdmin.userName-help=El nombre de usuario del administrador
BlogPost=Poste
BlogPost.postId=Identificación del Poste
BlogPost.postId-help=El identificador único para este poste del blog
BlogPost.author=Autor
BlogPost.author-help=El nombre del autor del poste del blog
BlogPost.comments=Comentarios
BlogPost.comments-help=Los comentarios sometieron para este poste del blog
BlogPost.post=Poste
BlogPost.post-help=El contenido del poste del blog
BlogPost.posted=Tiempo del poste
BlogPost.posted-help=La fecha y la hora en que este poste fue creado
BlogPost.title=Título
BlogPost.title-help=El título del poste del blog
BlogPostComment=Comentario
BlogPostComment.commentId=Identificación del comentario
BlogPostComment.commentId-help=El identificador único para este comentario
BlogPostComment.comment=Comentario
BlogPostComment.comment-help=El comentario que es hecho
BlogPostComment.email=Email
BlogPostComment.email-help=El email del autor del comentario
BlogPostComment.name=Nombre
BlogPostComment.name-help=El nombre del autor del comentario
BlogPostComment.post=Poste
BlogPostComment.post-help=El poste del blog este comentario fue sometido para
BlogPostComment.posted=El tiempo de la someta
BlogPostComment.posted-help=La fecha y la hora en que este comentario fue sometido
blog.deleteCommentConfirm=¿Está usted seguro usted quiere suprimir este comentario?
blog.deleteConfirm=¿Está usted seguro usted quiere suprimir este poste?
blog.edit.title=Corrija el poste del blog
blog.noComments=No hay comentarios para este poste
blog.post.title=Cree un poste del blog nuevo
blog.submitComment=Someta el nuevo comentario
error.email={$attr} es un email no correctamente formatado
error.maxLength=La longitud máxima de {$attr} es los caracteres {$maxLength}
error.minLength=La longitud mínima de {$attr} es los caracteres {$minLength}
error.required={$attr} se requiere
error.sys=Un error de sistema ha ocurrido
text.delete=Cancelación
text.edit=Corrija
text.login=¡Usted necesita un username y una contraseña hacer esto!That's all you have to do... no need to add a language selector to the site, because the preferred languages, and the order of preference for them is already provided by the browser in the request headers. sierra-php looks at those headers and finds the closest matching properties file to use for your application. If you want to text it, just launch Firefox and change your language preferences (under Preferences => Advanced => General => Languages in my browser) so Spanish is your first choice. Then reload your browser windows and all of the labels and help content should change automatically. Ugly URLs Pet PeeveI hate ugly, technology exposing URLs! So the final thing I'd like to change with this blogging app is to beautify the URLs using Apache rewrites. This is completely optional for you. Here goes: First setup the rewrite rules in [app]/www/html/.htaccess: RewriteRule ^comment/(.*) /add-comment.php?post=$1 RewriteRule ^comment/delete/(.*) /delete-comment?commentId=$1 RewriteRule ^post/delete/(.*) /delete.php?postId=$1 RewriteRule ^post/edit/(.*) /edit.php?postId=$1 RewriteRule ^post/ /post.php RewriteRule ^logout /post.php?logout=1 Now, edit the links in blog-post.tpl: <hr />
<h2>{$entity->getTitle()}</h2>
<p>
<strong>{if $entity->getAuthor()}{$entity->getAuthor()} - {/if}{$entity->getPosted(0, 1)}</strong>
<a href="/post/edit/{$entity->getPostId()}">{$resources->getString('text.edit')}</a> |
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteConfirm')}')) document.location.replace('/post/delete/{$entity->getPostId()}')">{$resources->getString('text.delete')}</a>
<br />
{$entity->getPost()}
<h3>{$entity->getCommentsLabel()}</h3>
<p><a href="/comment/{$entity->getPostId()}">{$resources->getString('blog.submitComment')}</a></p>
{if $entity->getComments()}
{foreach from=$entity->getComments() item=comment}
{$comment->render('html')}
{/foreach}
{else}
{$resources->getString('blog.noComments')}
{/if}
</p>And in blog-post-comment.tpl: <p>
<strong><a href="mailto:{$entity->getEmail()}">{$entity->getName()}</a> - {$entity->getPosted(0, 1)}</strong>
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteCommentConfirm')}')) document.location.replace('/comment/delete/{$entity->getCommentId()}')">{$resources->getString('text.delete')}</a>
<br />
{$entity->getComment()}
</p>And finally in index.tpl: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="{$Locale->getId()}" xml:lang="{$Locale->getId()}">
<head>
<title>{$Controller->getAppName()}</title>
</head>
<body>
<h1>{$Controller->getAppName()}</h1>
{foreach from=$posts item=post}
{$post->render('html')}
{/foreach}
<p><a href="/post/">{$resources->getString('blog.post.title')}</a></p>
</body>
</html>Now test them all out... much, much better! Blog Post PicturesOne of the more powerful features of the sierra-php entity model is file handling. I'll demonstrate this by adding the ability to upload blog post pictures with just a few changes to our model and views. The requirement is that we want to allow blog posters to upload an optional related image with each that will be displayed below the title to spice up the post a bit. In order to do this, we must first modify our blog-model to include the additional attribute which we will call picture: <attribute key="picture" depends="maxFileSize mimeType" is-file="1">
<var key="mimeTypes" value="image.*" />
<var key="maxFileSize" value="102400" />
<view key="input" extends="input-file" />
<view key="output" extends="image" />
</attribute>This attribute uses 2 new constraints, maxFileSize and mimeType which allow us to restrict the type of files that can be uploaded to just image/* mime types (i.e. image/png or image/gif) and also restrict the file size to 100 KB (102400 bytes) so they aren't uploading their 4 megapixel images. The attribute also uses 2 new views, input-file and image which are defines in the global-views section as follows: <view key="image" template="model/sra-attr.tpl">
<param id="value" type="tpl" value="model/sra-html-img.tpl" />
</view>
<view key="input-file" template="model/sra-attr.tpl">
<param id="value" type="tpl" value="model/sra-form-file.tpl" />
<param id="showResetLink" value="text.reset" />
</view>These views use some generic file/image related templates provides by the sierra-php framework. We could have just as easily defined our own templates for these views with a file input field and img output element. Now we need to also add the error messages for the new validation constraints to our blog-model: <msg key="maxFileSize" resource="error.maxFileSize" /> <msg key="mimeType" resource="error.mimeType" /> and the corresponding error messages to app.properties: error.maxFileSize=The maximum size for uploaded files for {$attr} is 100 KB
error.mimeType=You may only upload the following types of files: {$mimeTypes}and finally we need to add a new string to BlogPost for the picture attribute also in app.properties: BlogPost.picture=Picture BlogPost.picture-help=An optional blog post picture That's it... we are now done making changes to the model. Now we just need to change our input and output views for BlogPost. To do so, first open the file www/tpl/edit.tpl where we need to make a simple change to the form element to support file uploads: <form action="" method="post" enctype="multipart/form-data"> Now open www/tpl/views/blog-post-form.tpl and make these changes: <label>{$entity->getTitleLabel()}</label>: {$entity->renderAttribute('title', 'input')}
<p>{$entity->getTitleHelpContent()}</p>
<hr />
<label>{$entity->getPostLabel()}</label>: {$entity->renderAttribute('post', 'input')}
<p>{$entity->getPostHelpContent()}</p>
<label>{$entity->getPictureLabel()}</label>: {$entity->renderAttribute('picture', 'input')}
<p>{$entity->getPictureHelpContent()}</p>
{if $entity->recordExists}{$entity->renderAttribute('postId', 'input')}{/if}
<input name="author" type="hidden" value="{$user.name}" />and www/tpl/views/blog-post.tpl: <hr />
<h2>{$entity->getTitle()}</h2>
{if $entity->getPicture()}
<p>{$entity->renderAttribute('picture', 'output')}</p>
{/if}
<p>
<strong>{if $entity->getAuthor()}{$entity->getAuthor()} - {/if}{$entity->getPosted(0, 1)}</strong>
<a href="/post/edit/{$entity->getPostId()}">{$resources->getString('text.edit')}</a> |
<a href="#" onclick="if (confirm('{$resources->getString('blog.deleteConfirm')}')) document.location.replace('/post/delete/{$entity->getPostId()}')">{$resources->getString('text.delete')}</a>
<br />
{$Template->lineBreaksToBr($entity->getPost())}
<h3>{$entity->getCommentsLabel()}</h3>
<p><a href="/comment/{$entity->getPostId()}">{$resources->getString('blog.submitComment')}</a></p>
{if $entity->getComments()}
{foreach from=$entity->getComments() item=comment}
{$comment->render('html')}
{/foreach}
{else}
{$resources->getString('blog.noComments')}
{/if}
</p>Now, there is one more thing I forgot to setup... sierra-php provides a special php script for handling file rendering. This script is located in sierra/lib/model/sra-file-renderer.php. So, we need to setup an alias and rewrite rule for that script in our www root. First, add the alias to the vhost configuration: <VirtualHost *:80> DocumentRoot /var/www/sierra/app/sierraphp-blog/www/html ServerName blog.sierra-php.org ServerAlias blog.sierraphp.org Alias /sra-ws-gateway.php "/var/www/sierra/lib/model/sra-ws-gateway.php" Alias /sra-file-renderer.php "/var/www/sierra/lib/model/sra-file-renderer.php" </VirtualHost> and now, create a rewrite rule in www/html/.htaccess: RewriteRule ^files/(.*)/(.*) /sra-file-renderer.php?eid=$1 and finally, we must specify the file script alias in blog-model (the file-script* attributes): <entity-model db="blog" file-script-rewrite="1" file-script-uri="/files" sync-schema="1" vo-suffix="" ws-gateway-rewrite="1" ws-gateway-uri="/ws"> that's it... now we can reload our blog through a browser, edit a post, and attach an image. when you do so, the image will now show up below the title automatically. If you look at the database schema for the blog_post table, you will also notice that sierra-php automatically added a picture column of type mediumblob. PDF BlogPost Print ViewThe last sierra-php feature I'll demonstrate is the ability to pipe Smarty template generated text into CLI executable programs in order to produce other types of output for that view. We'll use this feature to add a new PDF view to BlogPost which will provide users with a print-friendly view of the post. To do so, we'll use the view's Smarty template to generate XSL-FO markup and Apache FOP to transform that markup into a PDF document in real-time. If you are not familiar with XSL-FO, it is a W3C standard like XHTML, but designed with the intent of being transformed into a printable artifact, usually PDF. Apache FOP is a free open sourced implementation of that standard. The first step in adding this view is to define a view-processor in the blog model for Apache FOP. This is basically the path and arguments that should be used when piping in the XSL-FO generated by the Smarty template into Apache FOP: <view-processor key="fop"
args="{$outputFile} {$randomFile1}"
output-file-path="{$randomFile1}"
path="/usr/local/bin/fop" />Next we define a new view for BlogPost which also utilizes the FOP view processor we just defined, as well as specifies a custom mime-type which will translate to a Content-type response header when the view is rendered. <view key="pdf"
mime-type="application/pdf"
view-processors="fop"
template="views/blog-post-fo.tpl" />Finally, we create the Smarty XSL-FO template shown in Listing 14. Now all we need to do in order to render the PDF view is something like this: $dao =& SRA_DaoFactory::getDao('BlogPost');
if (BlogPost::isValid($post =& $dao->findByPk($_GET['postId'])))
$post->render('pdf');When $post->render('pdf') is invoked, sierra-php will first render views/blog-post-fo.tpl to a temporary file $outputFile, and then run /usr/local/bin/fop with two arguments, the first of which is the path to $outputFile, and the second is the path to a random temporary file, {$randomFile1}. When FOP runs, it will read the XSL-FO that was generated to $outputFile and write a PDF file to {$randomFile1}. Then sierra-php will output the HTTP header Content-type: application/pdf, followed by the contents of the PDF file {$randomFile1}. Finally, sierra-php will clean up the temporary files. SummarySo this may seem like a lot... and it really is a lot. However, we have really just skimmed the surface of the features provided by sierra-php with this demo application. If you are interested in learning more, the DTDs are very well documented and a great place to start: Happy coding! |