Laravel-8 Related data in a collection is lost - laravel-8

I am trying to get more familiar with laravel/livewire. I find myself struggling with collections with related data. In my component i get data from the database and store it in a variable, with related data. When i do a dd after the retrieval, i see a complete collection with the related data. In my bladefile i have a foreach loop to display all the lists and for each list i have a foreachloop to display all todos.
But when i want to edit a list, i want to show one of the lists in a modal with a form. I call a function 'edit_todo' and i want to extract the data from my collection to fill in a blade form.
But when i look at the same variable as before ($this->todolists) in that function, the related data is missing.
This is a part of the code.
class Dashboard extends Component
{
public $todolists, $list_id, $todolist;
public function render()
{
$this->todolists = TodoList::with('todos')->get();
//dd($this->todolists); //<-- relationsproperty shows all todos from each list
return view('livewire.dashboard');
}
/**
* The attributes that are mass assignable.
*
* #var array
*/
public function edit_todo($list_id)
{
dd($this->todolists); //<-- relationsproperty is empty
$this->todolist = $this->todolists->where('id',$list_id)->with('todos')->first()->toArray();
}
Any suggestions? What am i doing wrong?
Maybe i just have to get the data again from the database, but i'd like to understand why this isn't working.

Move the population of your $todoLists to the mount method:
public function mount()
{
$this->todolists = TodoList::with('todos')->get();
}
Have a look at https://laravel-livewire.com/docs/2.x/lifecycle-hooks and you'll notice that the render method is only requested at the very end. If you need your $todoLists populated as you wish, move it up in the list of lifecycle hooks.
Edit & extra: mount() in LiveWire is equal to __construct() in any other class as we are used to. Whatever you would like to 'construct', write it in the mount method.

Related

Trying to take some data from my file called "listing-card" which is is saved in the component folder

#foreach($listings as $listing)
<x-listing-card :listing="$listings" />
#endforeach
the listing card is in the component file
I was expecting to get results from 'listing-card' blade file. but I got this error
enter image description here
without a better understanding of your code, the first thing I see is the :listing=$listings
I would think you would want the individual item $listing from your foreach.
Secondly, The error states it can not find the class. Be wary of kabob-case vs CamelCase. It has bitten me a few times
Edit/Update addressing comment below:
You will have to go and look at your component blade file to check on your exact naming convention you are using. For example you have a class named SiteInfo and in the render section you would have it return something like : return view('components/site-info');
Your file and class being used will be SiteInfo.blade.php and the class in it will be: class SiteInfo extends Component
example:
#foreach($listings as $listing)
<x-listing-card :listing="$listing" />
#endforeach
Your Class file would be something similar to:
class Listing-card extends Component
{
public $listing;
}
/**
* Create a new component instance.
*
*
*/
public function __construct($listing)
{
$this->listing = $listing;
}
/**
* Get the view / contents that represent the component.
*
* #return \Illuminate\Contracts\View\View|\Closure|string
*/
public function render()
{
return view('components.listing-card');
}
}
You might have to enable some logging at key points in your app and pull data. The laravel log /storage/logs/laravel.log is invaluable in find your mistakes.

Problems using successCallback in ag-grid

I have a vue component with ag-grid grid (infinite model). I want to access to its params to use successCallback, directly in another method in the component, after do the setDatasource.
In sum up, I can not find where the params are.
I have tried:
this.gridApi.params.successCallback(data, totalRows)
this.gridOptions.params.successCallback(data, totalRows)
this.params.successCallback(data, totalRows)
Any advice?
Basically, you have missed the full flow of how the ag-grid operating with dataSource.
So according to the official doc, IDatasource looks like that:
interface IDatasource {
/** If you know up front how many rows are in the dataset, set it here. Otherwise leave blank.*/
rowCount?: number;
/** Callback the grid calls that you implement to fetch rows from the server. See below for params.*/
getRows(params: IGetRowsParams): void;
destroy?(): void;
}
Let's go deeply to getRows and IGetRowsParams interface (only needed part of it)
/** Params for the above IDatasource.getRows() */
export interface IGetRowsParams {
....
successCallback(rowsThisBlock: any[], lastRow?: number): void;
...
}
So as you can see, successCallback only accessible via dataSource and exactly in getRows method, which means, that you don't have to get direct access via gridAPI. It should be defined once (as dataSource) and then, ag-grid will execute getRows only when it requires.
Possible solution (not recommended to use, cut dataSource mostly required for infinite scroll and auto-fill the new data)
You can bind params.successCallback to your own property and execute it whenever you want
here is a simple hack example (check successCallbackBinding)

Yii2 REST API relational data return

I've set up Yii2 REST API with custom actions and everything is working just fine. However, what I'm trying to do is return some data from the API which would include database relations set by foreign keys. The relations are there and they are actually working correctly. Here's an example query in one of the controllers:
$result = \app\models\Person::find()->joinWith('fKCountry', true)
->where(..some condition..)->one();
Still in the controller, I can, for example, call something like this:
$result->fKCountry->name
and it would display the appropriate name as the relation is working. So far so good, but as soon as I return the result return $result; which is received from the API clients, the fkCountry is gone and I have no way to access the name mentioned above. The only thing that remains is the value of the foreign key that points to the country table.
I can provide more code and information but I think that's enough to describe the issue. How can I encode the information from the joined data in the return so that the API clients have access to it as well?
Set it up like this
public function actionYourAction() {
return new ActiveDataProvider([
'query' => Person::find()->with('fKCountry'), // and the where() part, etc.
]);
}
Make sure that in your Person model the extraFields function includes fKCountry. If you haven't implemented the extraFields function yet, add it:
public function extraFields() {
return ['fKCountry'];
}
and then when you call the url make sure you add the expand param to tell the action you want to include the fkCountry data. So something like:
/yourcontroller/your-action?expand=fKCountry
I managed to solve the above problem.
Using ActiveDataProvider, I have 3 changes in my code to make it work.
This goes to the controller:
Model::find()
->leftJoin('table_to_join', 'table1.id = table_to_join.table1_id')
->select('table1.*, table_to_join.field_name as field_alias');
In the model, I introduced a new property with the same name as the above alias:
public $field_alias;
Still in the model class, I modified the fields() method:
public function fields()
{
$fields = array_merge(parent::fields(), ['field_alias']);
return $fields;
}
This way my API provides me the data from the joined field.
use with for Eager loading
$result = \app\models\Person::find()->with('fKCountry')
->where(..some condition..)->all();
and then add the attribute 'fkCountry' to fields array
public function fields()
{
$fields= parent::fields();
$fields[]='fkCountry';
return $fields;
}
So $result now will return a json array of person, and each person will have attribute fkCountry:{...}

Doctrine ODM: Cannot prime->(true) getSingleResult(); throws cursor error

I have a document that has a ReferenceMany attribute to another document. The reference is setup fine, and the data is returned from the query fine, but each document in the arraycollection is returned as a proxy. On this site, I saw it was mentioned I should add ->prime(true) in order to return the actual referenced documents.
When that ArrayCollection of documents is returned, I am running a loop of ids I have submitted to the server to remove them from the referenced collection. The removeElement method is not working b/c the returned documents are proxies, and I am comparing an actual document vs. those proxies. So basically I am trying to:
Look up a single document
Force all documents in the ReferenceMany attribute to be actual documents and not Proxy documents
Loop through my array of id's and load each document
Send the document to the removeElement method
On the first getSingleResult query method below, I am getting an error cannot modify cursor after beginning iteration. I saw a thread on this site mention you should prime the results in order to get actual documents back instead of proxies, and in his example, he used getSingleResult.
$q = $this->dm->createQueryBuilder('\FH\Document\Person')->field('target')->prime(true)->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();
foreach($data->loc_id as $loc) {
$location = $this->dm->createQueryBuilder('\FH\Document\Location')->field('id')->equals(new \MongoId($loc))->getQuery()->getSingleResult();
$person->removeTarget($location);
}
....
....
....
public function removeTarget($document)
{
$this->target->removeElement($document);
return $this;
}
If I remove ->prime(true) from the first query, it doesn't throw an error, yet it doesn't actually remove any elements even though I breakpoint on the method, compare the two documents, and the data is exactly the same, except in $this->target they are Location Proxy documents, and the loaded one is an actual Location Document.
Can I prime the single result somehow so I can use the ArrayCollection methods properly, or do I need to just do some for loop and compare ids?
UPDATE
So here is an update showing the problem I am having. While the solution below would work just using the MongoId(s), when I submit an actual Document class, it never actually removes the document. The ArrayCollection comes back from Doctrine as a PersistentCollection. Each element in $this->coll is of this Document type:
DocumentProxy\__CG__\FH\Document\Location
And the actual Document is this:
FH\Document\Location
The removeElement method does an array_search like this:
public function removeElement($element)
{
$key = array_search($element, $this->_elements, true);
if ($key !== false) {
unset($this->_elements[$key]);
return true;
}
return false;
}
So because the object types are not exactly the same, even though the proxy object should be inheriting from the actual Document I created, $key always returns 0 (false), so the element is not removed. Everything between the two documents are exactly the same, except the object type.
Like I said, I guess I can do it by MongoId, but why isn't it working by submitting the entire object?
Don't worry about the prime(true) stuff for just now. All that does is tell doctrine to pull the referenced data now, so it doesn't have to make multiple calls to the database when you iterate over the cursor.
What I would do is change your removeTarget method to do the following.
$this->dm->createQueryBuilder('\FH\Document\Person')->field('id')->equals($data->p_id);
$person = $q->getQuery()->getSingleResult();
$person->removeTargets($data->loc_id);
Person.php
public function removeTargets($targets)
{
foreach ($targets as $target) {
$this->removeTarget($target);
}
}
public function removeTarget($target)
{
if ($target instanceof \FH\Document\Location) {
return $this->targets->removeElement($target);
}
foreach ($this->targets as $t) {
if ($t->getId() == $target) {
return $this->targets->removeElement($t);
}
}
return $this;
}
This would mean you don't have to perform the second query manually as doctrine will know it needs to pull the data on that reference when you iterate over it. Then you can make this operation quicker by using the prime(true) call to make it pull the information it needs in one call rather than doing it dynamically when you request the object.

Can't get data from a static array in HelperAdmin

I have a file/class::method (HelperAdmin.php/HelperAdmin::menuItem()) which extracts data from DB to generate main menu and submenu.
I have to get this data after the menu being generated but I don't want to call this method twice.
So I have created a static array in HelperAdmin class. It looks like this:
class HelperAdmin {
static $arrMenuItems;
...
public static function menuItem() {
....get $items....
self::$arrMenuItems = $items;
return $items;
}
....
}
But here is a problem. If I call the METHOD again:
$items=HelperAdmin::menuItem();
...I can get data.
In other hand if I try to get data through a static array:
$items=HelperAdmin::$arrMenuItems;
...it just returns null.
I hope to see some ideas. After all, if your opinion is that using static variable here (from Yii architecture view) is not the best solution, I'd like to get your advice!
#bool.dev:
OK, imagine the following scheme:
1. We have the main file
/modules/admin/views/layouts/admin.php
which is in essence our backend template.
2. We have the helper here:
/modules/admin/components/HelperAdmin.php
which contains:
* a class HelperAdmin,
* menuItem() method and
* a class static array $arrMenuItems.
A content of this ARRAY returned by calling HelperAdmin::menuItem().
We want get data from the HelperAdmin class twice:
1. While generating a menu in admin.php;
2. As part of content which we get with variable $content putting it at this file.
$content in turn is generated in another file:
/modules/admin/views/generator/index.php
So as you can see, our page compounds itself from the template file /modules/admin/views/layouts/admin.php and data ($content) getting from /modules/admin/views/generator/index.php.
First I get data for menu:
HelperAdmin::menuItem();
$items=HelperAdmin::$arrMenuItems;
$this->widget(....
'items'=>$items,
...),
));
That's OK. Notice that after this a static array $arrMenuItems already is generated in HelperAdmin.
Next I'm trying to get the same data ($arrMenuItems generated earlier) in file /modules/admin/views/generator/index.php to place it as $content:
$items=HelperAdmin::$arrMenuItems
And here I can't get it as described above.
Well, I hope it made a situation more clear (?).