I'm having trouble with relationships between tables with CakePHP 3.x. I am struggling to make realcionamento between tables with more than 2 levels realcionamento .
I will introduce the code of relationships and also the query I'm doing to make it more clear what behavior relations:
class CircuitosTable extends Table{
public function initialize(array $config)
{
$this->table('circuitos');
$this->addAssociations([
'belongsTo' => [
'Planostreinos' => [
'foreignKey' => 'id_plano_treino',
'joinType' => 'INNER',
'bindingKey' => 'id'
]
],
'hasMany' => [
'Atividades' => [
'className' => 'Atividades',
'foreignKey' => 'id_circuito',
'bindingKey' => 'id',
'joinType' => 'INNER',
'dependent' => false,
'cascadeCallbacks' => false,
'propertyName' => '_atividades'
],
]
]);
}
}
class AtividadesTable extends Table{
public function initialize(array $config)
{
$this->table('atividades');
$this->addAssociations([
'belongsTo' => [
'Tiposexercicios' => [
'foreignKey' => 'id_tipo_exercicio',
'joinType' => 'INNER',
'bindingKey' => 'id'
],
'Circuitos' => [
'foreignKey' => 'id_circuito',
'joinType' => 'INNER',
'bindingKey' => 'id'
]
]
]);
}
}
class TiposexerciciosTable extends Table{
public function initialize(array $config)
{
$this->table('tipos_exercicios');
$this->addAssociations([
'hasMany' => [
'Atividades' => [
'className' => 'Atividades',
'foreignKey' => 'id_tipo_exercicio',
'bindingKey' => 'id',
'joinType' => 'INNER',
'dependent' => false,
'cascadeCallbacks' => false,
'propertyName' => '_atividades'
]
]
]);
}
}
I'm doing this query:
$circuitos = $CircuitosTable->find('all', [
'conditions' => ['id_plano_treino' => $idPlano],
'contain' => ['Atividades'],
'joins' => [
[
"table" => "Tiposexercicios",
"alias" => "TipoExercicio",
"type" => "INNER",
"conditions" => ["TipoExercicio.id = Atividades.id_tipo_exericio"]
]
]
]);
The expected result:
Select * From Circuitos
JOIN Atividades on Atividades.id_circuito = Circuitos.id
JOIN Tiposexercicios on Tiposexercicios.id = Atividades.id_tipo_exercicio
The submitted query does not work properly. How should do?
hasMany associations are being queried using a separate query, so using contain() to include Atividades will not include the table in the main query.
It looks like you want to filter by associated data, so you can save yourself a lot of trouble by simply using Query::matching() or Query::innerJoinWith(). This will join in associated tables as needed.
The following would create a query similar to what you seem to be looking for:
$circuitos = $CircuitosTable
->find()
->innerJoinWith('Atividades.Tiposexercicios')
->where([
'Circuitos.id_plano_treino' => $idPlano
])
->group('Circuitos.id');
SELECT
-- ...
FROM
circuitos Circuitos
INNER JOIN atividades Atividades
ON Circuitos.id = (Atividades.id_circuito)
INNER JOIN tipos_exercicios Tiposexercicios
ON Tiposexercicios.id = (Atividades.id_tipo_exercicio)
WHERE
Circuitos.id_plano_treino = 123
GROUP BY
Circuitos.id
The grouping is required as you may otherwise receive duplicate results.
See also Cookbook > Database Access & ORM > Retrieving Data & Results Sets > Filtering by Associated Data
On a side note, the table key in the join definition is ment to hold the actual database table name, which, according to your table classes, is tipos_exercicios!
Related
Dears,
I have 2 fields (solicitante and resolvedor) related to the ID field of the users table, how can I display both in the index.ctp?
I use this code below, but I do not know how to differentiate the 2 fields, I put only one field because when I put the two, the information repeats itself
My index.ctp
<?= $chamado->has('user') ? $this->Html->link($chamado->user->nome, ['controller' => 'Users', 'action' => 'view', $chamado->user->id]) : '' ?>
My Controller
public function index()
{
$this->paginate = [
'contain' => ['Users']
];
$chamados = $this->paginate($this->Chamados);
$this->set(compact('chamados'));
$this->set('_serialize', ['chamados']);
}
My Model
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('chamados');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'solicitante',
'joinType' => 'INNER'
]);
}
follows the screen image:
Index.ctp screen
you can differentiate this way:
$this->belongsTo('Solicitantes', [
'className' => 'Users'
'foreignKey' => 'solicitante',
'joinType' => 'INNER'
]);
$this->belongsTo('Resolvedores', [
'className' => 'Users'
'foreignKey' => 'resolvedor',
'joinType' => 'INNER'
]);
and in your view
<?= $chamado->has('solicitante') ? $this->Html->link($chamado->solicitante->nome, ['controller' => 'Users', 'action' => 'view', $chamado->solicitante->id]) : '' ?>
<?= $chamado->has('resolvedor') ? $this->Html->link($chamado->resolvedor->nome, ['controller' => 'Users', 'action' => 'view', $chamado->resolvedor->id]) : '' ?>
see the manual
https://book.cakephp.org/3.0/en/orm/associations.html#belongsto-associations
Events hasMany TicketTypes
TicketTypes belogsTo Events
I am trying to retrieve all events with associated ticket types:
$query= $this->Events
->find()
->select(['id', 'name'])
->autoFields(false)
->contain(['TicketTypes' => function($q) {
return $q->select(['TicketTypes.id', 'TicketTypes.name']); }])
;
SQL query generated:
SELECT Events.id AS `Events__id`, Events.name AS `Events__name` FROM events Events
But what I expected is:
SELECT Events.id AS `Events__id`, Events.name AS `Events__name`, TicketTypes.id AS `TicketTypes__id`, TicketTypes.name AS `TicketTypes__name` FROM events Events LEFT JOIN ticket_types TicketTypes ON Events.id = (TicketTypes.event_id)
This is how my models are configured:
class EventsTable extends Table
{
public function initialize(array $config)
{
$this->displayField('name');
$this->addAssociations([
'hasMany'=> ['TicketTypes']
]);
}
}
class TicketTypesTable extends Table
{
public function initialize(array $config)
{
$this->displayField('name');
$this->addAssociations([
'belongsTo' => ['Events']
]);
}
}
Here is the result of debugging my find query:
object(Cake\ORM\Query) {
'(help)' => 'This is a Query object, to get the results execute or iterate it.',
'sql' => 'SELECT Events.id AS `Events__id`, Events.name AS `Events__name` FROM events Events',
'params' => [],
'defaultTypes' => [
'Events.id' => 'integer',
'id' => 'integer',
'Events.name' => 'string',
'name' => 'string',
'Events.datetime_start' => 'datetime',
'datetime_start' => 'datetime',
'Events.datetime_end' => 'datetime',
'datetime_end' => 'datetime',
'Events.created' => 'datetime',
'created' => 'datetime',
'Events.modified' => 'datetime',
'modified' => 'datetime',
'Events.slug' => 'string',
'slug' => 'string',
'TicketTypes.id' => 'integer',
'TicketTypes.event_id' => 'integer',
'event_id' => 'integer',
'TicketTypes.name' => 'string',
'TicketTypes.description' => 'text'
],
'decorators' => (int) 0,
'executed' => false,
'hydrate' => true,
'buffered' => true,
'formatters' => (int) 0,
'mapReducers' => (int) 0,
'contain' => [
'TicketTypes' => [
'queryBuilder' => object(Closure) {
}
]
],
'matching' => [],
'extraOptions' => [],
'repository' => object(App\Model\Table\EventsTable) {
'registryAlias' => 'Events',
'table' => 'events',
'alias' => 'Events',
'entityClass' => 'App\Model\Entity\Event',
'associations' => [
(int) 0 => 'tickettypes'
],
'behaviors' => [],
'defaultConnection' => 'default',
'connectionName' => 'default'
}
}
And here is the result of debugging $query->all():
object(Cake\ORM\ResultSet) {
'items' => [
(int) 0 => object(App\Model\Entity\Event) {
'id' => (int) 101,
'name' => 'qwertyuiop',
'ticket_types' => [],
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Events'
},
...
As you can see in this line 'ticket_types' => [] ticket types are not being returned by the query.
What can I do to retrieve TicketTypes data?
Thanks.
hasMany associations are being retrieved in a separate query
Your assumption about how the CakePHP ORM retrieves associated data is incorrect.
Unlike hasOne and belongsTo assocaitions which are using joins in the main query, hasMany and belongsToMany asociated data is being retrieved in a separate query, which is being filtered using foreign key values collected from the main query, which in your case would be the Events.id column values.
Look at the rest of the SQL log, you shound find a query similar to
SELECT
TicketTypes.id AS `TicketTypes__id`, ...
FROM
ticket_types TicketTypes
WHERE
TicketTypes.event_id IN (1,2,3, ...)
The results of that query are being stitched together with the main results, and returned in a single result set.
Foreign keys need to be selected
A second problem is that your containments select() call is missing the foreign key column (TicketTypes.event_id), which is required, as without it, the ORM cannot stitch the results together, and thus the ticket types will not be present in the results.
Quote from the docs:
When you limit the fields that are fetched from an association, you
must ensure that the foreign key columns are selected. Failing to
select foreign key fields will cause associated data to not be present
in the final result.
See also
Cookbook > Database Access & ORM > Retrieving Associated Data
Cookbook > Database Access & ORM > Query Builder > Passing Conditions to Contain
I have 2 tables in my cakephp 3 app - Items and Colors. An Item can have multiple Primary Colors and Secondary Colors as well.
So, I have created 2 junction tables - items_primary_colors and items_secondary_colors.
Both have the same schema - item_id and color_id (Joining the Items and Colors tables)
I am not sure how to specify these relationships in TableModel and how to format the form data to save both types of colors.
My ItemTable.php code has -
$this->belongsToMany('Colors', [
'foreignKey' => 'item_id',
'targetForeignKey' => 'color_id',
'joinTable' => 'items_primary_colors'
]);
$this->belongsToMany('Colors', [
'foreignKey' => 'item_id',
'targetForeignKey' => 'color_id',
'joinTable' => 'items_secondary_colors'
]);
And, I am formatting the form data this way -
[primary_colors] => Array
(
[_ids] => Array
(
[0] => 2
)
)
[secondary_colors] => Array
(
[_ids] => Array
(
[0] => 3
)
)
Its not working. How should I deal with this?
You need to give the belongsToMany relationships different names. Try
$this->belongsToMany('PrimaryColors', [
'className' => 'Colors',
'foreignKey' => 'item_id',
'targetForeignKey' => 'color_id',
'joinTable' => 'items_primary_colors'
]);
$this->belongsToMany('SecondaryColors', [
'className' => 'Colors',
'foreignKey' => 'item_id',
'targetForeignKey' => 'color_id',
'joinTable' => 'items_secondary_colors'
]);
I'm building an App with CakePHP 3.0. I have a FactsTable and an InterferencesTable. Interferences has this fields:
[id][changed_fact_id][influenced_fact_id][trend][modified_by][modified_at][created_by][created_at]
associations look like this:
FactsTable:
`$this->belongsToMany('InfluencedFacts', [
'through' => 'Interferences',
'className' => 'Facts',
'foreignKey' => 'changed_fact_id'
]);
$this->belongsToMany('ChangedFacts', [
'through' => 'Interferences',
'className' => 'Facts',
'foreignKey' => 'influenced_fact_id'
]);`
InterferencesTable:
`$this->belongsTo('ChangedFacts', [
'className' => 'Facts',
'foreignKey' => 'influenced_fact_id',
]);
$this->belongsTo('InfluencedFacts', [
'className' => 'Facts',
'foreignKey' => 'changed_fact_id',
]);`
I baked my controllers and views. Saving a new Fact works fine but the association isn't saved. I tried to save the association manually but it doesn't work, too.
If i made a mistake in model-association, please tell me ;)
saving code from FactsController.php:
`public function add() {
$fact = $this->Facts->newEntity($this->request->data);
if ($this->request->is('post')) {
if ($this->Facts->save($fact)) {
$id = $fact->get('id');
$this->Flash->success('The fact has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The fact could not be saved. Please, try again.');
}
}
$aggregates = $this->Facts->Aggregates->find('list');
$plants = $this->Facts->Plants->find('list');
$influencedFacts = $this->Facts->InfluencedFacts->find('list');
$this->set(compact('fact', 'aggregates', 'plants', 'influencedFacts'));
}`
Fact Entity:
`protected $_accessible = [
'name' => true,
'short' => true,
'description' => true,
'modified_by' => true,
'modified_at' => true,
'created_by' => true,
'created_at' => true,
'aggregates' => true,
'plants' => true,
];`
I want to make this query (the $ownerQuery is the relevant part here):
$searchQuery = $this->Tickets
->find('byStatus',['status' =>$status])
->find('byTitle',['queries' => $queries]);
$ownerQuery = $searchQuery
->innerJoinWith(
'Projects.ProjectsTickets', function($q) use( &$userId){
return $q->where(['Tickets.id' => 'ProjectsTickets.ticket_id'])
->where(['Projects.id' => 'ProjectsTickets.project_id'])
->where(['Projects.user_id' => $userId]);
}
);
Or something that achieves the same effect, however every variation of this I have tried ends up with a not associated with error.
Here are my associations:
ProjectsTicketsTable:
class ProjectsTicketsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects_tickets');
$this->displayField('project_id');
$this->primaryKey(['project_id', 'ticket_id']);
//$this->belongsTo('Projects', [
// 'foreignKey' => 'project_id',
// 'joinType' => 'INNER'
//]);
//$this->belongsTo('Tickets', [
// 'foreignKey' => 'ticket_id',
// 'joinType' => 'INNER'
//]);
$this->belongsTo('Projects');
$this->belongsTo('Users');
}
}
ProjectsTable:
class ProjectsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('projects');
$this->displayField('title');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Tags', [
'foreignKey' => 'tag_id',
'joinType' => 'INNER'
]);
//$this->belongsToMany('Tickets', [
// 'foreignKey' => 'project_id',
// 'targetForeignKey' => 'ticket_id',
// 'joinTable' => 'projects_tickets'
//]);
$this->belongsToMany('Tickets', [
'through' => 'ProjectTickets',
]);
$this->belongsToMany('Users', [
'foreignKey' => 'project_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'projects_users'
]);
}
}
TicketsTable:
class TicketsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('tickets');
$this->displayField('title');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
//$this->belongsToMany('Projects', [
// 'foreignKey' => 'ticket_id',
// 'targetForeignKey' => 'project_id',
// 'joinTable' => 'projects_tickets'
//]);
$this->belongsToMany('Projects', [
'through' => 'ProjectTickets',
]);
$this->belongsToMany('Comments', [
'foreignKey' => 'ticket_id',
'targetForeignKey' => 'comment_id',
'joinTable' => 'tickets_comments'
]);
$this->belongsToMany('Users', [
'foreignKey' => 'ticket_id',
'targetForeignKey' => 'user_id',
'joinTable' => 'tickets_users'
]);
}
}
Is there something I am doing wrong with the associations or the query that I could fix?
EDIT: By changing the associations like the link NDM provided I did manage to get rid of the 'no association errors' however all queries trying to combine columns from tables always result in 0 results.
For example this search that should return all tickets (because all of them are in the ProjectsTickets table) is returning nothing:
$ownerQuery = $searchQuery
->innerJoinWith(
'Projects.ProjectsTickets', function($q) use( &$userId){
return $q->where(['Tickets.id' => 'ProjectsTickets.ticket_id']);
//->where(['Projects.id' => 'ProjectsTickets.project_id'])
//->where(['Projects.user_id' => $userId]);
}
);
If I however uncomment only the user line it is correctly displaying all results.
EDIT 2: While the association workaround works the matching one doesnt wich proves a bit problematic. For example if I want to do this query without having to make an association between Tickets and ProjectsUsers:
$adminQuery = $searchQuery
->distinct()
->matching('ProjectsUsers')
->where([
'Tickets.id = ProjectsTickets.ticket_id',
'ProjectsTickets.project_id = ProjectsUsers.project_id',
'ProjectsUsers.user_id' => $userId,
'ProjectsUsers.role = Admin'
]);
This is still resulting in Tickets is not associated with ProjectsUsers