Skip to content

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.

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.

<!-- 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" classes
class Book extends BaseObject implements Persistent, Poolable, WritableModelInterface
{
use BookGenerated;
}
// Most of the generated code is actually in the *Generated traits
trait 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 class
class 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.

<?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 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, Propulsion uses a CamelCase version of the column name for these methods:

column namegetter & setter method names
titlegetTitle(), setTitle()
is_publishedgetIsPublished(), setIsPublished()
author_idgetAuthorId(), setAuthorId()
isbngetIsbn(), 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() -->

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 saving
echo $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);

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 Book class to the Author class
  • A one-to-many relationship from the Author class to the Book class

See Relationships for more details.

For each relationship, Propulsion generates additional getters and setters.

<?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() -->
<?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 relationship
echo $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() -->

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 methods
Book::addAuthor(), Book::getAuthors(), Book::countAuthors(), Book::clearAuthors()
Author::addBook(), Author::getBooks(), Author::countBooks(), Author::clearBooks()

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()

For some column types, Propulsion generates getters and setters with additional functionality.

<?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 argument
echo $book->getCreatedAt(); // DateTime Object
echo $book->getCreatedAt('U'); // 1291065396 (timestamp)
echo $book->getCreatedAt('Y-m-d H:i:s'); // 2010-11-29 22:20:21
<?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').
<?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);
}
<?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 this
echo 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 values
print_r(BookPeer::getValueSet(BookPeer::STYLE)); // array('novel', 'essay', 'poetry')
<?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;
}
}
<?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 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')
<?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 PropulsionException

Stored 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.

<?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.

<?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 table
echo $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 argument
print_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',
// )
// )
build.php
return [
'propulsion.addGenericAccessors' => 'false',
'propulsion.addGenericMutators' => 'false',
];

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 validator
if ($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.

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:

formatdumperparser
XMLtoXML()fromXML()
YAMLtoYAML()fromYAML()
JSONtoJSON()fromJSON()
CSVtoCSV()fromCSV()
<?php
// Dumping an object to a string
echo $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">

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'); // Tolstoi
echo $book->getAuthorName(); // Tolstoi

See the Model/Query reference for more details.

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() hooks
preInsert() // code executed before insertion of a new object
postInsert() // code executed after insertion of a new object
preUpdate() // code executed before update of an existing object
postUpdate() // code executed after update of an existing object
preSave() // code executed before saving an object (new or existing)
postSave() // code executed after saving an object (new or existing)
// delete() hooks
preDelete() // code executed before deleting an object
postDelete() // code executed after deleting an object

Each 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.

<?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 otherwise
echo $book->isNew(); // true
$book->save();
echo $book->isNew(); // false
// isModified() returns true if some columns were changed since the last save, false otherwise
echo $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 = 1234
echo $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'); // false
echo $book->isColumnModified('book.TITLE'); // true
print_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); // true
<?php
// Active Record objects have even more methods
echo $book->hasOnlyDefaultValues(); // returns true if the column values are default ones
echo $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 instance
echo $book1->equals($book2); // compares two ActiveRecord instances

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.