Check Eloquent wasChanged on hasMany/belongsTo Model relationship - eloquent

I want to check the wasChanged() status of my child/detail models, just as I do with the parent/header model in the hasMany(parent) :: belongsTo (child) relationship.
I've got code that looks like this:
$parent = Parent::updateOrCreate([ ... ], [ ... ]);
if ($parent->wasChanged()) {
// do something useful
}
foreach ($contents as $child) {
$parent->children()->updateOrCreate([ keys ],[ data ]);
// Now I want to check if the Child model named Contents was changed.
// if ($parents->children()->wasChanged()) { ... // does not work
}
How can I accomplish this?

Not sure what $contents does, but, you could always do something like this to accomplish the idea of what you want done:
$parent = Parent::updateOrCreate([ keys ],[ data ]);
$parent->load('children');
if ($parent->wasChanged()) {
// do something useful
}
$parent->children()->updateOrCreate([ keys ],[ data ]);
foreach($parent->children() as $child) {
if ($child->wasChanged()) {
// do something useful
}
}

I'm not even sure this is a correct answer, but what I ended up doing and seems to be working is adding save() calls after each createOrUpdate() call. The Laravel documentation suggests this is not how it is supposed to work, but it's hard to argue with empirical evidence.
$parent = Parent::updateOrCreate([ keys ],[ data ]);
if ($header->wasChanged() || $header->wasRecentlyCreated) {
// Do interesting work
}
$parent->save();
foreach ($myInterestingData as $newChildData) {
$child = $header->children()
->updateOrCreate( ... keys/values from $newChildData ...);
if ($child->wasChanged() || $child->wasRecentlyCreated) {
// Do boring work
}
$child->save();
}

Related

How to dereference entries in a hash of arrays of hashes

I have a data structure that was built like so:-
$ICVDWKey = "($LLX $LLY) ($URX $URY)";
...
push #{$ICVDWStats{$ICVDWKey}}, {
ICVDensity=>$Density,
ICVLayerArea=>$LayerArea,
ICVWindowArea=>$WindowArea
};
I can dereference its contents like so...
foreach $ICVDWKey (#AllICVDWCoords) {
foreach (#{$ICVDWStats{$ICVDWKey}}) {
$ICVDensity = $_->{ICVDensity};
$ICVLayerArea = $_->{ICVLayerArea};
$ICVWindowArea = $_->{ICVWindowArea};
...
}
}
...and everything is good. However, I am running into problems when another data structure is built the same way and I need to check its contents when looping through the original data structure, above. Here is an example...
foreach $ICVDWKey (#AllICVDWCoords) {
foreach (#{$ICVDWStats{$ICVDWKey}}) {
$ICVDensity = $_->{ICVDensity};
$ICVLayerArea = $_->{ICVLayerArea};
$ICVWindowArea = $_->{ICVWindowArea};
...
if (exists ($ICC2DWStats{$ICVDWKey})) {
$ICC2Density = $_->{ICC2Density};
$ICC2LayerArea = $_->{ICC2LayerArea};
$ICC2WindowArea = $_->{ICC2WindowArea};
...
}
}
}
I know the if exists $ICVDWKey matching is working properly, but I cannot cleanly dereference the contents of the ICC2DWStats hash data. What is the proper what to retrieve the ICC2* data when $ICVDWKey keys match between the two data structures? I am sure it is the $_ in the ICC2* references, but I do not know what should be used instead.
Thanks!
Instead of using $_ which represents a structure other than the $ICC2DWStats hashref which you want, you need to explicitly specify the hash and key of the actual hasn you want to extract from:
for $ICVDWKey (#AllICVDWCoords) {
for (#{$ICVDWStats{$ICVDWKey}}) {
$ICVDensity = $_->{ICVDensity};
$ICVLayerArea = $_->{ICVLayerArea};
$ICVWindowArea = $_->{ICVWindowArea};
...
if (exists ($ICC2DWStats{$ICVDWKey})) {
$ICC2Density = $ICC2DWStats->{$ICVDWKey}{ICC2Density};
$ICC2LayerArea = $ICC2DWStats->{$ICVDWKey}{ICC2LayerArea};
$ICC2WindowArea = $ICC2DWStats->{$ICVDWKey}{ICC2WindowArea};
...
}
}
}
Note that you should be using use strict; and use warnings;.

DoctrineODM Priming of Multi-Level References

Sorry for the awkward title but I have no better naming for the issue (comments on how to properly name the question are welcome).
Let's say I have 3 Documents:
Category
Product
Version
A Category has many Products. A Product has many Versions.
Now I want to Query for all Categories and list all Products and Versions of each Category.
I know about priming priming.
Is it possible to write a query like:
$qb = $dm->createQueryBuilder('Category')
->field('products')->prime(true)
->field('products.versions')->prime(true)
$query = $qb->getQuery();
Alright it seems that in the current state doctrine-odm does not support multi-level priming. This is a known issue on GitHub.
I found a solution in the GitHub Issue that passes a closure to the prime method to allow at least two level priming. Hope this helps someone.
$myPrimer = function(DocumentManager $dm, ClassMetadata $class, array $ids, array $hints) {
$qb = $dm->createQueryBuilder($class->name)
->field($class->identifier)->in($ids);
if ( ! empty($hints[Query::HINT_SLAVE_OKAY])) {
$qb->slaveOkay(true);
}
if ( ! empty($hints[Query::HINT_READ_PREFERENCE])) {
$qb->setReadPreference($hints[Query::HINT_READ_PREFERENCE], $hints[Query::HINT_READ_PREFERENCE_TAGS]);
}
$results = $qb->getQuery()->toArray();
$nestedPrimers = array(
'address' => true, // List of fields to prime
);
$uow = $dm->getUnitOfWork();
$referencePrimer = new ReferencePrimer($dm, $uow);
foreach ($nestedPrimers as $fieldName => $primer) {
$primer = is_callable($primer) ? $primer : null;
$referencePrimer->primeReferences($class, $results, $fieldName, $hints, $primer);
}
};
The Closure can the be passed to the Primer:
->field('club')->prime($myPrime)

Is this a valid way to check if db_row exists?

I am working with Zend and I needed to check whether a row in the DB already exists (A simple solution to get rid of the duplicate key error I was getting). I tried several things but nothing seemed to work... (for example the Zend_Validate_Db_NoRecordExists method)
So I wrote the following the code and I was wondering if this is a valid way to do it, or if I should do things differently:
In the model:
$where = $condition = array(
'user_id = ' . $user_id,
'page_id = ' . $page_id
);
$check = $this->fetchRow($where);
if(count($check) > 0) {
return null;
}else{
// Here I create a new row, fill it with data, save and return it.
}
And then in my view:
if($this->result != null) { /* do stuff */ }else{ /* do other stuff */ }
It does work but it does seem to take more time (duh, because of the extra query) and I am a bit unsure whether I should stick with this..
Any recommendation is welcome :)
Assuming you have coded your function in your controller
$row = $this->fetchRow($where); //If no row is found then $row is null .
if(!$row)
{
$row = $dbTb->createNew($insert); //$insert an associative array where it keys map cols of table
$row->save();
$this->view->row_not_found = true;
}
return $row;
In your view you can do this
if($this->row_not_found)
{
}else {
}

CakePHP SET error => false FOR All Forms

I want to handle application feedback regarding, in this case, form validation.
To do this I check for model validation in controller, using
// VALIDATE
if ($this->Event->validates($this->data))
{
// SAVE
$this->Event->create();
if ($this->Event->saveAll($this->data, array('validate' => false)))
{
$this->Session->setFlash('Evenimentul a fost salvat!', 'flash_admin_success');
$this->redirect(array('action' => 'index', 'admin' => true));
} else {
$this->Session->setFlash('Evenimentul nu a putut fi salvat. Va rugam sa incercati din nou!', 'flash_admin_error');
}
////////
$errors = 'O EROARE';
$this->set(compact('errors'));
}
else
{
// GET ERRORS to display it nicely :)
$errors = $this->Event->invalidFields();
$flash = '';
foreach($errors as $error)
{
$flash .= $error."<br />";
}
$this->Session->setFlash($flash, 'flash_admin_error');
}
I know that there is a way to get rid of form field errors using 'error' => false, but i want to set this for the entire application, thus for all fields in all forms.
It has to be there a way of setting that fot the object itself, and I would be gratefull if someone would tell me.
Thaks a lot!
Edit: This doesn't really disable error output, but will hide the error: go to webroot/css/cake.generic.css add display:none to selector div.error-message. That's the simplest way to achieve what you want that I can think of.
Though it may seem like a bit of an extreme approach to override a single property, you can achieve this by extend the core FormHelper. This will allow you to make Anh Pham's original suggestion the default for all FormHelper instances:
// app/views/helpers/app_form.php
App::import('Helper', 'Time');
class AppFormHelper extends FormHelper {
public $_inputDefaults = array('error' => false);
}
Now to use this as-is in CakePHP 1.3, you would have to use "AppForm" throughout your application to refer to this helper from now on (ie. $this->AppForm->input()). CakePHP 2.0 introduces helper aliasing to overcome this, but for now one has to resort to a bit of trickery to continue using "Form" instead. One blog post I found shows how to backport the functionality and another manages allow the helper to do it itself. I personally use the following without any problems:
// app/views/app.php
class AppView extends View {
function &_loadHelpers(&$loaded, $helpers, $parent = null) {
$return = parent::_loadHelpers($loaded, $helpers, $parent);
# rename App helpers (ie. AppHtml -> Html)
foreach ($return as $helperName => $helper) {
if (substr($helperName, 0, 3) === 'App') {
$newHelperName = substr($helperName, 3);
$return[$newHelperName] = $return[$helperName];
}
}
# done
return $return;
}
}
To use the new created classes above, just add the following to your AppController:
// app/app_controller.php
class AppController extends Controller {
public $helpers = array(/*...*/, 'AppForm');
public $view = array('App');
}

Symfony: exclude empty values from form save

I have a many to many relation between Product and Properties. I'm using embedRelation() in my Product form to edit a Product and it's Properties. Properties includes images which causes my issue. Every time I save the form the updated_at column is updated for file properties even when no file is uploaded.
Therefore, I want to exclude empty properties when saving my form.
I'm using Symfony 1.4 and Doctrine 1.2.
I'm thinking something like this in my ProductForm.class.php, but I need some input on how to make this work.
Thanks
class ProductForm extends BaseProductForm
{
public function configure()
{
unset($this['created_at'], $this['updated_at'], $this['id'], $this['slug']);
$this->embedRelation('ProductProperties');
}
public function saveEmbeddedForms($con = null, $forms = null)
{
if (null === $forms)
{
$properties = $this->getValue('ProductProperties');
$forms = $this->embeddedForms;
foreach($properties as $p)
{
// If property value is empty, unset from $forms['ProductProperties']
}
}
}
}
I ended up avoiding Symfony's forms and saving models instead of saving forms. It can be easier when playing with embedded forms. http://arialdomartini.wordpress.com/2011/04/01/how-to-kill-symfony%E2%80%99s-forms-and-live-well/
Solved it by checking if posted value is a file, and if both filename and value_delete is null I unset from the array. It might not be best practice, but it works for now.
Solution based on http://www.symfony-project.org/more-with-symfony/1_4/en/06-Advanced-Forms
class ProductPropertyValidatorSchema extends sfValidatorSchema
{
protected function configure($options = array(), $messages = array())
{
// N0thing to configure
}
protected function doClean($values)
{
$errorSchema = new sfValidatorErrorSchema($this);
foreach($values as $key => $value)
{
$errorSchemaLocal = new sfValidatorErrorSchema($this);
if(array_key_exists('value_delete', $values))
{
if(!$value && !$values['value_delete'])
{
unset($values[$key]);
}
}
// Some error for this embedded-form
if (count($errorSchemaLocal))
{
$errorSchema->addError($errorSchemaLocal, (string) $key);
}
}
// Throws the error for the main form
if (count($errorSchema))
{
throw new sfValidatorErrorSchema($this, $errorSchema);
}
return $values;
}
}