Here's the situation:
I have a table which has a composite key:
CREATE TABLE records (
id int(11) NOT NULL,
pacient_id int(11),
doctor_id int(11),
--
-- bunch of stuff that are null
--
PRIMARY KEY (id, pacient_id, doctor_id),
CONSTRAINT records_ibfk_1 FOREIGN KEY (pacient_id) REFERENCES pacients (id),
CONSTRAINT records_ibfk_2 FOREIGN KEY (doctor_id) REFERENCES doctors (id)
)
so far so good, the problem is when I try to add a record... I get this error
Cannot insert row, some of the primary key values are missing. Got (,
, ), expecting (id, pacient_id, doctor_id)
So I went to the add method in the controller and I started put two debugs. One debuging $this->request->data and other debuging $record itself (after the patchEntity) something like this:
public function add()
{
$record = $this->Records->newEntity();
if ($this->request->is('post')) {
$record = $this->Records->patchEntity($record, $this->request->data);
debug($record);
debug($this->request->data);die;
this code was generated with bake.
the result of debug is:
object(App\Model\Entity\Record) {
'others' => '',
'staging' => '',
'surgery' => '',
'medication' => '',
'alergy' => '',
'[new]' => true,
'[accessible]' => [
'*' => true
],
'[dirty]' => [
'others' => true,
'staging' => true,
'surgery' => true,
'medication' => true,
'alergy' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[repository]' => 'Records'
}
and
[
'doctor_id' => '2',
'pacient_id' => '7',
'points_unusual_lost_weigh' => '',
'points_lost_weight' => '',
'lack_apetite' => '',
'dm' => '',
'has' => '',
'dcv' => '',
'drenal' => '',
'others' => '',
'chemotherapy' => '',
'radiotherapy' => '',
'metastasis' => '',
'staging' => '',
'surgery' => '',
'alcoholic' => '',
'smoker' => '',
'smoker_date' => [
'year' => '',
'month' => '',
'day' => ''
],
'intestine_habit' => '',
'medication' => '',
'alergy' => ''
]
see, after patchEntity there is none _id, but, I really don't know why it's not reaching there.
thanks for those who help!
-- EDITED --
answering the first question below from rrd: yes! I did and it looks like this: (here are my associations)
$this->belongsTo('Pacients', [
'foreignKey' => 'pacient_id',
'joinType' => 'INNER'
]);
$this->belongsTo('Doctors', [
'foreignKey' => 'doctor_id',
'joinType' => 'INNER'
]);
second question from ndm: (here is my $_accessible variable)
protected $_accessible = [
'*' => true,
'id' => false,
'pacient_id' => false,
'doctor_id' => false,
];
Just as assumed, the primary key fields are set be non-accessible/mass-assignable.
Either modify the entity code and remove pacient_id and doctor_id, or set them to true, or use the accessibleFields option in the patchEntity call to override the behavior for the passed entity instance only.
See
Cookbook > Database Access & ORM > Entities > Mass Assignment
Cookbook > Database Access & ORM > Saving Data > Changing Accessible Fields
The debug output of the entity is a little misleading in showing only the *, as this suggests that all fields are accessible. This happens because the "empty" array entries are being filtered in order to have only those fields shown that are actually accessible, which however is obviously a problem when * is used, as the exceptions are lost, so there seems to be room for improvement.
Related
I am trying to retrieve records from a primary and associated model where the associated model has a condition on it. The is a basic one to many relationship. The WpPosts is the primary and i want all associated rows from WpPostmetas that have the meta_value of lead_data from a data range. The below code does this but it also including null associated models to the WpPost output. The below data should not have been retrieved. How do i prevent the below data from being retrieved?
object(App\Model\Entity\WpPost) {
'ID' => (int) 1997,
'post_author' => (int) 1,
'post_date' => object(Cake\I18n\FrozenTime) {
'time' => '2018-09-27T12:08:40+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
'post_date_gmt' => object(Cake\I18n\FrozenTime) {
'time' => '2018-09-27T12:08:40+00:00',
'timezone' => 'UTC',
'fixedNowTime' => false
},
.....
'comment_count' => (int) 0,
'wp_postmetas' => [], //see null value should be included
///query works but also included null associated models
$q2= $this->WpPosts->find()->contain('WpPostmetas', function ($q) {
return $q
->where(['meta_key LIKE' => '%lead_data%' ]);
})
->where(['WpPosts.post_date >=' => $searchDate ])
->order(['WpPosts.id' => 'DESC'])
->enableHydration(true);
Using the POST method $data = $this->request->getData(); , I get the archive:
[
'category_id' => '62',
'title' => 'Name-1',
'body' => '<p>Text</p>
',
'price' => '30',
'is_new' => '1',
'img' => [
'tmp_name' => 'D:\Web\OpenServer\userdata\temp\php70D9.tmp',
'error' => (int) 0,
'name' => 'IronMan.jpg',
'type' => 'image/jpeg',
'size' => (int) 131830
]
]
By preparing these data for the record in the database:
$product = $this->Products->patchEntity($product, $data);
But the patchEntity() method cuts out all the information about the image.
I get:
object(App\Model\Entity\Product) {
'category_id' => (int) 62,
'title' => 'Name-1',
'body' => '<p>Text</p>
',
'price' => (float) 30,
'is_new' => (int) 1,
'img' => '', // <--- empty :(
'[new]' => true,
'[accessible]' => [
'category_id' => true,
'title' => true,
'body' => true,
'price' => true,
'img' => true,
'is_new' => true,
'created' => true,
'modified' => true,
'category' => true
],
'[dirty]' => [
'category_id' => true,
'title' => true,
'body' => true,
'price' => true,
'is_new' => true,
'img' => true
],
'[original]' => [],
'[virtual]' => [],
'[errors]' => [],
'[invalid]' => [],
'[repository]' => 'Products'
}
It can be fixed? Tell me at least about. Thank you.
When patching/creating an entity, the data is bein marshalled according to the respective columns data type, as you can see for other properties like price, which is converted from a string to a float.
Your img column is probably of the type string, causing the marshaller to convert the data accordingly (see \Cake\Database\Type\StringType::marshal()).
There are various ways to avoid that, for example using a different property name that doesn't map to an existing column, like img_upload, and then after moving the upload, manually set the resulting filesystem path to the img property and save that.
That could also be done in the beforeMarshal event in your ProductsTable class, so that the view template can continue to use the img property:
public function beforeMarshal(
\Cake\Event\Event $event,
\ArrayObject $data,
\ArrayObject $options
) {
if (isset($data['img'])) {
$data['img_upload'] = $data['img'];
unset($data['img']);
}
}
You could also create a custom database type for the img column, one which doesn't marshal the data to a string, but just passes it on:
namespace App\Database\Type;
use Cake\Database\Type;
class FileType extends Type
{
public function marshal($value)
{
return $value;
}
}
You'd have to assign the filesystem path anyways though, you'd basically just avoid using a separate/temporary property.
See also
Cookbook > Database Access & ORM > Saving Data > Modifying Request Data Before Building Entities
Cookbook > Database Access & ORM > Database Basics > Adding Custom Types
I do not know how much this is correct, but in the end I did the following and everything works as I need:
In ProductsController:
public function add()
{
$product = $this->Products->newEntity();
if ($this->request->is('post')) {
$data = $this->request->getData();
$product = $this->Products->patchEntity($product, $data);
// If there is a picture that we checked (by the method of validationDefault, when calling patchEntity) and have already uploaded to the server in a temporary folder, then
if($product->img_upload['name']){
// We call the user method of processing the downloaded image
$product = $this->_customUploadImg($product);
// Leave only the name of the new file, adding it to the new property, with the name corresponding to the name of the table in the database
$product->img = $product->img_upload['name'];
}
// Delete an unnecessary property
unset($product->img_upload);
if ($this->Products->save($product)) {
// ...
}
// ...
}
In Product.php:
class Product extends Entity{
protected $_accessible = [
'category_id' => true,
'title' => true,
'body' => true,
'price' => true,
'is_new' => true,
'created' => true,
'modified' => true,
'category' => true,
// 'img' => true,
// Here we specify not 'img' as in the database table, but 'img_upload', in order for ProductsController not to delete our data file about the uploaded file when patchEntity was called.
'img_upload' => true,
];
}
In ProductsTable.php:
public function validationDefault(Validator $validator)
{
//...
$validator
->allowEmpty('img_upload', 'create')
->add('img_upload', [
'uploadError' => [
'rule' => 'uploadError',
'message' => 'Error loading picture'
],
'mimeType' => [
'rule' => ['mimeType', ['image/jpeg', 'image/jpg', 'image/png', 'image/gif']],
'message' => 'Only image files are allowed to be uploaded: JPG, PNG и GIF'
],
'fileSize' => [
'rule' => ['fileSize', '<=', '2MB'],
'message' => 'The maximum file size should be no more than 2 MB'
]
]);
//...
}
In add.ctp:
echo $this->Form->create($product, ['type' => 'file']) ?>
// ...
echo $this->Form->control('img_upload', ['type' => 'file', 'label'=>'Product photo']);
//...
Thanks "ndm" and "mark"!
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'm trying to make an object which has all the fields needed to save a Product, its n variants and its n images. But for some reason the saveAll() is not working.
I tried saving an array of products with its variants and images, but no luck. It saved only the products as well. I do have the relations set up in the models
Product.php:
public $hasMany = array(
'product_variant' => array(
'className' => 'ProductVariant',
'foreignKey' => 'product_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'product_image' => array(
'className' => 'ProductImage',
'foreignKey' => 'product_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
),
'product_mercadolibre' => array(
'className' => 'ProductMercadolibre',
'foreignKey' => 'product_id',
'dependent' => false,
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
ProductVariant.php:
//The Associations below have been created with all possible keys, those that are not needed can be removed
/**
* belongsTo associations
*
* #var array
*/
public $belongsTo = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
and ProductImage.php:
/**
* belongsTo associations
*
* #var array
*/
public $belongsTo = array(
'Product' => array(
'className' => 'Product',
'foreignKey' => 'product_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
Here is the Porduct object:
[Product] => Array
(
[product_group_id] => 418112473
[handle] => advocate-ct-circa
[title] => Advocate CT Circa
[body] => :html_body:
[vendor] => Britax
[type] => Car Seats
[tags] => 0-3m, 12-18m, 18-24m, 24+m, 3-6m, 6-12m, auto asientos, britax, car seats, Dic152015, GooglePLA, hotsale-equipo, Niña, Niño, rn-18, Unisex
[published_at] => 2015-06-24T01:02:00-05:00
[published_scope] => global
[option1_name] => Title
[option1_value] => Default Title,
[image_src] => https://cdn.shopify.com/s/files/1/0154/0015/products/circa.jpg?v=1447768099
[ProductVariant] => Array
(
[variant_id] => 1096438833
[title] => Default Title
[option1_name] => Default Title
[option2_name] =>
[option3_name] =>
[variant_sku] => E9LT95Q - E1A265Q
[variant_grams] => 0
[variant_inventory_tracker] => shopify
[variant_inventory_qty] => 4
[variant_inventory_policy] => deny
[variant_fulfillment_service] => manual
[variant_price] => 8999.00
[variant_compare_at_price] =>
[variant_requires_shipping] => 1
[variant_taxable] =>
[variant_barcode] =>
[variant_image] => 1151565069
[variant_weight_unit] => kg
)
[PorductImage] => Array
(
[variant_image_id] => 1225124
[variant_image] => https://cdn.shopify.com/s/files/1/0154/0015/products/2780-ca4_2d26fff2-368d-4271-bd13-c344b5d08fb7.jpg?v=1447768100
)
)
Here is the code I used to save the product object.
$this->Product->create();
if ($this->Product->saveAll($singleProduct)) {
} else {
echo "Fallo guardar " . $singleProduct['handle'] . "<br>";
}
$singleProduct holds the object before mentioned.
All this saves only the Product model but not the variants or images. What am I doing wrong here? D:
EDIT:
I tried saveAll() with an array of products like so:
[
{
"Product": {
"product_group_id": 418112473,
"handle": "advocate-ct-circa",
"title": "Advocate CT Circa",
"body": "HTML-Body",
"vendor": "Britax",
"type": "Car Seats",
"tags": "0-3m, 12-18m, 18-24m, 24+m, 3-6m, 6-12m, auto asientos, britax, car seats, Dic152015, GooglePLA, hotsale-equipo, Niña, Niño, rn-18, Unisex",
"published_at": "2015-06-24T01:02:00-05:00",
"published_scope": "global",
"option1_name": "Title",
"option1_value": "Default Title,",
"image_src": "https://cdn.shopify.com/s/files/1/0154/0015/products/circa.jpg?v=1447768099"
},
"ProductVariant": [
{
"variant_id": 1096438833,
"title": "Default Title",
"option1_name": "Default Title",
"option2_name": null,
"option3_name": null,
"variant_sku": "E9LT95Q - E1A265Q",
"variant_grams": 0,
"variant_inventory_tracker": "shopify",
"variant_inventory_qty": 4,
"variant_inventory_policy": "deny",
"variant_fulfillment_service": "manual",
"variant_price": "8999.00",
"variant_compare_at_price": null,
"variant_requires_shipping": true,
"variant_taxable": false,
"variant_barcode": "",
"variant_image": 1151565069,
"variant_weight_unit": "kg"
}
],
"ProductImage": [
{
"variant_image_id": 1151565069,
"variant_image": "https://cdn.shopify.com/s/files/1/0154/0015/products/circa.jpg?v=1447768099"
},
.....
]
},
{
"Product": {
"product_group_id": 418498017,
"handle": "advocate-ct-tahoe",
"title": "Advocate CT Tahoe",
"body": "HTML_BOdY",
"vendor": "Britax",
"type": "Car Seats",
"tags": "0-3m, 12-18m, 18-24m, 24+m, 3-6m, 6-12m, auto asientos, britax, car seats, Dic152015, GooglePLA, hotsale-equipo, Niña, Niño, rn-18, Unisex",
"published_at": "2015-06-24T01:02:00-05:00",
"published_scope": "global",
"option1_name": "Title",
"option1_value": "Default Title,",
"image_src": "https://cdn.shopify.com/s/files/1/0154/0015/products/tahoe.jpg?v=1447768138"
},
"ProductVariant": [
{
"variant_id": 1097451593,
"title": "Default Title",
"option1_name": "Default Title",
"option2_name": null,
"option3_name": null,
"variant_sku": "E1A265N",
"variant_grams": 0,
"variant_inventory_tracker": "shopify",
"variant_inventory_qty": 2,
"variant_inventory_policy": "deny",
"variant_fulfillment_service": "manual",
"variant_price": "8999.00",
"variant_compare_at_price": null,
"variant_requires_shipping": true,
"variant_taxable": false,
"variant_barcode": "",
"variant_image": null,
"variant_weight_unit": "kg"
}
],
"ProductImage": [
{
"variant_image_id": 1152463301,
"variant_image": "https://cdn.shopify.com/s/files/1/0154/0015/products/tahoe.jpg?v=1447768138"
},
.......
]
}
]
And Im back to square one D: It only saves the product images. Ive checked the models are corrected now. But it wont save the variants or the images.
Any thoughts on this? o.o
Your data is not structured properly. To be able to save hasMany data, you have to feed the following to saveAll():
[Product] => Array
(
[product_group_id] => 418112473
[handle] => advocate-ct-circa
[title] => Advocate CT Circa
[body] => :html_body:
[vendor] => Britax
[type] => Car Seats
[tags] => 0-3m, 12-18m, 18-24m, 24+m, 3-6m, 6-12m, auto asientos, britax, car seats, Dic152015, GooglePLA, hotsale-equipo, Niña, Niño, rn-18, Unisex
[published_at] => 2015-06-24T01:02:00-05:00
[published_scope] => global
[option1_name] => Title
[option1_value] => Default Title,
[image_src] => https://cdn.shopify.com/s/files/1/0154/0015/products/circa.jpg?v=1447768099
)
[ProductVariant] => Array
(
Array
(
[variant_id] => 1096438833
[title] => Default Title
[option1_name] => Default Title
[option2_name] =>
[option3_name] =>
[variant_sku] => E9LT95Q - E1A265Q
[variant_grams] => 0
[variant_inventory_tracker] => shopify
[variant_inventory_qty] => 4
[variant_inventory_policy] => deny
[variant_fulfillment_service] => manual
[variant_price] => 8999.00
[variant_compare_at_price] =>
[variant_requires_shipping] => 1
[variant_taxable] =>
[variant_barcode] =>
[variant_image] => 1151565069
[variant_weight_unit] => kg
)
)
[ProductImage] => Array
(
Array
(
[variant_image_id] => 1225124
[variant_image] => https://cdn.shopify.com/s/files/1/0154/0015/products/2780-ca4_2d26fff2-368d-4271-bd13-c344b5d08fb7.jpg?v=1447768100
)
)
EDIT: Fix Relationships
Replace your Product relationships with the following:
Product.php:
public $hasMany = array(
'ProductVariant' => array(
'className' => 'ProductVariant',
'foreignKey' => 'product_id',
'dependent' => false
),
'ProductImage' => array(
'className' => 'ProductImage',
'foreignKey' => 'product_id',
'dependent' => false
),
'ProductMercadolibre' => array(
'className' => 'ProductMercadolibre',
'foreignKey' => 'product_id',
'dependent' => false
)
);
See
Model::saveAssociated() in Cookbook 2.x | Saving your Data
Ok i think i did this wrong.
This is my current collection however I need to order these by percentage.
array (
'_id' => new MongoId("505006de36314b4b27000001"),
'status' => 'pending',
'store_id' => new MongoId("505006de36314b4b27000000"),
'minspendone' => '50.00',
'cashbackone' => '1.50',
'percentageone'▼ => '0.03',
'minspendtwo' => '100.00',
'cashbacktwo' => '3.00',
'percentagetwo' => '0.03',
'minspendthree' => '',
'cashbackthree' => '',
'percentagethree' => '',
'minspendfour' => '',
'cashbackfour' => '',
'percentagefour' => '',
)
so would I better of changing it to the following
array (
'_id' => new MongoId("505006de36314b4b27000001"),
'status' => 'pending',
'store_id' => new MongoId("505006de36314b4b27000000"),
'offers' => array(
'minspend' => '50.00',
'cashback' => '1.50',
'percentage' => '0.03'),
array(
'minspend' => '100.00',
'cashback' => '3.00',
'percentage' => '0.03'))
)
could someone please advise me.
Russell,
If you wish to use the mongodb internal sort function you will need to split the array elements into separate documents.
Mongodb sort works to set the order of whole documents returned, rather the return order of elements within documents.
Hope that clarifies.