<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Coding My Thoughts &#187; Lithium</title>
	<atom:link href="http://marianoiglesias.com.ar/category/li3-lithium/feed/" rel="self" type="application/rss+xml" />
	<link>http://marianoiglesias.com.ar</link>
	<description>A glimpse at a coder&#039;s troubled mind</description>
	<lastBuildDate>Wed, 06 Jan 2010 19:25:16 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0</generator>
		<item>
		<title>Building a blog with Lithium and Doctrine</title>
		<link>http://marianoiglesias.com.ar/li3-lithium/building-a-blog-with-lithium-and-doctrine/</link>
		<comments>http://marianoiglesias.com.ar/li3-lithium/building-a-blog-with-lithium-and-doctrine/#comments</comments>
		<pubDate>Sat, 02 Jan 2010 21:47:38 +0000</pubDate>
		<dc:creator>mariano</dc:creator>
				<category><![CDATA[Lithium]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[li3]]></category>

		<guid isPermaLink="false">http://marianoiglesias.com.ar/?p=137</guid>
		<description><![CDATA[<p>This post aims to be a very basic introduction to the world of <a href="http://li3.rad-dev.org" rel="nofollow" >Lithium</a>, also known as <a href="http://li3.rad-dev.org" rel="nofollow" >li3</a>. As such, it is mostly based on the <a href="http://rad-dev.org/lithium_examples" rel="nofollow" >ubiquitous blog sample</a>. The idea&#8230;</p>


Related posts:<ol><li><a href='http://marianoiglesias.com.ar/cakephp/pagination-with-custom-find-types-in-cakephp/' rel='bookmark' title='Permanent Link: Pagination with custom find types in CakePHP'>Pagination with custom find types in CakePHP</a></li>
</ol>]]></description>
			<content:encoded><![CDATA[<p>This post aims to be a very basic introduction to the world of <a href="http://li3.rad-dev.org" rel="nofollow" >Lithium</a>, also known as <a href="http://li3.rad-dev.org" rel="nofollow" >li3</a>. As such, it is mostly based on the <a href="http://rad-dev.org/lithium_examples" rel="nofollow" >ubiquitous blog sample</a>. The idea is to learn, through the source code, the basic notions on Lithium and its integration with the <a href="http://www.doctrine-project.org/" rel="nofollow" >Doctrine ORM mapper</a> to build a very basic blog application. Unlike the blog example at rad-dev.org, this example is based on <a href="http://www.doctrine-project.org/documentation/manual/2_0/en" rel="nofollow" >Doctrine 2.0</a>, which is (at the time of this writing) in Alpha stage, and is built for PHP 5.3. After this tutorial, you should be able to jump start and build your own li3 powered applications.</p>
<p>Before we proceed, a disclaimer. This tutorial shows a rather quick and dirty integration with Doctrine (as you&#8217;ll see it&#8217;s all mostly done in a base model class), while the most clean approach would be to implement all this as an extension, using Datasources and Query parsing. I&#8217;m happy to say this is all being done as we speak (thanks kuja!), so very soon you&#8217;ll find an even better approach to Doctrine integration.</p>
<p><span id="more-137"></span></p>
<h2>Other Lithium Tutorials</h2>
<p>Of course I&#8217;m not the only one to have written about Lithium! Here are some tutorials you may enjoy:</p>
<ul>
<li>Video Tutorial: <a href="http://vimeo.com/8005503" rel="nofollow" >li3 on Apache2</a>, and <a href="http://vimeo.com/8037408" rel="nofollow" >li3 on IIS7</a>.</li>
<li>The original <a href="http://rad-dev.org/lithium/wiki/drafts/blog-tutorial" rel="nofollow" >Lithium blog tutorial</a>.</li>
</ul>
<h2>The Basics</h2>
<p>For the sakes of me being lazy, I&#8217;ll assume you have prior knowledge of:</p>
<ul>
<li>PHP 5.3 basics</li>
<li>Another PHP framework (like <a href="http://www.cakephp.org" rel="nofollow" >CakePHP, the most popular PHP framework</a>).</li>
<li>Using GIT (you should have it installed, Ubunty folks: <code>$ sudo apt-get install git-core</code>)</li>
<li>You should have an account setup with your SSH key in <a href="http://rad-dev.org/users/add" rel="nofollow" >rad-dev.org</a> (<a href="http://rad-dev.org/lithium/wiki/guides/installation" rel="nofollow" >instructions here</a>)</li>
</ul>
<h3>Adding PHP 5.3 support</h3>
<p>There are many ways people have shown how to enable PHP 5.3 support on your servers. In my case, since I run my own <a href="/ubuntu/apache2-php5-mysql-xdebug-ssl-svn-python-trac-on-ubuntu-hardy-8-04-64-bits/" rel="nofollow" >compiled version of Apache + PHP</a>, it gets easier. I&#8217;ll show you here how to set up dual support (PHP 5.2 and PHP 5.3) on your Apache in case you are as smart as me, and decided to build it yourself as well ;-]</p>
<p>First download and compile PHP 5.3 (this instructions point to the latest release of PHP 5.3 at the time of this writing, you may need to check <a href="http://www.php.net" rel="nofollow" >PHP.net</a> to see if there&#8217;s a newer version):</p>
<pre name="code">
$ cd /usr/local/src
$ sudo wget http://ar.php.net/get/php-5.3.1.tar.gz/from/this/mirror
$ sudo tar xzvf php-5.3.1.tar.gz
$ sudo chown -R $USER:$USER php-5.3.1/
$ cd php-5.3.1/
$ ./configure \
  --prefix=/usr/local/php5.3 \
  --with-config-file-path=/usr/local/php5.3 \
  --with-libxml-dir=/usr/include/libxml2 \
  --with-mysql=/usr/local/mysql \
  --enable-pdo \
  --with-pdo-mysql=/usr/local/mysql \
  --with-zlib=/usr/local/zlib \
  --with-curl \
  --with-gd \
  --with-jpeg-dir=/usr/lib \
  --with-png-dir=/usr/lib \
  --with-freetype-dir=/usr/lib \
  --with-gettext \
  --enable-mbstring \
  --enable-soap \
  --enable-ftp \
  --with-openssl
$ make
$ sudo make install
</pre>
<p>Easy right? Now, whenever you want to add a virtual host that supports PHP 5.3, you&#8217;ll have to add these lines inside the &lt;VirtualHost&gt; directive:</p>
<pre name="code">
ScriptAlias /cgi-bin/ /usr/local/php5.3/bin/
Action php53-cgi /cgi-bin/php-cgi
AddHandler php53-cgi .php5 .php

&lt;Directory "/usr/local/php5.3/bin"&gt;
	Options +ExecCGI -Includes
	Order allow,deny
	Allow from all
&lt;/Directory&gt;
</pre>
<h3>Installing Lithium</h3>
<p>First thing you&#8217;ll have to do is clone from lithium&#8217;s git repository. We&#8217;ll do it in a generic place, that we&#8217;ll be able to share from all our lithium applications, in order to keep up with the amazing ongoing development in Lithium. So let&#8217;s clone that stuff:</p>
<pre name="code">
$ cd /var/www
$ git clone code@rad-dev.org:lithium.git lithium
</pre>
<p>So now in our <code>/var/www/lithium</code> directory we have lithium installed. Every time we&#8217;ll want to ugrade to lithium&#8217;s latest release, we&#8217;ll just do:</p>
<pre name="code">
$ cd /var/www/lithium
$ git pull --rebase
</pre>
<h3>Setup the application structure</h3>
<p>We now have a generic place (<code>/var/www/lithium</code>) where we have lithium installed. Each time we start a new application, we&#8217;ll just copy over the application directory, and link to lithium&#8217;s core directory.</p>
<p>So we want to create an application in the host <code>http://blog.li3</code> so we&#8217;ll store its files in <code>/var/www/blog.li3</code>. Let&#8217;s copy the files, and link to the core:</p>
<pre name="code">
$ mkdir /var/www/blog.li3
$ cd /var/www/blog.li3
$ cp -pR /var/www/lithium/app .
$ ln -s /var/www/lithium/libraries libraries
</pre>
<h3>Add the virtual host</h3>
<p>Ok we now have the application installed in <code>/var/www/blog.li3</code>. Let&#8217;s create the virtual host for it. First edit your <code>/etc/hosts</code> file and add this line:</p>
<pre name="code">
127.0.1.1	blog.li3
</pre>
<p>Now let&#8217;s add the virtual host to apache. Edit the file <code>/usr/local/apache2/conf/extra/httpd-vhosts.conf</code> (obviously this may be different in your setup) and add the following:</p>
<pre name="code">
&lt;VirtualHost *:80&gt;
	ServerName blog.li3
	DocumentRoot "/var/www/blog.li3/app/webroot"
	ErrorLog "logs/blog.li3.error.log"
	CustomLog "logs/blog.li3.access.log" combined

	ScriptAlias /cgi-bin/ /usr/local/php5.3/bin/
	Action php53-cgi /cgi-bin/php-cgi
	AddHandler php53-cgi .php5 .php

	&lt;Directory "/usr/local/php5.3/bin"&gt;
		Options +ExecCGI -Includes
		Order allow,deny
		Allow from all
	&lt;/Directory&gt;
&lt;/VirtualHost&gt;
</pre>
<p>You should now restart apache:</p>
<pre name="code">
$ sudo /etc/init.d/apachectl restart
</pre>
<p>And you should be able to browse to <a href="http://blog.li3" rel="nofollow" >http://blog.li3</a> and see a screen just like this:</p>
<div id="attachment_142" class="wp-caption aligncenter" style="width: 310px"><a href="http://marianoiglesias.com.ar/wp-content/uploads/2010/01/blog_li3-screen1.png"><img src="http://marianoiglesias.com.ar/wp-content/uploads/2010/01/blog_li3-screen1-300x183.png" alt="" title="blog_li3-screen1" width="300" height="183" class="size-medium wp-image-142" /></a><p class="wp-caption-text">Lithium's welcome screen</p></div>
<h3>Install Doctrine 2.0</h3>
<p>We&#8217;ll install doctrine as a system-wide library. Therefore we&#8217;ll move to our generic lithium install&#8217;s library path (<code>/var/www/lithium/libraries</code>) and download doctrine inside. Now we need the Doctrine ORM release (you may need to check the <a href="http://www.doctrine-project.org/download" rel="nofollow" >doctrine download page</a> for a more up-to-date version of the 2.0 branch:</p>
<pre name="code">
$ cd /var/www/lithium/libraries
$ mkdir doctrine
$ cd doctrine
$ wget http://www.doctrine-project.org/download/2_0_0_ALPHA3/format/tgz/package/ORM
$ tar -xzvf DoctrineORM-2.0.0-ALPHA3.tgz
</pre>
<h2>Loading the Database</h2>
<p>Now that we have the basics in place (PHP 5.3 enabled, Lithium installed, Doctrine installed), we&#8217;ll start building our application.</p>
<h3>Creating the Database and loading some data</h3>
<p>Let&#8217;s start by creating a database named <code>blog_li3</code>:</p>
<pre name="code">
$ mysqladmin -uroot -p create blog_li3
</pre>
<p>Now load this SQL statements to the database we&#8217;ve just created:</p>
<pre name="code" class="sql">
CREATE TABLE `posts`(
    `id` INT NOT NULL AUTO_INCREMENT,
    `title` VARCHAR(255) NOT NULL,
    `body` TEXT NOT NULL,
    `published` BOOLEAN NOT NULL default FALSE,
    `created` DATETIME NOT NULL,
    `modified` DATETIME NOT NULL,
    PRIMARY KEY(`id`)
);

CREATE TABLE `comments`(
    `id` INT NOT NULL AUTO_INCREMENT,
    `post_id` INT NOT NULL,
    `name` VARCHAR(255) NOT NULL,
    `email` VARCHAR(255) NOT NULL,
    `body` TEXT NOT NULL,
    `created` DATETIME NOT NULL,
    `modified` DATETIME NOT NULL,
    PRIMARY KEY(`id`)
);

ALTER TABLE `comments`
    ADD KEY `post_id`(`post_id`),
    ADD CONSTRAINT `comments__posts` FOREIGN KEY (`post_id`) REFERENCES `posts`(`id`);

INSERT INTO `posts` VALUES
    (1, 'First post', 'This is the body for the first post', TRUE, NOW(), NOW());

INSERT INTO `comments` VALUES
    (1, 1, 'Mariano Iglesias', 'mariano@email.com', 'This is a nice post!', NOW(), NOW());
</pre>
<h3>Setting up the DB connection</h3>
<p>Let&#8217;s add the basics Doctrine needs to run into Lithium&#8217;s bootstrap. Edit the file <code>app/config/bootstrap.php</code> in your application directory (<code>/var/www/blog.li3</code>) and add the following at the end:</p>
<pre name="code" class="php">
/**
 * Load Doctrine
 */

Libraries::add('Doctrine', array(
	'path' => LITHIUM_LIBRARY_PATH . '/doctrine/DoctrineORM-2.0.0/Doctrine'
));
</pre>
<p>Now how easy was that? Since Doctrine shares the same name structure Lithium does, the inclusion of Doctrine classes in Lithium is that easy. Next step: let&#8217;s set up the connection. Open <code>app/config/connections.php</code> and replace the whole <code>Connections::add</code> expression with the following (notice how we are adding a connection of type &#8216;doctrine&#8217; rather than the default type &#8216;database&#8217; lithium provides):</p>
<pre name="code" class="php">
Connections::add('default', 'doctrine', array(
	'driver' => 'pdo_mysql',
	'host' => 'localhost',
	'user' => 'root',
	'password' => 'password',
	'dbname' => 'blog_li3'
));
</pre>
<h3>Creating a wrapper for some Doctrine functionality</h3>
<p>If you go through <a href="http://www.doctrine-project.org/documentation/manual/2_0/en" rel="nofollow" >doctrine documentation</a>, you&#8217;ll see it&#8217;s quite powerful and flexible. However, we&#8217;ll want to have a basic wrapper around the most common functionality (establishing the connection, querying a model, creating / editing / deleting records) so our work from our controllers is more transparent, and Doctrine agnostic. So let&#8217;s create a base class for all models (CakePHP folks, this will sound familiar) and name it <code>AppModel</code></p>
<p>First, the code. Create a file named <code>AppModel.php</code> in your <code>app/</code> directory, with the following contents.</p>
<pre name="code" class="php">
&lt;?php
namespace app;

use \lithium\data\Connections;

abstract class AppModel extends \lithium\data\model\Record {
	protected static $_alias;
	protected static $_entityManagers = array();

	public static function create($data = array()) {
		$class = get_called_class();
		$record = new $class();
		return $record-&gt;set($data);
	}

	public function set($data = array()){
		if (isset($data) &#038;&#038; count($data) &gt; 1) {
			if (!isset($data['created']) &#038;&#038; !isset($data['id']) &#038;&#038; !isset($this-&gt;id)) {
				$data['created'] = new \DateTime(date('Y-m-d H:i:s'));
			}

			if (!isset($data['modified'])) {
				$data['modified'] = new \DateTime(date('Y-m-d H:i:s'));
			}

			foreach($data as $key =&gt; $value) {
				$this-&gt;$key = $value;
			}
		}
		return $this;
	}

	public function save() {
		$entityManager = $this-&gt;_entityManager();
		$entityManager-&gt;persist($this);
		$entityManager-&gt;flush();
		return $this;
	}

	public function delete($id = null) {
		if (empty($id)) {
			$id = $this-&gt;id;
		}
		$entityManager = $this-&gt;_entityManager();
		$entityManager-&gt;remove($this);
		$entityManager-&gt;flush();
		return $this;
	}

	public static function find($type = 'first', $query = array()) {
		$class = get_called_class();
		$alias = self::$_alias ?: substr($class, strrpos($class, '\\') + 1);
		$methods = array('first', 'all');

		if (!in_array($type, $methods)) {
			throw new \Exception("Find method \"{$type}\" is not implemented");
		}

		$query = array_merge(array(
			'fields' =&gt; null,
			'conditions' =&gt; null,
			'order' =&gt; null
		), $query);

		$fields = array();
		if (isset($query['fields'])) {
			foreach($query['fields'] as $field) {
				$model = $alias;
				if (strpos($field, '.') === false) {
					list($model, $field) = explode('.', $field);
				}
			}
		} else {
			$fields[] = $alias;
		}

		$queryBuilder = self::_entityManager()-&gt;createQueryBuilder();
		$queryBuilder-&gt;add('select', $alias)-&gt;add('from', $queryBuilder-&gt;expr()-&gt;from($class, $alias));

		$expression = self::_conditionsToDqlExpr($alias, $queryBuilder, $query['conditions']);
		if (isset($expression)) {
			$queryBuilder-&gt;add('where', $expression);
		}

		if (!empty($query['order'])) {
			$order = $query['order'];
			if (is_array($query['order'])) {
				$order = array();
				foreach($query['order'] as $field =&gt; $direction) {
					if (!is_string($field)) {
						$field = $direction;
						$direction = 'asc';
					}
					$order[] = (strpos($field, '.') === false ? $alias . '.' : '') . $field . ' ' . $direction;
				}
				$order = implode(',', $order);
			}
			$queryBuilder-&gt;add('orderBy', $order);
		}

		$dql = $queryBuilder-&gt;getDql();
		$result = self::_entityManager()-&gt;createQuery($dql)-&gt;getResult();
		if (isset($result) &#038;&#038; $type == 'first') {
			$result = current($result);
		}
		return $result;
	}

	protected static function _conditionsToDqlExpr($alias, $queryBuilder, $conditions) {
		$expr = $queryBuilder-&gt;expr();
		if (empty($conditions)) {
			return null;
		} else if (is_string($conditions)) {
			$expr = $expr-&gt;andx($conditions);
		} else {
			foreach($conditions as $key =&gt; $value) {
				if (is_string($key) &#038;&#038; in_array(strtolower($key), array('and', 'or'))) {
					$clause = strtolower($key);
					$pieces = array();
					foreach((array) $value as $innerKey =&gt; $piece) {
						if (is_string($innerKey)) {
							$piece = array($innerKey =&gt; $piece);
						}
						$pieces[] = self::_conditionsToDqlExpr($alias, $queryBuilder, $piece);
					}
					$expr = call_user_func_array(array($expr, $clause . 'x'), $pieces);
				} else if (is_string($key)) {
					if (strpos($key, '.') === false) {
						$key = $alias . '.' . $key;
					}
					if (is_array($value)) {
						$values = $value;
						foreach($values as $iv =&gt; $value) {
							$values[$iv] = $expr-&gt;literal($value);
						}
						$expr = $expr-&gt;in($key, $values);
					} else {
						$expr = $expr-&gt;eq($key, $expr-&gt;literal($value));
					}
				} else {
					$expr = $expr-&gt;andx(self::_conditionsToDqlExpr($alias, $queryBuilder, $value));
				}
			}
		}

		return $expr;
	}

	protected static function _entityManager($connectionName = 'default') {
		if (!isset(self::$_entityManagers[$connectionName])) {
			$connection = Connections::get($connectionName, array('config'=&gt;true));
			if (!isset($connection)) {
				throw new \Exception("Configuration {$connectionName} not found");
			} else if ($connection['type'] != 'doctrine') {
				throw new \Exception("Configuration {$connectionName} is not a doctrine configuration");
			}

			$config = new \Doctrine\ORM\Configuration();
			$config-&gt;setProxyDir(LITHIUM_APP_PATH . '/models');
			$config-&gt;setProxyNamespace('app\models');

			self::$_entityManagers[$connectionName] = \Doctrine\ORM\EntityManager::create($connection, $config);
		}
		return self::$_entityManagers[$connectionName];
	}

	/**
	 * Overrides for lithium/data/model/Record
	 */

	public function exists() {
		return !empty($this-&gt;id);
	}

	public function __get($name) {
		return isset($this-&gt;$name) ? $this-&gt;$name : null;
	}
}

?&gt;
</pre>
</p>
<p>That&#8217;s a lot, right? First of all, this is generic enough so we won&#8217;t go through the detailed explanation line by line, but we&#8217;ll go through what each method does, and why is there. So let&#8217;s see:</p>
<ul>
<li><code>create()</code>: creates a new instance of a record class, loading it with the specified data.</li>
<li><code>set()</code>: sets the specified data on the current record instance</li>
<li><code>save()</code>: saves (persists) the current record</li>
<li><code>delete()</code>: deletes the current record, or the record whose ID is specified</li>
<li><code>find()</code>: it finds :-] It tries to mimic the same parameters CakePHP has on its find method. Takes two parameters: first, the find type (only &#8216;first&#8217; and &#8216;all&#8217; supported here), and second an array of options (supported options: &#8216;fields&#8217;, &#8216;conditions&#8217;, &#8216;order&#8217;)</li>
<li><code>_conditionsToDqlExpr()</code>: helper method used by find() to convert the array based conditions into a Doctrine DQL Query (using <a href="http://www.doctrine-project.org/documentation/manual/2_0/en/query-builder" rel="nofollow" >Doctrine&#8217;s QueryBuilder</a>)</li>
<li><code>_entityManager()</code>: gives back <a href="http://www.doctrine-project.org/documentation/manual/2_0/en/working-with-objects" rel="nofollow" >Doctrine&#8217;s EntityManager</a> used to perform actual operations</li>
<li><code>exists()</code>: Tells if current record exists. Overriden from Lithium&#8217;s record to add support for the record in the FormHelper</li>
<li><code>__get()</code>: Gives back the given field&#8217;s value for current record. Overriden from Lithium&#8217;s record to add support for the record in the FormHelper</li>
</ul>
<h2>The models</h2>
<p>Ok now to the fun part, creating the models our application will need. If you remember, the SQL script created two tables: <code>posts</code> and <code>comments</code>. Therefore we&#8217;ll have two models, <code>Post</code> and <code>Comment</code>. In order to link each comment&#8217;s property to a real table field, we&#8217;ll use <a href="http://www.doctrine-project.org/documentation/manual/2_0/en/basic-mapping" rel="nofollow" >Doctrine&#8217;s annotations</a>. They define the characteristics of each record field through source code comments.</p>
<p>Let&#8217;s first create the <code>Post</code> model. Create a file named <code>Post.php</code> in your <code>app/models</code> folder with the following contents:</p>
<pre name="code" class="php">
&lt;?php
namespace app\models;

/**
 * @Entity
 * @Table(name="posts")
 */
class Post extends \app\AppModel {
	/**
	 * @Id @Column(type="integer")
	 * @GeneratedValue(strategy="AUTO")
	 */
	public $id;

	/** @Column(type="string") */
	public $title;

	/** @Column(type="text") */
	public $body;

	/** @Column(type="datetime") */
	public $created;

	/** @Column(type="datetime") */
	public $modified;

	/** @OneToMany(targetEntity="Comment", mappedBy="post", cascade={"remove"}) */
	public $comments;
}
?&gt;
</pre>
<p>To understand the actual annotations, go through Doctrine&#8217;s introduction to <a href="http://www.doctrine-project.org/documentation/manual/2_0/en/basic-mapping" rel="nofollow" >docblock annotations</a>. What it&#8217;s important for us is seeing that we have a property for each field in our <code>posts</code> table (what we want mapped to our record class), plus a property to store all related comments (the <code>$comments</code> property), each of which will be an instance of the <code>Comment</code> class.</p>
<p>So what about the <code>Comment</code> class?  Create a file named <code>Comment.php</code> in your <code>app/models</code> folder with the following contents:</p>
<pre name="code" class="php">
&lt;?php
namespace app\models;

/**
 * @Entity
 * @Table(name="comments")
 */
class Comment extends \app\AppModel {
	/**
	 * @Id @Column(type="integer")
	 * @GeneratedValue(strategy="AUTO")
	 */
	public $id;

	/** @Column(type="string") */
	public $name;

	/** @Column(type="string") */
	public $email;

	/** @Column(type="text") */
	public $body;

	/** @Column(type="datetime") */
	public $created;

	/** @Column(type="datetime") */
	public $modified;

	/**
	 * @ManyToOne(targetEntity="Post")
	 * @JoinColumn(name="post_id", referencedColumnName="id")
	 */
	public $post;
}
?&gt;
</pre>
<p>Just as before, we have a property per field, plus a property named <code>$post</code> to hold the instance of the <code>Post</code> a <code>Comment</code> belongs to.</p>
<h2>The Controllers</h2>
<p>Let&#8217;s first create the basic controller for <code>Post</code> records, that will simply show the list of post titles, with links for operations that we don&#8217;t yet have (editing, adding, removing, etc.) Create a file named <code>PostsController.php</code> in your <code>app/controllers</code> folder with the following contents.</p>
<pre name="code" class="php">
&lt;?php
namespace app\controllers;

use app\models\Post;

class PostsController extends \lithium\action\Controller {
	public function index() {
		$posts = Post::find('all');
		$this-&gt;set(compact('posts'));
	}
}
?&gt;
</pre>
<p>That&#8217;s a very simple controller, isn&#8217;t it? We just get the posts, and pass it to the view as a <code>$posts</code> variable. So let&#8217;s create the view then. Create a folder named <code>posts</code> in your <code>app/views/</code> folder, and then create a file named <code>index.html.php</code> in the newly created <code>app/views/posts</code> folder with the following contents:</p>
<pre name="code" class="php">
&lt;h1&gt;Posts&lt;/h1&gt;
&lt;p&gt;&lt;?php echo $this-&gt;html-&gt;link('Add new Post', '/posts/edit'); ?&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;?php foreach($posts as $post) { ?&gt;
	&lt;li&gt;
		&lt;?php echo $this-&gt;html-&gt;link($post-&gt;title, '/posts/view/' . $post-&gt;id); ?&gt;
		[&lt;?php echo $this-&gt;html-&gt;link('Edit', '/posts/edit/' . $post-&gt;id); ?&gt;]
		[&lt;?php echo $this-&gt;html-&gt;link('Delete', '/posts/delete/' . $post-&gt;id, array('onClick'=&gt;'return confirm("Are you sure you want to delete this record");')); ?&gt;]
	&lt;/li&gt;
&lt;?php } ?&gt;
&lt;/ul&gt;
</pre>
<p>This code also speaks for itself. So let&#8217;s now create an action to view an actual post. Add a method named <code>view()</code> in your <code>PostsController</code> with the following code:</p>
<pre name="code" class="php">
public function view($id = null) {
	$id = $id ?: $this-&gt;request-&gt;id;
	$post = Post::find('first', array('conditions' =&gt; compact('id')));
	$this->set(compact('post'));
}
</pre>
<p>We are basically getting the ID first (since it&#8217;s a numeric ID, a convenient Lithium route check will set it as part of the request information), and then looking for a <code>Post</code> with the given ID. That post will also contain the comments attached to it, so knowing that we can create the view for this action. Create a file named <code>view.html.php</code> in your <code>app/views/posts</code> folder with the following contents:</p>
<pre name="code" class="php">
&lt;h1&gt;&lt;?php echo $post-&gt;title; ?&gt;&lt;/h1&gt;
&lt;p&gt;&lt;small&gt;&lt;?php echo $post-&gt;created-&gt;format('F d, Y H:i'); ?&gt;&lt;/small&gt;&lt;/p&gt;
&lt;?php echo $post-&gt;body; ?&gt;

&lt;?php if (!$post-&gt;comments-&gt;isEmpty()) { ?&gt;
	&lt;h3&gt;Comments&lt;/h3&gt;
	&lt;?php foreach($post-&gt;comments as $comment) { ?&gt;
			&lt;p&gt;&lt;small&gt;
			by &lt;a href="mailto:&lt;?php echo $comment-&gt;email; ?&gt;"&gt;&lt;?php echo $comment-&gt;name; ?&gt;&lt;/a&gt;
			on &lt;?php echo $comment-&gt;created-&gt;format('F d, Y H:i'); ?&gt;
			&lt;/small&gt;&lt;/p&gt;
			&lt;?php echo $comment-&gt;body; ?&gt;
	&lt;?php } ?&gt;
&lt;?php } ?&gt;

&lt;h3&gt;Add your Comment&lt;/h3&gt;
&lt;?php echo $this-&gt;form-&gt;create(null, array('url'=&gt;array('controller'=&gt;'comments', 'action'=&gt;'add'))); ?&gt;
	&lt;?php echo $this-&gt;form-&gt;hidden('post_id', array('value' =&gt; $post-&gt;id)); ?&gt;
	&lt;label&gt;Your email:&lt;/label&gt; &lt;?php echo $this-&gt;form-&gt;text('email', array('size'=&gt;60)); ?&gt;
	&lt;label&gt;Your name:&lt;/label&gt; &lt;?php echo $this-&gt;form-&gt;text('name', array('size'=&gt;60)); ?&gt;
	&lt;label&gt;Comment:&lt;/label&gt; &lt;?php echo $this-&gt;form-&gt;textarea('body', array('rows'=&gt;5, 'cols'=&gt;60)); ?&gt;
	&lt;?php echo $this-&gt;form-&gt;submit('Add Comment'); ?&gt;
&lt;?php echo $this-&gt;form-&gt;end(); ?&gt;
</pre>
<p>This code should also speak for itself, except probably the <code>isEmpty()</code> method, which is part of the collection&#8217;s methods Doctrine provides.</p>
<p>Another thing to note in the above view is the form at the bottom, used to add comments to a post. First thing we notice there is that we are setting as action for the post an action called <code>add</code> in a controller named <code>Comments</code>. We don&#8217;t yet have this controller, do we? So let&#8217;s add it.</p>
<p>Create a file named <code>CommentsController.php</code> in your <code>app/controllers</code> folder with the following contents:</p>
<pre name="code" class="php">
&lt;?php
namespace app\controllers;

use app\models\Post;
use app\models\Comment;

class CommentsController extends \lithium\action\Controller {
	public function add() {
		if (empty($this-&gt;request-&gt;data)) {
			$this-&gt;redirect('/');
		}

		$post = Post::find('first', array(
			'conditions'=&gt;array('Post.id' =&gt; $this-&gt;request-&gt;data['post_id'])
		));
		if (!$post) {
			throw new \Exception("No post found for ID {$this-&gt;request-&gt;data['post_id']}");
		}

		$comment = Comment::create($this-&gt;request-&gt;data);
		$comment-&gt;post = $post;
		$comment-&gt;save();

		$this-&gt;redirect('/posts/view/' . $post-&gt;id);
	}
}
?&gt;
</pre>
<p>This action looks more involved. First thing we do, is make sure someone is posting data to this action (that&#8217;s the check on <code>$this-&gt;request-&gt;data</code>, where the posted data comes in Lithium). If they don&#8217;t, we kick their butts. Next, we make sure there&#8217;s an actual post for the given ID. If there&#8217;s not, we throw an exception. The last part is the actual saving of the comment, so let&#8217;s go through that:</p>
<ul>
<li>With <code>$comment = Comment::create($this-&gt;request-&gt;data);</code> we create an instance of the <code>Comment</code> class, initially populating with the posted data (so the properties <code>$email</code>, <code>$name</code>, etc. will get populated with the posted data)</li>
<li>Next with <code>$comment-&gt;post = $post;</code> we specify to which <code>Post</code> the <code>Comment</code> belongs to.</li>
<li>Finally we persist (save) the <code>Comment</code> with <code>$comment-&gt;save();</code></li>
</ul>
<p>Since we redirect them no matter what, we need no view for this action. Also, you may have noticed that there&#8217;s no validation. That will be left as homework for you. After the comment is saved, we redirect the user back to the post page.</p>
<p>So we now know how to show the list of posts, how to view a post, and add a comment. All we have to do next is add the ability to add/edit posts, and remove them. Let&#8217;s start with the easy part, deleting a post. Add the following to your <code>PostsController</code>:</p>
<pre name="code" class="php">
public function delete($id = null) {
	$id = $id ?: $this-&gt;request-&gt;id;
	if (!isset($id)) {
		$this-&gt;redirect('/posts');
	}

	$post = Post::find('first', array('conditions' =&gt; compact('id')));
	if (!$post) {
		throw new \Exception("No post found for ID {$id}");
	}

	$post-&gt;delete();
	$this-&gt;redirect('/posts');
}
</pre>
<p>This code almost needs no explaining after what we&#8217;ve seen so far. The only important part there is the actual deletion of the <code>Post</code> by calling its <code>delete()</code> method. Also, this is another action that needs no view, since we are redirecting them after the operation.</p>
<p>Finally, and since we have seen how to add a comment, adding a post won&#8217;t be that different. In fact, we&#8217;ll deal with adding and editing posts in the same action.</p>
<p>Add the following action to your <code>PostsController</code>:</p>
<pre name="code" class="php">
public function edit($id = null) {
	$id = $id ?: $this-&gt;request-&gt;id;
	if (isset($id)) {
		$post = Post::find('first', array('conditions' =&gt; compact('id')));
		if (!$post) {
			throw new \Exception("No post found for ID {$id}");
		}
	} else {
		$post = Post::create();
	}

	if (!empty($this-&gt;request-&gt;data)) {
		$post-&gt;set($this-&gt;request-&gt;data)-&gt;save();
		if (!isset($id)) {
			$this-&gt;redirect('/posts');
		}
		$this-&gt;redirect('/posts/view/' . $post-&gt;id);
	}
	$this-&gt;set(compact('post'));
}
</pre>
<p>The important thing to notice here is that if there&#8217;s an ID provided, we try to get the <code>Post</code>. If there&#8217;s none, we create a new Post record instance by calling <code>Post::create();</code>. Then, once we got submitted data, we simply populate the properties with posted data and save the record, all in a single line: <code>$post-&gt;set($this-&gt;request-&gt;data)-&gt;save();</code>. Yeah, there&#8217;s no validation here either. More homework!</p>
<p>So what about the view for this action? Create a file named <code>edit.html.php</code> in your <code>app/views/posts</code> folder with the following contents:</p>
<pre name="code" class="php">
&lt;h3&gt;Edit Post&lt;/h3&gt;
&lt;?php echo $this-&gt;form-&gt;create($post, array('url'=&gt;'/posts/edit/' . $post-&gt;id, 'method'=&gt;'post')); ?&gt;
	&lt;label&gt;Title:&lt;/label&gt; &lt;?php echo $this-&gt;form-&gt;text('title', array('size'=&gt;60)); ?&gt;
	&lt;label&gt;Body:&lt;/label&gt; &lt;?php echo $this-&gt;form-&gt;textarea('body', array('rows'=&gt;5, 'cols'=&gt;60)); ?&gt;
	&lt;?php echo $this-&gt;form-&gt;submit('Add Comment'); ?&gt;
&lt;?php echo $this-&gt;form-&gt;end(); ?&gt;
</pre>
<p>This is yet another code that speaks for itself. How lazy am I?</code></p>
<h2>Conclusion</h2>
<p>We've had fun, didn't we? We now have a very basic blog system working, using PHP 5.3 only tools (<a href="http://li3.rad-dev.org/" rel="nofollow" >Lithium, the most rad framework</a>, and <a href="http://www.doctrine-project.org/documentation/manual/2_0/en" rel="nofollow" >Doctrine 2, a very interesting ORM</a>). We could easily extend this further, but we'll wait on the work the Lithium team is doing to offer a much better integration with Doctrine. In the meantime, use this knowledge to have some fun, and build some cool projects!</p>


<p>Related posts:<ol><li><a href='http://marianoiglesias.com.ar/cakephp/pagination-with-custom-find-types-in-cakephp/' rel='bookmark' title='Permanent Link: Pagination with custom find types in CakePHP'>Pagination with custom find types in CakePHP</a></li>
</ol></p>]]></content:encoded>
			<wfw:commentRss>http://marianoiglesias.com.ar/li3-lithium/building-a-blog-with-lithium-and-doctrine/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Page Caching using disk
Database Caching 18/33 queries in 0.054 seconds using disk

Served from: marianoiglesias.com.ar @ 2010-07-30 05:18:56 -->