I am creating a function in model to find all related services.
function in ServiceCategory.php
class ServiceCategory extends Entity
{
public function relatedServices($id)
{
return $this->find('all', [
'conditions' => [
'where' => [
'id !=' => $id
],
'limit' => 5
]
]);
}
}
And calling in ServiceCategoriesController.php
public function view($id = null)
{
$serviceCategory = $this->ServiceCategories->get($id, [
'contain' => ['Services']
]);
$relatedServices = $this->ServiceCategories->relatedServices($id);
$this->set('serviceCategory', $serviceCategory);
$this->set('relatedServices', $relatedServices);
$this->set('_serialize', ['serviceCategory']);
}
But it gives Unknown method 'relatedServices'
Is there any thing wrong I am doing ?
The code is in the wrong class
In the question:
class ServiceCategory extends Entity
This is an entity class
$relatedServices = $this->ServiceCategories->relatedServices($id);
This is making a call on a table object, table objects and entities do not inherit from each other, the method is unavailable to the table class.
Move the code to the table class
The direct solution is to move the code to the table class:
// src/Model/Table/ServiceCategoriesTable.php
namespace App\Model\Table;
class ServiceCategoriesTable extends Table
{
public function relatedServices($id)
{
return $this->find('all', [
'conditions' => [
'where' => [
'id !=' => $id
],
'limit' => 5
]
]);
}
Though the arguably correct/better way to do that is to implement a finder:
// src/Model/Table/ServiceCategoriesTable.php
namespace App\Model\Table;
use Cake\ORM\Query;
use \InvalidArgumentException;
class ServiceCategoriesTable extends Table
{
public function findRelatedServices(Query $query, array $options)
{
if (!isset($options['id'])) {
$message = sprintf('No id in options: %s', json_encode($options));
throw new InvalidArgumentException($message);
}
$query->where(['id !=' => $options['id']);
return $query;
}
Which would be called in exactly the same way as other find calls:
$relatedServices = $this->ServiceCategories->find(
'relatedServices',
['id' => $id]
);
Related
That is my Query object dump from debug...
I want to add more condition in method JOIN clause or i want setting default condition when execute joins by CakePHP.
My code:
public function beforeFind(Event $event, Query $query)
{
if ($query->join()) {
foreach ($query->join() as $key => &$join) {
$join['conditions'] = new QueryExpression('"aaaa" = "bbbbb"');
}
return $query;
}
}
You can check CakePHP Docs.
I believe you searching something like this:
class CustomerTable extends Table
{
public function initialize(array $config)
{
$this->hasOne('User')
->setName('User')
->setConditions(['User.active' => '1']) // or any other additional clause
->setDependent(true);
}
}
I whould sugggest you to use innerJoinWith, if you need to hook your normal association behaviour.
As a hardcode solution (that what I cann see from your code samples), read join method docs:
$query->join([
'user' => [
'table' => 'user',
'type' => 'INNER',
'conditions' => '"aaaa" = "bbbbb"'
]
]);
Now some telepathy,.. abracadabra,... wait-wait,... try this:
public function beforeFind(Event $event, Query $query)
{
$joins = $query->join();
if ($joins) {
foreach ($joins as $key => &$join) {
if ($key == 'user')
$join['conditions'] = ($join['conditions'] ? ' AND ' : '') . ' "aaaa" = "bbbbb"';
}
return $joins ? $query->join($joins) : $query;
}
}
I am using Cakephp 3.2.10
I am able to load associated data with hasOne association and hasMany association. However, I have one hasOne inside the table associated with hasMany, and that gives error.
I have following tables
companies
company_revenues
currencies
states
countries.
CompaniesTable class has hasOne associations with states,countries and it works. CompaniesTable has hasMany association with CompanyRevenuesTable, and it works. CompanyRevenues table has a hasOne association with currencies, this gives error.
My relevant Code :
CompaniesTable.php
<?php
namespace Admin\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
class CompaniesTable extends Table
{
public function initialize(array $config)
{
$this->primaryKey('company_id');
$this->addAssociations(
[
'hasOne' =>
[
'Countries' =>
[ 'className' => 'Countries','foreignKey' => 'id','bindingKey' => 'company_country_id' ]
,'States' =>
[ 'className' => 'States','foreignKey' => 'id','bindingKey' => 'company_state_id' ]
,'Sectors' =>
[ 'className' => 'Sectors','foreignKey' => 'id','bindingKey' => 'company_sector_id' ]
]
,'hasMany' =>
[
'CompanyRevenues' =>
[ 'className' => 'CompanyRevenues','foreignKey' => 'revenue_company_id','bindingKey' => 'company_id' ]
]
]);
}
public function buildRules(RulesChecker $rules)
{
$rules->add( $rules->isUnique(['company_name']) );
return $rules;
}
public function compsTotalCount( Query $query )
{
$result = $query->select(['companiesCount' => $query->func()->count('*')])->first();
return $result;
}
public function findPopular( Query $query )
{
$result = $query->where(['times_viewed >' => 10]);
// debug($result);
return $result;
}
}
?>
CompanyRevenuesTable.php
<?php
namespace Admin\Model\Table;
use Cake\ORM\Table;
use Cake\ORM\Query;
use Cake\ORM\RulesChecker;
class CompanyRevenuesTable extends Table
{
public function initialize(array $config)
{
$this->table('company_revenues');
$this->addAssociations(
[
'hasOne' =>
[
'Currencies' =>
[ 'className' => 'Currencies','foreignKey' => 'id','bindingKey' => 'revenue_currency_id' ]
]
]);
}
}
?>
My CompaniesController.php
profile action
public function profile( $id = null )
{
$company = $this->Companies->find()
->where(['company_id' => $id])
->contain(['CompanyRevenues'])->first();
if( ! $this->request->is('ajax') )
{
$companyRevenuesTable = TableRegistry::get('CompanyRevenues');
$companyRevenues = $companyRevenuesTable->find()
->where(['revenue_company_id' => $company->company_id])
->contain(['Currencies']);
debug($companyRevenues);
}
if( $this->request->is('ajax') )
{
$company = $this->Companies->patchEntity($company, $this->request->data);
$company->company_last_updated = date("Y-m-d H:i:s");
$ajaxRespArr = array();
if( $this->Companies->save($company) )
{
$ajaxRespArr["result"] = 1;
}
else
{
$ajaxRespArr["result"] = 0;
}
$this->set( 'ajaxRespArr',$ajaxRespArr );
$this->set('_serialize', ['ajaxRespArr']);
}
else
{
$this->set('company', $company);
}
}
The debug on $$companyRevenues
gives error
\plugins\Admin\src\Controller\CompaniesController.php (line 92)
object(Cake\ORM\Query) {
(unable to export object: CompanyRevenues is not associated with Currencies)
}
I think the error is because of _ in my table company_revenues.
Can any one guide me please ?
Actually, in the profiles action, i do not want to load company revenues separately, as they come along with company.
I tried the following originally :
$company = $this->Companies->find()
->where(['company_id' => $id])
->contain(
[
'CompanyRevenues' => ["Currencies"]
])->first();
But that give error :
CompanyRevenues is not associated with Currencies InvalidArgumentException
Could this be caused by using Auto-Tables?
Some of the Table objects in your application were created by instantiating "Cake\ORM\Table" instead of any other specific subclass.
This could be the cause for this exception. Auto-Tables are created for you under the following circumstances:
The class for the specified table does not exist.
The Table was created with a typo: TableRegistry::get('Atricles');
The class file has a typo in the name or incorrect namespace: class Atricles extends Table
The file containing the class has a typo or incorrect casing: Atricles.php
The Table was used using associations but the association has a typo: $this->belongsTo('Atricles')
The table class resides in a Plugin but no plugin notation was used in the association definition.
Please try correcting the issue for the following table aliases:
CompanyRevenues
I would suggest adding the reverse relationships as well and see what happens.
By reverse relationships I mean belongsTo in your currencies and companyRevenues classes.
Currencies belongTo companyRevenues and
CompanyRevenues belongTo company class.
See what your debugging provides.
in a ctp, I display several virtual properties from 2 entities, computed by getters.
It's ok for one of the both entities but for the other, none of the properties getters are never called.
Here is an excerpt of the ctp:
<table>
....
<td><?= $agTheme->agthemelanguages_wc_string ?></td>
<td><?= $agPoi->paragraph_length_string ?></td>
<td><?= $agPoi->images_string ?></td>
<td><?= $agPoi->audios_string ?></td>
....
</table>
It's ok for AgTheme's property but no getter of the Agpoi's properties is called.
Agtheme model is:
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
use Cake\Collection\Collection;
use Cake\Core\Configure;
use Cake\Log\Log;
use Cake\ORM\TableRegistry;
class Agtheme extends Entity {
protected $_accessible = [
'site_id' => true,
'site' => true,
'name' => true,
'directory_name' => true,
'comment' => true,
'image_0' => true,
'imgpathname0' => true,
'imgfile0' => true,
'mobile_theme' => true,
'agthemelanguages' => true,
'poi_by_tag' => true,
'poi_to_map' => true,
'unlock_mode' => true,
'unlock_code' => true,
'published' => true,
'approved' => true,
];
protected function _getAgthemelanguagesWcString() {
if (isset($this->_properties['agthemelanguages_wc_string'])) {
return $this->_properties['agthemelanguages_wc_string'];
}
if (empty($this->agthemelanguages)) {
return '';
}
$agthemelanguages = new Collection($this->agthemelanguages);
$str = $agthemelanguages->reduce(function ($string, $agthemelanguage) {
return $string . $agthemelanguage->language->name_fr . ' :<br/>';
}, '');
return trim($str, '<br/>');
}
function _getImgpathname0() {
if (isset($this->_properties['imgpathname0'])) {
return $this->_properties['imgpathname0'];
}
if (!isset($this->_properties['id']) || empty($this->image_0) || !isset($this->_properties['directory_name'])) {
return '';
}
$img_path = SITES_CONTENT_URL . $this->_properties['directory_name'] .'/imgTheme_0.' . $this->_properties['image_0'];
return $img_path;
}
}
And Agpoi model is (Agpoi.php):
<?php
namespace App\Model\Entity;
use Cake\ORM\Entity;
use Cake\Collection\Collection;
class Agpoi extends Entity
{
protected $_accessible = [
'name' => true,
'latitude' => true,
'longitude' => true,
'bearing' => true,
'preload' => true,
'agtheme' => true,
'agpoiaudios' => true,
'agpoiimages' => true,
'agpoitextes' => true,
'agpoiwaypoints' => true,
];
protected function _getImagesString()
{
if (isset($this->_properties['images_string'])) {
return $this->_properties['images_string'];
}
if (empty($this->agpoiimages)) {
return '';
}
$agpoiimages = new Collection($this->agpoiimages);
$str = $agpoiimages->reduce(function ($string, $agpoiimage)
{
return $string . $agpoiimage->image . ', ';
}, '');
return trim($str, ', ');
}
protected function _getAudiosString()
{
if (isset($this->_properties['audios_string'])) {
return $this->_properties['audios_string'];
}
if (empty($this->agpoiaudios)) {
return '';
}
$agpoiaudios = new Collection($this->agpoiaudios);
$str = $agpoiaudios->reduce(function ($string, $agpoiaudio)
{
return $string . $agpoiaudio->filename . ', ';
}, '');
return trim($str, ', ');
}
protected function _getParagraphLengthString() {
if (isset($this->_properties['paragraph_length_string'])) {
return $this->_properties['paragraph_length_string'];
}
if (empty($this->agpoitextes)) {
return '';
}
$agpoitextes = new Collection($this->agpoitextes);
$str = $agpoitextes->reduce(function ($string, $agpoitexte) {
return $string . str_word_cound($agpoitexte->paragraph) . __(" mots<br/>");
}, '');
return trim($str, '<br/>');
}
}
Unfortunately, I doubt that the problem could come from the code above, I better think about of a stupid mistake somewhere else but I really wonder what could cause such problem and here is my question:
Could anyone tell me what allow cake to look for a getter?
What could prevent cake to call a getter??
I guess the answer is not obvious for the last question but any hint would be appreciated...
EDIT
#ndm
debug(get_class($agPoi));
'Cake\ORM\Entity'
whereas
debug(get_class($agTheme));
'App\Model\Entity\Agtheme'
so obviously Agpoi is not well defined but I don't see why...
EDIT 2
Table is (AgpoisTable.php):
<?php
namespace App\Model\Table;
use Cake\ORM\Query;
use Cake\ORM\Table;
use Cake\Validation\Validator;
/**
* Agpois Model
*/
class AgpoisTable extends Table {
/**
* Initialize method
*
* #param array $config The configuration for the Table.
* #return void
*/
public function initialize(array $config) {
$this->table('agpois');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsToMany('Agthemes', [
'foreignKey' => 'agpoi_id',
'targetForeignKey' => 'agtheme_id',
'joinTable' => 'agthemes_agpois',
'dependent' => true,
]);
$this->hasMany('Agpoiaudios', [
'dependent' => true,
'foreignKey' => 'agpoi_id',
]);
$this->hasMany('Agpoiimages', [
'dependent' => true,
'foreignKey' => 'agpoi_id',
]);
$this->hasMany('Agpoitextes', [
'dependent' => true,
'foreignKey' => 'agpoi_id',
]);
$this->hasMany('Agpoiwaypoints', [
'dependent' => true,
'foreignKey' => 'agpoi_id',
]);
}
public function validationDefault(Validator $validator) {
$validator
....
return $validator;
}
}
I also made a search for 'agpoi' everywhere in case.... no unexpected other agpoi classname or something like that.
debug($this->Agpois);
object(App\Model\Table\AgpoisTable) {
'registryAlias' => 'Agpois',
'table' => 'agpois',
'alias' => 'Agpois',
'entityClass' => '\Cake\ORM\Entity',
'associations' => [
(int) 0 => 'agthemes',
(int) 1 => 'agpoiaudios',
(int) 2 => 'agpoiimages',
(int) 3 => 'agpoitextes',
(int) 4 => 'agpoiwaypoints',
(int) 5 => 'agthemesagpois'
],
'behaviors' => [],
'defaultConnection' => 'default',
'connectionName' => 'default'
}
so always the problem of entityClass of course.
The problem here seems to be name of the table class, respetively the name of the entity class, probably depends on your perspective.
By default, the name of the corresponding entity class is guessed by using the singular table class name without the trailing Table, however Agpois doesn't inflect to Agpoi as you might have assumed, but just Agpois, and so it's looking for the class App\Model\Entity\Agpois, which doesn't exist, hence the fallback to the default entity base class.
I'm not sure what Agpois means, and which language it stems from, but it's certainly not english, and thus such problems aren't unexpected, as the Inflector is ment to handle english words only.
tl;dr
If you must use the name Apgois, then you'll have to either define the name of the entity class manually in your tables initialize() method:
public function initialize(array $config)
{
// ...
$this->entityClass('Apgoi');
// ...
}
or configure the inflector so that it can handle that word:
Inflector::rules('irregular', ['apgoi' => 'apgois']);
or even rename the entity class to Apgois if that is applicable and doesn't cause any naming conflicts.
See also
Cookbook > ORM > Table Objects > Customizing the Entity Class a Table Uses
Cookbook > Inflector > Loading Custom Inflections
I have a form that collects data about an Article, and I want to save that data, as well as for a model called Abstract, where an Article hasMany Abstracts. My models look like this:
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class AbstractsTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Articles');
}
public function validationDefault(Validator $validator)
{
$validator
->notEmpty('body');
return $validator;
}
}
And
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class ArticlesTable extends Table
{
public function initialize(array $config)
{
$this->addBehavior('Timestamp');
$this->hasMany('Abstracts');
}
public function validationDefault(Validator $validator)
{
$validator ->notEmpty('category')
return $validator;
}
}
My input form has a field named 'abstracts.body', and in my ArticlesController I have this function:
public function add()
{
$data = $this->request->data;
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
if ($this->request->is('post')) {
$article->user_id = $this->Auth->user('id');
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
$article = $this->Articles->patchEntity($article, $data, [
'associated' => ['Abstracts']
]);
if ($this->Articles->save($article, [ 'validate' => false,
'associated' => ['Abstracts']
]) )
{
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to add your article.'));
}
$this->set('article', $article);
}
My Abstracts table is pretty straightforward:
CREATE TABLE 'abstracts' ('id' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, 'article_id' INTEGER , 'user_id' INTEGER , 'body' TEXT, 'approved' BOOLEAN )
From debugging I can see that I have the correct 'abstracts' array within my $data (in add()), but it doesn't appear to ever try to save it to the database. Can someone please point out my error? Thanks!
Got it.
I started going wrong here:
My input form has a field named 'abstracts.body'
Because it's a hasMany relationship, I need to have that input be 'abstracts.0.body'
Then the rest of LeWestopher's answer will work-- adding an index to the fields I want to fill in from the Controller, so $data[abstracts][0]['user_id'] => ... and so on. Thanks!
You're post processing your $data['abstracts'] array incorrectly resulting in the association not saving. $data['abstracts'] is expected to be an array of Abstracts. Your issue lies here:
$data['abstracts']['user_id'] = $article->user_id;
$data['abstracts']['approved'] = 0;
You should be able to fix this pretty easily by changing this to:
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
This should correctly iterate over your array of abstracts, set the user_id and approved keys appropriately and then it should save correctly.
CakePHP 3.x Documentation on Saving Associations
EDIT: Very interesting issue indeed. Try it without using patchEntity, and use newEntity by itself instead:
public function add()
{
if ($this->request->is('post')) {
$data = $this->request->data;
// Post process abstracts objects
foreach($data['abstracts'] as $index => $abstract) {
$abstract['user_id'] = $article->user_id;
$abstract['approved'] = 0;
$data['abstracts'][$index] = $abstract;
}
// Build newEntity
$article = $this->Articles->newEntity($data, [
'associated' => ['Abstracts']
]);
// Save our entity with associations
if ($this->Articles->save($article, [
'validate' => false,
'associated' => ['Abstracts']
])) {
$this->Flash->success(__('Your article has been saved.'));
return $this->redirect(['action' => 'index']);
}
// On save fail
$this->Flash->error(__('Unable to add your article.'));
$this->set('article', $article);
}
}
EDIT 2: Your issue looks like it's definitely in your form helper. Your current form helper input creates an $data array that looks like this:
$data = [
'abstracts' => [
'body' => 'example text'
],
'category' => 'Science'
];
Which SHOULD look like:
$data = [
'abstracts' => [
['body' => 'example text'],
['body' => 'Im your second abstract'],
['body' => 'Abstract three!']
],
'category' => 'Science'
];
The issue lies in:
abstracts.body
Which should read as (in array dot notation):
// abstracts.0.body
echo $this->Form->input('abstracts.0.body', [
'label' => 'summary of article',
'maxlength' =>'440',
'rows' => '7'
]);
I believe that should be the last issue you run into.
I am using the following code to test the login action in UsersController
public function testLogin() {
$data = array('User' => array(
'username' => 'hello',
'password' => '411'
)
);
$this->Users = $this->generate('Users',array('components'=> array('Auth','Session')));
$this->Users->Auth->staticExpects($this->once())
->method('user')
->with('id');
$this->testAction('/users/login', array('data' => $data, 'method' => 'post'));
}
and the fixture is-
class UserFixture extends CakeTestFixture {
public $import = array('model' => 'User', 'records' => true, 'connection' => 'fixture');
}
adn action is-
public function login() {
if($this->request->is('post')) {
if($this->Auth->login()) {
$this->redirect($this->Auth->redirect());
} else {
return false;
$this->Session->setFlash(__('Wrong Username Or Password,Please Try Again'));
}
}
}
It always showing
Expectation failed for method name is equal to when invoked 1 time(s).
Method was expected to be called 1 times, actually called 0 times.
What is the problem?Cant find any solution,and the reason also.Please help.
I think your problem mocking the auth component is that in addition to including auth in your components array, you need to specify which methods of Auth to mock, and how to mock them.
The way I deal with AuthComponent in my tests is I create a superclass with a method: _generateMockWithAuthUserId which mocks the Auth component, among other things, the way I need.
I've pasted the code from my superclass below.
class AppControllerTest extends ControllerTestCase {
public function setUp() {
parent::setUp();
$this->User = ClassRegistry::init('User');
}
public function tearDown() {
unset($this->User);
parent::tearDown();
}
public function testPlaceholder(){
// This just here so we don't get "Failed - no tests found in class AppControllerTest"
$this->assertTrue(true);
}
protected function _generateMockWithAuthUserId($contollerName, $UserId){
$this->authUserId = $UserId;
$this->authUser = $this->User->findById($this->authUserId);
$this->controller = $this->generate($contollerName, array(
'methods' => array(
'_tryRememberMeLogin',
'_checkSignUpProgress'
),
'components' => array(
'Auth' => array(
'user',
'loggedIn',
),
'Security' => array(
'_validateCsrf',
),
'Session',
)
));
$this->controller->Auth
->expects($this->any())
->method('loggedIn')
->will($this->returnValue(true));
$this->controller->Auth
->staticExpects($this->any())
->method('user')
->will($this->returnCallback(array($this, 'authUserCallback')));
}
public function authUserCallback($param){
if(empty($param)){
return $this->authUser['User'];
} else {
return $this->authUser['User'][$param];
}
}
}
And then here's a example of a class that inherits from that superclass. Take note of where/how it calls _generateMockWithAuthUserId. Basically, doing that sets up a suitable controller with Auth mocked for the appropriate user id.
<?php
require_once dirname(__FILE__) . DS . 'AppControllerTest.php';
class EmployeeNotesControllerTestCase extends AppControllerTest {
public $fixtures = array(
// your fixtures go here
);
public function setUp() {
parent::setUp();
$this->EmployeeNote = ClassRegistry::init('EmployeeNote');
}
public function tearDown() {
unset($this->EmployeeNote);
parent::tearDown();
}
public function testSupervisorIndexCanNotSeeNotesOnSelf() {
$authUserId = 1;
$this->_generateMockWithAuthUserId('EmployeeNotes', $authUserId);
$this->controller->Session
->expects($this->once())
->method('setFlash');
$result = $this->testAction('supervisor/employee_notes/index/'.$authUserId, array('return' => 'vars', 'method' => 'get'));
$this->assertTrue(empty($result['employeeNotes']));
}
}
Hope that helps.
I have found a solution.it worked.
public function testLogin() {
$data = array('User' => array(
'username' => 'sasa',
'password' => '111'
)
);
$this->Users = $this->generate('Users', array());
$result = $this->testAction('/users/login', array('data' => $data, 'method' => 'post'));
$this->assertEquals($data['User']['username'],$this->Users->Session->read('Auth.User.username'));
$result = $this->testAction('/users/logout');
}