I want to make this tutorial http://miftyisbored.com/complete-tutorial-habtm-relationships-cakephp/ in cakePHP 3.0
I have 3 tables: recipes, ingredients and ingredients_recipes.
When making a recipe, I want to select ingredients. Then I want to store the recipe_id and ingredient_id in the ingredients_recipes table, but fail to do so. I think there's something wrong in my RecipesController. Can someone help me or point me in the right direction?
Problem:
$ingredients = $this->Recipes->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE MESSAGE "The recipe could not be saved. Please, try again."
$ingredients = $this->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE ERROR "Call to a member function find() on boolean"
When I do var dump (when using this $this->Recipes->Ingredients->find) I get this:
array(3) {
["recipe_name"]=> string(4) "Test"
["recipe_description"]=> string(4) "Test"
["Recipe"]=> array(1) {
["Ingredient"]=> array(1) { [0]=> string(1) "1" }
}
}
Tables:
CREATE TABLE `recipes` (
`recipe_id` int(11) NOT NULL auto_increment,
`recipe_name` varchar(255) NOT NULL,
`recipe_description` text NOT NULL,
PRIMARY KEY (`recipe_id`)
);
CREATE TABLE `ingredients` (
`ingredient_id` int(11) NOT NULL auto_increment,
`ingredient_name` varchar(255) NOT NULL,
`ingredient_description` text NOT NULL,
PRIMARY KEY (`ingredient_id`)
);
CREATE TABLE `ingredients_recipes` (
`ingredient_id` int(11) NOT NULL,
`recipe_id` int(11) NOT NULL,
PRIMARY KEY (`ingredient_id`,`recipe_id`)
);
Here's my code below:
Model > Entity:
Recipe
class Recipe extends Entity
{
protected $_accessible = [
'recipe_id' => true,
'recipe_name' => true,
'recipe_description' => true,
];
}
Ingredient
class Ingredient extends Entity
{
protected $_accessible = [
'ingredient_id' => true,
'ingredient_name' => true,
'ingredient_description' => true,
];
}
IngredientsRecipe
class IngredientsRecipe extends Entity
{
protected $_accessible = [
'ingredient' => true,
'recipe' => true,
];
}
Model > Table :
RecipesTable
class RecipesTable extends Table
{
public function initialize(array $config)
{
$this->table('recipes');
$this->displayField('recipe_name');
$this->primaryKey('recipe_id');
$this->belongsTo('Recipes', [
'foreignKey' => 'recipe_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Ingredients', [
'className' => 'Ingredients',
'joinTable' => 'ingredients_recipes',
'foreignKey' => 'recipe_id',
'targetForeignKey' => 'ingredient_id'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->requirePresence('recipe_name', 'create')
->notEmpty('recipe_name')
->requirePresence('recipe_description', 'create')
->notEmpty('recipe_description')
->requirePresence('Ingredients', 'create')
->notEmpty('Ingredients');
return $validator;
}
}
IngredientsTable
class IngredientsTable extends Table
{
public function initialize(array $config)
{
$this->table('ingredients');
$this->displayField('ingredient_name');
$this->primaryKey('ingredient_id');
$this->belongsTo('Ingredients', [
'foreignKey' => 'ingredient_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Recipies', [
'className' => 'Recipies',
'joinTable' => 'ingredients_recipes',
'foreignKey' => 'ingredient_id',
'targetForeignKey' => 'recipe_id'
]);
}
}
IngredientsRecipesTable
class IngredientsRecipesTable extends Table
{
public function initialize(array $config)
{
$this->table('ingredients_recipes');
$this->displayField('recipe_id');
$this->primaryKey(['recipe_id', 'ingredient_id']);
$this->belongsTo('Recipies', [
'foreignKey' => 'recipe_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Ingredients', [
'foreignKey' => 'ingredient_id',
'joinType' => 'INNER'
]);
}
Controller:
RecipesController
public function add()
{
$recipe = $this->Recipes->newEntity();
if ($this->request->is('post')) {
$recipe = $this->Recipes->patchEntity($recipe, $this->request->data);
// var_dump($this->request->data);
if ($this->Recipes->save($recipe)){
$this->Flash->success('The recipe has been saved.');
return $this->redirect(['action' => 'index']);
} else {
$this->Flash->error('The recipe could not be saved. Please, try again.');
}
}
$recipes = $this->Recipes->find('list', ['limit' => 200]);
$ingredients = $this->Recipes->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE MESSAGE "The recipe could not be saved. Please, try again."
$ingredients = $this->Ingredients->find('list', ['limit' => 200]);
// => THIS GIVES ME THE ERROR "Call to a member function find() on boolean"
$this->set(compact('recipe', 'recipes', 'ingredients'));
$this->set('_serialize', ['recipe']);
}
Template > Recipes
add.ctp
<?= $this->Form->create($recipe); ?>
<fieldset>
<legend><?= __('Add Recipe') ?></legend>
<?php
echo $this->Form->input('recipe_name', array(
'label' => 'Name'
)
);
echo $this->Form->input('recipe_description', array(
'label' => 'Description'
)
);
echo $this->Form->input('Recipes.Ingredients', ['multiple'=>true]);
?>
</fieldset>
<?= $this->Form->button(__('Submit')) ?>
<?= $this->Form->end() ?>
Maybe this tutorial could help: http://book.cakephp.org/3.0/en/tutorials-and-examples/bookmarks/intro.html.
It explains exactly how to set up HABTM relations, adding tags to bookmarks, and more.
Good luck and happy coding :)
Related
I am new to php frameworks and I've started to learn cakephp few days ago, but even after reading cakephp cookbook about saving belongsToMany associations I still don't know how to implement it in my situation.
I have three tables:
clients:
id | firstname | lastname | phone | email
interests:
id | text | comment | status_id | created_at
clients_interests:
client_id | interest_id
Models and entities were baked so I don't think it's something wrong there, but here is the code:
ClientsTable.php
class ClientsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('clients');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsToMany('Interests', [
'foreignKey' => 'client_id',
'targetForeignKey' => 'interest_id',
'joinTable' => 'clients_interests'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmptyString('id', 'create');
$validator
->scalar('firstname')
->maxLength('firstname', 64)
->requirePresence('firstname', 'create')
->allowEmptyString('firstname', false);
$validator
->scalar('middlename')
->maxLength('middlename', 64)
->allowEmptyString('middlename');
$validator
->scalar('lastname')
->maxLength('lastname', 64)
->requirePresence('lastname', 'create')
->allowEmptyString('lastname', false);
$validator
->scalar('phone')
->maxLength('phone', 24)
->requirePresence('phone', 'create')
->allowEmptyString('phone', false)
->add('phone', 'unique', ['rule' => 'validateUnique', 'provider' => 'table']);
$validator
->email('email')
->allowEmptyString('email');
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->isUnique(['email']));
$rules->add($rules->isUnique(['phone']));
return $rules;
}
}
InterestsTable.php
class InterestsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('interests');
$this->setDisplayField('id');
$this->setPrimaryKey('id');
$this->belongsTo('Statuses', [
'foreignKey' => 'status_id',
'joinType' => 'INNER'
]);
$this->belongsToMany('Clients', [
'foreignKey' => 'interest_id',
'targetForeignKey' => 'client_id',
'joinTable' => 'clients_interests'
]);
}
public function validationDefault(Validator $validator)
{
$validator
->integer('id')
->allowEmptyString('id', 'create');
$validator
->scalar('text')
->maxLength('text', 128)
->requirePresence('text', 'create')
->allowEmptyString('text', false);
$validator
->scalar('comment')
->maxLength('comment', 128)
->allowEmptyString('comment');
$validator
->date('created_at')
->requirePresence('created_at', 'create')
->allowEmptyDate('created_at', false);
return $validator;
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['status_id'], 'Statuses'));
return $rules;
}
public function getAll($id)
{
$connection = ConnectionManager::get('default');
$results = $connection
->execute('SELECT i.*, s.name status_name, s.classname status_classname FROM interests i INNER JOIN clients_interests ci ON ci.interest_id=i.id AND ci.client_id=:client_id INNER JOIN statuses s ON i.status_id=s.id ORDER BY created_at DESC;', ['client_id' => $id])
->fetchAll('assoc');
return $results;
}
}
ClientsInterestsTable.php
class ClientsInterestsTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->setTable('clients_interests');
$this->setDisplayField('client_id');
$this->setPrimaryKey(['client_id', 'interest_id']);
$this->belongsTo('Clients', [
'foreignKey' => 'client_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Interests', [
'foreignKey' => 'interest_id',
'joinType' => 'INNER'
]);
}
public function buildRules(RulesChecker $rules)
{
$rules->add($rules->existsIn(['client_id'], 'Clients'));
$rules->add($rules->existsIn(['interest_id'], 'Interests'));
return $rules;
}
}
Client entity - Client.php
class Client extends Entity
{
protected $_accessible = [
'firstname' => true,
'middlename' => true,
'lastname' => true,
'phone' => true,
'email' => true
];
}
Interest entity - Interest.php
class Interest extends Entity
{
protected $_accessible = [
'text' => true,
'comment' => true,
'status_id' => true,
'created_at' => true,
'clients_interest' => true,
'status' => true
];
}
ClientsInterests entity - ClientsInterests.php
class ClientsInterest extends Entity
{
protected $_accessible = [
'client' => true,
'interest' => true
];
}
And then in my controller I get all the data from a form. Interest entity after patchEntity() gets all its data besides client data for joinTable. And after save method new interest in interests table is created but there is no new row in clients_interests table. I've tried a lot of things from stackoverflow but it didn't work because I can't understand the mechanism of saving associated data even after reading the manual. Could anyone explain me in simple words how to save belongsToMany associated data?
Here is the code of my save method in InterestsController.php:
public function add()
{
$this->request->allowMethod(['ajax', 'post']);
$this->response->withDisabledCache();
$interest = $this->Interests->newEntity();
$interest = $this->Interests->patchEntity($interest, $this->request->getData(),
['associated'=>['Clients._joinData']]
);
if($this->Interests->save($interest)) {
$result = ['error' => null, 'error_text' => 'SUCCESSFUL'];
} else {
$result = ['error' => null, 'error_text' => 'ERRORS ERRORS ERRORS'];
}
$result = ['error' => null, 'error_text' => 'ERRORS ERRORS ERRORS'];
$this->set('result', $result);
$this->set('_serialize', ['result']);
}
And in my template I use this input for getting client_id:
<?php echo $this->Form->control('clients.0.client_id', ['hidden', 'type'=>'text','id'=>'client-id', 'class'=>'form-control']); ?>
Request data looks like this: request data
And Interest entity after patchEntity() looks like this: Interest entity
If you want to add an 'interest', I think your request data should look differently.
Here is an example, but sorry, I haven't tested it, it's made so you get the idea : you have to tell CakePHP you want to create a Interest entity, and inside add a Client information (just like in the "Converting BelongsToMany Data" in "Saving Data" of book.cakephp.org/3.0/en/orm/saving-data.html).
$data = [
_csrfToken => "...",
'interests' => [
'text' => 'texttext',
'comment' => '',
'created_at' => '2019-01-10',
'status_id' => 2,
'clients' => [
'_ids' => [0],
]
];
Personally, I learnt a lot looking at the requests generated through the Form Helper from the automatically baked templates.
I try to make a blog with articles and tags but for some reason my tags wont save when creating articles.
I did followed some ideas from official cake blog and from here but no luck for me .. Maybe something I do wrong and I dont see.
Tables for keywords
CREATE TABLE `keywords` (
`id` int(11) NOT NULL,
`article_id` int(11) DEFAULT NULL,
`tag` varchar(255) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Table model
Keywords:
class KeywordsTable extends Table
{
......................
public function initialize(array $config)
{
parent::initialize($config);
$this->table('keywords');
$this->displayField('id');
$this->primaryKey('id');
$this->belongsTo('Article', [
'foreignKey' => 'article_id',
'joinType' => 'INNER'
]);
}
....................
}
Article
class ArticleTable extends Table
{
public function initialize(array $config)
{
parent::initialize($config);
$this->table('article');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
'joinType' => 'INNER'
]);
$this->hasMany('Keywords', [
'foreignKey' => 'article_id'
]);
}
......................................
}
And here is my controller:
public function add()
{
//$this->autoRender = false;
$article = $this->Article->newEntity();
if ($this->request->is('post')) {
$article = $this->Article->patchEntity($article, $this->request->data, ['associated'=>['Keywords']]);
echo "<pre>";
print_r($this->Article->save($article));
die();
}
}
And this is the code I use in my form
<?= $this->Form->input('article[keywords][]', ['label'=>false, 'class' => 'form-control', 'placeholder' => 'keywords', 'templates' => ['inputContainer' => '{{content}}']]); ?>
So if any one could help me out I will appreciate.
Ok, after 4 hours I managed to find the problem.
Problem was in my view form input.
It should have been article.0.tag instead of article[keywords][]
Changed that and all works now.
Regards
in controller you should use associated array, more details-
$this->Article->save($article,['associated' => ['Keywords']]);
and in view file, make input field like this -
<?= $this->Form->input('keywords.0.title', ['label'=>false, 'class' => 'form-control', 'placeholder' => 'keywords', 'templates' => ['inputContainer' => '{{content}}']]); ?>
more details
I'm trying set association in cakephp
my db
two model:
namespace App\Model\Table;
class UsersTable extends Table {
public function initialize(array $config)
{
$this->hasOne('Scores',[
'className' => 'Scores',
'conditions' => '',
'dependent' => true
]);
}
}
and
class ScoresTable extends Table {
//var $name = 'Scores';
public final $connection = ConnectionManager::get('default');
public function initialize(array $config)
{
$this->belongTo('Users',
'foreignKey' => 'user_code_Student',
'joinType' => 'INNER',
);
}
}
function index in controller:
public function index() {
//$connection = ConnectionManager::get('default');
$modelUser = $this->loadModel('Users');
$dataUser = $modelUser->find('all')->contain(['Score']);
/*$data = array(
/*'listUser' => $modelUser->find('all', array(
'conditions' => array('email LIKE' => '%gmail.com'),
'limit' => 2
)),$connection
->execute('SELECT * FROM users WHERE email like :email limit 3', ['email' => '%gmail.com'])
->fetchAll('assoc')
'listUser' => $this->paginate($modelUser->find('all')),
'title' => 'Demo MVC'
);*/
$data = array(
'listUser' => $dataUser
);
$this->set('data', $data);
}
When I run it, the following error is shown: Users is not associated with Score
Can you help me?
Your relation looks like invalid.Try this:
users table
public function initialize(array $config)
{
$this->hasOne('Scores',[
'foreignKey' => 'user_code_Student'
]);
}
scores table
public function initialize(array $config)
{
$this->belongsTo('Users', // you wrote here belongTo that should be belongsTo instead
'foreignKey' => 'user_code_Student', // is user_code_Student foreignKey ???
'joinType' => 'INNER',
);
}
And also change this:
$dataUser = $modelUser->find('all')->contain(['Scores']); // not Score
I wanna make assoc with Locations via locations_ways join table what contain order and point (start / middle / end) of way.
In end I wanna get assoc
locations - all locations
end_location ONLY end location
start_location ONLY start location
Now for end_location and start_location I have array what containe one record. But I dont want write some like this: $way->end_location[0]->name;
How to process it?
class WaysTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Locations', [
'through' => 'LocationsWays',
'sort' => ['LocationsWays.position ASC'],
]);
$this->belongsToMany('LocationStartPoint', [
'className' => 'Locations',
'foreignKey' => 'way_id',
'targetForeignKey' => 'location_id',
'through' => 'LocationsWays',
'conditions' => ['LocationsWays.point' => 'start'],
]);
$this->belongsToMany('LocationEndPoint', [
'className' => 'Locations',
'foreignKey' => 'way_id',
'targetForeignKey' => 'location_id',
'through' => 'LocationsWays',
'conditions' => ['LocationsWays.point' => 'end'],
]);
}
}
class LocationsTable extends Table
{
public function initialize(array $config)
{
$this->belongsToMany('Ways', [
'through' => 'LocationsWays',
]);
}
}
class LocationsWaysTable extends Table
{
public function initialize(array $config)
{
$this->belongsTo('Ways', [
]);
$this->belongsTo('Locations', [
]);
}
}
Fetching func.
public function getWayById($id, $locale = 'uk')
{
I18n::locale($locale);
$item = $this->find()
->where(['Ways.id' => $id])
->contain(['Locations', 'LocationStartPoint', 'LocationEndPoint'])
->cache(function (Query $q) {
return 'way-' . md5(serialize($q->clause('where')));
}, 'orm')
->first();
return $item;
}
UPDATE
So I added some logs to the action upload in ProductsController and the method upload in MediasTable to find out what is happening. The entity from ProductsController this->Products->Medias->newEntity() was pass to MediasTable but it wasn't save.
It is necessary to upload the file to save the data in the db? Like if all the data is ok but the file is no present the event will be reject the data and do nothing in the db?
I'm using cakephp 3.1 with the file-storage plugin. I'm following the quickstart guide from the docs: Quick-Start but I don't understand some parts and doesn't upload, insert in database neither make thumbnails.
This is my database:
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(255) NOT NULL,
quantity INT NOT NULL,
sold INT NOT NULL,
description VARCHAR(1000),
price DECIMAL(7,2) NOT NULL,
old_price DECIMAL(7,2) NOT NULL,
visited INT NOT NULL,
status INT NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE media_types (
id INT AUTO_INCREMENT PRIMARY KEY,
name_media_type VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME
);
CREATE TABLE medias (
id INT AUTO_INCREMENT PRIMARY KEY,
media_type_id INT NOT NULL,
product_id INT NOT NULL,
path VARCHAR(255) NOT NULL,
created DATETIME,
modified DATETIME,
FOREIGN KEY media_type_key (media_type_id) REFERENCES media_types(id),
FOREIGN KEY product_key (product_id) REFERENCES products(id)
);
MediasTable:
...
use Burzum\FileStorage\Model\Table\ImageStorageTable;
class MediasTable extends ImageStorageTable {
public function initialize(array $config) {
parent::initialize($config);
$this->table('medias');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->belongsTo('MediaTypes', [
'foreignKey' => 'media_type_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Products', [
'foreignKey' => 'product_id',
'joinType' => 'INNER'
]);
}
...
public function upload($productId, $entity) {
$media = $this->patchEntity($entity, [
'adapter' => 'Local',
'model' => 'Media',
'foreign_key' => $productId
]);
Log::write('debug', $media);
return $this->save($media);
}
}
ProductsTable:
class ProductsTable extends Table {
public function initialize(array $config) {
parent::initialize($config);
$this->table('products');
$this->displayField('id');
$this->primaryKey('id');
$this->addBehavior('Timestamp');
$this->hasMany('Medias', [
'className' => 'Medias',
'foreignKey' => 'foreign_key',
'conditions' => [
'Medias.model' => 'Media'
]
]);
}
...
}
ProductsController:
class ProductsController extends AppController {
public function upload($productId = null) {
$productId = 2;
$entity = $this->Products->Medias->newEntity();
if ($this->request->is(['post', 'put'])) {
$entity = $this->Products->Medias->patchEntity(
$entity,
$this->request->data
);
if ($this->Products->Medias->upload($productId, $entity)) {
$this->Flash->set(__('Upload successful!'));
}
}
$this->set('productImage', $entity);
}
...
}
In config/local_store.php is the same as the example (I include this file in boostrap.php)
...
$listener = new LocalFileStorageListener();
EventManager::instance()->on($listener);
$listener = new ImageProcessingListener();
EventManager::instance()->on($listener);
Configure::write('FileStorage', [
'imageSizes' => [
'Medias' => [
'large' => [
...
]);
FileStorageUtils::generateHashes();
StorageManager::config('Local', [
'adapterClass' => '\Gaufrette\Adapter\Local',
'adapterOptions' => [TMP, true],
'class' => '\Gaufrette\Filesystem'
]);
upload.ctp
echo $this->Form->create($productImage, array('type' => 'file'));
echo $this->Form->file('file');
echo $this->Form->error('file');
echo $this->Form->submit(__('Upload'));
echo $this->Form->end();
In the quick start there is two upload methods: uploadImage and uploadDocument
but in the controller they use "upload".
There is another association in Products in the example, I need this?:
$this->hasMany('Documents', [
'className' => 'FileStorage.FileStorage',
'foreignKey' => 'foreign_key',
'conditions' => [
'Documents.model' => 'ProductDocument'
]
]);
I found this question (from there is the db I'm using) Getting Started with cakephp-file-storage quickstart guide and upload and insert but doesn't make the thumbnails and if I change the table to ImageStoreTable shows an error "Class not found"
So if anybody can help me I will be very grateful!