How to unset (remove) a collection element after fetching it? - laravel

I have a collection which I want to iterate and modify while I fetch some of its elements. But I could't find a way or method to remove that fetched element.
$selected = [];
foreach ($collection as $key => $value) {
if ($collection->selected == true) {
$selected[] = $value;
unset($value);
}
}
This is just a representation of my question for demonstration.
After #Ohgodwhy advice the forget() function I checked it again and saw that I actually misunderstood the function. It was exactly as I was looking for.
So for working solution I have added $collection->forget($key) inside the if statement.
Below is the working solution of my problem, using #Ohgodwhy's solution:
$selected = [];
foreach ($collection as $key => $value) {
if ($collection->selected == true) {
$selected[] = $value;
$collection->forget($key);
}
}
(this is just a demonstration)

You would want to use ->forget()
$collection->forget($key);
Link to the forget method documentation

Or you can use reject method
$newColection = $collection->reject(function($element) {
return $item->selected != true;
});
or pull method
$selected = [];
foreach ($collection as $key => $item) {
if ($item->selected == true) {
$selected[] = $collection->pull($key);
}
}

Laravel Collection implements the PHP ArrayAccess interface (which is why using foreach is possible in the first place).
If you have the key already you can just use PHP unset.
I prefer this, because it clearly modifies the collection in place, and is easy to remember.
foreach ($collection as $key => $value) {
unset($collection[$key]);
}

I'm not fine with solutions that iterates over a collection and inside the loop manipulating the content of even that collection. This can result in unexpected behaviour.
See also here: https://stackoverflow.com/a/2304578/655224 and in a comment the given link http://php.net/manual/en/control-structures.foreach.php#88578
So, when using foreach it seems to be ok but IMHO the much more readable and simple solution is to filter your collection to a new one.
/**
* Filter all `selected` items
*
* #link https://laravel.com/docs/7.x/collections#method-filter
*/
$selected = $collection->filter(function($value, $key) {
return $value->selected;
})->toArray();

If you know the key which you unset then put directly by comma
separated
unset($attr['placeholder'], $attr['autocomplete']);

You can use methods of Collection like pull, forget, reject etc.
But every Collection method returns an entirely new Collection instance.
Doc link: https://laravel.com/docs/8.x/collections#introduction

Related

Is there any issue in each loop to break it and not create models?

Is there any problem in following code, I tried so hard but got no answer.
I want to create models in each iteration from loop in filtering dom.
$node->filter('div.panel-section.font-size-2.font-size-xxs-normal .row')->each(function (Crawler $nestedNode, $i) use (&$part) {
$model_category = ModelCategory::firstOrCreate(['name' => $nestedNode->filter('.bold.mb-2 a')->first()->text()]);
$part->modelCategories()->sync($model_category, false);
$nestedNode->filter('.col-xs-6.col-md-3')->each(function (Crawler $columns, $i) use (&$model_category) {
$model = Model::Create(['name' => $columns->filter('a')->first()->text()]);
$model->category()->associate($model_category);
$model->save();
});
});
I have one to many relation between Model and ModelCategory
I found issue after sometimes looking and some searching. It's because of sync syntax. sync function in eloquent relationship get array of ids as first argument but I passed an object to it. Below is the corrected piece of code:
$node->filter('div.panel-section.font-size-2.font-size-xxs-normal .row')->each(function (Crawler $nestedNode, $i) use (&$part) {
$model_category = ModelCategory::firstOrCreate(['name' => $nestedNode->filter('.bold.mb-2 a')->first()->text()]);
$part->modelCategories()->sync([$model_category->id], false);
$nestedNode->filter('.col-xs-6.col-md-3')->each(function (Crawler $columns, $i) use (&$model_category) {
$model = Model::Create(['name' => $columns->filter('a')->first()->text()]);
$model->category()->associate($model_category);
$model->save();
});
});

Fetching a cached Eloquent collection of 10 items times out

I'm building a search functionality that returns large collections which are paginated using a LengthAwarePaginator. I'm trying to cache results using a key called $searchFilter_$query_$offsetPages for a single page of returned results (10 items). It goes into the cache just fine. However, it times out when I try to check using Cache::has($key) or fetch using Cache::get($key).
The same problem occurs in the browser as well as in artisan Tinker. Strangely, when I put a random set of 10 items into the cache in Tinker and fetch them back, everything works fine. I'm using Redis as the cache driver.
Here is my controller method:
public function search($filter, $query, $layout, Request $request) {
if($layout == "list-map") {
return view("list-map")->with(['filter' => $filter, 'query' => $query, 'layout' => 'list-map']);
} else {
$offsetPages = $request->input('page', 1) - 1;
$cacheKey = $filter . "_" . $query . "_" . $offsetPages;
if(Cache::has($cacheKey)) {
\Log::info("fetching results from cache");
$data = Cache::get($cacheKey);
$totalCt = $data[0];
$results = $data[1];
} else {
$results = $this->getResults($filter, $query);
$totalCt = $results->count();
$results = $results->slice($offsetPages, $this->resultsPerPage);
\Log::info("caching results");
Cache::put($cacheKey, [$totalCt, $results], 5);
}
$results = new LengthAwarePaginator($results,
$totalCt,
$this->resultsPerPage,
$request->input('page', 1),
['path' => LengthAwarePaginator::resolveCurrentPath()]
);
return view($layout)->with(['filter' => $filter, 'query' => $query, 'layout' => $layout, 'results' => $results]);
}
}
So, the issue was that many of the models in the collection returned from my getResults() method were obtained via relationship queries. When I would dd($results) on the single page of 10 results, I could see that there was a "relations" field on each model. Inside that array were thousands of recursively related models based on the relationship I originally queried. I was unable to find any information about an option to not eager load these related models. Instead I came up with a bit of a hacky workaround to fetch the models directly:
$results = $results->slice($offsetPages, $this->resultsPerPage);
//load models directly so they don't include related models.
$temp = new \Illuminate\Database\Eloquent\Collection;
foreach($results as $result) {
if(get_class($result) == "App\Doctor") {
$result = Doctor::find($result->id);
} else if(get_class($result == "App\Organization")) {
$result = Organization::find($result->id);
}
$temp->push($result);
}
$results = $temp;
\Log::info("caching results");
Cache::put($cacheKey, [$totalCt, $results], 5);
If anyone knows the best practice in this situation, please let me know. Thanks!
Edit:
I've found a better solution instead of the above workaround. If I query my relationships like this: $taxonomy->doctors()->get() rather than $taxonomy->doctors, it does not load in the huge recusive relations.
I dont really see why your code doesn't work. The only potential problems I see are the cache keys, which could contain problematic characters, as well as the way you check for a cached value. As you are using Cache::has($key) before Cache::get($key), you could end up with a race condition where the first call returns true and the latter null because the cached value timed out just between the two calls.
I tried to address both issues in the following snippet:
public function search($filter, $query, $layout, Request $request)
{
if($layout == "list-map") {
return view("list-map")->with(['filter' => $filter, 'query' => $query, 'layout' => 'list-map']);
} else {
$offsetPages = $request->input('page', 1) - 1;
$cacheKey = md5("{$filter}_{$query}_{$offsetPages}");
$duration = 5; // todo: make this configurable or a constant
[$totalCount, $results] = Cache::remember($cacheKey, $duration, function () use ($filter, $query) {
$results = $this->getResults($filter, $query);
$totalCount = $results->count();
$filteredResults = $results->slice($offsetPages, $this->resultsPerPage);
return [$totalCount, $filteredResults];
});
$results = new LengthAwarePaginator($results,
$totalCount,
$this->resultsPerPage,
$request->input('page', 1),
['path' => LengthAwarePaginator::resolveCurrentPath()]
);
return view($layout)->with(compact('filter', 'query', 'layout', 'results'));
}
}
The inbuilt function Cache::remember() doesn't use Cache::has() under the hood. Instead, it will simply call Cache::get(). As this function will return null as default if no cache was hit, the function can easily determine if it has to execute the closure or not.
I also wrapped the $cacheKey in md5(), which gives a consistently valid key.
Looking at the following part of your code
$results = $this->getResults($filter, $query);
$totalCount = $results->count();
$filteredResults = $results->slice($offsetPages, $this->resultsPerPage);
I am quite sure the whole search could be improved (independently of the caching). Because it seems you are loading all results for a specific search into memory, even if you throw away most parts of it. There is certainly a better way to do this.

Magento work with cache, can't serialize collection

I'm learning how to use magento cache and I'm a bit stuck trying to serialize a collection.
Actually this is my code:
class Feliu_Featuredcategories_Block_Topcategories extends Mage_Core_Block_Template
{
protected function _construct()
{
$storeId = Mage::app()->getStore()->getId();
$this->addData(array(
'cache_lifetime' => 3600,
'cache_tags' => array(Mage_Catalog_Model_Product::CACHE_TAG),
'cache_key' => 'homepage-most-view-' . $storeId,
));
}
public function setData()
{
$storeId = Mage::app()->getStore()->getId();
$cache = Mage::app()->getCache();
$key = 'homepage-most-view-' . $storeId;
$cached_categories = $cache->load($key);
if (! $cached_categories) {
$categories = Mage::getModel('catalog/category')
->getCollection()
->addAttributeToSelect(array('data', 'name', 'add_to_top_categories'))
->addAttributeToFilter('add_to_top_categories', array('eq' => '1'));
$categories->load();
$cache->save(serialize($categories), $key);
} else {
$categories = unserialize($cached_categories);
}
return $categories;
}
}
At first I tried to $cache->save($categories, $key); directly, but I read that collections can't be saved directly and I got an error that said: 'automatic_serialization must be on' when I tried to set automatic_serialization to true then I received a message saying that it can't be activated for security reasons.
Then I tried to serialize, just as the above code shows, but it did not work neither. It seems that magento protects collections from being serialized because they can be really big.
So finally I tried to urlencode() before serializing serialize(urlencode($categories)) and urldecode(unserialize($categories)) but I got the string "N;" serializing with this aproach and an empty string when unserialize.
I'm using magento 1.9.3 and I followed this documentation and previous questions:
https://www.nicksays.co.uk/developers-guide-magento-cache/
http://inchoo.net/magento/magento-block-caching/
Magento: serialization error on caching Collection
Magento how to cache a productCollection
And some other questions about this, but maybe there is no need to write too much links, I don't want to spam.
Edit: If instead a collection I use an array like
$categories = array('banana', 'apple', 'kiwi', 'strawberry', 'pomelo', 'melon');
then the code seems to work correctly
Finally I solved it, the answer it's easiest than I though at the beginning but I write it here because maybe it will help somebody in the future.
As collections cannot be cached nor serialized, I made an array with the data I need from the collection.
$categories = Mage::getModel('catalog/category')
->getCollection()
->addAttributeToFilter('add_to_top_categories', array('eq' => '1'))
->addAttributeToSelect(array('data', 'name'));
I make the collection adding only the fields I need, and selecting the data I want.
$array = array();
foreach ($categories as $_category)
{
array_push($array, array('url' => $_category->getUrl(), 'name' => $_category->getName()));
}
Now I make an array that holds objects with the data I wanted. Next step is serialize the array I just made and save it on the cache.
$cache->save(serialize($array), $key, array('custom_home_cache'), 60*60);
and retrieve the data is as easy as $cache->load(unserialize($key))

How to pass a view and a json from a function in laravel?

This is my function
if(isset($_POST['franchisesIds'])) {
$id_array = array();
foreach($_POST['franchisesIds'] as $data) {
array_push($id_array, (int)$data['id']);
}
$results = DB::table('franchises')->whereIn('id', $id_array)->get();
}
return Response::json(array($id_array));
return View::make('frontend.stores')->with('franchisesAll', $results);
So I am a little bit confused on how to pass all this data. I need to pass the json just to make sure everything worked. And at the same time I need to pass a list of ids to the view.
How can I do this??
Hopefully this is what you wanted :
Please don't use directly $_POST or $_GET instead use Input
$franchisesIds = Input::get('franchisesIds');
$id_array = array();
if($franchisesIds) {
foreach( $franchisesIds as $data) {
array_push($id_array, (int)$data['id']);
}
$results = DB::table('franchises')->whereIn('id', $id_array)->get();
}
$jsonArray = json_encode($id_array);
return View::make('frontend.stores')->with(array('franchisesAll'=>$results,'idArrays'=>$jsonArray));
In order to pass multiple values to the view, please read more about it in the official Laravel documentation
First of all you should use Input::get('franchisesIds') instead of $_POST['franchisesIds'], also there is no reason to do this foreach loop:
foreach($_POST['franchisesIds'] as $data) {
array_push($id_array, (int)$data['id']);
}
Because this is already an array and you are bulding another array from this array, makes no sense. So you may try this instead:
if($franchisesIds = Input::get('franchisesIds')) {
$franchises = DB::table('franchises')->whereIn('id', $franchisesIds)->get();
}
Then to pass both $franchisesIds and result to your view you may use this:
return View::make('frontend.stores')
->with('franchises', $franchises)
->with('franchisesIds', $franchisesIds);
You can also use something like this (compact):
return View::make('frontend.stores', compact('franchises', 'franchisesIds'));
There is no reason to use json_encode to encode your $franchisesIds.
You could also use
$results = DB::table('franchises')
->whereIn('id', $id_array)
->get()
->toJson();

Starting new query context in CodeIgniter's Active Record

I basically wanna do what I'd call nested queries (but not in the nested SELECT way) using CodeIgniter's Active Record.
So it would be like putting aside the current AR context to run a new query before restoring it.
A concrete example:
function handle_form($fields, $POST)
{
foreach ($fields as $field)
{
if ($field->type == "file")
do_upload($field->post); //This function does insert data in another table using AR too
$this->db->set($field->name, $POST[$field->post]);
}
$this->db->insert('table');
}
I haven't found any resources about that, maybe I'm just using the wrong keywords..
Thanks for your help !
function handle_form($fields, $POST)
{
$data = array();
foreach ($fields as $field)
{
if ($field->type == "file")
do_upload($field->post);
$data[$field->name] = $POST[$field->post];
}
$this->db->insert('table', $data);
}

Resources