LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Tuesday, December 30. 2008

Model Infrastructure

In the last two entries in this series on models, I covered using forms as input filters and integrating ACLs into models. In this entry, I tackle some potential infrastructure for your models.

The Model is a complex subject. However, it is often boiled down to either a single model class or a full object relational mapping (ORM). I personally have never been much of a fan of ORMs as they tie models to the underlying database structure; I don't always use a database, nor do I want to rely on an ORM solution too heavily on the off-chance that I later need to refactor to use services or another type of persistence store. On the other hand, the model as a single class is typically too simplistic.

I am, however, a fan of the Domain Model. To quote wikipedia,

[The] domain model can be thought of as a conceptual model of a system which describes the various entities involved in that system and their relationships.

When you think in these terms, you start breaking your system into discrete pieces that you need to manipulate, as well as consider how each piece relates to the others. This type of exercise also helps you stop thinking of your model in terms of database tables; instead, your database becomes the container in which data is persisted from one use of your model to the next. Your model instead is an object that can do things with either incoming or stored data -- or even completely autonomously.

As an example, when starting with Zend Framework, it's tempting to use Zend_Db_Table and Zend_Db_Table_Row as models. However, there's one big argument against doing so: when using a Table Data Gateway (TDG) or a Row Data Gateway (RDG), you're returning an object that is tied to the data storage implementation. You're basically putting on blinders and thinking of your model as simply the database table or an individual row, and the returned objects reflect this narrow view point. Furthermore, if you want to re-use your models with service layers, many web services do not work with objects, and of those that do, you likely do not want to expose all the properties and methods of the objects returned by your data provider. A row object in ZF, for instance, actually stores the data in protected members, effectively hiding it from services, and also includes methods for deleting the row, ArrayAccess methods, and access to the table object -- which gives you full control over the table! The security implications of exposing this directly over a service should be obvious.

Additionally, if in the future you wish to refactor your application to utilize memcached or a web service, you now not only need to completely rewrite your models, but also all consumer code, because the return values from your model have changed.

So, if you're not going to use an ORM or a Table Data Gateway directly, how should you architect your model infrastructure?

What are you modelling?

The principal question to ask is, "What am I modelling?"

Let's look at a rather standard website issue: user management. Typically, you'll get a requirement such as, "Users should be able to register for an account on the site. Once registered, they should be able to login with the credentials they provided. Administrators should be able to ban accounts or grant users higher levels of privileges." That's assuming you actually get good requirement documents, of course.

Most developers will immediately setup a database with a few fields that represent a user -- full name, username, email, password, etc -- create a form for registration and another for login, write a routine to validate each, create a page to list users for the administration screen... you know the drill. But what are you modelling?

The answer is: users. So, now it's time to define what a user is, and what a user can do. We have to decide what constitutes a new user, and what constitutes an authenticated user. We have an additional modelling consideration that's often overlooked: user roles. There's also the matter of what a group of users might look like (since the administrator needs to be able to list users), and how we might want to work with groups.

Let's start with narrowing down the definition of a user:

  • A user consists of the following metadata:
    • Unique username
    • Full name
    • Email address
    • Hashed password
    • A role within the site
  • A new user must provide a unique username, their full name, a valid email address, and a password and password verification.
  • An authenticated user is one who has provided a matching combination of username and password.
  • A user may logout of the site.
  • A user may be granted a new role.
  • A user may be marked as banned.

Notice the fifth piece of metadata? It mentions a "role"? That's something to do with our ACLs -- which means that ACLs are part of our user domain. I'll touch on this later.

If you look at the remaining points carefully, you'll note that there's talk of validation, authentication, and user and session persistence. Validation rules are part of our model -- and we'll use Zend_Form to fulfill that role. Authentication on the web usually consists of both validating submitted credentials against stored credentials, as well as persisting a verified identity in the session. This means that other parts of our model include data persistence and session management. We'll use Zend_Db_Table for data persistence, and Zend_Auth/Zend_Session for identity persistence.

Now, let's turn to defining lists of users:

  • Administrators should be able to pull lists of users. These lists should allow for:
    • Sorting by username, full name, email address, or role
    • Pagination (i.e., pulling a set number of users from a given offset)
    • Iteration
  • Administrators should be able to specify criteria for selecting users to list.

These criteria indicate that a list of users should be an object. This list will likely implement the SPL class Traversable in some fashion. Looking at this criteria, another aspect of our model becomes clear: we are modelling user selection -- which includes the ability to specify sorting and selection criteria. The user selection object would return a user list object, which would consist of user objects. User objects define ACL roles and can authenticate users.

We started this article by discussing the Domain Model, and defined it as a system, its entities, and the relations between those entities. We've now identified our domain: user management. The various entities include users, lists of users, ACL roles, a user persistence layer (database), and session persistence layer (web server sessions).

Now that we know what we're modelling, let's look at some of the objects in our model.

Gateway to the Domain

We've identified "user management" as the purpose of our model. This will include retrieving and saving individual users, as well as selecting groups of users.

It's clear that we'll need an object to represent a user, as well as another to represent a selection or group of users. But what may not be entirely clear is that we should likely have an object that is used to create new user objects, create selections of users, and basically coordinate several of the related objects -- the root ACL and data access, in particular.

This object will be what I'll term our domain gateway. It will be used to fetch other objects in our model, and will inject various dependencies into them when doing so, such as the data access and ACLs. The various dependencies may themselves be injected into the gateway -- or it can lazy-load them.

The API of this gateway might look something like the following.


// Instantiate the gateway
$userGateway = new Spindle_Model_UserGateway();

// configure the gateway:
$userGateway->setAcl(new Spindle_Acl_Spindle())
            ->setDbAdapter(Zend_Registry::get('db'));

// Alternately, do it all at instantiation:
$userGateway = new Spindle_Model_UserGateway(array(
    'acl'       => new Spindle_Acl_Spindle(),
    'dbAdapter' => Zend_Registry::get('db'),
));

// Grab a single user
$user = $userGateway->retrieve('matthew');

// Grab many users
$users = $userGateway->sort('email', 'ASC')
                     ->criteria(array('banned' => true))
                     ->fetch(array('offset' => 20, 'limit' => 20));

// Better yet, add some transaction script methods with preset criteria:
$users = $userGateway->fetchBannedUsers(array(
    'offset' => 20,
    'limit'  => 20,
    'sort'   = array('email', 'ASC'),
));

// Create a new user:
$user = $userGateway->createUser(array(
    'username' => 'matthew',
    'fullname' => "Matthew Weier O'Phinney",
    'password' => 'secret',
    'email'    => 'matthew@local',
));
 

The basic idea is to provide a scaffold for lazyloading necessary objects, methods for specifying options (such as sort order, criteria, limits, etc), and transaction methods for retrieving individual users and groups of users.

Of Value Objects and Record Sets

To other objects we've identified in our model are users and user lists. How should we define these?

The traditional answer is as value or data transfer objects and record sets. The Value Object is a standard design pattern used to aggregate all metadata that defines a single value. The Record Set is an aggregation of Value Objects.

Value Objects

Martin Fowler makes a differentiation between value objects and data transfer objects in his book "Patterns of Enterprise Application Architecture" (PoEAA). In it, he associates value objects with language variable types (i.e., Value Objects act as custom variable types), while defining data transfer objects as aggregating related values for the purpose of serialization and data transfer between objects.

In Java, however, value objects are arbitrary objects used to store a specific set of attributes -- very similar to the data transfer object. For the purposes of this discussion, I'll use the term "value object," as it will be familiar to those with a Java background, and to indicate that we are aggregating a unique value that is the sum of a number of attributes.

Basically, all of this verbiage describes something incredibly simple in implementation: an object with a specific set of attributes or properties. If you've been doing any OOP programming in PHP, this is the most natural and fundamental thing you can do.


class Spindle_Model_User
{
    protected $_data = array(
        'username' => null,
        'email'    => null,
        'fullname' => '',
        'role'     => 'guest',
    );

    public function __construct($data)
    {
        $this->populate($data);

        if (!isset($this->username)) {
            throw new Exception('Initial data must contain an id');
        }
    }

    public function populate($data)
    {
        if (is_object($data)) {
            $data = (array) $data;
        }
        if (!is_array($data)) {
            throw new Exception('Initial data must be an array or object');
        }

        foreach ($data as $key => $value) {
            $this->$key = $value;
        }
        return $this;
    }

    public function __set($name, $value)
    {
        if (!array_key_exists($name, $this->_data)) {
            throw new Exception('Invalid property "' . $name . '"');
        }
        $this->_data[$name] = $value;
    }

    public function __get($name)
    {
        if (array_key_exists($name, $this->_data)) {
            return $this->_data[$name];
        }
        return null;
    }

    public function __isset($name)
    {
        return isset($this->_data[$name]);
    }

    public function __unset($name)
    {
        if (isset($this->$name)) {
            $this->_data[$name] = null;
        }
    }
}
 

The above example is fairly simplistic, but it gets the idea across: the object defines a limited range of valid values, and enforces that only these values may be set -- as well as which values are required. You could certainly add accessor and mutator methods to enforce consistent access to member data, but the above will certainly suffice for many use cases. (I'll look at data integrity momentarily.)

One addition you might make to the class definition is to add some conversions from different types of objects. For instance, if you know that you'll be using Zend_Db_Table within your model, you might want to add the ability for your value object to accept a Zend_Db_Table_Row object, and pull its values from there:


class Spindle_Model_User
{
    /* ... */

    public function populate($data)
    {
        if ($data instanceof Zend_Db_Table_Row_Abstract) {
            $data = $data->toArray();
        } elseif (is_object($data)) {
            $data = (array) $data;
        }

        if (!is_array($data)) {
            throw new Exception('Initial data must be an array or object');
        }

        foreach ($data as $key => $value) {
            $this->$key = $value;
        }
        return $this;
    }

    /* ... */
}
 

This will help keep your model code clean, as you can potentially take the results of data storage operations and push them directly into your value object -- resulting in less re-working of code.

Now, what about data integrity? This is where Zend_Form comes into play. Don't think of Zend_Form as a web form; think of it as an input filter that has the ability to render itself as a form if so desired. If we think of it as an input filter, we can use it for data integrity:


class Spindle_Model_User
{
    /* ... */

    public function __set($name, $value)
    {
        if (!array_key_exists($name, $this->_data)) {
            throw new Exception('Invalid property "' . $name . '"');
        }

        $inputFilter = $this->getForm();
        if ($element = $inputFilter->getElement($name)) {
            if (!$element->isValid($value)) {
                throw new Exception(sprintf(
                    'Invalid value provided for "%s": %s',
                    $name,
                    implode(', ', $element->getMessages())
                );
            }
        }

        $this->_data[$name] = $value;
    }

    /* ... */

    protected $_form;

    public function getForm()
    {
        if (null === $this->_form) {
            $this->_form = new Spindle_Form_User();
        }
        return $this->_form;
    }

    /* ... */
}
 

One note: if your model contains metadata that will never be represented as part of a form, you shoould look into using Zend_Filter_Input or custom validation chains instead of Zend_Form. That's outside the scope of this article, however.

Now that we have input filtering out of the way, how shall we address saving a user? Recall in our discussion of the domain gateway that one of its responsibilities is injecting other dependencies into our objects. I find it's often easier to inject the gateway into objects, and then pull what I need from it. Let's look at how that might work for saving the user.


class Spindle_Model_User
{
    /* ... */

    protected $_gateway;

    public function __construct($data, $gateway)
    {
        $this->setGateway($gateway);

        /* ... */
    }

    public function setGateway(Spindle_Model_UserGateway $gateway)
    {
        $this->_gateway = $gateway;
        return $this;
    }

    public function getGateway()
    {
        return $this->_gateway;
    }

    public function save()
    {
        $gateway = $this->getGateway();
        $dbTable = $gateway->getDbTable('user');

        if ($row = $dbTable->find($this->username)) {
            foreach ($this->_data as $key => $value) {
                $row->$key = $value;
            }
            $row->save();
        } else {
            $dbTable->insert($this->_data);
        }
    }

    /* ... */
}
 

Note that the constructor now has a second argument -- the gateway. This ensures that the user always has a gateway instance, which further ensures that operations like the one listed -- retrieving the Zend_Db_Table instance from the gateway -- will always work. In this example, we simply check to see if a row already exists, and then save the record accordingly.

Another requirement we identified was that a user be able to authenticate itself. This can be done trivially by implementing Zend_Auth_Adapter_Interface:


class Spindle_Model_User implements Zend_Auth_Adapter_Interface
{
    /* ... */

    public function authenticate()
    {
        $gateway = $this->getGateway();
        $table   = $manager->getDbTable('user');
        $select  = $table->select();
        $select->where('username = ?', $this->username)
               ->where('password = ?', $this->password)
               ->where('date_banned IS NULL');
        $user = $table->fetchRow($select);
        if (null === $user) {
            // failed
            $result = new Zend_Auth_Result(
                Zend_Auth_Result::FAILURE_UNCATEGORIZED,
                null
            );
        } else {
            // passed
            $this->populate($user);
            unset($this->password);
            $result = new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $this);
        }
        return $result;
    }

    /* ... */
}
 

To authenticate a user, you would create a new user object with the username and password, and then attempt to authenticate it:


$auth = Zend_Auth::getInstance();
$user = $gateway->createUser(array(
    'username' => $username,
    'password' => $password,
));
if ($auth->authenticate($user)) {
    // AUTHENTICATED!
}
 

This also has the effect of populating the user from the persistence store, as well as storing the identity in the session.

I covered ACL roles previously, so I won't go into that here. However, you should now be getting a pretty clear understanding of how this object works, and how it coordinates with the user gateway. It should also illustrate that this aspect of our model is much, much more than simply data access: we're coordinating authentication, input filtering, and ACLs -- and providing a fairly simple API for manipulating the user itself.

Record Sets

A Record Set is similarly easy to create. Typically, you will merely want the object to be iterable and countable. Like the user object, we'll require a gateway instance in the constructor.


<?php
class Spindle_Model_Users implements Iterator,Countable
{
    protected $_count;
    protected $_gateway;
    protected $_resultSet;

    public function __construct($results, $gateway)
    {
        $this->setGateway($gateway);
        $this->_resultSet = $results;
    }

    public function setGateway(Spindle_Model_UserGateway $gateway)
    {
        $this->_gateway = $gateway;
        return $this;
    }

    public function getGateway()
    {
        return $this->_gateway;
    }

    public function count()
    {
        if (null === $this->_count) {
            $this->_count = count($this->_resultSet);
        }
        return $this->_count;
    }

    public function current()
    {
        if ($this->_resultSet instanceof Iterator) {
            $key = $this->_resultSet->key();
        } else {
            $key = key($this->_resultSet);
        }
        $result  = $this->_resultSet[$key];
        if (!$result instanceof Spindle_Model_User) {
            $gateway = $this->getGateway();
            $result  = $gateway->createUser($result);
            $this->_resultSet[$key] = $result;
        }
        return $result;
    }

    public function key()
    {
        return key($this-_resultSet);
    }

    public function next()
    {
        return next($this->_resultSet);
    }

    public function rewind()
    {
        return reset($this->_resultSet);
    }

    public function valid()
    {
        return (bool) $this->current();
    }
}
 

The logic here is incredibly simple. The main benefit from using a Record Set over an array is that it allows you to ensure the types of each item in the set, as well as allow your consuming code to perform type hinting on the Record Set class.

Using Value Objects and Record Sets in Your Gateway

Within your gateway class, it is then your responsibility to ensure that instances of your new classes are returned. As an example, let's look at some easy fetch() and fetchAll() methods:


class Spindle_Model_UserGateway
{
    /* ... */

    public function fetch($id)
    {
        $dbTable = $this->getDbTable();
        $select  = $dbTable->select();
        $select->where('id = ?', $id);
        $result = $dbTable->fetchRow($select);
        if (null !== $result) {
            $result = $this->createUser($result);
        }
        return $result;
    }

    public function fetchAll()
    {
        $result = $this->getDbTable()->fetchAll();
        return new Spindle_Model_Users($result, $this);
    }

    /* ... */
}
 

You'll notice the downside immediately: you have to introduce new objects, and that means re-casting of data. But let's look at it from a consumer viewpoint: the consuming code is looking for return types of Spindle_Model_User and Spindle_Model_Users.

But what's the point of the gateway, really? Couldn't both the value object and result set object simply inherit from a common base? Certainly they could. However, one common use case I have for gateways is providing pre-defined methods encapsulating common selection criteria. For instance, let's say you wanted to retrieve all banned users, and that this will be a common task. Define a method for it:


class Spindle_Model_UserGateway
{
    /* ... */

    public function fetchBannedUsers()
    {
        $dbTable = $this->getDbTable();
        $select  = $dbTable->select()->where('date_banned IS NOT NULL');
        $result  = $dbTable->fetchAll($select);
        return new Spindle_Model_Users($result, $this);
    }

    /* ... */
}
 

This is admittedly a trivial example, but it clearly illustrates the benefits: we now have an API method that tells us, in plain English, what operation we are performing, and provides a repeatable way to do it. The user consuming the model needs not know anything about how it works under the hood, only that they can expect to get a list of banned users when they call it.

Another key benefit to creating a gateway is for those times when we need to replace our data access layer with something else. Let's refactor our code to use a service instead:


class Spindle_Model_UserGateway
{
    /* ... */

    public function fetch($id)
    {
        $result  = $this->getService()->fetchUser($id);
        return $this->createUser($result);
    }

    public function fetchAll()
    {
        $result = $this->getService()->fetchAll();
        return new Spindle_Model_Users($result, $this);
    }

    /* ... */
}
 

From a consumer standpoint, nothing has changed; they are still calling the same methods, and receiving the same responses. This is absolutely key in creating maintainable, future proof code.

Summary

The solutions presented here are by no means canonical. You may find that your own models do not need a gateway class, or that you never work with lists of objects. Hopefully, however, I've illustrated that a model should cleanly provide a separation of concerns and consist of discrete objects -- whether they are directly related to your model, or related to aspects of how your model does stuff, like validation and data persistence. You should strive to make your models as simple as possible, while still meeting each of your requirements. The end result should be a re-usable, testable suite of functionality, and careful architecture of your solution should make it robust and easy to refactor in the future.

Updates:

  • 2009-01-04: Updated __unset() per Gabriel's feedback (comment #14)
  • 2009-01-05: Updated current() implementation per Falk's feedback (comment #15)
  • 2009-01-05: Updated current() implementation per Martin's feedback (comment #15.1.1)
Posted by Matthew Weier O'Phinney in PHP at 07:35 | Comments (80) | Trackbacks (3)
Defined tags for this entry: best practices, mvc, php, zend framework
Related entries by tags:
Module Bootstraps in Zend Framework: Do's and Don'ts
Responding to Different Content Types in RESTful ZF Apps
Symfony Live 2010
Creating Re-Usable Zend_Application Resource Plugins
Quick Start to Zend_Application_Bootstrap

Trackbacks
Trackback specific URI for this entry

Models and Zend_Paginator
I have been following the discussion of models recently, in particular the blog posts by Rob Allen and Matthew Weier O’Phinney. I’m not going to go into detail about my model implementation, for that I suggest you have a read of the aforeme...
Weblog: noginn
Tracked: Jan 18, 16:45
Lesestoff zum Zend Framework
In den letzten Wochen sind in diversen englischsprachigen Blogs sehr interessante und teilweise recht ausführliche Beiträge erschienen, die verschiedene Themen zum Zend Framework abdecken. Ich möchte an dieser Stelle einmal auf diese Beiträge hinwe...
Weblog: Ralfs Zend Framework und PHP Blog
Tracked: Jan 20, 12:23
Einheitliche Benutzerverwaltung am Beispiel meines CMS, Serendipity und anderem (Teil 1)
Vor einigen Wochen las ich auf der Homepage von Matthew Weier O'Phinney einen interessanten Artikel über das Domain-Model (Model Infrastructure). Dieses Model ist ein konzeptueller Ansatz, um die Verbindung zwischen den Systemen zu beschreiben. Einfach au
Weblog: Das FaDoeBlog
Tracked: Feb 01, 12:46

Comments
Display comments as (Linear | Threaded)

Nice post Matthew!

For those of you interested, there's a good book on Domain Driven Design: http://www.amazon.com/exec/obidos/ASIN/0321125215/domainlanguag-20

There's also a short, free version available at:
http://www.infoq.com/minibooks/domain-driven-design-quickly

Highly recommended reads!
#1 Jurriën Stutterheim (Link) on 2008-12-30 07:51 (Reply)
I've flipped through a copy at the bookstore before, but don't yet have it on my shelf. Thanks to the link to the "Quickly" download, though -- I'll give that a read.
#1.1 Matthew Weier O'Phinney (Link) on 2008-12-30 09:33 (Reply)
How exactly would you deal with a table's auto-incrementing numeric ID? In case you want to select a user based on his ID, you'll need it somewhere in Spindle_Model_User. A possible problem would be that this is closely linked to the actual database table. Should you just keep the ID in the DB and try to select rows based on other unique values, like a username?
#2 Jurriën Stutterheim (Link) on 2008-12-30 08:16 (Reply)
Identifiers are not unique to databases -- web services often use them, JSON-RPC uses identifiers for transactions with the server (to match requests to responses), git creates identifiers for each commit, etc. While they *are* part of the persistence mechanism, I don't think that means you should not reference them in your models.

If you *can* identify other unique fields, however, that's never a bad practice.
#2.1 Matthew Weier O'Phinney (Link) on 2008-12-30 09:32 (Reply)
Brilliant post. Time to refactor some old code :-)
#3 Tom Graham (Link) on 2008-12-30 08:58 (Reply)
There would be also nice to mention model flow problem. Simple model is good for simple things only and can be avoid. In enterprise you often refine your model data as a whole and moving back to previous state is necessary. Then you realy need some more complex model flow mechanism to wrap your simple model. My current project for example handles a tree hierarchy of different model flows (stack model flow, bidirectional model flow, simple model flow, incremental model flow).

Model is just the begining and is only one state of model flow state machine :-).
#4 Jiri Fornous on 2008-12-30 12:45 (Reply)
Is the decision to use the domain gateway driven primarily by wanting to have both a User object & a result set object or does it have additional benefits that go beyond some static find methods on the User class?
#5 Sean McSomething (Link) on 2008-12-30 13:02 (Reply)
That's the primary motivator. You can certainly combine the value and result set objects into a single object, but my feeling is that this is confusing -- a User is not multiple Users, and vice versa. (It's one of my problems with ActiveRecord as well.)

The other rationale is that there may be other related objects later down the line. Consider a BugTracker gateway -- it might need objects representing a bug, a comment, an attachment etc. Having a gateway allows you to manage dependencies for each of these.

However, it's all a matter of preference -- there are many ways to design models. This article was mainly intended to show how your model should grow out of discrete requirements, and may encompass multiple objects.
#5.1 Matthew Weier O'Phinney (Link) on 2008-12-30 13:12 (Reply)
so the gateway method: getDbTable, is just a method that returns an user class that extends Zend db table?
#6 kenrick (Link) on 2008-12-30 13:15 (Reply)
Yes. It could also be renamed to 'getDataProvider()', but that masks what is being done.
#6.1 Matthew Weier O'Phinney (Link) on 2008-12-30 13:18 (Reply)
Isn't your userGateway actually a data mapper? Or doesn't it fit the definition.... How do you see the data mapper pattern in the context of a domain?

Is it basically one step closer to ORM, which also ties you to the database? Or should I abstract it a bit and also allow for mapping data from web services, handle validation, etc. Like you did in your gateway?

Excellent post (again), learning a bit more each day!
#7 Taco Jung on 2008-12-30 14:08 (Reply)
The Gateway is actually one of the POEAA patterns: http://martinfowler.com/eaaCatalog/gateway.html The way I use it, it's basically handling dependencies for the other objects -- injecting the data access object, ACL object, etc. into the subcomponents.

The value objects actually act as data mappers in my examples here -- they have their own save() methods, which translate their properties to database calls.

It's definitely *not* classic ORM, though.
#7.1 Matthew Weier O'Phinney (Link) on 2008-12-30 14:16 (Reply)
Very good post indeed!!!

I have one question though: how this diffentiate from the ActiveRecord pattern?

Thank you.
#8 Edwin F. Lopez on 2008-12-30 15:00 (Reply)
There are several differences. First, classic ActiveRecord doesn't use instances to access individual items, but instead uses static access (this varies from implementation to implementation). Second, ActiveRecord typically doesn't rely on a separate data access layer that needs to be injected, but instead contains this access internally (it may come from a separate component, but instantiation and handling of that component is entirely internal).

The domain model typically involves a certain amount of dependency injection - i.e., the class may need certain artifacts, such as the database connection, ACLs, etc. This makes use a bit more complicated -- you need to provide arguments at instantiation -- but also makes it more flexible.

Finally, the way the examples here are defined, they could actually *consume* ActiveRecords to handle data persistence; it's actually common in domain models to use TDG or AR for that purpose.
#8.1 Matthew Weier O'Phinney (Link) on 2008-12-30 15:15 (Reply)
I just wanted to point you to Magento's Model implementation. It's very similar to your solution described above.

Model => Value Object plus Gateway methods
ResultSet => Collection of Value Objects
DbTable/Services => Resources

Just have a look at it =)
(Urgently have to blog about it...)
#9 Marc Jakubowski (Link) on 2008-12-30 16:15 (Reply)
Not surprising; I'm using some well-established patterns here. The build up is to an entry on why ZF doesn't, and likely won't, have any formal model support... but I'm getting ahead of myself. :-)
#9.1 Matthew Weier O'Phinney (Link) on 2008-12-30 16:36 (Reply)
So I actually tried out this code today, for fun, and while it took some mangling to get it to work, one part that is generating an error is when trying the authenticate method:

Message: You cannot serialize or unserialize PDO instances

My code follows yours pretty well, I'm not sure exactly where or why its trying to serialize PDO, (i have a debug backtrace that is slightly helpful), I'm assuming that it might be coming from these lines:

$this->populate($user);
unset($this->password);
$result = new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $this);

where $this has the gateway property which has the dbAdapter property. Is this where it might be trying to serialize it?
#10 kenrick (Link) on 2008-12-30 19:35 (Reply)
Yeah -- you need to add some __sleep() and __wake() code for this to work correctly. I should probably post about that, too...
#10.1 Matthew Weier O'Phinney (Link) on 2008-12-30 19:42 (Reply)
well, this might not be the optimal solution but it works, I just changed $this to $user->toArray() (the select statement I modified etc.). Now just a simple array is in the session instead of the object, which I think is fine, I guess the object would be nicer, I'm just not sure where to implement the sleep and wake.

I assume in the user class then and not the gateway class. I guess the sleep would have to unload the gateway and the wake would have to reinitialize it? Sorry I'm sure this is out of the scope of what you have been intending, but your tutorials of late have really coincided with what I'm currently re-writing, so I guess I'm taking your examples a little too literally :-)
#10.1.1 kenrick (Link) on 2008-12-30 22:24 (Reply)
Yeah, it's actually fairly simple. In your __sleep() method, only export serializable properties -- the $_data and $_allowed arrays -- and then have getGateway() capable of lazy-loading the gateway object. (I have it working locally that way, or similarly, that is.)
#10.1.1.1 Matthew Weier O'Phinney (Link) on 2008-12-31 15:19 (Reply)
so I tried to get the __sleep functionality working on just the User class, (just return array('_data') ) but whats crazy is that now zend session is throwing an exception after logging in... man I've been stepping through with the debugger so now Im in no mans land woohoo!
#10.1.2 kenrick (Link) on 2008-12-31 12:44 (Reply)
I know this is just a simple example, but for anyone wanting to try out some of the code, I ran into a problem when trying to iterate through the Spindle_Model_Users result set.

Fixed it by adding a check that $result is true under the current() method:

if ($result && !$result instanceof FormField) {
#11 Eugene Morgan (Link) on 2008-12-31 16:47 (Reply)
Great article, very interesting. One question: is the Model_User class used in the Spindle_Model_UserGateway as well? And how? Something like:
class Spindle_Model_UserGateway {
public function createUser($user) {
return new Spindle_Model_User($user, $this);
}
}
#12 Matthijs on 2009-01-02 07:44 (Reply)
Yes, basically something like that.
#12.1 Matthew Weier O'Phinney (Link) on 2009-01-02 07:51 (Reply)
Matthew - Excellent article!

One question --

I run into an issue with the Users object when passing it a Db_Table_Rowset in my UserGateway.

Admittedly, I am not that familiar with the Iterator interface, but what seems to happen is that the Users::current() method calls current() on the resultSet being passed in (in my case a Db_Table_Rowset). In this use case, Rowset::current() does not seem to return the Db_Table_Row as I would expect, but instead it appears to iterate over the properties of the RowSet.

Now, Zend_Db_Table_Rowset implements a SeekableIterator, so I would assume that Rowset::current() would return a Db_Table_Row.

I know Zend_Db isn't your baby, but I assume you may have an idea of what I'm doing wrong. Any suggestions?

It's probably worth mentioning that I'm following this article and your pastebin/bugapp code on github, so I'm attempting to use the abstract Result and ResultSet objects to keep things clean.
#13 Michael Crumm on 2009-01-02 13:57 (Reply)
The code for the __unset method doesn't seem right to me: First, unset() never returns a value, and placing it after a return statement is a syntax error. Second, if you unset the value, you cannot set the property again because array_key_exists() will return false. Maybe you should just set the value in data to null.
#14 Gabriel (Link) on 2009-01-04 11:56 (Reply)
Good points. I'll update. However, these are just minor implementation details -- don't take the code in here verbatim and expect it to work, as it's primarily for illustration purposes.
#14.1 Matthew Weier O'Phinney (Link) on 2009-01-04 12:20 (Reply)
Hi Matthew,

very good tutorial. Thank you. And now my question:

Is this right?

if (!$result instanceof User) {
$key = key($this->_resultSet);
$result = $gateway->createUser($result);
$this->_resultSet[$key] = $result;
}

Or must it be a check for an instance of Spindle_Model_User?
#15 Falk on 2009-01-05 06:11 (Reply)
gah! You're right. I've updated the code now. (I wrote the entry, then ported ideas into my Spindle application for testing things out, and then edited my draft. Missed that one.)
#15.1 Matthew Weier O'Phinney (Link) on 2009-01-05 06:23 (Reply)
Hi Matthew, also "$this->_resultSet[$key] = $result;" won't have any effect if $this->_resultSet is instance of Zend_Db_Table_Rowset and User will be instantiated twice in every iteration as current() method is also called in valid() method.

Do you plan any integration with Paginator component?
#15.1.1 Martin on 2009-01-05 11:05 (Reply)
Actually, that notation ($this->_resultSet[$key] = $result) *will* work, as Zend_Db_Table_Rowset_Abstract implements ArrayAccess. :-)

What *won't* work is key($this->_resultSet), and it should likely check to see if the resultset implements Iterator, and call it's key() method if so. I'm updating the post to reflect that.

As for valid/current instantiating the object twice, did you determine this during profiling? The first call to current() *should* set the value in the resultSet, so that the next call would get the instance. If it is double-instantiating, the way around it is to grab the key instead of current, and use the key to retrieve the value from the resultSet.
#15.1.1.1 Matthew Weier O'Phinney (Link) on 2009-01-05 11:29 (Reply)
Hey, Zend_Db_Table_Rowset do implement ArrayAccess, but offsetSet() (and offsetUnset()) method do nothing, please see the code (http://framework.zend.com/code/browse/~raw,r=12770/Zend_Framework/standard/trunk/library/Zend/Db/Table/Rowset/Abstract.php).

"key($this->_resultSet)" should be ok, Rowset implements SeekableIterator.
#15.1.1.1.1 Martin on 2009-01-05 11:37 (Reply)
Interesting -- I see offsetExists() and offsetGet() are filled out, but not the ones for writing. That would require an internal registry... ugh. I'll play some more with it.

Thanks for the heads-up.
#15.1.1.1.1.1 Matthew Weier O'Phinney (Link) on 2009-01-05 11:51 (Reply)
Excellent article, Matthew. I'm reading it through for the second time now. Your recent series on proper modeling techniques is just what the PHP community needs right now.

Thanks!!
#16 Justin Woods on 2009-01-06 10:40 (Reply)
Hey Matthew,

I like your blog entries a lot but this time I've to ask multiple questions and some of them may sound like criticism ;-)

1)
Let's start with the populate method where you check the type of the parameter - didn't we implement the gateway pattern to not rely on a specific DAL implementation?

2)
I don't like to pass a certain gateway instance to my model. In my understanding of models a model can have access to multiple gateways, therefore a factory for creating and retrieving gateway instances inside the model would be much more applicable, wouldn't it?

3) Implementing the authentication method inside the user model looks kind of strange to me - in an oop approach we should try to represent the real world; i.e. if you'd like to have a credit for your new bmw m5 you'll go to your bank's account manager and beg for a credit (well as a qualified zend employee you probably don't have to beg him, but it sounds more dramatic :-). Do you really expect the account manager to ask *you* if you're creaditworthy? I think he will check your account balance, want's to see paychecks from the last couple of month etc. Well.. I simply want to suggest implementing the authentication method in a class called AuthenticationManager or something like that.

4) Your save() method retrieves the ZDT instance from the gateway and calls a find() and save() method on it. Why don't you proxy the find() and save() call through your gateway? This way you don't have to touch your models once you decide to exchange your DAL

5) I think the following calls should reside in the key() method of your result set instead of the current() method. current() should simply call $this->key() or key($this)
if ($this->_resultSet instanceof Iterator) {
$key = $this->_resultSet->key();
} else {
$key = key($this->_resultSet);
}

Maybe I got your post totaly wrong but I think my points are valid.

Best regards,
Daniel
#17 Daniel Freudenberger (Link) on 2009-01-07 07:06 (Reply)
The other thing thats interesting, which I just figured out right now as Im testing these ideas, is that in the User model, the save function tries to do this:

if ($row = $dbTable->find($this->id)) {
foreach ($this->_data as $key => $value) {
$row->$key = $value;
}
$row->save();
} else {

// insert

Well, you can't really do a $row= $dbTable, as it doesnt return false, it always returns a Rowset object, regardless of # of rows, or 0 rows.. So the save method will just always be trying to do an 'update' and never get to the insert part.
#18 kenrick (Link) on 2009-01-07 12:44 (Reply)
Ugh. I should have pasted code verbatim from the working models I have. That should either be a fetchRow() with a select statement, or be "$rowset = $dbTable->find(...)" followed by checks on the count of $rowset, and pulling $rowset->current().
#18.1 Matthew Weier O'Phinney (Link) on 2009-01-07 12:51 (Reply)
yeah the other gotcha, as I'm trying to figure out zend db, is that if say you try to do this (within that save method):

$select = $dbTable->select()->where('id=?', $this->id);
if ($row = $dbTable->fetchRow($select)) {
...

it will throw this exception:
SQLSTATE[HY093]: Invalid parameter number: no parameters were bound

Well, $this->id happens to be null, cause its a new user so I don't have a key for it yet, but I can't even query the db as a 'check'.

Guess I'll figure out how zend db works sooner or later.
#19 kenrick (Link) on 2009-01-07 13:06 (Reply)
ah turns out I was bitten by an old PDO bug, now Im having fun with the Users::current method..
#19.1 kenrick (Link) on 2009-01-08 14:18 (Reply)
Hi,

I think there is something wrong in the "authenticate" function.
First you call the getGaetway-Function and pass it to the $gateway Variable but then you call $manager->getDbTable('user').
I think it should be
$gateway->getDbTable('user').

I hope this could help.
#20 Nilson (Link) on 2009-01-09 02:52 (Reply)
See a previous note: I have working code, but the examples here were not updated completely prior to posting (despite my best efforts when editing). I'll update.

Again, though, these examples are incomplete, and I wouldn't expect anyone to be able to use them directly.
#20.1 Matthew Weier O'Phinney (Link) on 2009-01-09 06:50 (Reply)
Sorry, didn´t read all the comments :-(

The examples how they are now are very good and i´m really excited to read the updated ones then =)
#20.1.1 Nilson (Link) on 2009-01-09 09:33 (Reply)
Hi Matt,
another great article, my question though is how do you make reusable applications with the Zend Framework? Would you use Zend_form and Zend_config to create an ini or xml file that is utilized by the bootstrap?
Also in the manual is their a list of what should be read first? I would think zend controller would be one of the first chapters to read ( besides the quickstart ) . I also think that a move to make the manual like the php manual would be helpful.
#21 david on 2009-01-10 10:28 (Reply)
Great article.


public function authenticate()
{
$gateway = $this->getGateway();
$table = $manager->getDbTable('user');


Shouldn't $manager be $gateway?
#22 Federico on 2009-01-16 12:07 (Reply)
Yep -- I'll be making updates soon, to incorporate all the feedback I've received.
#22.1 Matthew Weier O'Phinney (Link) on 2009-01-16 15:47 (Reply)
Great article and interesting pattern, i will try to get into because of it.

Would be great to have a kind of ZIP file with the skeletons and a graph to show the relationships :p
#23 Surt on 2009-01-20 04:45 (Reply)
Well, many of the ideas are already coded and posted in my pastebin demo app, in the bugapp branch -- and thus already available. :-)
#23.1 Matthew Weier O'Phinney (Link) on 2009-01-20 06:30 (Reply)
Hi Matthew,

thank you very much for this tutorial!
One question, though: Wouldn't it be nicer if you removed the table stuff from your model? I think the gateway should handle everything concerning materialization and loading, not the model itself. Thus it would be easier to replace the "SQL-Gateway" by a web service.

This seems to be ugly:
$gateway = $this->getGateway();
$dbTable = $gateway->getDbTable('user');
....

Would be nicer:
$gateway = $this->getGateway();
$gateway->save($this);

Regards maurice.
#24 Maurice on 2009-02-02 09:22 (Reply)
There are many ways to skin a cat.
#24.1 Matthew Weier O'Phinney (Link) on 2009-02-02 09:27 (Reply)
I am confused by your blog post.

You said it was tempting to use Zend_Db_Table / Row as models, so instead you just wrap those classes inside your 'model', which still couples your model with your storage method (in this case Zend_Db_Table).

At the moment, your Spindle_Model_User depends on your UserGateway returning a Zend_Db_Table, which is just as bad as your Spindle_Model_User extending Zend_Db_Table_Row or Model_UserGateway being child class of Zend_Db_Table in the first place?

I thought the idea of this pattern was to use the gateway as an disposable interface for your chosen storage method (DB, Filesystem, remote storage service etc), leaving the business logic in the Model_User free from storage concerns.

e.g. in Model_User 'updateName' or similar
n.b. please take as pseudo code

$this->name = 'foo';
$this->getGateway()->save($this);

-not-

$row = $this->getGateway()->fetchStorage()->find($this->id);
$row->username = 'foo';
$row->save();

I thought the gateway should be the class that takes all the dependencies on the chin, rather than passing the blow onto the Model_User - otherwise I don't see the advantage, since I still have to rewrite my all of my models when I change storage interface.

Something I think #24 was also trying to point out.

Perhaps I've missed the point of this article?
#24.1.1 Luke Barton on 2009-03-04 10:56 (Reply)
Don't blame him, it's been a long time and he is doing much better last time. Pay attention to his last data mapper experiments and object modelling presentations, he's much closer to the truth :-)

Though, for those who are still confused as Mathew, probably, was at the time of this article being created, and for those who are still reading this post trying to find a true way, I would suggest Mathew to give some links on more later presentations and articles about the Model somewhere on the page.
#24.1.1.1 lcf (Link) on 2009-12-09 06:24 (Reply)
Your gateway looks to me like a service layer object whose methods are modeled after use cases. The service layer object is injected with the dependencies that are required to fetch/persist domain model objects in accordance with the requisite business logic; it then composes those injected objects to perform fetching/persistence. Examples of injected dependencies might be a database connection (or persistence manager), a web service proxy, ACL rules, et al. A controller then uses the service layer object (or a facade that calls to one or more of them) in order to complete an application transaction.

One thing I don't like about placing data-store specific information inside of a domain model object is that it ties it to a specific application and thus makes it non-reusable. If I ever want to use model object Foo in another project, I have to rework it so that it fits with my new domain model. Keeping things as generic as possible facilitates reusability.

Regarding ORMs: they don't necessarily couple models to a particular database structure. Done right, you can have complete decoupling, since the service layer and the O/R mapper work in concert to populate the model without intruding upon it whatsoever. You service layer gets injected with the O/R mapper and then calls said mapper to fetch your object graph. The mapper only populates your model's properties (fields or mutators) and does not impose any dependencies on said model.

One last and minor thing: if I needed to retrieve more than one User, I wouldn't create a separate class encapsulating them. Instead, I would use some kind of generic list or set implementation as a container and pass that back to the caller instead of returning a Users object.

Either way, this was an excellent post. I'll be forwarding this along to a co-worker soon.
#25 Michael on 2009-02-07 21:39 (Reply)
Interesting Post. Could you provide an example on how to utilize the Gateway inside an MVC application? I am curious how you would tie this to the controller. Should the controller pass any request params to the gateway? Should the Gateway know about the View and maybe even set data to it?
#26 Gordon on 2009-02-10 03:37 (Reply)
This post is all about the M in MVC -- models. You instantiate the gateway, pass input to it (request parameters), and then do something with the result, typically passing it to your view.

As a quick example, you might instantiate the model, fetch a list of users, and pass them to the view:

$gateway = new Spindle_User_Gateway();
$this->view->users = $gateway->fetchAll();

In your view, you then iterate over each user:

foreach ($this->users as $user):
// do something with the user object...
endforeach;
#26.1 Matthew Weier O'Phinney (Link) on 2009-02-10 07:00 (Reply)
Thanks Matthew. Does it make sense to do ...

$gateway = new Spindle_User_Gateway();
$gateway->setRequest($this->getRequest());
$gateway->setView($this->view);
$gateway->fetchBannedUsers();

... and let the Gateway decide what data it needs from the Request Object and what it gives back to the View. This would make for extremely thin controllers and fat Gateways with very simple interfaces.
#26.1.1 Gordon on 2009-02-10 09:44 (Reply)
While it's appealling to do that, you're breaking a few rules of encapsulation here. The request environment should be managed by the controller, and it should pass input to the model. Similarly, mixing view logic into the model layer is a no-no, as you're now coupling display logic with the model. It's better for the view to fetch items out of the model as part of its display logic. This approach still achieves the goal of thin controllers and fat models, but ensures an appropriate separation of responsibilities.
#26.1.1.1 Matthew Weier O'Phinney (Link) on 2009-02-10 10:02 (Reply)
Is there anyone who has worked through this that would like to share their code as an example?

After reading through this a few times, I have some remaining questions that I think probably can be answered by looking at working code.

(FWIW, the pastebin/bugapp UserManager, BugTracker gateways in git as of Dec. 31 haven't fully been refactored yet. Matthew has been swamped)

Thanks,
--Rob T.
#27 Rob T. on 2009-02-12 11:52 (Reply)
Here's a cheat Sheet type summary that I did for myself.

Descriptions:

Model_User - Value Object, Simple implementation of "an object with a specific set of attributes or properties"

Model_Users - Record Set Object, set of Model_User Objects that can be iterated over.

Model_UserGateway - "Gateway to the domain ... used to fetch other objects in our model, and will inject various dependencies into them when doing so, such as the data access and ACLs. The various dependencies may themselves be injected into the gateway -- or it can lazy-load them." Can return a number of different types such Model_User or Model_Users (single object, vs. set of Model_User objects that can be iterated over).


Interfaces:

Model_User
* $_data (hold both values and provides valid attribute names)
* $_gateway, $_form
* __construct($data required) (calls populate, gateway not required?)
* populate() (fills in $_data from object using toArray or array copy)
* __set(), __get(), __isset(), __unset() customizable OO interface to internal data.
* save()
* getGateway(), setGateway()
* getForm()
* authenticate()

Model_Users
* $_count, $_gateway, $_resultSet;
* __construct($results & $gateway required)
* getGateway(), setGateway()
* count(), current(), key(), next(), rewind(), valid() (to implement Iterator & Countable interface)


Model_UserGateway
* createUser(array of user attributes/values)
* retrieve() (not defined, one example fetching username without spec'ing attribute name)
* fetch($id), fetchAll(), fetchBannedUser()
* sort()
#28 Rob T. on 2009-02-12 13:40 (Reply)
Hello,
very good articel! But it really needs to get updated! Please.

Well, i try to understand all the things mentioned..

In the last passage you mentioned that when replacing the data abstract layer only refactoring of the gateway is needed.
But I don't get it.. You use Zend_Db methods in the Object-Value classes as well... so i think this must be refactored too!
That seems strange, because this whole pattern should avoid much refactoring!?!? maybe i'm wrong, but i need explanation.
#29 Mario G. (Link) on 2009-02-25 03:11 (Reply)
Matthew, I'm writing a post about ZF and Model-Driven Design. Quick question, considering that Spindle_Model_User is a Value Object, shouldn't it be immutable? To void being modified you can remove the __set() method and make populate() protected.
#30 Federico on 2009-03-03 16:27 (Reply)
I didn't read all the comments so hopefully this hasn't been mentioned yet.

In the example below this text:
*** If we think of it as an input filter, we can use it for data integrity: ***

Shouldn't you get the value from the inputFilter something like
$this->_data[$name] = $element->getValue();

If you are using filters like StrToLower, that filter would not have been applied to the value, which I think should happen in this case.

- Ambis
#31 Matti Niemelä (Link) on 2009-03-25 16:05 (Reply)
Out of interest how do you then integrate your Zend_Form into save() if you pass in a complete set of data from the post array? The way you have it save doesn't do any validation at all?
#32 Simon on 2009-03-26 08:42 (Reply)
Thanks for this article, it helped me a lot. But I have one question: Isn't better to make the Gateway class a singleton and then easily getInstance() wherever it is necessary?
Imagine a collection of objects of class User, where there is a method getAddress() which internally calls AddressGateway's method retrieveAddressByUser(User $user). When I iterated throught that collection of Users and wanted to retrieve each User's Address, it would instantiate AddressGateway many many times...

And I thing this kind of coupling is very common... Thanks!
#33 Petr Sobotka (Link) on 2009-04-12 02:26 (Reply)
Singletons are almost always a bad solution, as they provide implicit (instead of explicit) coupling, and lead to difficult to test architectures (the only really good cases I can find for them currently are for autoloaders and session access). It's better to inject your dependencies into an object, either via the constructor or setter methods, and with good planning, this can be accomplished fairly trivially.
#33.1 Matthew Weier O'Phinney (Link) on 2009-04-12 09:45 (Reply)
I've been reading blog posts on this topic a lot recently - yours, Rob Allens and more recently Leendert's (HolyGoat), which focuses more on the idea and theory, rather than code http://www.angryobjects.com/2009/03/30/writing-robust-php-backends-with-zend-framework/

What I've found is that the idea makes perfect sense and I really think it's the best approach, but then when I see ideas like this starting to go in the direction of injecting the persistence layer into the domain model, it seems as though the entire point of separating them to begin with was pointless. Why not just stick your domain logic inside Zend_Db_Table_Row_Abstract? Sure, the consumer of the model will technically have access to the persistence layer's methods - but muddying your domain model by making it aware of the persistence layer seems equally wrong.

Surely it's possible to come up with a solution where by there's even less coupling? I like the service layer approach that Leendert proposes, but even this seems to have some shortfalls for me (in the way data is accessed). Hopefully I can find a way that I'm happy with soon enough, but just thought I'd share my opinion.

I'm nearly always on irc (in #zftalk and ##php on freenode) if anybody wants to give me an education - I am always ready to learn new things! :-)

Best Regards
#34 Steven Bakhtiari (breakerfall) on 2009-04-15 14:54 (Reply)
Hi Matthew, couple of concerns I have with the implemenation that I was hoping you could work through the though process on...

1. having a $_data array that contains the model attributes. Wouldn't it make more sense to make them actual attributes for documentation purposes to put phpdocs on those? What are you trying to gain with the array?

2. Should a model really have a "save" method or even have any idea a gateway exists on it? Your article mentions the Gateway is the "Gateway to the domain" but by having the gateway in the model itself what your saying is the model is the gateway to the gateway of the domain. I would think it would be cleaner to have the model standalone with business logic and all operations on that model are done through the gateway.


3. You have an authenticate method on the model itself. Shouldn't that also go in the gateway since the auth is entirely dependent on the gateway and the persistence layer underneath? Your saying authenticate -> grab the gateway, then do some actual persistence specific items.

Wouldn't it be better to have

$user = $userMapper->authenticate($user);
if($user->isAuthenticated) { do stuff }
to keep the model clean and more reusable?

anyway just some issues I was hoping you could address
thanks
#35 Jim Plush (Link) on 2009-07-10 13:54 (Reply)
Jim -- thank you for your comments. I've re-thought a lot of this since I wrote it originally, but haven't had chance to blog it yet. Probably the most representative state of where I'm experimenting is my DPC09 ZF workshop: http://www.slideshare.net/weierophinney/zend-framework-workshop-dpc09

In this, I show using Domain Models with a DataMapper that maps the model to a data source. On top of it all is a service layer which does the actual work of manipulating the models.

save() is about persisting the entity. While it doesn't belong directly with the entity itself, it's typically convenient to define it at the entity level -- this is in fact what most ORM solutions do at this time, and I see no really useful reason to diverge from it (I see an OOP purity reason, but not a pragmatic reason).

I've also been looking at utilizing Doctrine in order to define my model entities, and then building service layers on top of these. It's a nice concept, and allows me to separate the more application-level tasks such as data validation/filtering and ACL checking from the domain models themselves -- and simultaneously create my basic application API, which can then be re-used for both the MVC application and web services.
#35.1 Matthew Weier O'Phinney (Link) on 2009-07-10 14:21 (Reply)
Matt

I've seen you mention Doctrine several times in relation to Models and Service Layers and am anxiously awaiting a follow up as I've recently begun using the ORM.

When using Doctrine from your service layer, do you simply hydrate as an array and populate your models using the returned data?

How do you handle related entities (not much point using an ORM without dealing with relationships)?

How about the other nifty features of Doctrine such as being able to return related entities (or even just a count of such) using the one query?
#35.1.1 Phil Brown (Link) on 2010-03-07 05:25 (Reply)
so I've thought about this a little more and did some digging around Eric Evans works...

I created a little diagram here: http://litfuel.net/plush/images/UserDomainSample.jpg

Where the concept is you have a User who belongs to a Company. So we have two models. User and Company. A user holds a Company instance. Each one of those has a mapper that will contain all the SQL required to persist their data.

The real business logic sits in the service layer that would allow for X number of complex business rules using the same object entities. The service layer would be in charge of instantiating the appropriate models or mappers and then carrying out the business logic.

The bonus with the service layer is you can show stakeholders a concrete example of the domain scenario they were describing to you instead of having everything buried inside an entity itself. The User model stays clean and just represents a User. The biz logic can be grown and tested around it.

Here are a couple of code examples using the above...

using the auth service...
http://pastebin.com/f228acba0

creating a new user:
http://pastebin.com/f52ec244

Is that along the lines of what you're toying around with ?

Here's a sample directory structure for the above:
--models/
---------User/
--------------User.php
--------------UserMapper.php
--------------UserAuthService.php
--------------UserPayrollService.php
---------Company/
-----------------Company.php
-----------------CompanyMapper.php
#36 Jim Plush (Link) on 2009-07-14 15:35 (Reply)
This is precisely what I've been doing. My directory structure is a little flatter:

|-- models/
| |-- User.php
| |-- UserTable.php // I'm using Doctrine lately, which implements mapper
| |-- Company.php
| `-- CompanyTable.php
|-- services/
| |-- UserAuth.php // App_Service_UserAuth
| `-- UserPayroll.php // App_Service_UserPayroll

As you say -- this pattern then lets you keep your models clean, and presents the API directly via the service classes, which do the nitty gritty of things like ACLs, validation, etc.

A side benefit is you have something that you can typically very easily expose via a facade as a web service.
#36.1 Matthew Weier O'Phinney (Link) on 2009-07-14 16:14 (Reply)
I have a few questions about testability. Do you unit test your code? If yes..

1. How do you test your table classes? Do you use database fixtures like in PHPUnit Database extension getDataSet() ?

2. Let's imagine you have to pay salaries to all the employees in the company. How would you do using the latest model you described? How would you test it?

Thank you for sharing your insights.
#36.1.1 Zilvinas (Link) on 2009-09-14 19:23 (Reply)
Yes, I do unit test my code. As for testing my table classes, it depends on what I'm doing. If all I'm doing is extending Zend_Db_Table_Abstract, and I'm not adding any extra functionality other than defining the table name and potentially the primary key, then I don't test the table class at all; ZF contains unit tests for that already.

If I am adding functionality, I typically write a fixture that utilizes an in-memory sqlite database. This fixture will create the database and populate it with a dataset so that I have a known state at the beginning of my test case.

As for an example of testing, that's really a topic big enough for its own blog post. ;-)
#36.1.1.1 Matthew Weier O'Phinney (Link) on 2009-09-15 05:48 (Reply)
Hey Jim,

Quick question around the naming convention you've run into with your directory structure. Does that mean you have something like:

new Default_Model_User_User(); when creating a new User model? Wondering how you get around this ugly prefix or if you just deal with it?
#36.2 Jared on 2009-11-30 23:49 (Reply)
Well, it'd be "Default_Model_User" (not "_User_User"). But yes, I "just deal with it." The conventions ensure that I know where to look for the model class files -- particularly when I have multiple modules in play (which would mean multiple model directories)
#36.2.1 Matthew Weier O'Phinney (Link) on 2009-12-01 07:46 (Reply)
I wonder if the new namespace feature in PHP will be able to alleviate this. Because_Having_Really_Long_Class_Names_Is_A_Real_Pain_In_The_Butt_Especially_When_You're_Calling_Static_Methods. I wish it were more like Java which has the notion of a package. In Java, there isn't a _convention_ regarding packages and the directories in which package member's reside; rather, it's strictly enforced. A convention is a convenience that you use in lieu of a mandatory rule; the downside is that people can ignore conventions, whereas you cannot ignore a rule or "law." It's much like Python which enforces a particular source code style (indentation). The result is consistency, universal readability, and uniform expectations.
#36.2.1.1 Michael on 2009-12-01 13:22 (Reply)
Namespaces will make this much easier, particularly when you're in the given namespace, or have aliased the namespace within the file you're currently working in.

As for strict enforcement, the nature of dynamically typed languages is such that strict enforcement simply makes no sense. That said, there are some efforts to make this easier (the interop group, aka "php standards", for instance, is providing some standards around namespace naming); since namespaces are new, however, you'll need to wait for the standards to develop.
#36.2.1.1.1 Matthew Weier O'Phinney (Link) on 2009-12-01 13:49 (Reply)
Hey,

Nice article. Wondering if this is still relevant after 2 years in 1.9.6

Kind regards.
#37 sanders on 2010-01-05 15:43 (Reply)
Absolutely it is. (And, actually, the article is only 13 months old ;-) )

Zend Framework still does not dictate how your models should look, which means it is up to the developer to determine the best practices; posts like these are intended to help put forth some ideas on what those might be.
#37.1 Matthew Weier O'Phinney (Link) on 2010-01-06 06:47 (Reply)
Matthew after reading this article and also dealing with the new way to do the things using data mapper (currently quickstart guide) i ask :

The class "Spindle_Model_UserGateway" would become (Spindle_Model_UserMapper) right? or it becomes a service (Spindle_Service_User) ?

For example, the bellow method should be in the DataMapper and not in a service layer right? Or not? Or it depends?...lol

public function fetchBannedUsers()
{
$dbTable = $this->getDbTable();
$select = $dbTable->select()->where('date_banned IS NOT NULL');
$result = $dbTable->fetchAll($select);
return new Spindle_Model_Users($result, $this);
}

Thanks for your effort on ZF
#38 ed on 2010-02-10 23:16 (Reply)
Yes -- I'd recommend using a data mapper, and yes, put methods in your mapper for retrieving individual or aggregate items.

Your Service Layer would then consume one or more mappers and domain entities (such as your user, team, etc.).
#38.1 Matthew Weier O'Phinney (Link) on 2010-02-11 07:29 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back March '10 Forward
Mon Tue Wed Thu Fri Sat Sun
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

March 2010
February 2010
January 2010
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml cw09
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml git
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml rest
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zendcon09
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net