TL;DR is at the end to cut to the chase.
I have a lot of belongsTo() models that can have any number of records, and I'm trying to bind them to an edit form. I have the following foreach that creates the form elements:
#foreach ($department->department_10 as $key => $value)
{{ Form::select(
'department_10['.(isset($value->pk_department_10)?$value->pk_department_10:0).']',
$department_10_opts,
(isset($value->department_10)?$value->department_10:''),
array('class'=>'form-control input-md department_10', 'placeholder'=>'Other Types of Service')) }}
#endforeach
Since there can be 0 records (rows?) that belong to the model, to simplify my #foreach, I wanted to create a "blank" instance of the model. Additionally, because I'm going to have to deal with about 70 more cases like this, I created a function that would create the new blank model. Here's the function (in my controller for lack of a better place):
function mkBlankModel($parentModel, $newModel){
if(count($parentModel->$newModel) === 0){
$parentModel->$newModel[0] = new $newModel();
$parentModel->$newModel[0]->fk_department = $parentModel->pk_department;
$parentModel->$newModel[0]->$newModel = '';
}
return $parentModel;
}
When I run it, I don't get any errors, but I do get unexpected results and I can't really make sense of them:
Test Step 1) View the edit page while loading a record with 2 department_10's. It works as expected; loads two fields properly.
Test Step 2) View the edit page while loading a record with 0 department_10's. The page loads but without any fields. Because apparently my function didn't work, so I verify by dumping dump($department->$department_10) and it confirms this.
Test Step 3) I replace the $parentModel->$newModel[0] with $parentModel->department_10[0] like so:
function mkBlankModel($parentModel, $newModel){
if(count($parentModel->$newModel) === 0){
$parentModel->department_10[0] = new $newModel();
$parentModel->department_10[0]->fk_department = $parentModel->pk_department;
$parentModel->department_10[0]->$newModel = '';
}
return $parentModel;
}
And both scenarios (with records and without) work just fine. So my problem likely isn't Laravel specific, but I'm just curious how I can accomplish this.
TL;DR:
I'm trying to create a model instance for a parent model, if one doesn't exist, so a blank field will be created by my #foreach loop in my edit.blade.php's form. I can do this just fine if I manually spell out the model's name when creating it, but since I'll be doing this frequently, I'd prefer to define the class, and populate it with a string.
So I figured it out; I needed to wrap the $newModel in curly-brackets:
function mkBlankModel($parentModel, $newModel){
if(count($parentModel->$newModel) === 0){
$parentModel->{$newModel}[0] = new $newModel();
$parentModel->{$newModel}[0]->fk_department = $parentModel->pk_department;
$parentModel->{$newModel}[0]->$newModel = '';
}
return $parentModel;
}
More info here: http://php.net/manual/en/language.types.string.php#language.types.string.parsing.complex
Related
I've a very big doubt about how works laravel for a very simple thing:
If I call:
$companies=User::All();
Then I can use statement like this in a forach:
foreach($companies as $company)
$company['new_field']= 'something';
If i'm limiting the output of the query like:
$companies = DB::table('companies')
->select('id','name','email','business_name',...)->get();
The things doesnt work as before,
I try with or without the ->get()
I try to convert with ->toArray() (errors rised)
I try with put() and push() for collections method and agains errors...
How can I add a field in every item of the collection just to pass it to a view?
Try like this, hope it works for you:
$users=User::select('id','name','email','business_name',...)->get()->toArray();
and then use foreach loop like this:
foreach($users as $key => $value ){
$users[$key]['newField'] = "Demo";
}
If you are using Laravel and model in it so there is a better way to add custom attribute or field here is what i do for custom field
For Example :
There is a Model Name User
so in User Model
add a property name appends like :
class User extends Model
{
protected $appends = ['new_field'];
public function getNewFieldAttribute() // defining field logic here
{
return // your code
}
So you no need to use foreach and looping and adding new field
for more have a look on laravel doc : https://laravel.com/docs/5.5/eloquent-mutators#accessors-and-mutators
Suggestion
you can limit your output with Model too.
User::select('id','name','email','business_name',...)->get();
if you are making an array like
User::select('id','name','email','business_name',...)->get()->toArray();
so this will also give you your custom field
I've got two tables: step and links joined 1:n. I'm aiming to maintain the links through the step objects. I retrieve all steps from the database and populate the relation with the links table. I persist the step object containing a collection of links to JSON and return it to the front end using REST.
That means that if a step is linked or unlinked to another step in the front end I send the entire step back to the backend including a collection of links. In the back end I use the following code:
public function put($processStep) {
if (isset($processStep['Processesid']) && isset($processStep['Coordx']) && isset($processStep['Coordy'])) {
$p = $this->query->findPK($processStep['Id']);
$p->setId($processStep['Id']);
$p->setProcessesid($processStep['Processesid']);
if (isset($processStep['Flowid'])) $p->setFlowid($processStep['Flowid']);
if (isset($processStep['Applicationid'])) $p->setApplicationid($processStep['Applicationid']);
$p->setCoordx($processStep['Coordx']);
$p->setCoordy($processStep['Coordy']);
$links = $p->getLinksRelatedByFromstep();
$links->clear();
foreach ($processStep['Links'] as $link) {
if (!isset($link['Linkid'])) {
$newLink = new \Link();
$newLink->setFromstep($link['Fromstep']);
$newLink->setTostep($link['Tostep']);
$links->prepend($newLink);
}
}
$p->save();
return $p;
} else {
throw new Exceptions\ProcessStepException("Missing mandatory fields.", 1);
}
}
I'm basically deleting every link from a step and based upon the request object I recreate the links. This saves me the effort to compare what links are deleted and added. The insert work like a charm Propel automatically creates the new links. Thing is it doesn't delete like it inserts. I've checked the object that is being persisted ($p) and I see the link being deleted but in the MySQL log there is absolutely no action being performed by Propel. It looks like a missing member from the link collection doesn't trigger a dirty flag or something like that.
Maybe I'm going about this the wrong way, I hope someone can offer some advice.
Thanks
To delete records, you absolutely always have to use delete. The diff method on the collection is extremely helpful when determining which entities need added, updated, and deleted.
Thanks to Ben I got on the right track, an explicit call for a delete is not needed. I came across a function called: setRelatedBy(ObjectCollection o) I use this function to provide a list of related objects, new objects are interpreted as inserts and omissions are interpreted as deletes.
I didn't find any relevant documentation regarding the problem so here's my code:
$p = $this->query->findPK($processStep['Id']);
$p->setId($processStep['Id']);
$p->setProcessesid($processStep['Processesid']);
$p->setCoordx($processStep['Coordx']);
$p->setCoordy($processStep['Coordy']);
if (isset($processStep['Flowid'])) $p->setFlowid($processStep['Flowid']);
if (isset($processStep['Applicationid'])) $p->setApplicationid($processStep['Applicationid']);
//Get related records, same as populaterelation
$currentLinks = $p->getLinksRelatedByFromstep();
$links = new \Propel\Runtime\Collection\ObjectCollection();
//Check for still existing links add to new collection if so.
//This is because creating a new Link instance and setting columns marks the object as dirty creating an exception due to duplicate keys
foreach ($currentLinks as $currentLink) {
foreach ($processStep['Links'] as $link) {
if (isset($link['Linkid']) && $currentLink->getLinkid() == $link['Linkid']) {
$links->prepend($currentLink);
break;
}
}
}
//Add new link objects
foreach ($processStep['Links'] as $link) {
if (!isset($link['Linkid'])) {
$newLink = new \Link();
$newLink->setFromstep($link['Fromstep']);
$newLink->setTostep($link['Tostep']);
$links->prepend($newLink);
}
}
//Replace the collection and save the processstep.
$p->setLinksRelatedByFromstep($links);
$p->save();
I want to include the model name in the returned results of a query using CakePHP's find() methods.
For instance, if I do a
$person = $this->Person->find("first", array(
"conditions" => array (
"Person.id" => $id
)
));
I get back
Person{id:1, name:Abraham Lincoln}
I want to get back
Person{id:1, name:Abraham Lincoln, model: Person}
I'm fairly front-end oriented. I know I could loop through results and add these at the controller level, but that seems tedious, especially since most of my queries are far more complex, utilizing contain(). I imagine somewhere in CakePHP's core there's a place this kind of functionality could be added, I just don't know where.
Essentially, I'm looking for where CakePHP casts the database query to a php variable, so I can inject my additional model value.
I do know I will never use the column name "model" anywhere in my application. I'm also certain I want this information where I'm requesting it to be in every singe query, as little sense as it may make.
Add this to every model where you need it:
public function afterFind($results, $primary = false) {
foreach($results as $ikey => $item) {
foreach($item as $skey => $subitem) {
if(is_array($subitem))
$results[$ikey][$skey]['model'] = $skey;
else $results[$ikey]['model'] = $skey;
}
}
return $results;
}
Unfortunately I wasn't able to get this work when I stored it in AppModel.
I am building an application where the user can edit some data and then gets presented with a screen where he can confirm (and comment on) his edits.
In the confirmation form I display the changes that have been made to the entity. This works for "normal" fields. Here is some code that works for checking a single field:
// create $form
// bind $form
if ($form->isValid() {
$data = $form->getData();
// example, get changes of a "normal" field
if ($data['color'] != $entity->getColor()) {
// do something with changes
}
}
But I can't do the same for a relation (example ManyToMany with Users) :
if ($data['users'] != $entity->getUsers()
doesn't work because $data['users'] and $entity->getUsers() refer to the same persistent collection. It is possible to call this function to see if there are changes:
if ($data['users']->isDirty())
but it isn't possible to see what changes were made.
The second problem with the above is that if all items are removed from the persistent collection, Doctrine does not mark it as "changed" (isDirty() = true), so I can't catch the specific change where the user removes all "users" from the entity in the form.
Please note that the code all works, the only problem I have is that I am unable to view/process the changes made on the confirmation step.
Doctrine\ORM\PersistentCollection has internal API (public) methods getSnapshot, getDeleteDiff, getInsertDiff that can be used during lifecycle events of the Doctrine\ORM\UnitOfWork. You could for example check the insert diff of a persistent collection during onFlush.
Solved it like this:
1) To get changes that will be made directly to the Entity, use the following:
// create form
// bind form
// form isValid()
$uow = $em->getUnitOfWork();
$uow->computeChangeSets();
$changeset = $uow->getEntityChangeSet($entity);
print_r($changeset);
2a) To get changes to the relations, use the answer from Lighthart above:
$oldUsers = $entity->getUsers()->toArray();
// bind form
// form isValid
$newUsers = $entity->getUsers()->toArray();
// compare $oldUsers and $newUsers
2b) Use these methods on Persistent Collection to find inserts / deletes:
$newUsers = $entity->getUsers();
$inserted = $newUsers->getDeleteDiff();
$deleted = $newUsers->getInsertDiff();
The only problem with (2b) is that if ALL users are removed and none added then getDeleteDiff() is empty which appears to be a Doctrine bug/idiosyncrasy
Store the original collection in a variable before bind and then compared the new collection after bind. PHP has quite a few array comparison functions, and collections are readily turned into native arrays by $collection->toArray();
eg:
// create form
$oldusers=$entity->getUsers()->toArray();
// bind form
if ($form->isValid() {
$data = $form->getData();
if ($data['users'] != $oldusers) {
// do something with changes
}
}
In one of my controllers to fetch all the data for my general view page I use a foreach loop and then $object->column_name but now I have decided I would like to do a couple of things with this data:
Edit it -> It is an edit page for each $object by its $id
Use the $object->name field via the controller to enable me to use it in a $data['pageTitle']= Edit '.$object->name.';
What would be the best way to change the model below so that I can use it for many purposes / different ways of displaying the data for manipulation?
public function showAll()
{
$database = $this->db->get('form');
if($database->num_rows() > 0)
{
$row = $database->result();
}
return $row;
}
It is good practice to keep the database query part in your model rather than the controller.
In your controller you can do something like:
$recs = $this->sample_model->model_function();
foreach ($recs as $r)
{
$r->additional_info_appended_to_each_row = 'whatever';
}
This way you can append an additional variable to each database row for displaying / editing etc.
That code doesn't really show us enough of the relevant code to be able to answer that question. Assuming $database->result() returns an array of rows (a database query's result set) then the data it contains depends on how the query looks. All you show is is $this->db->get('form'), which can mean just about anything.
A generic answer to your question would be: Alter the SQL query to include the id and name fields. Then inject those into your view through your controller. (Or get them directly through the view. That's up to you.)
Not knowing a thing about your controllers or your views, here is an example that assumes your views extend the Smarty template engine.
public function GeneralController
{
public function defaultAction()
{
$model = new GeneralModel();
$objects = $model->getAll();
$view = new GeneralView();
$view->assign("objects", $objects);
$view->show();
}
}
The Smarty template would then use those rows when generating the HTML
{foreach $objects as $object}
<section class="object">
<header>
<h1>{$object->name}</h1>
</header>
<p>{$object->contents}</p>
<footer>
Edit
</footer>
</section>
{/foreach}