Modifying Model data in beforeSave of Behavior when using saveAll - php

I'm trying to write a Meta behavior for a project I am working on that will allow me to assign custom variables/attributes to a model, similar to how you can create custom fields in a wordpress post.
I have created a Meta behavior that binds the meta model to the model it is acting on and also has a beforeSave callback that loops through the models data variable and puts the model name into the meta array.
Everything is saving but when I check the database the model field is coming back empty.
The database structure for the Meta is
id - A unique if for the meta
model - The name of the model that this meta entry is associated with
foreign_id - The id of the above model that this meta entry is associated with
key - Name of the meta variable
value - Value of the meta variable
The data that comes to the saveAll function from the form is
Array
(
[Page] => Array
(
[id] => 12
[name] => Test
[slug] => a/b/c/d
[layout] => 0
[body] => Test multilevel
)
[Meta] => Array
(
[0] => Array
(
[id] => 1
[key] => page_title
[value] => About Us
)
[1] => Array
(
[id] => 6
[key] => test4
[value] => test
)
[2] => Array
(
[key] => test3
[value] => lala
)
)
)
and after it has run through the behavior beforeSave it is
Array
(
[Page] => Array
(
[id] => 12
[name] => Test
[slug] => a/b/c/d
[layout] => 0
[body] => Test multilevel
[modified] => 2010-05-04 15:56:54
)
[Meta] => Array
(
[0] => Array
(
[id] => 1
[key] => page_title
[value] => About Us
[model] => Page
)
[1] => Array
(
[id] => 6
[key] => test4
[value] => test
[model] => Page
)
[2] => Array
(
[key] => test3
[value] => lala
[model] => Page
)
)
)
The code from the behavior is
<?php
/**
* Meta Model Behavior
*
* Adds custom variables to models
*
**/
class MetaBehavior extends ModelBehavior {
/**
* Contains configuration settings for use with individual model objects.
* Individual model settings should be stored as an associative array,
* keyed off of the model name.
*
* #var array
* #access public
* #see Model::$alias
*/
var $__settings = array();
var $__defaults = array(
'class' => 'Meta',
'foreign_key' => 'foreign_id',
'dependent' => true,
'auto_bind' => true
);
/**
* Initiate Meta Behavior
*
* #param object $model
* #param array $config
* #return void
* #access public
*/
function setup(&$model, $settings = array()) {
$default = $this->__defaults;
$default['conditions'] = array('Meta.model' => $model->alias);
if (!isset($this->__settings[$model->alias])) {
$this->__settings[$model->alias] = $default;
}
$this->__settings[$model->alias] = array_merge($this->__settings[$model->alias], ife(is_array($settings), $settings, array()));
if ($this->__settings[$model->alias]['auto_bind']) {
$hasManyMeta = array(
'Meta' => array(
'className' => $this->__settings[$model->alias]['class'],
'foreignKey' => $this->__settings[$model->alias]['foreign_key'],
'dependent' => $this->__settings[$model->alias]['dependent'],
'conditions' => $this->__settings[$model->alias]['conditions']
)
);
$metaBelongsTo = array(
$model->alias => array(
'className' => $model->alias,
'foreignKey' => $this->__settings[$model->alias]['foreign_key']
)
);
$model->bindModel(array('hasMany' => $hasManyMeta), false);
$model->Meta->bindModel(array('belongsTo' => $metaBelongsTo), false);
}
}
function beforeSave(&$model) {
foreach($model->data[$this->__settings['class']] as $key => $value) {
$model->data[$this->__settings['class']][$key]['model'] = $model->alias;
}
return true;
}
} // End of MetaBehavior
?>
I have a feeling it may be because of the relationships and the saveall is using the passed data (original) when saving the associations.
The only other way I thought of doing it would be to remove the relationships and put some code into the afterSave function of the behavior to handle the saving and then put some other code into the afterFind to retrieve them.
Any ideas?
Cheers,
Dean

I don't believe you'll have any luck with this as Model::saveAll() doesn't call beforeSave() at all. In fact, it loads associations before then calling $this->save().
Line 1652 of the Model source shows the associations being loaded prior to any real call to Model::__save() that doesn't just validate the data.
What this means is, at first glance, it looks like the magic bindModel() call in your MetaBehavior won't have any affect. To be honest, I'd not really bother with something like that as it's just simpler to set the association in your Page Model class definition.
Of course, you could:
Create an afterSave in your MetaBehavior that facilitates the saving of the Meta Model data or,
Override saveAll() in the model to add the binding and then call parent::saveAll()
Ultimately, I feel it's more sane to utilise Model::saveAll() and set a real association in your Model class definition anyway. Utilising behaviours to promote DRY practices is very noble but it doesn't mean you need to make things less trivial as part of a development methodology that you or another person follows
TLDR; You've already added Meta to your behaviors array, adding an association is only another few lines of code within the same file.

Related

Laravel keyBy - Sorting results from linked table

I have a BlogPost model that has a belongsToMany relationship called images, this relationship uses a link table to associate the blog post id with the image id.
When pulling in the data for a blog post, the images property looks like this:
[images] => Array
(
[0] => Array
(
[id] => 8304
[original] => /img/blog/2017/5/wifiradio_original_59089cae673db.png
[large] => /img/blog/2017/5/wifiradio_large_59089cae673db.jpg
[medium] => /img/blog/2017/5/wifiradio_medium_59089cae673db.jpg
[small] => /img/blog/2017/5/wifiradio_small_59089cae673db.jpg
[name] => wifiradio.png
[alt] => wifiradio.png
[created_at] => 2017-05-02 14:50:22
[updated_at] => 2017-05-02 14:50:22
[pivot] => Array
(
[blog_post_id] => 47749
[image_id] => 8304
[id] => 136949
[type] => featured
)
)
)
)
The array key is not useful as the numeric value, i would like the array key to be the value of pivot->type.
Laravels keyBy method almost does what I need but I can not get it to work directly on the data returned.
Is it possible to use keyBy from within the model so that data is always returned in a useable format?
I solved this by formatting the array within the controller.
Here's the function I created if anyone has a similair problem:
public function formatPosts($posts){
foreach($posts as $post){
$images = collect($post->images);
unset($post->images);
$post->images = $images->keyBy('pivot.type');
}
return $posts;
}

Why does Doctrine\ORM\Configuration's "DoctrineProxies" Object contain the Universe?

In my ORM code I have an Entity with a field fined like so:
//part of entity class Item:
/** #Column(name="product_id", type="integer") */
private $productId;
I then executed this code:
//3 lines ~straight out of Doctrine configuration to get EntityManager
include 'config/doctrine-config.php';
$config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode);
$em = EntityManager::create($dbParams, $config);
//my own code to retrieve an entity instance:
$instance = $em->find(Item::class, 2);
print_r($instance);
And this is the output I get (skipping few other similar properties):
Application\Entity\Item Object
(
[id:Application\Entity\Item:private] => 2
[description:Application\Entity\Item:private] => Product Kit
[productId:Application\Entity\Item:private] => -1
)
Note how there are 6 (six) lines above that came out of print_r() function.
And everything was fine, Until
Next, I have changed the $productId column to ManyToOne Relationship on my Item Entity class, like so:
/**
* #ManyToOne(targetEntity="Product", inversedBy="id")
* #JoinColumn(name="product_id", referencedColumnName="id")
*/
private $productId;
I ran the same code.
OUT CAME THE UNIVERSE OF 2,392,600 LINES, WHAT?
Two million, three hundred and ninety two thousand, six hundred lines lines of print_r output.
looking at the print-out I see that DoctrineProxies\__CG__\Application\Entity\Product Object contains 2,392,564 lines printed by print_r
Question:
What is exactly in this object and why is it so big as to take up nearly 300Mb of disk space when printed out?
I cannot help but wonder if such complexity is apt to cause performance issues in every-day code. For example, I am not printing out the contents of the $instance variable in my every-day code, but I surely return the humongousness from a method call. Does that mean it is a 300Mb variable that gets passed from i.e. the $em->find(Item::class, 2); call above?
(Very) Partial Listing
Application\Entity\Item Object
(
[id:Application\Entity\Item:private] => 2
[description:Application\Entity\Item:private] => Product Kit
[ProductId:Application\Entity\Item:private] => DoctrineProxies\__CG__\Application\Entity\Product Object
(
[__initializer__] => Closure Object
(
[static] => Array
(
[entityPersister] => Doctrine\ORM\Persisters\Entity\BasicEntityPersister Object
(
[class:protected] => Doctrine\ORM\Mapping\ClassMetadata Object
(
[name] => Application\Entity\Product
[namespace] => Application\Entity
[rootEntityName] => Application\Entity\Product
[inheritanceType] => 1
[generatorType] => 5
[fieldMappings] => Array
(
[id] => Array
(
[fieldName] => id
[type] => integer
[scale] => 0
[length] =>
[unique] =>
[nullable] =>
[precision] => 0
[columnName] => id
[id] => 1
)
[fieldNames] => Array
(
[id] => id
[description] => description
)
[columnNames] => Array
(
[id] => id
[description] => description
)
[idGenerator] => Doctrine\ORM\Id\AssignedGenerator Object
[reflClass] => ReflectionClass Object
(
[name] => Application\Entity\Product
)
[namingStrategy:protected] => Doctrine\ORM\Mapping\DefaultNamingStrategy Object
[instantiator:Doctrine\ORM\Mapping\ClassMetadataInfo:private] => Doctrine\Instantiator\Instantiator Object
)
[conn:protected] => Doctrine\DBAL\Connection Object
(
[_conn:protected] => Doctrine\DBAL\Driver\PDOConnection Object
(
)
[_config:protected] => Doctrine\ORM\Configuration Object
(
[_attributes:protected] => Array
(
[metadataCacheImpl] => Doctrine\Common\Cache\ArrayCache Object
(
[data:Doctrine\Common\Cache\ArrayCache:private] => Array
(
[dc2_b1e855bc8c5c80316087e39e6c34bc26_[Application\Entity\Item$CLASSMETADATA][1]] => Array
(
[0] => Doctrine\ORM\Mapping\ClassMetadata Object
(
[name] => Application\Entity\Item
[namespace] => Application\Entity
[rootEntityName] => Application\Entity\Item
[customGeneratorDefinition] =>
[customRepositoryClassName] =>
[isMappedSuperclass] =>
[isEmbeddedClass] =>
[parentClasses] => Array
[BAZILLION LINES redacted for brevity]
You can't dump a proxy object without XDebug or similar tools (which limit the dumped object size).
The problem is really, really simple:
Proxy -> references EntityManager -> references UnitOfWork -> contains Proxy
This obviously leads to a recursive data-structure dump, which in turn leads to a mess any time you try to dump it without sensible limits.
DoctrineProxies\__CG__\Application\Entity\Product
is a proxy class... which means that doctrine doesn't actually fetch the entity from the database (for performance) unless it is needed (i.e. calling $product->getName() those proxy Classes are in a recursive loop with eachother and are VERY large as you saw... most of the information there you don't really need unless you are diving deep ... you should never use print_r ... in the new symfony 2.7+ i think there is a function called dump() in debug mode ... if you use that to print the entity it has loop protection and it just shows reference numbers ... you can also use the \Doctrine\Common\Util\Debug::dump() that also will print out a smaller list than 2^234234234 lines ...

Pagination on custom Array in CakePHP 3

I am having custom array like:
[business] => Array
(
[55] => Array
(
[id] => 1
[name] => abc
[contact] => 1325467897
),
[96] => Array
(
[id] => 5
[name] => xyz
[contact] => 9876543210
)
)
This array is derived from conditional (if-else) multiple queries. So, I just want to add pagination on array. Can somebody suggest me how to add custom pagination on this type of array using standard paginator of CakePHP 3.
The PaginatorComponent can take a Cake\ORM\Query as it's thing to paginate, so I would suggest passing your compiled query object to the paginator.
So in your controller you could do something like the following.
public function example()
{
$this->paginate = ['limit' => 5];
$query = $this->Examples->find();
if ($something === 'foo') {
$query->where(['foo' => $something]);
}
if ($wtfbbq) {
$query->contain(['Wtfs', 'Barbeques']);
}
$results = $this->paginate($query);
}
This will produce a Cake\ORM\Query with the pagination built in. Then you can adjust the pagination parameters as you see fit, by checking the book page on the Paginator.

Cakephp, validate multiple models manually

I have a form that produces the following array upon submition (see below).
I am using this data in my controller to perform several operations, after which I save each one individually. (Saving them all at once is not an option).
What I would need to do is to find a way to validate each of this models.
I have tried already:
$this->Model->set($pertinentData);
$this->Model2->set($pertinentData);
if($this->Model->validates() && $this->Model2->validates()){
//Do whatever
}
This produces unaccurate results, says it validates when I can see it doesn't and viceversa.
Anybody has any idea of a viable option? Ain't there a way to create a tableless model where I can define validation rules for these fields like:
Order.package_id
User.first_name
etc...
Any idea is appreciated. Below the array that the form produces.
Thanks.
Array
(
[Order] => Array
(
[id] => 15
[package_id] => 1743
[tariff_id] => 5470
[tarjeta] => 332
[numero_tarjeta] => 121204045050
[vencimiento_tarjeta] => 10/20
[cod_tarjeta] => 170
[titular_tarjeta] => JESUS CRISTO
[tarjeta_nacimiento] => 22/04/1988
[tarjeta_pais] => Argentina
[tarjeta_provincia] => Buenos Aires
[tarjeta_ciudad] => Ciudad
[tarjeta_cp] => 1428
[tarjeta_calle] => Calle
[tarjeta_numero] => 1477
[tarjeta_piso] => 2
)
[User] => Array
(
[id] =>
[email] => bla8#gmail.com
[phone] => 1568134449
[first_name] => Jesus
[last_name] => Something
[documento_tipo] => dni
[dni] => 335556666
[nacionalidad] => argentino
[birthdate] => 22/04/2019
)
[OrdersCompanion] => Array
(
[1] => Array
(
[first_name] => Chango
[last_name] => Mas
[documento_tipo] => dni
[dni] => 445556666
[nacionalidad] => argentino
[birthdate] => 30/02/2010
)
[1] => Array
(
[first_name] => Chango
[last_name] => Mas
[documento_tipo] => dni
[dni] => 445556666
[nacionalidad] => argentino
[birthdate] => 30/02/2010
)
)
)
You can usea tableless model by defining $useTable= false in the model. Like this
public $useTable = false;
Define all your custom validation and, of course, your schema (since your model has no table you have to define manually the model schema). Then in your controller, you must first indicate that it has no model, and then declare the $model variable. This is to avoid the automatic model-controller binding of cakePHP, your controller would look like this
public $useModel = false;
$model = ClassRegistry::init('ContactOperation');
Now your model is related to your controller as you want, and you can easily make your custom validation, previously defined.
$model->set($this->request->data);
if($model->validates()) {
$this->Session->setFlash(_('Thank you!'));
// do email sending and possibly redirect
// elsewhere for now, scrub the form
// redirect to root '/'.
unset($this->request->data);
$this->redirect('/');
} else {
$this->Session->setFlash(_('Errors occurred.'));
// display the form with errors.
}
You can find more detail from here

Magento Event / Observer Object getData problems

I have a Magento Module I built that allows you to save a string via the admin interface to the core config table in Magento. I have an observer setup to run a method when the string is saved in the backend. I'm killing myself trying to intercept the string and encode it before saving it to the database.
So when my event is triggered it runs this :
public function myModSaved($observer)
{
echo "<h1> WOWSERS IT ACTUALLY WORKED!!</h1>";
$data = $observer->getData();
print_r($data);
}
The output looks like this:
Array ( [event] => Varien_Event Object ( [_observers:protected] => Varien_Event_Observer_Collection Object ( [_observers:protected] => Array ( ) ) [_data:protected] => Array ( [website] => [store] => [name] => admin_system_config_changed_section_mymodule_section ) [_hasDataChanges:protected] => [_origData:protected] => [_idFieldName:protected] => [_isDeleted:protected] => [_oldFieldsMap:protected] => Array ( ) [_syncFieldsMap:protected] => Array ( ) ) [website] => [store] => )
Now there is only one string being written to the database, how do I get that string before it is saved, then add my new modified string to the object so that one saves in the DB?
Thanks in advance!
In your system.xml file where the config field is defined add this declaration:
<backend_model>adminhtml/system_config_backend_encrypted</backend_model>
That will take care of all the encoding/decoding both before and after the database access. Also to get the 'password' type field it is customary to use this:
<frontend_type>obscure</frontend_type>

Categories