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

Curious about the future of data-driven systems? Join our Data Engineering roundtable and learn how to build scalable data platforms.

Data Engineering: The industry has come a long way from organizing unstructured data to adopting today's modern data pipelines. See how.

Threat Detection: Learn core practices for managing security risks and vulnerabilities in your organization — don't regret those threats!

Managing API integrations: Assess your use case and needs — plus learn patterns for the design, build, and maintenance of your integrations.

Related

  • SQL Interview Preparation Series: Mastering Questions and Answers Quickly
  • Just Use PostgreSQL, a Quick-Start Guide: Exploring Essential and Extended Capabilities of the Most Beloved Database
  • Custom Health Checks in Spring Boot
  • Fine-Tuning Performance, Resolving Common Issues in FinTech Application With MySQL

Trending

  • Building Scalable AI-Driven Microservices With Kubernetes and Kafka
  • Real-Time Data Streaming on Cloud Platforms: Leveraging Cloud Features for Real-Time Insights
  • Using Oracle Database 23AI for Generative AI RAG Implementation: Part 1
  • Cost Optimization Strategies for Managing Large-Scale Open-Source Databases
  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.2K 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

  • SQL Interview Preparation Series: Mastering Questions and Answers Quickly
  • Just Use PostgreSQL, a Quick-Start Guide: Exploring Essential and Extended Capabilities of the Most Beloved Database
  • Custom Health Checks in Spring Boot
  • Fine-Tuning Performance, Resolving Common Issues in FinTech Application With MySQL

Partner Resources


Comments

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: