I'm really struggling with a recurring OOP / database concept.
Please allow me to explain the issue with pseudo-PHP-code.
Say you have a "user" class, which loads its data from the users table in its constructor:
class User {
public $name;
public $height;
public function __construct($user_id) {
$result = Query the database where the `users` table has `user_id` of $user_id
$this->name= $result['name'];
$this->height = $result['height'];
}
}
Simple, awesome.
Now, we have a "group" class, which loads its data from the groups table joined with the groups_users table and creates user objects from the returned user_ids:
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['user_ids'] as $user_id) {
// Make the user objects
$users[] = new User($user_id);
}
}
}
A group can have any number of users.
Beautiful, elegant, amazing... on paper. In reality, however, making a new group object...
$group = new Group(21); // Get the 21st group, which happens to have 4 users
...performs 5 queries instead of 1. (1 for the group and 1 for each user.) And worse, if I make a community class, which has many groups in it that each have many users within them, an ungodly number of queries are ran!
The Solution, Which Doesn't Sit Right To Me
For years, the way I've got around this, is to not code in the above fashion, but instead, when making a group for instance, I would join the groups table to the groups_users table to the users table as well and create an array of user-object-like arrays within the group object (never using/touching the user class):
class Group {
public $type;
public $schedule;
public $users;
public function __construct($group_id) {
$result = Query the `groups` table, joining the `groups_users` table,
**and also joining the `users` table,**
where `group_id` = $group_id
$this->type = $result['type'];
$this->schedule = $result['schedule'];
foreach ($result['users'] as $user) {
// Make user arrays
$users[] = array_of_user_data_crafted_from_the_query_result;
}
}
}
...but then, of course, if I make a "community" class, in its constructor I'll need to join the communities table with the communities_groups table with the groups table with the groups_users table with the users table.
...and if I make a "city" class, in its constructor I'll need to join the cities table with the cities_communities table with the communities table with the communities_groups table with the groups table with the groups_users table with the users table.
What an unmitigated disaster!
Do I have to choose between beautiful OOP code with a million queries VS. 1 query and writing these joins by hand for every single superset? Is there no system that automates this?
I'm using CodeIgniter, and looking into countless other MVC's, and projects that were built in them, and cannot find a single good example of anyone using models without resorting to one of the two flawed methods I've outlined.
It appears this has never been done before.
One of my coworkers is writing a framework that does exactly this - you create a class that includes a model of your data. Other, higher models can include that single model, and it crafts and automates the table joins to create the higher model that includes object instantiations of the lower model, all in a single query. He claims he's never seen a framework or system for doing this before, either.
Please Note:
I do indeed always use separate classes for logic and persistence. (VOs and DAOs - this is the entire point of MVCs). I have merely combined the two in this thought-experiment, outside of an MVC-like architecture, for simplicity's sake. Rest assured that this issue persists regardless of the separation of logic and persistence. I believe this article, introduced to me by James in the comments below this question, seems to indicate that my proposed solution (which I've been following for years) is, in fact, what developers currently do to solve this issue. This question is, however, attempting to find ways of automating that exact solution, so it doesn't always need to be coded by hand for every superset. From what I can see, this has never been done in PHP before, and my coworker's framework will be the first to do so, unless someone can point me towards one that does.
And, also, of course I never load data in constructors, and I only call the load() methods that I create when I actually need the data. However, that is unrelated to this issue, as in this thought experiment (and in the real-life situations where I need to automate this), I always need to eager-load the data of all subsets of children as far down the line as it goes, and not lazy-load them at some future point in time as needed. The thought experiment is concise -- that it doesn't follow best practices is a moot point, and answers that attempt to address its layout are likewise missing the point.
EDIT : Here is a database schema, for clarity.
CREATE TABLE `groups` (
`group_id` int(11) NOT NULL, <-- Auto increment
`make` varchar(20) NOT NULL,
`model` varchar(20) NOT NULL
)
CREATE TABLE `groups_users` ( <-- Relational table (many users to one group)
`group_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL
)
CREATE TABLE `users` (
`user_id` int(11) NOT NULL, <-- Auto increment
`name` varchar(20) NOT NULL,
`height` int(11) NOT NULL,
)
(Also note that I originally used the concepts of wheels and cars, but that was foolish, and this example is much clearer.)
SOLUTION:
I ended up finding a PHP ORM that does exactly this. It is Laravel's Eloquent. You can specify the relationships between your models, and it intelligently builds optimized queries for eager loading using syntax like this:
Group::with('users')->get();
It is an absolute life saver. I haven't had to write a single query. It also doesn't work using joins, it intelligently compiles and selects based on foreign keys.
Say you have a "wheel" class, which loads its data from the wheels table in its constructor
Constructors should not be doing any work. Instead they should contain only assignments. Otherwise you make it very hard to test the behavior of the instance.
Now, we have a "car" class, which loads its data from the cars table joined with the cars_wheels table and creates wheel objects from the returned wheel_ids:
No. There are two problems with this.
Your Car class should not contain both code for implementing "car logic" and "persistence logic". Otherwise you are breaking SRP. And wheels are a dependency for the class, which means that the wheels should be injected as parameter for the constructor (most likely - as a collection of wheels, or maybe an array).
Instead you should have a mapper class, which can retrieve data from database and store it in the WheelCollection instance. And a mapper for car, which will store data in Car instance.
$car = new Car;
$car->setId( 42 );
$mapper = new CarMapper( $pdo );
if ( $mapper->fetch($car) ) //if there was a car in DB
{
$wheels = new WheelCollection;
$otherMapper = new WheelMapper( $pdo );
$car->addWheels( $wheels );
$wheels->setType($car->getWheelType());
// I am not a mechanic. There is probably some name for describing
// wheels that a car can use
$otherMapper->fetch( $wheels );
}
Something like this. The mapper in this case are responsible for performing the queries. And you can have several source for them, for example: have one mapper that checks the cache and only, if that fails, pull data from SQL.
Do I really have to choose between beautiful OOP code with a million queries VS. 1 query and disgusting, un-OOP code?
No, the ugliness comes from fact that active record pattern is only meant for the simplest of usecases (where there is almost no logic associated, glorified value-objects with persistence). For any non-trivial situation it is preferable to apply data mapper pattern.
..and if I make a "city" class, in its constructor I'll need to join the cities table with the cities_dealerships table with the dealerships table with the dealerships_cars table with the cars table with the cars_wheels table with the wheels table.
Jut because you need data about "available cares per dealership in Moscow" does not mean that you need to create Car instances, and you definitely will not care about wheels there. Different parts of site will have different scale at which they operate.
The other thing is that you should stop thinking of classes as table abstractions. There is no rule that says "you must have 1:1 relation between classes and tables".
Take the Car example again. If you look at it, having separate Wheel (or even WheelSet) class is just stupid. Instead you should just have a Car class which already contains all it's parts.
$car = new Car;
$car->setId( 616 );
$mapper = new CarMapper( $cache );
$mapper->fetch( $car );
The mapper can easily fetch data not only from "Cars" table but also from "Wheel" and "Engines" and other tables and populate the $car object.
Bottom line: stop using active record.
P.S.: also, if you care about code quality, you should start reading PoEAA book. Or at least start watching lectures listed here.
my 2 cents
ActiveRecord in Rails implements the concept of lazy loading, that is deferring database queries until you actually need the data. So if you instantiate a my_car = Car.find(12) object, it only queries the cars table for that one row. If later you want my_car.wheels then it queries the wheels table.
My suggestion for your pseudo code above is to not load every associated object in the constructor. The car constructor should query for the car only, and should have a method to query for all of it's wheels, and another to query it's dealership, which only queries for the dealership and defers collecting all of the other dealership's cars until you specifically say something like my_car.dealership.cars
Postscript
ORMs are database abstraction layers, and thus they must be tuned for ease of querying and not fine tuning. They allow you to rapidly build queries. If later you decide that you need to fine tune your queries, then you can switch to issuing raw sql commands or trying to otherwise optimize how many objects you're fetching. This is standard practice in Rails when you start doing performance tuning - look for queries that would be more efficient when issued with raw sql, and also look for ways to avoid eager loading (the opposite of lazy loading) of objects before you need them.
In general, I'd recommend having a constructor that takes effectively a query row, or a part of a larger query. How do do this will depend on your ORM. That way, you can get efficient queries but you can construct the other model objects after the fact.
Some ORMs (django's models, and I believe some of the ruby ORMs) try to be clever about how they construct queries and may be able to automate this for you. The trick is to figure out when the automation is going to be required. I do not have personal familiarity with PHP ORMs.
Related
I'm using Yii2's ActiveRecord implementation in (hopefully) exactly the way it should be used, according to the docs.
Problem
In a quite simple setup with simple relations betweens the tables, fetching 10 results is fast, 100 is slow. 1000 is impossible. The database is extremely small and indexed perfectly. The problem is definitly Yii2's way to request data, not the db itself.
I'm using a standard ActiveDataProvider like:
$provider = new ActiveDataProvider([
'query' => Post::find(),
'pagination' => false // to get all records
]);
What I suspect
Debugging with the Yii2 toolbar showed thousands of single SELECTs for a simple request that should just get 50 rows from table A with some simple "JOINs" to table B to table C. In plain SQL everybody would solve this with one SQL statement and two joins. Yii2 however fires a SELECT for every relation in every row (which makes sense to keep the ORM clean). Resulting in (more or less) 1 * 50 * 30 = 1500 queries for just getting two relations of each row.
Question
Why is Yii2 using so many single SELECTs, or is this a mistake on my side ?
Addionally, does anybody know how to "fix" this ?
As this is a very important issue for me I'll provide 500 bounty on May 14th.
By default, Yii2 uses lazy loading for better performance. The effect of this is that any relation is only fetched when you access it, hence the thousands of sql queries. You need to use eager loading. You can do this with \yii\db\ActiveQuery::with() which:
Specifies the relations with which this query should be performed
Say your relation is comments, the solution is as follows:
'query' => Post::find()->with('comments'),
From the guide for Relations, with will perform an extra query to get the relations i.e:
SELECT * FROM `post`;
SELECT * FROM `comment` WHERE `postid` IN (....);
To use proper joining, use joinWith with the eagerLoading parameter set to true instead:
This method allows you to reuse existing relation definitions to perform JOIN queries. Based on the definition of the specified relation(s), the method will append one or multiple JOIN statements to the current query.
So
'query' => Post::find()->joinWith('comments', true);
will result in the following queries:
SELECT `post`.* FROM `post` LEFT JOIN `comment` comments ON post.`id` = comments.`post_id`;
SELECT * FROM `comment` WHERE `postid` IN (....);
From #laslov's comment and https://github.com/yiisoft/yii2/issues/2379
it's important to realise that using joinWith() will not use the JOIN query to eagerly load the related data. For various reasons, even with the JOIN, the WHERE postid IN (...) query will still be executed to handle the eager loading. Thus, you should only use joinWith() when you specifically need a JOIN, e.g. to filter or order on one of the related table's columns
TLDR:
joinWith = with plus an actual JOIN (and therefore the ability to filter/order/group etc by one of the related columns)
In order to use relational AR, it is recommended that primary-foreign key constraints are declared for tables that need to be joined. The constraints will help to keep the consistency and integrity of the relational data.
Support for foreign key constraints varies in different DBMS. SQLite 3.6.19 or prior does not support foreign key constraints, but you can still declare the constraints when creating tables. MySQL’s MyISAM engine does not support foreign keys at all.
In AR, there are four types of relationships:
BELONGS_TO: if the relationship between table A and B is one-to-many, then B belongs to A (e.g. Post belongs to User);
HAS_MANY: if the relationship between table A and B is one-to-many, then A has many B (e.g. User has many Post);
HAS_ONE: this is special case of HAS_MANY where A has at most one B (e.g. User has at most one Profile);
MANY_MANY: this corresponds to the many-to-many relationship in database. An associative table is needed to break a many-to-many relationship into one-to-many relationships, as most DBMS do not support many-to-many relationship directly. In our example database schema, the tbl_post_category serves for this purpose. In AR terminology, we can explain MANY_MANY as the combination of BELONGS_TO and HAS_MANY. For example, Post belongs to many Category and Category has many Post.
The following code shows how we declare the relationships for the User and Post classes.
class Post extends CActiveRecord
{
......
public function relations()
{
return array(
'author'=>array(self::BELONGS_TO, 'User', 'author_id'),
'categories'=>array(self::MANY_MANY, 'Category',
'tbl_post_category(post_id, category_id)'),
);
}
}
class User extends CActiveRecord
{
......
public function relations()
{
return array(
'posts'=>array(self::HAS_MANY, 'Post', 'author_id'),
'profile'=>array(self::HAS_ONE, 'Profile', 'owner_id'),
);
}
}
The query result will be saved to the property as instance(s) of the related AR class. This is known as the lazy loading approach, i.e., the relational query is performed only when the related objects are initially accessed. The example below shows how to use this approach:
// retrieve the post whose ID is 10
$post=Post::model()->findByPk(10);
// retrieve the post's author: a relational query will be performed here
$author=$post->author;
You are somehow doing it the wrong please go through from the documentaion here http://www.yiiframework.com/doc/guide/1.1/en/database.arr
I need some help with database access in PHP. I'm trying to do things the OOP way but I'm not sure if I'm heading the right way.
Lets say I have a class Person, for example:
class Person {
private $id;
private $firstname;
private $lastname;
// maybe some more member variables
function __construct($id = NULL) {
if(isset($id)) {
$this->id = $id;
$this->retrieve();
}
}
// getters, setters and other member functions
private function retrieve() {
global $db;
// Retrieve user from database
$stmt = $db->prepare("SELECT firstname, lastname FROM users WHERE id = :id");
$stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
$stmt->execute();
$result = $stmt->fetch();
$this->firstname = $result['firstname'];
$this->lastname = $result['lastname'];
}
function insert() {
global $db;
// Insert object into database, or update if exists
$stmt = $db->prepare("REPLACE INTO users (id, firstname, lastname) VALUES (:id, :firstname, :lastname)");
$stmt->bindParam(":id", $this->id, PDO::PARAM_INT);
$stmt->bindParam(":firstname", $this->firstname, PDO::PARAM_STR);
$stmt->bindParam(":lastname", $this->lastname, PDO::PARAM_STR);
$stmt->execute();
}
}
Note that this is just an example I just wrote to describe my question, not actual code I use in an application.
Now my first question is: is this the correct way to handle database interaction? I thought this would be a good way because you can instantiate an object, manipulate it, then insert/update it again.
In other words: is it better to handle database interaction inside the class (like in my example) or outside it, in the code that instantiates/uses the class?
My second question is about updating a whole bunch of rows that may or may not have been modified. Lets say the class Person has a member variable $pets[], which is an array containing all the pets that person owns. The pets are stored in a separate table in the database, like this:
+---------+-------------+---------+
| Field | Type | Key |
+---------+-------------+---------+
| pet_id | int(11) | PRI |
| user_id | int(11) | MUL |
| name | varchar(25) | |
+---------+-------------+---------+
Lets say I modified some pets in the Person object. Maybe I added or deleted some pets, maybe I only updated some pet's names.
What is the best way to update the whole Person, including their pets in that case? Lets say one Person has 50 pets, do I just update them all even if only one of them has changed?
I hope this is clear enough ;)
EDIT:
Even more importantly, how do I handle deletions/insertions at the same time? My current approach is that on an "edit page", I retrieve a certain Person (including their pets) and display/print them in a form for the user to edit. The user then can edit pets, add new pets or delete some pets. When the user clicks the "apply" button, the form gets POSTed back to the PHP script.
The only way I can think of to update these changes into the database is to just remove all pets currently listed in the database, and then insert the new set of pets. There are some problems with this though: first of all, all rows in the pets table are deleted and reinserted on every edit, and second, the auto increment id will take huge leaps every time because of this.
I'm feeling I'm doing something wrong. Is it just not possible to let users remove/add pets and modify existing pets at the same time (should I handle those actions separately)?
What you're trying to accomplish is a task called object-relational mapping (mapping objects to tables in a relational database and vice-versa). Entire books have been written on that, but I'll try to give a short overview.
Now my first question is: is this the correct way to handle database interaction? I thought this would be a good way because you can instantiate an object, manipulate it, then insert/update it again.
It's a valid approach. However,
in general, you should try to adhere to Separation of Concerns. In my opinion, modelling a domain entity (like a person, in your case) and storing this object in/loading from a database are two (arguably three) different concerns that should be implemented in separate classes (again, personal opinion!). It makes unit-testing your classes very difficult and adds a lot of complexity.
Regarding ORM, there are several design patterns that have emerged over time. Most prominently:
Active Record is basically the approach that you've already suggested in your question; it tightly couples data and data access logic together in one object. In my opinion not the best approach, because it violates Separation of Concerns, but probably easiest to implement.
Gateways or Mappers: Try to create a separate class for accessing your Persons table (something like a PersonGateway. That way, your Person class contains only the data and assorted behaviour, and your PersonGateway all kinds of insert/update/delete methods. A mapper on the other hand might be a class that converts generic database result objects (for instance a row returned by a PDO query) into Person objects (in this case, the Person class does not need to be aware of the existence of such a mapper class).
What is the best way to update the whole Person, including their pets in that case? Lets say one Person has 50 pets, do I just update them all even if only one of them has changed?
Again, several possibilities. You should map our Pet as a separate class in any case.
Keep the pets in their separate table and implement your own data access logic for this class (for example using one of the patterns mentioned above). You can then individually update, insert or delete them at will. You can keep track of added/deleted pets in your parent object (the person) and then cascade your update operations when you persist the parent object.
Embed the pets collection inside your Persons table. Depending on the average size of this collection, and how you might want to query them, this might not be a good idea, though.
If your project gains complexity, you might also want to have a look at ORM frameworks (like for example the Doctrine ORM) that take care of these problems for you.
In answer to question 1:
I would suggest having the sql in the class, and feed in the condition via the argument as per your example. As they would (should) all relate to a table of data representing Person objects.
On your second:
If pets are in a second table, I would suggest you having this as a separate class, with different sql queries. The variable $pets[] in your example can hold the pet_ids and you can have separate sql in the Pet class to do any changing, adding or removing as necessary.
I'm experimenting with the Doctrine ORM (v1.2) for PHP. I have defined a class "liquor", with two child classes "gin" and "whiskey". I am using concrete inheritance (class table inheritance in most literature) to map the classes to three seperate database tables.
I am attempting to execute the following:
$liquor_table = Doctrine_Core::getTable('liquor');
$liquors = $liquor_table->findAll();
Initially, I expected $liquors to be a Doctrine_Collection containing all liquors, whether they be whiskey or gin. But when I execute the code, I get a empty collection, despite having several rows in the whiskey and gin database tables. Based on the generated SQL, I understand why: the ORM is querying the "liquor" table, and not the whiskey/gin tables where the actual data is stored.
Note that the code works perfectly when I switch the inheritance type to column aggregation (simple table inheritance).
What's the best way to obtain a Doctrine_Collection containing all liquors?
Update
After some more research, it looks like I'm expecting Doctrine to be performing a SQL UNION operation behind the scenes to combine the result sets from the "whiskey" and "gin" tables.
This is known as a polymorphic query.
According to this ticket, this functionality is not available in Doctrine 1.x. It is destined for the 2.0 release. (also see Doctrine 2.0 docs for CTI).
So in light of this information, what would be the cleanest, most efficient way to work around this deficiency? Switch to single table inheritance? Perform two DQL queries and manually merge the resulting Doctrine_Collections?
the only stable and useful inheritence mode of Doctrine for the moment is column_aggregation. I have tried the others in different projects. With column_aggregation you can imitate polymorphic queries.
Inheritance in general is something that is a bit buggy in Doctrine (1.x). With 2.x this will change, so we may have better options in the future.
I wrote the (not production ready) beginnings of an ORM that would do exactly what you're looking for a while back. Just so that I could have a proof of concept. All my studies did yield that you're in some way mixing code and data (subclass information in the liquor table).
So what you might do is write a method on your liquor class/table class that queries it's own table. The best way to get away with not having to hard-code all the subclasses in your liquor class is to have a column which contains the class name of the subclass in it.
How you spread the details around is entirely up to you. I think the most normalized (and anyone can correct me if I'm wrong here) way to do it is to store all fields that appear in your liquor class in the liquor table. Then, for each subclass, have a table that stores the specific data that pertains to the subclass type.
Which is the point at which you are mixing code and data because your code is reading the liquor table to get the name of the subclass to perform a join.
I'll use cars & bikes and some minimal, yet trivial differences between them for my example:
Ride
----
id
name
type
(1, 'Sebring', 'Car')
(2, 'My Bike', 'Bicycle')
Bicycle
-------
id
bike_chain_length
(2, '2 feet')
Car
---
id
engine_size
(1, '6 cylinders')
There's all kinds of variations from here forward like storing all liquor class data in the subclass table and only storing references and subclass names in the liquor table. I like this the least though because if you are aggregating the common data, it saves you from having to query every subclass table for the common fields.
Hope this helps!
The situation is as follows: I've got 2 models: 'Action' and 'User'. These models refer to the tables 'actions' and 'users', respectively.
My action table contains a column user_id. At this moment, I need an overview of all actions, and the users to which they are assigned to. When i use $action->fetchAll(), I only have the user ID, so I want to be able to join the data from the user model, preferably without making a call to findDependentRowset().
I thought about creating custom fetchAll(), fetchRow() and find() methods in my model, but this would break default behaviour.
What is the best way to solve this issue? Any help would be greatly appreciated.
I designed and implemented the table-relationships feature in Zend Framework.
My first comment is that you wouldn't use findDependentRowset() anyway -- you'd use findParentRow() if the Action has a foreign key reference to User.
$actionTable = new Action();
$actionRowset = $actionTable->fetchAll();
foreach ($actionRowset as $actionRow) {
$userRow = $actionRow->findParentRow('User');
}
Edit: In the loop, you now have an $actionRow and a $userRow object. You can write changes back to the database through either object by changing object fields and calling save() on the object.
You can also use the Zend_Db_Table_Select class (which was implemented after I left the project) to retrieve a Rowset based on a join between Action and User.
$actionTable = new Action();
$actionQuery = $actionTable->select()
->setIntegrityCheck(false) // allows joins
->from($actionTable)
->join('user', 'user.id = action.user_id');
$joinedRowset = $actionTable->fetchAll($actionQuery);
foreach ($joinedRowset as $joinedRow) {
print_r($joinedRow->toArray());
}
Note that such a Rowset based on a join query is read-only. You cannot set field values in the Row objects and call save() to post changes back to the database.
Edit: There is no way to make an arbitrary joined result set writable. Consider a simple example based on the joined result set above:
action_id action_type user_id user_name
1 Buy 1 Bill
2 Sell 1 Bill
3 Buy 2 Aron
4 Sell 2 Aron
Next for the row with action_id=1, I change one of the fields that came from the User object:
$joinedRow->user_name = 'William';
$joinedRow->save();
Questions: when I view the next row with action_id=2, should I see 'Bill' or 'William'? If 'William', does this mean that saving row 1 has to automatically update 'Bill' to 'William' in all other rows in this result set? Or does it mean that save() automatically re-runs the SQL query to get a refreshed result set from the database? What if the query is time-consuming?
Also consider the object-oriented design. Each Row is a separate object. Is it appropriate that calling save() on one object has the side effect of changing values in a separate object (even if they are part of the same collection of objects)? That seems like a form of Content Coupling to me.
The example above is a relatively simple query, but much more complex queries are also permitted. Zend_Db cannot analyze queries with the intention to tell writable results from read-only results. That's also why MySQL views are not updateable.
You could always make a view in your database that does the join for you.
CREATE OR REPLACE VIEW VwAction AS
SELECT [columns]
FROM action
LEFT JOIN user
ON user.id = action.user_id
Then just use
$vwAction->fetchAll();
Just remember that views in MySQL are read-only (assuming this is MySQL)
isn't creating a view sql table a good solution to make joint ?
and after a simple table class to access it
I would think it's better if your logic is in sql than in php
If I have a class representing access to one table in my database in a class:table relationship, so I can hide table details in one class. But as any useful application I have to query several tables. How can I accomodate this using the class:table design?
There's a couple of different ways you can achieve this, however, which one you choose really depends on your circumstances.
1) Break the connection between your objects and your database
Write your objects to have no connection with your database tables. First normalise your database tables, then, look at how the user of your application will interact with your data. Model the data to objects, but don't tie each object to the table (ie with a Zend_DB_Table_Abstract class)
Once you have established your objects, then write mapper classes which map your objects back to the relevant tables in your database. These are the classes which extend Zend_DB_Table (if appropriate).
You can handle joins in two ways, either map the joins through the Zend_DB_Table relationship functionallity, or, (IMHO a better choice) just use Zend_DB_Select to make the relevant methods within your your mapper class.
So you've then got two classes (probably per table, but not always)
Person
PersonMapper
In your code, when you want to work with some objects, either create a new object
$person = new Person();
$person->setName('andrew taylor');
Then write pass it to the mapper to save it:
$personMapper = new PersonMapper();
$pesonnMapper->save($person);
Or, do it the other way:
$personMapper = new PersonMapper();
$person = personMapper->load(29);
$person->setName('joe bloggs');
$personMapper->save($person);
The next step on from here would be a collection class based on the SPL:
$personList = $personMapper->loadAllMen();
foreach($personList AS $person) {
echo $person->getName();
}
Where $personMapper->loadAllMen() is a method like:
$select = $this->select();
$select=>where('gender = "Male"');
$zendDbRows = this->fetchAll($select);
return new PersonList($zendDbRows);
2) MySQL Views
If you have a lot of joined tables where there is one row per join, so, you're joining customer information based on an id in your orders table, and you're doing it read-only (so you don't want to update any information through the Zend_DB_Table adaptor) you create your normalised tables, then, a single view across the top. The view handles the joins behind the scenes so through Zend it feels like you're connecting to a single table.
There are some caveats with this, MySQL views do have some performance problems (which is why it's best on single row FK joins), and, they're strictly read only.