$_referenceMap Relationships with Zend_Db_Table_Abstract creates too many queries - zend-framework

This is my first time using Zend Framework for an application and i don't know if I completely have by head around Models.
I have four tables: shopping_cart, product, product_unit, distributor.
shopping cart has an cart_id, product_id, unit_id and dist_id (shopping cart joins on the other tables with their corresponding id).
Before Zend I would create a class like this:
class ShoppingCart
{
function getItems()
{
$sql ="select * from shopping_cart, product, product_unit, distributor
where
shopping_cart.product_id = product.id AND
shopping_cart.unit_id = product_unit.id AND
shopping_cart.dist_id = distributor.id AND
cart_id = xxx";
$items = $this->db->getAll($sql);
}
One query to get all the information from the joined tables.
When I set up the relationship mapping in Zend_Db_Table_Abstract:
My Shopping Cart Model:
class Application_Model_ShoppingCart
{
function __construct()
{
$this->ShoppingCartTable = new Application_Model_DbTable_ShoppingCart();
}
function getItems()
{
$cart_items = $this->ShoppingCartTable->getItems($this->GetCartId());
return $cart_items;
}
}
class Application_Model_DbTable_ShoppingCart extends Zend_Db_Table_Abstract
{
protected $_name = 'shopping_cart';
protected $_rowClass = 'Application_Model_DbTable_ShoppingCart_Item';
protected $_referenceMap = array(
'Product' => array(
'columns' => 'product_id',
'refTableClass' => 'Application_Model_DbTable_Product',
'refColumns' => 'id'
),
'Distributor' => array(
'columns' => 'dist_id',
'refTableClass' => 'Application_Model_DbTable_Distributor',
'refColumns' => 'id'
),
'Unit' => array(
'columns' => 'unit_id',
'refTableClass' => 'Application_Model_DbTable_ProductUnit',
'refColumns' => 'id'
)
);
public function getItems($cart_id)
{
$where = $this->getAdapter()->quoteInto('cart_id = ?', $cart_id);
return $this->fetchAll($where);
}
}
In my controller:
$this->_shoppingCartModel = new Application_Model_ShoppingCart();
$items = $this->_shoppingCartModel->getItems();
IN my view :
foreach($this->items AS $item)
{
$item_product = $item->findParentRow('Application_Model_DbTable_Product');
$item_dist = $item->findParentRow('Application_Model_DbTable_Distributor');
$item_unit = $item->findParentRow('Application_Model_DbTable_ProductUnit');
}
when I have ten items in my cart the db profiler shows over sixty queries (WHOA) to view the cart items ( information across all four tables are displayed - product name, unit description, distributor name).
For each item it queries the shopping_cart, then querys the product table, then the product unit, then the distributor_table.
Is there a way to have this run as one query joining all the tables via Zend_Db_Table_Abstract relationships?
Will I have to go back to using db adapter in the my Application_Model_ShoppingCart class?
I want to abstract all data access to the table models (Application_Model_DbTable_ShoppingCart) and not have the Application_Model_ShoppingCart tied to a db handler.
Thanks in advance for advice, I love the Zend Framework but models are still hard for me to understand given the different conflicting ways people talk about using them.

In short, no, unfortunately it's not possible to get a set of table rows together with their relationships in a single query.
It's just that all methods dealing with relationships are defined for a row, not for a table.
But at least, you can form your sql with Zend_Db_Table_Select instead of writing it all manually.
Upd: Your code for fetching ShoppingCarts, in my opinion, should belong to the table (DbTable_ShoppingCart). So, the code you provided in the beginning could be transformed to the following:
class Application_Model_DbTable_ShoppingCart extends Zend_Db_Table_Abstract {
public function getItem($cart_id) {
$select = $this->select()
->from( array('sc' => 'shopping_cart'), array(Zend_Db_Select::SQL_WILDCARD) )
->join( array('p' => 'product'), 'sp.product_id = p.id', array(Zend_Db_Select::SQL_WILDCARD) )
->join( array('pu' => 'product_unit'), 'sp.unit_id = pu.id', array(Zend_Db_Select::SQL_WILDCARD) )
->join( array('d' => 'distributor'), 'sp.dist_id = d.id', array(Zend_Db_Select::SQL_WILDCARD) )
->where('sp.cart_id = ?', $cart_id)
->setIntegrityCheck(false);
return $this->fetchAll($select);
}
}

Related

Fuel PHP - to_array() method and multiple belongs_to relationships and eager loading

I am attempting to migrate some legacy data models/schemas to a fuel API, and have run into an odd issue with the to_array() method on a model that has two $_belongs_to properties.
When I load the model without the to_array() method, I properly receive both related items with eager loading, but as soon as I pass them through this function to convert the data to make it digestable by the new API, it will strip out the second $_belongs_to property. If I re-order the props in the $belongs_to array, it will show whichever item is first in the array.
My question is, how can I convert this data to an array without losing the second relationship?
Here are some cleaned up examples for ease of reference:
Transaction Model:
protected static $_belongs_to = array(
'benefactor' => array(
'key_from' => 'from_user_id',
'model_to' => 'Model\\Legacy\\User',
'key_to' => 'id',
),
'user' => array(
'key_from' => 'user_id',
'model_to' => 'Model\\Legacy\\User',
'key_to' => 'id',
),
);
Transaction Controller:
$result = array();
$id = $this->param('id');
if (!empty($id)) {
$transaction = Transaction::find($id, array('related' => array('user', 'benefactor',)));
if (!empty($transaction)) {
// Works -- both benefactor and user are returned
$result['transaction_works'] = $transaction;
// Does not work -- only the benefactor is returned
$result['transaction_doesnt_work'] = $transaction->to_array();
}
}
return $this->response($result);
For any googlers looking for help on this issue, I was seemingly able to return all relationships by simply executing the to_array() method before setting the return/results variable:
$result = array();
$id = $this->param('id');
if (!empty($id)) {
$transaction = Transaction::find($id, array('related' => array('user', 'benefactor',)));
if (!empty($transaction)) {
$transaction->to_array();
$result['transaction_works'] = $transaction;
}
}
return $this->response($result);
Good luck!

Form for deep association with CakePHP, almost working

I'm writing my first Cake app and trying to set up my first deep association. It's very nearly working but I have a couple of small issues.
The first part of my app is a customer database. A customer has many addresses, and an address has many contacts. The data structure returned contains duplicated data from the address model.
Here's the 3 models:
class Customer extends AppModel {
public $hasMany = 'CustomerAddress';
}
class CustomerAddress extends AppModel {
public $belongsTo = 'Customer';
public $hasMany = 'CustomerContact';
}
class CustomerContact extends AppModel {
public $belongsTo = 'CustomerAddress';
}
In my customer controller, I want to get associated addresses and their contacts. I can do this by setting recursion on the find command:
CustomerController.php
public function find( $id = NULL) {
$this->set('customers', $this->Customer->find('all', array( 'recursive' => 2)));
}
And this works brilliantly, with one caveat. The data structure returned looks like this:
array(
'Customer' => array(
'id' => '46',
....
),
'CustomerAddress' => array(
(int) 0 => array(
'id' => '35',
'customer_id' => '46',
.....
'Customer' => array(
'id' => '46',
.....
),
'CustomerContact' => array(
(int) 0 => array(
'id' => '29',
'customer_address_id' => '35',
.....
)
)
)
)
)
At a glance, this looks fine, and for all intents and purposes works as the data is formatted how you'd expect. But, CustomerAddress also contains a Customer object, which is a duplicate of the top level Customer object. I assume this is because recursion is working with the belongsTo on the address model.
I've tried setting the recursion to 1, but then I only get the address not the contact. I've tried setting the recursion in the address model, but that doesn't seem to effect the find at the Customer level.
It's not a huge issue, just worried about future performance issues and unnecessary calls to the database.
This is one of the reasons why it is discouraged to use the recursive setting. Most turn off recursion (set it to -1) then use the Containable behavior to get associated models instead. Make sure in AppModel, there is the line
public $actsAs = array('Containable');
Then, to get your data:
$this->Customer->find('all', array('contain' => array('CustomerAddress' => array('CustomerContact'))));

Cakephp automatically filled form select for two word named belongTo model

What is right naming or what am I missing to get automagic run for two word named Model. Actual model belong to the two words named model.
Exact example:
Tour belongs to Accommodation type.
in database there is table tours and table accommodation_types
foreign key from tours is tours.accommodation_type_id
Snapshots of code below.
ToursController.php
public function add() {
//...
$accommodation_types = $this->Tour->AccommodationType->find('list');
//...
$this->set(compact('accommodation_types', ...));
}
Tour.php
//...
public $belongsTo = array(
//...
'AccommodationType' => array(
'className' => 'AccommodationType',
'foreignKey' => 'accommodation_type_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
//...
);
Tours/add.ctp (inside a form)
echo $this->Form->input('accommodation_type_id', array('label' => 'Accommodation type'));
As per convention the view vars names should be camelBacked. So rename the view var from $accommodation_types to $accommodationTypes. If you don't follow convention you have to explicitly specify the options var to use like this:
echo $this->Form->input('accommodation_type_id', array('options' => $accommodation_types, 'label' => 'Accommodation type'));

Zend One To Many doesn't work

I was trying an example of one to many relationship in zend framework, but I can't get results.
Here are the tables:
UsersTable:
<?php
class Application_Model_DbTable_UsersTable extends Zend_Db_Table_Abstract
{
protected $_name = 'users';
protected $_dependentTables = array('Application_Model_DbTable_BugsTable');
}
BugsTable:
class Application_Model_DbTable_BugsTable extends Zend_Db_Table_Abstract
{
protected $_name = 'bugs';
protected $_dependentTables = array('Application_Model_DbTable_BugsProductsTable');
protected $_referenceMap = array(
'Reporter' => array(
'columns' => 'reported_by',
'refTableClass' => 'Application_Model_DbTable_UsersTable',
'refColumns' => 'username'
),
'Engineer' => array(
'columns' => 'assigned_to',
'refTableClass' => 'Application_Model_DbTable_UsersTable',
'refColumns' => 'username'
),
'Verifier' => array(
'columns' => array('verified_by'),
'refTableClass' => 'Application_Model_DbTable_UsersTable',
'refColumns' => array('username')
)
);
}
As you can see, this is one to many relationship from db table 'users' to table 'bugs' , where we have three foreign reference keys in bugs talbe.
Now when I try to use Zend methods for one to many relationships I always get empty results:
$tableUser = new Application_Model_DbTable_UsersTable();
$tableBugs = new Application_Model_DbTable_BugsTable();
$result= $tableUser->find(1);
$user= $result->current();
$userBugs = $user->findDependentRowset('Application_Model_DbTable_BugsTable','Verifier');
echo count($userBugs); //returns 0
$bugresult = $tableBugs->find(1);
$thisbug= $bugresult->current();
$verifier= $thisbug->findParentRow('Application_Model_DbTable_UsersTable','Verifier');
return $verifier //returns nothing
The proper data is in the database, when I for example do a query:
select * from bugs b, users u where b.reported_by=u.id and u.id=1;
I get the expected results. But when I try in zend no results.
Do you have any suggestions? Thanks.
In your sql u get bugs by field
reported_id
but in this code:
$result= $tableUser->find(1);
$user= $result->current();
$userBugs = $user->findDependentRowset('Application_Model_DbTable_BugsTable','Verifier');
echo count($userBugs); //returns 0
$bugresult = $tableBugs->find(1);
$thisbug= $bugresult->current();
$verifier= $thisbug->findParentRow('Application_Model_DbTable_UsersTable','Verifier');
return $verifier //returns nothing
u try to get bug by
verified_by
field
try this:
$result= $tableUser->find(1);
$user= $result->current();
$userBugs = $user->findDependentRowset('Application_Model_DbTable_BugsTable','Reporter');
echo count($userBugs);

Zend_Db_Table Cascade DELETE And UPDATE

I'm trying to achieve a cascading UPDATE and DELETE effect in a MyISAM database (similar effect as you can create in InnoDB tables with foreign keys). Two example tables:
albums
photos (has an album_id column that is a "foreign key" to the albums table)
Now when I delete a row in the albums table I would like Zend_Db_Table to automatically delete all related rows in the photos table. This is what I have in the albums table:
protected $_name = 'albums';
protected $_dependentTables = array(
'Photos'
);
And I have this in the photos table:
protected $_name = 'photos';
protected $_referenceMap = array(
'Album' => array(
'columns' => array('album_id'),
'refTableClass' => 'Albums',
'refColumns' => array('id')
)
);
Yes when I delete a row in the albums table, the photos from that album do not get removed.
This is how I'm removing the album:
public function remove($id)
{
$where = $this->getAdapter()->quoteInto('id = ?', $id, 'INTEGER');
return $this->delete($where);
}
You need to setup the cascade delete. So your reference map should be:
protected $_referenceMap = array(
'Album' => array(
'columns' => array('album_id'),
'refTableClass' => 'Albums',
'refColumns' => array('id'),
'onDelete' => self::CASCADE
));
See full description of cascading operations here: http://framework.zend.com/manual/en/zend.db.table.relationships.html#zend.db.table.relationships.cascading
NB Cascading operations are only triggered when functions called on the actual row of the results set (i.e. Zend_Db_Table_Row class). To trigger the delete function in this example:
$album = $albums->find($id);
$album->delete();//This triggers the cascading delete