When getting a single record, and associated records (two levels down), is there any way we can pass condition to the contained records in CakePHP 3? In this case, for example:
$user = $this->Users->get($this->Auth->user('id'), [
'contain' => [
'Articles' => ['Comments']
]
]);
If tried a bunch of ways with callbacks, but that only seems to work on the first level. Like so, for example:
$user = $this->Users->get($this->Auth->user('id'), [
'contain' => [
'Articles' => function ($q) {
return $q->where(['Articles.published' => true]);
}
]
]);
Any thoughts?
you should be able to do:
$user = $this->Users->get($this->Auth->user('id'), [
'contain' => [
'Articles' => function ($q) {
return $q->where(['Articles.published' => true]);
},
'Articles.Comments' => function ($q) {
return $q->where(['Comments.deleted' => false]);
}
]
]);
or
$user = $this->Users->get($this->Auth->user('id'), [
'contain' => [
'Articles' => function ($q) {
$q->contain([
'Comments' => function ($q) {
return $q->where(['Comments.deleted' => false]);
}
]);
return $q->where(['Articles.published' => true]);
}
]
]);
#arilia's options should work fine. My preference is
$user = $this->Users->get($this->Auth->user('id'), [
'contain' => [
'Articles' => [
'queryBuilder' => function ($q) {
return $q->where(['Articles.published' => true]);
},
'Comments' => [
'querybuilder' => function ($q) {
return $q->where(['Comments.deleted' => false]);
}
],
],
],
]);
(Tried to post this as a comment, instead of a separate answer, but it won't format the code properly there.)
Related
I am trying to list public status posts and friends posts
getting friends post
$friendsPosts= $this->Posts->find('all')
->contain(['Users', 'Languages', 'PostStates'])
->matching('Users.Dusers', function ($q) {
return $q->where(['Dusers.id' => $this->Auth->user('id')]);
});
getting public post
$posts= $this->Posts->find('all')
->where(['Posts.post_state_id' => 3])
->contain(['Users', 'Languages', 'PostStates']);
$posts->union($friendsPosts);
dd($posts->toArray());
error message: The used SELECT statements have a different number of columns
Here is solution
$posts = $this->Posts->find()
->where(['Posts.post_state_id' => 3])
->contain(['Users', 'PostStates', 'Languages', 'Tags', 'Translations' => ['Users', 'Languages', 'conditions' => ['post_state_id' => 3]]]);
$friendsPosts = $this->Posts->find('all')
->where(['Posts.post_state_id' => 2])
->contain(['PostStates', 'Languages', 'Users', 'Tags', 'Translations' => ['Users', 'Languages', 'conditions' => ['post_state_id' => 3]]])
->innerJoinWith('Users.Dusers', function ($q) {
return $q->where(['Dusers.id' => $this->Auth->user('id'), 'UsersUsers.status' => 1])->select($this->Posts->Users);
});
$posts->union($friendsPosts);
I want to get count of tenancies based on differnet times
$start_date = $end_date = $end_date1 = new \DateTime('first day of last month');
$start_date = $start_date->format('Y-m-01 00:00:00');
$end_date = $end_date->format('Y-m-t 23:59:59');
$end_date1 = $end_date1->format('Y-m-'. date('d') .' 23:59:59');
$start_date2 = $end_date2 = new \DateTime('now');
$start_date2 = $start_date2->format('Y-m-01 00:00:00');
$end_date2 = $end_date2->format('Y-m-d 23:59:59');
$tenancyDetails = $this
->find()
->select([
'total_last_month' => $this->find()->func()->count('Tenancy.id'),
'count_last_month' => $this->find()->func()->count('Tenancy1.id'),
'count_curr_month' => $this->find()->func()->count('Tenancy2.id'),
'Tenancy.created', 'Tenancy1.created', 'Tenancy2.created',
])
->leftJoin(['Tenancy1' => 'tenancy'],[
'Tenancy1.active' => 1,
'Tenancy1.stage !=' => 1,
'Tenancy1.company_id' => $id,
])
->leftJoin(['Tenancy2' => 'tenancy'],[
'Tenancy2.active' => 1,
'Tenancy2.stage !=' => 1,
'Tenancy2.company_id' => $id,
])
->where([
'Tenancy.active' => 1,
'Tenancy.stage !=' => 1,
'Tenancy.company_id' => $id,
])
->where(function ($exp, $q) use ($start_date, $end_date) {
return $exp->between('Tenancy.created', $start_date, $end_date);
})
->where(function ($exp, $q) use ($start_date, $end_date1) {
return $exp->between('Tenancy1.created', $start_date, $end_date1);
})
->where(function ($exp, $q) use ($start_date2, $end_date2) {
return $exp->between('Tenancy2.created', $start_date2, $end_date2);
})->toArray();
Here is what I get. The number of counts are the same. Eventhough the datesare different
[
(int) 0 => object(App\Model\Entity\Tenancy) {
'total_last_month' => (int) 4590,
'count_last_month' => (int) 4590,
'count_curr_month' => (int) 4590,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2016-03-01T10:57:21+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'Tenancy1' => [
'created' => '2016-03-01 10:57:21'
],
'Tenancy2' => [
'created' => '2016-04-04 10:59:42'
],
'[new]' => false,
'[accessible]' => [
'*' => true
],
'[dirty]' => [],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Tenancy'
}
]
Even though I have written the query this way but still the same problem.
$query = $tenancies->find();
$query->select([
'total_last_month' => $query->func()->count('Tenancy.id'),
])
->where([
'Tenancy.active' => 1,
'Tenancy.stage !=' => 1,
'Tenancy.company_id' => $id,
])
->where(function ($exp) use ($start_date, $end_date) {
return $exp->between('Tenancy.created', $start_date, $end_date);
});
$query->select([
'count_last_month' => $query->func()->count('Tenancy1.id'),
])
->leftJoin(['Tenancy1' => 'tenancy'],[
'Tenancy1.company_id' => $id,
])
->where([
'Tenancy1.active' => 1,
'Tenancy1.stage !=' => 1,
])
->where(function ($exp) use ($start_date, $end_date1) {
return $exp->between('Tenancy1.created', $start_date, $end_date1);
});
I can see the printed query which is correct.
After looking at all the examples in the documentation it looks like you need to instantiate the ORM query builder instance before using the func() helpers.
http://book.cakephp.org/3.0/en/orm/query-builder.html#using-sql-functions
In theory $this->func() should work the same if the return is simply a string. but maybe using $this->func() results in a disconnect between the query instance you are trying to build and an unbound new instance?
try:
$tenancyDetails = $this>find();
$tenancyDetails->select([
'total_last_month' => $tenancyDetails->find()->func()->count('Tenancy.id'),
'count_last_month' => $tenancyDetails->find()->func()->count('Tenancy1.id'),
'count_curr_month' => $tenancyDetails->find()->func()->count('Tenancy2.id'),
'Tenancy.created',
'Tenancy1.created',
'Tenancy2.created',
])......
Edit
I checked the code for the count function in /ORM/query.php file.
If no values are set cake will perform the count() query on the call $this->func->count(). The PHP compiler must be reading and therefore executing the count query before the query has been built, meaning before your conditions have been set. That's why you you need to instantiate the class first to stop the count query executing prematurely.
Currently I'm trying to test a controller which uses the authentication component to retrieve the user id. Since I'm fairly new to unit/integration testing, I have no idea how to make this working. Moreover, all the content that I could find for this particular problem is written for Cakephp version 2.
Controller function:
public function favorite(){
// Particular line where the problem is occurring:
$userId = $this->Auth->User()['id'];
// Get all favorite pictures of the user
$query = $this->UsersPictures->getFavoritePictures($userId);
$results = $query->all();
// Replace the link in the result set by a presigned url of Amazon
foreach($results as $result){
$result->picture->link = $this->Aws->getTokenizedItem($result->picture->link);
}
$this->set([
'success' => true,
'data' => [
'pictures' => $results
],
'_serialize' => ['success', 'data']
]);
}
Integration test:
public function testFavoriteShouldPass(){
$this->configRequest([
'headers' => [
'Accept' => 'application/json'
]
]);
$this->get('api/pictures/favorite/1.json');
$expected = [
'success' => true,
'data' => [
[
'user_id' => 1,
'picture_id' => 1,
'created' => '2016-04-03T20:35:40+0000',
'modified' => '2016-04-03T20:35:40+0000',
'picture' => [
'id' => 1,
'album_id' => 1,
'description' => 'Lorem ipsum dolor sit amet',
'link' => 'test',
'favorite' => true,
'created' => null,
'modified' => null,
'cover_photo' => true
]
]
]
];
$this->assertEquals($expected, $response);
}
My question is how can I insert a user with a default id of 1 for $this->Auth->User()['id']. I saw in other questions that I need to use something that looks like this:
$this->_controller->Auth
->staticExpects($this->any())
->method('user')
->will($this->returnValue([
'id' => 1,
'username' => 'admin',
'created' => '2013-05-08 00:00:00',
'modified' => '2013-05-08 00:00:00',
'email' => 'me#me.com',
]));
However, I read that staticExpects is deprecated from phpunit version 3.8 (I'm using 5.2). How should I mock this?
You can set the session data in your integration tests using $this->session(), for example (taken from cakephp book):
// Set session data
$this->session([
'Auth' => [
'User' => [
'id' => 1,
'username' => 'testing',
// other keys.
]
]
]);
You can actually find it in their docs: http://book.cakephp.org/3.0/en/development/testing.html#controller-integration-testing
You can find it in the section
Testing Actions That Require Authentication
If you want to use the same session data on each test you can use the setUp method of your integration test class, like so:
public function setUp()
{
parent::setUp();
// Set session data
$this->session([
'Auth' => [
'User' => [
'id' => 1,
'username' => 'testing',
// other keys.
]
]
]);
}
With thanks to azahar :), this worked for me:
public function controllerSpy($event)
{
parent::controllerSpy($event);
if (isset($this->_controller)) {
$this->_controller->Auth->setUser([
'id' => 1,
'username' => 'testtesttesttest',
'email' => 'john#doe.com',
'first_name' => 'John',
'last_name' => 'Doe',
'uuid' => 'wepoewoweo-ew-ewewpoeopw',
'sign_in_count' => 1,
'current_sign_in_ip' => '127.0.0.1',
'active' => true
]);
}
}
I have a query as below but I need only certain fields not all the fields. I have set the autoFields to false but the query still returns all the fields.
$tenancies = $this
->find('all')
->autoFields(false)
->select([
'Tenancy.id', 'Tenancy.created', 'Tenancy.stage',
'Properties.id', 'Properties.address1', 'Properties.postcode',
'Tenants.stage',
])
->contain('Properties', function(\Cake\ORM\Query $query) {
return $query->where([
'Properties.active' => 1
]);
})
->contain(['Tenants'])
->leftJoinWith('Tenants', function(\Cake\ORM\Query $query) {
return $query
->autoFields(false)
->select([
'id', 'stage'
])
->where([
'Tenants.active' => 1
]);
})
->where([
'Tenancy.active' => 1,
$conditions
])
->order([
'Tenancy.created' => 'DESC',
'Tenants.tenancy_id'
])
->group(['Properties.id'])
->autoFields(false);
return $tenancies;
Prints =>
object(App\Model\Entity\Tenancy) {
'id' => (int) 3934,
'created' => object(Cake\I18n\FrozenTime) {
'time' => '2016-03-20T18:25:20+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'stage' => (int) 2,
'tenants' => [
(int) 0 => object(Cake\ORM\Entity) {
'id' => (int) 8922,
'user_id' => (int) 56456,
'tenancy_id' => (int) 3934,
'needs_guarantor' => true,
'guarantor_id' => null,
'holding_fee' => (float) 0,
...
...
Note: Using matching or leftJoinWith I can select the fields that I need but it doesn't work on contain
The main answer is you need to set the associations correctly. Once the associations are set correctly you can print it as simple as this.
return $this->find()
->select([
'Property.id', 'Property.company_id', 'Property.address1', 'Property.address2',
'Property.address3','Property.postcode',
])
->contain([
'Tenancies' => function($q) {
return $q
->select([
'Tenancies.id','Tenancies.property_id','Tenancies.created',
'Tenancies.stage',
])
->contain([
'Tenants' => function($q) {
return $q
->select([
'Tenants.id', 'Tenants.stage', 'Tenants.tenancy_id', 'Tenants.holding_fee',
])
->where([
'active = 1',
]);
}
])
->contain([
'Tenants'
])
->where([
'Tenancies.active = 1',
]);
}
])
->where(['Property.active = 1', $conditions])
->toArray();
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,
];`