Getting Started

In this chapter, we're going to walkthrough all the steps necessary to set up and use the provided "bookstore" application. We'll start with an overview of the directory layout, so that you know where you can expect to find files.

Structure of propel/generator

Before we begin with our "build a bookstore" example, you should take a moment to familiarize yourself with the Propel generator directory structure. To get started, you should note the projects/bookstore/ directory, as you will want to customize files in this directory. Also, after you've completed the build, there will be a projects/bookstore/build/ directory which will contain generated files that you'll need.

propel/generator  
  |-- classes
  |    +-- propel
  |         |-- engine
  |         |    |-- database
  |         |    |    |-- model
  |         |    |    +-- transform
  |         |    +-- sql
  |         +-- phing    
  |-- dtd
  |-- projects
  |    +-- bookstore
  |-- templates
  +-- test
       |-- classes
       |    +-- propel
       +-- etc

The following table briefly describes the contents of the major directories:

Propel source tree directories
Directory Contents

classes

Repository of all the classes used by Propel. These include the Creole classes, which provide a lightweight unified DB API.

dtd

This holds a single DTD file for validating database XML schema files.

projects

This is a repository for all project files (one directory per project). Propel reads schema & config files from this directory and creates all output in this directory.

templates

These are PHP templates (Capsule) that create the PHP classes and SQL dump files based on the datamodel.

test

PHPUnit2 testcases and the bookstore-test.php script which you can run after completing this installation excercise.

Describing Your Database in XML

In order to create classes that accurately represent your tables -- and the relationships between tables in your database -- you need to provide an XML representation of your database. We will walk through the projects/bookstore/schema.xml file, which you will find in your Propel distribution.

Note: if you would like to use Propel with an already existing database, you can have Propel use the Creole metadata classes to construct the XML file for you. You will still - most likely - need to tweak the XML that Propel produces (e.g. to specify autoincrement for columns, or customize the PHP names for columns, add foreign key relationships for unsupporting DBs, etc.). See AppendixA - Propel Operations to find out more.

Here's a part of the provided schema.xml file. As you can see, Propel datamodel definitions are very closely tied to the actual structure of the database. (We're contantly adding things, so the version in your code may look a little different.)

<?xml version="1.0" encoding="ISO-8859-1" standalone="no"?>
<database name="bookstore" defaultIdMethod="native">
 <table name="book" description="Book Table">
  <column
    name="book_id"
    required="true"
    primaryKey="true"
    type="INTEGER"
    description="Book Id"/>
  <column
    name="title"
    required="true"
    type="VARCHAR"
    size="255"
    description="Book Title"/>
  <column
    name="isbn"
    required="true"
    type="VARCHAR"
    size="24"
    phpName="ISBN"
    description="ISBN Number"/>
  <column
    name="publisher_id"
    required="true"
    type="INTEGER"
    description="Foreign Key Publisher"/>
  <column
    name="author_id"
    required="true"
    type="INTEGER"
    description="Foreign Key Author"/>
  <foreign-key foreignTable="publisher">
   <reference
     local="publisher_id"
     foreign="publisher_id"/>
  </foreign-key>
  <foreign-key foreignTable="author">
   <reference
     local="author_id"
     foreign="author_id"/>
  </foreign-key>
 </table>

 <table name="publisher" description="Publisher Table">
  <column
    name="publisher_id"
    required="true"
    primaryKey="true"
    type="INTEGER"
    description="Publisher Id"/>
  <column
    name="name"
    required="true"
    type="VARCHAR"
    size="128"
    description="Publisher Name"/>
 </table>

 <table name="author" description="Author Table">
  <column
    name="author_id"
    required="true"
    primaryKey="true"
    type="INTEGER"
    description="Author Id"/>
  <column
    name="first_name"
    required="true"
    type="VARCHAR"
    size="128"
    description="First Name"/>
  <column
    name="last_name"
    required="true"
    type="VARCHAR"
    size="128"
    description="Last Name"/>
 </table>
</database>

The XML above should be fairly self-explanatory, but we'll discuss some of the attributes in a little more detail. For a full reference of allowed attributes see AppendixB - Schema Reference.

<table idMethod="" ... >, <database defaultIdMethod="" ... >

The <table> element supports the idMethod attribute; if none is provided then the defaultIdMethod attribute of the <database> element is checked. The valid idMethod values are "native" or "none". Setting this value to "native" implies that the databases native method of generating IDs will be used -- e.g. autoincrement for MySQL, sequences for PostgreSQL.

Note: in Torque, there is also an "id-broker" option which uses a database table to emulate sequences (akin to PEAR::DB or MDB sequence emulation). At this point Propel does not support the id-broker method, as all supported databases have built-in support for either autoincrement or sequence columns (at much less performance cost than emulation).

<column type="">

The type attribute in the <column> tag is used to specify a Propel metatype. These metatypes look like SQL types, but they do not correspond directly. These types are generic types that are converted by Propel to the best match for your RDBMS. For example, the "TIMESTAMP" Propel type would be rendered as "DATETIME" for MySQL (because MySQL's native TIMESTAMP type is idiosyncratic and a pain to read and parse). See the Propel Column Types chapter for more information.

<column phpName="" ... />

When creating classes, Propel automatically "PEAR-ifies" the names of the columns when creating the object model classes. The default naming scheme converts "underscore" column names to PEAR standard class method names, such that book.my_column_name creates methods Book::getMyColumnName(), Book::setMyColumnName(), etc.

You can override the converted name behavior by specifying your own name using the phpName attribute.

Setting Build Properties

There's an important distinction to be made between build properties and runtime properties. The build properties contain information about your project that propel needs in order to build your classes and SQL. Not all properties are required: for example, Propel doesn't need to know how to connect to your database unless you want to use Propel to create the database or export data from an existing database.

Propel will look for the build properties in build.properties and default.properties in the base Propel directory and in the directory of the current project being built. For those using Propel from CVS: You will need to rename the build.properties-sample that is provided to build.properties. You may wish to customize the contents, but it is recommended that project-specific changes are made in the build.properties file in the project directory (e.g. projects/bookstore/build.properties). Here's a sample project build.properties file:

# The name of the project 
propel.project = bookstore
 
# The database driver 
propel.database = mssql

# The connection parameters (optional)
propel.database.url = mssql://localhost/bookstore

Setting Runtime Properties

Runtime properties may often be the same as your build db connection properties, but they are not assumed to be. This means that you will need to edit the runtime-conf.xml file to specify your settings. Note that for runtime properties you must specify database connection information. Also note that currently you must specify the connection information in a way that conforms to the PEAR-style DSN array, rather than the DSN URL used for build.properties connection parameters.

<?xml version="1.0" encoding="ISO-8859-1"?>

<config>
 <log>
  <ident>propel-bookstore</ident>
<level>7</level> </log> <propel> <datasources default="bookstore"> <datasource id="bookstore"> <!-- the Propel adapter (usually same as phptype of connection DSN) --> <adapter>sqlite</adapter> <connection> <phptype>sqlite</phptype> <hostspec>localhost</hostspec> <database>./test/bookstore.db</database> <username></username> <password></password> </connection> </datasource> </datasources> </propel> </config>

During the build process this file will be converted into a PHP array in projects/bookstore/build/conf/runtime-conf.php , so that no runtime parsing is necessary. Note that currently the generated array does not match the format of the XML file. This is for backwards compatibility with the old .properties file format.

<?php
return array (
  'propel' => 
  array (
    'database' => 
    array (
      'default' => 'bookstore',
      'bookstore' => 
      array (
        'adapter' => 'mssql',
      ),
    ),
    'dsfactory' => 
    array (
      'bookstore' => 
      array (
        'connection' => 
        array (
          'phptype' => 'mssql',
          'database' => 'bookstore',
          'hostspec' => 'localhost',
          'username' => 'sa',
          'password' => '',
        ),
      ),
    ),
  ),
  'log' => 
  array (
    'name' => '/var/log/propel/propel.log',
    'ident' => 'propel-bookstore',
  ),
);

The Propel classes can now read properties from this file with minimal overhead. You will see later that you pass the path to this file to Propel::init() to get the whole thing started.

Building

At this point Propel has already been installed, all configuration options have been set and you are ready to build. Building requires that you have already installed and configured Phing.

To build your classes and SQL, simply run Phing using the build-propel.xml file:

$> cd /usr/local/propel-generator
$> phing -Dproject=bookstore

The procedure on Windows is exactly the same:

C:\> cd C:\PHP\apps\propel-generator
C:\PHP\apps\propel-generator> phing -Dproject=bookstore

The build.xml file provides a convenient wrapper for the build-propel.xml workhorse script, and also allows you to use per-project build.properties files. If you prefer to call the build-propel.xml script directly, you must make sure that all your project properties are specified in the top-level build.properties file.

This will build the SQL files and the object & peer classes based on any XML schemas in the projects/bookstore/ directory -- in this case, there is just one schema.xml file.

The resulting files will be placed in the projects/bookstore/build directory.

Your output should look something like this (with your paths, of course, and maybe with a faster build time than my slow laptop):

C:\sandbox\propel-generator>phing -Dproject=bookstore
Buildfile: C:\sandbox\propel-generator\build.xml

propel-project-builder > projectcheck:

propel-project-builder > main:
    [phing] Calling Buildfile 'C:\sandbox\propel-generator\build-propel.xml' wit
h target 'main'

propel > main:
[phingcall] Calling Buildfile 'C:\sandbox\propel-generator\build-propel.xml' wit
h target 'sql'

propel > check-run-only-on-schema-change:

propel > sql-check:

propel > sql:
     [echo] +------------------------------------------+
     [echo] |                                          |
     [echo] | Generating SQL for YOUR Propel project!  |
     [echo] |                                          |
     [echo] +------------------------------------------+
[phingcall] Calling Buildfile 'C:\sandbox\propel-generator\build-propel.xml' wit
h target 'sql-template'

propel > sql-template:
[propel-sql] Processing: schema.xml
[propel-sql] Target database type: sqlite
[propel-sql] Target package:
[propel-sql] Using template path: C:\sandbox\propel-generator\templates
[propel-sql] Output directory: C:\sandbox\propel-generator\projects\bookstore\bu
ild\sql
[propel-sql] Generating SQL tables for database: bookstore
[propel-sql] Writing to SQL file: C:\sandbox\propel-generator\projects\bookstore
\build\sql\schema.sql
[propel-sql]     + book
[propel-sql]     + publisher
[propel-sql]     + author
[propel-sql]     + review
[propel-sql]     + media
[phingcall] Calling Buildfile 'C:\sandbox\propel-generator\build-propel.xml' wit
h target 'om'

propel > check-run-only-on-schema-change:

propel > om-check:

propel > om:
     [echo] +------------------------------------------+
     [echo] |                                          |
     [echo] | Generating Peer-based Object Model for   |
     [echo] | YOUR Propel project!                     |
     [echo] |                                          |
     [echo] +------------------------------------------+
[phingcall] Calling Buildfile 'C:\sandbox\propel-generator\build-propel.xml' wit
h target 'om-template'

propel > om-template:
[propel-om] Target database type: sqlite
[propel-om] Target package: bookstore
[propel-om] Using template path: C:\sandbox\propel-generator\templates
[propel-om] Output directory: C:\sandbox\propel-generator\projects\bookstore\bui
ld\classes
[propel-om] Processing: schema.xml
[propel-om] Processing Datamodel : schema.xml
[propel-om]   - processing database : bookstore
[propel-om]     + book
[propel-om]             -> BaseBookPeer
[propel-om]             -> BaseBook
[propel-om]             -> BookMapBuilder
[propel-om]             -> BookPeer
[propel-om]             -> Book
[propel-om]     + publisher
[propel-om]             -> BasePublisherPeer
[propel-om]             -> BasePublisher
[propel-om]             -> PublisherMapBuilder
[propel-om]             -> PublisherPeer
[propel-om]             -> Publisher
[propel-om]     + author
[propel-om]             -> BaseAuthorPeer
[propel-om]             -> BaseAuthor
[propel-om]             -> AuthorMapBuilder
[propel-om]             -> AuthorPeer
[propel-om]             -> Author
[propel-om]     + review
[propel-om]             -> BaseReviewPeer
[propel-om]             -> BaseReview
[propel-om]             -> ReviewMapBuilder
[propel-om]             -> ReviewPeer
[propel-om]             -> Review
[propel-om]     + media
[propel-om]             -> BaseMediaPeer
[propel-om]             -> BaseMedia
[propel-om]             -> MediaMapBuilder
[propel-om]             -> MediaPeer
[propel-om]             -> Media
[phingcall] Calling Buildfile 'C:\sandbox\propel-generator\build-propel.xml' wit
h target 'convert-props'

propel > convert-props:
     [echo] +------------------------------------------+
     [echo] |                                          |
     [echo] | Converting project properties file to an |
     [echo] | array dump for run-time performance.     |
     [echo] |                                          |
     [echo] +------------------------------------------+
  [capsule] Using templatePath: C:\sandbox\propel-generator\templates
  [capsule] Generating to file C:\sandbox\propel-generator\projects\bookstore\bu
ild\conf\bookstore-conf.php
  [capsule] Parsing control template: conf/Control.tpl

BUILD FINISHED

Total time: 5.9279 seconds

Note: if you encounter any problems, try adding -verbose or -debug options to get more output from the Phing build process.

Using the SQL Files

The SQL definition file that we just created can be found at projects/bookstore/build/schema.sql. This file contains the SQL to create tables (and other objects) in an already-created database. You can also use the "create-db" task to create the database, but be advised that some drivers, e.g. MS SQL Server, don't support this.

For example, to create and populate database using MySQL:

% mysqladmin -u root create bookstore
% mysql -u root bookstore < projects/bookstore/build/sql/schema.sql

Or, have Propel/Phing do it for you:

% phing -Dproject=bookstore -Dtarget=create-db
% phing -Dproject=bookstore -Dtarget=insert-sql

Using the Object Model

Now for the interesting part -- how to use these classes in your PHP application. The classes have been created in the build/classes/bookstore directory; you should either add the build/classes directory to your path or move the entire build/classes/bookstore directory to a location on your path.

For demonstration purposes, let's assume that you're using a Unix system and have /var/www/bookstore_app/classes on your PHP include_path:

$> cd /usr/local/propel-generator
$> mv projects/bookstore/build/classes/bookstore /var/www/bookstore_app/classes

On Windows, of course, you could use that mouse thingy to click and drag the bookstore folder ... :)

Now you also want to move the "compiled" properties file to some location in your application:

$> mv projects/bookstore/conf/runtime-conf.php /var/www/bookstore_app/conf/

Windows: click .... drag ... drop.

Now, initialize Propel and include the classes you need -- and get on with worrying about more important things:

<?php

// Initialize Propel using path to the converted
// property file that was created with the convert-props phing target
require_once 'propel/Propel.php';
Propel::init('/var/www/bookstore_app/conf/runtime-conf.php');

// these are in the build/classes subdir of your project, so 
// that needs to be on your include_path
include_once 'bookstore/Author.php';
include_once 'bookstore/Publisher.php';
include_once 'bookstore/Book.php';

$author = new Author();
$author->setFirstName("Leo");
$author->setLastName("Tolstoy");

$pub = new Publisher();
$pub->setName("Viking Press");

$book = new Book();
$book->setTitle("War & Peace");
$book->setIsbn("0140444173");
$book->setPublisher($pub);
$book->setAuthor($author);

// save (insert, in this case) the new object
$book->save(); 

// $book->save() will automatically trigger $author->save()
// and $publisher->save() since those objects don't yet exist in the db.

// -------------------------------------

// Now FIND that book!

// "peer" class is static class that handles things like queries
$c = new Criteria();
$c->add(BookPeer::TITLE, "War%", Criteria::LIKE);
$c->setLimit(10); // just in case we keep running this script :)

$books = BookPeer::doSelect($c);

if ($books) {
  print "<p><strong>Found books!</strong></p>";


  foreach($books as $book) {
    print "<br/>" . $book->getTitle() . ", by " . 
          $book->getAuthor()->getFirstName();
} } else { print "<p><strong>Did NOT find books!</strong></p>"; } ?>

Hopefully at this point you understand the basics of how to build and use Propel classes. The next chapters will look at more complicated -- and probably more common -- scenarios and how Propel can make even very complicated things look pretty simple.