DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Last call! Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Designing a Blog Application Using Document Databases
  • Relational DB Migration to S3 Data Lake Via AWS DMS, Part I
  • NoSQL for Relational Minds
  • Business Logic Database Agent

Trending

  • Automating Data Pipelines: Generating PySpark and SQL Jobs With LLMs in Cloudera
  • The Role of Functional Programming in Modern Software Development
  • Teradata Performance and Skew Prevention Tips
  • A Guide to Container Runtimes
  1. DZone
  2. Data Engineering
  3. Databases
  4. Practical PHP Patterns: Active Record

Practical PHP Patterns: Active Record

By 
Giorgio Sironi user avatar
Giorgio Sironi
·
May. 12, 10 · Interview
Likes (0)
Comment
Save
Tweet
Share
8.5K Views

Join the DZone community and get the full member experience.

Join For Free

The Active Record pattern effectively prescribes to wrap a row of a database table in a domain object with a 1:1 relationship, managing its state and adding business logic in the wrapping class code.

An Active Record implementation is in fact a classical C structure aka Record aka associative array of data, with the addition of utility methods that encapsulate behavior that acts on these data. The most useful method is usually the save() one, which updates the database reflecting in the row the current state of the record. Thus, the Active Record transparently works with SQL queries and provides an higher-level Api.

Although Active Record is similar in implementation to the Row Data Gateway pattern, it is distinguished from it in the fact that it defines methods with domain-specific logic. The consequence of the presence of domain-specific logic is that generic implementations of Active Record provided by libraries must be customized to met the need of the object model. Typically this customization is done with a thin subclassing, which at least renames the library class with a domain name (like User or Post) and may specify metadata on the database table where the Active Records state is kept, if they are not inferred.

The issue of subclassing

Subclassing allows the developer to create new methods and properties to represent business logic, and to build a richer and more specific interface than the one constituted by simple Row Data Gateway objects. Despite these advantage, this interface is not much segregated, as subclassing exposes all the public methods of the base library class, on which the developers of the domain model have no control.

Subclassing also ties the domain layer to the infrastructure one, being it a library or a framework or every kind of data persistence layer (examples of PHP ORMs that use Active Record are the Zend_Db component, Doctrine 1 and Propel). Domain objects cannot be created or even their class source code loaded without having the library code available. This is an issue when reusing the model in a different environment, and even in test suites. If the library is powerful enough, it may provide adapters for different databases so that a lightweight database instance can be created in the testing environment.

Another caveat of Active Record is the fundamental assumption that a domain entity is always a row of a table of a relational database; this constraint is forced even when it is not appropriate, and the database and object model must match. In fact, part of the database (like foreign keys) often scatter into the domain model, as an Active Record with an external one-to-one relationship will usually store not only the related object but also its foreign key.

Another example of mirroring of the relational model into the object graph is for the management of M-to-N associating entities, often forced to become real entities even when they do not make sense (the famous UserGroup classes that tie together User and Group rows).

Diffusion

Thus, the Active Record pattern puts at risk the freedom of implementing a powerful Domain Model, where the object graph is a mix of state-carrying and behavior-carrying objects, like Strategies and Specifications. It is however, a radical simplification in implementation of domain models where CRUD functions are all the rage, and there is no gain in implementing objects that do not simply map to a relational database.

Note that in the case of PHP, most of the custom web applications developed in this language are deeply influenced by the back end, assumed as a relational database or even as MySQL. But while the technologies for user-to-application and application-to.application interaction on the web continue to grow, the situation will continue to evolve and if PHP wants to keep up with the pace of other dynamic languages, Java and .NET, it needs to finally decouple from the relational database as the unique model of data.

By the way, current implementation of persistence frameworks are transitioning towards a Data Mapper approach (not only in PHP but also in Ruby, while Java has done that years ago with Hibernate and JPA), which is less invasive on the Domain Model source code, and does not introduce an hard dependency from the domain layer to infrastructure components.

Examples

This sample implementation of the Active Record pattern is taken from the Doctrine 1.2 ORM. The base class in this framework id Doctrine_Record (together with Doctrine_Record_Abstract), while a base class with the schema metadata is regenerated from a model, and it is subclassed for orthogonality of customization and synchronization by the developer.

The base Active Record class looks like this:

/**
* Implements also __get() and __set(), not shown along with many other dozen methods.
*/
abstract class Doctrine_Record extends Doctrine_Record_Abstract implements Countable, IteratorAggregate, Serializable
{
/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure.
*/
public function preSave($event)
{ }

/**
* Empty template method to provide concrete Record classes with the possibility
* to hook into the saving procedure.
*/
public function postSave($event)
{ }

/**
* applies the changes made to this object into database
* this method is smart enough to know if any changes are made
* and whether to use INSERT or UPDATE statement
*
* this method also saves the related components
*
* @param Doctrine_Connection $conn optional connection parameter
* @return void
*/
public function save(Doctrine_Connection $conn = null)
{
if ($conn === null) {
$conn = $this->_table->getConnection();
}
$conn->unitOfWork->saveGraph($this);
}

/**
* returns a string representation of this object
*/
public function __toString()
{
return (string) $this->_oid;
}
}
If we have an Article entity, it will be represented via subclassing of the generic Active Record. In Doctrine 1, a subclass can be generated by writing a compact Yaml model, or even reverse engineered from an existing database:
/**
* BaseOtk_Content_Article
*
* This class has been auto-generated by the Doctrine ORM Framework
*
* @property integer $id
* @property integer $section_id
* @property integer $author_id
* @property integer $image_id
* @property string $title
* @property string $description
* @property string $text
* @property integer $visits
* @property boolean $draft
* @property boolean $closed
* @property Otk_Content_Section $section
* @property Otk_User $author
* @property Otk_File $image
* @property Doctrine_Collection $sections
* @property Doctrine_Collection $Otk_Content_Tag
* @property Doctrine_Collection $comments
*
*/
abstract class BaseOtk_Content_Article extends Otk_Model_Record
{
public function setTableDefinition()
{
$this->setTableName('oss_content_articles');
$this->hasColumn('id', 'integer', 3, array('type' => 'integer', 'primary' => true, 'autoincrement' => true, 'length' => '3'));
$this->hasColumn('section_id', 'integer', 2, array('type' => 'integer', 'notnull' => true, 'length' => '2'));
$this->hasColumn('author_id', 'integer', 3, array('type' => 'integer', 'length' => '3'));
$this->hasColumn('image_id', 'integer', 3, array('type' => 'integer', 'length' => '3'));
$this->hasColumn('title', 'string', 255, array('type' => 'string', 'length' => '255'));
$this->hasColumn('description', 'string', 1000, array('type' => 'string', 'length' => '1000'));
$this->hasColumn('text', 'string', null, array('type' => 'string'));
$this->hasColumn('visits', 'integer', 3, array('type' => 'integer', 'length' => '3'));
$this->hasColumn('draft', 'boolean', null, array('type' => 'boolean', 'notnull' => true, 'default' => false));
$this->hasColumn('closed', 'boolean', null, array('type' => 'boolean'));
}

public function setUp()
{
$this->hasOne('Otk_Content_Section as section', array('local' => 'section_id',
'foreign' => 'id'));

$this->hasOne('Otk_User as author', array('local' => 'author_id',
'foreign' => 'id'));

$this->hasOne('Otk_File as image', array('local' => 'image_id',
'foreign' => 'id'));

$this->hasMany('Otk_Content_Section as sections', array('refClass' => 'Otk_Content_Tag',
'local' => 'article_id',
'foreign' => 'section_id'));

$this->hasMany('Otk_Content_Tag', array('local' => 'id',
'foreign' => 'article_id'));

$this->hasMany('Otk_Content_Comment as comments', array('local' => 'id',
'foreign' => 'article_id'));

$timestampable0 = new Doctrine_Template_Timestampable();
$sluggable0 = new Doctrine_Template_Sluggable(array('fields' => array(0 => 'title'), 'canUpdate' => true, 'unique' => true));
$this->actAs($timestampable0);
$this->actAs($sluggable0);
}
}
To support further regenerations of the subclass as the schema evolves, another subclassing step is necessary. This class will never be touched by the regeneration process and it is the one referred in client code.
/**
* This class defines the domain logic via addition of methods.
*/
class Otk_Content_Article extends BaseOtk_Content_Article
{
public function getTags()
{
$tags = array();
foreach ($this->sections as $section) {
$tags[$section->slug] = $section->name;
}
return $tags;
}
}
Database PHP Relational database

Opinions expressed by DZone contributors are their own.

Related

  • Designing a Blog Application Using Document Databases
  • Relational DB Migration to S3 Data Lake Via AWS DMS, Part I
  • NoSQL for Relational Minds
  • Business Logic Database Agent

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!