i have a question on mongodb, model cakephp and relationships.
I'd create the following relations:
User -> hasMany -> City
City -> belongsTo -> User
In MongoDB, I have two tables:
users
cities (with key user_id)
In cakephp, I have 2 model:
User.php
class User extends Model {
public $name = 'User';
public $actsAs = array('Containable');
public $hasMany = array ('City');
..
}
and:
City.php
class City extends Model {
public $name = 'City';
public $actsAs = array('Containable');
public $belongsTo = array('User');
..
}
In my controller I use :
$user = $this->User->find('all');
but it doesn't work. In sql dump, cakephp uses a find only on tbl users.
Why? Where I wrong?
I normally place recursive to -1 and containable in app model, so it applies to all models you create unless you override specifically.
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
}
Your relationships are fine, although I usually add className and foreignKey just to be safe and clear. In your controller you should do something like this:
$users = $this->User->find('all', array(
'contain' => array(
'City'
)
));
Recursive will prevent any associated records being included by default, this is good as sometimes you do not need the recursive data and extra data will help slow down your application.
Next adding contain into your find call may seem like a chore but it will be clear and concise what you are querying, any 3rd party developer will understand exactly what you are doing if they know how to use Cake. Hope this helps.
Related
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.
So I created a form to add a customer. A customer has many addresses and an address has many contacts. The issue I have is that the Form helper doesn't seem to recognise the contact fields for formatting purposes. However, if I perform a find() in the action the form displays correctly. It's like the model isn't linking until it's used.
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';
}
Until it's submitted, the add action basically does nothing so I won't bother posting that, but here's some extracts from the view:
add.ctp
echo $this->Form->input('CustomerAddress.0.CustomerContact.title', 'options' => array( 'Mr' => 'Mr', 'Miss' => 'Miss', 'Mrs' => 'Mrs', 'Ms' => 'Ms', 'Dr' => 'Dr')))."\n";
echo $this->Form->input('CustomerAddress.0.CustomerContact.first')."\n";
echo $this->Form->input('CustomerAddress.0.CustomerContact.last')."\n";
.....
echo $this->Form->input('CustomerAddress.address1')."\n";
echo $this->Form->input('CustomerAddress.address2')."\n";
.....
echo $this->Form->input('type', array( 'label' => 'Business Customer?'))."\n";
So, the CustomerAddress fields work fine, they are the right type, limited length etc to match the database, but the CustomerContact fields aren't working at all. Likewise, if I submit invalid data, the add action does a saveAll and fails, but the form fields are then displayed correctly.
If I add a find() to the action before the page is displayed, they work fine. It's like the Model isn't being included until it's used.
I'm sure there's probably a simple command to get it to read the models or something but I'm a bit stuck with it.
ok, after much digging, I got this to work by calling ->loadModel on the controller. Since in this case I need all 3 models loaded in virtually every case, I added a beforeFilter to do this every time.
I didn't change the code for any models, but added this to the controller:
CustomerController.php
class CustomerController extends AppController {
public $helpers = array( 'Html', 'Form', 'Humanize');
public function beforeFilter() {
// Load associated models
$this->loadModel( 'CustomerAddress');
$this->loadModel( 'CustomerContact');
}
.....
}
Even though the CustomerContact model is loaded from the Customer model instead of the CustomerAddress model, Cake seems to respect the relationship and load it under CustomerAddress. At first, I tried:
$this->CustomerAddress->loadModel( 'CustomerContact');
but it was failing. Seems to work fine this way.
I'm using entity framework with POCOs and the repository pattern and am wondering if there is any way to filter a child list lazy load. Example:
class Person
{
public virtual Organisation organisation {set; get;}
}
class Organisation
{
public virtual ICollection<Product> products {set; get;}
}
class Product
{
public bool active {set; get;}
}
Currently I only have a person repository because I'm always starting from that point, so ideally I would like to do the following:
Person person = personRepo.GetById(Id);
var products = person.organisation.products;
And have it only load products where active = true from the database.
Is this possible and if so how?
EDIT My best guess would be either a filter can be added to the configuration of the entity. Or there might be a way to intercept/override the lazy load call and modify it. Obviously if I created an Organisation Repository I could manually load it as I please but I am trying to avoid that.
There's not a direct way to do this via lazy loading, but if you were willing to explicitly load the collection, you could follow whats in this blog, see the Applying filters when explicitly loading related entities section.
context.Entry(person)
.Collection(p => p.organisation.products)
.Query()
.Where(u => u.IsActive)
.Load();
You can do what Mark Oreta and luksan suggest while keeping all the query logic within the repository.
All you have to do is pass a Lazy<ICollection<Product>> into the organization constructor, and use the logic they provided. It will not evaluate until you access the value property of the lazy instance.
UPDATE
/*
First, here are your changes to the Organisation class:
Add a constructor dependency on the delegate to load the products to your
organization class. You will create this object in the repository method
and assign it to the Person.Organization property
*/
public class Organisation
{
private readonly Lazy<ICollection<Product>> lazyProducts;
public Organisation(Func<ICollection<Product>> loadProducts){
this.lazyProducts = new Lazy<ICollection<Product>>(loadProducts);
}
// The underlying lazy field will not invoke the load delegate until this property is accessed
public virtual ICollection<Product> Products { get { return this.lazyProducts.Value; } }
}
Now, in your repository method, when you construct the Person object you will assign the Organisation property with an Organisation object containing the lazy loading field.
So, without seeing your whole model, it will looks something like
public Person GetById(int id){
var person = context.People.Single(p => p.Id == id);
/* Now, I'm not sure about the cardinality of the person-organization or organisation
product relationships, but let's assume you have some way to access the PK of the
organization record from the Person and that the Product has a reference to
its Organisation. I may be misinterpreting your model, but hopefully you
will get the idea
*/
var organisationId = /* insert the aforementioned magic here */
Func<ICollection<Product>> loadProducts = () => context.Products.Where(product => product.IsActive && product.OrganisationId == organisationId).ToList();
person.Organisation = new Organisation( loadProducts );
return person;
}
By using this approach, the query for the products will not be loaded until you access the Products property on the Organisationinstance, and you can keep all your logic in the repository. There's a good chance that I made incorrect assumptions about your model (as the sample code is quite incomplete), but I think there is enough here for you to see how to use the pattern. Let me know if any of this is unclear.
This might be related:
Using CreateSourceQuery in CTP4 Code First
If you were to redefine your properties as ICollection<T> rather than IList<T> and enable change-tracking proxies, then you might be able to cast them to EntityCollection<T> and then call CreateSourceQuery() which would allow you to execute LINQ to Entities queries against them.
Example:
var productsCollection = (EntityCollection<Product>)person.organisation.products;
var productsQuery = productsCollection.CreateSourceQuery();
var activeProducts = products.Where(p => p.Active);
Is your repository using something like:
IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> expression)
If so you can do something like this:
var person = personRepo.Find(p => p.organisation.products.Any(e => e.active)).FirstOrDefault();
You could possibly use Query() method to achieve this. Something like:
context.Entry(person)
.Collection(p => p.organisation.products)
.Query()
.Where(pro=> pro.Active==true)
.Load();
Have a look at this page click here
I've got a zend framework model:
class User extends Zend_Db_Table_Abstract {
protected $_name = 'users';
protected $_primary = 'id';
protected $_dependentTables = array('UserItem');
public function refresh($) {
$items = $this->findDependentRowset('UserItem', 'items');
// do stuff with each item
print_r($items);
die();
}
}
I've also got the related model:
<?php
class UserItem extends Zend_Db_Table_Abstract
{
protected $_name = 'user_items';
protected $_referenceMap = array(
'items' => array(
// user_id is the name of the field on the USER_ITEMS table
'columns' => 'user_id',
'refTableClass' => 'User',
// id is the name of the field on the USERS table
'refColumns' => 'id'
)
);
}
?>
I'd like to me able to call User->refresh(); and have a fancy little stack of things happen. But the error is
Fatal error: Call to undefined method FbUser::findDependentRowset()
Which is telling me that although I think i'm doing it right according to the Zend documentation http://framework.zend.com/manual/en/zend.db.table.relationships.html I'm missing something.
If it makes a difference, at first run the items list will be empty, then I'll "Upsert" a whole bunch of items - future runs I'll compare all items an only update the ones that are different. Hmm... nope that's definitely not relevant :)
You have your classses mixed. You should have 2 classes for every entity... a EntityTable (your table gateway) and an Entity (your row gateway). so the class declarations should look something like:
class User extends Zend_Db_Table_Row
class FbUser extends User
class UserTable extends Zend_Db_Table_Abstract
class UserItem extends Zend_Db_Table_Row
class UserItemTable extends Zend_Db_Table_Abstract
The row classes are your models (or are linked to models depending on how you want to wire it up), not the table classes.
The findDependentRowset method is on the Zend_Db_Table_Row class which is why you are getting the error... you have extended the incorrect class in a way.
By in a way, i mean that your table definitions are correct, but you are trying to use the table instances like rows. You can either add/change class usage as suggested above or you can pass the wor instance of user to the table class as an arg ument to refresh:
public function refresh(Zend_Db_Table_Row $user)
{
$items = $user->findDependentRowset('UserItem', 'items');
// do stuff with each item
print_r($items);
die();
}
I am new to zend. I have been asked to redevelop a website that was once written in plain PHP and put it into the zend framework.
I am having a lot of trouble with database relationships, I cant seem to get my head round defining and querying relationships.
I would like to find a Category. From that Category I would like to be able to find all the CategoryInfo associated with it, and be able to query/sort/limit that dataset.
Here are my models.
Categorys.php
<?php
class Default_Model_Categorys extends Zend_Db_Table_Abstract
{
protected $_name = 'Categorys';
protected $_primary = 'id';
protected $_dependentTables = array('Default_Model_CategoryInfo');
}
?>
CategoryInfo.php
<?php
class Default_Model_CategoryInfo extends Zend_Db_Table_Abstract
{
protected $_name = 'Category_Info';
protected $_primary = 'id';
protected $_referenceMap = array(
'Categorys' => array(
'columns' => array('cat_id'),
'refTableClass' => 'Default_Model_Categorys',
'refColumns' => array('id')
)
);
}
?>
CategoryController.php
<?php
class CategorysController extends Zend_Controller_Action
{
public function indexAction()
{
/*
this should redirect to all games
*/
return $this->_forward("index", "games");
}
public function categoryAction()
{
/*
shows a specific category
*/
$id = (int) $this->_request->getParam('id');
$category = new Default_Model_Categorys();
$this->view->category = $category->fetchRow(
$category->select()->where('id = ?', $id)
);
$categoryInfo = $this->view->category->findDependentRowset('Default_Model_CategoryInfo');
}
}
Firstly... am I doing anything wrong?
Secondly... how do I go about querying the dependent rowset?
First, if you're searching for a category by its primary key, it's simpler to use the find() method:
$id = (int) $this->_request->getParam('id');
$category = new Default_Model_Categorys();
$this->view->category = $category->find($id)->current();
Second, to restrict or sort dependent Category_Info rows, you can use a Zend_Db_Table_Select object as an optional parameter of findDependentRowset(). Here's an example:
$select = $category->select()->where("info_type = 'PRICE'")
->order("info_date")
->limit(3);
$categoryInfo = $this->view->category->findDependentRowset(
'Default_Model_CategoryInfo', null, $select);
Notice you can use any table object to create that select object. Since the "FROM" clause for that select will be set by the findDependentRowset() method, you just add other clauses and then pass it in.
PS: You don't need to declare $_dependentTables at all, unless you're going to use cascading update or cascading delete via PHP code. I recommend strongly against doing that -- it's far more efficient to let the RDBMS handle those cascading operations.
Likewise you should never have to declare $_primary if your database tables actually declare primary key constraints. Zend_Db_Table_Abstract knows how to inspect metadata to get the primary key column(s).
Everything looks correctly to me. You don't query a dependent rowset. It is a query itself and it returns a result set. Basically what it is doing is pulling all records related to the current row you are working with as defined by $_referenceMap. Once you execute findDependentRowset(), you can foreach over the results which will give you instances of Zend_Db_Table_Row. From there you can display the related data as needed.
Personally I don't use Zend_Db Relationships. It is much easier to just make a second model method to query what I need. Also, Zend_Db Relationships do not support where clauses, so just making a second query is much more flexible than relationships.
I'm trying to understand how the Zend Framework works.Are the models designed to do something like this?I have just a basic setup, so I can use in my controllers something like this:
$db->query($this->selectAll())
Can you also give me an example on how to use this on a controller?
class Country extends Zend_Db_Table
{
protected $_name = 'country';
public function selectAll()
{
return 'SELECT * FROM'.$this->_name.'';
}
}
Best Regards!
Pedantic terminology: Zend_Db_Table is a class to represent database tables. This is not the same thing as a Model in the MVC sense.
I wrote a lot of the documentation for the Zend_Db components, and nowhere did I treat Tables and Models as synonyms (as many frameworks do).
Also see a blog I wrote on this subject:
http://karwin.blogspot.com/2008/05/activerecord-does-not-suck.html
Zend Models are desigend to be linked to a table and help you to interact with a table.
class BugsProducts extends Zend_Db_Table_Abstract
{
protected $_name = 'bugs_products';
protected $_primary = array('bug_id', 'product_id');
}
$table = new BugsProducts();
$rows = $table->fetchAll('bug_status = "NEW"', 'bug_id ASC', 10, 0);
$rows = $table->fetchAll($table->select()->where('bug_status = ?', 'NEW')
->order('bug_id ASC')
->limit(10, 0));
// Fetching a single row
$row = $table->fetchRow('bug_status = "NEW"', 'bug_id ASC');
$row = $table->fetchRow($table->select()->where('bug_status = ?', 'NEW')
->order('bug_id ASC'));
more informations in the manual