A bookshop application
This is an example application of a bookshop.
Throughout this tutorial I will use Var_Dump::display rather then var_dump, this is just force of habit. PEAR's Var_Dump controller makes dumps look nice.
Please note that a lot of this document reflects how I (Ian, aka http://pookey.co.uk pookey) use propel. You may choose to put files in different places, not use Var_Dump from PEAR, use your mouse in your left hand - whatever you want to change, feel free!
This guide is not a replacement for the Documentation/1.2/UserGuide User Guide?. If you have not read the Documentation/1.2/UserGuide User Guide?, please do go and read it now.
General Setup
Here are things I keep reasonably common with my web applications.
I keep 3 directories
htdocs/ lib/ propel/
The propel directory is a symlink into the propel projects directry
bookshop # ln -s /usr/lib/php/data/propel-generator/projects/bookshop/ ./propel
Inside lib, I do a symlink to the build data files
lib # ln -s ../propel/build/classes/bookshop/ ./Data
This is my init script, this does not go in the htdocs folder, but in the parent folder
<?php define('SITE_ROOT', dirname(__FILE__) . '/'); define('LIB_ROOT', SITE_ROOT . 'lib/'); // my use of PEAR_ROOT might seem odd to some people, remove it if you don't like it :) // I like to make things clear, if it's coming from PEAR I like to know that simply by // looking at the include line define('PEAR_ROOT', ''); require_once PEAR_ROOT . 'propel/Propel.php'; require_once PEAR_ROOT . 'Var_Dump.php'; Var_Dump::displayInit( array( 'display_mode' => 'HTML4_Table' ), array( 'show_caption' => FALSE, 'bordercolor' => '#DDDDDD', 'bordersize' => '2', 'captioncolor' => 'white', 'cellpadding' => '4', 'cellspacing' => '0', 'color1' => '#FFFFFF', 'color2' => '#F4F4F4', 'before_num_key' => '<font color="#CC5450"><b>', 'after_num_key' => '</b></font>', 'before_str_key' => '<font color="#5450CC">', 'after_str_key' => '</font>', 'before_value' => '<i>', 'after_value' => '</i>' ) ); // ------------------------------------- // all the init code... // change the include path for propel set_include_path( SITE_ROOT . 'propel/build/classes/' . PATH_SEPARATOR . get_include_path() ); Propel::init(SITE_ROOT . 'propel/build/conf/bookshop-conf.php');
Setting up Propel
In your propel directroy, you'll need a schema.xml. For more information on the following 3 files, please look at the Documentation/1.2/UserGuide User Guide?. For this example, here is the schema I'm using:
<?xml version="1.0" encoding="utf-8"?> <database name="bookshop"> <table name="author" idMethod="native"> <column name="author_id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" /> <column name="name" type="VARCHAR" size="255" required="true" /> </table> <table name="publisher" idMethod="native"> <column name="publisher_id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" /> <column name="name" type="VARCHAR" size="255" required="true" /> </table> <table name="book" idMethod="native"> <column name="book_id" type="INTEGER" required="true" autoIncrement="true" primaryKey="true" /> <column name="title" type="VARCHAR" size="255" required="true" /> <column name="author_id" type="INTEGER" required="true" /> <column name="publisher_id" type="INTEGER" required="true" /> <foreign-key foreignTable="author"> <reference local="author_id" foreign="author_id" /> </foreign-key> <foreign-key foreignTable="publisher"> <reference local="publisher_id" foreign="publisher_id" /> </foreign-key> </table> </database>
build.properties
propel.project = bookshop propel.database = mysql propel.database.url = mysql://user:password@localhost/bookshop propel.targetPackage = bookshop
runtime-conf.xml
<?xml version="1.0" encoding="ISO-8859-1"?> <config> <log> <ident>propel-bookshop</ident> <level>7</level> </log> <propel> <datasources default="bookshop"> <datasource id="bookshop"> <adapter>mysql</adapter> <connection> <phptype>mysql</phptype> <hostspec>localhost</hostspec> <database>bookshop</database> <username>user</username> <password>password</password> </connection> </datasource> </datasources> </propel> </config>
Generating the Code
In the propel-generator directory, use phing to generate your classes
# phing -Dproject=bookshop
This will create the following files
# ls projects/bookshop/* -R projects/bookshop/build.properties projects/bookshop/runtime-conf.xml projects/bookshop/schema.xml projects/bookshop/build: classes conf sql projects/bookshop/build/classes: bookshop projects/bookshop/build/classes/bookshop: Author.php AuthorPeer.php Book.php BookPeer.php map om projects/bookshop/build/classes/bookshop/map: AuthorMapBuilder.php BookMapBuilder.php projects/bookshop/build/classes/bookshop/om: BaseAuthor.php BaseAuthorPeer.php BaseBook.php BaseBookPeer.php projects/bookshop/build/conf: bookshop-conf.php projects/bookshop/build/sql: schema.sql sqldb.map
Prepairing the database
Create the datbase in mysql
mysql> create database bookshop; Query OK, 1 row affected (0.05 sec)
Get propel to create the tables in the database
# phing -Dproject=bookshop insert-sql
Creating the documentation
It's almost essential to build documentation at this point for your propel classes. For information on how to do this, have a look Development/Guidelines/BuildingAPIDocs here?.
http://bookshop.dev.pengus.net/docs click here for the API docs for this example.
Putting it all together
All of this allows me to include propel's generated classes into my project by doing this:
<?php // this is htdocs/index.php require_once '../init.php'; require_once LIB_ROOT . 'Data/Book.php'; $book = new Book(); Var_Dump::display($book); ?>
This should show something like this:
| book_id:protected | NULL |
| title:protected | NULL |
| author_id:protected | NULL |
| aAuthor:protected | NULL |
| alreadyInSave:private | bool false |
| alreadyInValidation:protected | bool false |
| _new:private | bool true |
| _deleted:private | bool false |
| modifiedColumns:protected | array(0) |
| _log:private | NULL |
Example Code
Creating a set of Publishers
Ths code snippet shows looping though an array and creating a new publisher for each one. Ater inserting each one it will print out the PublisherId that was gererated by the database on insertion of the data.
$publishers = array(
'Acme Publisher',
'Penguin Books',
'Wibble Press',
);
echo "creating publishers...\n";
foreach($publishers as $publisherName) {
echo "\t - creating publisher: '{$publisherName}'... ";
$publisher = new Publisher();
$publisher->setName($publisherName);
$publisher->save();
echo "done! (id: {$publisher->getPublisherId()})\n";
}
echo "done!\n";
Creating an Author
This will insert a new author, and then print out the autonumber id generated by the database.
$author = new Author();
$author->setName('Ian P. Christian');
$author->save();
echo $author->getAuthorId();
Creating a Book
This will create a book, and link it to an author.
echo "creating a book: 'Propel Example Bookshop Book!'...\n";
$book = new Book();
$book->setTitle('Propel Example Bookshop Book!');
echo "\t - Linking book to author... ";
$book->setAuthor($author);
echo "done\n";
echo "\t - Linking book to publisher... ";
$book->setPublisher($publisher);
echo "done\n";
echo "\t - saving book...";
$book->save();
echo "done\n";
Get a book joined to author
This shows how to get a specific book, and print it's author's name.
$c = new Criteria();
$c->add(BookPeer::BOOK_ID, $bookId);
// the following function returns an array of objects, but
// we know we only want the first one, so list() is used.
list($book) = BookPeer::doSelectJoinAuthor($c);
echo $book->getTitle() . ' was written by ' . $book->getAuthor()->getName() . "\n";
Alternativly, if you don't mind 2 queries, you could do this:
$book = BookPeer::retrieveByPK($bookId);
echo $book->getTitle() . ' was written by ' . $book->getAuthor()->getName() . "\n";
The difference between these two is that propel does a join in the first example, so saves on a databasea lookup. The second looks far simpler, but should be avoided, especially if you are looping though books, as each loop itteration would result in an extra query.
Using a custom SQL query
This will affectivly achive the same as the above two functions, returning an array of books and the author who wrote it.
$sql = "SELECT " . BookPeer::TABLE_NAME . ".*, " . AuthorPeer::TABLE_NAME . ".* FROM " . AuthorPeer::TABLE_NAME . " JOIN " . BookPeer::TABLE_NAME .
" ON " . AuthorPeer::AUTHOR_ID . " = " . BookPeer::AUTHOR_ID;
$con = Propel::getConnection(AuthorPeer::DATABASE_NAME);
$stmt = $con->createStatement();
$rs = $stmt->executeQuery($sql, ResultSet::FETCHMODE_NUM);
$results = array();
$book_start_col = 1;
$author_start_col = (BookPeer::$numColumns - BookPeer::$numLazyLoadColumns) + 1;
while ($rs->next()) {
$book = new Book();
$book->hydrate($rs, $book_start_col);
$author = new Author();
$author->hydrate($rs,$author_start_col);
$book->setAuthor($author);
$results[] = $book;
}
foreach($results as $book) {
echo $book->getTitle() . ' was written by ' . $book->getAuthor()->getName() . "\n";
}