How to find all objects using CakePHP model associations? - mysql

Here's my database schema:
create table Personas
(
id int primary key AUTO_INCREMENT,
Nombre varchar(255),
Apellidos varchar(255),
FechaDeNacimiento date,
Sexo Bool,
CarnetDeIdentidad varchar(255)
);
create table Tutors
(
id int primary key AUTO_INCREMENT,
persona_id int,
FOREIGN KEY (persona_id) REFERENCES Personas(id)
);
create table Alumnos
(
id int primary key AUTO_INCREMENT,
persona_id int,
FOREIGN KEY (persona_id) REFERENCES Personas(id)
);
create table CoordinadorDeProgramas
(
id int primary key AUTO_INCREMENT,
persona_id int,
FOREIGN KEY (persona_id) REFERENCES Personas(id)
);
And here are my Model declarations:
<?php
class Alumno extends AppModel {
public $belongsTo = 'Persona';
}
<?php
class Coordinadordeprograma extends AppModel {
public $belongsTo = 'Persona';
}
<?php
class Tutor extends AppModel {
public $belongsTo = 'Persona';
}
<?php
class Persona extends AppModel {
public $hasOne = array('Alumno', 'Tutor', 'Coordinadordeprograma');
}
In my Controller I just want to fetch all Persona records if they have a foreign key relationship in Alumnos (for example).
Here's my code, I hope it illustrates what I'm trying to do:
public function filter($type = null) {
if ($type == "alumno") { // www.app.com/personas/filter/alumno
$this->set('personas', $this->Alumno->Persona->find('all'));
}
}
However this is returning every single Persona record, and not just the ones that have a record in the Alumno table.
How do you suggest I solve this problem? I thought that by using $this->Alumno->Persona I would only be reaching for Persona's that are in the Alumno table.
Thanks!

You could use the containable behaviour and just make a find on Alumno??
$this->set('personas', $this->Alumno->find('all'));
it should retrieve the 'Alumno' with all the models associated to it. You could also choose which models you want to retrieve. For example, this code will retrieve all the 'Alumno' with its corresponding 'Persona'
$this->set('personas', $this->Alumno->find('all',array('contain'=>array('Persona')));
An of course.. like #Paulo answered, you could manually make the join, but using "containable" is cleaner. I only make the join manually when I don't have any other solution.
Hope this helps,

You can try make INNER JOIN on the fly, like this:
$personas = $this->Alumno->Persona->find('all', array(
'joins' => array(
array(
'table' => 'Alumnos',
'alias' => 'Alumno',
'type' => 'INNER',
'conditions' => 'Persona.id = Alumno.persona_id'
)
)
));
$this->set('personas', $personas);

Related

Relationships of models (Laravel 5.2)

My tables (Mysql DB):
// Stores Table
CREATE TABLE IF NOT EXISTS `app_beta`.`stores` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL,
PRIMARY KEY (`id`))
// Items Table
CREATE TABLE IF NOT EXISTS `app_beta`.`items` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`user_id` INT UNSIGNED NOT NULL,
`title` TEXT NOT NULL,
`content` LONGTEXT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_items_user_id`
FOREIGN KEY (`user_id`)
REFERENCES `app_beta`.`users` (`id`))
// Products Table
CREATE TABLE IF NOT EXISTS `app_beta`.`products` (
`id` INT UNSIGNED NOT NULL,
`reviews` DECIMAL(7,1) NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_products_id`
FOREIGN KEY (`id`)
REFERENCES `app_beta`.`items` (`id`))
// Product_Store Table
CREATE TABLE IF NOT EXISTS `app_beta`.`products_stores` (
`product_id` INT UNSIGNED NOT NULL,
`store_id` INT UNSIGNED NOT NULL,
`price` DECIMAL(7,2) NOT NULL,
`url` VARCHAR(255) NOT NULL,
CONSTRAINT `fk_products_store_product_id`
FOREIGN KEY (`product_id`)
REFERENCES `app_beta`.`products` (`id`),
CONSTRAINT `fk_products_stores_store_id`
FOREIGN KEY (`store_id`)
REFERENCES `app_beta`.`stores` (`id`))
// Offers Table
CREATE TABLE IF NOT EXISTS `app_beta`.`offers` (
`id` INT UNSIGNED NOT NULL,
`store_id` INT UNSIGNED NOT NULL,
`price` DECIMAL(7,2) NULL,
`url` VARCHAR(255) NOT NULL,
`start_date` DATE NOT NULL,
`end_date` DATE NOT NULL,
PRIMARY KEY (`id`),
CONSTRAINT `fk_offers_store_id`
FOREIGN KEY (`store_id`)
REFERENCES `app_beta`.`stores` (`id`),
CONSTRAINT `fk_offers_id`
FOREIGN KEY (`id`)
REFERENCES `app_beta`.`items` (`id`))
Add. Info:
My tables are migrated. Just to clarify... the products and offers inherit from the items table. If the item is not created I can not add products and offers.
The product can have the title, summary, content, category etc... same for the offer.
The product can be on 1-many stores
The offer can be only on 1-1 store.
If I'm wrong LET ME KNOW!
** Please, I want someone to help me creating the relationships between the Item model, product and offer. Can i use polymorphic relations? **
Models DONE:
class Store extends Model
{
public function offers()
{
return $this->hasMany('App\Offer');
}
public function products()
{
return $this->hasMany('App\Product');
}
}
class Product extends Model
{
public function stores()
{
return $this->belongsToMany('App\Store');
}
}
class Offer extends Model
{
public function store()
{
return $this->belongsTo('App\Offer');
}
}
using php artisan tinker, all works nice!
namespace App
$user = new User
$store = new Store
$item = new Item
$item->id = 1
$item->user_id = 1
$item->title = 'test'
$item->content 'test'
$item->save();
true
$item2 = new Item
$item2->id = 2
....
true
$product1 = new Product
$product1->id = 1 (FK item->id)
$product1->reviews = 5
$product1->save()
true
$offer1 = new Offer
$offer1->id = 2 (FK item->id)
$offer1->store_id = 1
...
true
I'll add later a function to attach product to one or many stores (products_stores table).
Thanks.
This is how I think you can have a good start...
First of all, your model and migration can handle all it.
There is for relationship:Laravel 5.2 Relationship
There is for migration:Laravel 5.2 Migration
So there you create your migration:
Schema::create('stores', function (Blueprint $table) {
$table->bigIncrements('id')->unsigned();
$table->string('name', 50);
$table->timestamps();
});
Schema::create('items', function (Blueprint $table) {
$table->bigIncrements('id')->unsigned();
$table->bigInteger('user_id')->unsigned();
$table->foreign('user_id')->references('id')->on('users');
$table->text('title');
$table->longText('content');
$table->timestamps();
});
Schema::create('products', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('store_id')->unsigned();
$table->foreign('store_id')->references('id')->on('stores');
$table->decimal('reviews', 7,1);
$table->timestamps();
});
Schema::create('offers', function (Blueprint $table) {
$table->bigIncrements('id');
$table->bigInteger('store_id')->unsigned();
$table->foreign('store_id')->references('id')->on('stores');
$table->bigInteger('item_id')->unsigned();
$table->foreign('item_id')->references('id')->on('items');
$table->decimal('price', 7,2);
$table->string('url', 255);
$table->dte('start_date');
$table->dte('end_date');
$table->timestamps();
});
So, once you did this, you can make your relationship onto your model. This way you don't need all the "between" tables. When you will use associate(), Laravel will create the link for you. This way you can do something like this: $offer->store()->name to get the name of the store of the current offer. Take a look:
Into Store's model
public function products()
{
return $this->hasMany(Product::class);
}
public function offers()
{
return $this->hasMany(Offer::class);
}
Into Offer's model
public function store()
{
return $this->belongsTo(Store::class);
}
This way, You create a one-to-many relation. Has I said, $offer->store() will retrieve the store of the offer. $store->offers()->get() will retrieve all offer of the store.
Hope it help.
EDIT
There is one only problem with what I said. The n + 1 problem. So like it explain there(search google "laravel n+1 problem" and pick the link to laracast) (can't put it as a link, not enough reputation) , when you call things like I said, the script will do 2 query. When you use a foreach() loop, it'll have as much loop +1 query. I suggest you to do things like that
$offers = Offer::with('store')->all();
This way you'ill have only 1 query and you will still able to do
$offer->store;
without doing another query.
When you use $model = Model::with('something')->all();, the query will fetch data from 2 table and return the result with an array into an array. Like this:
offers {
[0]:{a,b,c,d,e, store{a,b,c,d,e}}
[1]:{a,b,c,d,e, store{a,b,c,d,e}}
[2]:{a,b,c,d,e, store{a,b,c,d,e}}
[3]:{a,b,c,d,e, store{a,b,c,d,e}}
}
You can use the opposite:
$stores = Store::with('offers')->all();
So you can use:
$store->offers[i]->somthing;
Because the array will look like this:
stores {
[0]:{a,b,c,d,e, offers{
[0]:{a,b,c,d,e}
[1]:{a,b,c,d,e}
[2]:{a,b,c,d,e}
[3]:{a,b,c,d,e}
}}
[1]:{a,b,c,d,e, offers{
[0]:{a,b,c,d,e}
[1]:{a,b,c,d,e}
[2]:{a,b,c,d,e}
[3]:{a,b,c,d,e}
}}
[2]:{a,b,c,d,e, offers{
[0]:{a,b,c,d,e}
[1]:{a,b,c,d,e}
[2]:{a,b,c,d,e}
[3]:{a,b,c,d,e}
}}
}

Cakephp $scaffold doesn't display associated models

Help me please, I'm desperate!
I have this schema:
CREATE TABLE `baskets` (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`name` varchar(20)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
CREATE TABLE `apples` (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`basket_id` INT UNSIGNED ,`type` int,
FOREIGN KEY (basket_id) references baskets(id)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
These are my models:
class Apple extends AppModel {
public $name = 'Apple';
public $recursive = 2;
public $belongsTo = 'Basket';
}
class Basket extends AppModel {
public $name = 'Basket';
public $recursive = 2;
public $hasMany = 'Apple';
}
However, when I call the variable $scaffold on either one of them, and try to update or create a new apple; the little drop-down thingy where I would normally choose a basket is empty.
What can I do???? I'm gonna get fired if I don't solve this by monday :(

cakePHP hasOne empty

I'm new to cakePHP and I have a Problem with Relations.
My Model Looks like this:
<?
class Operator extends AppModel
{
var $name = 'Operator';
var $hasOne = array('Contact','Adress','Land');
}
?>
and my Table Looks like this:
CREATE TABLE `operators` (
`id` varchar(45) NOT NULL,
`name` int(11) NOT NULL,
`adress_id` int(11) NOT NULL,
`land_id` int(11) NOT NULL,
`contact_id` int(11) NOT NULL,
`operator_category_id` int(11) NOT NULL,
`legal_form` varchar(45) DEFAULT NULL,
`affidavit` varchar(45) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `id_UNIQUE` (`id`),
KEY `operator_category_fk_idx` (`operator_category_id`),
KEY `contact_id_idx` (`contact_id`),
KEY `adress_id_idx` (`adress_id`),
KEY `land_id_idx` (`land_id`),
CONSTRAINT `adress_id` FOREIGN KEY (`adress_id`) REFERENCES `adresses` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `contact_id` FOREIGN KEY (`contact_id`) REFERENCES `contacts` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `land_id` FOREIGN KEY (`land_id`) REFERENCES `lands` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
CONSTRAINT `operator_category_fk` FOREIGN KEY (`operator_category_id`) REFERENCES `operator_category` (`id`) ON DELETE NO ACTION ON UPDATE NO ACTION
)
but cakePHP doesn't Show any data from e.g. contacts
what am I doing wrong?
hasOne relationship only serves if you have 1 to 1 association to be able to access the associated model within the model which has no foreign key.
belongsTo
In your case, you want to use belongsTo, belongsTo is used as soon as you have a foreign key in your model pointing to another model, your Operator model has foreign keys pointing to Contact, Address, Land which are resp. contact_id, address_id and land_id.
Since the contact_id field is in the Operator model, then the Operator model belongsTo the Contact model.
class Operator extends AppModel {
public $belongsTo = array('Contact') ;
}
When fetching an Operator entry, you will get something like:
Array(
[Operator] => Array
(
[id] => 42,
[name] => 'operator',
[contact_id] => 33
)
[Contact] => Array
(
[id] => 33,
[name] => 'contact'
)
)
hasOne and hasMany
Since your Operator belongsTo Contact, your Contact hasOne OR hasMany Operator. Let's illustrate the difference between the two:
Each of your Contact entry is associated to only one Operator, you could add a operator_id foreign key inside your Contact model but it would be redundant because you already have the association using contact_id in Operator. Thus, what you do is create a hasOne association inside the Contact model, so Contact hasOne Operator.
class Contact extends AppModel {
public $hasOne = array('Operator') ;
}
Again, when fetching a Contact entry you will get:
Array(
[Contact] => Array
(
[id] => 33,
[name] => 'contact'
)
[Operator] => Array
(
[id] => 42,
[name] => 'operator',
[contact_id] => 33
)
)
Note that this is the same as when fecthing an Operator with a belongsTo Contact. The difference between hasOne and belongsTo is mainly which model has a foreign key pointing to the other.
Eah of your Contact entry is associated to multiple (many) Operator, in this case your Contact hasMany Operator.
class Contact extends AppModel {
public $hasMany = array('Operator') ;
}
Again, the output of $this->Contact->find():
Array(
[Contact] => Array
(
[id] => 33,
[name] => 'contact'
)
[Operator] => Array
(
[0] => Array
(
[id] => 42,
[name] => 'operator 42',
[contact_id] => 33
)
[0] => Array
(
[id] => 47,
[name] => 'operator 47',
[contact_id] => 33
)
)
)
hasAndBelongsToMany
Now, assume one Operator can have multiple Contact, and one Contact can serve multiple Operator. In this case, you need an extra table (which should be called operators_contacts if you follow CakePHP naming convention), with two fields operator_id and contact_id:
class Contact extends AppModel {
public $hasAndBelongsToMany = array('Operator') ;
}
class Operator extends AppModel {
public $hasAndBelongsToMany = array('Contact') ;
}
The output of $this->Contact->find('all') will be similar to the one using the hasMany relationship, main difference would be that their is no operator_id or contact_id field in the model.
Full example
Let's assume I have the following models: Company, Employee, Address, and the following constraints:
A Company has various Employee but only one CEO which is part of its Employee
A Company has one Address, and there is at most one company at each Address
You have the following tables:
CREATE TABLE companies (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
ceo_id INTEGER REFERENCES employees (id)
) ;
CREATE TABLE employees (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
company_id INTEGER REFERENCES companies (id)
) ;
CREATE TABLE addresses (
id INTEGER AUTO_INCREMENT PRIMARY KEY,
company_id INTEGER REFERENCES companies (id)
) ;
Note that in this case, you cannot the table with all the constraints because you have a loop between companies and employees, you need to add the constraints after.
And the following CakePHP models:
class Company extends AppModel {
/* A Company has many Employee. */
public $hasMany = array('Employee') ;
/* A Company has one CEO, but the key is in the Company model,
so it is a belongsTo relationship. */
public $belongsTo = array(
'CEO' => array(
'className' => 'Employee', // It's an Employee
'foreignKey' => 'ceo_id'
)
) ;
/* A Company has one address. */
public $hasOne = array('Address') ;
} ;
class Employee extends AppModel {
/* An Employee belongs to a Company. */
public $belongsTo = array('Company') ;
} ;
class Address extends AppModel {
/* An address belongs to a Company. */
public $belongsTo = array('Company') ;
} ;
Notice I have put a company_id foreign key in the Address model, so Address belongsTo Company and Company hasOne Address, I could have put a address_id inside the Company model and I would have had Company belongsTo Address and Address hasOne Company. I made an arbitrary choice here, in a real application you should think of which of the two above cases is the most meaningful.
Also note that the way I defined my model, there is nothing that prevent an Employee to be the CEO of a Company without being one of its own Employee.
You can find more information in the CakePHP 2.x Book.
you should you use $belongsTo instead of hasOne relationship for this purpose.
public $belongsTo = array(
'Contact' => array(
'className' => 'Contact',
'foreignKey' => 'contact_id', // check this
'conditions' => '',
'fields' => '',
'order' => '',
'counterCache' => true
)
);
Operator belongsTo xyz, not hasOne
hasOne and belongsTo associations are similar, but not the same. To illustrate the difference consider the following schema:
users
id (int)
name (string)
addresses
id (int)
user_id (int)
is_home (bool)
... other fields ...
With User and Address models. Let's assume that there is only one "home" address per user, but a user is able to create multiple address records.
belongsTo means the table has a foreign key field in it (addresses.user_id) has* associations mean the other table has the foreign key (there is no users.address_id, User does not belongTo Address).
In summary, these statements would describe the model associations:
User hasMany Addresses
User hasOne home Address
Address belongsTo User

CakePHP belongsTo not working

I have a couple of tables in my database:
CREATE TABLE IF NOT EXISTS `team_players` (
`artificialid` int(11) NOT NULL AUTO_INCREMENT,
`team_name_id` int(11) NOT NULL,
`player_id` int(11) NOT NULL,
PRIMARY KEY (`artificialid`)
)
And:
CREATE TABLE IF NOT EXISTS `team_names` (
`id` int(11) NOT NULL,
`name` varchar(50) NOT NULL
)
And I have set up my TeamPlayer model like this:
class TeamPlayerModel extends AppModel {
public $belongsTo = array(
'TeamName',
'Player'
)
);
}
The players table has a field id, but neither of the associations are working.
I don't really understand why it isn't working, I have something like this working in another app, pretty much line for line.
Thanks in advance, I really appreciate any help.
You can try to setup your model:
TeamPlayer.php:
class TeamPlayer extends AppModel {
public $hasMany = array(
'PlayerName'
)
) ;
}
TeamName.php:
class TeamName extends AppModel {
public $belognsTo = array(
'TeamPlayer'
)
) ;
}

cakephp messaging system scheme/model/controller Best Practice?

I am trying to implement a simple messaging system for my users.
I am not an habtm expert, so any assistance will be appreciated!
Here is what I got this far, please make any suggestions!! (cake1.3)
CREATE TABLE IF NOT EXISTS `app_messages` (
`id` int(8) unsigned NOT NULL AUTO_INCREMENT,
`from_user_id` int(8) unsigned DEFAULT NULL,
`title` varchar(255) DEFAULT NULL,
`body` text,
`created` datetime DEFAULT NULL,
PRIMARY KEY (`id`)
);
CREATE TABLE IF NOT EXISTS `app_messages_users` (
`id` int(8) unsigned NOT NULL,
`message_id` int(8) unsigned NOT NULL,
`to_user_id` int(8) unsigned NOT NULL,
`read` tinyint(1) DEFAULT '0',
`replied` tinyint(1) NOT NULL DEFAULT '0',
`trash` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
);
Message.php
var $hasAndBelongsToMany = array(
'User' =>
array(
'className' => 'User',
'joinTable' => 'messages_users',
'foreignKey' => 'message_id',
'associationForeignKey' => 'to_user_id',
'unique' => true
)
);
User.php
var $hasMany = array(
'Message' => array(
'className' => 'message',
'foreignKey' => 'from_user_id',
'dependent' => true
)
So now, my question is, Am I doing this correct?
How do I go around creating a SEND MESSAGE function, to insert correct values to both tables?
I am a total noob regarding HABTM relationships, but I am trying to learn. Have spent several hours reading about it on the web, but still, need to ask if I am going the right way.
Thanks for your time!!
-Tom
Given that your join table has additional fields (read, replied, trash), you should not use an HABTM relationship. If you do, you won't be able to access them from your application. So instead, you should configure two hasMany associations through a new model for the join table. It is well explained here in the CookBook.
So you'll end up having something like this:
// User.php
class User extends AppModel {
public $hasMany = array(
'MessageUser' /* you can call this 'SentMessage' or something that makes more sense */
);
}
// Message.php
class Message extends AppModel {
public $hasMany = array(
'MessageUser'
);
}
// MessageUser.php
class MessageUser extends AppModel {
public $belongsTo = array(
'User', 'Message'
);
}