Active Record reference
Propulsion generates Active Record classes based on the schema definition of tables. Active Record objects offer a powerful API to manipulate database records in an intuitive way. This API is essentially unchanged from Propel 1 — only the class/namespace names have been renamed (Propel* → Propulsion*); see Migrating from Propel 1 for the full list of renames.
Overview
Section titled “Overview”For each table present in the XML schema, Propulsion generates one Active Record class — also called the Model class elsewhere in this documentation. Instances of the Active Record classes represent a single row from the database, as specified in the Active record design 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:
<table name="book" description="Book Table"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" /> <column name="title" type="VARCHAR" required="true" /> <column name="isbn" required="true" type="VARCHAR" size="24" phpName="ISBN" /> <column name="author_id" required="false" type="INTEGER" /></table>Based on this schema, Propulsion generates a Book class that lets you manipulate book records:
<?php$book = new Book();$book->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
Section titled “Active Record class naming conventions”<!-- For each table, Propulsion creates an Active Record class in PHP named using a CamelCase version of the table name --><table name="book"><table name="book_reader"><!-- generates the following Active Record classes: Book, BookReader -->
<!-- Propulsion advocates the use of singular table names --><table name="books"><!-- generates the following Active Record class: Books (not good) -->
<!-- You can customize the name of an Active Record class in PHP by setting the phpName attribute in the <table> tag --><table name="foo_books" phpName="Book" /><!-- generates the following Active Record class: Book -->
<!-- Active Record classes are generated in the directory passed via --output-dir (or the propulsion.php.dir build property) --><table name="book"><!-- generates the Book class under /path/to/project/src/Model/Book.php -->
<!-- To group Active Record classes into subdirectories, set the package attribute in the <table> tag --><table name="book" package="bookstore"><!-- generates the Book class under /path/to/project/src/Model/bookstore/Book.php --><?php// Generated Active Record classes carry no generated logic of their own -- they// use a generated trait. That's why they are called "stub" classesclass Book extends BaseObject implements Persistent, Poolable, WritableModelInterface{ use BookGenerated;}
// Most of the generated code is actually in the *Generated traitstrait BookGenerated{ // lots of generated code}
// BaseObject, Persistent, Poolable, and WritableModelInterface are bundled with Propulsion
// Do not alter the code of a *Generated trait, as your modifications will be overridden// each time you rebuild the model. Instead, add your custom code to the stub classclass Book extends BaseObject implements Persistent, Poolable, WritableModelInterface{ use BookGenerated;
public function getCapitalTitle() { return strtoupper($this->getTitle()); }}
// To generate Active Record classes using a particular namespace,// set the namespace attribute in the <table> tag.// <table name="book" namespace="Bookstore">// generates the following stub Active Record class:namespace Bookstore;use Bookstore\Map\BookGenerated;
class Book extends BaseObject implements Persistent, Poolable, WritableModelInterface{ use BookGenerated;}Peers are the one generated class that still extends a base rather than using a trait — BookPeer extends BaseBookPeer — because every peer method is static and has no $this for a trait to narrow. See Upgrading from 2.x to 3.0 for the full shape of this change.
Generated getter and setter
Section titled “Generated getter and setter”<?php// For each column, Propulsion generates a setter method, also called "mutator"$book->setTitle('War and Peace');$book->setISBN('067003469X');$book->setAuthorId(456745);
// For each column, Propulsion 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 getterecho $book->getPrimaryKey(); // 1234// same asecho $book->getId(); // 1234// For tables with composite PKs, getPrimaryKey() returns an arrayprint_r($bookOpinion->getPrimaryKey()); // array(1234, 67)By default, Propulsion 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 <column> tag:
<!-- set the phpName to have Uppercase getter and setter in PHP --><column name="isbn" required="true" type="VARCHAR" size="24" phpName="ISBN" /><!-- getISBN(), setISBN() -->
<!-- set the phpName to customize the PHP name when you don't control the column name --><column name="bz_ygt" required="true" type="VARCHAR" size="24" phpName="Title" /><!-- getTitle(), setTitle() -->Persistence methods
Section titled “Persistence methods”Active Record objects provide only two methods that may alter the data stored in the database: save(), and delete().
<?php// To insert an object to the database, call the save() method$book = new Book();$book->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 savingecho $book->getId(); // 1234
// To update an object in the database, also use save().// Propulsion knows when an object is new and when it was already persisted,// so 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
// Propulsion 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 = Propulsion::getWriteConnection(BookTableMap::DATABASE_NAME);$book->delete($con);Relationship getters and setters
Section titled “Relationship getters and setters”Consider the previous book table, now with a foreign key to an author table:
<table name="book"> <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" /> <column name="title" type="VARCHAR" required="true" /> <column name="isbn" required="true" type="VARCHAR" size="24" phpName="ISBN" /> <column name="author_id" required="false" type="INTEGER" /> <foreign-key foreignTable="author"> <reference local="author_id" foreign="id" /> </foreign-key></table><table name="author" > <column name="id" required="true" primaryKey="true" autoIncrement="true" type="INTEGER" /> <column name="first_name" required="true" type="VARCHAR" size="128" /> <column name="last_name" required="true" type="VARCHAR" size="128"/></table>Based on this schema, Propulsion defines:
- A many-to-one relationship from the
Bookclass to theAuthorclass - A one-to-many relationship from the
Authorclass to theBookclass
See Relationships for more details.
For each relationship, Propulsion generates additional getters and setters.
One-to-many relationships
Section titled “One-to-many relationships”<?php// On columns holding a Foreign Key, Propulsion adds a getter and a setter for the related object$author = new Author();$author->setFirstName('Leo');$author->setLastName('Tolstoi');$book = new Book();$book->setTitle('War and Peace');// A Book has one Author, therefore Propulsion generates Book::setAuthor() and Book::getAuthor() methods$book->setAuthor($author);echo $book->getAuthor()->getLastName(); // Tolstoi// This allows relating two objects without worrying about the primary and foreign keys,// and it even works on objects not yet persisted.<table name="book"> <foreign-key foreignTable="author" phpName="Writer"> <reference local="author_id" foreign="id" /> </foreign-key></table><!-- Generated methods will then be Book::setWriter(), and Book::getWriter() -->Many-to-one relationships
Section titled “Many-to-one relationships”<?php// On the other member of the relationship, Propulsion generates 4 methods instead of 2$book = new Book();$book->setTitle('War and Peace');$author = new Author();$author->setFirstName('Leo');$author->setLastName('Tolstoi');// An Author has many Books, therefore Propulsion generates Author::addBook() and Author::getBooks() methods$author->addBook($book);echo $author->getBooks(); // array($book)// Propulsion also generates two other methods on that part of the relationshipecho $author->countBooks(); // 1$author->clearBooks(); // removes the relationship<table name="book"> <foreign-key foreignTable="author" refPhpName="Publication"> <reference local="author_id" foreign="id" /> </foreign-key></table><!-- Generated methods will then be Author::addPublication(), Author::getPublications(), Author::countPublications(), and Author::clearPublications() -->Many-to-many relationships
Section titled “Many-to-many relationships”A many-to-many relationship is defined by a cross reference table. Both sides of the relationship see it as a one-to-many relationship.
<?php// If a Book can be written by several Authors, then they share a many-to-many relationship// Therefore Propulsion generates the following methodsBook::addAuthor(), Book::getAuthors(), Book::countAuthors(), Book::clearAuthors()Author::addBook(), Author::getBooks(), Author::countBooks(), Author::clearBooks()One-to-one relationships
Section titled “One-to-one relationships”If a table contains a foreign key that is also a primary key, Propulsion sees it as a one-to-one relationship, seen as a many-to-one relationship from both sides.
<?php// If a User has one Profile using the user PK as foreign key, that's a one-to-one relationship.// Therefore Propulsion generates the following methods:User::getProfile(), User::setProfile()Profile::getUser(), Profile::setUser()Datatype-specific getter and setter
Section titled “Datatype-specific getter and setter”For some column types, Propulsion generates getters and setters with additional functionality.
Temporal columns
Section titled “Temporal columns”<?php// No need to convert a date or time before using the setter on a temporal column// (i.e. of type DATE, TIME, TIMESTAMP, BU_DATE, or BU_TIMESTAMP).// The generated setter accepts strings, timestamps, and DateTime objects,// and automatically converts the argument to the internal storage format.// So the three following calls are equivalent:$book->setCreatedAt('now');$book->setCreatedAt(time());$book->setCreatedAt(new DateTime());
// The generated getter returns a DateTime object, but accepts a format string as argumentecho $book->getCreatedAt(); // DateTime Objectecho $book->getCreatedAt('U'); // 1291065396 (timestamp)echo $book->getCreatedAt('Y-m-d H:i:s'); // 2010-11-29 22:20:21Boolean columns
Section titled “Boolean columns”<?php// The generated setter converts non-boolean values to boolean in a smart way.// The following statements are equivalent:$book->setIsPublished(true);$book->setIsPublished('true');$book->setIsPublished('1');$book->setIsPublished('yes');$book->setIsPublished('on');// Check on string values is case insensitive (so 'FaLsE' is seen as 'false').BLOB columns
Section titled “BLOB columns”<?php// The setter for a BLOB column accepts either a string or a stream as parameter.// Setting the value from a string$media = new Media();$media->setCoverImage(file_get_contents("/path/to/file.ext"));// Setting the value from a stream$fp = fopen("/path/to/file.ext", "rb");$media = new Media();$media->setCoverImage($fp);
// The getter for a BLOB column returns a PHP stream resource,// or NULL if the value is not set in the database.$media = MediaQuery::create()->findPk(43564376);$fp = $media->getCoverImage();if ($fp !== null) { echo stream_get_contents($fp);}ENUM columns
Section titled “ENUM columns”<?php// ENUM columns accept only values chosen from a list of permitted values// that are enumerated explicitly in the column specification at table creation time.
// Example for the book table:// <column name="style" type="ENUM" valueSet="novel, essay, poetry" />$book = new Book();$book->setStyle('novel');echo $book->getStyle(); // novel// An enum is stored as a TINYINT in the database
// Each value in an ENUM column has a related constant in the Peer class// Your IDE with code completion should love thisecho BookPeer::STYLE_NOVEL; // 'novel'echo BookPeer::STYLE_ESSAY; // 'essay'echo BookPeer::STYLE_POETRY; // 'poetry'// The Peer class also gives access to the list of available valuesprint_r(BookPeer::getValueSet(BookPeer::STYLE)); // array('novel', 'essay', 'poetry')OBJECT columns
Section titled “OBJECT columns”<?php// OBJECT columns allow storing PHP objects in the database.// That's especially useful for Value Objects
// 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// The setter serializes the PHP object and stores it as a string// The getter deserializes the string into a PHP object// All that is transparent to the end user, who just manipulates PHP objects
class GeographicCoordinates{ public $latitude, $longitude;
public function __construct($latitude, $longitude) { $this->latitude = $latitude; $this->longitude = $longitude; }
public function isInNorthernHemisphere() { return $this->latitude > 0; }}ARRAY columns
Section titled “ARRAY columns”<?php// ARRAY columns allow storing simple PHP arrays in the database.// Nested arrays and associative arrays are not accepted.
// The 'book' table has a 'tags' column of type ARRAY$book = new Book();$book->setTags(array('novel', 'russian'));print_r($book->getTags()); // array('novel', 'russian')// The setter serializes the PHP array and stores it as a string// The getter deserializes the string into a PHP array// All that is transparent to the end user, who just manipulates PHP arrays
// If the column name is plural, Propulsion also generates hasXXX(), addXXX(),// and removeXXX() methods, where XXX is the singular column nameecho $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')UUID columns
Section titled “UUID columns”<?php// UUID columns accept and return the canonical 8-4-4-4-12 hyphenated hexadecimal form.// Example for the user table:// <column name="external_id" type="UUID" />$user = new User();$user->setExternalId('8DDB2EC4-F996-4777-B4F4-D59399530734');echo $user->getExternalId(); // '8ddb2ec4-f996-4777-b4f4-d59399530734' (lower-cased)
// Setting a malformed UUID throws a PropulsionException rather than storing garbage$user->setExternalId('not-a-uuid'); // throws PropulsionExceptionStored as PostgreSQL’s native uuid type where available, and as CHAR(36) elsewhere (MySQL, SQLite, Oracle, MSSQL) — see Column types. There is no UUID_BINARY type for 16-byte binary storage; see UUID and binary columns for that case and for migrating an existing Propel 2 schema.
JSON/JSONB columns
Section titled “JSON/JSONB columns”<?php// JSON/JSONB columns store real JSON text (via json_encode()/json_decode()), not// PHP serialize() the way OBJECT/ARRAY columns do.// Example for the product table:// <column name="attributes" type="JSON" />$product = new Product();$product->setAttributes(['color' => 'red', 'sizes' => [38, 39, 40]]);$product->save();
$product = ProductQuery::create()->findPk(1);print_r($product->getAttributes()); // ['color' => 'red', 'sizes' => [38, 39, 40]]The getter/setter accept and return whatever shape json_decode()/json_encode() produce — an array, a scalar, or null — so the generated property type is mixed. Malformed JSON already in the database raises a PropulsionException naming the offending column at hydration time, rather than returning null silently or corrupting the value further. See Working with Advanced Column Types and Column types.
Generic getters and setters
Section titled “Generic getters and setters”<?php// Each Active Record class offers generic getter and setter by name$book = new Book();$book->setByName('Title', 'War and Peace');echo $book->getByName('Title'); // War and Peace// The name used is the column phpName - the same name used in generated getters and setters.// You can also use the table column name by adding a converter argument$book->setByName('title', 'War and Peace', BookPeer::TYPE_FIELDNAME);echo $book->getByName('title', BookPeer::TYPE_FIELDNAME); // War and Peace
// Each Active Record class also offers generic getter and setter by position$book->setByPosition(2, 'War and Peace'); // 'title' is the second column of the tableecho $book->getByPosition(2); // War and Peace
// Each ActiveRecord class offers the ability to dump to and populate from an array$properties = array( 'Title' => 'War and Peace', 'ISBN' => '067003469X', 'AuthorId' => 456745);$book = new Book();$book->fromArray($properties);echo $book->getTitle(); // 'War and Peace'print_r($book->toArray());// array(// 'Id' => null// 'Title' => 'War and Peace',// 'ISBN' => '067003469X',// 'AuthorId' => 456745// )
// As with getByName() and setByName(), you can use the table column names by adding a converter argumentprint_r($book->toArray(BookPeer::TYPE_FIELDNAME));// array(// 'id' => null// 'title' => 'War and Peace',// 'isbn' => '067003469X',// 'author_id' => 456745// )
// If the class has lazy-loaded columns, those are included by default in the output of toArray().// To exclude them, set the second argument to false.
// If the class has related objects, they are not included by default in the output of toArray().// To include them, set the fourth argument to true (the third argument is an internal// recursion guard used when dumping related objects, and should normally be left at its default).print_r($book->toArray($keyType = BasePeer::TYPE_PHPNAME, $includeLazyLoadColumns = true, $alreadyDumpedObjects = array(), $includeForeignObjects = true));// array(// 'Id' => null// 'Title' => 'War and Peace',// 'ISBN' => '067003469X',// 'AuthorId' => 456745,// 'Author' => array(// 'Id' => 456745,// 'FirstName' => 'Leo',// 'LastName' => 'Tolstoi',// )// )return [ 'propulsion.addGenericAccessors' => 'false', 'propulsion.addGenericMutators' => 'false',];Validation
Section titled “Validation”Active Record classes have two additional methods, validate() and getValidationFailures(), when the propulsion.addValidateMethod build property is enabled (see the configuration file reference).
<?php$book = new Book();$book->setTitle('a'); // too short for a length validatorif ($book->validate()) { // no validation errors, so the data can be persisted $book->save();} else { // Something went wrong. // getValidationFailures() returns Propulsion\Validator\ValidationFailed objects keyed by column name foreach ($book->getValidationFailures() as $column => $failure) { echo $column . ': ' . $failure->getMessage() . "\n"; }}See Behaviors for more details.
Import and export capabilities
Section titled “Import and export capabilities”Active Record objects have the ability to be converted to and from a string, using any of the XML, YAML, JSON, and CSV formats. The four dumpers are real methods on BaseObject; the four parsers are resolved through __call(), which pattern-matches the format out of the method name and hands off to importFrom(). @method phpDoc blocks on BaseObject and on the generated <Model>Generated trait declare all eight, so an IDE sees them regardless of which half is magic.
Each Active Record object accepts the following method calls:
| format | dumper | parser |
|---|---|---|
| XML | toXML() | fromXML() |
| YAML | toYAML() | fromYAML() |
| JSON | toJSON() | fromJSON() |
| CSV | toCSV() | fromCSV() |
<?php// Dumping an object to a stringecho $book->toXML();// <?xml version="1.0" encoding="UTF-8"?>// <data>// <Id>1234</Id>// <Title><![CDATA[War and Peace]]></Title>// <ISBN><![CDATA[067003469X]]></ISBN>// <AuthorId>456745</AuthorId>// <Author>// <Id>456745</Id>// <FirstName><![CDATA[Leo]]></FirstName>// <LastName><![CDATA[Tolstoi]]></LastName>// </Author>// </data>
echo $book->toYAML();// Id: 1234// Title: War and Peace// ISBN: 067003469X// AuthorId: 456745// Author:// Id: 456745// FirstName: Leo// LastName: Tolstoi
echo $book->toJSON();// {// "Id":1234,// "Title":"War and Peace",// "ISBN":"067003469X",// "AuthorId":456745,// "Author": {// "Id":456745,// "FirstName":"Leo",// "LastName":"Tolstoi"// }// }
// Parsing a string into an Active Record object$xml = <<<EOF<?xml version="1.0" encoding="UTF-8"?><data> <Id>1234</Id> <Title><![CDATA[War and Peace]]></Title> <ISBN><![CDATA[067003469X]]></ISBN> <AuthorId>456745</AuthorId> <Author> <Id>456745</Id> <FirstName><![CDATA[Leo]]></FirstName> <LastName><![CDATA[Tolstoi]]></LastName> </Author></data>EOF;$book = new Book();$book->fromXML($xml);echo $book->getTitle(); // War and Peace
// Active Record Objects also provide generic importFrom() and exportTo() methods,// accepting either a format name, or a parser instance (extending PropulsionParser)$book->importFrom('XML', $xml);echo $book->exportTo('XML');// This allows for custom parser formats<table name="book" defaultStringFormat="XML">Virtual columns
Section titled “Virtual columns”Propulsion queries allow you to hydrate additional columns from related objects, at runtime. These columns can be fetched using the getVirtualColumn($name) method, or using the magic getter supported by the generated __call() method:
<?php$book = BookQuery::create() ->filterByTitle('War and Peace') ->join('Book.Author') ->withColumn('Author.LastName', 'AuthorName') ->findOne();echo $book->getVirtualColumn('AuthorName'); // Tolstoiecho $book->getAuthorName(); // TolstoiSee the Model/Query reference for more details.
Lifecycle events
Section titled “Lifecycle events”To execute custom code before or after any of the persistence methods, just create methods using any of the following names in a stub Active Record class:
<?php// save() hookspreInsert() // code executed before insertion of a new objectpostInsert() // code executed after insertion of a new objectpreUpdate() // code executed before update of an existing objectpostUpdate() // code executed after update of an existing objectpreSave() // code executed before saving an object (new or existing)postSave() // code executed after saving an object (new or existing)// delete() hookspreDelete() // code executed before deleting an objectpostDelete() // code executed after deleting an objectEach of these hooks also dispatches a PSR-14 event (PreSaveEvent, PostSaveEvent, PreInsertEvent, PostInsertEvent, PreUpdateEvent, PostUpdateEvent, PreDeleteEvent, PostDeleteEvent — the Propulsion\Event namespace) via Propulsion::dispatch(), a no-op unless an event dispatcher has been registered with Propulsion::setEventDispatcher(). The pre* events are stoppable: a listener calling $event->stopPropagation() vetoes the operation exactly like an overridden hook method returning false. See PSR-14 model events for the full API and ModelCriteria & Query for the equivalent bulk-operation events.
See Behaviors for more details.
Persistence status
Section titled “Persistence status”<?php// At all times, you can monitor the status of an Active Record object regarding persistence$book = new Book();
// isNew() returns true if the object has not yet been persisted, false otherwiseecho $book->isNew(); // true$book->save();echo $book->isNew(); // false
// isModified() returns true if some columns were changed since the last save, false otherwiseecho $book->isModified(); // false$book->setTitle('War and Peace');echo $book->isModified(); // true$book->save();echo $book->isModified(); // false
// To cancel modifications on an object and return to the persisted state, call reload()$book->setTitle('War and Peace and Love');$book->reload();// SELECT * FROM book WHERE id = 1234echo $book->getTitle(); // War and Peace
// isDeleted() returns true if an object has been deleted, false otherwise.// Note that deleted objects continue to live in the PHP space after deletion.echo $book->isDeleted(); // false$book->delete();echo $book->isDeleted(); // true
// You can test and list the modified columns using isColumnModified() and getModifiedColumns()// The function uses fully qualified column names (i.e. of type BasePeer::TYPE_COLNAME)$book = new Book();$book->setTitle('War and Peace');echo $book->isColumnModified('book.ISBN'); // falseecho $book->isColumnModified('book.TITLE'); // trueprint_r($book->getModifiedColumns());// array('book.TITLE')// To use column phpNames, just convert the parameter using translateFieldName()$colName = BookPeer::translateFieldName('Title', BasePeer::TYPE_PHPNAME, BasePeer::TYPE_COLNAME);echo $book->isColumnModified($colname); // trueMiscellaneous
Section titled “Miscellaneous”<?php// Active Record objects have even more methodsecho $book->hasOnlyDefaultValues(); // returns true if the column values are default onesecho $book->hashCode(); // returns a hash code for the instance
$book->clear(); // resets all the user-modified properties and returns the object to a new state$book->clearAllReferences() // resets all collections of referencing foreign keys
$book1 = BookQuery::create()->findPk(1234);$book2 = $book1->copy(); // creates a copy of an Active Record instanceecho $book1->equals($book2); // compares two ActiveRecord instancesConclusion
Section titled “Conclusion”Active Record classes are not only an object-oriented tool to execute SQL queries in a database-independent way. They provide a lot of features to streamline day-to-day work with persisted objects. They can also be seen as a persistence ability that can be added to any PHP object.