diff --git a/_includes/footer.html b/_includes/footer.html deleted file mode 100644 index ea9234f0..00000000 --- a/_includes/footer.html +++ /dev/null @@ -1,21 +0,0 @@ -
-

-

-

-
diff --git a/_includes/navbar.html b/_includes/navbar.html deleted file mode 100644 index b4abc1cd..00000000 --- a/_includes/navbar.html +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/_layouts/blog.html b/_layouts/blog.html deleted file mode 100644 index bd5a3d25..00000000 --- a/_layouts/blog.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - {% if page.title %}{{ page.title }}{% else %}Blog{% endif %} - Propel - - - - - - - - - - -
-
- - {% include navbar.html %} - -
- {% if page.title %} -

Blog > {{ page.title }}

- {% else %} -

Blog

- {% endif %} - -
- {{ content }} -
- -

- Found a typo ? Something is wrong in this documentation ? Just fork and edit it ! -

-
-
- {% include footer.html %} -
- - - diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index dfdb528f..00000000 --- a/_layouts/default.html +++ /dev/null @@ -1,7 +0,0 @@ ---- -layout: base ---- - -
- {{ content }} -
diff --git a/_layouts/documentation.html b/_layouts/documentation.html deleted file mode 100644 index 3f5ace3a..00000000 --- a/_layouts/documentation.html +++ /dev/null @@ -1,16 +0,0 @@ ---- -layout: base ---- - - -
- {{ content }} -
- - - diff --git a/_layouts/post.html b/_layouts/post.html deleted file mode 100644 index 842ec375..00000000 --- a/_layouts/post.html +++ /dev/null @@ -1,14 +0,0 @@ ---- -layout: blog ---- - -

- {% if page.author %} - {{ page.author }} - {% else %} - The Propel Team - {% endif %} - – {{ page.date | date_to_long_string }} -

- -{{ content }} diff --git a/_layouts/search.html b/_layouts/search.html deleted file mode 100644 index 9d12817d..00000000 --- a/_layouts/search.html +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: base ---- - -
-
- {{ content }} -
- -
-
- -
-
-
- - - diff --git a/behaviors/aggregate-column.markdown b/behaviors/aggregate-column.markdown deleted file mode 100644 index 607889db..00000000 --- a/behaviors/aggregate-column.markdown +++ /dev/null @@ -1,161 +0,0 @@ ---- -layout: documentation -title: Aggregate Column Behavior ---- - -# Aggregate Column Behavior # - -The `aggregate_column` behavior keeps a column updated using an aggregate function executed on a related table. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `aggregate_column` behavior to a table. You must provide parameters for the aggregate column `name`, the foreign table name, and the aggegate `expression`. For instance, to add an aggregate column keeping the comment count in a `post` table: - -```xml - - - - - - - - -
- - - - - - -
-``` - -Rebuild your model, and insert the table creation sql again. The model now has an additional `nb_comments` column, of type `integer` by default. And each time an record from the foreign table is added, modified, or removed, the aggregate column is updated: - -```php -setTitle('How Is Life On Earth?'); -$post->save(); -echo $post->getNbComments(); // 0 -$comment1 = new Comment(); -$comment1->setPost($post); -$comment1->save(); -echo $post->getNbComments(); // 1 -$comment2 = new Comment(); -$comment2->setPost($post); -$comment2->save(); -echo $post->getNbComments(); // 2 -$comment2->delete(); -echo $post->getNbComments(); // 1 -``` - -The aggregate column is also kept up to date when related records get modified through a Query object: - -```php -filterByPost($post) - ->delete(): -echo $post->getNbComments(); // 0 -``` - -## Customizing The Aggregate Calculation ## - -Any aggregate function can be used on any of the foreign columns. For instance, you can use the `aggregate_column` behavior to keep the latest update date of the related comments, or the total votes on the comments. You can even keep several aggregate columns in a single table: - -```xml - - - - - - - - - - - - - - - - - - -
- - - - - - - - -
-``` - -The behavior adds a `computeXXX()` method to the `Post` class to compute the value of the aggregate function. This method, called each time records are modified in the related `comment` table, is the translation of the behavior settings into a SQL query: - -```php -prepare('SELECT COUNT(id) FROM `comment` WHERE comment.POST_ID = :p1'); - $stmt->bindValue(':p1', $this->getId()); - $stmt->execute(); - return $stmt->fetchColumn(); -} -``` - -You can override this method in the model class to customize the aggregate column calculation. - -## Additional Condition ## - -What if you use your own soft deletion and want to calculate only comments which are not marked as deleted? -It is possible to add a custom SQL condition: - -```xml - - - - - - - - - -
-``` - -Which will result in generated SQL query: - -```php -prepare('SELECT COUNT(id) FROM `comment` WHERE is_deleted = false AND comment.POST_ID = :p1'); - $stmt->bindValue(':p1', $this->getId()); - $stmt->execute(); - return $stmt->fetchColumn(); -} -``` - -## Customizing The Aggregate Column ## - -By default, the behavior adds one columns to the model. If this column is already described in the schema, the behavior detects it and doesn't add it a second time. This can be useful if you need to use a custom `type` or `phpName` for the aggregate column: - -```xml - - - - - - - - - -
-``` diff --git a/behaviors/alternative-coding-standards.markdown b/behaviors/alternative-coding-standards.markdown deleted file mode 100644 index 81f130e7..00000000 --- a/behaviors/alternative-coding-standards.markdown +++ /dev/null @@ -1,91 +0,0 @@ ---- -layout: documentation -title: Alternative Coding Standards Behavior ---- - -# Alternative Coding Standards Behavior # - -The `alternative_coding_standards` behavior changes the coding standards of the model classes generated by Propel to match your own coding style. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `alternative_coding_standards` behavior to a table: -```xml - - - - -
-``` - -Rebuild your model, and you're ready to go. The code of the model classes now uses an alternative set of coding standards: - -```php -title !== $v) - { - $this->title = $v; - $this->modifiedColumns[] = BookPeer::TITLE; - } - - return $this; - } - -// instead of - - /** - * Set the value of [title] column. - * - * @param string $v new value - * @return Table4 The current object (for fluent API support) - */ - public function setTitle($v) - { - if ($v !== null) { - $v = (string) $v; - } - - if ($this->title !== $v) { - $this->title = $v; - $this->modifiedColumns[] = BookPeer::TITLE; - } - - return $this; - } // setTitle() -``` - -The behavior replaces tabulations by whitespace (2 spaces by default), places opening brackets on newlines, removes closing brackets comments, and can even strip every comments in the generated classes if you wish. - -## Parameters ## - -Each of the new coding style rules has corresponding parameter in the behavior description. Here is the default configuration: - -```xml - - - - - - - - - - -
-``` - -You can change these settings to better match your own coding styles. diff --git a/behaviors/archivable.markdown b/behaviors/archivable.markdown deleted file mode 100644 index c869664c..00000000 --- a/behaviors/archivable.markdown +++ /dev/null @@ -1,249 +0,0 @@ ---- -layout: documentation -title: Archivable Behavior ---- - -# Archivable Behavior # - -The `archivable` behavior gives model objects the ability to be copied to an archive table. By default, the behavior archives objects on deletion, acting as a replacement of the [`soft_delete`](./soft-delete) behavior, which is deprecated. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `archivable` behavior to a table: - -```xml - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has one new table, `book_archive`, with the same columns as the original `book` table. This table stores the archived `books` together with their archive date. To archive an object, call the `archive()` method: - -```php -setTitle('War And Peace'); -$book->save(); -// copy the current Book to a BookArchive object and save it -$archivedBook = $book->archive(); -``` - -The archive table contains only the freshest copy of each archived objects. Archiving an object twice doesn't create a new record in the archive table, but updates the existing archive. - -The `book_archive` table has generated ActiveRecord and ActiveQuery classes, so you can browse the archive at will. The archived objects have the same primary key as the original objects. In addition, they contain an `ArchivedAt` property storing the date where the object was archived. - -```php -findPk($book->getId()); -echo $archivedBook->getTitle(); // 'War And Peace' -echo $archivedBook->getArchivedAt(); // 2011-08-23 18:14:23 -``` - -The ActiveRecord class of an `archivable` model has more methods to deal with the archive: - -```php -// restore an object to the state it had when last archived -$book->restoreFromArchive(); -// find the archived version of an existing book -$archivedBook = $book->getArchive(); -// populate a book based on an archive -$book = new book(); -$book->populateFromArchive($archivedBook); -``` - -By default, an `archivable` model is archived just before deletion: - -```php -setTitle('Sense and Sensibility'); -$book->save(); -// delete and archive the book -$book->delete(); -echo BookQuery::create()->count(); // 0 -// find the archived book -$archivedBook = BookArchiveQuery::create() - ->findOneByTitle('Sense and Sensibility'); -``` - ->**Tip**
The behavior does not take care of archiving the related objects. This may be surprising on deletions if the deleted object has 'ON DELETE CASCADE' foreign keys. If you want to archive relations, override the generated `archive()` method in the ActiveRecord class with your custom logic. - -To recover deleted objects, use `populateFromArchive()` on a new object and save it: - -```php -populateFromArchive($archivedBook); -$book->save(); -echo $book->getTitle(); // 'Sense and Sensibility' -``` - -If you want to delete an `archivable` object without archiving it, use the `deleteWithoutArchive()` method generated by the behavior: - -```php -deleteWithoutArchive(); -``` - -## Archiving A Set Of Objects ## - -The `archivable` behavior also generates an `archive()` method on the generated ActiveQuery class. That means you can easily archive a set of objects, in the same way you archive a single object: - -```php -filterByTitle('War%') - ->archive(); -``` - -`archive()` returns the number of archived objects, and not the current ActiveQuery object, so it's a termination method. - ->**Tip**
Since the `archive()` method doesn't duplicate archived objects, it must iterate over the results of the query to check whether each object has already been archived. In practice, `archive()` issues 2n+1 database queries, where `n` is the number of results of the query as returned by a `count()`. - -As explained earlier, an `archivable` model is archived just before deletion by default. This is also true when using the `delete()` and `deleteAll()` methods of the ActiveQuery class: - -```php -filterByTitle('War%') - ->delete(); - -// use deleteWithoutArchive() if you just want to delete -$nbDeletedObjects = BookQuery::create() - ->filterByTitle('War%') - ->deleteWithoutArchive(); - -// you can also turn off the query alteration on the current query -// by calling setArchiveOnDelete(false) before deleting -$nbDeletedObjects = BookQuery::create() - ->filterByTitle('War%') - ->setArchiveOnDelete(false) - ->delete(); -``` - -## Archiving on Insert, Update, or Delete ## - -As explained earlier, the `archivable` behavior archives objects on deletion by default, but insertions and updates don't trigger the `archive()` method. You can disable the auto archiving on deletion, as well as enable it for insertion and update, in the behavior `` tags. Here is the default configuration: - -```xml - - - - - - - - -
-``` - -If you turn on `archive_on_insert`, a call to `save()` on a new ActiveRecord object archives it - unless you call `saveWithoutArchive()`. - -If you turn on `archive_on_update`, a call to `save()` on an existing ActiveRecord object archives it, and a call to `update()` on an ActiveQuery object archives the results as well. You can still use `saveWithoutArchive()` on the ActiveRecord class and `updateWithoutArchive()` on the ActiveQuery class to skip archiving on updates. - -Of course, even if `archive_on_insert` or any of the similar parameters isn't turned on, you can always archive manually an object after persisting it by simply calling `archive()`: - -```php -save(); -$book->archive(); -``` - -## Archiving To Another Database ## - -The behavior can use another database connection for the archive table, to make it safer. To allow cross-database archives, you must declare the archive schema manually in another XML schema, and reference the archive class on in the behavior parameter: - -```xml - - - - - - - -
-
- - - - - -
-
-``` - -The archive table must have the same columns as the archivable table, but without autoIncrements, and without foreign keys. - -With this setup, the behavior uses `MyBookArchive` and `MyBookArchiveQuery` for all operations on archives, and therefore uses the `backup` connection. - -## Migrating From `soft_delete` ## - -If you use `archivable` as a replacement for the `soft_delete` behavior, here is how you should update your code: - -```php -delete(); // with soft_delete -$book->delete(); // with archivable - -// do a hard delete -// with soft_delete -$book->forceDelete(); -// with archivable -$book->deleteWithoutArchive(); - -// find deleted objects -// with soft_delete -$books = BookQuery::create() - ->includeDeleted() - ->where('Book.DeletedAt IS NOT NULL') - ->find(); -// with archivable -$bookArchives = BookArchiveQuery::create() - ->find(); - -// recover a deleted object -// with soft_delete -$book->unDelete(); -// with archivable -$book = new Book(); -$book->populateFromArchive($bookArchive); -$book->save(); -``` - -## Additional Parameters ## - -You can change the name of the archive table added by the behavior by setting the `archive_table` parameter. If the table doesn't exist, the behavior creates it for you. - -```xml - - - -``` - ->**Tip**
The `archive_table` and `archive_class` parameters are mutually exclusive. You can only use either one of the two. - -You can also change the name of the column storing the archive date: - -```xml - - - -``` - -Alternatively, you can disable the addition of an archive date column altogether: - -```xml - - - -``` diff --git a/behaviors/auto-add-pk.markdown b/behaviors/auto-add-pk.markdown deleted file mode 100644 index 7b4291e0..00000000 --- a/behaviors/auto-add-pk.markdown +++ /dev/null @@ -1,74 +0,0 @@ ---- -layout: documentation -title: AutoAddPk Behavior ---- - -# AutoAddPk Behavior # - -The `auto_add_pk` behavior adds a primary key columns to the tables that don't have one. Using this behavior allows you to omit the declaration of primary keys in your tables. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `auto_add_pk` behavior to a table: - -```xml - - - -
-``` - -Rebuild your model, and insert the table creation sql. You will notice that the `book` table has two columns and not just one. The behavior added an `id` column, of type integer and autoincremented. This column can be used as any other column: - -```php -setTitle('War And Peace'); -$b->save(); -echo $b->getId(); // 1 -``` - -This behavior is more powerful if you add it to the database instead of a table. That way, it will alter all tables not defining a primary key column - and leave the others unchanged. - -```xml - - - - -
-
-``` - -You can even enable it for all your databases by adding it to the default behaviors in your `build.properties` file: - -```ini -propel.behavior.default = auto_add_pk -``` - -## Parameters ## - -By default, the behavior adds a column named `id` to the table if the table has no primary key. You can customize all the attributes of the added column by setting corresponding parameters in the behavior definition: - -```xml - - - - - - - - -
-
-``` - -Once you regenerate your model, the column is now named differently: - -```php -setTitle('War And Peace'); -$b->setIdentifier(1); -$b->save(); -echo $b->getIdentifier(); // 1 -``` diff --git a/behaviors/delegate.markdown b/behaviors/delegate.markdown deleted file mode 100644 index 1f06e28f..00000000 --- a/behaviors/delegate.markdown +++ /dev/null @@ -1,204 +0,0 @@ ---- -layout: documentation -title: Delegate Behavior ---- - -# Delegate Behavior # - -The `delegate` behavior allows a model to delegate methods to one of its relationships. This helps to isolate logic in a dedicated model, or to simulate [class table inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html). - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `delegate` behavior to a table. In the `` tag, specify the table that the current table delegates to as the `to` parameter: - -```xml - - - - - - - -
- - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The delegate `profile` table is now related to the `account` table using a one-to-one relationship. That means that the behavior creates a foreign primary key in the `profile` table. In fact, everything happens as if you had defined the following schema: - -```xml - - - - -
- - - - - - - -
-``` - ->**Tip**
If the delegate table already has a foreign key to the main table, the behavior doesn't recreate it. It allows you to have full control over the relationship between the two tables. - -In addition, the ActiveRecord `Account` class now provides integrated delegation capabilities. That means that it offers to handle directly the columns of the `Profile` model, while in reality it finds or create a related `Profile` object and calls the methods on this delegate: - -```php -setLogin('francois'); -$account->setPassword('S€cr3t'); - -// Fill the profile via delegation -$account->setEmail('francois@example.com'); -$account->setTelephone('202-555-9355'); -// same as -$profile = new Profile(); -$profile->setEmail('francois@example.com'); -$profile->setTelephone('202-555-9355'); -$account->setProfile($profile); - -// save the account and its profile -$account->save(); - -// retrieve delegated data directly from the main object -echo $account->getEmail(); // francois@example.com -``` - -Getter and setter methods for delegate columns don't exist on the main object ; the delegation is handled by the magical `__call()` method. Therefore, the delegation also works for custom methods in the delegate table. - -```php -setEmail($fakeEmail); - } -} - -$account = new Account(); -$account->setFakeEmail(); // delegates to Profile::setFakeEmail() -``` - -## Delegating Using a Many-To-One Relationship ## - -Instead of adding a one-to-one relationship, the `delegate` behavior can take advantage of an existing many-to-one relationship. For instance: - -```xml - - - - -
- - - - - - - - - - - - -
- -``` - -In that case, the behavior doesn't modify the foreign keys, it just proxies method called on `Basketballer` to the related `Player`, or creates one if it doesn't exist: - -```php -setPoints(101); -$basketballer->setFieldGoals(47); -$basketballer->setThreePointsFieldGoals(7); -// set player identity via delegation -$basketballer->setFirstName('Michael'); -$basketballer->setLastName('Giordano'); -// same as -$player = new Player(); -$player->setFirstName('Michael'); -$player->setLastName('Giordano'); -$basketballer->setPlayer($player); - -// save basketballer and player -$basketballer->save(); - -// retrieve delegated data directly from the main object -echo $basketballer->getFirstName(); // Michael -``` - -And since several models can delegate to the same player object, that means that a single player can have both basketball and soccer stats! - ->**Tip**
In this example, table delegation is used to implement [Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html). See how Propel implements this inheritance type, and others, in the [inheritance chapter](../documentation/09-inheritance.html). - -## Delegating To Several Tables ## - -Delegation allows to delegate to several tables. Just separate the name of the delegate tables by commas in the `to` parameter of the `delegate` behavior tag in your schema to delegate to several tables: - -```xml - - - - - - - -
- - - -
- - - -
-``` - -Now the `Account` class has two delegates, that can be addressed seamlessly: - -```php -setLogin('francois'); -$account->setPassword('S€cr3t'); - -// Fill the profile via delegation -$account->setEmail('francois@example.com'); -$account->setTelephone('202-555-9355'); -// Fill the preference via delegation -$account->setPreferredColor('orange'); -$account->setMaxSize('200'); - -// save the account and its profile and its preference -$account->save(); -``` - -On the other hand, it is not possible to cascade delegation to yet another model. So even if the `profile` table delegates to another `detail` table, the methods of the `Detail` model won't be accessibe to the `Profile` objects. - -## Parameters ## - -The `delegate` behavior takes only one parameter, the list of delegate tables: - -```xml - - - - - - - -
-``` - -Note that the delegate tables must exist, but they don't need to share a relationship with the main table (in which case the behavior creates a one-to-one relationship). diff --git a/behaviors/i18n.markdown b/behaviors/i18n.markdown deleted file mode 100644 index ef3e3f06..00000000 --- a/behaviors/i18n.markdown +++ /dev/null @@ -1,222 +0,0 @@ ---- -layout: documentation -title: I18n Behavior ---- - -# I18n Behavior # - -The `i18n` behavior provides internationalization to any !ActiveRecord object. Using this behavior, you can separate text data from the other data, and keep several translations of the text data for a single object. Applications supporting several languages always use the `i18n` behavior. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `i18n` behavior to a table. In the `` tag, list the columns that need internationalization as the `i18n_columns` parameter: - -```xml - - - - - - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The internationalized columns have now been moved to a new translation table called `item_i18n`; this new table contains a `locale` column, and shares a many-to-one relationship with the `item` table. In fact, everything happens as if you had defined the following schema: - -```xml - - - - -
- - - - - - - - -
-``` - -In addition, the ActiveRecord Item class now provides integrated translation capabilities. - -```php -setPrice('12.99'); - -// add an English translation -$item->setLocale('en_US'); -$item->setName('Microwave oven'); -// same as -$itemI18n = new ItemI18n(); -$itemI18n->setLocale('en_US'); -$itemI18n->setName('Microwave oven'); -$item->addItemI18n($itemI18n); - -// add a French translation -$item->setLocale('fr_FR'); -$item->setName('Four micro-ondes'); - -// save the item and its translations -$item->save(); - -// retrieve text and non-text translations directly from the main object -echo $item->getPrice(); // 12.99 -$item->setLocale('en_US'); -echo $item->getName(); // Microwave oven -$item->setLocale('fr_FR'); -echo $item->getName(); // Four micro-ondes -``` - -Getter an setter methods for internationalized columns still exist on the main object ; they are just proxy methods to the current translation object, using the same signature and phpDoc for better IDE integration. - ->**Tip**
Propel uses the [locale](http://en.wikipedia.org/wiki/Locale) concept to identify translations. A locale is a string composed of a language code and a territory code (such as 'en_US' and 'fr_FR'). This allows different translations for two countries using the same language (such as 'fr_FR' and 'fr_CA'); - -## Dealing With Locale And Translations ## - -If you prefer to deal with real translation objects, the behavior generates a `getTranslation()` method on the !ActiveRecord class, which returns a translation object with the required locale. - -```php -setPrice('12.99'); - -// get the English translation -$t1 = $item->getTranslation('en_US'); -// same as -$t1 = new ItemI18n(); -$t1->setLocale('en_US'); -$item->addItemI18n($t1); - -$t1->setName('Microwave oven'); - -// get the French translation -$t2 = $item->getTranslation('fr_FR'); - -$t2->setName('Four micro-ondes'); - -// these translation objects are already related to the main item -// and therefore get saved together with it -$item->save(); // already saves the two translations -``` - ->**Tip**
Or course, if a translation already exists for a given locale, `getTranslation()` returns the existing translation and not a new one. - -You can remove a translation using `removeTranslation()` and a locale: - -```php -findPk(1); -// remove the French translation -$item->removeTranslation('fr_FR'); -``` - -## Querying For Objects With Translations ## - -If you need to display a list, the following code will issue n+1 SQL queries, n being the number of items: - -```php -find(); // one query to retrieve all items -$locale = 'en_US'; -foreach ($items as $item) { - echo $item->getPrice(); - $item->setLocale($locale); - echo $item->getName(); // one query to retrieve the English translation -} -``` - -Fortunately, the behavior adds methods to the Query class, allowing you to hydrate both the `Item` objects and the related `ItemI18n` objects for the given locale: - -```php -joinWithI18n('en_US') - ->find(); // one query to retrieve both all items and their translations -foreach ($items as $item) { - echo $item->getPrice(); - echo $item->getName(); // no additional query -} -``` - -In addition to hydrating translations, `joinWithI18n()` sets the correct locale on results, so you don't need to call `setLocale()` for each result. - ->**Tip**
`joinWithI18n()` adds a left join with two conditions. That means that the query returns all items, including those with no translation. If you need to return only objects having translations, add `Criteria::INNER_JOIN` as second parameter to `joinWithI18n()`. - -If you need to search items using a condition on a translation, use the generated `useI18nQuery()` as you would with any `useXXXQuery()` method: - -```php -useI18nQuery('en_US') // tests the condition on the English translation - ->filterByName('Microwave oven') - ->endUse() - ->find(); -``` - -## Symfony Compatibility ## - -This behavior is entirely compatible with the i18n behavior for symfony. That means that it can generate `setCulture()` and `getCulture()` methods as aliases to `setLocale()` and `getLocale()`, provided that you add a `locale_alias` parameter. That also means that if you add the behavior to a table without translated columns, and that the translation table is present in the schema, the behavior recognizes them. - -So the following schema is exactly equivalent to the first one in this tutorial: - -```xml - - - - - - - -
- - - - -
-``` - -Such a schema is almost similar to a schema built for symfony; that means that the Propel i18n behavior is a drop-in replacement for symfony's i18n behavior, keeping BC but improving performance and usability. - -## Parameters ## - -If you don't specify a locale when dealing with a translatable object, Propel uses the default English locale 'en_US'. This default can be overridden in the schema using the `default_locale` parameter: - -```xml - - - - - - - - - - -
-``` - -You can change the name of the locale column added by the behavior by setting the `locale_column` parameter. Also, you can change the table name, the primary key name and the phpName of the i18n table by setting the `i18n_table`, `i18n_pk_name` and `i18n_phpname` parameters: - -```xml - - - - - - - - - - - - - -
-``` diff --git a/behaviors/nested-set.markdown b/behaviors/nested-set.markdown deleted file mode 100644 index e080ff70..00000000 --- a/behaviors/nested-set.markdown +++ /dev/null @@ -1,363 +0,0 @@ ---- -layout: documentation -title: NestedSet Behavior ---- - -# NestedSet Behavior # - -The `nested_set` behavior allows a model to become a tree structure, and provides numerous methods to traverse the tree in an efficient way. - -Many applications need to store hierarchical data in the model. For instance, a forum stores a tree of messages for each discussion. A CMS sees sections and subsections as a navigation tree. In a business organization chart, each person is a leaf of the organization tree. [Nested sets](http://en.wikipedia.org/wiki/Nested_set_model) are the best way to store such hierachical data in a relational database and manipulate it. The name "nested sets" describes the algorithm used to store the position of a model in the tree; it is also known as "modified preorder tree traversal". - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `nested_set` behavior to a table: - -```xml - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has the ability to be inserted into a tree structure, as follows: - -```php -setTitle('Home'); -$s1->makeRoot(); // make this node the root of the tree -$s1->save(); -$s2 = new Section(); -$s2->setTitle('World'); -$s2->insertAsFirstChildOf($s1); // insert the node in the tree -$s2->save(); -$s3 = new Section(); -$s3->setTitle('Europe'); -$s3->insertAsFirstChildOf($s2); // insert the node in the tree -$s3->save(); -$s4 = new Section(); -$s4->setTitle('Business'); -$s4->insertAsNextSiblingOf($s2); // insert the node in the tree -$s4->save(); - -/* -The sections are now stored in the database as a tree: - -$s1:Home - | -$s2:World - | \ -$s3:Europe $s4:Business - -*/ -``` - -You can continue to insert new nodes as children or siblings of existing nodes, using any of the `insertAsFirstChildOf()`, `insertAsLastChildOf()`, `insertAsPrevSiblingOf()`, and `insertAsNextSiblingOf()` methods. - -Once you have built a tree, you can traverse it using any of the numerous methods the `nested_set` behavior adds to the query and model objects. For instance: - -```php -findRoot(); // $s1 -$worldNode = $rootNode->getFirstChild(); // $s2 -$businessNode = $worldNode->getNextSibling(); // $s4 -$firstLevelSections = $rootNode->getChildren(); // array($s2, $s4) -$allSections = $rootNode->getDescendants(); // array($s2, $s3, $s4) -// you can also chain the methods -$europeNode = $rootNode->getLastChild()->getPrevSibling()->getFirstChild(); // $s3 -$path = $europeNode->getAncestors(); // array($s1, $s2) -``` - -The nodes returned by these methods are regular Propel model objects, with access to the properties and related models. The `nested_set` behavior also adds inspection methods to nodes: - -```php -isRoot(); // false -echo $s2->isLeaf(); // false -echo $s2->getLevel(); // 1 -echo $s2->hasChildren(); // true -echo $s2->countChildren(); // 1 -echo $s2->hasSiblings(); // true -``` - -Each of the traversal and inspection methods result in a single database query, whatever the position of the node in the tree. This is because the information about the node position in the tree is stored in three columns of the model, named `tree_left`, `tree_right`, and `tree_level`. The value given to these columns is determined by the nested set algorithm, and it makes read queries much more effective than trees using a simple `parent_id` foreign key. - -## Manipulating Nodes ## - -You can move a node - and its subtree - across the tree using any of the `moveToFirstChildOf()`, `moveToLastChildOf()`, `moveToPrevSiblingOf()`, and `moveToLastSiblingOf()` methods. These operations are immediate and don't require that you save the model afterwards: - -```php -moveToFirstChildOf($s4); -/* The tree is modified as follows: -$s1:Home - | -$s4:Business - | -$s2:World - | -$s3:Europe -*/ -// now move the "Europe" section directly under root, after "Business" -$s3->moveToFirstChildOf($s4); -/* The tree is modified as follows: - $s1:Home - | \ -$s4:Business $s3:Europe - | -$s2:World -*/ -``` - -You can delete the descendants of a node using `deleteDescendants()`: - -```php -deleteDescendants($s4); -/* The tree is modified as follows: - $s1:Home - | \ -$s4:Business $s3:Europe -*/ -``` - -If you `delete()` a node, all its descendants are deleted in cascade. To avoid accidental deletion of an entire tree, calling `delete()` on a root node throws an exception. Use the `delete()` Query method instead to delete an entire tree. - -## Filtering Results ## - -The `nested_set` behavior adds numerous methods to the generated Query object. You can use these methods to build more complex queries. For instance, to get all the children of the root node ordered by title, build a Query as follows: - -```php -childrenOf($rootNode) - ->orderByTitle() - ->find(); -``` - -Alternatively, if you already have an existing query method, you can pass it to the model object's methods to filter the results: - -```php -orderByTitle(); -$children = $rootNode->getChildren($orderQuery); -``` - -## Multiple Trees ## - -When you need to store several trees for a single model - for instance, several threads of posts in a forum - use a _scope_ for each tree. This requires that you enable scope tree support in the behavior definition by setting the `use_scope` parameter to `true`: - -```xml - - - - - - - - - - -
-``` - -Now, after rebuilding your model, you can have as many trees as required: - -```php -findPk(123); -$firstPost = PostQuery::create()->findRoot($thread->getId()); // first message of the discussion -$discussion = PostQuery::create()->findTree(thread->getId()); // all messages of the discussion -PostQuery::create()->inTree($thread->getId())->delete(); // delete an entire discussion -$firstPostOfEveryDiscussion = PostQuery::create()->findRoots(); -``` - -## Using a RecursiveIterator ## - -An alternative way to browse a tree structure extensively is to use a [RecursiveIterator](http://php.net/RecursiveIterator). The `nested_set` behavior provides an easy way to retrieve such an iterator from a node, and to parse the entire branch in a single iteration. - -For instance, to display an entire tree structure, you can use the following code: - -```php -findRoot(); -foreach (new RecursiveIteratorIterator($root->getIterator(), RecursiveIteratorIterator::SELF_FIRST) as $node) { - echo str_repeat(' ', $node->getLevel()) . $node->getTitle() . "\n"; -} -``` - -The iterator parses the tree in a recursive way by retrieving the children of every node. This can be quite effective on very large trees, since the iterator hydrates only a few objects at a time. - -Beware, though, that the iterator executes many queries to parse a tree. On smaller trees, prefer the `getBranch()` method to execute only one query, and hydrate all records at once: - -```php -findRoot(); -foreach ($root->getBranch() as $node) { - echo str_repeat(' ', $node->getLevel()) . $node->getTitle() . "\n"; -} -``` - -## Parameters ## - -By default, the behavior adds three columns to the model - four if you use the scope feature. You can use custom names for the nested sets columns. The following schema illustrates a complete customization of the behavior: - -```xml - - - - - - - - - - - - - - - - - -
-``` - -Whatever name you give to your columns, the `nested_sets` behavior always adds the following proxy methods, which are mapped to the correct column: - -```php -getLeftValue(); // returns $post->lft -$post->setLeftValue($left); -$post->getRightValue(); // returns $post->rgt -$post->setRightValue($right); -$post->getLevel(); // returns $post->lvl -$post->setLevel($level); -$post->getScopeValue(); // returns $post->thread_id -$post->setScopeValue($scope); -``` - -If your application used the old nested sets builder from Propel 1.4, you can enable the `method_proxies` parameter so that the behavior generates method proxies for the methods that used a different name (e.g. `createRoot()` for `makeRoot()`, `retrieveFirstChild()` for `getFirstChild()`, etc. - -```xml - - - - - - -
-``` - -## Complete API ## - -Here is a list of the methods added by the behavior to the model objects: - -```php -` tag to add the `query_cache` behavior to a table: -```xml - - - - -
-``` - -After you rebuild your model, all the queries on this object can now be cached. To trigger the query cache on a particular query, just give it a query key using the `setQueryKey()` method. The key is a unique identifier that you can choose, later used for cache lookups: - -```php -setQueryKey('search book by title') - ->filterByTitle($title) - ->findOne(); -``` - -The first time Propel executes the termination method, it computes the SQL translation of the Query object and stores it into a cache backend (APC by default). Next time you run the same query, it executes faster, even with different parameters: - -```php -setQueryKey('search book by title') - ->filterByTitle($title) - ->findOne(); -``` - ->**Tip**
The more complex the query, the greater the boost you get from the query cache behavior. - -## Parameters ## - -You can change the cache backend and the cache lifetime (in seconds) by setting the `backend` and `lifetime` parameters: - -```xml - - - - - - - -
-``` - -To implement a custom cache backend, just override the generated `cacheContains()`, `cacheFetch()` and `cacheStore()` methods in the Query object. For instance, to implement query cache using Zend_Cache and memcached, try the following: - -```php -getCacheBackend()->test($key); - } - - public function cacheFetch($key) - { - return $this->getCacheBackend()->load($key); - } - - public function cacheStore($key, $value) - { - return $this->getCacheBackend()->save($key, $value); - } - - protected function getCacheBackend() - { - if (self::$cacheBackend === null) { - $frontendOptions = array( - 'lifetime' => 7200, - 'automatic_serialization' => true - ); - $backendOptions = array( - 'servers' => array( - array( - 'host' => 'localhost', - 'port' => 11211, - 'persistent' => true - ) - ) - ); - self::$cacheBackend = Zend_Cache::factory('Core', 'Memcached', $frontendOptions, $backendOptions); - } - - return self::$cacheBackend; - } -} -``` diff --git a/behaviors/sluggable.markdown b/behaviors/sluggable.markdown deleted file mode 100644 index afe33c4d..00000000 --- a/behaviors/sluggable.markdown +++ /dev/null @@ -1,134 +0,0 @@ ---- -layout: documentation -title: Sluggable Behavior ---- - -# Sluggable Behavior # - -The `sluggable` behavior allows a model to offer a human readable identifier that can be used for search engine friendly URLs. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `sluggable` behavior to a table: - -```xml - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has an additional getter for its slug, which is automatically set before the object is saved: - -```php -setTitle('Hello, World!'); -$p1->save(); -echo $p1->getSlug(); // 'hello-world' -``` - -By default, the behavior uses the string representation of the object to build the slug. In the example above, the `title` column is defined as `primaryString`, so the slug uses this column as a base string. The string is then cleaned up in order to allow it to appear in a URL. In the process, blanks and special characters are replaced by a dash, and the string is lowercased. - ->**Tip**
The slug is unique by design. That means that if you create a new object and that the behavior calculates a slug that already exists, the string is modified to be unique: - -```php -setTitle('Hello, World!'); -$p2->save(); -echo $p2->getSlug(); // 'hello-world-1' -``` - -The generated model query offers a `findOneBySlug()` method to easily retrieve a model object based on its slug: - -```php -findOneBySlug('hello-world'); -``` - -## Parameters ## - -By default, the behavior adds one column to the model. If this column is already described in the schema, the behavior detects it and doesn't add it a second time. The behavior parameters allow you to use custom patterns for the slug composition. The following schema illustrates a complete customization of the behavior: - -```xml - - - - - - - - - - - - - -
-``` - -Whatever `slug_column` name you choose, the `sluggable` behavior always adds the following proxy methods, which are mapped to the correct column: - -```php -getSlug(); // returns $post->url -$post->setSlug($slug); // $post->url = $slug -``` - -The `slug_pattern` parameter is the rule used to build the raw slug based on the object properties. Any substring enclosed between brackets '{}' is turned into a getter, so the `Post` class generates slugs as follows: - -```php -getTitle(); -} -``` - -Incidentally, that means that you can use names that don't match a real column phpName, as long as your model provides a getter for it. - -The `replace_pattern` parameter is a regular expression that shows all the characters that will end up replaced by the `replacement` parameter. In the above example, special characters like '!' or ':' are replaced by '-', but not letters, digits, nor '/'. - -The `separator` parameter is the character that separates the slug from the incremental index added in case of non-unicity. Set as '/', it makes `Post` objects sharing the same title have the following slugs: - -```text -'posts/hello-world' -'posts/hello-world/1' -'posts/hello-world/2' -... -``` - -A `permanent` slug is not automatically updated when the fields that constitute it change. This is useful when the slug serves as a permalink, that should work even when the model object properties change. Note that you can still manually change the slug in a model using the `permanent` setting by calling `setSlug()`; - -## Further Customization ## - -The slug is generated by the object when it is saved, via the `createSlug()` method. This method does several operations on a simple string: - -```php -createRawSlug(); - // truncate the slug to accomodate the size of the slug column - $slug = $this->limitSlugSize($slug); - // add an incremental index to make sure the slug is unique - $slug = $this->makeSlugUnique($slug); - - return $slug; -} - -protected function createRawSlug() -{ - // here comes the string composition code, generated according to `slug_pattern` - $slug = 'posts/' . $this->cleanupSlugPart($this->getTitle()); - // cleanupSlugPart() cleans up the slug part - // based on the `replace_pattern` and `replacement` parameters - - return $slug; -} -``` - -You can override any of these methods in your model class, in order to implement a custom slug logic. diff --git a/behaviors/soft-delete.markdown b/behaviors/soft-delete.markdown deleted file mode 100644 index 391b4cf8..00000000 --- a/behaviors/soft-delete.markdown +++ /dev/null @@ -1,130 +0,0 @@ ---- -layout: documentation -title: SoftDelete Behavior ---- - -# SoftDelete Behavior # - -The `soft_delete` behavior overrides the deletion methods of a model object to make them 'hide' the deleted rows but keep them in the database. Deleted objects still don't show up on select queries, but they can be retrieved or undeleted when necessary. - -**Warning**: This behavior is deprecated due to limitations that can't be fixed. Use the [`archivable`](archivable.html) behavior instead. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `soft_delete` behavior to a table: -```xml - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has one new column, `deleted_at`, that stores the deletion date. Select queries don't return the deleted objects: - -```php -setTitle('War And Peace'); -$b->save(); -$b->delete(); -echo $b->isDeleted(); // false -echo $b->getDeletedAt(); // 2009-10-02 18:14:23 -$books = BookQuery::create()->find(); // empty collection -``` - -Behind the curtain, the behavior adds a condition to every SELECT query to return only records where the `deleted_at` column is null. That's why the deleted objects don't appear anymore upon selection. - -_Warning_gg Deleted results may show up in related results (i.e. when you use `joinWith()` on a query and point to a `soft_delete` model). This is something that can't be fixed, and a good reason to use the `archivable` behavior instead. - -You can include deleted results in a query by calling the `includeDeleted()` filter: - -```php -includeDeleted() - ->findOne(); -echo $book->getTitle(); // 'War And Peace' -``` - -You can also turn off the query alteration for the next query by calling the static method `disableSoftDelete()` on the related Query object: - -```php -findOne(); -echo $book->getTitle(); // 'War And Peace' -``` - -Note that `find()` and other selection methods automatically re-enable the `soft_delete` filter, so `disableSoftDelete()` is really a single shot method. You can also enable the query alteration manually by calling the `enableSoftDelete()` method on Query objects. - ->**Tip**
`ModelCriteria::paginate()` executes two queries, so `disableSoftDelete()` doesn't work in this case. Prefer `includeDeleted()` in queries using `paginate()`. - -If you want to recover a deleted object, use the `unDelete()` method: - -```php -unDelete(); -$books = BookQuery::create()->find(); -$book = $books[0]; -echo $book->getTitle(); // 'War And Peace' -``` - -If you want to force the real deletion of an object, call the `forceDelete()` method: - -```php -forceDelete(); -echo $book->isDeleted(); // true -$books = BookQuery::create()->find(); // empty collection -``` - -The query methods `delete()` and `deleteAll()` also perform a soft deletion, unless you disable the behavior on the peer class: - -```php -setTitle('War And Peace'); -$b->save(); - -BookQuery::create()->delete($b); -$books = BookQuery::create()->find(); // empty collection -// the rows look deleted, but they are still there -BookQuery::disableSoftDelete(); -$books = BookQuery::create()->find(); -$book = $books[0]; -echo $book->getTitle(); // 'War And Peace' - -// To perform a true deletion, disable the softDelete feature -BookQuery::disableSoftDelete(); -BookQuery::create()->delete(); -// Alternatively, use forceDelete() -BookQuery::create()->filterByTitle('%Lo%')->forceDelete(); -// To remove all use -BookQuery::create()->forceDeleteAll(); -``` - -## Parameters ## - -You can change the name of the column added by the behavior by setting the `deleted_column` parameter: - -```xml - - - - - - - -
-``` - -```php -setTitle('War And Peace'); -$b->save(); -$b->delete(); -echo $b->getMyDeletionDate(); // 2009-10-02 18:14:23 -$books = BookQuery::create()->find(); // empty collection -``` diff --git a/behaviors/sortable.markdown b/behaviors/sortable.markdown deleted file mode 100644 index b5255046..00000000 --- a/behaviors/sortable.markdown +++ /dev/null @@ -1,371 +0,0 @@ ---- -layout: documentation -title: Sortable Behavior ---- - -# Sortable Behavior # - -The `sortable` behavior allows a model to become an ordered list, and provides numerous methods to traverse this list in an efficient way. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `sortable` behavior to a table: -```xml - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has the ability to be inserted into an ordered list, as follows: - -```php -setTitle('Wash the dishes'); -$t1->save(); -echo $t1->getRank(); // 1, the first rank to be given (not 0) -$t2 = new Task(); -$t2->setTitle('Do the laundry'); -$t2->save(); -echo $t2->getRank(); // 2 -$t3 = new Task(); -$t3->setTitle('Rest a little'); -$t3->save() -echo $t3->getRank(); // 3 -``` - -As long as you save new objects, Propel gives them the first available rank in the list. - -Once you have built an ordered list, you can traverse it using any of the methods added by the `sortable` behavior. For instance: - -```php -findOneByRank(1); // $t1 -$secondTask = $firstTask->getNext(); // $t2 -$lastTask = $secondTask->getNext(); // $t3 -$secondTask = $lastTask->getPrevious(); // $t2 - -$allTasks = TaskQuery::create()->findList(); -// => collection($t1, $t2, $t3) -$allTasksInReverseOrder = TaskQuery::create()->orderByRank('desc')->find(); -// => collection($t3, $t2, $t2) -``` - -The results returned by these methods are regular Propel model objects, with access to the properties and related models. The `sortable` behavior also adds inspection methods to objects: - -```php -isFirst(); // false -echo $t2->isLast(); // false -echo $t2->getRank(); // 2 -``` - -## Manipulating Objects In A List ## - -You can move an object in the list using any of the `moveUp()`, `moveDown()`, `moveToTop()`, `moveToBottom()`, `moveToRank()`, and `swapWith()` methods. These operations are immediate and don't require that you save the model afterwards: - -```php -moveToTop(); -// The list is now 1 - Do the laundry, 2 - Wash the dishes, 3 - Rest a little -$t2->moveToBottom(); -// The list is now 1 - Wash the dishes, 2 - Rest a little, 3 - Do the laundry -$t2->moveUp(); -// The list is 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little -$t2->swapWith($t1); -// The list is now 1 - Do the laundry, 2 - Wash the dishes, 3 - Rest a little -$t2->moveToRank(3); -// The list is now 1 - Wash the dishes, 2 - Rest a little, 3 - Do the laundry -$t2->moveToRank(2); -``` - -By default, new objects are added at the bottom of the list. But you can also insert them at a specific position, using any of the `insertAtTop()`, `insertAtBottom()`, and `insertAtRank()` methods. Note that the `insertAtXXX` methods don't save the object: - -```php -setTitle('Clean windows'); -$t4->insertAtRank(2); -$t4->save(); -// The list is now 1 - Wash the dishes, 2 - Clean Windows, 3 - Do the laundry, 4 - Rest a little -``` - -Whenever you `delete()` an object, the ranks are rearranged to fill the gap: - -```php -delete(); -// The list is now 1 - Wash the dishes, 2 - Do the laundry, 3 - Rest a little -``` - ->**Tip**
You can remove an object from the list without necessarily deleting it by calling `removeFromList()`. Don't forget to `save()` it afterwards so that the other objects in the lists are rearranged to fill the gap. - -## Multiple Lists ## - -When you need to store several lists for a single model - for instance, one task list for each user - use a _scope_ for each list. This requires that you enable scope support in the behavior definition by setting the `use_scope` parameter to `true`: - -```xml - - - - - - - - - - - -
-``` - -Now, after rebuilding your model, you can have as many lists as required: - -```php -setTitle('Wash the dishes'); -$t1->setUser($paul); -$t1->save(); -echo $t1->getRank(); // 1 -$t2 = new Task(); -$t2->setTitle('Do the laundry'); -$t2->setUser($paul); -$t2->save(); -echo $t2->getRank(); // 2 -$t3 = new Task(); -$t3->setTitle('Rest a little'); -$t3->setUser($john); -$t3->save() -echo $t3->getRank(); // 1, because John has his own task list -``` - -The generated methods now accept a `$scope` parameter to restrict the query to a given scope: - -```php -findOneByRank($rank = 1, $scope = $paul->getId()); // $t1 -$lastPaulTask = $firstTask->getNext(); // $t2 -$firstJohnTask = TaskPeer::create()->findOneByRank($rank = 1, $scope = $john->getId()); // $t1 -``` - -Models using the sortable behavior with scope benefit from one additional Query method named `inList()`: - -```php -inList($scope = $paul->getId())->find(); -``` - -## Multi-Column scopes ## - -As of Propel 1.7.0 we added support for Multi-Column scoped Sortable Behavior. This is defined using a comma separated list of column names as `scope_column` parameter. -Note that API methods which are generated by the behavior take parameters in the order which they are defined in `scope_column` parameter. - -```xml - - - - - - - - - - - - -
-``` - -As an alternative you may define the same schema using several `scope_column` tags. - -```xml - - - - - - - - - - - - - -
-``` - -With this schema defined Propel manages one sortable list of tasks per User per Group, so for each User-Group combination: - -```php -setTitle('Create permissions'); -$t1->setUser($paul); -$t1->setGroup($adminGroup); -$t1->save(); -echo $t1->getRank(); // 1 - -$t2 = new Task(); -$t2->setTitle('Grant permissions to users'); -$t2->setUser($paul); -$t2->setGroup($adminGroup); -$t2->save(); -echo $t2->getRank(); // 2 - -$t3 = new Task(); -$t3->setTitle('Install servers'); -$t3->setUser($john); -$t3->setGroup($adminGroup); -$t3->save() -echo $t3->getRank(); // 1, because John has his own task list inside the admin-group - -$t4 = new Task(); -$t4->setTitle('Manage content'); -$t4->setUser($john); -$t4->setGroup($userGroup); -$t4->save() -echo $t4->getRank(); // 1, because John has his own task list inside the user-group - -``` - -The generated methods now accept one parameter per scoped column, to restrict the query to a given scope: - -```php -findOneByRank($rank = 1, $userIdScope = $paul->getId(), $groupIdScope = $adminGroup->getId()); // $t1 -$lastPaulTask = $firstTask->getNext(); // $t2 -$firstJohnUserTask = TaskPeer::create()->findOneByRank($rank = 1, $userIdScope = $john->getId(), $groupIdScope = $userGroup->getId()); // $t4 -``` - -Models using the sortable behavior with scope benefit from one additional Query method named `inList()`: - -```php -inList($userIdScope = $john->getId(), $groupIdScope = $userGroup->getId())->find(); -``` - -## Parameters ## - -By default, the behavior adds one columns to the model - two if you use the scope feature. If these columns are already described in the schema, the behavior detects it and doesn't add them a second time. The behavior parameters allow you to use custom names for the sortable columns. The following schema illustrates a complete customization of the behavior: - -```xml - - - - - - - - - - - - - -
-``` - -Whatever name you give to your columns, the `sortable` behavior always adds the following proxy methods, which are mapped to the correct column: - -```php -getRank(); // returns $task->my_rank_column -$task->setRank($rank); -$task->getScopeValue(); // returns $task->user_id -$task->setScopeValue($scope); -``` - -The same happens for the generated Query object: - -```php -filterByRank(); // proxies to filterByMyRankColumn() -$query = TaskQuery::create()->orderByRank(); // proxies to orderByMyRankColumn() -$tasks = TaskQuery::create()->findOneByRank(); // proxies to findOneByMyRankColumn() -``` - ->**Tip**
The behavior adds columns but no index. Depending on your table structure, you might want to add a column index by hand to speed up queries on sorted lists. - -## Complete API ## - -Here is a list of the methods added by the behavior to the model objects: - -```php - $rank associative array -// only for behavior with use_scope -array inList($scope) -``` - -The behavior also adds a few methods to the Peer classes: - -```php - $rank associative array -// only for behavior with use_scope -array retrieveList($scope) -int countList($scope) -int deleteList($scope) -``` diff --git a/behaviors/timestampable.markdown b/behaviors/timestampable.markdown deleted file mode 100644 index cc5b2dde..00000000 --- a/behaviors/timestampable.markdown +++ /dev/null @@ -1,114 +0,0 @@ ---- -layout: documentation -title: Timestampable Behavior ---- - -# Timestampable Behavior # - -The `timestampable` behavior allows you to keep track of the date of creation and last update of your model objects. - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `timestampable` behavior to a table: - -```xml - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has two new columns, `created_at` and `updated_at`, that store a timestamp automatically updated on save: - -```php -setTitle('War And Peace'); -$b->save(); -echo $b->getCreatedAt(); // 2009-10-02 18:14:23 -echo $b->getUpdatedAt(); // 2009-10-02 18:14:23 -$b->setTitle('Anna Karenina'); -$b->save(); -echo $b->getCreatedAt(); // 2009-10-02 18:14:23 -echo $b->getUpdatedAt(); // 2009-10-02 18:14:25 -``` - -The object query also has specific methods to retrieve recent objects and order them according to their update date: - -```php -recentlyUpdated() // adds a minimum value for the update date - ->lastUpdatedFirst() // orders the results by descending update date - ->find(); -``` - -You can use any of the following methods in the object query: - -```php -**Tip**
You may need to keep the update date unchanged after an update on the object, for instance when you only update a calculated row. In this case, call the `keepUpdateDateUnchanged()` method on the object before saving it. - - -## Parameters ## - -You can change the name of the columns added by the behavior by setting the `create_column` and `update_column` parameters: - -```xml - - - - - - - - - -
-``` - -```php -setTitle('War And Peace'); -$b->save(); -echo $b->getMyCreateDate(); // 2009-10-02 18:14:23 -echo $b->getMyUpdateDate(); // 2009-10-02 18:14:23 -$b->setTitle('Anna Karenina'); -$b->save(); -echo $b->getMyCreateDate(); // 2009-10-02 18:14:23 -echo $b->getMyUpdateDate(); // 2009-10-02 18:14:25 -``` - -If you only want to keep track of the date of creation of your model objects set the `disable_updated_at` parameter to true: - -```xml - - - - - - -
-``` - -```php -setTitle('War And Peace'); -$b->save(); -echo method_exists($b, 'getCreatedAt'); // true -echo method_exists($b, 'getUpdatedAt'); // false -echo $b->getCreatedAt(); // 2009-10-02 18:14:23 -``` diff --git a/behaviors/versionable.markdown b/behaviors/versionable.markdown deleted file mode 100644 index 7cda9edc..00000000 --- a/behaviors/versionable.markdown +++ /dev/null @@ -1,264 +0,0 @@ ---- -layout: documentation -title: Versionable Behavior ---- - -# Versionable Behavior # - -The `versionable` behavior provides versioning capabilities to any ActiveRecord object. Using this behavior, you can: - -* Revert an object to previous versions easily -* Track and browse history of the modifications of an object -* Keep track of the modifications in related objects - -## Basic Usage ## - -In the `schema.xml`, use the `` tag to add the `versionable` behavior to a table: -```xml - - - - -
-``` - -Rebuild your model, insert the table creation sql again, and you're ready to go. The model now has one new column, `version`, which stores the version number. It also has a new table, `book_version`, which stores all the versions of all `Book` objects, past and present. You won't need to interact with this second table, since the behavior offers an easy-to-use API that takes care of all verisoning actions from the main ActiveRecord object. - -```php -setTitle('War and Peas'); -$book->save(); -echo $book->getVersion(); // 1 -$book->setTitle('War and Peace'); -$book->save(); -echo $book->getVersion(); // 2 - -// reverting to a previous version -$book->toVersion(1); -echo $book->getTitle(); // 'War and Peas' -// saving a previous version creates a new one -$book->save(); -echo $book->getVersion(); // 3 - -// checking differences between versions -print_r($book->compareVersions(1, 2)); -// array( -// 'Title' => array(1 => 'War and Peas', 2 => 'War and Pace'), -// ); - -// deleting an object also deletes all its versions -$book->delete(); -``` - -## Adding details about each revision ## - -For future reference, you probably need to record who edited an object, as well as when and why. To enable audit log capabilities, add the three following parameters to the `` tag: - -```xml - - - - - - - - -
-``` - -Rebuild your model, and you can now define an author name and a comment for each revision using the `setVersionCreatedBy()` and `setVersionComment()` methods, as follows: - -```php -setTitle('War and Peas'); -$book->setVersionCreatedBy('John Doe'); -$book->setVersionComment('Book creation'); -$book->save(); - -$book->setTitle('War and Peace'); -$book->setVersionCreatedBy('John Doe'); -$book->setVersionComment('Corrected typo on book title'); -$book->save(); -``` - -## Retrieving revision history ## - -```php -toVersion(1); -echo $book->getVersionCreatedBy(); // 'John Doe' -echo $book->getVersionComment(); // 'Book creation' -// besides, the behavior also logs the creation date for all versions -echo $book->getVersionCreatedAt(); // '2010-12-21 22:57:19' - -// if you need to list the revision details, it is better to use the version object -// than the main object. The following requires only one database query: -foreach ($book->getAllVersions() as $bookVersion) { - echo sprintf("'%s', Version %d, updated by %s on %s (%s)\n", - $bookVersion->getTitle(), - $bookVersion->getVersion(), - $bookVersion->getVersionCreatedBy(), - $bookVersion->getVersionCreatedAt(), - $bookVersion->getVersionComment(), - ); -} -// 'War and Peas', Version 1, updated by John Doe on 2010-12-21 22:53:02 (Book Creation) -// 'War and Peace', Version 2, updated by John Doe on 2010-12-21 22:57:19 (Corrected typo on book title) -``` - -## Conditional versioning ## - -You may not need a new version each time an object is created or modified. If you want to specify your own condition, just override the `isVersioningNecessary()` method in your stub class. The behavior calls it behind the curtain each time you `save()` the main object. No version is created if it returns false. - -```php -getISBN() !== null && parent::isVersioningNecessary(); - } -} - -$book = new Book(); -$book->setTitle('Pride and Prejudice'); -$book->save(); // book is saved, no new version is created -$book->setISBN('0553213105'); -$book->save(): // book is saved, and a new version is created -``` - -Alternatively, you can choose to disable the automated creation of a new version at each save for all objects of a given model by calling the `disableVersioning()` method on the Peer class. In this case, you still have the ability to manually create a new version of an object, using the `addVersion()` method on a saved object: - -```php -setTitle('Pride and Prejudice'); -$book->setVersion(1); -$book->save(); // book is saved, no new version is created -$book->addVersion(); // a new version is created - -// you can reenable versioning using the Peer static method enableVersioning() -BookPeer::enableVersioning(); -``` - -## Versioning Related objects ## - -If a model uses the versionable behavior, and is related to another model also using the versionable behavior, then each object automatically keeps track of the modifications of related objects. This means that calling `toVersion()` restores the state of the main object _and of the related objects as well_. - -The following example assumes that both the `Book` model and the `Author` model are versionable - one `Author` has many `Books`: - -```php -setFirstName('Leo'); -$author->setLastName('Totoi'); -$book = new Book(); -$book->setTitle('War and Peas'); -$book->setAuthor($author); -$book->save(); // version 1 - -$book->setTitle('War and Peace'); -$book->save(); // version 2 - -$author->setLastName('Tolstoi'); -$book->save(); // version 3 - -$book->toVersion(1); -echo $book->getTitle(); // 'War and Peas' -echo $book->getAuthor()->getLastName(); // 'Totoi' -$book->toVersion(3); -echo $book->getTitle(); // 'War and Peace' -echo $book->getAuthor()->getLastName(); // 'Tolstoi' -``` - ->**Tip**
Versioning of related objects is only possible for simple foreign keys. Relationships based on composite foreign keys cannot preserve relation versionning for now. - -## Parameters ## - -You can change the name of the column added by the behavior by setting the `version_column` parameter. Propel only adds the column if it's not already present, so you can easily customize this column by adding it manually to your schema: - -```xml - - - - - - - -
-``` - -```php -setTitle('War And Peace'); -$b->save(); -echo $b->getMyVersionColumn(); // 1 -// For convenience and ease of use, Propel creates a getVersion() anyway -echo $b->getVersion(); // 1 -``` - -You can also change the name of the version table by setting the `version_table` parameter. Again, Propel automatically creates the table, unless it's already present in the schema: - -```xml - - - - - - -
-``` - -The audit log abilities need to be enabled in the schema as well: - -```xml - - - - - - - - - - - -
-``` - -## Public API ## - -### ActiveRecord class ### - -* `void save()`: Adds a new version to the object version history and increments the `version` property -* `void delete()`: Deletes the object version history -* `boolean isVersioningNecessary(PropelPDO $con = null)`: Checks whether a new version needs to be saved -* `void enforceVersioning()`: Enforces a new version of the object upon next save -* `BaseObject toVersion(integer $version_number)`: Populates the properties of the current object with values from the requested version. Beware that saving the object afterwards will create a new version (and not update the previous version). -* `integer getLastVersionNumber(PropelPDO $con)`: Queries the database for the highest version number recorded for this object -* `boolean isLastVersion()`: Returns true if the current object is the last available version -* `Version addVersion(PropelPDO $con)`: Creates a new Version record and saves it. To be used when isVersioningNecessary() is false. Beware that it doesn't take care of incrementing the version number of the main object, and that the main object must be saved prior to calling this method. -* `array getAllVersions(PropelPDO $con)`: Returns all Version objects related to the main object in a collection -* `array getLastVersions($number, $criteria, $con)`: Retrieves the last $number versions -* `Version getOneVersion(integer $versionNumber PropelPDO $con)`: Returns a given version object -* `array compareVersions(integer $version1, integer $version2)`: Returns an array of differences showing which parts of a resource changed between two versions -* `compareVersion(integer $versionNumber, string $keys, PropelPDO $con, array $ignoredColumns)`: Compares the current object with another of its version -* `BaseObject populateFromVersion(Version $version, PropelPDO $con)`: Populates an ActiveRecord object based on a Version object -* `BaseObject setVersionCreatedBy(string $createdBy)`: Defines the author name for the revision -* `string getVersionCreatedBy()`: Gets the author name for the revision -* `mixed getVersionCreatedAt()`: Gets the creation date for the revision (the behavior takes care of setting it) -* `BaseObject setVersionComment(string $comment)`: Defines the comment for the revision -* `string getVersionComment()`: Gets the comment for the revision - -### Peer class ### - -* `void enableVersioning()`: Enables versionning for all instances of the related ActiveRecord class -* `void disableVersioning()`: Disables versionning for all instances of the related ActiveRecord class -* `boolean isVersioningEnabled()`: Checks whether the versionnig is enabled diff --git a/contribute.markdown b/contribute.markdown deleted file mode 100644 index 4b6aace6..00000000 --- a/contribute.markdown +++ /dev/null @@ -1,68 +0,0 @@ ---- -layout: default -title: How To Contribute ? ---- - -# How To Contribute ? # - -You can easily contribute to the Propel project since all projects are hosted by [GitHub](https://github.com). -You just have to _fork_ the Propel project on the [PropelORM organization](https://github.com/propelorm) and -to provide Pull Requests or to submit issues. Note, we are using [Git](http://git-scm.com) as main Source Code Management. - -The Propel organization maintains four projects: - -* [Propel](http://github.com/propelorm/Propel) : the main project. -* [PropelBundle](http://github.com/propelorm/PropelBundle) : a bundle to integrate Propel with [Symfony2](http://www.symfony.com). -* [sfPropelORMPlugin](http://github.com/propelorm/sfPropelORMPlugin) : a plugin to integrate Propel with [symfony 1.x](http://www.symfony-project.org); -* [propelorm.github.com](https://github.com/propelorm/propelorm.github.com) : the Propel documentation (aka this website). - -## Submit an issue ## - -The ticketing system is also hosted on GitHub: - -* Propel: [https://github.com/propelorm/Propel/issues](https://github.com/propelorm/Propel/issues) -* PropelBundle: [https://github.com/propelorm/PropelBundle/issues](https://github.com/propelorm/PropelBundle/issues) -* sfPropelORMPlugin: [https://github.com/propelorm/sfPropelORMPlugin/issues](https://github.com/propelorm/sfPropelORMPlugin/issues) - -## Make a Pull Request ## - -The best way to submit a patch is to [make a Pull Request on GitHub](https://help.github.com/articles/creating-a-pull-request). -First, you should create a new branch from the `master`. -Assuming you are in your local Propel project: - -```bash -$ git checkout -b fix-my-patch master -``` - -Now you can write your patch in this branch. Don't forget to provide unit tests with your fix to prove both the bug and the patch. -It will ease the process to accept or refuse a Pull Request. - -When you're done, you have to rebase your branch to provide a clean and safe Pull Request. - -```bash -$ git checkout master -$ git pull --ff-only upstream master -$ git checkout fix-my-patch -$ git rebase master -``` - -In this example, the `upstream` remote is the PropelORM organization repository. - -Once done, you can submit the Pull Request by pushing your branch to your fork: - -```bash -$ git push origin fix-my-patch -``` - -Go to your fork on Github, switch to your patch's branch (in the above example, -it's `fix-my-patch`) and click on the "Compare and pull request" button. Add a -short description to this Pull Request and submit it! - -## Running Unit Tests ## - -See [Working with unit tests](../cookbook/working-with-unit-tests.html) How to setup propel's required environment and use PHPUnit. - - -## Improve the documentation ## - -The Propel documentation is written in [Markdown](http://daringfireball.net/projects/markdown/) syntax and runs through [GitHub Pages](http://pages.github.com/). Everybody can contribute to the documentation by forking the [propelorm.github.com](https://github.com/propelorm/propelorm.github.com) project and to submit Pull Requests. diff --git a/cookbook/adding-additional-sql-files.markdown b/cookbook/adding-additional-sql-files.markdown deleted file mode 100644 index 752145a0..00000000 --- a/cookbook/adding-additional-sql-files.markdown +++ /dev/null @@ -1,38 +0,0 @@ ---- -layout: documentation -title: Adding Additional SQL Files ---- - -# Adding Additional SQL Files # - -In many cases you may wish to have the _insert-sql_ task perform additional SQL operations (e.g. add views, stored procedures, triggers, sample data, etc.). Rather than have to run additional SQL statements yourself every time you re-build your object model, you can have the Propel generator do this for you. - -## 1. Create the SQL DDL files ## - -Create any additional SQL files that you want executed against the database (after the base _schema.sql_ file is applied). - -For example, if we wanted to add a default value to a column that was unsupported in the schema (e.g. where value is a SQL function): - -```sql --- (for postgres) -ALTER TABLE my_table ALTER COLUMN my_column SET DEFAULT CURRENT_TIMESTAMP; -``` - -Now we save that as _'my\_column-default.sql'_ in the same directory as the generated _'schema.sql'_ file (usually in projectdir/build/sql/). - -## 2. Tell Propel Generator about the new file ## - -In that same directory (where your _'schema.sql'_ is located), there is a _'sqldb.map'_ file which contains a mapping of SQL DDL files to the database that they should be executed against. After running the propel generator, you will probably have a single entry in that file that looks like: - -```text -schema.sql=your-db-name -``` - -We want to simply add the new file we created to this file (future builds will preserve anything you add to this file). When we're done, the file will look like this: - -```text -schema.sql=your-db-name -my_column-default.sql=your-db-name -``` - -Now when you execute the _insert-sql_ Propel generator target, the _'my_column-default.sql_' file will be executed against the _your-db-name_ database. diff --git a/cookbook/copying-persisted-objects.markdown b/cookbook/copying-persisted-objects.markdown deleted file mode 100644 index 9f974753..00000000 --- a/cookbook/copying-persisted-objects.markdown +++ /dev/null @@ -1,48 +0,0 @@ ---- -layout: documentation -title: Copying Persisted Objects ---- - -# Copying Persisted Objects # - -Propel provides the `copy()` method to perform copies of mapped row in the database. Note that Propel does _not_ override the `__clone()` method; this allows you to create local duplicates of objects that map to the same persisted database row (should you need to do this). - -The `copy()` method by default performs shallow copies, meaning that any foreign key references will remain the same. - -```php -setFirstName("Aldous"); -$a->setLastName("Huxley"); - -$p = new Publisher(); -$p->setName("Harper"); - -$b = new Book(); -$b->setTitle("Brave New World"); -$b->setPublisher($p); -$b->setAuthor($a); - -$b->save(); // so that auto-increment IDs are created - -$bcopy = $b->copy(); -var_export($bcopy->getId() == $b->getId()); // FALSE -var_export($bcopy->getAuthorId() == $b->getAuthorId()); // TRUE -var_export($bcopy->getAuthor() == $b->getAuthor()); // TRUE -``` - -## Deep Copies ## - -By calling `copy()` with a `TRUE` parameter, Propel will create a deep copy of the object; this means that any related objects will also be copied. - -To continue with example from above: - -```php -copy(true); -var_export($bcopy->getId() == $b->getId()); // FALSE -var_export($bcopy->getAuthorId() == $b->getAuthorId()); // FALSE -var_export($bcopy->getAuthor() == $b->getAuthor()); // FALSE -``` diff --git a/cookbook/customizing-build.markdown b/cookbook/customizing-build.markdown deleted file mode 100644 index 68dc8336..00000000 --- a/cookbook/customizing-build.markdown +++ /dev/null @@ -1,173 +0,0 @@ ---- -layout: documentation -title: Customizing build ---- - -# Customizing Build # - -It is possible to customize the Propel build process by overriding values in your propel `build.properties` file. For maximum flexibility, you can even create your own Phing `build.xml` file. - -## Customizing the build.properties ## - -The easiest way to customize your Propel build is to simply specify build properties in your project's `build.properties` file. - -### Understanding Phing build properties ### - -_Properties_ are essentially variables. These variables can be specified on the commandline or in _properties files_. - -For example, here's how a property might be specified on the commandline: - -```bash -> phing -Dpropertyname=value -``` - -More typically, properties are stored in files and loaded by Phing. For those not familiar with Java properties files, these files look like PHP INI files; the main difference is that values in properties files can be references to other properties (a feature that will probably exist in in INI files in PHP 5.1). - ->**Importantly**
properties, once loaded, are not overridden by properties with the same name unless explicitly told to do so. In the Propel build process, the order of precedence for property values is as follows: - -1. Commandline properties -2. Project `build.properties` -3. Top-level `build.properties` -4. Top-level `default.properties` - -This means, for example, that values specified in the project's `build.properties` files will override those in the top-level `build.properties` and `default.properties` files. - -### Changing values ### - -To get an idea of what you can modify in Propel, simply look through the `build.properties` and `default.properties` files. - -_Note, however, that some of the current values exist for legacy reasons and will be cleaned up in Propel 1.1._ - -#### New build output directories #### - -This can easily be customized on a project-by-project basis. For example, here is a `build.properties` file for the _bookstore _project that puts the generated classes in `/var/www/bookstore/classes` and puts the generated SQL in `/var/www/bookstore/db/sql`: - -```ini -propel.project = bookstore -propel.database = sqlite -propel.database.url = sqlite://localhost/./test/bookstore.db -propel.targetPackage = bookstore - -# directories -propel.output.dir = /var/www/bookstore -propel.php.dir = ${propel.output.dir}/classes -propel.phpconf.dir = ${propel.output.dir}/conf -propel.sql.dir = ${propel.output.dir}/db/sql -``` - -The _targetPackage_ property is also used in determining the path of the generated classes. In the example above, the `Book.php` class will be located at `/var/www/bookstore/classes/bookstore/Book.php`. You can change this `bookstore` subdir by altering the _targetPackage_ property: - -```ini -propel.targetPackage = propelom -``` - -Now the class will be located at `/var/www/bookstore/classes/propelom/Book.php` - -_Note that you can override the targetPackage property by specifying a package="" attribute in the `` tag or even the `` tag of the schema.xml._ - -## Creating a custom build.xml file ## - -If you want to make more major changes to the way the build script works, you can setup your own Phing build script. This actually is not a very scary task, and once you've managed to create a Phing build script, you'll probably want to create build targets for other aspects of your project (e.g. running batch unit tests is now supported in Phing 2.1-CVS). - -To start with, I suggest taking a look at the `build-propel.xml` script (the build.xml script is just a wrapper script). Note, however, that the `build-propel.xml` script does a lot and has a lot of complexity that is designed to make it easy to configure using properties (so, don't be scared). - -Without going into too much detail about how Phing works, the important thing is that Phing build scripts XML and they are grouped into _targets_ which are kinda like functions. The actual work of the scripts is performed by _tasks_, which are PHP5 classes that extend the base Phing _Task_ class and implement its abstract methods. Propel provides some Phing tasks that work with templates to create the object model. - -### Step 1: register the needed tasks ### - -The Propel tasks must be registered so that Phing can find them. This is done using the `` tag. You can see this near the top of the `build-propel.xml` file. - -For example, here is how we register the `` task, which is the task that creates the PHP classes for your object model: - -```xml - -``` - -Simple enough. Phing will now associate the `` tag with the _PropelOMTask_ class, which it expects to find at `propel/phing/PropelOMTask.php` (on your _include_path_). If Propel generator classes are not on your _include_path_, you can specify that path in your `` tag: - -```xml - -``` - -Or, for maximum re-usability, you can create a `` object, and then reference it (this is the way `build-propel.xml` does it): - -```xml - - - - - -``` - -### Step 2: invoking the new task ### - -Now that the `` task has been registered with Phing, it can be invoked in your build file. - -```xml - - - -``` - -In the example above, it's worth pointing out that the `` task can actually transform multiple `schema.xml` files, which is why there is a `` sub-element. Phing _filesets_ are beyond the scope of this HOWTO, but hopefully the above example is obvious enough. - -### Step 3: putting it together into a build.xml file ### - -Now that we've seen the essential elements of our custom build file, it's time to look at how to assemble them into a working whole: - -```xml - - - - - - - - - - - - - - - - - - - - - - - -``` - -If that build script was named `build.xml` then it could be executed by simply running _phing_ in the directory where it is located: - -```bash -> phing om -``` - -Actually, specifying the _om_ target is not necessary since it is the default. - -Refer to the `build-propel.xml` file for examples of how to use the other Propel Phing tasks -- e.g. `` for generating the DDL SQL, `` for inserting the SQL, etc. diff --git a/cookbook/dbdesigner.markdown b/cookbook/dbdesigner.markdown deleted file mode 100644 index 6011d72b..00000000 --- a/cookbook/dbdesigner.markdown +++ /dev/null @@ -1,42 +0,0 @@ ---- -layout: documentation -title: How to Create A Schema Based On A DBDesigner Model ---- - -# How to Create A Schema Based On A DBDesigner Model # - -If you use [DBDesigner 4](http://www.fabforce.net/dbdesigner4/) to design your model, Propel can take the XML file used by DBDesigner as storage, and convert it to a Propel schema. - -## Basic Usage ## - -The process is very straightforward. Just copy a DBDesigner model file under the `dbd/` subdirectory of your project folder, and call the `dbd2propel` task: - -```text -> cd /path/to/myproject -> mkdir dbd -> cp /path/to/dbdesigner/models/* dbd/ -> propel-gen dbd2propel -``` - -Propel looks for all `dbd/*.xml` files, converts them to the Propel XML schema format, and saves the new schemas in the project folder. For instance, if you have a DBDesigner4 model named `model.xml` under the `dbd/` directory, Propel will create a `model.schema.xml` file out of it. - -Once the Propel XML schema is created, you can build the project, as described in the [Building A Project chapter](../documentation/02-buildtime). - -## Customizing The Task ## - -You can customize the task by overriding any of the three following settings in your `build.properties`: - -```ini -# ------------------------------------------------------------------- -# -# D B D E S I G N E R 2 P R O P E L S E T T I N G S -# -# ------------------------------------------------------------------- - -# Directory where the task looks for DBDesigner model files -propel.dbd2propel.dir = ${propel.project.dir}/dbd -# Pattern for DBDesigner file names -propel.dbd2propel.includes = *.xml -# XSLT used to transform DBDesigner files to Propel schemas -propel.dbd2propel.xsl.file = ${propel.home}/resources/xsl/dbd2propel.xsl -``` diff --git a/cookbook/geocodable-behavior.markdown b/cookbook/geocodable-behavior.markdown deleted file mode 100644 index c3220157..00000000 --- a/cookbook/geocodable-behavior.markdown +++ /dev/null @@ -1,179 +0,0 @@ ---- -layout: documentation -title: Geocodable Behavior ---- - -# Geocodable Behavior # - -The [**Geocodable Behavior**](https://github.com/willdurand/GeocodableBehavior) helps you build geo-aware applications. It automatically geocodes your models when they are saved, giving you the ability to search by location and calculate distances between records. - -This behavior uses [Geocoder](https://github.com/willdurand/Geocoder), the Geocoder PHP 5.3 library and requires [Propel](http://github.com/propelorm/Propel) 1.6.4-dev and above. - -## Installation ## - -Download the behavior: [https://github.com/willdurand/GeocodableBehavior](https://github.com/willdurand/GeocodableBehavior), then cherry-pick the `GeocodableBehavior.php` file in `src/`, and put it somewhere. - -Add the following line to your `propel.ini` or `build.properties` configuration file: - -```ini -propel.behavior.geocodable.class = path.to.GeocodableBehavior -``` - -## Usage ## - -Just add the following XML tag in your `schema.xml` file: - -```xml - -``` - -Basically, the behavior will add: - -* two new columns to your model (`latitude` and `longitude`); -* four new methods to the _ActiveRecord_ API (`getDistanceTo()`, `isGeocoded()`, `getCoordinates()`, and `setCoordinates()`); -* a new method to the _ActiveQuery_ API (`filterByDistanceFrom()`). - - -### ActiveRecord API ### - -`getDistanceTo()` returns the distance between the current object and a given one. -The method takes two arguments: - -* a geocoded object; -* a measure unit (`KILOMETERS_UNIT`, `MILES_UNIT`, or `NAUTICAL_MILES_UNIT` defined in the `Peer` class of the geocoded model). - -`isGeocoded()` returns a boolean value whether the object has been geocoded or not. - -`getCoordinates()`, `setCoordinates()` allows to quickly set/get latitude and longitude values. - - -### ActiveQuery API ### - -`filterByDistanceFrom()` takes five arguments: - -* a latitude value; -* a longitude value; -* a distance value; -* a measure unit (`KILOMETERS_UNIT`, `MILES_UNIT`, or `NAUTICAL_MILES_UNIT` defined in the `Peer` class of the geocoded model); -* a comparison sign (`Criteria::LESS_THAN` is the default value). - - -It will add a filter by distance on your current query and returns itself for fluid interface. - - -## Automatic Geocoding ## - -At this step, you have to fill in the two columns (`latitude` and `longitude`) yourself. -It's not really useful, right ? - -Automatic geocoding to the rescue! There are two automatic ways to get geocoded information: - -* using IP addresses; -* using street addresses. - -It provides a ```geocode``` method that autoupdate the loaction values. -To prevent autofill when modified, just set ```auto_update``` attribute to false. - -Note: You can use both at the same time. - - -### IP-Based Geocoding ### - -To enable the IP-Based geocoding, add the following configuration in your `schema.xml` file: - -```xml - - - - -``` - -By default, the default Geocoder `provider` is `YahooProvider` so you'll need to fill in an API key. - -If you want to use another provider, you'll need to set a new parameter: - -```xml - -``` - -Read the **Geocoder** documentation to know more about providers. - -This configuration will add a new column to your model: `ip_address`. You can change the name of this column using the following parameter: - -```xml - -``` - -The behavior will now use the `ip_address` value to populate the `latitude` and `longitude` columns thanks to **Geocoder**. - - -### Address-Based Geocoding ### - -To enable the Address-Based geocoding, add the following configuration: - -```xml - - - - -``` - -By default, the default Geocoder `provider` is `YahooProvider` so you'll need to fill in an API key but keep in mind it's an optional parameter depending on the provider you choose. - -If you want to use another provider, you'll need to set a new parameter: - -```xml - -``` - -Read the **Geocoder** documentation to know more about providers. - -Basically, the behavior looks for attributes called street, locality, region, postal_code, and country. It tries to make a complete address with them. As usual, you can tweak this parameter to add your own list of attributes that represents a complete street address: - -```xml - -``` - -These parameters will be concatened and separated by a comma to make a street address. This address will be used to get `latitude` and `longitude` values. - -Now, each time you save your object, the two columns `latitude` and `longitude` are populated thanks to **Geocoder**. - - -## HTTP Adapters ## - -**Geocoder** provides HTTP adapters which can be configured through the behavior. By default, this behavior uses the `CurlHttpAdapter`. - -If you want to use another `adapter`, you'll need to use the following parameter: - -```xml - -``` - -Read the **Geocoder** documentation to know more about adapters. - - -## Parameters ## - -```xml - - - - - - - - - - - - - - - - - - - -``` - -This is the default configuration. diff --git a/cookbook/index.markdown b/cookbook/index.markdown deleted file mode 100644 index 136f6db2..00000000 --- a/cookbook/index.markdown +++ /dev/null @@ -1,70 +0,0 @@ ---- -layout: default -title: The Cookbook ---- - -# The Cookbook # - -### Common Tasks ### - -* [Adding Additional SQL files](adding-additional-sql-files.html) - -* [Copying Persisted Objects](copying-persisted-objects.html) - -* [Customizing build](customizing-build.html) - -* [How to Create A Schema Based On A DBDesigner Model](dbdesigner.html) - -* [How to Use PHP 5.3 Namespaces](namespaces.html) - -* [Model Introspection At Runtime](runtime-introspection.html) - -* [Multi-Component Data Model](multi-component-data-model.html) - -* [Replication](replication.html) - -* [Using Propel With MSSQL Server](using-mssql-server.html) - -* [Using SQL Schemas](using-sql-schemas.html) - -* [Working With Advanced Column Types](working-with-advanced-column-types.html) - -* [Working With Existing Databases](working-with-existing-databases.html) - - -### Extending Propel ### - -* [How to Write A Behavior](writing-behavior.html) - -* [How to Unit Test Your Behaviors](testing-your-behaviors.html) - -* [User Contributed Behaviors](user-contributed-behaviors.html) - - -### Working with symfony 1.4 ### - -* [Init A Symfony Project With Propel As Default ORM - The Git Way](symfony1/init-a-Symfony-project-with-Propel-git-way.html) - -* [How To Use Propel i18n Behavior With Symfony 1.4](symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4.html) - -* [How To Use Old SfPropelBehaviori18n (Aka symfony_i18n) With Symfony 1.4](symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4.html) - - -### Working with Symfony2 ### - -* [Working with Symfony2 (Introduction)](symfony2/working-with-symfony2.html) - -* [Symfony2 And Propel In Real Life](symfony2/symfony2-and-propel-in-real-life.html) - -* [Mastering Symfony2 Forms With Propel](symfony2/mastering-symfony2-forms-with-propel.html) - -* [The Symfony2 Security Component And Propel](symfony2/the-symfony2-security-component-and-propel.html) - -* [Adding A New Behavior In Symfony2](symfony2/adding-a-new-behavior-in-symfony2.html) - -* [Testing](symfony2/testing.html) - - -### Working with Silex ### - -* [Working with Silex](silex/working-with-silex.html) diff --git a/cookbook/multi-component-data-model.markdown b/cookbook/multi-component-data-model.markdown deleted file mode 100644 index 4f90fa09..00000000 --- a/cookbook/multi-component-data-model.markdown +++ /dev/null @@ -1,301 +0,0 @@ ---- -layout: documentation -title: Multi-Component Data Model ---- - -# Multi-Component Data Model # - -Propel comes along with packaging capabilities that allow you to more easily integrate Propel into a packaged or modularized application. - -## Muliple Schemas ## - -You can use as many `schema.xml` files as you want. Schema files have to be named `(*.)schema.xml`, so names like `schema.xml`, `package1.schema.xml`, `core.package1.schema.xml` are all acceptable. These files _have_ to be located in your project directory. - -Each schema file has to contain a `` element with a `name` attribute. This name references the connection settings to be used for this database (and configured in the `runtime-conf.xml`), so separated schemas can share a common database name. - -Whenever you call a propel build task, Propel will consider all these schema files and build the classes (or the SQL) for all the tables. - -## Understanding Packages ## - -In Propel, a _package_ represents a group of models. This is a convenient way to organize your code in a modularized way, since classes and SQL files of a given package are be grouped together and separated from the other packages. By carefully choosing the package of each model, applications end up in smaller, independent modules that are easier to manage. - -### Package Cascade ### - -The package is defined in a configuration cascade. You can set it up for the whole project, for all the tables of a schema, or for a single table. - -For the whole project, the main package is set in the `build.properties`: - -```ini -propel.targetPackage = my_project -``` - -By default, all the tables of all the schemas in the project use this package. However, you can override the package for a given `` by setting its `package` attribute: - -```xml - - -
- -
-
- - - - - -
- - -
-
-``` - -In this example, thanks to the `package` attribute, the tables are grouped into the following packages: - -* `my_project.author` package: `author` table -* `my_project.book` package: `book` and `review` tables - ->**Warning**
If you separate tables related by a foreign key into separate packages (like `book` and `author` in this example), you must enable the `packageObjectModel` build property to let Propel consider other packages for relations. - -You can also override the `package` attribute at the `` element level. - -```xml - - -
- -
- - - - - - -
- - -
-
-``` - -This ends up in the following package: - -* `my_project.author` package: `author` table -* `my_project.book` package: `book` table -* `my_project.review` package: `review` table - -Notice that tables can end up in separated packages even though they belong to the same schema file. - -_Tip_: You can use dots in a package name to add more package levels. - -### Packages and Generated Model Files ### - -The `package` attribute of a table translates to the directory in which Propel generates the Model classes for this table. - -For instance, if no `package` attribute is defined at the database of table level, Propel places all classes according to the `propel.targetPackage` from the `build.properties`: - -```text -build/ - classes/ - my_project/ - om/ - map/ - Author.php - AuthorPeer.php - AuthorQuery.php - Book.php - BookPeer.php - BookQuery.php - Review.php - ReviewPeer.php - ReviewQuery.php -``` - -You can further tweak the location where Propel puts the created files by changing the `propel.output.dir` build property. By default this property is set to: - -```ini -propel.output.dir = ${propel.project.dir}/build -``` - -You can change it to use any other directory as your build directory. - -If you set up packages for `` elements, Propel splits up the generated model classes into subdirectories named after the package attribute: - -```text -build/ - classes/ - my_project/ - author/ - om/ - map/ - Author.php - AuthorPeer.php - AuthorQuery.php - book/ - om/ - map/ - Book.php - BookPeer.php - BookQuery.php - Review.php - ReviewPeer.php - ReviewQuery.php -``` - -And of course, if you specialize the `package` attribute per table, you can have one table use its own package: - -```text -build/ - classes/ - my_project/ - author/ - om/ - map/ - Author.php - AuthorPeer.php - AuthorQuery.php - book/ - om/ - map/ - Book.php - BookPeer.php - BookQuery.php - review/ - om/ - map/ - Review.php - ReviewPeer.php - ReviewQuery.php -``` - -### Packages And SQL Files ### - -Propel also considers packages for SQL generation. In practice, Propel generates one SQL file per package. Each file contains the CREATE TABLE SQL statements necessary to create all the tables of a given package. - -So by default, all the tables end up in a single SQL file: - -```text -build/ - sql/ - schema.sql -``` - -If you specialize the `package` for each `` element, Propel uses it for SQL files: - -```text -build/ - sql/ - author.schema.sql // contains CREATE TABLE author - book.schema.sql // contains CREATE TABLE book and CREATE TABLE review -``` - -And, as you probably expect it, a package overridden at the table level also acocunts for an independent SQL file: - -```text -build/ - sql/ - author.schema.sql // contains CREATE TABLE author - book.schema.sql // contains CREATE TABLE book - review.schema.sql // contains CREATE TABLE review -``` - -## Understanding The packageObjectModel Build Property ## - -The `propel.packageObjectModel` build property enables the "packaged" build process. This modifies the build tasks behavior by joining `` elements of the same name - but keeping their packages separate. That allows to split a large schema into several files, regardless of foreign key dependencies, since Propel will join all schemas using the same database name. - -To switch this on, simply add the following line to the `build.properties` file in your project directory: - -```text -propel.packageObjectModel = true -``` - -## The Bookstore Packaged Example ## - -In the bookstore-packaged example you'll find the following schema files: - - * author.schema.xml - * book.schema.xml - * club.schema.xml - * media.schema.xml - * publisher.schema.xml - * review.schema.xml - * log.schema.xml - -Each schema file has to contain a `` tag that has its `package` attribute set to the package name where _all_ of the tables in this schema file/database belong to. - -For example, in the bookstore-packaged example the `author.schema.xml` contains the following `` tag: - -```text - -``` - -That means, that the Author OM classes will be created in a subdirectory `core/author/` of the build output directory. - -You can have more than one schema file that belong to one package. For example, in the the bookstore-packaged example both the `book.schema.xml` and `media.schema.xml` belong to the same package "core.book". The generated OM classes for these schemas will therefore end up in the same `core/book/` subdirectory. - -### The OM build ### - -To run the packaged bookstore example build simply go to the `propel/test/fixtures/bookstore-packages/` directory and type: - -```text -../../../generator/bin/propel-gen om -``` - -This should run without any complaints. When you have a look at the projects/bookstore-packaged/build/classes directory, the following directory tree should have been created: -```text -addon/ - club/ - BookClubList.php - BookClubListPeer.php - BookListRel.php - BookListRelPeer.php -core/ - author/ - Author.php - AuthorPeer.php - book/ - Book.php - BookPeer.php - - Media.php - MediaPeer.php - publisher/ - Publisher.php - PublisherPeer.php - review/ - Review.php - ReviewPeer.php -util/ - log/ - BookstoreLog.php - BookstoreLogPeer.php -``` - -(The additional subdirectories map/ and om/ in each of these directories have been omitted for clarity.) - -## The SQL build ## - -From the same schema files, run the SQL generation by calling: - -```text -../../../generator/bin/propel-gen sql -``` - -Then, have a look at the `build/sql/` directory: you will see that for each package (that is specified as a package attribute in the schema file database tags), one sql file has been created: - -* addon.club.schema.sql -* core.author.schema.sql -* core.book.schema.sql -* core.publisher.schema.sql -* core.review.schema.sql -* util.log.schema.sql - -These files contain the CREATE TABLE SQL statements necessary for each package. - -When you now run the insert-sql task by typing: - -```text -../../../generator/bin/propel-gen insert-sql -``` - -these SQL statements will be executed on a SQLite database located in the Propel/generator/test/ directory. diff --git a/cookbook/namespaces.markdown b/cookbook/namespaces.markdown deleted file mode 100644 index c5526819..00000000 --- a/cookbook/namespaces.markdown +++ /dev/null @@ -1,130 +0,0 @@ ---- -layout: documentation -title: How to Use PHP 5.3 Namespaces ---- - -# How to Use PHP 5.3 Namespaces # - -The generated model classes can use a namespace. It eases the management of large database models, and makes the Propel model classes integrate with PHP 5.3 applications in a clean way. - -## Namespace Declaration And Inheritance ## - -To define a namespace for a model class, you just need to specify it in a `namespace` attribute of the `` element for a single table, or in the `` element to set the same namespace to all the tables. - -Here is an example schema using namespaces: - -```xml - - - -
- - - - - - - - - - - - -
- - - - - - -
- - - - -
- - - - - -
- -
-``` - -The `` element defines a `namespace` attribute. The `book` and `author` tables inherit their namespace from the database, therefore the generated classes for these tables will be `\Bookstore\Book` and `\Bookstore\Author`. - -The `publisher` table defines a `namespace` attribute on its own, which _extends_ the database namespace. That means that the generated class will be `\Bookstore\Book\Publisher`. - -As for the `user` table, it defines an absolute namespace (starting with a backslash), which _overrides_ the database namespace. The generated class for the `user` table will be `Admin\User`. - ->**Tip**
You can use subnamespaces (i.e. namespaces containing backslashes) in the `namespace` attribute. - -## Using Namespaced Models ## - -Namespaced models benefit from the Propel runtime autoloading just like the other model classes. You just need to alias them, or to use their fully qualified name. - -```php -setAuthor($author); -$book->save(); -``` - -The namespace is used for the ActiveRecord class, but also for the Query and Peer classes. Just remember that when you use relation names in a query, the namespace should not appear: - -```php -useBookQuery() - ->filterByPrice(array('max' => 10)) - ->endUse() - ->findOne(); -``` - -Related tables can have different namespaces, it doesn't interfere with the functionality provided by the object model: - -```php -findOne(); -echo get_class($book->getPublisher()); -// \Bookstore\Book\Publisher -``` - ->**Tip**
Using namespaces make generated model code incompatible with versions of PHP less than 5.3. Beware that you will not be able to use your model classes in an older PHP application. - -## Using Namespaces As A Directory Structure ## - -In a schema, you can define a `package` attribute on a `` or a `` tag to generate model classes in a subdirectory (see [Multi-Component](multi-component-data-model.html)). If you use namespaces to autoload your classes based on a SplClassAutoloader (see [http://groups.google.com/group/php-standards](http://groups.google.com/group/php-standards)), then you may find yourself repeating the `namespace` data in the `package` attribute: - -```xml - -``` - -To avoid such repetitions, just set the `propel.namespace.autoPackage` setting to `true` in your `build.properties`: - -```ini -propel.namespace.autoPackage = true -``` - -Now Propel will automatically create a `package` attribute, and therefore distribute model classes in subdirectories, based on the `namespace` attribute, and you can omit the manual `package` attribute in the schema: - -```xml - -``` diff --git a/cookbook/replication.markdown b/cookbook/replication.markdown deleted file mode 100644 index 84515856..00000000 --- a/cookbook/replication.markdown +++ /dev/null @@ -1,98 +0,0 @@ ---- -layout: documentation -title: Replication ---- - -# Replication # - -Propel can be used in a master-slave replication environment. These environments are set up to improve the performance of web applications by dispatching the database-load to multiple database-servers. While a single master database is responsible for all write-queries, multiple slave-databases handle the read-queries. The slaves are synchronised with the master by a fast binary log (depending on the database). - -## Configuring Propel for Replication ## - -* Set up a replication environment (see the Databases section below) -* Use the latest Propel-Version -* add a slaves-section to your `runtime-conf.xml` file -* verify the correct setup by checking the masters log file (should not contain "select ..." statements) - -You can configure Propel to support replication by adding a `` element with nested `` element(s) to your `runtime-conf.xml`. - -The `` section is at the same level as the master `` and contains multiple nested `` elements with the same information as the top-level (master) ``. It is recommended that they are numbered. The follwing example shows a slaves section with a several slave connections configured where "localhost" is the master and "slave-server1" and "slave-server2" are the slave-database connections. - -```xml - - - - propel-bookstore - console - 7 - - - - - sqlite - - mysql:host=localhost;dbname=bookstore - testuser - password - - - - mysql:host=slave-server1; dbname=bookstore - testuser - password - - - mysql:host=slave-server2; dbname=bookstore - testuser - password - - - - - - -``` - -## Implementation ## - -The replication functionality is implemented in the Propel connection configuration and initialization code and in the generated Peer and Object classes. - -### Propel::getConnection() ### - -When requesting a connection from Propel (_Propel::getConnection()_), you can either specify that you want a READ connection (slave) or WRITE connection (master). Methods that are designed to perform READ operations, like the `doSelect*()` methods of your generated Peer classes, will always request a READ connection like so: - -```php -query('SELECT * FROM my'); -/* ... */ -``` - -### Propel::setForceMasterConnection() ### - -You can force Propel to always return a WRITE (master) connection from _Propel::getConnection()_ by calling _Propel::setForceMasterConnection(true);_. This can be useful if you must be sure that you are getting the most up-to-date data (i.e. if there is some latency possible between master and slaves). - -## Databases ## - -### MySql ### - -[http://dev.mysql.com/doc/refman/5.0/en/replication-howto.html](http://dev.mysql.com/doc/refman/5.0/en/replication-howto.html) - -## References ## - -* Henderson Carl (2006): Building Scalable Web Sites. The Flickr Way. O'Reilly. ISBN-596-10235-6. diff --git a/cookbook/runtime-introspection.markdown b/cookbook/runtime-introspection.markdown deleted file mode 100644 index 1c234b26..00000000 --- a/cookbook/runtime-introspection.markdown +++ /dev/null @@ -1,158 +0,0 @@ ---- -layout: documentation -title: Model Introspection At Runtime ---- - -# Model Introspection At Runtime # - -In addition to the object and peer classes used to do C.R.U.D. operations, Propel generates an object mapping for your tables to allow runtime introspection. - -The intospection objects are instances of the map classes. Propel maps databases, tables, columns, validators, and relations into objects that you can easily use. - -## Retrieving a TableMap ## - -The starting point for runtime introspection is usually a `TableMap`. This objects stores every possible property of a table, as defined in the `schema.xml`, but accessible at runtime. - -To retrieve a table map for a table, use the `getTableMap()` static method of the related peer class. For instance, to retrieve the table map for the `book` table, just call: - -```php -getName(); // 'table' -echo $bookTable->getPhpName(); // 'Table' -echo $bookTable->getPackage(); // 'bookstore' -echo $bookTable->isUseIdGenerator(); // true -``` - ->**Tip**
A TableMap object also references the `DatabaseMap` that contains it. From the database map, you can also retrieve other table maps using the table name or the table phpName: - -```php -getDatabaseMap(); -$authorTable = $dbMap->getTable('author'); -$authorTable = $dbMap->getTablebyPhpName('Author'); -``` - -To introspect the columns of a table, use any of the `getColumns()`, `getPrimaryKeys()`, and `getForeignKeys()` `TableMap` methods. They all return an array of `ColumnMap` objects. - -```php -getColumns(); -foreach ($bookColumns as $column) { - echo $column->getName(); -} -``` - -Alternatively, if you know a column name, you can retrieve the corresponding ColumnMap directly using the of `getColumn($name)` method. - -```php -getColumn('title'); -``` - -The `DatabaseMap` object offers a shortcut to every `ColumnMap` object if you know the fully qualified column name: -```php -getColumn('book.TITLE'); -``` - -## ColumnMaps ## - -A `ColumnMap` instance offers a lot of information about a table column. Check the following examples: - -```php -getTableName(); // 'book' -$bookTitleColumn->getTablePhpName(); // 'Book' -$bookTitleColumn->getType(); // 'VARCHAR' -$bookTitleColumn->getSize(); // 255 -$bookTitleColumn->getDefaultValue(); // null -$bookTitleColumn->isLob(); // false -$bookTitleColumn->isTemporal(); // false -$bookTitleColumn->isEpochTemporal(); // false -$bookTitleColumn->isNumeric(); // false -$bookTitleColumn->isText(); // true -$bookTitleColumn->isPrimaryKey(); // false -$bookTitleColumn->isForeignKey(); // false -$bookTitleColumn->hasValidators(); // false -$bookTitleColumn->isPrimaryString(); // true -``` - -`ColumnMap` objects also keep a reference to their parent `TableMap` object: - -```php -getTable(); -``` - -Foreign key columns give access to more information, including the related table and column: - -```php -getColumn('publisher_id'); -echo $bookPublisherIdColumn->isForeignKey(); // true -echo $bookPublisherIdColumn->getRelatedName(); // 'publisher.ID' -echo $bookPublisherIdColumn->getRelatedTableName(); // 'publisher' -echo $bookPublisherIdColumn->getRelatedColumnName(); // 'ID' -$publisherTable = $bookPublisherIdColumn->getRelatedTable(); -$publisherRelation = $bookPublisherIdColumn->getRelation(); -``` - -## RelationMaps ## - -To get an insight on all the relationships of a table, including the ones relying on a foreign key located in another table, you must use the `RelationMap` objects related to a table. - -If you know its name, you can retrieve a `RelationMap` object using `TableMap::getRelation($relationName)`. Note that the relation name is the phpName of the related table, unless the foreign key defines a phpName in the schema. For instance, the name of the `RelationMap` object related to the `book.PUBLISHER_ID` column is 'Publisher'. - -```php -getRelation('Publisher'); -``` - -alternatively, you can access a `RelationMap` from a foreign key column using `ColumnMap::getRelation()`, as follows: - -```php -getColumn('publisher_id')->getRelation(); -``` - -Once you have a `RelationMap` instance, inspect its properties using any of the following methods: - -```php -getType(); // RelationMap::MANY_TO_ONE -echo $publisherRelation->getOnDelete(); // 'SET NULL' -$bookTable = $publisherRelation->getLocalTable(); -$publisherTable = $publisherRelation->getForeignTable(); -print_r($publisherRelation->getColumnMappings()); - // array('book.PUBLISHER_ID' => 'publisher.ID') -print_r(publisherRelation->getLocalColumns()); - // array($bookPublisherIdColumn) -print_r(publisherRelation->getForeignColumns()); - // array($publisherBookIdColumn) -``` - -This also works for relationships referencing the current table: - -```php -getRelation('Review'); -echo $reviewRelation->getType(); // RelationMap::ONE_TO_MANY -echo $reviewRelation->getOnDelete(); // 'CASCADE' -$reviewTable = $reviewRelation->getLocalTable(); -$bookTable = $reviewRelation->getForeignTable(); -print_r($reviewRelation->getColumnMappings()); - // array('review.BOOK_ID' => 'book.ID') -``` - -To retrieve all the relations of a table, call `TableMap::getRelations()`. You can then iterate over an array of `RelationMap` objects. - ->**Tip**
RelationMap objects are lazy-loaded, which means that the `TableMap` will not instanciate any relation object until you call `getRelations()`. This allows the `TableMap` to remain lightweight for when you don't use relationship introspection. diff --git a/cookbook/silex/index.markdown b/cookbook/silex/index.markdown deleted file mode 100644 index e3a3f3d5..00000000 --- a/cookbook/silex/index.markdown +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: default -title: Working With Silex ---- - -# Working With Silex # - -* [Working with Silex](working-with-silex.html) diff --git a/cookbook/silex/working-with-silex.markdown b/cookbook/silex/working-with-silex.markdown deleted file mode 100644 index 53f5033a..00000000 --- a/cookbook/silex/working-with-silex.markdown +++ /dev/null @@ -1,130 +0,0 @@ ---- -layout: documentation -title: Working With Silex ---- - -# Working With Silex # - -The [PropelServiceProvider](https://github.com/propelorm/PropelServiceProvider) provides integration with [Propel](http://www.propelorm.org). - -Parameters ----------- - -* **propel.path** (optional): The path in wich Propel.php will be found. Usually, for - PEAR installation, it is `propel` while for Git installation it is - `vendor/propel/runtime/lib`. - Default is `/full/project/path/vendor/propel/runtime/lib`. - -* **propel.config_file** (optional): The name of Propel configuration file with full path. - Default is `/full/project/path/build/conf/projectname-conf.php` - -* **propel.model_path** (optional): Path to where model classes are located. - Default is `/full/project/path/build/classes` - -* **propel.internal_autoload** (optional): Setting to true, forces Propel to use - its own internal autoloader, instead of Silex one, to load model classes. - Default is `false` - - ->**Tip**
It's strongly recommanded to use absolute paths for previous options. - - -Services --------- - -No service is provided. - -Propel configures and manages itself by **using** static methods, so no service is registered into Application. -Actually, the PropelServiceProvider class initializes Propel in a more "Silex-ian" way. - - -Registering ------------ - -Make sure you place a copy of *Propel* in `vendor/propel` or install it through PEAR, or Composer. - -For more informations consult the [Propel documentation](http://www.propelorm.org/documentation/01-installation.html): - -{% highlight php %} -registerNamespaces(array( - 'Propel\Silex' => __DIR__ . '/../../vendor/propel/propel-service-provider/src', -)); - -$app->register(new Propel\Silex\PropelServiceProvider(), array( - 'propel.path' => __DIR__.'/path/to/Propel.php', - 'propel.config_file' => __DIR__.'/path/to/myproject-conf.php', - 'propel.model_path' => __DIR__.'/path/to/model/classes', -)); -{% endhighlight %} - -Alternatively, if you 've installed Propel by Git in `vendor/propel` and -you built your model with default Propel generator options: - -{% highlight php %} -register(new Propel\Silex\PropelServiceProvider()); -{% endhighlight %} - - -We can consider "default" Propel generator options: - -* Put `build.properties` and `schema.xml` files into the main directory project, -usually where file `index.php` is located. - -* In `build.properties` file, define only `propel.database`, `propel.project` -and `propel.namespace.autopackage` properties. - - -Usage ------ - -You'll have to build the model by yourself. According to Propel documentation, you'll need three files: - -* `schema.xml` which contains your database schema; - -* `build.properties` more information below; - -* `runtime-conf.xml` which contains the database configuration. - - -Use the `propel-gen` script to create all files (SQL, configuration, Model classes). - -By default, the *PropelServiceProvider* relies on the Silex autoloader you have to configure to load -model classes. Of course, the Silex autoloader needs the model to be built with namespaces, -so be sure to set this property into the `build.properties` file: - -{% highlight yaml %} -propel.namespace.autopackage = true - -The recommended configuration for your `build.properties` file is: - -propel.project = - -propel.namespace.autoPackage = true -propel.packageObjectModel = true - -# Enable full use of the DateTime class. -# Setting this to true means that getter methods for date/time/timestamp -# columns will return a DateTime object when the default format is empty. -propel.useDateTimeClass = true - -# Specify a custom DateTime subclass that you wish to have Propel use -# for temporal values. -propel.dateTimeClass = DateTime - -# These are the default formats that will be used when fetching values from -# temporal columns in Propel. You can always specify these when calling the -# methods directly, but for methods like getByName() it is nice to change -# the defaults. -# To have these methods return DateTime objects instead, you should set these -# to empty values -propel.defaultTimeStampFormat = -propel.defaultTimeFormat = -propel.defaultDateFormat = -{% endhighlight %} - -If you plan to build your model without using namespaces, you need to force Propel to use -its internal autoloader. Do this by setting the option `propel.internal_autoload` to `true`. diff --git a/cookbook/symfony1/how-to-use-Propel i18n-behavior-with-sf1.4.markdown b/cookbook/symfony1/how-to-use-Propel i18n-behavior-with-sf1.4.markdown deleted file mode 100644 index 08006856..00000000 --- a/cookbook/symfony1/how-to-use-Propel i18n-behavior-with-sf1.4.markdown +++ /dev/null @@ -1,189 +0,0 @@ ---- -layout: documentation -title: How To Use Propel i18n Behavior With Symfony 1.4 ---- - -# How To Use Propel i18n Behavior With Symfony 1.4 # - -`Propel` i18n behavior is now fully integrated with `symfony 1.4`. - -All you have to do is to write your `schema.xml` with the i18n `` tag, instead of using the old SfPropelBehaviorI18n style `
` with a `culture` column. - -First [init a `symfony` project with `Propel` as default ORM](init-a-Symfony-project-with-Propel-git-way) and let's start with this `schema.xml` - -{% highlight xml %} - - -
- - -
- - - - - - - - - - - -
- - - - - - - -
-
-{% endhighlight %} - -And those fixtures: - -{% highlight yaml %} -Author: - bach: - id: 1 - name: Richard Bach - -Book: - livingston: - id: 1 - author_id: bach - ISBN: 0-380-01286-3 - illusions: - id: 2 - author_id: bach - ISBN: 0-440-20488-7 - -BookI18n: - livingston_fr: - id: livingston - locale: fr - title: Jonathan Livingston le goéland - livingston_en: - id: livingston - locale: en - title: Jonathan Livingston Seagull - illusions_fr: - id: illusions - locale: fr - title: Le Messie récalcitrant - illusions_en: - id: illusions - locale: en - title: Jonathan Livingston Seagull -{% endhighlight %} - -Let's build this schema: - -{% highlight bash %} -php symfony propel:build --all --and-load --no-confirmation -{% endhighlight %} - -## Simple Use Of embedI18n() - -Create a book module: - -{% highlight bash %} -php symfony propel:generate-module main book Book: -{% endhighlight %} - -Add i18N to book form `lib/form/BookForm.class.php`: - -{% highlight php %} -embedI18n(array('fr','en')); - } -} -{% endhighlight %} - -Let's print the form with the i18n embedded form in `apps/main/modules/book/templates/_form.php`: - -{% highlight php %} - - - -
isMultipart() and print 'enctype="multipart/form-data" ' ?>> -getObject()->isNew()): ?> - - - - - - - - - - renderGlobalErrors() ?> - - -
- renderHiddenFields(false) ?> -  Back to list - getObject()->isNew()): ?> -  getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?> - - -
-
-{% endhighlight %} - -## Use embedI18n() In An Embedded Form - -Create an author module: - -{% highlight php %} -php symfony propel:generate-module main author Author -{% endhighlight %} - -Embed book form in author `lib/form/AuthorForm.class.php`: - -{% highlight php %} -embedRelation('Book'); - } -} -{% endhighlight %} - -Finally let's print the form with all his embedded forms in `apps/main/modules/templates/_form.php`: - -{% highlight php %} - - - -
isMultipart() and print 'enctype="multipart/form-data" ' ?>> -getObject()->isNew()): ?> - - - - - - - - - - renderGlobalErrors() ?> - - -
- renderHiddenFields(false) ?> -  Back to list - getObject()->isNew()): ?> -  getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?> - - -
-
-{% endhighlight %} \ No newline at end of file diff --git a/cookbook/symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4.markdown b/cookbook/symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4.markdown deleted file mode 100644 index 228e9930..00000000 --- a/cookbook/symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4.markdown +++ /dev/null @@ -1,195 +0,0 @@ ---- -layout: documentation -title: How To Use Old SfPropelBehaviori18n (Aka symfony_i18n) With Symfony 1.4 ---- - -# How To Use Old SfPropelBehaviori18n (Aka symfony_i18n) With Symfony 1.4 # - ->**Warning**
If you're currently starting a new project or just willing to update your `symfony` project, you should consider using the `Propel` i18n behavior integration with `symfony 1.4`. - -All you have to do is to write your `schema.xml` with the old SfPropelBehaviorI18n style `` with a `culture` column, instead of the i18n `` tag. - -First [init a `symfony` project with `Propel` as default ORM](init-a-Symfony-project-with-Propel-git-way) and let's start with this `schema.xml`: - -{% highlight xml %} - - -
- - -
- - - - - - - -
- - - - - - - - -
-
-{% endhighlight %} - -And those fixtures: - -{% highlight yaml %} -Author: - bach: - id: 1 - name: Richard Bach - -Book: - livingston: - id: 1 - author_id: bach - ISBN: 0-380-01286-3 - illusions: - id: 2 - author_id: bach - ISBN: 0-440-20488-7 - -BookI18n: - livingston_fr: - id: livingston - culture: fr - title: Jonathan Livingston le goéland - livingston_en: - id: livingston - culture: en - title: Jonathan Livingston Seagull - illusions_fr: - id: illusions - culture: fr - title: Le Messie récalcitrant - illusions_en: - id: illusions - culture: en - title: Jonathan Livingston Seagull -{% endhighlight %} - -Let's build this schema: - -{% highlight bash %} -php symfony propel:build --all --and-load --no-confirmation -{% endhighlight %} - -## Simple Use Of embedI18n() - -Create a book module: - -{% highlight bash %} -php symfony propel:generate-module main book Book -{% endhighlight %} - -Add i18N to book form `lib/form/BookForm.class.php`: - -{% highlight php %} -embedI18n(array('fr','en')); - } -} -{% endhighlight %} - -Let's print the form with the i18n embedded forms in `apps/main/modules/book/templates/_form.php`: - -{% highlight php %} - - - -
isMultipart() and print 'enctype="multipart/form-data" ' ?>> -getObject()->isNew()): ?> - - - - - - - - - - renderGlobalErrors() ?> - - -
- renderHiddenFields(false) ?> -  Back to list - getObject()->isNew()): ?> -  getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?> - - -
-
-{% endhighlight %} - -## Use embedI18n() In An Embedded Form - -Create an author module: - -{% highlight bash %} -php symfony propel:generate-module main author Author -{% endhighlight %} - -Embed book form in author `lib/form/AuthorForm.class.php`: - -{% highlight php %} -embedRelation('Book'); - } -} -{% endhighlight %} - -Finally let's print the form with all his embedded forms in `apps/main/modules/templates/_form.php`: - -{% highlight php %} - - - -
isMultipart() and print 'enctype="multipart/form-data" ' ?>> -getObject()->isNew()): ?> - - - - - - - - - - renderGlobalErrors() ?> - - -
- renderHiddenFields(false) ?> -  Back to list - getObject()->isNew()): ?> -  getObject()->getId(), array('method' => 'delete', 'confirm' => 'Are you sure?')) ?> - - -
-
-{% endhighlight %} - -As a bonus you can use special joinWithI18n() query even if it's not native (thanks to javer). - ->**Warning**
Remember you should consider using the `Propel` i18n behavior integration with `symfony 1.4`. - - - - - diff --git a/cookbook/symfony1/index.markdown b/cookbook/symfony1/index.markdown deleted file mode 100644 index eff28efe..00000000 --- a/cookbook/symfony1/index.markdown +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: default -title: Working With symfony 1.4 ---- - -# Working with symfony 1.4 # - -* [Init A Symfony Project With Propel As Default ORM - The Git Way](init-a-Symfony-project-with-Propel-git-way.html) - -* [How To Use Propel i18n Behavior With Symfony 1.4](how-to-use-Propel i18n-behavior-with-sf1.4.html) - -* [How To Use Old SfPropelBehaviori18n (Aka symfony_i18n) With Symfony 1.4](how-to-use-old-SfPropelBehaviori18n-with-sf1.4.html) diff --git a/cookbook/symfony1/init-a-Symfony-project-with-Propel-git-way.markdown b/cookbook/symfony1/init-a-Symfony-project-with-Propel-git-way.markdown deleted file mode 100644 index 3b582e86..00000000 --- a/cookbook/symfony1/init-a-Symfony-project-with-Propel-git-way.markdown +++ /dev/null @@ -1,144 +0,0 @@ ---- -layout: default -title: Init A Symfony Project With Propel As Default ORM - The Git Way ---- - -# Init A Symfony Project With Propel As Default ORM - The Git Way # - -Since this summer (2011) `Propel` ORM has a new `symfony` integration plugin `sfPropelORMPlugin` replacing the old one `sfPropel15Plugin`. - -The old `sfPropel15Plugin` caused [some confusion at each new Propel's version](http://propel.posterous.com/sfpropel16plugin-is-already-there-didnt-you-k). -Now `sfPropelORMPlugin` will always integrate the last `Propel`'s version to `Symfony 1.4`. - -You'll learn how to set up a new `symfony 1.4` project with all necessary libraries as git submodules. - -First, set up a new project: - -{% highlight bash %} -mkdir propel_project -cd propel_project -git init -{% endhighlight %} - -Install `symfony 1.4` as a git submodule: - -{% highlight bash %} -git submodule add git://github.com/symfony/symfony1.git lib/vendor/symfony -{% endhighlight %} - -Generate a symfony project: - -{% highlight bash %} -php lib/vendor/symfony/data/bin/symfony generate:project propel -{% endhighlight %} - -Add the `sfPropelORMPlugin` plugin: - -{% highlight bash %} -git submodule add git://github.com/propelorm/sfPropelORMPlugin plugins/sfPropelORMPlugin -{% endhighlight %} - -Get Propel and Phing bundled with the plugin: - -{% highlight bash %} -cd plugins/sfPropelORMPlugin -git submodule update --init -{% endhighlight %} - -You should add a `.gitignore` file with the following content: - -{% highlight bash %} -config/databases.yml -cache/* -log/* -data/sql/* -lib/filter/base/* -lib/form/base/* -lib/model/map/* -lib/model/om/* -{% endhighlight %} - -Now, enable `sfPropelORMPlugin` in `config/ProjectConfiguration.class.php`: - -{% highlight php %} -enablePlugins('sfPropelORMPlugin'); - } -} -{% endhighlight %} - -Publish assets: - -{% highlight bash %} -php symfony plugin:publish-assets -{% endhighlight %} - -Copy the `propel.ini` default file in your project: - -{% highlight bash %} -cp plugins/sfPropelORMPlugin/config/skeleton/config/propel.ini config/propel.ini -{% endhighlight %} - -Verify behaviors lines look like: - -{% highlight ini %} -// config/propel.ini - -propel.behavior.symfony.class = plugins.sfPropelORMPlugin.lib.behavior.SfPropelBehaviorSymfony -propel.behavior.symfony_i18n.class = plugins.sfPropelORMPlugin.lib.behavior.SfPropelBehaviorI18n -propel.behavior.symfony_i18n_translation.class = plugins.sfPropelORMPlugin.lib.behavior.SfPropelBehaviorI18nTranslation -propel.behavior.symfony_behaviors.class = plugins.sfPropelORMPlugin.lib.behavior.SfPropelBehaviorSymfonyBehaviors -propel.behavior.symfony_timestampable.class = plugins.sfPropelORMPlugin.lib.behavior.SfPropelBehaviorTimestampable -{% endhighlight %} - -Adapt your `databases.yml` or copy the model in your project: - -{% highlight bash %} -cp plugins/sfPropelORMPlugin/config/skeleton/config/databases.yml config/databases.yml -{% endhighlight %} - -It has to look like this: - -{% highlight yaml %} -# You can find more information about this file on the symfony website: -# http://www.symfony-project.org/reference/1_4/en/07-Databases - -dev: - propel: - param: - classname: DebugPDO - debug: - realmemoryusage: true - details: - time: { enabled: true } - slow: { enabled: true, threshold: 0.1 } - mem: { enabled: true } - mempeak: { enabled: true } - memdelta: { enabled: true } - -test: - propel: - param: - classname: DebugPDO - -all: - propel: - class: sfPropelDatabase - param: - classname: PropelPDO - dsn: mysql:dbname=test;host=localhost - username: root - password: - encoding: utf8 - persistent: true - pooling: true -{% endhighlight %} - ->**Warning**
If your PHP version is under 5.3.6 you won't be allowed to set the `encoding` parameter due to a security issue in PHP. - -You're now ready for writing a `schema.xml` and building your project. \ No newline at end of file diff --git a/cookbook/symfony2/adding-a-new-behavior-in-symfony2.markdown b/cookbook/symfony2/adding-a-new-behavior-in-symfony2.markdown deleted file mode 100644 index d65666a1..00000000 --- a/cookbook/symfony2/adding-a-new-behavior-in-symfony2.markdown +++ /dev/null @@ -1,44 +0,0 @@ ---- -layout: default -title: Adding A New Behavior In Symfony2 ---- - -# Adding A New Behavior In Symfony2 # - -Propel provides a lot of [behaviors](../../documentation/07-behaviors.html) but there are also [third-party behaviors](../user-contributed-behaviors.html) provided by the community. - -In order to get these behaviors working in a Symfony2 application with Propel, you need to register them, here is -how you should do. - -The first step is to get the code. If the behavior is available on a Git repository, like GitHub for instance, -then you'll be able to add it to your `deps` file. -Assuming you want to use the [GeocodableBehavior](https://github.com/willdurand/GeocodableBehavior), you'll write: - -{% highlight bash %} -[GeocodableBehavior] - git=http://github.com/willdurand/GeocodableBehavior.git - target=/willdurand/propel-geocodable-behavior -{% endhighlight %} - -If you are using Git submodules, then run: - -{% highlight bash %} -git submodule add http://github.com/willdurand/GeocodableBehavior.git vendor/willdurand/propel-geocodable-behavior -{% endhighlight %} - -If you are using [Composer](http://getcomposer.org), then just require the behavior in your `composer.json`: - -{% highlight javascript %} -{ - "require": { - // ... - "willdurand/propel-geocodable-behavior": "dev-master" - } -} -{% endhighlight %} - -And run, `php composer.php update`. - ->**Tip**
If there is no available Git repository for a behavior, just copy it to `vendor/propel-thebehavior-behavior`. It's up to you to version it or not. - -You're done! diff --git a/cookbook/symfony2/images/basic_form.png b/cookbook/symfony2/images/basic_form.png deleted file mode 100644 index 6b723643..00000000 Binary files a/cookbook/symfony2/images/basic_form.png and /dev/null differ diff --git a/cookbook/symfony2/images/many_to_many_form.png b/cookbook/symfony2/images/many_to_many_form.png deleted file mode 100644 index bdd8f69e..00000000 Binary files a/cookbook/symfony2/images/many_to_many_form.png and /dev/null differ diff --git a/cookbook/symfony2/images/many_to_many_form_with_existing_objects.png b/cookbook/symfony2/images/many_to_many_form_with_existing_objects.png deleted file mode 100644 index 65581303..00000000 Binary files a/cookbook/symfony2/images/many_to_many_form_with_existing_objects.png and /dev/null differ diff --git a/cookbook/symfony2/images/one_to_many_form.png b/cookbook/symfony2/images/one_to_many_form.png deleted file mode 100644 index 3d0f7fb6..00000000 Binary files a/cookbook/symfony2/images/one_to_many_form.png and /dev/null differ diff --git a/cookbook/symfony2/images/one_to_many_form_with_collection.png b/cookbook/symfony2/images/one_to_many_form_with_collection.png deleted file mode 100644 index f316cd05..00000000 Binary files a/cookbook/symfony2/images/one_to_many_form_with_collection.png and /dev/null differ diff --git a/cookbook/symfony2/index.markdown b/cookbook/symfony2/index.markdown deleted file mode 100644 index cc46b7b8..00000000 --- a/cookbook/symfony2/index.markdown +++ /dev/null @@ -1,18 +0,0 @@ ---- -layout: default -title: Working With Symfony2 ---- - -# Working With Symfony2 # - -* [Working with Symfony2 (Introduction)](working-with-symfony2.html) - -* [Symfony2 And Propel In Real Life](symfony2-and-propel-in-real-life.html) - -* [Mastering Symfony2 Forms With Propel](mastering-symfony2-forms-with-propel.html) - -* [The Symfony2 Security Component And Propel](the-symfony2-security-component-and-propel.html) - -* [Adding A New Behavior In Symfony2](adding-a-new-behavior-in-symfony2.html) - -* [Testing](testing.html) diff --git a/cookbook/symfony2/mastering-symfony2-forms-with-propel.markdown b/cookbook/symfony2/mastering-symfony2-forms-with-propel.markdown deleted file mode 100644 index 9c40d467..00000000 --- a/cookbook/symfony2/mastering-symfony2-forms-with-propel.markdown +++ /dev/null @@ -1,535 +0,0 @@ ---- -layout: documentation -title: Mastering Symfony2 Forms With Propel ---- - -# Mastering Symfony2 Forms With Propel # - -In this chapter, you'll learn how to master Symfony2 forms with Propel. - ->**Code along with the example**
If you want to follow along with the example in this chapter, create a `LibraryBundle` bundle by using this command: `php app/console generate:bundle --namespace=Acme/LibraryBundle`. - -Assuming you manage `Book` and `Author` objects, you'll define the following `schema.xml`: - -{% highlight xml %} - - - - - - - - - - -
- - - - -
-
-{% endhighlight %} - -In Symfony2, you deal with `Type` so let's create a `BookType` to manage -our books. For the moment, just ignore the relation with `Author` objects. - - ->**Quickly generate a `Type` with Symfony2**
-If you want a `Type` generated from a `Model`, you can use the Symfony2 console. -For the following example, the command is: -`php app/console propel:form:generate @AcmeLibraryBundle Book` - -{% highlight php %} -add('title'); - $builder->add('isbn'); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\LibraryBundle\Model\Book', - )); - } - - public function getName() - { - return 'book'; - } -} -{% endhighlight %} - ->**Setting the `data_class`**
Every form needs to know the name of the class that holds the underlying data (e.g. `Acme\LibraryBundle\Model\Book`). Usually, this is just guessed based off of the object passed to the second argument to createForm(). - -Basically, you will use this class in an action of one of your controllers. -Assuming you have a `BookController` controller in your `LibraryBundle`, you will -write the following code to create new books: - -{% highlight php %} -createForm(new BookType(), $book); - - return $this->render('AcmeLibraryBundle:Book:new.html.twig', array( - 'form' => $form->createView(), - )); - } -} -{% endhighlight %} - ->**Warning**
To quickly explain how forms are rendered, the controller above extends the `Controller` class which provides the `render()` method used to return a `Response` but this is not considered as a best practice. It's better to create a controller as a service. - -To render the form, you'll need to create a Twig template like below: - -{% highlight django %} -{# src/Acme/LibraryBundle/Resources/views/Book/new.html.twig #} - -
- {{ "{{ form_widget(form)" }} }} - - -
-{% endhighlight %} - -You'll get this result: - -![](./images/basic_form.png) - -As such, the topic of persisting the `Book` object to the database is entirely -unrelated to the topic of forms. But, if you've created a `Book` class with Propel, -then persisting it after a form submission can be done when the form is valid: - -{% highlight php %} -createForm(new BookType(), $book); - - $request = $this->getRequest(); - - if ('POST' === $request->getMethod()) { - $form->handleRequest($request); - - if ($form->isValid()) { - $book->save(); - - return $this->redirect($this->generateUrl('book_success')); - } - } - - return $this->render('AcmeLibraryBundle:Book:new.html.twig', array( - 'form' => $form->createView(), - )); - } -} -{% endhighlight %} - ->**Note**
`handleRequest` has been introduced in Symfony 2.3. Be sure to use -`bindRequest` instead in previous versions of Symfony. - -If, for some reason, you don't have access to your original `$book` object, -you can fetch it from the form: - -{% highlight php %} -getData(); -{% endhighlight %} - -As you can see, this is really easy to manage basic forms with both Symfony2 -and Propel. But, in real life, this kind of forms is not enought and you'll probably -manage objects with relations, this is the next part of this chapter. - - -## One-To-Many relations ## - -A `Book` has an `Author`, this is a **One-To-Many** relation. Let's modifing your -`BookType` to handle this relation: - -{% highlight php %} -add('title'); - $builder->add('isbn'); - // Author relation - $builder->add('author', new AuthorType()); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\LibraryBundle\Model\Book' - )); - } - - public function getName() - { - return 'book'; - } -} -{% endhighlight %} - -You now have to write an `AuthorType` to reflect the new requirements: - -{% highlight php %} -add('first_name'); - $builder->add('last_name'); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\LibraryBundle\Model\Author', - ); - } - - public function getName() - { - return 'author'; - } -} -{% endhighlight %} - -If you refresh your page, you'll now get the following result: - -![](./images/one_to_many_form.png) - -When the user submits the form, the submitted data for the `Author` fields are used to construct an -instance of `Author`, which is then set on the author field of the `Book` instance. -The `Author` instance is accessible naturally via $book->getAuthor(). - -But you could have the following use case: to add books to an author. The main type will be the `AuthorType` as below: - -{% highlight php %} -add('first_name'); - $builder->add('last_name'); - $builder->add('books', 'collection', array( - 'type' => new \Acme\LibraryBundle\Form\Type\BookType(), - 'allow_add' => true, - 'allow_delete' => true, - 'by_reference' => false, - )); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\LibraryBundle\Model\Author', - ); - } - - public function getName() - { - return 'author'; - } -} -{% endhighlight %} - -You'll also need to refactor your `BookType`: - -{% highlight php %} -add('title'); - $builder->add('isbn'); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\LibraryBundle\Model\Book', - ); - } - - public function getName() - { - return 'book'; - } -} -{% endhighlight %} - -When you'll create a new `Author` object, you'll be able to add a set of new `Books` objects and they will be -linked to this author without any effort thanks to Propel and specific methods to handle collections on related objects. - -![](./images/one_to_many_form_with_collection.png) - - -## Many-To-Many relations ## - -Now, imagine you want to add your books to some lists for book clubs. A `BookClubList` can have many -`Book` objects and a `Book` can be in many lists (`BookClubList`). This is a **Many-To-Many** relation. - -Add the following defintion to your `schema.xml ` and rebuild your model classes: - -{% highlight xml %} - - - - - -
- - - - - - - - - -
-{% endhighlight %} - -You now have `BookClubList` and `BookListRel` objects. Let's create a `BookClubListType`: - -{% highlight php %} -add('group_leader'); - $builder->add('theme'); - // Book collection - $builder->add('books', 'collection', array( - 'type' => new \Acme\LibraryBundle\Form\Type\BookType(), - 'allow_add' => true, - 'allow_delete' => true, - 'by_reference' => false - )); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\LibraryBundle\Model\BookClubList', - ); - } - - public function getName() - { - return 'book_club_list'; - } -} -{% endhighlight %} - -You've added a `CollectionType` for the `Book` list and you've configured it -with your `BookType`. In this example, you allow to add and/or delete books. - ->**Warning**
The parameter `by_reference` has to be defined and set to `false`. This is required to tell the Form Component to call the setter method (`setBooks()` in this example). - -Thanks to the smart collection setter provided by Propel, there is nothing more to configure. -Use the `BookClubListType` as you previously did with the `BookType`. Note the Symfony2 Form Component -doesn't handle the add/remove abilities in the view. You have to write some JavaScript for that. - -![](./images/many_to_many_form.png) - -### The ModelType ### - -In the previous example, you always create new objects. - -If you want to select existing authors when you create new books, you'll have to - use a `Model` type. -You can change the text wich be displayed by passing the `property` argument. If - left blank, the `__toString()` method will be used. - -{% highlight php %} -add('title'); - $builder->add('isbn'); - - //$builder->add('author', new AuthorType()); - $builder->add('author', 'model', array( - 'class' => 'Acme\LibraryBundle\Model\Author', - 'property' => 'fullname', - )); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\LibraryBundle\Model\Book', - ); - } - - public function getName() - { - return 'book'; - } -} -{% endhighlight %} - -You'll obtain the following result: - -![](./images/many_to_many_form_with_existing_objects.png) - ->**Information**
The `ModelType` is part of the [`Symfony Propel Bridge`](https://github.com/symfony/symfony/blob/master/src/Symfony/Bridge/Propel1/). - -## Validation ## - -Using Propel in a Symfony2 project lacks a convenient way to use validation through annotation. -Instead you have to use classic validation process, using a validation (in yml, xml or php format) file. - -In order to use this type of validation you must configure your application. - -In YAML: - -{% highlight yaml %} -# in app/config/config.yml -framework: - validation: { enabled: true } -{% endhighlight %} - -In XML: - -{% highlight xml %} - - - - -{% endhighlight %} - -In PHP: - -{% highlight php %} -// in app/config/config.php -$container->loadFromExtension('framework', array('validation' => array( - 'enabled' => true, -))); -{% endhighlight %} - -Now just follow the official documentation [`about validation`](http://symfony.com/doc/current/book/validation.html) to know how to create your validation file. - - -### Introducing the UniqueObject constraint ### - -As Doctrine has his `UniqueEntity` constraint, Propel has its `UniqueObject` constraint. -The use of this constraint is similar to the use of the UniqueEntity. - - -In a form, if you want to validate the unicity of a field in a table you have to use the UniqueObject constraint. -To use it is in a `validation.yml` file just add those few lines in your validation file: - -{% highlight yaml %} -BundleNamespace\Model\User: - constraints: - - Propel\PropelBundle\Validator\Constraints\UniqueObject: - fields: username - message: User already exists ({{ fields }}). -{% endhighlight %} - -In order to validate the unicity of more than just one fields: - -{% highlight yaml %} -BundleNamespace\Model\User: - constraints: - - Propel\PropelBundle\Validator\Constraints\UniqueObject: - fields: [username, login] -{% endhighlight %} - -As many validator of this type as you want can be used. - -## Summary ## - -The Symfony2 Form Component doesn't have anymore secrets for you and to use it with Propel is really -easy. diff --git a/cookbook/symfony2/symfony2-and-propel-in-real-life.markdown b/cookbook/symfony2/symfony2-and-propel-in-real-life.markdown deleted file mode 100644 index 4711cd85..00000000 --- a/cookbook/symfony2/symfony2-and-propel-in-real-life.markdown +++ /dev/null @@ -1,8 +0,0 @@ ---- -layout: documentation -title: Symfony2 And Propel In Real Life ---- - -# Symfony2 And Propel In Real Life # - -If you want more information on how to work with Propel and Symfony2, this document is now available on [the Symfony2 official documentation](http://symfony.com/doc/master/book/propel.html). \ No newline at end of file diff --git a/cookbook/symfony2/testing.markdown b/cookbook/symfony2/testing.markdown deleted file mode 100644 index f3371135..00000000 --- a/cookbook/symfony2/testing.markdown +++ /dev/null @@ -1,201 +0,0 @@ ---- -layout: documentation -title: Testing ---- - -# Testing # - -To build better and more reliable applications, you should test your code using -both functional and unit tests. First, read the [Testing chapter]( -http://symfony.com/doc/current/book/testing.html) on the Symfony2 documentation. -It explains everything you need to know to write tests for your Symfony2 -application. However, it doesn't explain how to configure Propel, or how to use -it in your test classes. - -This recipe introduces Propel in the wonderful testing world. -If you are reading it, you are probably interested in **functional tests** as -relying on a database means you write functional tests, not unit tests. - -Symfony2 provides a -[WebTestCase](https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/Test/WebTestCase.php) -which provides great features for your functional test classes. This is the -class you need when you want to do black box testing. Then again, this is -explained in the [Testing chapter - Functional -tests](http://symfony.com/doc/current/book/testing.html#functional-tests). -Moreover, Symfony2 comes with multiple environments, like `dev`, `prod` but -also `test`. The Symfony2 Client, detailled in the section [Working with the -Test Client](http://symfony.com/doc/current/book/testing.html#working-with-the-test-client) -in the Symfony2 documentation, relies on this `test` environment. - - -## The Test Environment ## - -The `config_test.yml` file is where you have to put specific configuration for -testing purpose. For example, you can setup a new database for your tests like -`yourdatabase_test`: - -{% highlight yaml %} -# app/config/config_test.yml -propel: - dbal: - dsn: %database_driver%:host=%database_host%;dbname=%database_name%_test;charset=UTF8 -{% endhighlight %} - -You can also configure a `SQLite` connection instead of your production database -vendor (`MySQL`, `PostgreSQL`, etc.). It's a good idea to use a different -database vendor to ensure your application is not tied to a specific database, -however sometimes it's not possible. - -As you may know, Propel uses smart code generation depending on the database -vendor, and so on. You need to build both SQL and PHP code before to run your -tests. It's doable by running a single command line, but it's not optimal. - - -## The Propel WebTestCase ## - -A good idea is to extend the `WebTestCase` class in your application, and to add -a few methods to run commands for you: - -{% highlight php %} -getKernel()); - self::$application->setAutoExit(false); - } - - return self::$application; - } - - protected static function runCommand($command) - { - $command = sprintf('%s --quiet', $command); - - return self::getApplication()->run(new \Symfony\Component\Console\Input\StringInput($command)); - } -} -{% endhighlight %} - -Basically, for each test class, it will build everthing before to execute test -methods. By using this class, you just need to run `phpunit` in your project to -run all tests, including functional tests that rely on a database. In other -words, it's setup your application in `test` environment, just like you would do -in production. - -{% highlight php%} -boot(); - - self::$application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel); - self::$application->setAutoExit(false); - } - - return self::$application; - } - - protected static function runCommand($command) - { - $command = sprintf('%s --quiet', $command); - - return self::getApplication()->run(new \Symfony\Component\Console\Input\StringInput($command)); - } -} -{% endhighlight %} - -Having both `WebTestCase` and `TestCase` classes allow you to write Propel aware -tests in your application. You don't need anything else. You can read the [PHPUnit -documentation](http://www.phpunit.de/manual/current/en/) for more information on -assertions. - - -## Travis-CI ## - -Once you have a decent test suite, you may want to use -[Travis-CI](http://travis-ci.org). Here is a standard configuration for Symfony2 -projects: - -{% highlight yaml %} -language: php - -php: - - 5.3 - - 5.4 - -before_script: - - curl -s http://getcomposer.org/installer | php -- --quiet - - php composer.phar install - - php app/console propel:database:create --env=test - -script: phpunit -{% endhighlight %} diff --git a/cookbook/symfony2/the-symfony2-security-component-and-propel.markdown b/cookbook/symfony2/the-symfony2-security-component-and-propel.markdown deleted file mode 100644 index b20b6084..00000000 --- a/cookbook/symfony2/the-symfony2-security-component-and-propel.markdown +++ /dev/null @@ -1,73 +0,0 @@ ---- -layout: documentation -title: The Symfony2 Security Component And Propel ---- - -# The Symfony2 Security Component And Propel # - ->**Warning:**
This documentation is for Symfony2 2.1. If you are using Symfony2 2.0.x, please follow [this documentation](https://github.com/propelorm/propelorm.github.com/blob/d1dbefdd6346f36e72d3349934ef2768ebe1a0c8/cookbook/symfony2/the-symfony2-security-component-and-propel.markdown). - -If you've started to play with the awesome Symfony2 Security Component, you'll know that you can configure a **provider** -to retrieve your users. Symfony2, and the PropelBundle provide a `propel` provider, so you just need to add the following -configuration to your `security.yml` file: - -{% highlight yaml %} -# app/config/security.yml -providers: - main: - propel: - class: My\AwesomeBundle\Model\User - property: username -{% endhighlight %} - -Your `User` class have to implement the [`UserInterface`](https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Security/Core/User/UserInterface.php): - -{% highlight php %} -**Tip**
Different bundle version may be needed for different Symfony2 version. Check [PropelBundle](https://github.com/propelorm/PropelBundle) for details. - -Alternatively, you can use Git, SVN, Git submodules, or the Symfony vendor management (deps file): - -Clone this bundle in the `vendor/bundles/Propel` directory: - - > git submodule add https://github.com/propelorm/PropelBundle.git vendor/bundles/Propel/PropelBundle - -Checkout Propel and Phing in the `vendor` directory: - - > svn checkout http://svn.github.com/propelorm/Propel.git vendor/propel - - > svn checkout http://svn.phing.info/tags/2.4.6/ vendor/phing - -Instead of using svn, you can clone the unofficial Git repositories: - - > git submodule add https://github.com/phingofficial/phing.git vendor/phing - - > git submodule add https://github.com/propelorm/Propel.git vendor/propel - -Instead of doing this manually, you can use the Symfony vendor management via the deps file. -If you are using a Symfony2 2.x.x version (actually, a version which is not 2.1 or above), -be sure to deps.lock the PropelBundle to a commit on the 2.0 branch, which does not use the Bridge - - -The second step is to register this bundle in the `AppKernel` class: - -{% highlight php %} -registerNamespaces(array( - // ... - 'Propel' => __DIR__.'/../vendor/bundles', -)); -$loader->registerPrefixes(array( - // ... - 'Phing' => __DIR__.'/../vendor/phing/classes/phing', -)); -{% endhighlight %} - -You are almost ready, the next steps are: - -* to [configure the bundle](#configuration); -* to [configure Propel](#propel-configuration); -* to [write an XML schema](#xml-schema). - -Now, you can build your model classes, and SQL by running the following command: - - > php app/console propel:build [--classes] [--sql] [--insert-sql] - -To insert SQL statements, use the `propel:sql:insert` command: - - > php app/console propel:sql:insert [--force] - -Note that the `--force` option is needed to actually execute the SQL statements. - -Congratulations! You're done; just use the Model classes as any other class in Symfony2: - -{% highlight php %} -setFirstName($name); - $author->save(); - - return $this->render('AcmeDemoBundle:Hello:index.html.twig', array( - 'name' => $name, 'author' => $author) - ); - } -} -{% endhighlight %} - -## Bundle Inheritance ## - -The `PropelBundle` makes use of the bundle inheritance. Currently only schema inheritance is provided. - -### Schema Inheritance ### - -You can override the defined schema of a bundle from within its child bundle. -To make use of the inheritance you only need to drop a schema file in the `Resources/config` folder of the child bundle. - -Each file can be overridden without interfering with other schema files. -If you want to remove parts of a schema, you only need to add an empty schema file. - -## Configuration ## - -### Symfony configuration ### - -In order to use Propel, you have to configure few parameters in your `app/config/config.yml` file. - -If you are **not** using Composer, add this configuration: - -{% highlight yaml %} -# in app/config/config.yml -propel: - path: "%kernel.root_dir%/../vendor/propel" - phing_path: "%kernel.root_dir%/../vendor/phing" -{% endhighlight %} - -Now, you can configure your application. - - -#### Basic Configuration #### - -If you have just one database connection, your configuration will look like as following: - -{% highlight yaml %} -# app/config/config*.yml -propel: - dbal: - driver: mysql - user: root - password: null - dsn: mysql:host=localhost;dbname=test;charset=UTF8 - options: {} - attributes: {} -{% endhighlight %} - -The recommended way to fill in these information is to use parameters: - - -{% highlight yaml %} -# app/config/config*.yml -# define the parameters in app/config/parameters.yml -propel: - dbal: - driver: %database_driver% - user: %database_user% - password: %database_password% - dsn: %database_driver%:host=%database_host%;dbname=%database_name%;charset=UTF8 - options: {} - attributes: {} -{% endhighlight %} - - -#### Configure Multiple Connection #### - -If you have more than one connection, or want to use a named connection, the configuration -will look like: - -{% highlight yaml %} -# app/config/config*.yml -propel: - dbal: - default_connection: conn1 - connections: - conn1: - driver: mysql - user: root - password: null - dsn: mysql:host=localhost;dbname=db1 - conn2: - driver: mysql - user: root - password: null - dsn: mysql:host=localhost;dbname=db2 -{% endhighlight %} - - -#### Configure Master/Slaves #### - -You can also configure Master/Slaves: - -{% highlight yaml %} -# app/config/config*.yml -propel: - dbal: - default_connection: default - connections: - default: - driver: mysql - user: root - password: null - dsn: mysql:host=localhost;dbname=master - slaves: - slave_1: - user: root - password: null - dsn: mysql:host=localhost;dbname=slave_1 -{% endhighlight %} - - -#### Attributes, Options, Settings #### - -{% highlight yaml %} -# app/config/config*.yml -propel: - dbal: - default_connection: default - connections: - default: - # ... - options: - ATTR_PERSISTENT: false - attributes: - ATTR_EMULATE_PREPARES: true - settings: - charset: { value: UTF8 } - queries: { query: 'INSERT INTO BAR ('hey', 'there')' } -{% endhighlight %} - -`options`, `attributes` and `settings` are parts of the runtime configuration. See [Runtime Configuration File](http://www.propelorm.org/reference/runtime-configuration.html) documentation for more explanation. - - -#### Logging #### - -You can disable the logging by changing the `logging` parameter value: - -{% highlight yaml %} -# in app/config/config.yml -propel: - logging: %kernel.debug% -{% endhighlight %} - -### Propel Configuration ### - -You can add a `app/config/propel.ini` file in your project to specify some -configuration parameters. See the [Build properties Reference]( -http://www.propelorm.org/reference/buildtime-configuration.html) to get more -information. However, **the recommended way** to configure Propel is to rely -on **build properties**, see the section below. - -By default the PropelBundle is configured with the default parameters: - -{% highlight ini %} -# Enable full use of the DateTime class. -# Setting this to true means that getter methods for date/time/timestamp -# columns will return a DateTime object when the default format is empty. -propel.useDateTimeClass = true - -# Specify a custom DateTime subclass that you wish to have Propel use -# for temporal values. -propel.dateTimeClass = DateTime - -# These are the default formats that will be used when fetching values from -# temporal columns in Propel. You can always specify these when calling the -# methods directly, but for methods like getByName() it is nice to change -# the defaults. -# To have these methods return DateTime objects instead, you should set these -# to empty values -propel.defaultTimeStampFormat = -propel.defaultTimeFormat = -propel.defaultDateFormat = - -# A better Pluralizer -propel.builder.pluralizer.class = builder.util.StandardEnglishPluralizer -{% endhighlight %} - - -#### Build properties #### - -You can define _build properties_ by creating a `propel.ini` file in `app/config` like below, but you can also follow -the Symfony2 convention by adding build properties in `app/config/config.yml`: - -{% highlight yaml %} -# app/config/config.yml -propel: - build_properties: - xxxxx.xxxx.xxxxx: XXXX - xxxxx.xxxx.xxxxx: XXXX - // ... -{% endhighlight %} - - -#### Behaviors #### - -You can register Propel behaviors using the following syntax: - -{% highlight yaml %} -# app/config/config.yml -propel: - behaviors: - behavior_name: My\Bundle\Behavior\BehaviorClassName -{% endhighlight %} - -If you rely on third party behaviors, most of them are autoloaded so you don't -need to register them. But, for your own behaviors, you can either configure the -autoloader to autoload them, or register them in this section (this is the -recommended way when you namespace your behaviors). - -## XML Schema ## - -Place the following schema in `src/Acme/DemoBundle/Resources/config/schema.xml`: - -{% highlight xml %} - - - - - - - - - - - -
- - - - - -
- -
-{% endhighlight %} - -If you are working with an existing database, please [check the related section](#working-with-existing-databases). - -## The Commands ## - -The PropelBundle provides a lot of commands to manage migrations, database/table manipulations, -and so on. - - -### Database Manipulations ### - -You can create a **database**: - - > php app/console propel:database:create [--connection[=""]] - -As usual, `--connection` allows you to specify a connection. - - -You can drop a **database**: - - > php app/console propel:database:drop [--connection[=""]] [--force] - -As usual, `--connection` allows you to specify a connection. - -Note that the `--force` option is needed to actually execute the SQL statements. - - -### Form Types ### - -You can generate stub classes based on your `schema.xml` in a given bundle: - - > php app/console propel:form:generate [-f|--force] bundle [models1] ... [modelsN] - -It will write Form Type classes in `src/YourVendor/YourBundle/Form/Type`. - -You can choose which Form Type to build by specifing Model names: - - > php app/console propel:form:generate @AcmeDemoBundle Book Author - - -### Graphviz ### - -You can generate **Graphviz** file for your project by using the following command line: - - > php app/console propel:graphviz:generate - -It will write files in `app/propel/graph/`. - - -### Migrations ### - -Generates SQL diff between the XML schemas and the current database structure: - - > php app/console propel:migration:generate-diff - -Executes the migrations: - - > php app/console propel:migration:migrate - -Executes the next migration up: - - > php app/console propel:migration:migrate --up - -Executes the previous migration down: - - > php app/console propel:migration:migrate --down - -Lists the migrations yet to be executed: - - > php app/console propel:migration:status - - -### Table Manipulations ### - -You can drop one or several **tables**: - - > php app/console propel:table:drop [--force] [--connection[="..."]] [table1] ... [tableN] - -As usual, `--connection` allows to specify a connection. - -The table arguments define which table will be delete, by default all table. - -Note that the `--force` option is needed to actually execute the deletion. - - -### Working with existing databases ### - -Run the following command to generate an XML schema from your `default` database: - - > php app/console propel:reverse - -You can define which connection to use: - - > php app/console propel:reverse --connection=default - -This will create your schema file under `app/propel/generated-schemas`. You need to move/copy it to the corresponding -bundle config directory. For example: `src/Acme/DemoBundle/Resources/config/`. - -## The Fixtures ## - -Fixtures are data you usually write to populate your database during the development, or static content -like menus, labels, ... you need by default in your database in production. - -### Loading Fixtures ### - -The following command is designed to load fixtures: - - > php app/console propel:fixtures:load [-d|--dir[="..."]] [--xml] [--sql] [--yml] [--connection[="..."]] [bundle] - -As you can see, there are many options to allow you to easily load fixtures. - -As usual, `--connection` allows to specify a connection. The `--dir` option allows to specify a directory -containing the fixtures (default is: `app/propel/fixtures/`). -Note that the `--dir` expects a relative path from the root dir (which is `app/`). - -The `--xml` parameter allows you to load only XML fixtures. -The `--sql` parameter allows you to load only SQL fixtures. -The `--yml` parameter allows you to load only YAML fixtures. - -You can mix `--xml`, `--yml` and `--sql` parameters to load XML, YAML and SQL fixtures at the same time. -If none of this parameter are set all files YAML, XML and SQL in the directory will be load. - -You can pass a bundle name to load fixtures from it. A bundle's name starts with `@` like `@AcmeDemoBundle`. - - > php app/console propel:fixtures:load @AcmeDemoBundle - - -### XML Fixtures ### - -A valid _XML fixtures file_ is: - -{% highlight xml %} - - - - - - - - -{% endhighlight %} - - -### YAML Fixtures ### - -A valid _YAML fixtures file_ is: - -{% highlight yaml %} -Awesome\Object: - o1: - Title: My title - MyFoo: bar - -Awesome\Related: - r1: - ObjectId: o1 - Description: Hello world ! - -Awesome\Tag: - t1: - name: Foo - t2: - name: Baz - -Awesome\Post: - p1: - title: A Post with tags (N-N relation) - tags: [ t1, t2 ] -{% endhighlight %} - - -#### Using Faker in YAML Fixtures #### - -If you use [Faker](https://github.com/fzaninotto/Faker) with its [Symfony2 integration](https://github.com/willdurand/BazingaFakerBundle), -then the PropelBundle offers a facility to use the Faker generator in your YAML files: - -{% highlight yaml %} -Acme\DemoBundle\Model\Book: - Book1: - name: "Awesome Feature" - description: -{% endhighlight %} - -The aim of this feature is to be able to mix both real and fake data in the same file. Fake data is interesting to quickly -add data to your application, but most of the time you need to rely on real data. Integrating Faker in with your YAML files -allows you to write strong fixtures efficiently. - - -## Dumping data ## - -You can dump data from your database into YAML fixtures file by using this command: - - > php app/console propel:fixtures:dump [--connection[="..."]] - -Dumped files will be written in the fixtures directory: `app/propel/fixtures/` with the following name: -`fixtures_99999.yml` where `99999` is a timestamp. - -Once done, you will be able to load these files by using the `propel:fixtures:load` command. - - -## ACL Implementation ## - - -The `PropelBundle` provides a model-based implementation of the Security components' interfaces. -To make us of this `AuditableAclProvider` you only need to change your security configuration. - -{% highlight yaml %} -security: - acl: - provider: propel.security.acl.provider -{% endhighlight %} - -This will switch the provider to be the `AuditableAclProvider` of the `PropelBundle`. - -The auditing of this provider is set to a sensible default. It will audit all ACL failures but no success by default. -If you also want to audit successful authorizations, you need to update the auditing of the given ACL accordingly. - -After adding the provider, you only need to run the `propel:acl:init` command in order to get the model generated. -If you already got an ACL database, the schema of the `PropelBundle` is compatible with the default schema of Symfony2. - -### Separate database connection for ACL ### - -In case you want to use a different database for your ACL than your business model, you only need to configure this service. - -{% highlight yaml %} -services: - propel.security.acl.connection: - class: PropelPDO - factory_class: Propel - factory_method: getConnection - arguments: - - "acl" -{% endhighlight %} - -The `PropelBundle` looks for this service, and if given uses the provided connection for all ACL related operations. -The given argument (`acl` in the example) is the name of the connection to use, as defined in your runtime configuration. - -## The PropelParamConverter ## - -You can use the PropelParamConverter with the [SensioFrameworkExtraBundle](http://github.com/sensio/SensioFrameworkExtraBundle). -You just need to put the right _Annotation_ on top of your controller: - -{% highlight php %} - - - ... - -
-
-EOF; - $builder = new PropelQuickBuilder(); - $config = $builder->getConfig(); - $config->setBuildProperty('behavior.my_awesome.class', __DIR__.'/../src/MyAwesomeBehavior'); - $builder->setConfig($config); - $builder->setSchema($schema); - $con = $builder->build(); - } - } -} -``` - -We rely on the [PropelQuickBuilder](https://github.com/propelorm/Propel/blob/master/generator/lib/util/PropelQuickBuilder.php) -to generated all classes in memory. It's a convenient way to test some parts of Propel without using fixtures files. - -Write a XML schema as usual, and use your new behavior in it. Now, take care of the line below. It's how you will register -your new behavior in the `PropelQuickBuilder`: - -```php -setBuildProperty('behavior.my_awesome.class', __DIR__.'/../src/MyAwesomeBehavior'); -``` - - -The `PropelQuickBuilder` comes with a **SQLite** database. That means you can execute database queries in your tests. -Did you notice the `$con` variable? It can be useful to add some logic to the database connection in use: - -```php -sqliteCreateFunction('ACOS', 'acos', 1); -``` - -You can now use your in memory classes in your test methods. It will just work. - - -## Using Composer ## - -[Composer](http://getcomposer.org) is designed to manage your PHP dependencies without any effort. It's a pretty -nice way to share your behavior with other people, or just to require it in your project. - -A basic configuration looks like: - -```javascript -{ - "name": "willdurand/propel-myawesome-behavior", - "description": "A nice description.", - "keywords": [ "propel", "behavior" ], - "license": "MIT", - "authors": [ - { - "name": "William DURAND", - "email": "william.durand1@gmail.com" - } - ], - "require": { - "propel/propel1": "1.6.*" - }, - "autoload": { - "classmap": ["src/"] - } -} -``` - - ->**Note**
The convention is to prefix your package name with propel-, and to suffix it with -behavior. - - -## Configuring PHPUnit ## - -If you run the command below, Composer will setup your behavior to run the test suite: - - php composer.phar install --dev - -Now, you have to configure your project for PHPUnit. It's really easy. Start by copying the following `phpunit.xml.dist` file: - -```xml - - - - - ./tests/ - - - - - ./src/ - - - -``` - -Now, create the `tests/bootstrap.php` file: - -```php -sqlsrv:server=localhost\SQLEXPRESS;Database=propel -sqlsrv:server=localhost\SQLEXPRESS,1433;Database=propel -sqlsrv:server=localhost,1433;Database=propel -``` - -Sample runtime-conf.xml for pdo_sqlsrv: - -```xml - - sqlsrv - - DebugPDO - sqlsrv:server=localhost,1433;Database=propel - username - password - - -``` - -Sample build.properties for pdo_sqlsrv: - -```ini -propel.database = sqlsrv -propel.database.url = sqlsrv:server=127.0.0.1,1433;Database=propel -``` - -### pdo_sybase ### - -When built against FreeTDS dblib it will be called `pdo_sybase`. This requires properly setting up the FreeTDS `freetds.conf` and `locales.conf`. There is a workaround for the lack of transactions support in the `pdo_dblib` driver by using `MssqlDebugPDO` or `MssqlPropelPDO` classes. - -c:\freetds.conf - -```ini -[global] - client charset = UTF-8 - tds version = 8.0 - text size = 20971520 -``` - -c:\locales.conf - -```ini -[default] - date format = %Y-%m-%d %H:%M:%S.%z -``` - -Sample dsn's for pdo_sybase: - -```xml -sybase:host=localhost\SQLEXPRESS;dbname=propel -sybase:host=localhost\SQLEXPRESS:1433;dbname=propel -sybase:host=localhost:1433;dbname=propel -``` - -Sample `runtime-conf.xml` for pdo_sybase: - -```xml - - mssql - - MssqlDebugPDO - sybase:host=localhost:1433;dbname=propel - username - password - - -``` - -Sample `build.properties` for `pdo_sybase`: - -```ini -propel.database = mssql -propel.database.url = sybase:host=localhost:1433;dbname=propel -``` - -### pdo_mssql ### - -When built against MS SQL Server dblib the driver will be called `pdo_mssql`. It is not recommended to use the `pdo_mssql` driver because it strips blobs of single quotes when retrieving from the database and will not return blobs or clobs longer that 8192 characters. The dsn differs from `pdo_sybase` in that it uses a comma between the server and port number instead of a colon and mssql instead of sybase for the driver name. - -Sample dsn's for `pdo_mssql`: - -```xml -mssql:host=localhost\SQLEXPRESS;dbname=propel -mssql:host=localhost\SQLEXPRESS,1433;dbname=propel -mssql:host=localhost,1433;dbname=propel -``` - -### pdo_odbc ### - -Currently `pdo_odbc` cannot be used to access MSSQL with propel because of a [long standing bug](http://connect.microsoft.com/SQLServer/feedback/details/521409/odbc-client-mssql-does-not-work-with-bound-parameters-in-subquery) with the MS SQL Server ODBC Client. Last update on 8/3/2010 was that it would be resolved in a future release of the SQL Server Native Access Client. This bug is related to two php bugs ([Bug #44643](http://bugs.php.net/bug.php?id=44643) and [Bug #36561](http://bugs.php.net/bug.php?id=36561)) - -## Linux ## - -Linux has 2 driver implementations that could be used: `pdo_dblib`, and `pdo_obdc`. - -### pdo_dblib ### - -`pdo_dblib` is built against the FreeTDS dblib implementation. The driver is not a complete PDO driver implementation and lacks support for transactions or driver attributes. This requires properly setting up the FreeTDS `freetds.conf` and `locales.conf`. There is a workaround for the lack of transactions support in the `pdo_dblib` driver by using `MssqlDebugPDO` or `MssqlPropelPDO` classes. - -Redhat: `/etc/freetds.conf` -Ubuntu: `/etc/freetds/freetds.conf` - -```ini -[global] - client charset = UTF-8 - tds version = 8.0 - text size = 20971520 -``` - -Redhat: `/etc/locales.conf` -Ubuntu: `/etc/freetds/locales.conf` - -```ini -[default] - date format = %Y-%m-%d %H:%M:%S.%z -``` - -Sample dsn's for `pdo_dblib`: - -```xml -dblib:host=localhost\SQLEXPRESS;dbname=propel -dblib:host=localhost\SQLEXPRESS:1433;dbname=propel -dblib:host=localhost:1433;dbname=propel -``` - -Sample `runtime-conf.xml` for `pdo_dblib`: - -```xml - - mssql - - MssqlDebugPDO - dblib:host=localhost:1433;dbname=propel - username - password - - -``` - -Sample `build.properties` for `pdo_dblib`: - -```ini -propel.database = mssql -propel.database.url = dblib:host=localhost:1433;dbname=propel -``` - -### pdo_odbc ### - -`pdo_odbc` using UnixODBC and FreeTDS. This should be supported in propel but with ubuntu 10.04 and php 5.2.x any statement binding causes apache to segfault so I have not been able to test it further. If anyone has any additional experience with this please post information to the propel development group. If you would like to experiment there are some instructions you can follow [here](http://kitserve.org.uk/content/accessing-microsoft-sql-server-php-ubuntu-using-pdo-odbc-and-freetds) for getting it setup on ubuntu. diff --git a/cookbook/using-sql-schemas.markdown b/cookbook/using-sql-schemas.markdown deleted file mode 100644 index 317d8fb6..00000000 --- a/cookbook/using-sql-schemas.markdown +++ /dev/null @@ -1,100 +0,0 @@ ---- -layout: documentation -title: Using SQL Schemas ---- - -# Using SQL Schemas # - -Some database vendors support "schemas", a.k.a. namespaces of collections of database objects (tables, views, etc.). MSSQL, PostgreSQL, and to a lesser extent MySQL, all provide the ability to group and organize tables into schemas. Propel supports tables organized into schemas, and works seamlessly in this context. - -## Schema Definition ## - -### Assigning a Table to a Schema ### - -In a XML schema, you can assign all the tables included into a `` tag to a given schema by setting the `schema` attribute on the `` tag: - -```xml - - - - -
-
-``` - ->**Tip**
On RDBMS that do not support SQL schemas (Oracle, SQLite), the `schema` attribute is ignored. - -You can also assign a table to a given schema individually ; this overrides the `schema` of the parent ``: - -```xml - - - -
-``` - -### Foreign Keys Between Schemas ### - -You can create foreign keys between tables assigned to different schemas, provided you set the `foreignSchema` attribute in the `` tag. - -```xml - - - - - - - -
- - - -
-``` - -## Schemas in Generated SQL ## - -When generating the SQL for table creation, Propel correctly adds the schema prefix (example for MySQL): - -```sql -CREATE TABLE `bookstore`.`book` -( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `title` VARCHAR(255), - PRIMARY KEY (`id`) -) -``` - ->**Tip**
Propel does not take care of creating the schema. The target database must already contain the required schemas, and the user credentials must allow Propel to access this schema. - -## Schemas in PHP Code ## - -Just like actual table names, SQL schemas don't appear in the PHP code. For the PHP developer, who manipulates phpNames, it's as if schemas didn't existed. - -Of course, you can make queries spanning across several schemas. - ->**Tip**
in Mysql, "SCHEMA" and "DATABASE" are synonyms. Therefore, the ability to define another schema for a given table actually allows cross-database queries. - -## Using the Schema As Base for PHP code Organization ## - -Propel provides other features to organize your model: - -* _Packages_ are subdirectories in which Model classes get generated (see [Multi-Component Data Model](./multi-component-data-model.html)) -* _Namespaces_ are actual PHP5.3 namespaces for generated Model classes (see [PHP 5.3 Namespaces](./namespaces.html)) - -You can easily tell Propel to copy the `schema` attribute to both the `package` and the `namespace` attributes, in order to reproduce the SQL organization at the PHP level. To that extent, modify the following settings in `build.properties`: - -```ini -propel.schema.autoPackage = true -propel.schema.autoNamespace = true -``` - -With such a configuration, a `book` table assigned to the `bookstore` schema will generate a `Bookstore\Book` ActiveRecord class under the `bookstore/` subdirectory. - -If you're stuck with PHP 5.2, you probably need to use the schema name as a class prefix rather than a namespace. That's what the `autoPrefix` setting is for: - -```ini -propel.schema.autoPrefix = true -``` - -With such a configuration, a `book` table assigned to the `bookstore` schema will generate a `BookstoreBook` ActiveRecord class. diff --git a/cookbook/working-with-advanced-column-types.markdown b/cookbook/working-with-advanced-column-types.markdown deleted file mode 100644 index 41bcf9c5..00000000 --- a/cookbook/working-with-advanced-column-types.markdown +++ /dev/null @@ -1,215 +0,0 @@ ---- -layout: documentation -title: Working with Advanced Column Types ---- - -# Working with Advanced Column Types # - -Propel offers a set of advanced column types. The database-agnostic implementation allows these types to work on all supported RDBMS. - -## LOB Columns ## - -Propel uses PHP streams internally for storing _Binary_ Locator Objects (BLOBs). This choice was made because PDO itself uses streams as a convention when returning LOB columns in a resultset and when binding values to prepared statements. Unfortunately, not all PDO drivers support this (see, for example, [http://bugs.php.net/bug.php?id=40913](http://bugs.php.net/bug.php?id=40913)); in those cases, Propel creates a `php://temp` stream to hold the LOB contents and thus provide a consistent API. - -Note that CLOB (_Character_ Locator Objects) are treated as strings in Propel, as there is no convention for them to be treated as streams by PDO. - -### Getting BLOB Values ### - -BLOB values will be returned as PHP stream resources from the accessor methods. Alternatively, if the value is NULL in the database, then the accessors will return the PHP value NULL. - -```php -getCoverImage(); -if ($fp !== null) { - echo stream_get_contents($fp, -1, 0); // important to use 0 as offset so it works when being called multiple times -} -``` - -### Setting BLOB Values ### - -When setting a BLOB column, you can either pass in a stream or the blob contents. - -```php -setCoverImage($fp); - -// Setting using file contents -$media = new Media(); -$media->setCoverImage(file_get_contents("/path/to/file.ext")); -``` - -Regardless of which setting method you choose, the BLOB will always be represented internally as a stream resource -- _and subsequent calls to the accessor methods will return a stream._ - -For example: -```php -setCoverImage(file_get_contents("/path/to/file.ext")); - -$fp = $media->getCoverImage(); -print gettype($fp); // "resource" -``` - -### Setting BLOB columns and isModified() ### - -Note that because a stream contents may be externally modified, _mutator methods for BLOB columns will always set the _isModified()_ to report true_ -- even if the stream has the same identity as the stream that was returned. - -For example: -```php -getCoverImage(); -$media->setCoverImage($fp); - -var_export($media->isModified()); // TRUE -``` - -## ENUM Columns ## - -Although stored in the database as integers, ENUM columns let users manipulate a set of predefined values, without worrying about their storage. - -```xml - - ... - -
-``` - -```php -setStyle('novel'); -echo $book->getStyle(); // novel -// Trying to set a value not in the valueSet throws an exception - -// ENUM columns are also searchable, using the generated filterByXXX() method -// or other ModelCritera methods (like where(), condition()) -$books = BookQuery::create() - ->filterByStyle(BookPeer::STYLE_NOVEL) // BookPeer::STYLE_NOVEL is a PHP equivalent for 'novel' - ->find(); - -// Alternatively there are getters for SQL value which can be used in Criteria or even plain SQL -$style = BookPeer::getStyleSqlValue(BookPeer::STYLE_NOVEL); -$criteria = new Criteria(); -$criteria->add(BookPeer::STYLE, $style); -$books = BookPeer::doSelect($criteria); - -// NOTE: method getStyleSqlValue() is only an alias for: -BookPeer::getSqlValueForEnum(BookPeer::STYLE, BookPeer::STYLE_NOVEL); -``` - -## OBJECT Columns ## - -Propel offers an `OBJECT` column type to store PHP objects in the database. The column setter serializes the object, which is later stored to the database as a string. The column getter unserializes the string and returns the object. Therefore, for the end user, the column contains an object. - -### Getting and Setting OBJECT Values ### - -```php -latitude = $latitude; - $this->longitude = $longitude; - } - - public function isInNorthernHemisphere() - { - return $this->latitude > 0; - } -} - -// The 'house' table has a 'coordinates' column of type OBJECT -$house = new House(); -$house->setCoordinates(new GeographicCoordinates(48.8527, 2.3510)); -echo $house->getCoordinates()->isInNorthernHemisphere(); // true -$house->save(); -``` - -### Retrieving Records based on OBJECT Values ### - -Not only do `OBJECT` columns benefit from these smart getter and setter in the generated Active Record class, they are also searchable using the generated `filterByXXX()` method in the query class: - -```php -filterByCoordinates(new GeographicCoordinates(48.8527, 2.3510)) - ->find(); -``` - -Propel looks in the database for a serialized version of the object passed as parameter of the `filterByXXX()` method. - -## ARRAY Columns ## - -An `ARRAY` column can store a simple PHP array in the database (nested arrays and associative arrays are not accepted). -The column setter serializes the array, which is later stored to the database as a string. -The column getter unserializes the string and returns the array. Therefore, for the end user, the column contains an array. - -### Getting and Setting ARRAY Values ### - -```php -setTags(array('novel', 'russian')); -print_r($book->getTags()); // array('novel', 'russian') - -// If the column name is plural, Propel also generates hasXXX(), addXXX(), -// and removeXXX() methods, where XXX is the singular column name -echo $book->hasTag('novel'); // true -$book->addTag('romantic'); -print_r($book->getTags()); // array('novel', 'russian', 'romantic') -$book->removeTag('russian'); -print_r($book->getTags()); // array('novel', 'romantic') -``` - -### Retrieving Records based on ARRAY Values ### - -Propel doesn't use `serialize()` to transform the array into a string. -Instead, it uses a special serialization function, that makes it possible to search for values of `ARRAY` columns. - -```php -filterByTags(array('novel', 'russian'), Criteria::CONTAINS_ALL) - ->find(); - -// Search books that contain at least one of the specified tags -$books = BookQuery::create() - ->filterByTags(array('novel', 'russian'), Criteria::CONTAINS_SOME) - ->find(); - -// Search books that don't contain any of the specified tags -$books = BookQuery::create() - ->filterByTags(array('novel', 'russian'), Criteria::CONTAINS_NONE) - ->find(); - -// If the column name is plural, Propel also generates singular filter methods -// expecting a scalar parameter instead of an array -$books = BookQuery::create() - ->filterByTag('russian') - ->find(); -``` - ->**Tip**
Filters on array columns translate to SQL as LIKE conditions. That means that the resulting query often requires a full table scan, and is not suited for large tables. - -### Using default values ### - -You may want to add default values to your `ARRAY` column, Propel 1.6.6, and upper allows to use the `defaultValue` -attribute in your XML schema to set a list of values as comma separated values: - -```xml - -``` - -_Warning_: Only generated Query classes (through generated `filterByXXX()` methods) and `ModelCriteria` (through `where()`, and `condition()`) allow conditions on `ENUM`, `OBJECT`, and `ARRAY` columns. `Criteria` alone (through `add()`, `addAnd()`, and `addOr()`) does not support conditions on such columns. diff --git a/cookbook/working-with-existing-databases.markdown b/cookbook/working-with-existing-databases.markdown deleted file mode 100644 index eaa4263a..00000000 --- a/cookbook/working-with-existing-databases.markdown +++ /dev/null @@ -1,123 +0,0 @@ ---- -layout: documentation -title: Working With Existing Databases ---- - -# Working With Existing Databases # - -The following topics are targeted for developers who already have a working database solution in place, but would like to use Propel to work with the data. For this case, Propel provides a number of command-line utilities helping with migrations of data and data structures. - -## Working with Database Structures ## - -Propel uses an abstract XML schema file to represent databases (the [schema](../reference/schema)). Propel builds the SQL specific to a database based on this schema. Propel also provides a way to reverse-engineer the generic schema file based on database metadata. - -### Creating an XML Schema from a DB Structure ### - -To generate a schema file, create a new directory for your project & specify the connection information in your `build.properties` file for that project. For example, to create a new project, `legacyapp`, follow these steps: - - 1. Create the `legacyapp` project directory anywhere on your filesystem: - -```bash -> mkdir legacyapp -> cd legacyapp -``` - - 2. Create a `build.properties` file in `legacyapp/` directory with the DB connection parameters for your existing database, e.g.: - -```ini -propel.project = legacyapp - -# The Propel driver to use for generating SQL, etc. -propel.database = mysql - -# This must be a PDO DSN -propel.database.url = mysql:dbname=legacyapp -propel.database.user = root -# propel.database.password # -``` - - 3. Run the `reverse` task to generate the `schema.xml`: - -```bash -> propel-gen . reverse -``` - -Or if you create the `build.properties` file in a subdirectory called `config`: - -```bash -> propel-gen config reverse -``` - - 4. Pay attention to any errors/warnings issued by Phing during the task execution and then examine the generated `schema.xml` file to make any corrections needed. - - 5. _'You're done! _' Now you have a `schema.xml` file in the `legacyapp/` project directory. You can now run the default Propel build to generate all the classes. - -The generated `schema.xml` file should be used as a guide, not a final answer. There are some datatypes that Propel may not be familiar with; also some datatypes are simply not supported by Propel (e.g. arrays in PostgreSQL). Unfamiliar datatypes will be reported as warnings and substituted with a default VARCHAR datatype. - ->**Tip**
The reverse engineering classes may not be able to provide the same level of detail for all databases. In particular, metadata information for SQLite is often very basic since SQLite is a typeless database. - -### Migrating Structure to a New RDBMS ### - -Because Propel has both the ability to create XML schema files based on existing database structures and to create RDBMS-specific DDL SQL from the XML schema file, you can use Propel to convert one database into another. - -To do this you would simply: - - 1. Follow the steps above to create the `schema.xml` file from existing db. - 2. Then you would change the target database type and specify connection URL for new database in the project's `build.properties` file: - -```ini -propel.database = pgsql -propel.database.url = pgsql://unix+localhost/newlegacyapp -``` - - 3. And then run the `sql` task to generate the new DDL: - -```bash -> propel-gen sql -``` - - 4. And (optionally) the `insert-sql` task to create the new database: - -```bash -> propel-gen insert-sql -``` - -## Working with Database Data ## - -Propel also provides several tasks to facilitate data import/export. The most important of these are `datadump` and `datasql`. The first dumps data to XML and the second converts the XML data dump to a ready-to-insert SQL file. - ->**Tip**
Both of these tasks require that you already have generated the `schema.xml` for your database. - -### Dumping Data to XML ### - -Once you have created (or reverse-engineered) your `schema.xml` file, you can run the `datadump` task to dump data from the database into a `data.xml` file. - -```bash -> propel-gen datadump -``` - -The task transfers database records to XML using a simple format, where each row is an element, and each column is an attribute. So for instance, the XML representation of a row in a `publisher` table: - -|publisher_id |name -|---------------|-------------- -|1 |William Morrow - -... is rendered in the `data.xml` as follows: - -```xml - - ... - - ... - -``` - -### Creating SQL from XML ### - -To create the SQL files from the XML, run the `datasql` task: - -```bash -> propel-gen datasql -``` - -The generated SQL is placed in the `build/sql/` directory and will be inserted when you run the `insert-sql` task. diff --git a/cookbook/working-with-unit-tests.markdown b/cookbook/working-with-unit-tests.markdown deleted file mode 100644 index 34142530..00000000 --- a/cookbook/working-with-unit-tests.markdown +++ /dev/null @@ -1,241 +0,0 @@ ---- -layout: documentation -title: Working With Unit Tests ---- - -# Working With Unit Tests # - -Propel's test environment has a couple of requirements which should be setup before running the test suite. - -## Setup the environment ## - -### PHP ### - -Install following PHP modules: - - php5-mysql - php5-sqlite - php5-iconv - -Increase the `memory_limit` in your `php.ini` to something very high: - - memory_limit = 512M - -Set a default timezone in your `php.ini`: - - date.timezone = Europe/Berlin - - -### MySQL ### - -By default, the Propel test suite uses the username `root` without a password. -This is the default setting on most platforms after installing MySQL. - -Create following databases: - - CREATE DATABASE test; - CREATE SCHEMA bookstore_schemas; - CREATE SCHEMA contest; - CREATE SCHEMA second_hand_books; - CREATE DATABASE reverse_bookstore; - -If you want to test with your own credentials, then create a user and authorize him to the -created databases. See `Configure the credentials to be used`. - -### Configure the credentials to be used ### - -You must configure both the generator and the runtime connection settings: - -```ini -// in test/fixtures/bookstore/build.properties -propel.database = mysql -propel.database.url = mysql:dbname=test -propel.mysqlTableType = InnoDB -propel.disableIdentifierQuoting=true -# For MySQL or Oracle, you also need to specify username & password -propel.database.user = myusername -propel.database.password = p@ssw0rd -``` - -```xml -// in test/fixtures/bookstore/runtime-conf.xml - - - mysql - - - DebugPDO - mysql:dbname=test - myusername - p@ssw0rd - - - - - - - - - - utf8 - - - -``` - ->**Tip**
To run the unit tests for the namespace support in PHP 5.3, you must also configure the `fixtures/namespaced` project. - -
- ->**Tip**
To run the unit tests for the database schema support, you must also configure the `fixtures/schemas` project. This projects requires that your database supports schemas, and already contains the following schemas: `bookstore_schemas`, `contest`, and `second_hand_books`. Note that the user defined in `build.properties` and `runtime-conf.xml` must have access to these schemas. - - -### Get PHPUnit ### - -You can get PHPUnit here: [https://github.com/sebastianbergmann/phpunit/](https://github.com/sebastianbergmann/phpunit/). - -The fastest way to get it is: - - wget http://pear.phpunit.de/get/phpunit.phar - chmod +x phpunit.phar - -This manual is based on this `phpunit.phar`. - -### Get Phing ### - -To get phing, you can download it manually -([http://www.phing.info/trac/wiki/Users/Download](http://www.phing.info/trac/wiki/Users/Download)) -or use [Composer](http://getcomposer.org/). - - curl -s https://getcomposer.org/installer | php - -or - - wget http://getcomposer.org/composer.phar - -and then: - - php composer.phar install - - -## Preparing fixures ## - -Every time you start using the test suite with a new Propel repository you should fire: - - ./test/reset_tests.sh - -It prepares all required `fixures` and creates some tables in your databases. -So if you re-setup your databases, re-run this script. - -If you get something like: - - [ bookstore ] - No VERSION.TXT file found; try setting phing.home environment variable. - -then you don't have setup Phing correctly. - -If you get messages like: - - BUILD FAILED - Propel/generator/build.xml:95:15: No project directory specified - -then this is OK - just ignore them. - -## Writing Tests ## - -### How the Tests Work ### - -Every method in the test classes that begins with `test` is run as a test case by PHPUnit. -All tests are run in isolation; the `setUp()` method is called at the beginning of "each" -test and the `tearDown()` method is called at the end. - -The `BookstoreTestBase` class specifies `setUp()` and `tearDown()` methods which populate -and depopulate, respectively, the database. -This means that every unit test is run with a cleanly populated database. -To see the sample data that is populated, take a look at the `BookstoreDataPopulator` class. -You can also add data to this class, if needed by your tests; however, proceed cautiously when -changing existing data in there as there may be unit tests that depend on it. More typically, -you can simply create the data you need from within your test method. It will be deleted by the -`tearDown()` method, so no need to clean up after yourself. - - -If you've made a change to a template or to Propel behavior, the right thing to do is write a -unit test that ensures that it works properly -- and continues to work in the future. - -### Write one ### - -Writing a unit test often means adding a method to one of the existing test classes. For example, -let's test a feature in the Propel templates that supports saving of objects when only default -values have been specified. Just add a `testSaveWithDefaultValues()` method to the -`GeneratedObjectTest` class (test/testsuite/generator/builder/om/GeneratedObjectTest.php), as -follows: - -```php -setName('Penguin'); - // in the past this wouldn't have marked object as modified - // since 'Penguin' is the value that's already set for that attrib - $pub->save(); - - // if getId() returns the new ID, then we know save() worked. - $this->assertNotNull($pub->getId(), "Expect Publisher->save() to work with only default values."); - } - -[...] - -} - -?> -``` - - -You can also write additional unit test classes to any of the directories in `test/testsuite/` -(or add new directories if needed). PHPUnit command will find these files automatically and run them. - - -## Start the magic ## - -Now you should be ready to go with PHPUnit. - -To start all tests, run: - - ./phpunit.phar - -To start only one test, run: - - ./phpunit.phar test/testsuite/generator/model/TableTest.php - - -If you get - - OK, but incomplete or skipped tests! - -then anything looks quite good :-) - - - -### Errors #### - -If you get something like: - - Fatal error: Call to a member function isCommitable() on a non-object in test/tools/helpers/bookstore/BookstoreTestBase.php on line 43 - -then you have probably not created all necessary databases or you credentials are wrong. - -If you get a lot of errors in your first line, then you probably didn't fire the `./test/reset_tests.sh`. diff --git a/cookbook/writing-behavior.markdown b/cookbook/writing-behavior.markdown deleted file mode 100644 index b53eed33..00000000 --- a/cookbook/writing-behavior.markdown +++ /dev/null @@ -1,432 +0,0 @@ ---- -layout: documentation -title: How to Write A Behavior ---- - -# How to Write A Behavior # - -Behaviors are a good way to reuse code across models without requiring inheritance (a.k.a. horizontal reuse). This step-by-step tutorial explains how to port model code to a behavior, focusing on a simple example. - -In the tutorial "[Keeping an Aggregate Column up-to-date](http://propel.posterous.com/getting-to-know-propel-15-keeping-an-aggregat)", posted in the [Propel blog](http://propel.posterous.com/), the `TotalNbVotes` property of a `PollQuestion` object was updated each time a related `PollAnswer` object was saved, edited, or deleted. This "aggregate column" behavior was implemented by hand using hooks in the model classes. To make it truly reusable, the custom model code needs to be refactored and moved to a Behavior class. - -## Boostrapping A Behavior ## - -A behavior is a class that can alter the generated classes for a table of your model. It must only extend the `Behavior` class and implement special "hook" methods. Here is the class skeleton to start with for the `aggregate_column` behavior: - -```php - null, - ); -} -``` - -Save this class in a file called `AggregateColumnBehavior.php`, and set the path for the class file in the project `build.properties` (just replace directory separators with dots). Remember that the `build.properties` paths are relative to the include path: - -```ini -propel.behavior.aggregate_column.class = path.to.AggregateColumnBehavior -``` - -Test the behavior by adding it to a table of your model, for instance to a `poll_question` table: - -```xml - - - - - - - -
-
-``` - -Rebuild your model, and check the generated `PollQuestionTableMap` class under the `map` subdirectory of your build class directory. This class carries the structure metadata for the `PollQuestion` ActiveRecord class at runtime. The class should feature a `getBehaviors()` method as follows, proving that the behavior was correctly applied: - -```php - array('name' => 'total_nb_votes', ), - ); - } // getBehaviors() -} -``` - -## Adding A Column ## - -The behavior works, but it still does nothing at all. Let's make it useful by allowing it to add a column. In the `AggregateColumnBehavior` class, just implement the `modifyTable()` method with the following code: - -```php -getTable(); - if (!$columnName = $this->getParameter('name')) { - throw new InvalidArgumentException(sprintf( - 'You must define a \'name\' parameter for the \'aggregate_column\' behavior in the \'%s\' table', - $table->getName() - )); - } - // add the aggregate column if not present - if(!$table->containsColumn($columnName)) { - $table->addColumn(array( - 'name' => $columnName, - 'type' => 'INTEGER', - )); - } - } -} -``` - -This method shows that a behavior class has access to the `` defined for it in the `schema.xml` through the `getParameter()` command. Behaviors can also always access the `Table` object attached to them, by calling `getTable()`. A `Table` can check if a column exists and add a new one easily. The `Table` class is one of the numerous generator classes that serve to describe the object model at buildtime, together with `Column`, `ForeignKey`, `Index`, and a lot more classes. You can find all the buildtime model classes under the `generator/lib/model` directory. - -_Tip_: Don't mix up the _runtime_ database model (`DatabaseMap`, `TableMap`, `ColumnMap`, `ValidatorMap`, `RelationMap`) with the _buildtime_ database model (`Database`, `Table`, `Column`, `Validator`, etc.). The buildtime model is very detailed, in order to ease the work of the builders that write the ActiveRecord and Query classes. On the other hand, the runtime model is optimized for speed, and carries minimal information to allow correct hydration and binding at runtime. Behaviors use the buildtime object model, because they are run at buildtime, so they have access to the most powerful model. - -Now rebuild the model and the SQL, and sure enough, the new column is there. `BasePollQuestion` offers a `getTotalNbVotes()` and a `setTotalNbVotes()` method, and the table creation SQL now includes the additional `total_nb_votes` column: - -```sql -DROP TABLE IF EXISTS poll_question; -CREATE TABLE poll_question -( - id INTEGER NOT NULL AUTO_INCREMENT, - title VARCHAR(100), - total_nb_votes INTEGER, - PRIMARY KEY (id) -)Type=InnoDB; -``` - -_Tip_: The behavior only adds the column if it's not present (`!$table->containsColumn($columnName)`). So if a user needs to customize the column type, or any other attribute, he can include a `` tag in the table with the same name as defined in the behavior, and the `modifyTable()` will then skip the column addition. - -## Adding A Method To The ActiveRecord Class ## - -In the previous post, a method of the ActiveRecord class was in charge of updating the `total_nb_votes` column. A behavior can easily add such methods by implementing the `objectMethods()` method: - -```php -addUpdateAggregateColumn(); - } - - protected function addUpdateAggregateColumn() - { - $sql = sprintf('SELECT %s FROM %s WHERE %s = ?', - $this->getParameter('expression'), - $this->getParameter('foreign_table'), - $this->getParameter('foreign_column') - ); - $table = $this->getTable(); - $aggregateColumn = $table->getColumn($this->getParameter('name')); - $columnPhpName = $aggregateColumn->getPhpName(); - $localColumn = $table->getColumn($this->getParameter('local_column')); - return " -/** - * Updates the aggregate column {$aggregateColumn->getName()} - * - * @param PropelPDO \$con A connection object - */ -public function update{$columnPhpName}(PropelPDO \$con) -{ - \$sql = '{$sql}'; - \$stmt = \$con->prepare(\$sql); - \$stmt->execute(array(\$this->get{$localColumn->getPhpName()}())); - \$this->set{$columnPhpName}(\$stmt->fetchColumn()); - \$this->save(\$con); -} -"; - } -} -``` - -The ActiveRecord class builder expects a string in return to the call to `Behavior::objectMethods()`, and appends this string to the generated code of the ActiveRecord class. Don't bother about indentation: builder classes know how to properly indent a string returned by a behavior. A good rule of thumb is to create one behavior method for each added method, to provide better readability. - -Of course, the schema must be modified to supply the necessary parameters to the behavior: - -```xml - - - - - - - - - - - -
- - - - - - - - -
-
-``` - -Now if you rebuild the model, you will see the new `updateTotalNbVotes()` method in the generated `BasePollQuestion` class: - -```php -prepare($sql); - $stmt->execute(array($this->getId())); - $this->setTotalNbVotes($stmt->fetchColumn()); - $this->save($con); - } -} -``` - -Behaviors offer similar hook methods to allow the addition of methods to the query classes (`queryMethods()`) and to the peer classes (`peerMethods()`). And if you need to add attributes, just implement one of the `objectAttributes()`, `queryAttributes()`, or `peerAttributes()` methods. - -## Using a Template For Generated Code ## - -The behavior's `addUpdateAggregateColumn()` method is somehow hard to read, because of the large string containing the PHP code canvas for the added method. Propel behaviors can take advantage of Propel's simple templating system to use an external file as template for the code to insert. - -Let's refactor the `addUpdateAggregateColumn()` method to take advantage of this feature: - -```php -getParameter('expression'), - $this->getParameter('foreign_table'), - $this->getParameter('foreign_column') - ); - $table = $this->getTable(); - $aggregateColumn = $table->getColumn($this->getParameter('name')); - return $this->renderTemplate('objectUpdateAggregate', array( - 'aggregateColumn' => $aggregateColumn, - 'columnPhpName' => $aggregateColumn->getPhpName(), - 'localColumn' => $table->getColumn($this->getParameter('local_column')), - 'sql' => $sql, - )); - } -} -``` - -The method no longer returns a string created by hand, but a _rendered template_. Propel templates are simple PHP files executed in a sandbox - they have only access to the variables declared as second argument of the `renderTemplate()` call. - -Now create a `templates/` directory in the same directory as the `AggregateColumnBehavior` class file, and add in a `objectUpdateAggregate.php` file with the following code: - -```php -/** - * Updates the aggregate column getName() ?> - * - * @param PropelPDO $con A connection object - */ -public function update(PropelPDO $con) -{ - $sql = ''; - $stmt = $con->prepare($sql); - $stmt->execute(array($this->getgetPhpName() ?>())); - $this->set($stmt->fetchColumn()); - $this->save($con); -} -``` - -No need to escape dollar signs anymore: this syntax allows for a cleaner separation, and is very convenient for large behaviors. - -## Adding Another Behavior From A Behavior ## - -This is where it's getting tricky. In the [blog post](http://propel.posterous.com/getting-to-know-propel-15-keeping-an-aggregat) describing the column aggregation technique, the calls to the `updateTotalNbVotes()` method come from the `postSave()` and `postDelete()` hooks of the `PollAnswer` class. But the current behavior is applied to the `poll_question` table, how can it modify the code of a class based on another table? - -The short answer is: it can't. To modify the classes built for the `poll_answer` table, a behavior must be registered on the `poll_answer` table. But a behavior is just like a column or a foreign key: it has an object counterpart in the buildtime database model. So the trick here is to modify the `AggregateColumnBehavior::modifyTable()` method to _add a new behavior_ to the foreign table. This second behavior will be in charge of implementing the `postSave()` and `postDelete()` hooks of the `PollAnswer` class. - -```php -getDatabase()->getTable($this->getParameter('foreign_table')); - if (!$foreignTable->hasBehavior('concrete_inheritance_parent')) { - require_once 'AggregateColumnRelationBehavior.php'; - $relationBehavior = new AggregateColumnRelationBehavior(); - $relationBehavior->setName('aggregate_column_relation'); - $relationBehavior->addParameter(array( - 'name' => 'foreign_table', - 'value' => $table->getName() - )); - $relationBehavior->addParameter(array( - 'name' => 'foreign_column', - 'value' => $this->getParameter('name') - )); - $foreignTable->addBehavior($relationBehavior); - } - } -} -``` - -In practice, everything now happens as if the `poll_answer` had its own behavior: - -```xml - - - - - - - - -
-
-``` - -Adding a behavior to a `Table` instance, as well as adding a `Parameter` to a `Behavior` instance, is quite straightforward. And since the second behavior class file is required in the `modifyTable()` method, there is no need to add a path for it in the `build.properties`. - -## Adding Code For Model Hooks ## - -The new `AggregateColumnRelationBehavior` is not yet ready to write. It must implement a call to `PollQuestion::updateTotalNbVotes()` in the `postSave()` and `postDelete()` hooks. - -Adding code to hooks from a behavior is just like adding methods: add a method with the right hook name returning a code string, and the code will get appended at the right place. Unsurprisingly, the behavior hook methods for `postSave()` and `postDelete()` are called `postSave()` and `postDelete()`: - -```php - null, - 'foreignColumn' => null, - ); - - public function postSave() - { - $table = $this->getTable(); - $foreignTable = $table->getDatabase()->getTable($this->getParameter('foreign_table')); - $foreignColumn = $foreignTable->getColumn($this->getParameter('foreign_column')); - $foreignColumnPhpName = $foreignColumn->getPhpName(); - return "\$this->updateRelated{$foreignColumnPhpName}(\$con)"; - } - - public function postDelete() - { - return $this->postSave(); - } - - public function objectMethods() - { - $script = _; - $script .= $this->addUpdateRelatedAggregateColumn(); - return $script; - } - - protected function addUpdateRelatedAggregateColumn() - { - $table = $this->getTable(); - $foreignTable = $table->getDatabase()->getTable($this->getParameter('foreign_table')); - $foreignTablePhpName = foreignTable->getPhpName(); - $foreignColumn = $foreignTable->getColumn($this->getParameter('foreign_column')); - $foreignColumnPhpName = $foreignColumn->getPhpName(); - return " -/** - * Updates an aggregate column in the foreign {$foreignTable->getName()} table - * - * @param PropelPDO \$con A connection object - */ -protected function updateRelated{$foreignColumnPhpName}(PropelPDO \$con) -{ - if (\$parent{$foreignTablePhpName} = \$this->get{$foreignTablePhpName}()) { - \$parent{$foreignTablePhpName}->update{$foreignColumnPhpName}(\$con); - } -} -"; - } -} -``` - -The `postSave()` and `postDelete()` behavior hooks will not add code to the ActiveRecord `postSave()` and `postDelete()` methods - to allow users to further implement these methods - but instead it adds code directly to the `save()` and `delete()` methods, inside a transaction. Check the generated `BasePollAnswer` class for the added code in these methods: - -```php -updateRelatedTotalNbVotes($con); -``` - -You will also see the new `updateRelatedTotalNbVotes()` method added by `AggregateColumnBehavior::objectMethods()`: - -```php -getPollQuestion()) { - $parentPollQuestion->updateTotalNbVotes($con); - } -} -``` - -## Specifying a Priority For Behavior Execution ## - -Since behaviors can modify tables, and even add tables, you may encounter cases where two behaviors conflict with each other. The usual way to solve these conflicts is to force a particular execution order, i.e. behavior A must be executed before behavior B, no matter in what order they were specified in the schema. - -Propel Behavior classes support a `$tableModificationOrder` attribute just for that purpose. By default, it is set to 50; set it to a lower number to force an early execution, or to a greater number to force a late execution. For instance, in the following example, `BehaviorA` will be executed before `BehaviorB`: - -```php -**Tip**
Propel uses the PDO and SPL components, which are bundled and enabled by default in PHP5. - -## Project-Local Installation ## - -For a quick start, the best choice is to install Propel inside a project directory structure, typically under a `vendor/` subdirectory: -You could install propel manually into the your `vendor` directory, or let [composer](http://getcomposer.org/) do the job for you. - -### Composer Installation ### - - -The only thing you need to do is, to drop the following line into your projects' `composer.json` file. - -```yaml -{ - "require": { - "propel/propel1": "~1.6" - } -} -``` - -composer will also take care to download all dependencies which are required by Propel. - ->**Note**
Composer requires [PHP 5.3.2+](http://www.php.net/). In case you are running PHP5.2.X you should stick to the "Manual Installation" - -### Manual Installation ### - -```bash -myproject/ - ... - vendor/ <= This is where third-party libraries usually go -``` - -To install Propel there using Git, type: - -```bash -cd myproject/vendor -git clone https://github.com/propelorm/Propel.git propel -``` - -This will export the propel library to a local `myproject/vendor/propel/` directory. - -Alternatively, to use a tarball, type the following commands on unix platforms: - -```bash -cd myproject/vendor -wget http://files.propelorm.org/propel-1.6.X.tar.gz -tar zxvf propel-1.6.X.tar.gz -mv propel-1.6.X propel -``` - -Or, in Windows, download a ZIP from [files.propelorm.org](http://files.propelorm.org), unzip it under the `vendor/` directory, and rename it to `propel`. - -## Propel Directory Structure ## - -The root directory of the Propel library includes the following folders: - -|Folders |Explanations -|---------------|---------------------------------------------------------------------- -|generator |Contains the classes required to run Propel in the command line. Propel commands can build the object model, compile configuration files, execute migrations, etc. -|runtime |Contains the classes required to access Propel models and the database. Typically, applications using a web server will only access the `runtime` directory and not the `generator`. -|tests |Propel unit tests. Ignore this if you don't want to contribute to Propel. - -Usually, both the generator and the runtime components are installed on development environments, while the actual test or production servers may need only the runtime component installed. - -## Installing Dependencies ## - -The Propel generator uses [Phing 2.4.5](http://phing.info/) to manage command line tasks; both the generator and the runtime classes use [PEAR Log](http://pear.php.net/package/Log/) to log events. - -To install these packages, use the PEAR command as follows: - -```bash -pear channel-discover pear.phing.info -pear install phing/phing -pear install Log -``` - -Refer to their respective websites for alternative installation strategies for Phing and PEAR Log. - -## Testing Propel Installation ## - -The Propel generator component bundles a `propel-gen` sh script (and a `propel-gen.bat` script for Windows). This script makes it easy to execute build commands. You can test this component is properly installed by calling the `propel-gen` script from the CLI: - -```bash -cd myproject -vendor/propel/generator/bin/propel-gen -``` - -The script should output a welcome message, followed by a 'BUILD FAILED' message, which is normal - you haven't defined a model to build yet. - ->**Tip**
In order to allow an easier execution the script, you can also add the propel generator's `bin/` directory to your PATH, or create a symlink. For example: - -```bash -cd myproject -ln -s vendor/propel/generator/bin/propel-gen propel-gen # Make a symlink to the propel generator file -``` - -Or edit your ~/.bashrc or ~/.zshrc file with : - -```bash -export PATH=$PATH:/path/to/propel/bin -``` - -On Windows you could set the PATH for the opened command with : - -``` -set PATH=%PATH%;C:/path/to/propel/generator/bin/ -``` - -To globally define the PATH adjust it inside the "Environment Variables", which you can find in your system advanced settings panel. - -At this point, Propel should be setup and ready to use. You can follow the steps in the [Build Guide](02-buildtime.html) to try it out. - -## Alternative: Global Installation Using PEAR ## - -Alternatively, you can install Propel globally on your system using PEAR. All your projects will use the same Propel version - that may or may not be a good idea, depending on how often you update your projects. - -Propel has its own PEAR channel, that you must "discover". Using the `pear install -a` command, you can let PEAR download and install all dependencies (Phing and PEAR Log). - -So the commands to install Propel, Phing and PEAR Log globally sum up to this: - -```bash -pear channel-discover pear.propelorm.org -pear install -a propel/propel_generator -pear install -a propel/propel_runtime -``` - -Once Propel is installed globally, you can access the `propel-gen` command from everywhere without symlink. - ->**Tip**
If you want to install non-stable versions of Propel, change your `preferred_state` PEAR environment variable before installing the Propel packages. Valid states include 'stable', 'beta', 'alpha', and 'devel': - -```bash -pear config-set preferred_state beta -``` - -## Troubleshooting ## - -### PHP Configuration ### - -Propel requires the following settings in `php.ini`: - -|Variable |Value -|-----------------------|----- -|ze1_compatibility_mode |Off -|magic_quotes_gpc |Off -|magic_quotes_sybase |Off - -### PEAR Directory In Include Path ### - -If you choose to install Propel via PEAR, and if it's your first use of PEAR, the PEAR directory may not be on your PHP `include_path`. Check the [PEAR documentation](http://pear.php.net/manual/en/installation.checking.php) for details on how to do that. - -### Phing Version ### - -Phing versions 2.4.3 and 2.4.4 are incompatible with Propel. Check your Phing version by calling: - -```bash -phing -v -``` - -In case you're using a version less than 2.4.5, upgrade to the latest stable version: - -```bash -pear upgrade phing/phing -``` - -### Getting Help ### - -If you can't manage to install Propel, don't hesitate to ask for help. See [Support](../support) for details on getting help. diff --git a/documentation/02-buildtime.markdown b/documentation/02-buildtime.markdown deleted file mode 100644 index 35ecb319..00000000 --- a/documentation/02-buildtime.markdown +++ /dev/null @@ -1,355 +0,0 @@ ---- -layout: documentation -title: The Build Time ---- - -# The Build Time # - -The initial step in every Propel project is the "build". During build time, a developer describes the structure of the datamodel in a XML file called the "schema". From this schema, Propel generates PHP classes, called "model classes", made of object-oriented PHP code optimized for a given RDMBS. The model classes are the primary interface to find and manipulate data in the database in Propel. - -The XML schema can also be used to generate SQL code to setup your database. Alternatively, you can generate the schema from an existing database (see the [Existing-Database reverse engineering chapter](../cookbook/working-with-existing-databases) for more details), or from a DBDesigner 4 model (see the [DBDesigner2Propel chapter](../cookbook/dbdesigner)). - -During build time, a developer also defines the connection settings for communicating with the database. - -To illustrate Propel's build abilities, this chapter uses the data structure of a bookstore as an example. It is made of three tables: a `book` table, with a foreign key to two other tables, `author` and `publisher`. - -## Describing Your Database as XML Schema ## - -Propel generates PHP classes based on a _relational_ description of your data model. This "schema" uses XML to describe tables, columns and relationships. The schema syntax closely follows the actual structure of the database. - -Create a `bookstore` directory. This will be the root of the bookstore project. - -### Database Connection Name ### - -Create a file called `schema.xml` in the new `bookstore/` directory. - -The root tag of the XML schema is the `` tag: - -```xml - - - - -``` - -The `name` attribute defines the name of the connection that Propel uses for the tables in this schema. It is not necessarily the name of the actual database. In fact, Propel uses a second file to link a connection name with real connection settings (like database name, user and password). This `runtime-conf.xml` file will be explained later in this chapter. - -The `defaultIdMethod` attribute indicates that the tables in this schema use the database's "native" auto-increment/sequence features to handle id columns that are set to auto-increment. - ->**Tip**
You can define several schemas for a single project. Just make sure that each of the schema filenames end with `schema.xml`. - -### Tables And Columns ### - -Within the `` tag, Propel expects one `` tag for each table: - -```xml - - -
- -
- - -
- - -
-
-``` - -This time, the `name` attributes are the real table names. The `phpName` is the name that Propel will use for the generated PHP class. By default, Propel uses a CamelCase version of the table name as its phpName - that means that you could omit the `phpName` attribute in the example above. - -Within each set of `` tags, define the columns that belong to that table: - -```xml - - -
- - - - - -
- - - - -
- - - -
-
-``` - -Each column has a `name` (the one used by the database), and an optional `phpName` attribute. Once again, the Propel default behavior is to use a CamelCase version of the `name` as `phpName` when not specified. - -Each column also requires a `type`. The XML schema is database agnostic, so the column types and attributes are probably not exactly the same as the one you use in your own database. But Propel knows how to map the schema types with SQL types for many database vendors. Existing Propel column types are `boolean`, `tinyint`, `smallint`, `integer`, `bigint`, `double`, `float`, `real`, `decimal`, `char`, `varchar`, `longvarchar`, `date`, `time`, `timestamp`, `blob`, `clob`, `object`, and `array`. Some column types use a `size` (like `varchar` and `int`), some have unlimited size (`longvarchar`, `clob`, `blob`). Check the [schema reference](../reference/schema) for more details on each column type. - -As for the other column attributes, `required`, `primaryKey`, and `autoIncrement`, they mean exactly what their names imply. - ->**Tip**
Propel supports namespaces (for PHP > 5.3). If you specify a `namespace` attribute in a `` element, the generated PHP classes for this table will use this namespace. - -### Foreign Keys ### - -A table can have several `` tags, describing foreign keys to foreign tables. Each `` tag consists of one or more mappings between a local column and a foreign column. - -```xml - - -
- - - - - - - - - - - -
- - - - -
- - - -
-
-``` - -A foreign key represents a relationship. Just like a table or a column, a relationship has a `phpName`. By default, Propel uses the `phpName` of the foreign table as the `phpName` of the relation. The `refPhpName` defines the name of the relation as seen from the foreign table. - -There are many more attributes and elements available to describe a datamodel. Propel's documentation provides a complete [Schema of the schema syntax](../reference/schema), together with a [DTD](https://github.com/propelorm/Propel/blob/master/generator/resources/dtd/database.dtd) and a [XSD](https://github.com/propelorm/Propel/blob/master/generator/resources/xsd/database.xsd) schema for its validation. - -## Building The Model ## - -### Setting Up Build Configuration ### - -The build process is highly customizable. Whether you need the generated classes to inherit one of your classes rather than Propel's base classes, or to enable/disable some methods in the generated classes, pretty much every customization is possible. Of course, Propel provides sensible defaults, so that you actually need to define only two settings for the build process to start: the RDBMS you are going to use, and a name for your project. - -Propel expects the build configuration to be stored in a file called `build.properties`, and stored at the same level as the `schema.xml`. Here is an example for a MySQL database: - -```ini -# Database driver -propel.database = mysql - -# Project name -propel.project = bookstore -``` - -Use your own database vendor driver, chosen among pgsql, mysql, sqlite, mssql, and oracle. - -You can learn more about the available build settings and their possible values in the [build configuration reference](../reference/buildtime-configuration). - -### Using the `propel-gen` Script To Build The Model ### - -The Propel generator uses the `propel-gen` script, as seen in the previous chapter. This executable expects a command name as its argument. - -Open a terminal and browse to the `bookstore/` directory, where you saved the two previous files (`schema.xml`, and `build.properties`). Then use the `propel-gen` script to call the "Object Model generator" command using its shortcut - "om": - -```bash -cd /path/to/bookstore -propel-gen om -``` - -You should normally see a some colored lines appear in the terminal, logging all the class generation, and ending with "BUILD FINISHED". If not, look for red lines in the log and follow the directions in the error messages. - -### Generated Object Model ### - -The "om" command added a new directory in the `bookstore/` project, called `build/`. The generated model classes are located under the `classes/bookstore/` subdirectory: - -```bash -> cd /path/to/bookstore -> cd build/classes/bookstore/ -> ls - om/ - map/ - Author.php - AuthorPeer.php - AuthorQuery.php - Book.php - BookPeer.php - BookQuery.php - Publisher.php - PublisherPeer.php - PublisherQuery.php -``` - -For every table in the database, Propel creates 3 PHP classes: - -* a _model_ class (e.g. `Book`), which represents a row in the database; -* a _peer_ class (e.g. `BookPeer`), offering static constants and methods mostly for compatibility with previous Propel versions; -* a _query_ class (e.g. `BookQuery`), used to operate on a table to retrieve and update rows - -Propel uses the `phpName` attribute of each table as the base for the PHP class names. - -All these classes are empty, but they inherit from `Base` classes that you will find under the `om/` directory. For instance, if we follow our previous example with the book, the file `/path/to/bookstore/build/classes/bookstore/Book.php` could looks like this : -```php -**Tip**
Never add any code of your own to the classes generated by Propel in the `om/` and `map/` directories; this code would be lost next time you call the `propel-gen` script. - -Basically, all that means is that despite the fact that Propel generates _seven_ classes for each table, you should only care about two of them: the model class and the query class. - -## Building The Database ## - -To save you the burden of defining your model twice, Propel can initialize a database based on the schema, by creating the tables and foreign keys. - -### Building The SQL File ### - -Once again, use the `propel-gen` script to generate the SQL files necessary to create the tables, this time with the "sql" command: - -```bash -cd /path/to/bookstore -propel-gen sql -``` - -The generated SQL definition can be found in the `build/sql/schema.sql` file. The code is optimized for the database driver defined in the `build.properties`. - -### Using The SQL File ### - -Create the database and setup the access permissions using your favorite database client. For instance, to create the `my_db_name` database with MySQL, type: - -```bash -mysqladmin -u root -p create my_db_name -``` - -Now you can use the generated code directly: - -```bash -mysql -u root -p my_db_name < build/sql/schema.sql -``` - ->**Tip**
The `schema.sql` file will DROP any existing table before creating them, which will effectively erase your database. - -Depending on which RDBMS you are using, it may be normal to see some errors (e.g. "unable to DROP...") when you first run this command. This is because some databases have no way of checking to see whether a database object exists before attempting to DROP it (MySQL is a notable exception). It is safe to disregard these errors, and you can always run the script a second time to make sure that the errors are no longer present. - -### Inserting SQL With `propel-gen` ### - -As an alternative to using the generated sql code directly, you can ask Propel to insert it directly into your database. Start by defining the database connection settings in the `build.properties`, as follows: - -```ini -# Connection parameters -propel.database.url = mysql:host=localhost;dbname=my_db_name -propel.database.user = my_db_user -propel.database.password = my_db_password - -# Other examples: -# propel.database.url = sqlite:/path/to/bookstore.db -# propel.database.url = pgsql:host=localhost dbname=my_db_name user=my_db_user password=my_db_password -``` - -The `propel.database.url` setting should be a PDO DSN (see the [PDO documentation](http://www.php.net/pdo) for more information about vendor-specific DSN). The `user` and `password` are only necessary for the `mysql` and `oracle` drivers. - -Then use the `propel-gen` script with the "insert-sql" command to connect to the database and inject the generated SQL code: - -```bash -cd /path/to/bookstore -propel-gen insert-sql -``` - -## Runtime Connection Settings ## - -The database and PHP classes are now ready to be used. But they don't know yet how to communicate with each other at runtime. You must add a configuration file so that the generated object model classes and the shared Propel runtime classes can connect to the database, and log the Propel activity. - -### Writing The XML Runtime Configuration ### - -Create a file called `runtime-conf.xml` at the root of the `bookstore` project, using the following content: - -```xml - - - - - - - mysql - - mysql:host=localhost;dbname=my_db_name - my_db_user - my_db_password - - - - - -``` - -Notice how the `id` attribute of the `` tag matches the connection name defined in the `` tag of the `schema.xml`. This is how Propel maps a database description to a connection. - -Replace the `` and the `` settings with the ones of your database. - -See the [runtime configuration reference](../reference/runtime-configuration) for a more detailed explanation of this file. - ->**Tip**
If you uncomment the `` section, Propel will attempt to instantiate the `Log` class (from the [PEAR Log](http://pear.php.net/package/Log/) package) with the specified parameters and use that to log queries. Propel's statement logging happens at the DEBUG level (7); errors and warnings are logged at the appropriate (non-debug) level. - -### Building the Runtime Configuration ### - -For performance reasons, Propel prefers to use a PHP version of the connection settings rather than the XML file you just defined. So you must use the `propel-gen` script one last time to build the PHP version of the `runtime-conf.xml` configuration: - -```bash -cd /path/to/bookstore -propel-gen convert-conf -``` - -The resulting file can be found under `build/conf/bookstore-conf.php`, where "bookstore" is the name of the project you defined in `build.properties`. - ->**Tip**
As you saw, a Propel project setup requires that you call three commands with the `propel-gen` script: `om`, `sql`, and `convert-conf`. This is so usual that if you call the `propel-gen` script with no parameter, it will execute these three commands in a row: - -```bash -cd /path/to/bookstore -propel-gen -``` - -## Setting Up Propel ## - -This is the final step: initialize Propel in your PHP script. You may wish to do this step in an init or setup script that is included at the beginning of your PHP scripts. - -Here is a sample initialization file: - -```php -**Tip**
If you installed Propel using PEAR, you can replace the first line of this script with the more simple: - -```php -setFirstName('Jane'); -$author->setLastName('Austen'); -$author->save(); -``` - -The column names used in the `setXXX()` methods correspond to the `phpName` attribute of the `` tag in your schema, or to a CamelCase version of the column name if the `phpName` is not set. - -In the background, the call to `save()` results in the following SQL being executed on the database: - -```sql -INSERT INTO author (first_name, last_name) VALUES ('Jane', 'Austen'); -``` - -## Reading Object Properties ## - -Propel maps the columns of a table into properties of the generated objects. For each property, you can use a generated getter to access it. - -```php -getId(); // 1 -echo $author->getFirstName(); // 'Jane' -echo $author->getLastName(); // 'Austen' -``` - -The `id` column was set automatically by the database, since the `schema.xml` defines it as an `autoIncrement` column. The value is very easy to retrieve once the object is saved: just call the getter on the column phpName. - -These calls don't issue a database query, since the `Author` object is already loaded in memory. - -You can also export all the properties of an object by calling one of the following methods: `toArray()`, `toXML()`, `toYAML()`, `toJSON()`, `toCSV()`, and `__toString()`: - -```php -toJSON(); -// {"Id":1,"FirstName":"Jane","LastName":"Austen"} -``` - ->**Tip**
For each export method, Propel also provides an import method counterpart. So you can easily populate an object from an array using `fromArray()`, and from a string using any of `fromXML()`, `fromYAML()`, `fromJSON()`, and `fromCSV()`. - -There are a lot more useful methods offered by the generated objects. You can find an extensive list of these methods in the [Active Record reference](../reference/active-record). - -## Retrieving Rows ## - -Retrieving objects from the database, also referred to as _hydrating_ objects, is essentially the process of executing a SELECT query against the database and populating a new instance of the appropriate object with the contents of each returned row. - -In Propel, you use the generated Query objects to retrieve existing rows from the database. - -### Retrieving by Primary Key ### - -The simplest way to retrieve a row from the database, is to use the generated `findPK()` method. It simply expects the value of the primary key of the row to be retrieved. - -```php -findPK(1); -// now $firstAuthor is an Author object, or NULL if no match was found. -``` - -This issues a simple SELECT SQL query. For instance, for MySQL: - -```sql -SELECT author.id, author.first_name, author.last_name -FROM `author` -WHERE author.id = 1 -LIMIT 1; -``` - -When the primary key consists of more than one column, `findPK()` accepts multiple parameters, one for each primary key column. - ->**Tip**
Every generated Query objects offers a factory method called `create()`. This methods creates a new instance of the query, and allows you to write queries in a single line: - -```php -findPK(1); -``` - -You can also select multiple objects based on their primary keys, by calling the generated `findPKs()` method. It takes an array of primary keys as a parameter: - -```php -findPKs(array(1,2,3,4,5,6,7)); -// $selectedAuthors is a collection of Author objects -``` - -### Querying the Database ### - -To retrieve rows other than by the primary key, use the Query `find()` method. - -An empty Query object carries no condition, and returns all the rows of the table -```php -find(); -// $authors contains a collection of Author objects -// one object for every row of the author table -foreach($authors as $author) { - echo $author->getFirstName(); -} -``` - -To add a simple condition on a given column, use one of the generated `filterByXXX()` methods of the Query object, where `XXX` is a column phpName. Since `filterByXXX()` methods return the current query object, you can continue to add conditions or end the query with the result of the method call. For instance, to filter by first name: - -```php -filterByFirstName('Jane') - ->find(); -``` - -When you pass a value to a `filterByXXX()` method, Propel uses the column type to escape this value in PDO. This protects you from SQL injection risks. - ->**Tip**
`filterByXXX()` is the preferred method for creating queries. It is very flexible and accepts values with wildcards as well as arrays for more complex use cases. See [Column Filter Methods](../reference/model-criteria.html#column_filter_methods) for details. - -You can also easily limit and order the results on a query. Once again, the Query methods return the current Query object, so you can easily chain them: - -```php -orderByLastName() - ->limit(10) - ->find(); -``` - -`find()` always returns a collection of objects, even if there is only one result. If you know that you need a single result, use `findOne()` instead of `find()`. It will add the limit and return a single object instead of an array: - -```php -filterByFirstName('Jane') - ->findOne(); -``` - ->**Tip**
Propel provides magic methods for this simple use case. So you can write the above query as: - -```php -findOneByFirstName('Jane'); -``` - -The Propel Query API is very powerful. The next chapter will teach you to use it to add conditions on related objects. If you can't wait, jump to the [Query API reference](../reference/model-criteria). - ->**Tip**
The `findPk` method and `findOneByXXX` magic methods (for primary key attributes) do not always query the database, but sometimes give you information from a cache. See the section about the [instance pool](#propel-instance-pool) for more information. - -### Using Custom SQL ### - -The `Query` class provides a relatively simple approach to constructing a query. Its database neutrality and logical simplicity make it a good choice for expressing many common queries. However, for a very complex query, it may prove more effective (and less painful) to simply use a custom SQL query to hydrate your Propel objects. - -As Propel uses PDO to query the underlying database, you can always write custom queries using the PDO syntax. For instance, if you have to use a sub-select: - -```php -prepare($sql); -$stmt->execute(array(':name' => 'Austen')); -``` - -With only a little bit more work, you can also populate `Book` objects from the resulting statement. Create a new `PropelObjectCollection` for the `Book` model, and call the `format()` method using the statement: - -```php -setClass('Book'); -$books = $formatter->format($stmt); -// $books contains a collection of Book objects -``` - ->**Tip**
->There are a few important things to remember when using custom SQL to populate Propel: -> -> * The resultset columns must be numerically indexed -> * The resultset must contain all the columns of the table (except lazy-load columns) -> * The resultset must have columns _in the same order_ as they are defined in the `schema.xml` file - -## Updating Objects ## - -Updating database rows basically involves retrieving objects, modifying the contents, and then saving them. In practice, for Propel, this is a combination of what you've already seen in the previous sections: - -```php -findOneByFirstName('Jane'); -$author->setLastName('Austen'); -$author->save(); -``` - -Alternatively, you can update several rows based on a Query using the query object's `update()` method: - -```php -filterByFirstName('Jane') - ->update(array('LastName' => 'Austen')); -``` - -This last method is better for updating several rows at once, or if you didn't retrieve the objects before. - -## Deleting Objects ## - -Deleting objects works the same as updating them. You can either delete an existing object: - -```php -findOneByFirstName('Jane'); -$author->delete(); -``` - -Or use the `delete()` method in the query: - -```php -filterByFirstName('Jane') - ->delete(); -``` - ->**Tip**
A deleted object still lives in the PHP code. It is marked as deleted and cannot be saved anymore, but you can still read its properties: - -```php -isDeleted(); // true -echo $author->getFirstName(); // 'Jane' -``` - -## Query Termination Methods ## - -The Query methods that don't return the current query object are called "Termination Methods". You've already seen some of them: `find()`, `findOne()`, `update()`, `delete()`. There are two more termination methods that you should know about: - -```php -count(); -// You could also count the number of results from a find(), but that would be less effective, -// since it implies hydrating objects just to count them - -// paginate() returns a paginated list of results -$authorPager = AuthorQuery::create()->paginate($page = 1, $maxPerPage = 10); -// This method will compute an offset and a limit -// based on the number of the page and the max number of results per page. -// The result is a PropelModelPager object, over which you can iterate: -foreach ($authorPager as $author) { - echo $author->getFirstName(); -} -// A pager object gives more information -echo $authorPager->getNbResults(); // total number of results if not paginated -echo $authorPager->haveToPaginate(); // return true if the total number of results exceeds the maximum per page -echo $authorPager->getFirstIndex(); // index of the first result in the page -echo $authorPager->getLastIndex(); // index of the last result in the page -$links = $authorPager->getLinks(5); // array of page numbers around the current page; useful to display pagination controls -``` - -## Collections And On-Demand Hydration ## - -The `find()` method of generated Model Query objects returns a `PropelCollection` object. You can use this object just like an array of model objects, iterate over it using `foreach`, access the objects by key, etc. - -```php -limit(5) - ->find(); -foreach ($authors as $author) { - echo $author->getFirstName(); -} -``` - -The advantage of using a collection instead of an array is that Propel can hydrate model objects on demand. Using this feature, you'll never fall short of memory when retrieving a large number of results. Available through the `setFormatter()` method of Model Queries, on-demand hydration is very easy to trigger: - -```php -limit(50000) - ->setFormatter(ModelCriteria::FORMAT_ON_DEMAND) // just add this line - ->find(); -foreach ($authors as $author) { - echo $author->getFirstName(); -} -``` - -In this example, Propel will hydrate the `Author` objects row by row, after the `foreach` call, and reuse the memory between each iteration. The consequence is that the above code won't use more memory when the query returns 50,000 results than when it returns 5. - -`ModelCriteria::FORMAT_ON_DEMAND` is one of the many formatters provided by the Query objects. You can also get a collection of associative arrays instead of objects, if you don't need any of the logic stored in your model object, by using `ModelCriteria::FORMAT_ARRAY`. - -The [ModelCriteria Query API reference](../reference/model-criteria) describes each formatter, and how to use it. - -## Propel Instance Pool ## - -Propel keeps a list of the objects that you already retrieved in memory to avoid calling the same request twice in a PHP script. This list is called the instance pool, and is automatically populated from your past requests. -The instance pool is consulted whenever searching for an object using its primary key via `findPk` or `findOneById` (which is an alias for the former). - -```php -findPk(1); -// Issues a SELECT query -... -// second call -$author2 = AuthorQuery::create()->findPk(1); // or AuthorQuery::create()->findOneById(1); -// Skips the SQL query and returns the existing $author1 object -``` - -The instance pool is located in and managed via an entity's peer class (e.g. `AuthorPeer::getInstanceFromPool((string) $key)`) but there should seldom or never be the need to call it manually. - -That said, remember that in some cases, executing an actual query to the database might be necessary, e.g. if the state of the object differs from the database state. -This is frequently the case if for instance the attributes are set externally by e.g. binding it to a form. -If the values as of the database state of an entity are needed within an entity, for instance in its `save()` function, using `findPk($this->id)` and `findOneById($this->getId())` won't work, since the instance pool will answer this request, giving you the data you already have. -To retrieve the object either use one of the other `find` methods or disable instance pooling for the moment: - -```php -findOneById($this->getId()); - if( null !== $uq ){ - $oldPassword = $uq->getPassword(); - $this->setPassword($oldPassword); - } - - \Propel::enableInstancePooling(); -``` diff --git a/documentation/04-relationships.markdown b/documentation/04-relationships.markdown deleted file mode 100644 index 7dc571fc..00000000 --- a/documentation/04-relationships.markdown +++ /dev/null @@ -1,401 +0,0 @@ ---- -layout: documentation -title: Basic Relationships ---- - -# Basic Relationships # - -The definition of foreign keys in your schema allows Propel to add smart methods to the generated model and query objects. In practice, these generated methods mean that you will never actually have to deal with primary and foreign keys yourself. It makes the task of dealing with relations extremely straightforward. - -## Inserting A Related Row ## - -Propel creates setters for related objects that simplify the foreign key handling. You don't actually have to define a foreign key value. Instead, just set a related object, as follows: - -```php -setFirstName("Leo"); -$author->setLastName("Tolstoy"); -$author->save(); - -$book = new Book(); -$book->setTitle("War & Peace"); -// associate the $author object with the current $book -$book->setAuthor($author); -$book->save(); -``` - -Propel generates the `setAuthor()` method based on the `phpName` attribute of the `` element in the schema. When the attribute is not set, Propel uses the `phpName` of the related table instead. - -Internally, the call to `Book::setAuthor($author)` translates into `Book::setAuthorId($author->getId())`. But you don't actually have to save a Propel object before associating it to another. In fact, Propel automatically "cascades" INSERT statements when a new object has other related objects added to it. - -For one-to-many relationships - meaning, from the other side of a many-to-one relationship - the process is a little different. In the previous example, one `Book` has one `Author`, but one `Author` has many `Books`. From the `Author` point of view, a one-to-many relationships relates it to `Book`. So Propel doesn't generate an `Author::setBook()`, but rather an `Author::addBook()`: - -```php -setTitle("War & Peace"); -$book->save(); - -$author = new Author(); -$author->setFirstName("Leo"); -$author->setLastName("Tolstoy"); -// associate the $book object with the current $author -$author->addBook($book); -$author->save(); -``` - -The result is the same in the database - the `author_id` column of the `book` row is correctly set to the `id` of the `author` row. - -## Save Cascade ## - -As a matter of fact, you don't need to `save()` an object before relating it. Propel knows which objects are related to each other, and is capable of saving all the unsaved objects if they are related to each other. - -The following example shows how to create new `Author` and `Publisher` objects, which are then added to a new `Book` object; all 3 objects are saved when the `Book::save()` method is eventually invoked. - -```php -setFirstName("Leo"); -$author->setLastName("Tolstoy"); -// no need to save the author yet - -$publisher = new Publisher(); -$publisher->setName("Viking Press"); -// no need to save the publisher yet - -$book = new Book(); -$book->setTitle("War & Peace"); -$book->setIsbn("0140444173"); -$book->setPublisher($publisher); -$book->setAuthor($author); -$book->save(); // saves all 3 objects! -``` - -In practice, Propel _'cascades_' the `save()` action to the related objects. - -## Reading Related Object Properties ## - -Just like the related object setters, Propel generates a getter for every relation: - -```php -findPk(1); -$author = $book->getAuthor(); -echo $author->getFirstName(); // 'Leo' -``` - -Since a relationship can also be seen from the other end, Propel allows the foreign table to retrieve the related objects as well: - -```php -findPk(1); -$books = $author->getBooks(); -foreach ($books as $book) { - echo $book->getTitle(); -} -``` - -Notice that Propel generated a `getBooks()` method returning an array of `Book` objects, rather than a `getBook()` method. This is because the definition of a foreign key defines a many-to-one relationship, seen from the other end as a one-to-many relationship. - ->**Tip**
Propel also generates a `countBooks()` methods to get the number of related objects without hydrating all the `Book` objects. for performance reasons, you should prefer this method to `count($author->getBooks())`. - -Getters for one-to-many relationship accept an optional query object. This allows you to hydrate related objects, or retrieve only a subset of the related objects, or to reorder the list of results: - -```php -orderByTitle() - ->joinWith('Book.Publisher'); -$books = $author->getBooks($query); -``` - -## Using Relationships In A Query ## - -### Finding Records Related To Another One ### - -If you need to find objects related to a model object that you already have, you can take advantage of the generated `filterByXXX()` methods in the query objects, where `XXX` is a relation name: - -```php -findPk(1); -$books = BookQuery::create() - ->filterByAuthor($author) - ->orderByTitle() - ->find(); -``` - -You don't need to specify that the `author_id` column of the `Book` object should match the `id` column of the `Author` object. Since you already defined the foreign key mapping in your schema, Propel knows enough to figure it out. - -### Embedding Queries ### - -In SQL queries, relationships often translate to a JOIN statement. Propel abstracts this relational logic in the query objects, by allowing you to _embed_ a related query into another. - -In practice, Propel generates one `useXXXQuery()` method for every relation in the Query objects. So the `BookQuery` class offers a `useAuthorQuery()` and a `usePublisherQuery()` method. These methods return a new Query instance of the related query class, that you can eventually merge into the main query by calling `endUse()`. - -To illustrate this, let's see how to write the following SQL query with the Propel Query API: - -```sql -SELECT book.* -FROM book INNER JOIN author ON book.AUTHOR_ID = author.ID -WHERE book.ISBN = '0140444173' AND author.FIRST_NAME = 'Leo' -ORDER BY book.TITLE ASC -LIMIT 10; -``` - -That would simply give: - -```php -filterByISBN('0140444173') - ->useAuthorQuery() // returns a new AuthorQuery instance - ->filterByFirstName('Leo') // this is an AuthorQuery method - ->endUse() // merges the Authorquery in the main Bookquery and returns the BookQuery - ->orderByTitle() - ->limit(10) - ->find(); -``` - -Propel knows the columns to use in the `ON` clause from the definition of foreign keys in the schema. The ability to use methods of a related Query object allows you to keep your model logic where it belongs. - -Of course, you can embed several queries to issue a query of any complexity level: - -```php -useBookQuery() - ->usePublisherQuery() - ->filterByName('Viking Press') - ->endUse() - ->endUse() - ->find(); -``` - -You can see how the indentation of the method calls provide a clear explanation of the embedding logic. That's why it is a good practice to format your Propel queries with a single method call per line, and to add indentation every time a `useXXXQuery()` method is used. - -## Many-to-Many Relationships ## - -Databases typically use a cross-reference table, or junction table, to materialize the relationship. For instance, if the `user` and `group` tables are related by a many-to-many relationship, this happens through the rows of a `user_group` table. To inform Propel about the many-to-many relationship, set the `isCrossRef` attribute of the cross reference table to true: - ->**Warning**
Use only `isCrossRef` with a middle table that is design the same way as the example (user_group), two foreign keys that are also primary keys. If you use a different schema for the middle table, use it at your own risk - -```xml - - - -
- - - - -
- - - - - - - - - - -
-``` - -Once you rebuild your model, the relationship is seen as a one-to-many relationship from both the `User` and the `Group` models. That means that you can deal with adding and reading relationships the same way as you usually do: - -```php -setName('John Doe'); -$group = new Group(); -$group->setName('Anonymous'); -// relate $user and $group -$user->addGroup($group); -// save the $user object, the $group object, and a new instance of the UserGroup class -$user->save(); -``` - -The same happens for reading related objects ; Both ends see the relationship as a one-to-many relationship: - -```php -getUsers(); -$nbUsers = $group->countUsers(); -$groups = $user->getGroups(); -$nbGroups = $user->countGroups(); -``` - -Just like regular related object getters, these generated methods accept an optional query object, to further filter the results. - -To facilitate queries, Propel also adds new methods to the `UserQuery` and `GroupQuery` classes: - -```php -filterByGroup($group) - ->find(); -$groups = GroupQuery::create() - ->filterByUser($user) - ->find(); -``` - -## One-to-One Relationships ## - -Propel supports the special case of one-to-one relationships. These relationships are defined when the primary key is also a foreign key. For example : - -```xml - - - -
- - - - - - - - -
-``` - -Because the primary key of the `bookstore_employee_account` is also a foreign key to the `bookstore_employee` table, Propel interprets this as a one-to-one relationship and will generate singular methods for both sides of the relationship (`BookstoreEmployee::getBookstoreEmployeeAccount()`, and `BookstoreEmployeeAccount::getBookstoreEmployee()`). - -## On-Update and On-Delete Triggers # - -Propel also supports the _ON UPDATE_ and _ON DELETE_ aspect of foreign keys. These properties can be specified in the `` tag using the `onUpdate` and `onDelete` attributes. Propel supports values of `CASCADE`, `SETNULL`, and `RESTRICT` for these attributes. For databases that have native foreign key support, these trigger events will be specified at the database level when the foreign keys are created. For databases that do not support foreign keys, this functionality will be emulated by Propel. - -```xml - - - - - - - -
-``` - -In the example above, the `review` rows will be automatically removed if the related `book` row is deleted. - -## Minimizing Queries ## - -Even if you use a foreign query, Propel will issue new queries when you fetch related objects: - -```php -useAuthorQuery() - ->filterByFirstName('Leo') - ->endUse() - ->findOne(); -$author = $book->getAuthor(); // Needs another database query -``` - -Propel allows you to retrieve the main object together with related objects in a single query. You just use the `with()` method to specify which objects the main object should be hydrated with. - -```php -useAuthorQuery() - ->filterByFirstName('Leo') - ->endUse() - ->with('Author') - ->findOne(); -$author = $book->getAuthor(); // Same result, with no supplementary query -``` - -Since the call to `with()` adds the columns of the related object to the SELECT part of the query, and uses these columns to populate the related object, that means that a query using `with()` is slower and consumes more memory. So use it only when you actually need the related objects afterwards. - -If you don't want to add a filter on a related object but still need to hydrate it, calling `useXXXQuery()`, `endUse()`, and then `with()` can be a little cumbersome. For this case, Propel provides a proxy method called `joinWith()`. It expects a string made of the initial query name and the foreign query name. For instance: - -```php -joinWith('Book.Author') - ->findOne(); -$author = $book->getAuthor(); // Same result, with no supplementary query -``` - -`with()` and `joinWith()` are not limited to immediate relationships. As a matter of fact, just like you can nest `use()` calls, you can call `with()` several times to populate a chain of objects: - -```php -joinWith('Review.Book') - ->joinWith('Book.Author') - ->joinWith('Book.Publisher') - ->findOne(); -$book = $review->getBook() // No additional query needed -$author = $book->getAuthor(); // No additional query needed -$publisher = $book->getPublisher(); // No additional query needed -``` - -So `with()` is very useful to minimize the number of database queries. As soon as you see that the number of queries necessary to perform an action is proportional to the number of results, adding a `with()` call is the trick to get down to a more reasonnable query count. - ->**Tip**
`with()` also works for left joins on one-to-many relationships, but you mustn't use a `limit()` in the query in this case. This is because Propel has no way to determine the actual number of rows of the main object in such a case. - -```php -leftJoinWith('Author.Book') - ->find(); -// this does not work -$authors = AuthorQuery::create() - ->leftJoinWith('Author.Book') - ->limit(5) - ->find(); -``` - -However, it is quite easy to achieve hydration of related objects with only one additional query: - -```php -limit(5) - ->find(); -// $authors is a PropelObjectCollection -$authors->populateRelation('Book'); -foreach ($authors as $author) { - // now you can iterate over each author's book without further queries - foreach ($author->getBooks() as $book) { // no database query, the author already has a Books collection - // do stuff with $book and $author - } -} -``` - -## Model-Only Relationships ## - -Propel models can share relationships even though the underlying tables aren't linked by a foreign key. This ability may be of great use when using Propel on top of a legacy database. - -For example, a `review` table designed for a MyISAM database engine is linked to a `book` table by a simple `book_id` column: - -```xml - - - - -
-``` - -To enable a model-only relationship, add a `` tag using the `skipSql` attribute, as follows: - -```xml - - - - - - - - -
-``` - -Such a foreign key is not translated into SQL when Propel builds the table creation or table migration code. It can be seen as a "virtual foreign key". However, on the PHP side, the `Book` model actually has a one-to-many relationship with the `Review` model. The generated `ActiveRecord` and `ActiveQuery` classes take advantage of this relationship to offer smart getters and filters. diff --git a/documentation/05-validators.markdown b/documentation/05-validators.markdown deleted file mode 100644 index ba27631f..00000000 --- a/documentation/05-validators.markdown +++ /dev/null @@ -1,246 +0,0 @@ ---- -layout: documentation -title: Validators ---- - -# Validators # - -Validators help you to validate an input before persisting it to the database. In Propel, validators are rules describing what type of data a column accepts. Validators are referenced in the `schema.xml` file, using `` tags. - -Validators are applied at the PHP level, they are not created as constraints on the database itself. That means that if you also use another language to work with the database, the validator rules will not be enforced. -You can also apply multiple rule entries per validator entry in the schema.xml file. - -## Overview ## - -In the following example, the `username` column is defined to have a minimum length of 4 characters: - -```xml - - - - - - -
-``` - -Every column rule is represented by a `` tag. A `` is a set of `` tags bound to a column. - -At runtime, you can validate an instance of the model by calling the `validate()` method: - -```php -setUsername("foo"); // only 3 in length, which is too short... -if ($user->validate()) { - // no validation errors, so the data can be persisted - $user->save(); -} else { - // Something went wrong. - // Use the validationFailures to check what - foreach ($objUser->getValidationFailures() as $failure) { - echo $failure->getMessage() . "\n"; - } -} -``` - -`validate()` returns a boolean. If the validation failed, you can access the array `ValidationFailed` objects by way of the `getValidationFailures()` method. Each `ValidationFailed` instance gives access to the column, the message and the validator that caused the failure. - -## Core Validators ## - -Propel bundles a set of validators that should help you deal with the most common cases. - -### MatchValidator ### - -The `MatchValidator` is used to run a regular expression of choice against the column. Note that this is a `preg`, not `ereg` (check [the preg_match documentation](http://www.php.net/preg_match) for more information about regexps). - -```xml - - - - -``` - ->**Tip**
Propel expects your pattern defined within `value` either without delimiters or in case you need a delimiter to be surrounded by `/`. Other pattern delimiters are not supported. - -### NotMatchValidator ### - -Opposite of `MatchValidator`, this validator returns false if the regex returns true - -```xml - - - - - -``` - ->**Tip**
Propel expects your pattern defined within `value` either without delimiters or in case you need a delimiter to be surrounded by `/`. Other pattern delimiters are not supported. - -### MaxLengthValidator ### - -When you want to limit the size of the string to be inserted in a column, use the `MaxLengthValidator`. Internally, it uses `strlen()` to get the length of the string. For instance, some database completely ignore the length of `LONGVARCHAR` columns; you can enforce it using a validator: - -```xml - - - - -``` - ->**Tip**
If you have specified the `size` attribute in the `` tag, you don't have to specify the `value` attribute in the validator rule again, as this is done automatically. - ->**Tip**
The `MaxLengthValidator` uses `mb_strlen` internally when available. Therefore make sure you defined the correct `mb_internal_encoding` when handling e.g. UTF-8 Strings. - -### MinLengthValidator ### - -```xml - - - - -``` - -### MaxValueValidator ### - -To limit the value of an integer column, use the `MaxValueValidator`. Note that this validator uses a non-strict comparison ('less than or equal'): - -```xml - - - - -``` - -### MinValueValidator ### - -```xml - - - - -``` - ->**Tip**
You can run multiple validators against a single column. - -```xml - - - - - -``` - -### RequiredValidator ### - -This validator checks the same rule as a `required=true` on the column at the database level. This, however, will give you a clean error to work with. - -```xml - - - - -``` - -### UniqueValidator ### - -To check whether the value already exists in the table, use the `UniqueValidator`: - -```xml - - - - -``` - -### ValidValuesValidator ### - -This rule restricts the valid values to a list delimited by a pipe ('|'). - -```xml - - - - -``` - -### TypeValidator ### - -Restrict values to a certain PHP type using the `TypeValidator`: - -```xml - - - - -``` - -## Adding A Custom Validator ## - -You can easily add a custom validator. A validator is a class extending `BasicValidator` providing a public `isValid()` method. For instance: - -```php -` tag. So `$map->getValue()` returns the `value` attribute. - ->**Tip**
Make sure that `isValid()` returns a boolean, so really true or false. Propel is very strict about this. Returning a mixed value just won't do. - -To enable the new validator on a column, add a corresponding `` in your schema and use 'class' as the rule `name`. - -```xml - - - -``` - -The `class` attribute of the `` tag should contain a path to the validator class accessible from the include_path, where the directory separator is replaced by a dot. diff --git a/documentation/06-transactions.markdown b/documentation/06-transactions.markdown deleted file mode 100644 index 8d7712e2..00000000 --- a/documentation/06-transactions.markdown +++ /dev/null @@ -1,282 +0,0 @@ ---- -layout: documentation -title: Transactions ---- - -# Transactions # - -Database transactions are the key to assure the data integrity and the performance of database queries. Propel uses transactions internally, and provides a simple API to use them in your own code. - ->**Tip**
If the [ACID](http://en.wikipedia.org/wiki/ACID) acronym doesn't ring a bell, you should probably learn some [fundamentals about database transactions](http://en.wikipedia.org/wiki/Database_transaction) before reading further. - -## Wrapping Queries Inside a Transaction ## - -Propel uses PDO as database abstraction layer, and therefore uses [PDO's built-in support for database transactions](http://www.php.net/manual/en/pdo.transactions.php). The syntax is the same, as you can see in the classical "money transfer" example: - -```php -beginTransaction(); - - try { - // remove the amount from $fromAccount - $fromAccount->setValue($fromAccount->getValue() - $amount); - $fromAccount->save($con); - // add the amount to $toAccount - $toAccount->setValue($toAccount->getValue() + $amount); - $toAccount->save($con); - - $con->commit(); - } catch (Exception $e) { - $con->rollback(); - throw $e; - } -} -``` - -The transaction statements are `beginTransaction()`, `commit()` and `rollback()`, which are methods of the PDO connection object. Transaction methods are typically used inside a `try/catch` block. The exception is rethrown after rolling back the transaction: That ensures that the user knows that something wrong happened. - -In this example, if something wrong happens while saving either one of the two accounts, an `Exception` is thrown, and the whole operation is rolled back. That means that the transfer is cancelled, with an insurance that the money hasn't vanished (that's the A in ACID, which stands for "Atomicity"). If both account modifications work as expected, the whole transaction is committed, meaning that the data changes enclosed in the transaction are persisted in the database. - -Tip: In order to build a transaction, you need a connection object. The connection object for a Propel model is always available through `Propel::getConnection([ModelName]Peer::DATABASE_NAME)`. - -## Denormalization And Transactions ## - -Another example of the use of transactions is for [denormalized schemas](http://en.wikipedia.org/wiki/Denormalization). - -For instance, suppose that you have an `Author` model with a one to many relationship to a `Book` model. every time you need to display the number of books written by an author, you call `countBooks()` on the author object, which issues a new query to the database: - -```php -
    - -
  • getName() ?> (countBooks() ?> books)
  • - -
-``` - -If you have a large number of authors and books, this simple code snippet can be a real performance blow to your application. The usual way to optimize it is to _denormalize_ your schema by storing the number of books by each author in a new `nb_books` column, in the `author` table. - -```xml - - - - - -
-``` - -You must update this new column every time you save or delete a `Book` object; this will make write queries a little slower, but read queries much faster. Fortunately, Propel model objects support pre- and post- hooks for the `save()` and `delete()` methods, so this is quite easy to implement: - -```php -updateNbBooks($con); - } - - public function postDelete(PropelPDO $con) - { - $this->updateNbBooks($con); - } - - public function updateNbBooks(PropelPDO $con) - { - $author = $this->getAuthor(); - $nbBooks = $author->countBooks($con); - $author->setNbBooks($nbBooks); - $author->save($con); - } -} -``` - -The `BaseBook::save()` method wraps the actual database INSERT/UPDATE query inside a transaction, together with any other query registered in a pre- or post- save hook. That means that when you save a book, the `postSave()` code is executed in the same transaction as the actual `$book->save()` method. Everything happens as is the code was the following: - -```php -beginTransaction(); - - try { - // insert/update query for the current object - $this->doSave($con); - - // postSave hook - $author = $this->getAuthor(); - $nbBooks = $author->countBooks($con); - $author->setNbBooks($nbBooks); - $author->save($con); - - $con->commit(); - } catch (Exception $e) { - $con->rollback(); - throw $e; - } - } -} -``` - -In this example, the `nb_books` column of the `author` table will always we synchronized with the number of books. If anything happens during the transaction, the saving of the book is rolled back, as well as the `nb_books` column update. The transaction serves to preserve data consistency in a denormalized schema ("Consistency" stands for the C in ACID). - ->**Tip**
Check the [behaviors documentation]() for details about the pre- and post- hooks in Propel model objects. - -## Nested Transactions ## - -Some RDBMS offer the ability to nest transactions, to allow partial rollback of a set of transactions. PDO does not provide this ability at the PHP level; nevertheless, Propel emulates nested transactions for all supported database engines: - -```php -beginTransaction(); - try { - $c = new Criteria(); - $c->add(BookPeer::PRICE, null, Criteria::ISNULL); - BookPeer::doDelete($c, $con); - $con->commit(); - } catch (Exception $e) { - $con->rollback(); - throw $e; - } -} - -function deleteAuthorsWithNoEmail(PropelPDO $con) -{ - $con->beginTransaction(); - try { - $c = new Criteria(); - $c->add(AuthorPeer::EMAIL, null, Criteria::ISNULL); - AuthorPeer::doDelete($c, $con); - $con->commit(); - } catch (Exception $e) { - $con->rollback(); - throw $e; - } -} - -function cleanup(PropelPDO $con) -{ - $con->beginTransaction(); - try { - deleteBooksWithNoPrice($con); - deleteAuthorsWithNoEmail($con); - $con->commit(); - } catch (Exception $e) { - $con->rollback(); - throw $e; - } -} -``` - -All three functions alter data in a transaction, ensuring data integrity for each. In addition, the `cleanup()` function actually executes two nested transactions inside one main transaction. - -Propel deals with this case by seeing only the outermost transaction, and ignoring the `beginTransaction()`, `commit()` and `rollback()` statements of nested transactions. If nothing wrong happens, then the last `commit()` call (after both `deleteBooksWithNoPrice()` and `deleteAuthorsWithNoEmail()` end) triggers the actual database commit. However, if an exception is thrown in either one of these nested transactions, it is escalated to the main `catch` statement in `cleanup()` so that the entire transaction (starting at the main `beginTransaction()`) is rolled back. - -So you can use transactions everywhere it's necessary in your code, without worrying about nesting them. Propel will always commit or rollback everything altogether, whether the RDBMS supports nested transactions or not. - ->**Tip**
This allows you to wrap all your application code inside one big transaction for a better integrity. - -## Using Transactions To Boost Performance ## - -A database transaction has a cost in terms of performance. In fact, for simple data manipulation, the cost of the transaction is more important than the cost of the query itself. Take the following example: - -```php -setTitle($i . ': A Space Odyssey'); - $book->save($con); -} -``` - -As explained earlier, Propel wraps every save operation inside a transaction. In terms of execution time, this is very expensive. Here is how the above code would translate to MySQL in an InnodDB table: - -```sql -BEGIN; -INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'0: A Space Odyssey'); -COMMIT; -BEGIN; -INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'1: A Space Odyssey'); -COMMIT; -BEGIN; -INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'2: A Space Odyssey'); -COMMIT; -... -``` - -You can take advantage of Propel's nested transaction capabilities to encapsulate the whole loop inside one single transaction. This will reduce the execution time drastically: - -```php -beginTransaction(); -for ($i=0; $i<2002; $i++) -{ - $book = new Book(); - $book->setTitle($i . ': A Space Odyssey'); - $book->save($con); -} -$con->commit(); -``` - -The transactions inside each `save()` will become nested, and therefore not translated into actual database transactions. Only the outmost transaction will become a database transaction. So this will translate to MySQL as: - -```sql -BEGIN; -INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'0: A Space Odyssey'); -INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'1: A Space Odyssey'); -INSERT INTO book (`ID`,`TITLE`) VALUES (NULL,'2: A Space Odyssey'); -... -COMMIT; -``` - -In practice, encapsulating a large amount of simple queries inside a single transaction significantly improves performance. - -Tip: Until the final `commit()` is called, most database engines lock updated rows, or even tables, to prevent any query outside the transaction from seeing the partially committed data (this is how transactions preserve Isolation, which is the I in ACID). That means that large transactions will queue every other queries for potentially a long time. Consequently, use large transactions only when concurrency is not a requirement. - -## Why Is The Connection Always Passed As Parameter? ## - -All the code examples in this chapter show the connection object passed a a parameter to Propel methods that trigger a database query: - -```php -setValue($fromAccount->getValue() - $amount); -$fromAccount->save($con); -``` - -The same code works without explicitly passing the connection object, because Propel knows how to get the right connection from a Model: - -```php -setValue($fromAccount->getValue() - $amount); -$fromAccount->save(); -``` - -However, it's a good practice to pass the connection explicitly, and for three reasons: - -* Propel doesn't need to look for a connection object, and this results in a tiny boost in performance. -* You can use a specific connection, which is required in distributed (master/slave) environments, in order to distinguish read and write operations. -* Most importantly, transactions are tied to a single connection. You can't enclose two queries using different connections in a single transaction. So it's very useful to identify the connection you want to use for every query, as Propel will throw an exception if you use the wrong connection. - -## Limitations ## - -* Currently there is no support for row locking (e.g. `SELECT blah FOR UPDATE`). -* You must rethrow the exception caught in the `catch` statement of nested transactions, otherwise there is a risk that the global rollback doesn't occur. -* True nested transactions, with partial rollback, are only possible in MSSQL, and can be emulated in other RDBMS through savepoints. This feature may be added to Propel in the future, but for the moment, only the outermost PHP transaction triggers a database transaction. -* If you rollback a partially executed transaction and ignore the exception thrown, there are good chances that some of your objects are out of sync with the database. The good practice is to always let a transaction exception escalate until it stops the script execution. diff --git a/documentation/07-behaviors.markdown b/documentation/07-behaviors.markdown deleted file mode 100644 index 54a95f94..00000000 --- a/documentation/07-behaviors.markdown +++ /dev/null @@ -1,433 +0,0 @@ ---- -layout: documentation -title: Behaviors ---- - - -# Behaviors # - -Behaviors are a great way to package model extensions for reusability. They are the powerful, versatile, fast, and help you organize your code in a better way. - -## Pre and Post Hooks For `save()` And `delete()` Methods ## - -The `save()` and `delete()` methods of your generated objects are easy to override. In fact, Propel looks for one of the following methods in your objects and executes them when needed: - -```php - - ... - - -``` - -Then, you can force the update of the `created_at` column before every insertion as follows: - -```php -setCreatedAt(time()); - return true; - } -} -``` - -Whenever you call `save()` on a new object, Propel now executes the `preInsert()` method on this objects and therefore update the `created_at` column: - -```php -setTitle('War And Peace'); -$b->save(); -echo $b->getCreatedAt(); // 2009-10-02 18:14:23 -``` - -_Warning_: If you implement `preInsert()`, `preUpdate()`, `preSave()` or `preDelete()`, these methods **must return a boolean value**. Any return value other than `true` stops the action (save or delete). This is a neat way to bypass persistence on some cases, but can also create unexpected problems if you forget to return `true`. - ->**Tip**
Since this feature adds a small overhead to write operations, you can deactivate it completely in your build properties by setting `propel.addHooks` to `false`. - -```ini -# ------------------- -# TEMPLATE VARIABLES -# ------------------- -propel.addHooks = false -``` - -## Introducing Behaviors ## - -When several of your custom model classes end up with similar methods added, it is time to refactor the common code. - -For example, you may want to add the same ability you gave to `Book` to all the other objects in your model. Let's call this the "Timestampable behavior", because then all of your rows have a timestamp marking their creation. In order to achieve this behavior, you have to repeat the same operations on every table. First, add a `created_at` column to the other tables: - -```xml - - ... - -
- - ... - -
-``` - -Then, add a `preInsert()` hook to the object stub classes: - -```php -setCreatedAt(time()); - } -} - -class Author extends BaseAuthor -{ - public function preInsert() - { - $this->setCreatedAt(time()); - } -} -``` - -Even if the code of this example is very simple, the repetition of code is already too much. Just imagine a more complex behavior, and you will understand that using the copy-and-paste technique soon leads to a maintenance nightmare. - -Propel offers three ways to achieve the refactoring of the common behavior. The first one is to use a custom builder during the build process. This can work if all of your models share one single behavior. The second way is to use table inheritance. The inherited methods then offer limited capabilities. And the third way is to use Propel behaviors. This is the right way to refactor common model logic. - -Behaviors are special objects that use events called during the build process to enhance the generated model classes. Behaviors can add attributes and methods to both the Peer and model classes, they can modify the course of some of the generated methods, and they can even modify the structure of a database by adding columns or tables. - -For instance, Propel bundles a behavior called `timestampable`, which does exactly the same thing as described above. But instead of adding columns and methods by hand, all you have to do is to declare it in a `` tag in your `schema.xml`, as follows: - -```xml - - ... - -
- - ... - -
-``` - -Then rebuild your model, and there you go: two columns, `created_at` and `updated_at`, were automatically added to both the `book` and `author` tables. Besides, the generated `BaseBook` and `BaseAuthor` classes already contain the code necessary to auto-set the current time on creation and on insertion. - -## Bundled Behaviors ## - -Propel currently bundles several behaviors. Check the behavior documentation for details on usage: - -* [aggregate_column](../behaviors/aggregate-column) -* [alternative_coding_standards](../behaviors/alternative-coding-standards) -* [archivable](../behaviors/archivable) (Replace the deprecated `soft-delete` behavior) -* [auto_add_pk](../behaviors/auto-add-pk) -* [delegate](../behaviors/delegate) -* [timestampable](../behaviors/timestampable) -* [sluggable](../behaviors/sluggable) -* [soft_delete](../behaviors/soft-delete) **Deprecated** -* [sortable](../behaviors/sortable) -* [nested_set](../behaviors/nested-set) -* [versionable](../behaviors/versionable) -* [i18n](../behaviors/i18n) -* [query_cache](../behaviors/query-cache) -* And [concrete_inheritance](./09-inheritance), documented in the Inheritance Chapter even if it's a behavior - -You can also look at [user contributed behaviors](../cookbook/user-contributed-behaviors.html). - -Behaviors bundled with Propel require no further installation and work out of the box. - -## Customizing Behaviors ## - -Behaviors often offer some parameters to tweak their effect. For instance, the `timestampable` behavior allows you to customize the names of the columns added to store the creation date and the update date. The behavior customization occurs in the `schema.xml`, inside `` tags nested in the `` tag. So let's set the behavior to use `created_on` instead of `created_at` for the creation date column name (and same for the update date column): - -```xml - - ... - - - - -
-``` - -If the columns already exist in your schema, a behavior is smart enough not to add them one more time. - -```xml - - ... - - - - - - -
-``` - -## Using Third-Party Behaviors ## - -As a Propel behavior can be packaged into a single class, behaviors are quite easy to reuse and distribute across several projects. All you need to do is to copy the behavior file into your project, and declare it in `build.properties`, as follows: - -```ini -# ---------------------------------- -# B E H A V I O R S E T T I N G S -# ---------------------------------- - -propel.behavior.timestampable.class = propel.engine.behavior.timestampable.TimestampableBehavior -# Add your custom behavior paths here -propel.behavior.formidable.class = path.to.FormidableBehavior -``` - -Propel will then find the `FormidableBehavior` class whenever you use the `formidable` behavior in your schema: - -```xml - - ... - - -
-``` - ->**Tip**
If you use autoloading during the build process, and if the behavior classes benefit from the autoloading, then you don't even need to declare the path to the behavior class. - -## Applying a Behavior To All Tables ## - -You can add a `` tag directly under the `` tag. That way, the behavior will be applied to all the tables of the database. - -```xml - - - - ... -
- - ... -
-
-``` - -In this example, both the `book` and `author` table benefit from the `timestampable` behavior, and therefore automatically update their `created_at` and `updated_at` columns upon saving. - -Going one step further, you can even apply a behavior to all the databases of your project, provided the behavior doesn't need parameters - or can use default parameters. To add a behavior to all databases, simply declare it in the project's `build.properties` under the `propel.behavior.default` key, as follows: - -```ini -propel.behavior.default = soft_delete, timestampable -``` - -## Writing a Behavior ## - -Check the behaviors bundled with Propel to see how to implement your own behavior: they are the best starting point to understanding the power of behaviors and builders. - -### Modifying the Data Model ### - -Behaviors can modify their table, and even add another table, by implementing the `modifyTable` method. In this method, use `$this->getTable()` to retrieve the table buildtime model and manipulate it. - -For instance, to add a new column named 'foo' in the current table, add the following method to a behavior: - -```php - 'foo', - ); - - public function modifyTable() - { - $table = $this->getTable(); - $columnName = $this->getParameter('column_name'); - // add the column if not present - if(!$this->getTable()->containsColumn($columnName)) { - $column = $this->getTable()->addColumn(array( - 'name' => $columnName, - 'type' => 'INTEGER', - )); - } - } -} -``` - -### Modifying the ActiveRecord Classes ### - -Behaviors can add code to the generated model object by implementing one of the following methods: - -```php -objectAttributes() // add attributes to the object -objectMethods() // add methods to the object -preInsert() // add code to be executed before insertion of a new object -postInsert() // add code to be executed after insertion of a new object -preUpdate() // add code to be executed before update of an existing object -postUpdate() // add code to be executed after update of an existing object -preSave() // add code to be executed before saving an object (new or existing) -postSave() // add code to be executed after saving an object (new or existing) -preDelete() // add code to be executed before deleting an object -postDelete() // add code to be executed after deleting an object -objectCall() // add code to be executed inside the object's __call() -objectFilter(&$script) // do whatever you want with the generated code, passed as reference -``` - -### Modifying the Query Classes ### - -Behaviors can also add code to the generated query objects by implementing one of the following methods: - -```php -queryAttributes() // add attributes to the query class -queryMethods() // add methods to the query class -preSelectQuery() // add code to be executed before selection of a existing objects -preUpdateQuery() // add code to be executed before update of a existing objects -postUpdateQuery() // add code to be executed after update of a existing objects -preDeleteQuery() // add code to be executed before deletion of a existing objects -postDeleteQuery() // add code to be executed after deletion of a existing objects -queryFilter(&$script) // do whatever you want with the generated code, passed as reference -``` - -### Modifying the Peer Classes ### - -Behaviors can also add code to the generated peer objects by implementing one of the following methods: - -```php -staticAttributes() // add static attributes to the peer class -staticMethods() // add static methods to the peer class -preSelect() // adds code before every select query -peerFilter(&$script) // do whatever you want with the generated code, passed as reference -``` - -### Adding New Classes ### - -Behaviors can add entirely new classes based on the data model. To build a new class, a behavior must provide an array of builder class names (in return to `getAdditionalBuilders()`) and the builder classes themselves. - -For instance, to add an empty child class for the ActiveRecord class, create the following behavior: - -```php -getStubObjectBuilder()->getUnprefixedClassname() . 'Child'; - } - - protected function addClassOpen(&$script) - { - $table = $this->getTable(); - $tableName = $table->getName(); - $script .= " -/** - * Test class for Additional builder enabled on the '$tableName' table. - * - */ -class " . $this->getClassname() . " extends " . $this->getStubObjectBuilder() . " -{ -"; - } - - protected function addClassBody(&$script) - { - $script .= " // no code"; - } - - protected function addClassClose(&$script) - { - $script .= " -}"; - } -} -``` - -By default, classes added by a behavior are generated each time the model is rebuilt. To limit the generation to the first time (for instance for stub classes), add a public `$overwrite` attribute to the builder and set it to `false`. - -You can set the additional class to be generated in a subfolder by implementing the `getPackage()` method. - -### Replacing or Removing Existing Methods ### - -Behaviors can modify existing methods even if no hook is called in the builders, thanks to a service class called `PropelPHPParser`. This class can remove a method, replace a method by another one, or add a new method before or after an existing one. - -The `PropelPHPParser` constructor takes a string as input, so it is best used inside "filter" hooks in behaviors. For instance, to replace the `findPk()` method in the generated query class by a custom one, use the following syntax: - -```php -prepare(\$query); - \$stmt->bindValue(1, \$key); - \$res = \$stmt->execute(); - // hydrate ActiveRecord objects with the result - \$formatter = new PropelObjectFormatter(); - \$formatter->setClass('%s'); - return \$formatter->formatOne(\$res); -} -"; - $table = $this->getTable(); - $newFindPkMethod = sprintf($newFindPkMethod, $table->getName(), $table->getPhpName(), $table->getPhpName()); - $parser = new PropelPHPParser($script, true); - $parser->replaceMethod('findPk', $newFindPkMethod); - $script = $parser->getCode(); - } -} -``` - -The `PropelPHPParser` class provides the following utility methods: - -```php -removeMethod($methodName) -replaceMethod($methodName, $newCode) -addMethodAfter($methodName, $newCode) -addMethodBefore($methodName, $newCode) -``` - -### Add a new Interface ### - -Behaviors can also add new Interfaces to the generated class. It is achieved by adding the following 3 lines to the `objectFilter` method. - -```php -**Tip**
Install PEAR Log using `pear install Log` - - -### Logger Configuration ### - -The Propel log handler is configured in the `` section of your project's `runtime-conf.xml` file. Here is the accepted format for this section with the default values that Propel uses: - -```xml - - - - file - ./propel.log - propel - 7 - - - - ... - - -``` - ->**Tip**
Remember to run `propel-gen convert-conf` after modifying the configuration files. - -Using these parameters, Propel creates a `file` Log handler in the background, and keeps it for later use: - -```php -` nested elements may vary, depending on which log handler you are using. Most common accepted logger types are `file`, `console`, `syslog`, `display`, `error_log`, `firebug`, and `sqlite`. Refer to the [PEAR::Log](http://www.indelible.org/php/Log/guide.html#standard-log-handlers) documentation for more details on log handlers configuration and options. - -Note that the `` tag needs to correspond to the integer represented by one of the `PEAR_LOG_*` constants: - -|Constant |Value |Description -|-------------------|-------|------------------------- -|PEAR_LOG_EMERG |0 |System is unusable -|PEAR_LOG_ALERT |1 |Immediate action required -|PEAR_LOG_CRIT |2 |Critical conditions -|PEAR_LOG_ERR |3 |Error conditions -|PEAR_LOG_WARNING |4 |Warning conditions -|PEAR_LOG_NOTICE |5 |Normal but significant -|PEAR_LOG_INFO |6 |Informational -|PEAR_LOG_DEBUG |7 |Debug-level messages - -### Logging Messages ### - -Use the static `Propel::log()` method to log a message using the configured log handler: - -```php -setName('foo'); -Propel::log('uh-oh, something went wrong with ' . $myObj->getName(), Propel::LOG_ERR); -``` - -You can log your own messages from the generated model objects by using their `log()` method, inherited from `BaseObject`: - -```php -log('uh-oh, something went wrong', Propel::LOG_ERR); -``` - -The log messages will show up in the log handler defined in `runtime-conf.xml` (`propel.log` file by default) as follows: - -```text -Oct 04 00:00:18 [error] uh-oh, something went wrong with foo -Oct 04 00:00:18 [error] MyObj: uh-oh, something went wrong -``` - ->**Tip**
All serious errors coming from the Propel core do not only issue a log message, they are also thrown as `PropelException`. - -### Using An Alternative PEAR Log Handler ### - -In many cases you may wish to integrate Propel's logging facility with the rest of your web application. In `runtime-conf.xml`, you can customize a different PEAR logger. Here are a few examples: - -_Example 1:_ Using `display` handler (for output to HTML) - -```xml - - display - 6 - -``` - -_Example 2:_ Using `syslog` handler - -```xml - - syslog - 8 - propel - 6 - -``` - -### Using A Custom Logger ### - -If you omit the `` section of your `runtime-conf.xml`, then Propel will not setup _any_ logging for you. In this case, you can set a custom logging facility and pass it to Propel at runtime. - -Here's an example of how you could configure your own logger and then set Propel to use it: - -```php -log($m, Propel::LOG_EMERG); - } - public function alert($m) - { - $this->log($m, Propel::LOG_ALERT); - } - public function crit($m) - { - $this->log($m, Propel::LOG_CRIT); - } - public function err($m) - { - $this->log($m, Propel::LOG_ERR); - } - public function warning($m) - { - $this->log($m, Propel::LOG_WARNING); - } - public function notice($m) - { - $this->log($m, Propel::LOG_NOTICE); - } - public function info($m) - { - $this->log($m, Propel::LOG_INFO); - } - public function debug($m) - { - $this->log($m, Propel::LOG_DEBUG); - } - - public function log($message, $priority) - { - $color = $this->priorityToColor($priority); - echo '

' . $message . '

'; - } - - private function priorityToColor($priority) - { - switch($priority) { - case Propel::LOG_EMERG: - case Propel::LOG_ALERT: - case Propel::LOG_CRIT: - case Propel::LOG_ERR: - return 'red'; - break; - case Propel::LOG_WARNING: - return 'orange'; - break; - case Propel::LOG_NOTICE: - return 'green'; - break; - case Propel::LOG_INFO: - return 'blue'; - break; - case Propel::LOG_DEBUG: - return 'grey'; - break; - } - } -} -``` - ->**Tip**
There is also a bundled `MojaviLogAdapter` class which allows you to use a Mojavi logger with Propel. - -## Debugging Database Activity ## - -By default, Propel uses `PropelPDO` for database connections. This class, which extends PHP's `PDO`, offers a debug mode to keep track of all the database activity, including all the executed queries. - -### Enabling The Debug Mode ### - -The debug mode is disabled by default, but you can enable it at runtime as follows: - -```php -useDebug(true); -``` - -You can also disable the debug mode at runtime, by calling `PropelPDO::useDebug(false)`. Using this method, you can choose to enable the debug mode for only one particular query, or for all queries. - -Alternatively, you can ask Propel to always enable the debug mode for a particular connection by using the `DebugPDO` class instead of the default `PropelPDO` class. This is accomplished in the `runtime-conf.xml` file, in the `` tag of a given datasource connection (see the [runtime configuration reference]() for more details). - -```xml - - - - - - sqlite - - - DebugPDO -``` - ->**Tip**
You can use your own connection class there, but make sure that it extends `PropelPDO` and not only `PDO`. Propel requires certain fixes to PDO API that are provided by `PropelPDO`. - -### Counting Queries ### - -In debug mode, `PropelPDO` keeps track of the number of queries that are executed. Use `PropelPDO::getQueryCount()` to retrieve this number: - -```php -getQueryCount(); // 1 -``` - -Tip: You cannot use persistent connections if you want the query count to work. Actually, the debug mode in general requires that you don't use persistent connections in order for it to correctly log bound values and count executed statements. - -### Retrieving The Latest Executed Query ### - -For debugging purposes, you may need the SQL code of the latest executed query. It is available at runtime in debug mode using `PropelPDO::getLastExecutedQuery()`, as follows: - -```php -getLastExecutedQuery(); // 'SELECT * FROM my_obj'; -``` - -Tip: You can also get a decent SQL representation of the criteria being used in a SELECT query by using the `Criteria->toString()` method. - -Propel also keeps track of the queries executed directly on the connection object, and displays the bound values correctly. - -```php -prepare('SELECT * FROM my_obj WHERE name = :p1'); -$stmt->bindValue(':p1', 'foo'); -$stmt->execute(); -echo $con->getLastExecutedQuery(); // 'SELECT * FROM my_obj where name = "foo"'; -``` - ->**Tip**
The debug mode is intended for development use only. Do not use it in production environment, it logs too much information for a production server, and adds a small overhead to the database queries. - -## Full Query Logging ## - -The combination of the debug mode and a logging facility provides a powerful debugging tool named _full query logging_. If you have properly configured a log handler, enabling the debug mode (or using `DebugPDO`) automatically logs the executed queries into Propel's default log file: - -```text -Oct 04 00:00:18 propel-bookstore [debug] INSERT INTO publisher (`ID`,`NAME`) VALUES (NULL,'William Morrow') -Oct 04 00:00:18 propel-bookstore [debug] INSERT INTO author (`ID`,`FIRST_NAME`,`LAST_NAME`) VALUES (NULL,'J.K.','Rowling') -Oct 04 00:00:18 propel-bookstore [debug] INSERT INTO book (`ID`,`TITLE`,`ISBN`,`PRICE`,`PUBLISHER_ID`,`AUTHOR_ID`) VALUES (NULL,'Harry Potter and the Order of the Phoenix','043935806X',10.99,53,58) -Oct 04 00:00:18 propel-bookstore [debug] INSERT INTO review (`ID`,`REVIEWED_BY`,`REVIEW_DATE`,`RECOMMENDED`,`BOOK_ID`) VALUES (NULL,'Washington Post','2009-10-04',1,52) -... -Oct 04 00:00:18 propel-bookstore [debug] SELECT bookstore_employee_account.EMPLOYEE_ID, bookstore_employee_account.LOGIN FROM `bookstore_employee_account` WHERE bookstore_employee_account.EMPLOYEE_ID=25 -``` - -By default, Propel logs all SQL queries, together with the date of the query and the name of the connection. - -### Setting The Data To Log ### - -The full query logging feature can be configured either in the `runtime-conf.xml` configuration file, or using the runtime configuration API. - -In `runtime-conf.xml`, tweak the feature by adding a `` tag under ``: - -```xml - - - - ... - - - - ... - - - -
- - true - - - - true - -
-
-
-
-
-``` - -To accomplish the same configuration as above at runtime, change the settings in your main include file, after `Propel::init()`, as follows: - -```php -setParameter('debugpdo.logging.details.method.enabled', true); -$config->setParameter('debugpdo.logging.details.time.enabled', true); -$config->setParameter('debugpdo.logging.details.mem.enabled', true); -``` - -Let's see a few of the provided parameters. - -### Logging More Connection Messages ### - -`PropelPDO` can log queries, but also connection events (open and close), and transaction events (begin, commit and rollback). Since Propel can emulate nested transactions, you may need to know when an actual `COMMIT` or `ROLLBACK` is issued. - -To extend which methods of `PropelPDO` do log messages in debug mode, customize the `'debugpdo.logging.methods'` parameter, as follows: - -```php -setParameter('debugpdo.logging.methods', $allMethods, false); -``` - -By default, only the messages coming from `PropelPDO::exec`, `PropelPDO::query`, and `DebugPDOStatement::execute` are logged. - -### Logging Execution Time And Memory ### - -In debug mode, Propel counts the time and memory necessary for each database query. This very valuable data can be added to the log messages on demand, by adding the following configuration: - -```php -setParameter('debugpdo.logging.details.time.enabled', true); -$config->setParameter('debugpdo.logging.details.mem.enabled', true); -``` - -Enabling the options shown above, you get log output along the lines of: - -```text -Feb 23 16:41:04 Propel [debug] time: 0.000 sec | mem: 1.4 MB | SET NAMES 'utf8' -Feb 23 16:41:04 Propel [debug] time: 0.002 sec | mem: 1.6 MB | SELECT COUNT(tags.NAME) FROM tags WHERE tags.IMAGEID = 12 -Feb 23 16:41:04 Propel [debug] time: 0.012 sec | mem: 2.4 MB | SELECT tags.NAME, image.FILENAME FROM tags LEFT JOIN image ON tags.IMAGEID = image.ID WHERE image.ID = 12 -``` - -The order in which the logging details are enabled is significant, since it determines the order in which they will appear in the log file. - -### Complete List Of Logging Options ### - -The following settings can be customized at runtime or in the configuration file: - -|Parameter |Default |Meaning -|-----------------------------------------------|-----------|--------------------------------------------------------------------------------- -|`debugpdo.logging.innerglue` |":" |String to use for combining the title of a detail and its value -|`debugpdo.logging.outerglue` |"|" |String to use for combining details together on a log line -|`debugpdo.logging.realmemoryusage` |`false` |Parameter to [memory_get_usage()](http://www.php.net/manual/en/function.memory-get-usage.php) and [memory_get_peak_usage()](http://www.php.net/manual/en/function.memory-get-peak-usage.php) calls -|`debugpdo.logging.methods` |`array` |An array of method names (`Class::method`) to be included in method call logging -|`debugpdo.logging.details.slow.enabled` |`false` |Enables flagging of slow method calls -|`debugpdo.logging.details.slow.threshold` |`0.1` |Method calls taking more seconds than this threshold are considered slow -|`debugpdo.logging.details.time.enabled` |`false` |Enables logging of method execution times -|`debugpdo.logging.details.time.precision` |`3` |Determines the precision of the execution time logging -|`debugpdo.logging.details.time.pad` |`10` |How much horizontal space to reserve for the execution time on a log line -|`debugpdo.logging.details.mem.enabled` |`false` |Enables logging of the instantaneous PHP memory consumption -|`debugpdo.logging.details.mem.precision` |`1` |Determines the precision of the memory consumption logging -|`debugpdo.logging.details.mem.pad` |`9` |How much horizontal space to reserve for the memory consumption on a log line -|`debugpdo.logging.details.memdelta.enabled` |`false` |Enables logging differences in memory consumption before and after the method call -|`debugpdo.logging.details.memdelta.precision` |`1` |Determines the precision of the memory difference logging -|`debugpdo.logging.details.memdelta.pad` |`10` |How much horizontal space to reserve for the memory difference on a log line -|`debugpdo.logging.details.mempeak.enabled` |`false` |Enables logging the peak memory consumption thus far by the currently executing PHP script -|`debugpdo.logging.details.mempeak.precision` |`1` |Determines the precision of the memory peak logging -|`debugpdo.logging.details.mempeak.pad` |`9` |How much horizontal space to reserve for the memory peak on a log line -|`debugpdo.logging.details.querycount.enabled` |`false` |Enables logging of the number of queries performed by the DebugPDO instance thus far -|`debugpdo.logging.details.querycount.pad` |`2` |How much horizontal space to reserve for the query count on a log line -|`debugpdo.logging.details.method.enabled` |`false` |Enables logging of the name of the method call -|`debugpdo.logging.details.method.pad` |`28` |How much horizontal space to reserve for the method name on a log line - -### Changing the Log Level ### - -By default the connection log messages are logged at the `Propel::LOG_DEBUG` level. This can be changed by calling the `setLogLevel()` method on the connection object: - -```php -setLogLevel(Propel::LOG_INFO); -``` - -Now all queries and bind param values will be logged at the INFO level. - -### Configuring a Different Full Query Logger ### - -By default the `PropelPDO` connection logs queries and binds param values using the `Propel::log()` static method. As explained above, this method uses the log storage configured by the `` tag in the `runtime-conf.xml` file. - -If you would like the queries to be logged using a different logger (e.g. to a different file, or with different ident, etc.), you can set a logger explicitly on the connection at runtime, using `Propel::setLogger()`: - -```php -setLogger($logger); -``` - -This will not affect the general Propel logging, but only the full query logging. That way you can log the Propel error and warnings in one file, and the SQL queries in another file. diff --git a/documentation/09-inheritance.markdown b/documentation/09-inheritance.markdown deleted file mode 100644 index 05fe88e8..00000000 --- a/documentation/09-inheritance.markdown +++ /dev/null @@ -1,498 +0,0 @@ ---- -layout: documentation -title: Inheritance ---- - -# Inheritance # - -Developers often need one model table to extend another model table. Inheritance being an object-oriented notion, it doesn't have a true equivalent in the database world, so this is something an ORM must emulate. Propel offers three types of table inheritance: [Single Table Inheritance](http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html), which is the most efficient implementations from a SQL and query performance perspective, but is limited to a small number of inherited fields ; [Class Table Inheritance](http://martinfowler.com/eaaCatalog/classTableInheritance.html), which separates data into several tables and uses joins to fetch complete children entities, and [Concrete Table Inheritance](http://www.martinfowler.com/eaaCatalog/concreteTableInheritance.html), which provides the most features but adds a small overhead on write queries. - -## Single Table Inheritance ## - -In this implementation, one table is used for all subclasses. This has the implication that your table must have all columns needed by the main class and subclasses. Propel will create stub subclasses. - -Let's illustrate this idea with an example. Consider an object model with three classes, `Book`, `Essay`, and `Comic` - the first class being parent of the other two. With single table inheritance, the data of all three classes is stored in one table, named `book`. - -### Schema Definition ### - -A table using Single Table Inheritance requires a column to identify which class should be used to represent the _table_ row. Classically, this column is named `class_key` - but you can choose whatever name fits your taste. The column needs the `inheritance="single"` attribute to make Propel understand that it's the class key column. Note that this 'key' column must be a real column in the table. - -```xml - - - - - - - - -
-``` - -Once you rebuild your model, Propel generated all three model classes (`Book`, `Essay`, and `Comic`) and three query classes (`BookQuery`, `EssayQuery`, and `ComicQuery`). The `Essay` and `Comic` classes extend the `Book` class, the `EssayQuery` and `ComicQuery` classes extend `BookQuery`. - ->**Tip**
An inherited class can extend another inherited class. That mean that you can add a `Manga` kind of book that extends `Comic` instead of `Book`. - -
- ->**Tip**
If you define an `` tag with no `extends` attribute, as in the example above, it must use the table `phpName` as its `class` attribute. - -### Using Inherited Objects ### - -Use inherited objects just like you use regular Propel model objects: - -```php -setTitle('War And Peace'); -$book->save(); -$essay = new Essay(); -$essay->setTitle('On the Duty of Civil Disobedience'); -$essay->save(); -$comic = new Comic(); -$comic->setTitle('Little Nemo In Slumberland'); -$comic->save(); -``` - -Inherited objects share the same properties and methods by default, but you can add your own logic to each of the generated classes. - -Behind the curtain, Propel sets the `class_key` column based on the model class. So the previous code stores the following rows in the database: - -```text -id | title | class_key ----|-----------------------------------|---------- -1 | War And Peace | Book -2 | On the Duty of Civil Disobedience | Essay -3 | Little Nemo In Slumberland | Comic -``` - -Incidentally, that means that you can add new classes manually, even if they are not defined as `` tags in the `schema.xml`: - -```php -setClassKey('Novel'); - } -} -$novel = new Novel(); -$novel->setTitle('Harry Potter'); -$novel->save(); -``` - -### Retrieving Inherited objects ### - -In order to retrieve books, use the Query object of the main class, as you would usually do. Propel will hydrate children objects instead of the parent object when necessary: - -```php -find(); -foreach ($books as $book) { - echo get_class($book) . ': ' . $book->getTitle() . "\n"; -} -// Book: War And Peace -// Essay: On the Duty of Civil Disobedience -// Comic: Little Nemo In Slumberland -// Novel: Harry Potter -``` - -If you want to retrieve only objects of a certain class, use the inherited query classes: - -```php -findOne(); -echo get_class($comic) . ': ' . $comic->getTitle() . "\n"; -// Comic: Little Nemo In Slumberland -``` - ->**Tip**
You can override the base peer's `getOMClass()` to return the classname to use based on more complex logic (or query). - -### Abstract Entities ### - -If you wish to enforce using subclasses of an entity, you may declare a table "abstract" in your XML data model: - -```xml - - ... -``` - -That way users will only be able to instantiate `Essay` or `Comic` books, but not `Book`. - -## Class Table Inheritance ## - -Class Table Inheritance uses one table per class in the inheritance structure ; each table stores only the columns it doesn't inherit from its parent. -Propel doesn't offer class table inheritance per se, however it provides a behavior called `delegate`, which offers the same functionality. - -### Schema Definition ### - -A sports news website displays stats about various sports player. The object oriented design therefore produces a `Player` model with an identity, and two children classes, `Footballer` and `Basketballer`, with distinct stats columns. - -Instead of having the children table inherit from the parent table, Propel lets you _delegate_ column handling from the child to the parent table. Here is how it looks like: - -```xml -
- - - -
- - - - - - - - - - - -
- - - - - - - - - - - - -
-``` - -The `footballer` table doesn't own the identity columns, which are in the `player` table. However, the `Footballer` class can _delegate_ these columns to the `Player` class. In this case, the `delegate` behavior doesn't alter the schema, it just allows the `Footballer` and `Basketballer` classes to proxy method calls to their "parent" `Player` class. - ->**Tip**
Delegation only works for a single level. If you have a deep inheritance hierarchy, you will need to delegate to each ancestors in the hierarchy by simulating multiple inheritance (see below). - -### Delegation in Practice ### - -Using the previous schema, here is how you create a `Basketballer` and set his stats and identity: - -```php -setPoints(101); -$basketballer->setFieldGoals(47); -$basketballer->setThreePointsFieldGoals(7); -// set player identity via delegation -$basketballer->setFirstName('Michael'); -$basketballer->setLastName('Giordano'); -// same as -$player = new Player(); -$player->setFirstName('Michael'); -$player->setLastName('Giordano'); -$basketballer->setPlayer($player); - -// save basketballer and player -$basketballer->save(); - -// retrieve delegated data directly from the main object -echo $basketballer->getFirstName(); // Michael -``` - -The `Basketballer` object delegates `Player` methods to its related `Player` object. If no `Player` object exists, the `delegate` behavior creates one and relates it to the current `basketballer`, so that both objects can be persisted together. - -The same happens for `Footballer` class, which also delegates to `Player`. And since a `Basketballer` and a `Footballer` instance can delegate to the same `Player` instance, a player can play both soccer and basketball. - ->**Tip**
Again, delegation isn't really inheritance. The `Basketballer` class doesn't inherit from `Player`, but _proxies_ method calls to `Player`. And since Propel uses `__call()` magic to achieve this proxying, an IDE won't see the `Player` methods accessible to a `Basketballer` instance. - -### Multiple Inheritance ### - -The `delegate` behavior allows to delegate to more than one class, effectively simulating multiple inheritance. Here is an example: - -```xml - - - - -
- - - -
- - - - - - - - - - - - - - - -
-``` - -Now you can access the `Person` and `Team` properties directly from the `Footballer` class: - -```php -setGoalsScored(43); -$footballer->setFoulsCommitted(4); -$footballer->setThreePointsFieldGoals(7); -// set player identity via delegation -$footballer->setFirstName('Michael'); -$footballer->setLastName('Platino'); -// set team name via delegation -$footballer->setName('Saint Etienne'); - -// save footballer and player and team -$footballer->save(); - -// retrieve delegated data directly from the main object -echo $footballer->getFirstName(); // Michael -``` - -Multiple delegation also allows to implement a deep inheritance hirerarchy. For instance, if your object model contains a `ProBasketballer` inheriting from `Basketballer` inheriting from `Player`, the `ProBasketballer` should delegate to both `Basketballer` and `Player`; delegating to `Basketballer` only isn't enough. - -```xml - - - - -
- - - - - - - - - - - - -
- - - - - - - - - - - - - - -
-``` - -That way, you can call `setPoints()` as well as `setFirstName()` directly on `ProBasketballer`. - -The `delegate` behavior offers much more than just simulating Class Table Inheritance. Discover other use cases and complete reference in the [delegate behavior documentation](../behaviors/delegate.html). - -## Concrete Table Inheritance ## - -Concrete Table Inheritance uses one table for each class in the hierarchy. Each table contains columns for the class and all its ancestors, so any fields in a superclass are duplicated across the tables of the subclasses. - -Propel implements Concrete Table Inheritance through a behavior. - -### Schema Definition ### - -Once again, this is easier to understand through an example. In a Content Management System, content types are often organized in a hierarchy, each subclass adding more fields to the superclass. So let's consider the following schema, where the `article` and `video` tables use the same fields as the main `content` tables, plus additional fields: - -```xml - - - - - - - -
- - - -
- - - - - - - - -
- - - - - - - - -
-``` - -Since the columns of the main table are copied to the child tables, this schema is a simple implementation of Concrete Table Inheritance. This is something that you can write by hand, but the repetition makes it tedious. Instead, you should let the `concrete_inheritance` behavior do it for you: - -```xml - - - - - - - -
- - - -
- - - - - -
- - - - - -
-``` - ->**Tip**
The `concrete_inheritance` behavior copies columns, foreign keys, indices and validators. - -### Using Inherited Model Classes ### - -For each of the tables in the schema above, Propel generates a Model class: - -```php -setName('Movie'); -$cat->save(); -// create a new Article -$art = new Article(); -$art->setTitle('Avatar Makes Best Opening Weekend in the History'); -$art->setCategory($cat); -$art->setContent('With $232.2 million worldwide total, Avatar had one of the best-opening weekends in the history of cinema.'); -$art->save(); -// create a new Video -$vid = new Video(); -$vid->setTitle('Avatar Trailer'); -$vid->setCategory($cat); -$vid->setResourceLink('http://www.avatarmovie.com/index.html') -$vid->save(); -``` - -And since the `concrete_inheritance` behavior tag defines a parent table, the `Article` and `Video` classes extend the `Content` class (same for the generated Query classes): - -```php -getCategory()->getName(); - } -} -echo $art->getCategoryName(); // 'Movie' -echo $vid->getCategoryName(); // 'Movie' - -// methods of the parent query are accessible to the child query -class ContentQuery extends BaseContentQuery -{ - public function filterByCategoryName($name) - { - return $this - ->useCategoryQuery() - ->filterByName($name) - ->endUse(); - } -} -$articles = ArticleQuery::create() - ->filterByCategoryName('Movie') - ->find(); -``` - -That makes of Concrete Table Inheritance a powerful way to organize your model logic and to avoid repetition, both in the schema and in the model code. - -### Data Replication ### - -By default, every time you save an `Article` or a `Video` object, Propel saves a copy of the `title` and `category_id` columns in a `Content` object. Consequently, retrieving objects regardless of their child type becomes very easy: - -```php -find(); -foreach ($conts as $content) { - echo $content->getTitle() . "(". $content->getCategoryName() ")/n"; -} -// Avatar Makes Best Opening Weekend in the History (Movie) -// Avatar Trailer (Movie) -``` - -Propel also creates a one-to-one relationship between a object and its parent copy. That's why the schema definition above doesn't define any primary key for the `article` and `video` tables: the `concrete_inheritance` behavior creates the `id` primary key which is also a foreign key to the parent `id` column. So once you have a parent object, getting the child object is just one method call away: - -```php -getContent(); - } -} -class Movie extends BaseMovie -{ - public function getPreview() - { - return $this->getResourceLink(); - } -} -$conts = ContentQuery::create()->find(); -foreach ($conts as $content) { - echo $content->getTitle() . "(". $content->getCategoryName() ")/n" - if ($content->hasChildObject()) { - echo ' ' . $content->getChildObject()->getPreview(), "\n"; -} -// Avatar Makes Best Opening Weekend in the History (Movie) -// With $232.2 million worldwide total, Avatar had one of the best-opening -// weekends in the history of cinema. -// Avatar Trailer (Movie) -// http://www.avatarmovie.com/index.html -``` - -The `hasChildObject()` and `getChildObject()` methods are automatically added by the behavior to the parent class. Behind the curtain, the saved `content` row has an additional `descendant_column` field allowing it to use the right model for the job. - ->**Tip**
You can disable the data replication by setting the `copy_data_to_parent` parameter to "false". In that case, the `concrete_inheritance` behavior simply modifies the table at buildtime and does nothing at runtime. Also, with `copy_data_to_parent` disabled, any primary key copied from the parent table is not turned into a foreign key: - -```xml - - - - - - -
-// results in - - - - - - - - -
-``` diff --git a/documentation/10-migrations.markdown b/documentation/10-migrations.markdown deleted file mode 100644 index 4aecaae3..00000000 --- a/documentation/10-migrations.markdown +++ /dev/null @@ -1,364 +0,0 @@ ---- -layout: documentation -title: Migrations ---- - -# Migrations # - -During the life of a project, the Model seldom stays the same. New tables arise, and existing tables often need modifications (a new column, a new index, another foreign key...). Updating the database structure accordingly, while preserving existing data, is a common concern. Propel provides a set of tools to allow the _migration_ of database structure and data with ease. - ->**Tip**
Propel only supports migrations in MySQL and PostgreSQL for now. - -## Migration Workflow ## - -The workflow of Propel migrations is very simple: - -1. Edit the XML schema to modify the model -2. Call the `diff` task to create a migration class containing the SQL statements altering the database structure -3. Review the migration class Propel just generated, and add data migration code if necessary -4. Execute the migration using the `migrate` task. -5. Call the `om` task to generate the updated object model classes. - -Here is a concrete example. On a new bookstore project, a developer creates an XML schema with a single `book` table: - -```xml - - - - -
-
-``` - -The developer then calls the `diff` task to ask Propel to compare the database structure and the XML schema: - -```text -> propel-gen diff - -[propel-sql-diff] Reading databases structure... -[propel-sql-diff] Database is empty -[propel-sql-diff] Loading XML schema files... -[propel-sql-diff] 1 tables found in 1 schema file. -[propel-sql-diff] Comparing models... -[propel-sql-diff] Structure of database was modified: 1 added table -[propel-sql-diff] "PropelMigration_1286483354.php" file successfully created in /path/to/project/build/migrations -[propel-sql-diff] Please review the generated SQL statements, and add data migration code if necessary. -[propel-sql-diff] Once the migration class is valid, call the "migrate" task to execute it. -``` - -It is recommended to review the generated migration class to check the generated SQL code. It contains two methods, `getUpSQL()` and `getDownSQL()`, allowing to migrate the database structure to match the updated schema, and back: - -```php - ' -CREATE TABLE `book` -( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `title` VARCHAR(255) NOT NULL, - `isbn` VARCHAR(24) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB COMMENT=\'Book Table\'; -', -); - } - - public function getDownSQL() - { - return array('bookstore' => ' -DROP TABLE IF EXISTS `book`; -', -); - } -} -``` - ->**Tip**
On a project using version control, it is important to commit the migration classes to the code repository. That way, other developers checking out the project will just have to run the same migrations to get a database in a similar state. - -Now, to actually create the `book` table in the database, the developer has to call the `migrate` task: - -```text -> propel-gen migrate - -[propel-migration] Executing migration PropelMigration_1286483354 up -[propel-migration] 1 of 1 SQL statements executed successfully on datasource "bookstore" -[propel-migration] Migration complete. No further migration to execute. -``` - -The `book` table is now created in the database. It can be populated with data. - -Finally, in order to use the newly added table in application development, the developer has to call the `om` task to generate the updated object model classes: - -```text -> propel-gen om -``` - -After a few days, the developer wants to add a new `author` table, with a foreign key in the `book` table. The schema is modified as follows: - -```xml - - - - - - - - - -
- - - - -
-
-``` - -In order to update the database structure accordingly, the process is the same: - -```text -> propel-gen diff - -[propel-sql-diff] Reading databases structure... -[propel-sql-diff] 1 tables imported from databases. -[propel-sql-diff] Loading XML schema files... -[propel-sql-diff] 2 tables found in 1 schema file. -[propel-sql-diff] Comparing models... -[propel-sql-diff] Structure of database was modified: 1 added table, 1 modified table -[propel-sql-diff] "PropelMigration_1286484196.php" file successfully created in /path/to/project/build/migrations -[propel-sql-diff] Please review the generated SQL statements, and add data migration code if necessary. -[propel-sql-diff] Once the migration class is valid, call the "migrate" task to execute it. - -> propel-gen migrate - -[propel-migration] Executing migration PropelMigration_1286484196 up -[propel-migration] 4 of 4 SQL statements executed successfully on datasource "bookstore" -[propel-migration] Migration complete. No further migration to execute. -``` - -Propel has executed the `PropelMigration_1286484196::getUpSQL()` code, which alters the `book` structure _without removing data_: - -```sql -ALTER TABLE `book` ADD -( - `author_id` INTEGER -); - -CREATE INDEX `book_FI_1` ON `book` (`author_id`); - -ALTER TABLE `book` ADD CONSTRAINT `book_FK_1` - FOREIGN KEY (`author_id`) - REFERENCES `author` (`id`) - ON UPDATE CASCADE - ON DELETE SET NULL; - -CREATE TABLE `author` -( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `first_name` VARCHAR(255), - `last_name` VARCHAR(255), - PRIMARY KEY (`id`) -) ENGINE=InnoDB; -``` - ->**Tip**
`diff` and `migrate` often come one after the other, so you may want to execute them both in one call. That's possible, provided that the first argument of the `propel-gen` script is the path to the current project: - -```text -> propel-gen . diff migrate -``` - -## Migration Tasks ## - -The two basic migration tasks are `diff` and `migrate` - you already know them. `diff` creates a migration class, and `migrate` executes the migrations. But there are three more migration tasks that you will find very useful. - -### Migration Up or Down, One At A Time ### - -In the previous example, two migrations were executed. But the developer now wants to revert the last one. The `down` task provides exactly this feature: it reverts only one migration. - -```text -> propel-gen down - -[propel-migration-down] Executing migration PropelMigration_1286484196 down -[propel-migration-down] 4 of 4 SQL statements executed successfully on datasource "bookstore" -[propel-migration-down] Reverse migration complete. 1 more migrations available for reverse. -``` - -Notice that the `PropelMigration_1286484196` was executed _down_, not _up_ like the previous time. You can call this command several times to continue reverting the database structure, up to its original state: - -```text -> propel-gen down - -[propel-migration-down] Executing migration PropelMigration_1286483354 down -[propel-migration-down] 1 of 1 SQL statements executed successfully on datasource "bookstore" -[propel-migration-down] Reverse migration complete. No more migration available for reverse -``` - -As you may have guessed, the `up` task does exactly the opposite: it executes the next migration up: - -```text -> propel-gen up - -[propel-migration-up] Executing migration PropelMigration_1286483354 up -[propel-migration-up] 1 of 1 SQL statements executed successfully on datasource "bookstore" -[propel-migration-up] Migration complete. 1 migrations left to execute. -``` - ->**Tip**
The difference between the `up` and `migrate` tasks is that `up` executes only one migration, while `migrate` executes all the migrations that were not yet executed. - -### Migration Status ### - -If you followed the latest example, you may notice that the schema and the database should now be desynchronized. By calling `down` twice, and `up` just once, there is one migration left to execute. This kind of situation sometimes happen in the life of a project: you don't really know which migrations were already executed, and which ones need to be executed now. - -For these situations, Propel provides the `status` task. It simply lists the migrations not yet executed, to help you understand where you are in the migration process. - -```text -> propel-gen status - -[propel-migration-status] Checking Database Versions... -[propel-migration-status] Listing Migration files... -[propel-migration-status] 1 migration needs to be executed: -[propel-migration-status] PropelMigration_1286484196 -[propel-migration-status] Call the "migrate" task to execute it -``` - ->**Tip**
Like all other Propel tasks, `status` offers a "verbose" mode, where the CLI output shows a lot more details. Add `-verbose` at the end of the call to enable it - but remember to add the path to the project as first argument: - -```text -> propel-gen . status -verbose -[propel-migration-status] Checking Database Versions... -[propel-migration-status] Connecting to database "bookstore" using DSN "mysql:dbname=bookstore" -[propel-migration-status] Latest migration was executed on 2010-10-07 22:29:14 (timestamp 1286483354) -[propel-migration-status] Listing Migration files... -[propel-migration-status] 2 valid migration classes found in "/Users/francois/propel/1.6/test/fixtures/migration/build/migrations" -[propel-migration-status] 1 migration needs to be executed: -[propel-migration-status] > PropelMigration_1286483354 (executed) -[propel-migration-status] PropelMigration_1286484196 -[propel-migration-status] Call the "migrate" task to execute it -``` - -`up`, `down`, and `status` will help you to find your way in migration files, especially when they become numerous or when you need to revert more than one. - ->**Tip**
There is no need to keep old migration files if you are sure that you won't ever need to revert to an old state. If a new developer needs to setup the project from scratch, the `sql` and `insert-sql` tasks will initialize the database structure to the current XML schema. - -## How Do Migrations Work? ## - -The Propel `diff` task creates migration class names (like `PropelMigration_1286483354`) using the timestamp of the date they were created. Not only does it make the classes automatically sorted by date in a standard directory listing, it also avoids collision between two developers working on two structure changes at the same time. - -Propel creates a special table in the database, where it keeps the date of the latest executed migration. That way, by comparing the available migrations and the date of the latest ones, Propel can determine the next migration to execute. - -```text -mysql> select * from propel_migration; -+------------+ -| version | -+------------+ -| 1286483354 | -+------------+ -1 row in set (0.00 sec) -``` - -So don't be surprised if your database show a `propel_migration` table that you never added to your schema - this is the Propel migration table. Propel doesn't use this table at runtime, and it never contains more than one line, so it should not bother you. - -## Migration Configuration ## - -The migration tasks support customization through a few settings from `build.properties`: - -```ini -# Name of the table Propel creates to keep the latest migration date -propel.migration.table = propel_migration -# Whether the comparison between the XML schema and the database structure -# cares for differences in case (e.g. 'my_table' and 'MY_TABLE') -propel.migration.caseInsensitive = true -# The directory where migration classes are generated and looked for -propel.migration.dir = ${propel.output.dir}/migrations -``` - ->**Tip**
The `diff` task supports an additional parameter, called `propel.migration.editor`, which specifies a text editor to be automatically launched at the end of the task to review the generated migration. Unfortunately, only editors launched in another window are accepted due to a Phing limitation. Mac users will find it useful, though: - -```text -> propel-gen . diff -Dpropel.migration.editor=mate -``` - -## Migrating Data ## - -Propel generates the SQL code to alter the database structure, but your project may require more. For instance, in the newly added `author` table, the developer may want to add a few records. - -That's why Propel automatically executes the `preUp()` and `postUp()` migration before and after the structure migration. If you want to add data migration, that's the place to put the related code. - -Each of these methods receive a `PropelMigrationManager` instance, which is a good way to get PDO connection instances based on the buildtime configuration. - -Here is an example implementation of data migration: - -```php - ' -ALTER TABLE `book` ADD -( - `author_id` INTEGER -); -//... -'); - } - - public function postUp($manager) - { - // post-migration code - $sql = "INSERT INTO author (first_name,last_name) values('Leo','Tolstoi')"; - $pdo = $manager->getPdoConnection('bookstore'); - $stmt = $pdo->prepare($sql); - $stmt->execute(); - } -} -``` - ->**Tip**
If you return `false` in the `preUp()` method, the migration is aborted. - -You can also use Propel ActiveRecord and Query objects, but you'll then need to bootstrap the `Propel` class and the runtime autoloading in the migration class. This is because the Propel CLI does not know where the runtime classes are. - -```php -getPdoConnection('bookstore'); - $author = new Author(); - $author->setFirstName('Leo'); - $author->setLastname('Tolstoi'); - $author->save($pdo); - } - - public function getUpSQL() - { - // ... - } -} -``` - -Of course, you can add code to the `preDown()` and `postDown()` methods to execute a data migration when reverting migrations. diff --git a/documentation/index.markdown b/documentation/index.markdown deleted file mode 100644 index 0233942d..00000000 --- a/documentation/index.markdown +++ /dev/null @@ -1,97 +0,0 @@ ---- -layout: documentation -title: Documentation ---- - -# Documentation # - - * [What's New in Propel 1.6](whats-new.html) Users of previous versions can check the changes here. - * [Changelog](https://raw.github.com/propelorm/Propel/master/CHANGELOG) Updates in the 1.6 branch since the release of 1.6.0 stable. - * [API Documentation](http://api.propelorm.org/) The generated API documentation. - -## Project Setup ## - - * [Installing Propel](01-installation.html) Install Propel using Git, PEAR, or a tarball. - * [Building A Project](02-buildtime.html) Generate a PHP model based on a XML schema - -## Propel Basics ## - -* [Basic CRUD](03-basic-crud.html) The basics of Propel C.R.U.D. (Create, Retrieve, Update, Delete) operations -* [Relationships](04-relationships.html) Searching and manipulating data from related tables. -* [Validators](05-validators.html) The validation framework checks data before insertion based on column type. -* [Transactions](06-transactions.html) Where and when to use transactions. -* [Behaviors](07-behaviors.html) The behavior system allows to package and reuse common model features. -* [Logging And Debugging](08-logging.html) Propel can log a lot of information, including the SQL queries it executes. -* [Inheritance](09-inheritance.html) Single Table Inheritance, Class Table Inheritance, and Concrete Table Inheritance come free with Propel. -* [Migrations](10-migrations.html) Change the structure of the database without altering the data. - -## Reference ## - -* [XML Schema Format](../reference/schema.html) All the database, table, column and foreign key options explained -* [Active Record Classes](../reference/active-record.html) Complete list of the methods of Active Record classes. -* [Active Query Classes](../reference/model-criteria.html) Complete list of the methods of Propel Query classes. -* [Build Properties](../reference/buildtime-configuration.html) Reference for the `build.properties` file (`propel.ini` in symfony). -* [Runtime Configuration File](../reference/runtime-configuration.html) Reference for the `runtime-conf.xml` file. - -## Behaviors Reference ## - -* [`aggregate_column`](../behaviors/aggregate-column.html) -* [`alternative_coding_standards`](../behaviors/alternative-coding-standards.html) -* [`archivable`](../behaviors/archivable.html) -* [`auto_add_pk`](../behaviors/auto-add-pk.html) -* [`delegate`](../behaviors/delegate.html) -* [`i18n`](../behaviors/i18n.html) -* [`nested_set`](../behaviors/nested-set.html) -* [`query_cache`](../behaviors/query-cache.html) -* [`sluggable`](../behaviors/sluggable.html) -* *[`soft_delete`](../behaviors/soft-delete.html) (deprecated, use `archivable` instead)* -* [`timestampable`](../behaviors/timestampable.html) -* [`sortable`](../behaviors/sortable.html) -* [`versionable`](../behaviors/versionable.html) -* And [`concrete_inheritance`](09-inheritance.html), documented in the Inheritance Chapter even if it's a behavior - -You can also look at [user contributed behaviors](../cookbook/user-contributed-behaviors.html). - -## Cookbook ## - -### Common Tasks ### - -* [Additional SQL Files](../cookbook/adding-additional-sql-files.html) How to execute custom SQL statements at buildtime -* [Advanced Column Types](../cookbook/working-with-advanced-column-types.html) How to work with BLOBs, serialized PHP objects, ENUM, and ARRAY column types. -* [Customizing build](../cookbook/customizing-build.html) How to customize the Phing build process. -* [DB Designer](../cookbook/dbdesigner.html) How to import an XML schema from existing DBDesigner 4 file. -* [How to Use PHP 5.3 Namespaces](../cookbook/namespaces.html) How to generate model classes with namespaces, and how to use them. -* [Model Introspection At Runtime](../cookbook/runtime-introspection.html) How to use the Map classes to discover table properties at runtime. -* [Multi-Component Data Model](../cookbook/multi-component-data-model.html) How to generate model classes in subdirectories, and organize your model into independent packages / modules. -* [Object Copy](../cookbook/copying-persisted-objects.html) How to clone and copy persisted objects. -* [Replication](../cookbook/replication.html) How to use Propel in a Master-Slave Replication Environment. -* [Using Propel With MSSQL Server](../cookbook/using-mssql-server.html) How to choose and configure Propel to persist data to a Microsoft SQL Server database. -* [Using SQL Schemas](../cookbook/using-sql-schemas.html) How to organize tables into SQL schemas (only for MySQL, PostgreSQL, and MSSQL). -* [Working With Existing Databases](../cookbook/working-with-existing-databases.html) How to build an XML schema from an existing db structure, how to dump data to XML, how to import it into a new database, etc. - -### Contribute to Propel ### - -* [Writing A Behavior](../cookbook/writing-behavior.html) How to write a custom behavior to reuse model code horizontally. -* [Testing Your Behaviors](../cookbook/testing-your-behaviors.html) How to unit test your behaviors. -* [Working with unit tests](../cookbook/working-with-unit-tests.html) How to setup propel's required environment and use PHPUnit. - -### Working with symfony 1.4 ### - -* [Using Propel as Default ORM](../cookbook/symfony1/init-a-Symfony-project-with-Propel-git-way.html) How to initialize a symfony project with Propel as default ORM - the git way. -* [Using the `i18n` behavior](../cookbook/symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4.html) How to use Propel's `i18n` behavior with symfony 1.4. -* [Using the legacy `symfony_i18n` behavior](../cookbook/symfony1/how-to-use-old-SfPropelBehaviori18n-with-sf1.4.html) How to use the old `SfPropelBehaviori18n` (a.k.a. `symfony_i18n`) with symfony 1.4. - -### Working with Symfony2 ### - -* [Working with Symfony2 (Introduction)](../cookbook/symfony2/working-with-symfony2.html) -* [Symfony2 And Propel In Real Life](../cookbook/symfony2/symfony2-and-propel-in-real-life.html) -* [Mastering Symfony2 Forms With Propel](../cookbook/symfony2/mastering-symfony2-forms-with-propel.html) -* [The Symfony2 Security Component And Propel](../cookbook/symfony2/the-symfony2-security-component-and-propel.html) -* [Adding A New Behavior In Symfony2](../cookbook/symfony2/adding-a-new-behavior-in-symfony2.html) -* [Testing](../cookbook/symfony2/testing.html) - -### Working with Silex ### - -* [Working with Silex](../cookbook/silex/working-with-silex.html) - ->**Tip**
This is the up-to-date documentation for the last Propel version. To access the old documentation, please visit [trac.propelorm.org](http://trac.propelorm.org). diff --git a/documentation/whats-new.markdown b/documentation/whats-new.markdown deleted file mode 100644 index e780b4e3..00000000 --- a/documentation/whats-new.markdown +++ /dev/null @@ -1,723 +0,0 @@ ---- -layout: documentation -title: What's new in Propel 1.6? ---- - -# What's new in Propel 1.6? # - -Propel 1.6 is a new backwards compatible iteration of the Propel 1.x branch. As usual, don't forget to rebuild your model once you upgrade to this new version. - -## Migrations ## - -How do you manage changes to your database as the Model evolves and the schema changes? Calling the `sql` and `insert-sql` task each time the schema is updated has one dreadful drawback: it erases all the data in the database. - -Starting with Propel 1.6, the `sql`-`insert-sql` sequence is replaced by the `diff`-`migrate` sequence: - - > propel-gen diff - - [propel-sql-diff] Reading databases structure... - [propel-sql-diff] 1 tables imported from databases. - [propel-sql-diff] Loading XML schema files... - [propel-sql-diff] 2 tables found in 1 schema file. - [propel-sql-diff] Comparing models... - [propel-sql-diff] Structure of database was modified: 1 added table, 1 modified table - [propel-sql-diff] "PropelMigration_1286484196.php" file successfully created in /path/to/project/build/migrations - [propel-sql-diff] Please review the generated SQL statements, and add data migration code if necessary. - [propel-sql-diff] Once the migration class is valid, call the "migrate" task to execute it. - - > propel-gen migrate - - [propel-migration] Executing migration PropelMigration_1286484196 up - [propel-migration] 4 of 4 SQL statements executed successfully on datasource "bookstore" - [propel-migration] Migration complete. No further migration to execute. - -`diff` compares the schema to the database, and generates a class with all the required `ALTER TABLE` and `CREATE TABLE` statements to update the database structure. This migration class then feeds the `migrate` task, which connects to the database and executes the migrations - with no data loss. - -Migrations are a fantastic way to work on complex projects with always evolving models ; they are also a great tool for team work, since migration classes can be shared among all developers. That way, when a developer adds a table to the model, a second developer just needs to run the related migration to have the table added to the table. - -Propel migrations can also be executed incrementally - the new `up` and `down` tasks are there for that. And when you're lost in migration, call the `status` task to check which migrations were already executed, and which ones should be executed to update the database structure. - -The Propel documentation offers [an entire chapter on Migrations](10-migrations.html) to explain how to use them and how they work. - -Migrations only work on MySQL and PostgreSQL for now. On other platforms, you should continue to use `sql` and `insert-sql`. - -## New Behaviors ## - -Propel 1.6 ships with more core behaviors than ever. - -### Versionable behavior ### - -Once enabled on a table, the `versionable` behavior will store a copy of the ActiveRecord object in a separate table each time it is saved. This allows to keep track of the changes made on an object, whether to review modifications, or revert to a previous state. - -The classic Wiki example is a good illustration of the utility of the `versionable` behavior: - -```xml - - - - - -
-``` - -After rebuild, the `WikiPage` model has versioning abilities: - -```php -setTitle('Propel'); -$page->setBody('Propel is a CRM built in PHP'); -$page->save(); -echo $page->getVersion(); // 1 -$page->setBody('Propel is an ORM built in PHP5'); -$page->save(); -echo $page->getVersion(); // 2 - -// reverting to a previous version -$page->toVersion(1); -echo $page->getBody(); // 'Propel is a CRM built in PHP' -// saving a previous version creates a new one -$page->save(); -echo $page->getVersion(); // 3 - -// checking differences between versions -print_r($page->compareVersions(1, 2)); -// array( -// 'Body' => array(1 => 'Propel is a CRM built in PHP', 2 => 'Propel is an ORM built in PHP5'), -// ); - -// deleting an object also deletes all its versions -$page->delete(); -``` - -The `versionable` behavior offers audit log functionality, so you can track who made a modification, when, and why: - -```php -setTitle('PEAR'); -$page->setBody('PEAR is a framework and distribution system for reusable PHP components'); -$page->setVersionCreatedBy('John Doe'); -$page->setVersionComment('First draft'); -$page->save(); -// do more modifications... - -// list all modifications -foreach ($page->getAllVersions() as $pageVersion) { - echo sprintf("'%s', Version %d, updated by %s on %s (%s)\n", - $pageVersion->getTitle(), - $pageVersion->getVersion(), - $pageVersion->getVersionCreatedBy(), - $pageVersion->getVersionCreatedAt(), - $pageVersion->getVersionComment(), - ); -} -// 'PEAR', Version 1, updated by John Doe on 2010-12-21 22:53:02 (First draft) -// 'PEAR', Version 2, updated by ... -``` - -If it was just for that, the `versionable` behavior would already be awesome. Versioning is a very common feature, and there is no doubt that this behavior will replace lots of boilerplate code. Consider the fact that it's very configurable, [fully documented](../behaviors/versionable.html), and unit tested, and there is no reason to develop your own versioning layer. - -But there is more. The `versionable` behavior also works on relationships. If the `WikiPage` has one `Category`, and if the `Category` model also uses the `versionable` behavior, then each time a `WikiPage` is saved, it saves the version of the related `Category` it is related to, and it is able to restore it: - -```php -setName('Libraries'); -$page = new WikiPage(); -$page->setTitle('PEAR'); -$page->setBody('PEAR is a framework and distribution system for reusable PHP components'); -$page->setCategory($category); -$page->save(); // version 1 - -$page->setTitle('PEAR - PHP Extension and Application Repository'); -$page->save(); // version 2 - -$category->setName('PHP Libraries'); -$page->save(); // version 3 - -$page->toVersion(1); -echo $page->getTitle(); // 'PEAR' -echo $page->getCategory()->getName(); // 'Libraries' -$page->toVersion(3); -echo $page->getTitle(); // 'PEAR - PHP Extension and Application Repository' -echo $page->getCategory()->getName(); // 'PHP Libraries' -``` - -Now the versioning is not limited to a single class anymore. You can even design a fully versionable **application** - it all depends on your imagination. - -### I18n behavior ### - -The `i18n` behavior provides support for internationalization on the model. Using this behavior, the text columns of an !ActiveRecord object can have several translations. - -This is useful in multilingual applications, such as an e-commerce website selling home appliances across the world. This website should keep the name and description of each item separated from the other details, and keep one version for each supported language. - -Starting with Propel 1.6, this is possible by adding a simple `` tag to the table that needs internationalization: - -```xml -#!xml - - - - - - - - - -
-``` - -In this example, the `name` and `description` columns are moved to a new table, called `item_i18n`, which shares a many-to-one relationship with Item - one Item has many Item translations. But all this happens in the background; for the end user, everything happens as if there were only one main `Item` object: - -```php -setPrice('12.99'); -$item->setName('Microwave oven'); -$item->save(); -``` - -This creates one record in the `item` table with the price, and another in the `item_i18n` table with the English (default language) translation for the name. Of course, you can add more translations: - -```php -setLocale('fr_FR'); -$item->setName('Four micro-ondes'); -$item->setLocale('es_ES'); -$item->setName('Microondas'); -$item->save(); -``` - -This works both for setting AND for getting internationalized columns: - -```php -setLocale('en_US'); -echo $item->getName(); //'Microwave oven' -$item->setLocale('fr_FR'); -echo $item->getName(); // 'Four micro-ondes' -``` - -**Tip**: The big advantage of Propel behaviors is that they use code generation. Even though it's only a proxy method to the `ItemI18n` class, `Item::getName()` has all the phpDoc required to make your IDE happy. - -This new behavior also adds special capabilities to the Query objects. The most interesting allows you to execute less queries when you need to query for an Item and one of its translations - which is common to display a list of items in the locale of the user: - -```php -find(); // one query to retrieve all items -$locale = 'en_US'; -foreach ($items as $item) { - echo $item->getPrice(); - $item->setLocale($locale); - echo $item->getName(); // one query to retrieve the English translation -} -``` - -This code snippet requires 1+n queries, n being the number of items. But just add one more method call to the query, and the SQL query count drops to 1: - -```php -joinWithI18n('en_US') - ->find(); // one query to retrieve both all items and their translations -foreach ($items as $item) { - echo $item->getPrice(); - echo $item->getName(); // no additional query -} -``` - -In addition to hydrating translations, `joinWithI18n()` sets the correct locale on results, so you don't need to call `setLocale()` for each result. - -**Tip**: `joinWithI18n()` adds a left join with two conditions. That means that the query returns all items, including those with no translation. If you need to return only objects having translations, add `Criteria::INNER_JOIN` as second parameter to `joinWithI18n()`. - -Last but not least, Propel's `i18n` behavior is a drop-in replacement for symfony's `i18n` behavior. That means that with a couple more parameters, the locale can be accessed using `setCulture()`, and the `i18n_columns` parameter can be omitted if you explicit the `i18n` table. - -Just like the `versionable` behavior, the `i18n` behavior is thoroughly unit-tested and [fully documented]((../behaviors/i18n.html)). - -## XML/YAML/JSON/CSV Parsing and Dumping ## - -ActiveRecord and Collection objects now have the ability to be converted to and from a string, using any of the XML, YAML, JSON, and CSV formats. - -The syntax is very intuitive: ActiveRecord and collection objects now offer a `toXML()` and a `fromXML()` method (same for YAML, JSON, and CSV). Here are a few examples: - -```php -orderByTitle() - ->joinWith('Author') - ->find(); -echo $books->toYAML(); -// Book_1: -// Id: 123 -// Title: Pride and Prejudice -// AuthorId: 456 -// Author: -// Id: 456 -// FirstName: Jane -// LastName: Austen -// Book_2: -// Id: 789 -// Title: War and Peace -// AuthorId: 147 -// Author: -// Id: 147 -// FirstName: Leo -// LastName: Tolstoi - -// parse an XML string into an object -$bookString = << - - 9012 - <![CDATA[Don Juan]]> - - 12.99 - 1234 - 5678 - -EOF; -$book = new Book(); -$book->fromXML($bookString); -echo $book->getTitle(); // Don Juan -``` - -## Model Objects String Representation ## - -Taking advantage of the dumping abilities just introduced, all ActiveRecord objects now have a string representation based on a YAML dump of their properties: - -```php -setFirstName('Leo'); -$author->setLastName('Tolstoi'); -echo $author; -// Id: null -// FirstName: Leo -// LastName: Tolstoi -// Email: null -// Age: null -``` - -**Tip**: Tables with a column using the `isPrimaryString` attribute still output the value of a single column as string representation. - -`PropelCollection` objects also take advantage from this possibility: - -```php -orderByLastName() - ->find(); -echo $authors; -// Author_0: -// Id: 456 -// FirstName: Jane -// LastName: Austen -// Email: null -// Age: null -// Author_1: -// Id: 147 -// FirstName: Leo -// LastName: Tolstoi -// Email: null -// Age: null -``` - -If you want to use another format for the default string representation instead of YAML, you can set the `defaultStringFormat` attribute to any of the supported formats in either the `` or the `` elements in the XML schema: - -```xml -
- - -
-``` - -```php -setName('Penguin'); -echo $publisher; -// -// -// -// -// -``` - -## Easier OR in Queries ## - -Combining two generated filters with a logical `OR` used to be impossible in previous Propel versions - the alternative was to use `orWhere()` or `combine()`, but that meant losing all the smart defaults of generated filters. - -Propel 1.6 introduces a new method for Query objects: `_or()`. It just specifies that the next condition will be combined with a logical `OR` rather than an `AND`. - -```php -where('Book.Title = ?', 'War And Peace') - ->_or() - ->where('Book.Title LIKE ?', 'War%') - ->find(); -// SELECT * FROM book WHERE book.TITLE = 'War And Peace' OR book.TITLE LIKE 'War%' - -// _or() also works on generated filters: -$books = BookQuery::create() - ->filterByTitle('War And Peace') - ->_or() - ->filterByTitle('War%') - ->find(); -// SELECT * FROM book WHERE book.TITLE = 'War And Peace' OR book.TITLE LIKE 'War%' - -// _or() also works on embedded queries -$books = BookQuery::create() - ->filterByTitle('War and Peace') - ->_or() - ->useAuthorQuery() - ->filterByName('Leo Tolstoi') - ->endUse() - ->find(); -// SELECT book.* from book -// INNER JOIN author ON book.AUTHOR_ID = author.ID -// WHERE book.TITLE = 'War and Peace' -// OR author.NAME = 'Leo Tolstoi' -``` - -This new method is implemented in the `Criteria` class, so it also works for the old-style queries (using `add()` for conditions). - -**Tip**: Since `ModelCriteria::orWhere()` is a synonym for `->_or()->where()`, it is now deprecated. - -## Multiple Buildtime Connections ## - -Propel 1.5 used the `build.properties` for buildtime connection settings. This had one major drawback: it used to be impossible to deal with several connections at buildtime, let alone several RDBMS. - -In Propel 1.6, you can write your buildtime connection settings in a `buildtime-conf.xml` file. The format is the same as the `runtime-conf.xml` file, so a good starting point is to copy the runtime configuration, and change the settings for users with greater privileges. - -Here is an example buildtime configuration file that defines a MySQL and a SQLite connection: - -```xml - - - - - - mysql - - mysql:host=localhost;dbname=bookstore - testuser - password - - - - sqlite - - sqlite:/opt/databases/mydb.sq3 - - - - - -``` - -Now that Propel can deal with database vendors at buildtime more accurately, the generated classes offer more optimizations for the database they rely one. Incidentally, that means that you should rebuild your model if you use different database vendors. Thats includes cases when your development and production environments use different vendors. - -## Support For SQL Schemas ## - -For complex models showing a large number of tables, database administrators often like to group tables into "SQL schemas", which are namespaces in the SQL server. Starting with Propel 1.6, it is now possible to assign tables to SQL schemas using the `schema` attribute in the `` of the `` tag: - -```xml - -
- - - - - - -
- - - -
-
-``` - -**Tip**: This feature is only available in PostgreSQL, MSSQL, and MySQL. The `schema` attribute is simply ignored in Oracle and SQLite. - -Propel also supports foreign keys between tables assigned to two different schemas. For MySQL, where "SQL schema" is a synonym for "database", this allows for cross-database queries. - -The Propel documentation contains a new tutorial about the SQL schema attributes and usage, called [Using SQL Schemas](../cookbook/using-sql-schemas.html). - -## Foreign Key Filters Now Accept a Collection ## - -The generated `filterByRelationName()` methods in the model queries now accept a `PropelCollection` as argument. This will allow you to keep using objects and avoid dealing with foreign keys completely: - -```php -filterByAge(array('max' => 35)) - ->find(); // $authors is a PropelObjectCollection -// get all books by young authors -$books = BookQuery::create() - ->filterByAuthor($authors) // <= That's new - ->find(); -``` - -## Join With Several Conditions ## - -Creating a Join with more than one condition is now very easy. Just call `ModelCriteria::addJoinCondition($joinName, $condition)` after a `ModelCriteria::join()` to add further conditions to a join: - -```php -join('Author.Book') - ->addJoinCondition('Book', 'Book.Title IS NOT NULL') - ->find(); -// SELECT * FROM author -// INNER JOIN book ON (author.ID=book.AUTHOR_ID AND book.TITLE IS NOT NULL); -``` - -If you need to bind a variable to the condition, set the variable as last parameter of the `addJoinCondition()` call. Propel correctly binds the value using PDO: - -```php -join('Author.Book') - ->addJoinCondition('Book', 'Book.Title LIKE ?', 'War%', null, PDO::PARAM_STR) - ->find(); -// SELECT * FROM author -// INNER JOIN book ON (author.ID=book.AUTHOR_ID AND book.TITLE LIKE 'War%'); -``` - -**Tip**: `Criteria::addMultipleJoin()`, which allowed the same feature to some extent in previous versions, is now deprecated, since it was vulnerable in SQL injection attacks. - -## Model-Only Relationships ## - -Propel models can share relationships even though the underlying tables aren't linked by a foreign key. This ability may be of great use when using Propel on top of a legacy database. - -For example, a `review` table designed for a MyISAM database engine is linked to a `book` table by a simple `book_id` column: - -```xml - - - - -
-``` - -To enable a model-only relationship, add a `` tag using the `skipSql` attribute, as follows: - -```xml - - - - - - - - -
-``` - -Such a foreign key is not translated into SQL when Propel builds the table creation or table migration code. It can be seen as a "virtual foreign key". However, on the PHP side, the `Book` model actually has a one-to-many relationship with the `Review` model. The generated !ActiveRecord and !ActiveQuery classes take advantage of this relationship to offer smart getters and filters. - -## Advanced Column Types ## - -Propel 1.6 introduces a new set of column types. The database-agnostic implementation allows these column types to work on all supported RDBMS. - -### ENUM Columns ### - -Although stored in the database as integers, ENUM columns let users manipulate a set of predefined values, without worrying about their storage. - -```xml - - ... - -
-``` - -```php -setStyle('novel'); -echo $book->getStyle(); // novel -// Trying to set a value not in the valueSet throws an exception - -// Each value in an ENUM column has a related constant in the Peer class -// Your IDE with code completion should love this -echo BookPeer::STYLE_NOVEL; // 'novel' -echo BookPeer::STYLE_ESSAY; // 'essay' -echo BookPeer::STYLE_POETRY; // 'poetry' -// The Peer class also gives access to list of available values -print_r(BookPeer::getValueSet(BookPeer::STYLE)); // array('novel', 'essay', 'poetry') - -// ENUM columns are also searchable, using the generated filterByXXX() method -// or other ModelCritera methods (like where(), orWhere(), condition()) -$books = BookQuery::create() - ->filterByStyle('novel') - ->find(); -``` - -### OBJECT Columns ### - -An `OBJECT` column can store PHP objects (mostly Value Objects) in the database. The column setter serializes the object, which is later stored to the database as a string. The column getter unserializes the string and returns the object. Therefore, for the end user, the column contains an object. - -```php -latitude = $latitude; - $this->longitude = $longitude; - } - - public function isInNorthernHemisphere() - { - return $this->latitude > 0; - } -} - -// The 'house' table has a 'coordinates' column of type OBJECT -$house = new House(); -$house->setCoordinates(new GeographicCoordinates(48.8527, 2.3510)); -echo $house->getCoordinates()->isInNorthernHemisphere(); // true -$house->save(); -``` - -Not only do `OBJECT` columns benefit from these smart getter and setter in the generated Active Record class, they are also searchable using the generated `filterByXXX()` method in the query class: - -```php -filterByCoordinates(new GeographicCoordinates(48.8527, 2.3510)) - ->find(); -``` - -Propel looks in the database for a serialized version of the object passed as parameter of the `filterByXXX()` method. - -### ARRAY Columns ### - -An `ARRAY` column can store a simple PHP array in the database (nested arrays and associative arrays are not accepted). The column setter serializes the array, which is later stored to the database as a string. The column getter unserializes the string and returns the array. Therefore, for the end user, the column contains an array. - -```php -setTags(array('novel', 'russian')); -print_r($book->getTags()); // array('novel', 'russian') - -// If the column name is plural, Propel also generates hasXXX(), addXXX(), -// and removeXXX() methods, where XXX is the singular column name -echo $book->hasTag('novel'); // true -$book->addTag('romantic'); -print_r($book->getTags()); // array('novel', 'russian', 'romantic') -$book->removeTag('russian'); -print_r($book->getTags()); // array('novel', 'romantic') -``` - -Propel doesn't use `serialize()` to transform the array into a string. Instead, it uses a special serialization function, that makes it possible to search for values of `ARRAY` columns. - -```php -filterByTags(array('novel', 'russian'), Criteria::CONTAINS_ALL) - ->find(); - -// Search books that contain at least one of the specified tags -$books = BookQuery::create() - ->filterByTags(array('novel', 'russian'), Criteria::CONTAINS_SOME) - ->find(); - -// Search books that don't contain any of the specified tags -$books = BookQuery::create() - ->filterByTags(array('novel', 'russian'), Criteria::CONTAINS_NONE) - ->find(); - -// If the column name is plural, Propel also generates singular filter methods -// expecting a scalar parameter instead of an array -$books = BookQuery::create() - ->filterByTag('russian') - ->find(); -``` - -**Tip**: Filters on array columns translate to SQL as LIKE conditions. That means that the resulting query often requires a full table scan, and is not suited for large tables. - -**Warning**: Only generated Query classes (through generated `filterByXXX()` methods) and `ModelCriteria` (through `where()`, `orWhere()`, and `condition()`) allow conditions on `ENUM`, `OBJECT`, and `ARRAY` columns. `Criteria` alone (through `add()`, `addAnd()`, and `addOr()`) does not support conditions on such columns. - -## Table Subqueries (a.k.a "Inline Views") ## - -SQL supports table subqueries to solve complex cases that a single query can't solve, or to optimize slow queries with several joins. For instance, to find the latest book written by every author in SQL, it usually takes a query like the following: - -```sql -SELECT book.ID, book.TITLE, book.AUTHOR_ID, book.PRICE, book.CREATED_AT, MAX(book.CREATED_AT) -FROM book -GROUP BY book.AUTHOR_ID -``` - -Now if you want only the cheapest latest books with a single query, you need a subquery: -```sql -SELECT lastBook.ID, lastBook.TITLE, lastBook.AUTHOR_ID, lastBook.PRICE, lastBook.CREATED_AT -FROM -( - SELECT book.ID, book.TITLE, book.AUTHOR_ID, book.PRICE, book.CREATED_AT, MAX(book.CREATED_AT) - FROM book - GROUP BY book.AUTHOR_ID -) AS lastBook -WHERE lastBook.PRICE < 20 -``` - -To achieve this query using Propel, call the new `addSelectQuery()` method to use a first query as the source for the SELECT part of a second query: - -```php -$latestBooks = BookQuery::create() - ->withColumn('MAX(Book.CreatedAt)') - ->groupBy('Book.AuthorId'); -$latestCheapBooks = BookQuery::create() - ->addSelectQuery($latestBooks, 'lastBook') - ->where('lastBook.Price < ?', 20) - ->find(); -``` - -You could use two queries or a WHERE IN to achieve the same result, but it wouldn't be as effective. - -Inline views are used a lot in Oracle, so this addition should make Propel even more Oracle-friendly. - -## Better Pluralizer ## - -Have you ever considered Propel as a lame English speaker? Due to its poor pluralizer, Propel used to be unable to create proper getter methods in one-to-many relationships when dealing with foreign objects named 'Child', 'Category', 'Wife', or 'Sheep'. - -Starting with Propel 1.6, Propel adds a new pluralizer class named `StandardEnglishPluralizer`, which should take care of most of the irregular plural forms of your domain class names. This new pluralizer is disabled by default for backwards compatibility reasons, but it's very easy to turn it on in your `build.properties` file: - - propel.builder.pluralizer.class = builder.util.StandardEnglishPluralizer - -Rebuild your model, and voila: ActiveRecord objects can now retrieve foreign objects using `getChildren()`, `getCategories()`, `getWives()`, and... `getSheep()`. - -## New Reference Chapter in the Documentation: Active Record ## - -There wasn't any one-stop place to read about the abilities of the generated Active Record objects in the Propel documentation. Since Propel 1.6, the new [Active Record reference](/reference/active-record.html) makes it easier to learn the usage of Propel models using code examples. - -## Miscellaneous ## - - * The runtime performance was tweaked on various places. Small boosts may be visible when logging is enabled, and on large batch processes. - * A new `dbd2propel` task allows to convert [DBDesigner 4](http://www.fabforce.net/dbdesigner4/) models to a Propel schema in an easier way than the previous PHP script located under the `contrib/` folder. See the new [DBDesigner2Propel chapter]((../cookbook/dbdesigner.html)) for details. - * By the way, the `contrib/` directory was removed from the core - it was not maintained anymore. Previous versions can be found in the 1.5 branch. - * `ModelCriteria::keepQuery()` is now ON by default. This will remove bad surprises when doing a `count()` before a `find()`, and has a very limited impact on performance. You should not need to manually call `keepQuery()` anymore. - * `ModelCriteria::with()` is now a little smarter, and therefore reduces even more the query count when hydrating one-to-many relationships. - * Runtime Exceptions now look better in PHP 5.3, since Propel takes advantage of exception linking - * Read queries (issued by Query termination methods `findPk()`, `find()`, `findOne()`, and `count()`) now don't create a transaction by default. Write queries still do. - * The ActiveRecord and ActiveQuery APIs have been harmonized to provide similar parameter conversion for temporal and boolean columns in generated setters and filters. For instance, you can now set a boolean column to 'yes', or filter a temporal column on the 'now' string. The phpDoc blocks of generated setters and filters were rewritten to reflect these changes, so check the generated classes for details. - * The generated `toArray()` method for ActiveRecord objects can now include collections, without risk of infinite recursion. Therefore `$author->toArray()` also outputs the collection of Books by the author. The dumping abilities introduced in 1.6 take advantage of this feature. - * `PropelModelPager` now behaves more like a `PropelCollection`, thanks to the addition of `isFirst()`, `isLast()`, `isOdd()`, and `isEven()` methods. That means that it's easier to replace a `find()` by a `paginate()` as termination method for a query if there is a complex formatting. - * Despite all that you may have read about PHP 5.3's garbage collector, memory leaks still exist on some particular circular references. Propel generates a `clearAllReferences()` method on all !ActiveRecord classes, and this method has been improved in Propel 1.6 to handle the existing leaks. Use it when you iterate on large collections of objects, or where the memory is limited. - * For each database, Behaviors are now added to a priority queue. This allows to execute some behaviors before others, and to solve conflicts between behaviors. See [How to Write a Behavior](../cookbook/writing-behavior.html#specifying-a-priority-for-behavior-execution). - * Behaviors can now modify existing methods even if no hook is called in the builders, thanks to a new service class called `PropelPHPParser`. This class can remove a method, replace a method by another one, or add a new method before or after an existing one. See the [Behavior Documentation](07-behaviors.html#replacing-or-removing-existing-methods) for details. - * Propel now supports tablespace definition on Oracle tables using custom `` tags. See the [Schema reference](../reference/schema.html#adding-vendor-info) for details. - * The built-in [Runtime Introspection](../cookbook/runtime-introspection.html) classes are now a little smarter: `TableMap` objects have the knowledge of the primary string column. diff --git a/download.markdown b/download.markdown deleted file mode 100644 index abca7af3..00000000 --- a/download.markdown +++ /dev/null @@ -1,80 +0,0 @@ ---- -layout: default -title: Download Propel ---- - -# Download Propel # - -For a full installation tutorial, check the [Installation documentation](documentation/01-installation). The following options allow you to download the Propel code and documentation. - -## Git ## - -Clone it: - -```bash -$ git clone git://github.com/propelorm/Propel.git -``` - -Or add it as a submodule: - -```bash -$ git submodule add git://github.com/propelorm/Propel.git /path/to/propel -``` - -## Subversion Checkout / Externals ## - -```bash -$ svn co http://svn.github.com/propelorm/Propel.git -``` ->**Warning**
SVN is no longer the default Source Code Management since 2011. - -## PEAR Installer ## - -Propel is available through its own PEAR channel [pear.propelorm.org](pear.propelorm.org), in two separate packages for generator and runtime: - -```bash -$ pear channel-discover pear.propelorm.org -$ sudo pear install -a propel/propel_generator -$ sudo pear install -a propel/propel_runtime -``` - -Propel depends on the Phing library, and the dependency should be properly handled by PEAR thanks to the -a option above. Alternatively, you can install Phing separately: - -```bash -$ pear channel-discover pear.phing.info -$ sudo pear install phing/phing -``` - ->**Tip**
If you would like to use a beta or RC version of Propel, you may need to change your preferred_state PEAR environment variable. - -## Full Propel Package ## - -Please download one of the packages below if you would like to install the traditional Propel package, which includes both runtime and generator components. - -* [Last version of Propel as ZIP file](https://github.com/propelorm/Propel/zipball/master) -* [Last version of Propel as TAR.GZ file](https://github.com/propelorm/Propel/tarball/master) - -Other releases are available for download at [files.propelorm.org](http://files.propelorm.org). - -## License ## - -Copyright (c) 2005-2011 Hans Lellelid, David Zuelke, Francois Zaninotto, William -Durand - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/index.markdown b/index.markdown deleted file mode 100644 index 9df41067..00000000 --- a/index.markdown +++ /dev/null @@ -1,83 +0,0 @@ ---- -layout: home ---- - -![Propel logo](./images/propel-logo.png) - -### What? ### - -Propel is an open-source Object-Relational Mapping (ORM) for PHP5. It allows you to access your database using a set of objects, providing a simple API for storing and retrieving data. - -### Why? ### - -Propel gives you, the web application developer, the tools to work with databases in the same way you work with other classes and objects in PHP. - -* Propel gives your database a well-defined API. -* Propel uses the PHP5 OO standards -- Exceptions, autoloading, Iterators and friends. - -Propel makes database coding fun again. - -### Show Me! ### - -```php -findPK(123); // retrieve a record from a database -$book->setName('Don\'t be Hax0red!'); // modify. Don't worry about escaping -$book->save(); // persist the modification to the database - -$books = BookQuery::create() // retrieve all books... - ->filterByPublishYear(2009) // ... published in 2009 - ->orderByTitle() // ... ordered by title - ->joinWith('Book.Author') // ... with their author - ->find(); -foreach($books as $book) { - echo $book->getAuthor()->getFullName(); -} -``` - -### Get It! ### - -Fork the Propel GitHub repository at [http://github.com/propelorm/propel](http://github.com/propelorm/propel). - -### Dive In! ### - -Propel uses PDO as an abstraction layer, and code generation to remove the burden of runtime introspection. Therefore Propel is *fast*. - -Propel implements all the key concepts of mature ORM layers: the ActiveRecord pattern, validators, behaviors, table inheritance, reverse engineering an existing database, nested sets, nested transactions, lazy loading, LOB, you name it. - -Propel is built for developers who need to keep control of their code: - -* Extensibility is at the heart of Propel's design; whatever you need to customize, Propel allows you to do so in a snap. -* Propel can get out of your way for when you need custom queries or hyper-optimized transactions. -* If you need to change your RDBMS in the course of the project, rebuild your model and you're ready to go. Propel supports MySQL, PostgreSQL, SQLite, MSSQL, and Oracle. -* The code generated by Propel is well commented, IDE-friendly and easy to use. -* The Propel project started in 2005, and already powers thousands of websites. Thoroughly documented, backed by many tutorials across the web, it also benefits from an enthusiast community that provides rapid support for both beginner and hardcore developers. - -Propel is released under the [MIT license](https://github.com/propelorm/Propel/blob/master/LICENSE). It's free to use, even in commercial applications. - -Do you want to know more? Jump to the Documentation tab, or start exploring the code in the GitHub repository. - - -### Propel ECG (Build Status) ### - -Propel is strongly unit tested. Propel is developed under Continuous -Integration and with a Test Driven Development approach. -We use [Travis-CI](http://travis-ci.org) to automatically build our projects, -and here are the statuses: - - - - - - - - - - - - - - - - -
Propel 1.6Propel2 (unstable)sfPropelORMPluginPropelBundlePropelServiceProvider
diff --git a/js/anchorify.min.js b/js/anchorify.min.js deleted file mode 100644 index 618ea24e..00000000 --- a/js/anchorify.min.js +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * William DURAND - * MIT Licensed - */ -(function(b){var a=(function(){var c=/[ ;,.'?!_]/g;function d(f){return f.text().trim().replace(c,"-").replace(/[-]+/g,"-").replace(/-$/,"").toLowerCase()}function e(f){var h=1,g=f;while(0!==b("#"+f).length){f=g+"-"+h++}return f}return{anchorify:function(g){var h=g.text||"¶",i=g.cssClass||"anchor-link",f=g.$el.attr("id")||e(d(g.$el));g.$el.attr("id",f)[g.position||"append"](['',h,""].join(""))}}})();b.fn.anchorify=function(c){this.each(function(){a.anchorify(b.extend({},c||{},{$el:b(this)}))});return this}})(jQuery); \ No newline at end of file diff --git a/js/ga.js b/js/ga.js deleted file mode 100644 index 27b39c03..00000000 --- a/js/ga.js +++ /dev/null @@ -1,9 +0,0 @@ -var _gaq = _gaq || []; -_gaq.push(['_setAccount', 'UA-25671470-1']); -_gaq.push(['_trackPageview']); - -(function() { - var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; - ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; - var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s); -})(); diff --git a/js/jquery.tableofcontents.min.js b/js/jquery.tableofcontents.min.js deleted file mode 100644 index 117763cc..00000000 --- a/js/jquery.tableofcontents.min.js +++ /dev/null @@ -1,12 +0,0 @@ -/* - TableOfContents Plugin for jQuery (by Doug Neiner) - - Version: 0.8 - - Based on code and concept by Janko Jovanovic - in his article: http://www.jankoatwarpspeed.com/post/2009/08/20/Table-of-contents-using-jQuery.aspx - - This plugin is offered under the MIT license. - (c) 2009 by Doug Neiner, http://dougneiner.com -*/ -(function($){$.TableOfContents=function(el,scope,options){var base=this;base.$el=$(el);base.el=el;base.toc="";base.listStyle=null;base.tags=["h1","h2","h3","h4","h5","h6"];base.init=function(){base.options=$.extend({},$.TableOfContents.defaultOptions,options);if(typeof(scope)=="undefined"||scope==null)scope=document.body;base.$scope=$(scope);var $first=base.$scope.find(base.tags.join(', ')).filter(':first');if($first.length!=1)return;base.starting_depth=base.options.startLevel;if(base.options.depth<1)base.options.depth=1;var filtered_tags=base.tags.splice(base.options.startLevel-1,base.options.depth);base.$headings=base.$scope.find(filtered_tags.join(', '));if(base.options.topLinks!==false){var id=$(document.body).attr('id');if(id==""){id=base.options.topBodyId;document.body.id=id};base.topLinkId=id};if(base.$el.is('ul')){base.listStyle='ul'}else if(base.$el.is('ol')){base.listStyle='ol'};base.buildTOC();if(base.options.proportionateSpacing===true&&!base.tieredList()){base.addSpacing()};return base};base.tieredList=function(){return(base.listStyle=='ul'||base.listStyle=='ol')};base.buildTOC=function(){base.current_depth=base.starting_depth;base.$headings.each(function(i,element){var depth=this.nodeName.toLowerCase().substr(1,1);if(i>0||(i==0&&depth!=base.current_depth)){base.changeDepth(depth)};base.toc+=base.formatLink(this,depth,i)+"\n";if(base.options.topLinks!==false)base.addTopLink(this)});base.changeDepth(base.starting_depth,true);if(base.tieredList())base.toc="
  • \n"+base.toc+"
  • \n";base.$el.html(base.toc)};base.addTopLink=function(element){var text=(base.options.topLinks===true?"Top":base.options.topLinks);var $a=$("").html(text);$(element).append($a)};base.formatLink=function(element,depth,index){var id=element.id;if(id==""){id=base.buildSlug($(element).text());element.id=id};var a="';return a};base.changeDepth=function(new_depth,last){if(last!==true)last=false;if(!base.tieredList()){base.current_depth=new_depth;return true};if(new_depth>base.current_depth){var opening_tags=[];for(var i=base.current_depth;i'+"\n")};var li="
  • \n";base.toc+=opening_tags.join(li)+li}else if(new_depthnew_depth;i--){closing_tags.push(''+"\n")};base.toc+="
  • \n"+closing_tags.join(''+"\n");if(!last){base.toc+="\n
  • \n"}}else{if(!last){base.toc+="
  • \n
  • \n"}};base.current_depth=new_depth};base.buildSlug=function(text){text=text.toLowerCase().replace(/[^a-z0-9 -]/gi,'').replace(/ /gi,'-');text=text.substr(0,50);return text};base.depthClass=function(depth){return base.options.levelClass.replace('%',(depth-(base.starting_depth-1)))};base.addSpacing=function(){var start=base.$headings.filter(':first').position().top;base.$headings.each(function(i,el){var $a=base.$el.find('a:eq('+i+')');var pos=(($(this).position().top-start)/(base.$scope.height()-start))*base.$el.height();$a.css({position:"absolute",top:pos})})};return base.init()};$.TableOfContents.defaultOptions={startLevel:1,depth:3,levelClass:"toc-depth-%",levelText:"%",topLinks:false,topLinkClass:"toc-top-link",topBodyId:"toc-top",proportionateSpacing:false};$.fn.tableOfContents=function(scope,options){return this.each(function(){var toc=new $.TableOfContents(this,scope,options);delete toc})}})(jQuery); diff --git a/reference/active-record.markdown b/reference/active-record.markdown deleted file mode 100644 index 581d8ef3..00000000 --- a/reference/active-record.markdown +++ /dev/null @@ -1,730 +0,0 @@ ---- -layout: documentation -title: "Active Record Reference" ---- - -# Active Record Reference # - -Propel generates smart Active Record classes based on the schema definition of tables. Active Record objects offer a powerful API to manipulating database records in an intuitive way. - -## Overview ## - -For each table present in the XML schema, Propel generates one Active Record class - also called the Model class on some parts of the documentation. Instances of the Active Record classes represent a single row from the database, as specified in the [Active record design pattern](http://en.wikipedia.org/wiki/Active_record_pattern). That makes it easy to create, edit, insert or delete an individual row in the persistence layer. - -Consider the following schema, describing a simple `book` table with four columns: - -```xml - - - - - -
    -``` - -Based on this schema, Propel generates a `Book` class that lets you manipulate `book` records: - -```php -setTitle('War and Peace'); -$book->setISBN('067003469X'); -$book->save(); -// INSERT INTO book (title, isbn) VALUES ('War and Peace', '067003469X') - -$book->delete(); -// DELETE FROM book WHERE id = 1234; -``` - -The best way to learn what a generated Active Record class can do is to inspect the generated code - all methods are fully documented. - -## Active Record Class Naming Conventions ## - -```xml - - -
    - - - -
    - - - -
    - - - -
    - - - -
    - -``` - -```php -getTitle()); - } -} - -// To generate Active Record classes using a particular namespace in PHP 5.3, -// set the namespace attribute in the
    tag. -//
    -// generates the following stub Active Record class: -namespace Bookstore; -use Bookstore\om\BaseBook; - -class Book extends BaseBook -{ -} -``` - ->**Tip**
    See the [PHP 5.3 Namespaces](../cookbook/namespaces) chapter for more information on namespace usage in Propel. - -## Generated Getter and Setter ## - -```php -setTitle('War and Peace'); -$book->setISBN('067003469X'); -$book->setAuthorId(456745); - -// For each column, Propel also generates a getter method, also called "accessor" -echo $book->getTitle(); // 'War and Peace' -echo $book->getISBN(); // '067003469X' -echo $book->getAuthorId(); // 456745 - -// Every class has a getPrimaryKey() method. -// For tables with single column PK, it is a synonym for the PK getter -echo $book->getPrimaryKey(); // 1234 -// same as -echo $book->getId(); // 1234 -// For tables with composite PKs, getPrimaryKey() returns an array -print_r($bookOpinion->getPrimaryKey()); // array(1234, 67) -``` - -By default, Propel uses a CamelCase version of the column name for these methods: - -| column name | getter & setter method names -|---------------|--------------------------------------- -| title | `getTitle()`, `setTitle()` -| is_published | `getIsPublished()`, `setIsPublished()` -| author_id | `getAuthorId()`, `setAuthorId()` -| isbn | `getIsbn()`, `setIsbn()` - -To use a custom name for these methods, set the `phpName` attribute in the `` tag - -```xml - - - - - - - -``` - ->**Tip**
    Calling the setter on an autoincremented PK will throw an exception as soon as you try to save the object. You can allow PK inserts on such columns by setting the `allowPkInsert` attribute to `true` in the `
    ` tag of the XML schema. -
    ->**Tip**
    Calling a getter doesn't issue any database query, except for lazy-loaded columns. - -## Persistence Methods ## - -Active Record objects provide only two methods that may alter the data stored in the database: `save()`, and `delete()`. - -```php -setTitle('War and Peas'); -$book->save(); -// INSERT INTO book (title) VALUES ('War and Peace') - -// On tables with autoincremented PKs, the PK value is available immediately after saving -echo $book->getId(); // 1234 - -// To update an object in the database, also use save(). -// Propel knows when an object is new and when it was already persisted -// Therefore it correctly translates save() to either INSERT or UPDATE in SQL -$book->setTitle('War and Peace'); -$book->save(); -// UPDATE book SET title = 'War and Peace' WHERE id = 1234 - -// Generated SQL statements use PDO for binding, so the database is safe from SQL injections -// http://en.wikipedia.org/wiki/SQL_injection -$title = $_REQUEST['title']; -$book->setTitle($title); -$book->save(); // no need to worry - -// Propel inspects changes in the properties of Active Record objects before saving, -// so calling save() on an unchanged object issues no query to the database -$book->save(); // no additional query - -// To delete an object from the database, call the delete() method -$book->delete(); -// DELETE FROM book WHERE id = 1234 - -// All persistence methods accept a connection object -$con = Propel::getConnection(BookPeer::DATABASE_NAME, Propel::CONNECTION_WRITE); -$book->delete($con); -``` - -## Relationship Getters and Setters ## - -Consider the previous `book` table, now with a foreign key to an `author` table: - -```xml -
    - - - - - - - -
    - - - - -
    -``` - -Based on this schema, Propel defines: - -* A many-to-one relationship from the `Book` class to the `Author` class -* A one-to-many relationship from the `Author` class to the `Book` class - -See the [Relationships documentation](../documentation/04-relationships) for more details. - -For each relationship, Propel generates additional getters and setters. - -### One-to-many Relationships ### - -```php -setFirstName('Leo'); -$author->setLastName('Tolstoi'); -$book = new Book(); -$book->setTitle('War and Peace'); -// A Book has a one Author, therefore Propel generates Book::setAuthor() and Book::getAuthor() methods -$book->setAuthor($author); -echo $book->getAuthor()->getLastName(); // Tolstoi -// This allows to relate two objects without worrying about the primary and foreign keys, -// and it even works on objects not yet persisted. -``` - ->**Tip**
    By default, Propel uses the name of the Active Record class to generate the related object getter and setter. However, you can customize this name by setting the `phpName` attribute in the `` tag: - -```xml - - - - -
    - - INSERT INTO BAR ('hey', 'there') - - - - - - mysql:host=slave-server1; dbname=bookstore - - - mysql:host=slave-server2; dbname=bookstore - - - - - - -
    - - true - - - - true - 1 - -
    -
    -
    - - -``` - -## Explanation of Configuration Sections ## - -Below you will find an explanation of the primary elements in the configuration. - -### `` ### - -If the `` element is present, Propel will use the specified information to instantiate a PEAR Log logger. - -```xml - - - file - /path/to/logger.log - my-app - 7 - -``` - -The nested elements correspond to the configuration options for the logger (options that would otherwise be passed to **Log::factory()** method). - -|Element |Default |Description -|-----------|-------------------|---------------------------------------------------------------------------------------------- -| type |file |The logger type. -| name |./propel.log |Name of log, meaning is dependent on type specified. (For _file_ type this is the filename). -| ident |propel |The identifier tag for the log. -| level |7 (PEAR_LOG_DEBUG) |The logging level. - -This log configuring API is designed to provide a simple way to get log output from Propel; however, if your application already has a logging mechanism, we recommend instead that you use your existing logger (writing a simple log adapter, if you are using an unsupported logger). See the [Logging documentation](../documentation/08-logging) for more info. - -### `` ### - -This is the top-level tag for Propel datasources configuration. - -```xml - - - -``` - -### `` ### - -```xml - - - - -``` -A specific datasource being configured. - -* The @id must match the `` @name attribute from your `schema.xml`. - -### `` ### - -The adapter to use for Propel. Currently supported adapters: sqlite, pgsql, mysql, oracle, mssql. Note that it is possible that your adapter could be different from your connection driver (e.g. if using ODBC to connect to MSSQL database, you would use an ODBC PDO driver, but MSSQL Propel adapter). - -```xml - - - - - sqlite -``` - -### `` ### - -The PDO database connection for the specified datasource. - -Nested elements define the DSN, connection options, other PDO attributes, and finally some Propel-specific initialization settings. - -```xml - - - - - -``` - -#### `` #### - -A custom PDO class (must be a PropelPDO subclass) that you would like to use for the PDO connection. - -```xml - - - - - - DebugPDO -``` - -This can be used to specify the alternative **DebugPDO** class bundled with Propel, or your own subclass. _Your class must extend PropelPDO, because Propel requires the ability to nest transactions (without having exceptions being thrown by PDO)._ - -#### `` #### - -The PDO DSN that Propel will use to connect to the database for this datasource. - -```xml - - - - - - mysql:host=localhost;dbname=bookstore -``` - -See the PHP documentation for specific format: - * [MySQL DSN](http://www.php.net/manual/en/ref.pdo-mysql.connection.php) - * [PostgreSQL DSN](http://php.net/manual/en/ref.pdo-pgsql.connection.php) - * [SQLite DSN](http://www.php.net/manual/en/ref.pdo-sqlite.connection.php) - * [Oracle DSN](http://www.php.net/manual/en/ref.pdo-oci.connection.php) - * [MSSQL DSN](http://www.php.net/manual/en/ref.pdo-dblib.connection.php) - -Note that some database (e.g. PostgreSQL) specify username and password as part of the DSN while the others specify user and password separately. - -#### `` and `` #### - -Specifies credentials for databases that specify username and password separately (e.g. MySQL, Oracle). - -```xml - - - - - - mysql:host=localhost;dbname=bookstore - test - testpass -``` - -#### `` #### - -Specify any options which _must_ be specified when the PDO connection is created. For example, the ATTR_PERSISTENT option must be specified at object creation time. - -See the [PDO documentation](http://www.php.net/pdo) for more details. - -```xml - - - - - - - - - -``` - -#### `` #### - -`` are similar to ``; the difference is that options specified in `` are set after the PDO object has been created. These are set using the [PDO->setAttribute()](http://us.php.net/PDO-setAttribute) method. - -In addition to the standard attributes that can be set on the PDO object, there are also the following Propel-specific attributes that change the behavior of the PropelPDO connection: - -|Attribute constant | Valid Values (Default) | Description | -|---------------------------------------|---------------------------|----------------------------------------------------------------- -| PropelPDO::PROPEL_ATTR_CACHE_PREPARES | true/false (false) | Whether to have the PropelPDO connection cache the PDOStatement prepared statements. This will improve performance if you are executing the same query multiple times by your script (within a single request / script run). - -> Note that attributes in the XML can be specified with or without the PDO:: (or PropelPDO::) constant prefix. - -```xml - - - - - - - - - - - -``` - ->**Tip**
    If you are using MySQL and get the following error : "SQLSTATE[HY000]: General error: 2014 Cannot execute queries while other unbuffered queries are active", you can try adding the following attribute: - -```xml - -``` - -#### `` #### - -Settings are Propel-specific options used to further configure the connection -- or perform other user-defined initialization tasks. - -Currently supported settings are: - - * charset - * queries - -##### charset ##### - -Specifies the character set to use. Currently you must specify the charset in the way that is understood by your RDBMS. Also note that not all database systems support specifying charset (e.g. SQLite must be compiled with specific charset support). Specifying this option will likely result in an exception if your database doesn't support the specified charset. - -```xml - - - - - - - - utf8 - -``` - -##### queries ##### - -Specifies any SQL statements to run when the database connection is initialized. This can be used for any environment setup or db initialization you would like to perform. These statements will be executed with every Propel initialization (e.g. every PHP script load). - -```xml - - - - - - - - - set search_path myschema, public - INSERT INTO BAR ('hey', 'there') - - -``` - -### `` ### - -```xml - - - - - -``` - -The `` tag groups slave `` elements which provide support for configuring slave db servers -- when using Propel in a master-slave replication environment. See the [Master-Slave documentation](../cookbook/replication.html) for more information. The nested `` elements are configured the same way as the top-level `` element is configured. - -### `` ### - -The optional `` element may be provided to pass additional logging configuration options to DebugPDO. Note that these settings have no effect unless DebugPDO has been selected in `runtime-conf.xml` as the PDO connection class. See the [Logging documentation](../documentation/08-logging) for more information on configuring DebugPDO. diff --git a/reference/schema.markdown b/reference/schema.markdown deleted file mode 100644 index 513366de..00000000 --- a/reference/schema.markdown +++ /dev/null @@ -1,469 +0,0 @@ ---- -layout: documentation -title: "Database Schema" ---- - -# Database Schema # - -The schema for `schema.xml` contains a small number of elements with required and optional attributes. The Propel generator contains a DTD that can be used to validate your `schema.xml` document. Also, when you build your SQL and OM, the Propel generator will automatically validate your `schema.xml` file using a highly-detailed XSD. - -## At-a-Glance ## - -The hierarchical tree relationship for the elements is: - -```xml - - - - - - - - - - - - - - - -
    - -
    -``` - -You can find example schemas in the test fixtures that the Propel development team uses for unit testing. For instance, the bookstore schema describes the model of a Bookstore application. - ->**Tip**
    If you use an IDE supporting autocompletion in XML documents, you can take advantage of the XSD describing the `schema.xml` syntax to suggest elements and attributes as you type. To enable it, add a `xmlns:xsi` and a `xsi:noNamespaceSchemaLocation` attribute to the leading `` tag: - -```xml - -``` - -## Detailed Reference ## - -This page provides an alternate rendering of the Appendix B - Schema Reference from the user's guide. -It spells out in specific detail, just where each attribute or element belongs. - -First, some conventions: - -* Text surrounded by a `/` is text that you would provide and is not defined in the language. (i.e. a table name is a good example of this.) -* Optional items are surrounded by `[` and `]` characters. -* Items where you have an alternative choice have a `|` character between them (i.e. true|false) -* Alternative choices may be delimited by `{` and `}` to indicate that this is the default option, if not overridden elsewhere. -* **...** means repeat the previous item. - -### database element ### - -Starting with the `` element. The _attributes_ and _elements_ available are: - -```xml - - - - ... - -``` - -Only the `name` and the `defaultIdMethod` attributes are required. - -A Database element may include an `` element, or multiple `
    ` elements. - -#### Database Attributes #### - -* `defaultIdMethod` sets the default id method to use for auto-increment columns. -* `package` specifies the "package" for the generated classes. Classes are created in subdirectories according to the `package` value. -* `schema` specifies the default SQL schema containing the tables. Ignored on RDBMS not supporting database schemas. -* `namespace` specifies the default namespace that generated model classes will use (PHP 5.3 only). This attribute can be completed or overridden at the table level. -* `baseClass` allows you to specify a default base class that all generated Propel objects should extend (in place of `propel.om.BaseObject`). -* `basePeer` instructs Propel to use a different SQL-generating `BasePeer` class (or sub-class of `BasePeer`) for all generated objects. -* `defaultPhpNamingMethod` the default naming method to use for tables of this database. Defaults to `underscore`, which transforms table names into CamelCase phpNames. -* `heavyIndexing` adds indexes for each component of the primary key (when using composite primary keys). -* `tablePrefix` adds a prefix to all the SQL table names. - -### table element ### - -The `
    ` element is the most complicated of the usable elements. Its definition looks like this: - -```xml -
    - - - ... - - ... - - ... - - ... - - ... -
    -``` - -According to the schema, `name` is the only required attribute. Also, the `idMethod`, `package`, `schema`, `namespace`, `phpNamingMethod`, `baseClass`, `basePeer`, and `heavyIndexing` attributes all default to what is specified by the `` element. - -#### Table Attributes #### - -* `idMethod` sets the id method to use for auto-increment columns. -* `phpName` specifies object model class name. By default, Propel uses a CamelCase version of the table name as phpName. -* `package` specifies the "package" (or subdirectory) in which model classes get generated. -* `schema` specifies the default SQL schema containing the table. Ignored on RDBMS not supporting database schemas. -* `namespace` specifies the namespace that the generated model classes will use (PHP 5.3 only). If the table namespace starts with a `\`, it overrides the namespace defined in the `` tag; otherwise, the actual table namespace is the concatenation of the database namespace and the table namespace. -* `skipSql` instructs Propel not to generate DDL SQL for the specified table. This can be used together with `readOnly` for supporting VIEWS in Propel. -* `abstract` Whether the generated _stub_ class will be abstract (e.g. if you're using inheritance) -* `isCrossRef` Whether this is a cross-reference table (or "junction" table) for a [many-to-many relationship](../documentation/04-relationships.html#manytomany_relationships) -* `phpNamingMethod` the naming method to use. Defaults to `underscore`, which transforms the table name into a CamelCase phpName. -* `baseClass` allows you to specify a class that the generated Propel objects should extend (in place of `propel.om.BaseObject`). -* `basePeer` instructs Propel to use a different SQL-generating `BasePeer` class (or sub-class of `BasePeer`). -* `heavyIndexing` adds indexes for each component of the primary key (when using composite primary keys). -* `readOnly` suppresses the mutator/setter methods, save() and delete() methods. -* `treeMode` is used to indicate that this table is part of a node tree. Currently the only supported values are `NestedSet` (see the [NestedSet behavior section](../behaviors/nested-set.html)) and `MaterializedPath` (deprecated). -* `reloadOnInsert` is used to indicate that the object should be reloaded from the database when an INSERT is performed. This is useful if you have triggers (or other server-side functionality like column default expressions) that alters the database row on INSERT. -* `reloadOnUpdate` is used to indicate that the object should be reloaded from the database when an UPDATE is performed. This is useful if you have triggers (or other server-side functionality like column default expressions) that alters the database row on UPDATE. -* `allowPkInsert` can be used if you want to define the primary key of a new object being inserted. By default if idMethod is "native", Propel would throw an exception. However, in some cases this feature is useful, for example if you do some replication of data in an master-master environment. It defaults to false. - -### column element ### - -```xml - - [] - -``` - -#### Column Attributes #### - -* `type` The database-agnostic column type. Propel maps native SQL types to these types depending on the RDBMS. Using Propel types guarantees that a column definition is portable. -* `sqlType` The SQL type to be used in CREATE and ALTER statements (overriding the mapping between Propel types and RMDBS type) -* `defaultValue` The default value that the object will have for this column in the PHP instance after creating a "new Object". This value is always interpreted as a string. -* `defaultExpr` The default value for this column as expressed in SQL. This value is used solely for the "sql" target which builds your database from the schema.xml file. The defaultExpr is the SQL expression used as the "default" for the column. -* `valueSet` The list of enumerated values accepted on an ENUM column. The list contains 255 values at most, separated by commas. -* `lazyLoad` A lazy-loaded column is not fetched from the database by model queries. Only the generated getter method for such a column issues a query to the database. Useful for large column types (such as CLOB and BLOB). -* `primaryString` A column defined as primary string serves as default value for a `__toString()` method in the generated Propel object. -* `description` For adding a description to the column. In SQL the `description` is referred as `comment` - ->**Tip**
    For performance reasons, it is often a good idea to set BLOB and CLOB columns as lazyLoaded. A resultset containing one of more very large columns takes time to transit between the database and the PHP server, so you want to make sure this only happen when you actually need it. - -### foreign-key element ### - -To link a column to another table use the following syntax: - -```xml - - - -``` - -#### Foreign Key Attributes #### - -* `skipSql` Instructs Propel not to generate DDL SQL for the specified foreign key. This can be used to support relationships in the model without an actual foreign key. -* `defaultJoin` This affects the default join type used in the generated `joinXXX()` methods in the model query class. Propel uses an INNER JOIN for foreign keys attached to a required column, and a LEFT JOIN for foreign keys attached to a non-required column, but you can override this in the foreign key element. - -### index element ### - -To create an index on one or more columns, use the following syntax: - -```xml - - - ... - -``` - -In some cases your RDBMS may require you to specify an index size. - -### unique element ### - -To create a unique index on one or more columns, use the following syntax: - -```xml - - - ... - -``` - -In some cases your RDBMS may require you to specify an index size for unique indexes. - -### id-method-parameter element ### - -If you are using a database that uses sequences for auto-increment columns (e.g. PostgreSQL or Oracle), you can customize the name of the sequence using the `` tag: - -```xml - -``` - -### external-schema element ### - -The `` element includes another schema file from the filesystem into the current schema. The format is: - -```xml - -``` - -The `filename` can be relative or absolute. Beware that the external schema must contain a `` with the same name as the current element. By default, tables from external schemas are ignored by the `sql` task - that means that Propel won't try to _insert_ the external tables. If you want Propel to take the tables from an external schema into account in SQL, set the `referenceOnly` attribute to `false`. - -## Column Types ## - -Here are the Propel column types with some example mappings to native database and PHP types. There are also several ways to customize the mapping between these types. - -### Text Types ### - -|Propel Type|Desc |Example Default DB Type (MySQL)|Default PHP Native Type -|-----------|-----------------------------------|-------------------------------|----------------------- -|CHAR |Fixed-length character data |CHAR |string -|VARCHAR |Variable-length character data |VARCHAR |string -|LONGVARCHAR|Long variable-length character data|TEXT |string -|CLOB |Character LOB (locator object) |LONGTEXT |string - -`LONGVARCHAR` and `CLOB` need no declared size, and allow for very large strings (up to 2^16 and 2^32 characters in MySQL for instance). - -### Numeric Types ### - -|Propel Type|Desc |Example Default DB Type (MySQL)|Default PHP Native Type -|-----------|-----------------------|-------------------------------|--------------------------- -|NUMERIC |Numeric data |DECIMAL |string (PHP int is limited) -|DECIMAL |Decimal data |DECIMAL |string (PHP int is limited) -|TINYINT |Tiny integer |TINYINT |int -|SMALLINT |Small integer |SMALLINT |int -|INTEGER |Integer |INTEGER |int -|BIGINT |Large integer |BIGINT |string (PHP int is limited) -|REAL |Real number |REAL |double -|FLOAT |Floating point number |FLOAT |double -|DOUBLE |Floating point number |DOUBLE |double - ->**Tip**
    `BIGINT` maps to a PHP string, and therefore allows for 64 bit integers even on 32 bit systems. - -### Binary Types ### - -|Propel Type |Desc |Example Default DB Type (MySQL)|Default PHP Native Type -|---------------|-----------------------------------|-------------------------------|----------------------- -|BINARY |Fixed-length binary data |BLOB |double -|VARBINARY |Variable-length binary data |MEDIUMBLOB |double -|LONGVARBINARY |Long variable-length binary data |LONGBLOB |double -|BLOB |Binary LOB (locator object) |LONGBLOB |stream or string - ->**Tip**
    `BLOB` columns map to PHP as streams, and allows the storage of large binary objects (like images). - -### Temporal (Date/Time) Types ### - -|Propel Type|Desc |Example Default DB Type (MySQL)|Default PHP Native Type -|-----------|---------------------------------------|-------------------------------|----------------------- -|DATE |Date (e.g. YYYY-MM-DD) |DATE |DateTime object -|TIME |Time (e.g. HH:MM:SS) |TIME |DateTime object -|TIMESTAMP |Date + time (e.g. YYYY-MM-DD HH:MM:SS) |TIMESTAMP |DateTime object - -### Other Types ### - -* `BOOLEAN` columns map to a boolean in PHP. Depending on the native support for this type, they are stored in SQL as `BOOLEAN` or `TINYINT`. -* `ENUM` columns accept values among a list of predefined ones. Set the value set using the `valueSet` attribute, separated by commas. -* `OBJECT` columns map to PHP objects and are stored as strings. -* `ARRAY` columns map to PHP arrays and are stored as strings. - -### Legacy Temporal Types ### - -The following Propel 1.2 types are still supported, but are no longer needed with Propel 1.3. - -|Propel Type |Desc |Example Default DB Type (MySQL)|Default PHP Native Type -|---------------|-------------------------------------------------------|-------------------------------|----------------------- -|BU_DATE |Pre-/post-epoch date (e.g. 1201-03-02) |DATE |DateTime object -|BU_TIMESTAMP |Pre-/post-epoch Date + time (e.g. 1201-03-02 12:33:00) |TIMESTAMP |DateTime object - -## Customizing Mappings ## - -### Specify Column Attributes ### - -You can change the way that Propel maps its own types to native SQL types or to PHP types by overriding the values for a specific column. - -For example: - -(Overriding PHP type) - -```xml - -``` - -(Overriding SQL type) - -```xml - -``` - -### Adding Vendor Info ### - -Propel supports database-specific elements in the schema (currently only for MySQL). This "vendor" parameters affect the generated SQL. To add vendor data, add a `` tag with a `type` attribute specifying the target database vendor. In the `` tag, add `` tags with a `name` and a `value` attribue. For instance: - -```xml - - - - - -
    -``` - -This will change the generated SQL table creation to look like: - -```sql -CREATE TABLE book - () - ENGINE = InnoDB - DEFAULT CHARACTER SET utf8; -``` - -#### MySQL Vendor Info #### - -Propel supports the following vendor parameters for MySQL: - -|Name | Example values -|-------------------|--------------- -|// in `` element|  -|`Engine` | MYISAM (default), InnoDB, BDB, MEMORY, ISAM, MERGE, MRG_MYISAM, etc. -|`AutoIncrement` | 1234, N, etc -|`AvgRowLength` |   -|`Charset` | utf8, latin1, etc. -|`Checksum` | 0, 1 -|`Collate` | utf8_unicode_ci, latin1_german1_ci, etc. -|`Connection` | mysql://fed_user@remote_host:9306/federated/test_table (for FEDERATED storage engine) -|`DataDirectory` | /var/db/foo (for MyISAM storage engine) -|`DelayKeyWrite` | 0, 1 -|`IndexDirectory` | /var/db/foo (for MyISAM storage engine) -|`InsertMethod` | FIRST, LAST (for MERGE storage Engine) -|`KeyBlockSize` | 0 (default), 1024, etc -|`MaxRows` | 1000, 4294967295, etc -|`MinRows` | 1000 (for MEMORY storage engine) -|`PackKeys` | 0, 1, DEFAULT -|`RowFormat` | FIXED, DYNAMIC, COMPRESSED, COMPACT, REDUNDANT -|`Union` | (t1,t2) (for MERGE storage Engine) -|// in `` element|  -|`Charset` | utf8, latin1, etc. -|`Collate` | utf8_unicode_ci, latin1_german1_ci, etc. -|// in `` element|  -|`Index_type` | FULLTEXT - -#### Oracle Vendor Info #### - -Propel supports the following vendor parameters for Oracle: - -|Name | Example values -|-------------------|--------------- -|// in `
    ` element|  -|`PCTFree` | 20 -|`InitTrans` | 4 -|`MinExtents` | 1 -|`MaxExtents` | 99 -|`PCTIncrease` | 0 -|`Tablespace` | L_128K -|`PKPCTFree` | 20 -|`PKInitTrans` | 4 -|`PKMinExtents` | 1 -|`PKMaxExtents` | 99 -|`PKPCTIncrease` | 0 -|`PKTablespace` | IL_128K -|// in `` element|  -|`PCTFree` | 20 -|`InitTrans` | 4 -|`MinExtents` | 1 -|`MaxExtents` | 99 -|`PCTIncrease` | 0 -|`Tablespace` | L_128K - -### Using Custom Platform ### - -For overriding the mapping between Propel types and native SQL types, you can create your own Platform class and override the mapping. - -For example: - -```php -setSchemaDomainMapping(new Domain(PropelTypes::NUMERIC, "DECIMAL")); - $this->setSchemaDomainMapping(new Domain(PropelTypes::LONGVARCHAR, "TEXT")); - $this->setSchemaDomainMapping(new Domain(PropelTypes::BINARY, "BLOB")); - $this->setSchemaDomainMapping(new Domain(PropelTypes::VARBINARY, "MEDIUMBLOB")); - $this->setSchemaDomainMapping(new Domain(PropelTypes::LONGVARBINARY, "LONGBLOB")); - $this->setSchemaDomainMapping(new Domain(PropelTypes::BLOB, "LONGBLOB")); - $this->setSchemaDomainMapping(new Domain(PropelTypes::CLOB, "LONGTEXT")); - } -} -``` - -You must then specify that mapping in the `build.properties` for your project: - - propel.platform.class = propel.engine.platform.${propel.database}Platform - diff --git a/support.markdown b/support.markdown deleted file mode 100644 index c7706dc8..00000000 --- a/support.markdown +++ /dev/null @@ -1,40 +0,0 @@ ---- -layout: default -title: Support ---- - -# Support # - -There are several ways to get help when you are having problems with Propel. - - -## Mailing Lists ## - -In order to ask for support or help other users, use the **Propel Users** mailing-list: - -[http://groups.google.com/group/propel-users](http://groups.google.com/group/propel-users) - -If you want to help with the development of Propel, or if you want to be informed of the upcoming features, subscribe to the **Propel Development** mailing-list: - -[http://groups.google.com/group/propel-development](http://groups.google.com/group/propel-development) - -These lists were created in May 2009. A previous set mailing-lists were hosted by tigris.org, you can still consult the archives: - -* [Users list archives](http://propel.tigris.org/ds/viewForumSummary.do?dsForumId=1097) -* [Development list archives](http://propel.tigris.org/ds/viewForumSummary.do?dsForumId=1093) - - -## Stackoverflow ## - -You can ask other coders at [Stackoverflow Propel](http://stackoverflow.com/questions/tagged/propel). - -## IRC ## - -The [#propel](http://webchat.freenode.net/?channels=propel) channel on **irc.freenode.net** is an informal meeting place for discussing new ideas, asking support questions, or just airing general frustrations with the PHP5 engine :) - - -## Bug Reports ## - ->**Tip**
    Since 2011, the Propel project is hosted and managed on [GitHub](http://github.com). - -You can look at the [Propel organization](https://github.com/propelorm) and to report bugs or to ask for new features to the [Propel ticketing system](https://github.com/propelorm/Propel/issues).