cakePHP 2 afterFind for Model - php

I inherited code and in the Model the previous developer used the afterFind, but left it open when afterFind is executed in case of many to many relation to that table. So it works fine when getting one element from that Model, but using the relations break it.
public function afterFind($results, $primary = false) {
foreach ($results as $key => $val) {
if (isset($results[$key]['Pitch'])) {
if (isset($results[$key]['Pitch']['expiry_date'])) {
if($results[$key]['Pitch']['expiry_date'] > time()) {
$results[$key]['Pitch']['days_left'] = SOMETHINGHERE;
} else {
$results[$key]['Pitch']['days_left'] = 0;
}
} else {
$results[$key]['Pitch']['days_left'] = 0;
}
To solve this issue I added that code after the 2nd line.
// if (!isset($results[$key]['Pitch']['id'])) {
// return $results;
//
Is there a better way to solve that? I think afterFind is quite dangerous if not used properly.

Related

Laravel, return all data on single model row recursively through relationships

Given a model and an id. Get all $fillable data for that row. This is including data from relationships between models, so if there's a relationship with another model. It needs to fetch all fillable data from the related model too. And if that related model has relationships, we need to follow those as well.
I've tried lots of stuff so far but it's all following the same general thought process. What I have so far on my most recent attempt is below. Further information: Each model has protected $fillable and an array called $getPossibleRelations which has a list of relationship names used by that model. There are __get functions for fillable, and possible relations on the models.
$item = $model->where('id',$id);
function deepProcess($item) {
$fillable = $item->find(1)->getFillable();
$item = call_user_func_array(array($item, 'select'), $fillable);//select only fillable fields
$possibleRelations = $item->find(1)->getPossibleRelations();
foreach ($possibleRelations as $rel) {//eager load any possible relations
$item = $item->with([
$rel => function($query) {//reaches here ok, below recursion fails
$query = deepProcess($query);
}
]);
}
return $item;
}
$item = deepProcess($item)->get()->toArray();
dd($item);
Within the eager load the query needs to somehow loop back through the same function. And it needs to make sure it doesn't go back through a relation it has already been through (I used get_class() to check for that in a previous attempt).
I'm a bit lost as to how I should do this
Here's another attempt I made which is also flawed in many obvious ways.
$item = $model->where('id',$id);
$checkedModels = [];
$result = [];
function deepFetch($item,&$result,&$checkedModels,$className) {
if (in_array($className,$checkedModels)) {
return; //we've already added bits from this model
}
array_push($checkedModels,$className);
if($className == 'Illuminate\Database\Eloquent\Collection') {
dd($item);
}
var_dump('loop count');
$fillable = $item->get()[0]->getFillable();
$possibleRelations = $item->get()[0]->getPossibleRelations();
foreach($item->select($fillable)->get() as $row) {
array_push($result,$row);
}
foreach ($possibleRelations as $rel) {
dd($item->get());
$newItem = $item->get()->$rel;
deepFetch($newItem,$result[$rel],$checkedModels,get_class($newItem));
}
}
deepFetch($item,$result,$checkedModels,get_class($model));
$item = $model->where('id',$id);
function deepProcess($item,$depth,$currentRel,$currentRelClasses) {
foreach ($depth->first()->getPossibleRelations() as $rel) {//eager load any possible relations
$newRel = $rel;
if ($currentRel !== '') {
$newRel = $currentRel.'.'.$rel;// $newRel example, dog.owner.addresses
}
$futureItemCollection = $depth->first()->$rel->first();
if (!$futureItemCollection) {
continue; // no relationship found from $depth through $rel
}
array_push($currentRelClasses, get_class($futureItemCollection));//we need to check for the future relationship before the recursive call
if (max(array_count_values($currentRelClasses)) > 1) {//if we've hit the same relation more than once then skip current iteration
continue;
}
// $fillable = $futureItemCollection->getFillable();
// $item = $item->with([$newRel => function($query) use($fillable) {
// call_user_func_array([$query, 'select'], $fillable);//select only fillable fields
// }]);
$item = $item->with($newRel);//selecting only fillable fields wasn't working, likely due to unexpected fields being required
$item = deepProcess($item, $depth->first()->$rel,$newRel,$currentRelClasses);
}
return $item;
}
$item = deepProcess($item,$item,'',[get_class($item->first())])->get()->toArray();
dd($item);
Mostly works, it's rough, and I couldn't get the select statement to work (which is why it's commented out and a plain with(relation) is used instead. It works for my purposes though

How do I build a query saying "get me all diplomas of which all steps are in this pivot table"?

I have three models in my laravel project, Step, Diploma and Pupil. This is what the relations look like:
class Pupil {
function steps() {
return $this->belongsToMany(Step::class);
}
function diplomas() {
return $this->belongsToMany(Diploma::class);
}
}
class Step {
function diploma() {
return $this->belongsTo(Diploma::class);
}
}
class Diploma {
function steps() {
return $this->hasMany(Step::class);
}
}
Now I have a form where the admin can check boxes of which steps a pupil has accomplished, which I then save by doing $pupil->steps()->sync($request['steps']);. Now what I want to do is find the diplomas of which all the steps have been accomplished and sync them too. But for some reason I can't figure out how to build that query. Is this clear? Would anyone like to help?
edit I now have this, but it's not as clean as I would like:
class Pupil {
public function hasCompleted(array $completedSteps)
{
$this->steps()->sync($completedSteps);
$diplomas = [];
foreach(Diploma::all() as $diploma) {
// First see how many steps does a diploma have...
$c1 = Step::where('diploma_id', $diploma->id)->count();
// Then see how many of those we completed
$c2 = Step::where('diploma_id', $diploma->id)->whereIn('id', $completedSteps)->count();
// If that's equal, then we can add the diploma.
if ($c1 === $c2) $diplomas[] = $diploma->id;
}
$this->diplomas()->sync($diplomas);
}
}
Instead of syncing the diplomas, as the steps could change in the future, what about pulling the diplomas via the completed steps when you need to know the diplomas?
class Pupil {
public function hasCompleted(array $completedSteps) {
$this->steps()->sync($completedSteps);
}
public function getCompletedDiplomas() {
$steps = $this->steps;
$stepIds = // get the step ids
$diplomaIdsFromSteps = // get the diploma Ids from the $steps
$potentialDiplomas = Diploma::with('steps')->whereIn('id', $diplomaIdsFromSteps)->get();
$diplomas = [];
foreach($potentialDiplomas as $d) {
$diplomaSteps = $d->steps;
$diplomaStepIds = // get unique diploma ids from $diplomaSteps
if(/* all $diplomaStepIds are in $stepIds */) {
$diplomas[] = $d;
}
}
return $diplomas;
}
}
I haven't completed all the code since I'm unable to test it right now. However, I hope this points you in the right direction.

Are these underscored PHP functioned ever called?

Inherited an old CakePHP site and I'm trying to figure out what some functions do. I have several functions that have the same name as another function but with an underscore first, e.g. save() and _save(). However the function _save() is never called in any context, though save() is.
I read this question and it looks like it's from an old worst-practices exercise, but that doesn't really explain why it's in my code; you still have to call function _save() as _save() right? If there's no calls to _save() is it safe to remove?
I want it gone, even the save() function wasn't supposed to be there, rewriting perfectly good framework functionality. It looks like an older version of the same function, but there's no comments and I don't know if there's some weird context in which php/Cake will fall back to the underscored function name.
Here's the code for the curious. On closer inspection it appears the underscored functions were old versions of a function left in for some reason. At least one was a "private" method being called (from a public function of the same name, minus the underscore...):
function __save() {
$user = $this->redirectWithoutPermission('product.manage','/',true);
if ($this->data) {
$this->Prod->data = $this->data;
$saved_okay = false;
if ($this->Prod->validates()) {
if ($this->Prod->save()) $saved_okay = true;
}
if ($saved_okay) {
$product_id = ($this->data['Prod']['id']) ? $this->data['Prod']['id'] : $this->Prod->getLastInsertId();
if ($this->data['Plant']['id']) {
$this->data['Prod']['id'] = $product_id;
$this->Prod->data = $this->data;
$this->Prod->save_plants();
$this->redirect('/plant/products/'.$this->data['Plant']['id']);
} else {
$this->redirect('/product/view/'.$product_id);
}
die();
} else {
die('did not save properly');
}
} else {
die('whoops');
}
}
function save() {
$user = $this->redirectWithoutPermission('product.manage','/products',true);
if ($this->data) {
$this->Prod->data = $this->data;
if ($this->Prod->validates()) {
$this->Prod->save();
$gotoURL = isset($this->data['Navigation']['goto'])?$this->data['Navigation']['goto']:'/';
$gotoURL = str_replace('%%Prod.id%%', $this->data['Prod']['id'], $gotoURL);
if (isset($this->data['Navigation']['flash'])) {
$this->Session->setFlash($this->data['Navigation']['flash']);
}
if (isset($this->params['url']['ext']) && $this->params['url']['ext']=='ajax') {
$value = array(
'success'=>true
,'redirect'=>$gotoURL
);
print $this->Json->encode($value);
} else {
$this->redirect($gotoURL);
}
} else {
$value = array(
'success'=>false
,'message'=>"You have invalid fields."
,'reason'=>'invalid_fields'
,'fields'=>array(
'Prod'=>$this->Prod->invalidFields()
)
);
print $this->Json->encode($value);
}
} else {
$this->redirect('/products');
}
die();
}
I had hoped to learn whether or not some convention applied to this situation, but from testing I've found the functions are not called which is really the answer to the question I asked.

Working with multiple objects of the same type (PHP)

What is the corecte way to handle with al lot objects of the same type?
Example:
When i get a list of notes from the database with zend framework i get a rowset which contains an array with note data.
If the number of notes in the database is 20 records large it's no problem to create a note object for every note in the database. But if the database contains 12.500 note records what shall i do than? Try to create 12.500 objects is possible but it's shure isn't quick enough.
Ty, Mark
This is the code i use.
Code to get the data from the database:
if (is_numeric($id) && $id > 0) {
$select = $this->getDao()->select();
$select->where('methode_id = ?', $id);
$select->order('datum DESC');
$rowset = $this->getDao()->fetchAll($select);
if (null != $rowset) {
$result = $this->createObjectArray($rowset);
}
}
createObjectArray function:
protected function createObjectArray(Zend_Db_Table_Rowset_Abstract $rowset)
{
$result = array();
foreach ($rowset as $row) {
$model = new Notes();
$this->populate($row, $model);
if (isset($row['id'])) {
$result[$row['id']] = $model;
} else {
$result[] = $model;
}
}//endforeach;
return $result;
}
Populate function
private function populate($row, $model)
{
// zet de purifier uit om overhead te voorkomen
if (isset($row['id'])) {
$model->setId($row['id']);
}
if (isset($row['type'])) {
$model->setType($row['type']);
}
if (isset($row['tekst'])) {
$model->setLog($row['tekst']);
}
if (isset($row['methode_id'])) {
$model->setSurveyMethodId($row['methode_id']);
}
if (isset($row['klant_id'])) {
$model->setCustomerId($row['klant_id']);
}
if (isset($row['gebruiker_aangemaakt_tekst'])) {
$model->setCreatedByUser($row['gebruiker_aangemaakt_tekst']);
}
if (isset($row['gebruiker_gewijzigd_tekst'])) {
$model->setUpdatedByUser($row['gebruiker_gewijzigd_tekst']);
}
if (isset($row['gebruiker_aangemaakt'])) {
$model->setCreatedByUserId($row['gebruiker_aangemaakt']);
}
if (isset($row['gebruiker_gewijzigd'])) {
$model->setUpdatedByUserId($row['gebruiker_gewijzigd']);
}
if (isset($row['datum_aangemaakt'])) {
$model->setDateCreated($row['datum_aangemaakt']);
}
if (isset($row['datum_gewijzigd'])) {
$model->setDateUpdated($row['datum_gewijzigd']);
}
$model->clearMapper();
return $model;
}
You could page your requests, so you only get a set amount of notes back each time. Although I can't see a problem with "only 12,500" objects, unless your object creation is doing something costly, i.e more queries on the database etc.
I am not so sure about your question.
For eg :
//Create a single object
$obj = new NoteObject();
//Set the properties if it differs
$obj->setX( $row );
//Make use of the method
$obj->processMethod();
So this way you just need a single object. Lets see the code if its not to give you a right answer.
Edit :
What I thought was in
protected function createObjectArray(Zend_Db_Table_Rowset_Abstract $rowset)
{
$result = array();
//Create the model here
$model = new Notes();
foreach ($rowset as $row) {
//Yes populate the values
$this->populate($row, $model);
/*
And not like saving the object here in array
if (isset($row['id'])) {
$result[$row['id']] = $model;
} else {
$result[] = $model;
}*/
//Do some calculations and return the result in array
$result[$row['id']] = $this->doSomething();
}//endforeach;
return $result;
}
I am not sure why you are keeping this object there itself. Any reuse ? the probably paginate and do :-)

deep copy of doctrine record

I want to make a deep copy/clone of a doctrine record in a symfony project.
The existing copy($deep)-method doesn't work properly with $deep=true.
For an example let's have a look at a classroom lesson. This lesson has a start and end date and between them there are several breaks. This classroom is in a buildung.
lesson-break is a one-to-many relationship, so a lot of breaks could be inside a lesson.
lesson-building is a many-to-one relationship, so a lesson could only be in ONE Building.
If I want to make a copy of the room the breaks should be copied also. The building should stay the same (no copy here).
I found some examples on the web which create a PHP class which extends from the sfDoctrineRecord and overrides the copy-method.
What I tried was:
class BaseDoctrineRecord extends sfDoctrineRecord {
public function copy($deep = false) {
$ret = parent::copy(false);
if (!$deep)
return $ret;
// ensure to have loaded all references (unlike Doctrine_Record)
foreach ($this->getTable()->getRelations() as $name => $relation) {
// ignore ONE sides of relationships
if ($relation->getType() == Doctrine_Relation::MANY) {
if (empty($this->$name))
$this->loadReference($name);
// do the deep copy
foreach ($this->$name as $record)
$ret->{$name}[] = $record->copy($deep);
}
}
return $ret;
}
}
Now this causes in a failure: Doctrine_Connection_Mysql_Exception: SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry '2-1' for key 'PRIMARY'
So I need to "null" the id of the new record ($ret) because this should be a new record. Where and how could/should I do it?
UPDATE:
The error is fixed with following code:
class BaseDoctrineRecord extends sfDoctrineRecord {
public function copy($deep = false) {
$ret = parent::copy(false);
if($this->Table->getIdentifierType() === Doctrine_Core::IDENTIFIER_AUTOINC) {
$id = $this->Table->getIdentifier();
$this->_data[$id] = null;
}
if(!$deep) {
return $ret;
}
// ensure to have loaded all references (unlike Doctrine_Record)
foreach($this->getTable()->getRelations() as $name => $relation) {
// ignore ONE sides of relationships
if($relation->getType() == Doctrine_Relation::MANY) {
if(empty($this->$name)) {
$this->loadReference($name);
}
// do the deep copy
foreach($this->$name as $record) {
$ret->{$name}[] = $record->copy($deep);
}
}
}
return $ret;
}
}
But it doesn't work well. In the DoctrineCollection lesson->Breaks all new breaks are fine. But they aren't saved in the database.
I want to copy a lesson and add 7 days to it's time:
foreach($new_shift->Breaks as $break) {
$break->start_at = $this->addOneWeek($break->start_at);
$break->end_at = $this->addOneWeek($break->end_at);
$break->save();
}
So as you see, the breaks are saved, but it seems they are not in the db.
This works for me, it's a variant from the question code:
public function realCopy($deep = false) {
$ret = self::copy(false);
if(!$deep) {
return $ret;
}
// ensure to have loaded all references (unlike Doctrine_Record)
foreach($this->getTable()->getRelations() as $name => $relation) {
// ignore ONE sides of relationships
if($relation->getType() == Doctrine_Relation::MANY) {
if(empty($this->$name)) {
$this->loadReference($name);
}
// do the deep copy
foreach($this->$name as $record) {
$ret->{$name}[] = $record->realCopy($deep);
}
}
}
// this need to be at the end to ensure Doctrine is able to load the relations data
if($this->Table->getIdentifierType() === Doctrine_Core::IDENTIFIER_AUTOINC) {
$id = $this->Table->getIdentifier();
$this->_data[$id] = null;
}
return $ret;
}
I can't believe I'm working with Doctrine 1.2 in 2017.

Categories