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.
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:
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. |
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.
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).
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.
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.
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
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.
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.
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
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.