Remove row from pagination inside of transform - php

I have a variable that is a pagination object
$pagination
I am changing things inside of it using transform
$pagination->getCollection()->transform(function ($item, $key) use(&$data, &$pagination) {
$item->foo = 'bar';
}
I want to remove an item from the pagination if it meets a certain condition. I don't want it removed until after I've been able to use the data. Following is an example.
$pagination->getCollection()->transform(function ($item, $key) use(&$data, &$pagination) {
$data[] = $item->foo;
if ($item->foo === 'bar') {
$item->remove();
}
}
I've also tried using $pagination->getCollection()->forget($key); inside of the transform
$pagination->getCollection()->transform(function ($item, $key) use(&$data, &$pagination) {
$data[] = $item->foo;
if ($item->foo === 'bar') {
$pagination->getCollection()->forget($key);
}
}
That's from this question.
How to unset (remove) a collection element after fetching it?
I'm guessing the fact I'm dealing with a pagination may be making these answers not apply to my situation.

Doing a separate collection of pagination with filter() allows removing items from the pagination based on complex conditions. I return false in the transform and then simply target it in the filter.
$pagination = $pagination->getCollection()->filter(function ($item) {
return $item;
});
Edit: This actually removed the pagination properties, so I've recreated the pagination afterwards like so
// Remove already merged rows
$itemsTransformed = $pagination->getCollection()->filter(function ($item) {
return $item;
});
// Recreate because filter removed pagination properties
$itemsTransformedAndPaginated = new \Illuminate\Pagination\LengthAwarePaginator(
$itemsTransformed,
$pagination->total(),
$pagination->perPage(),
$pagination->currentPage(), [
'path' => \Request::url(),
'query' => [
'page' => $pagination->currentPage()
]
]
);

Related

Eloquent - Fetch, merge and paginate data from multiple models

I'm working on an application where users have different types of events. Each event type has its own database table/Laravel model. Right now I'm performing multiple database queries to fetch the events from the tables. After that I merge them manually by using for-each loops and creating a uniform structure.
Because the code is really long, I give you guys an example code here:
$output = [];
$events1 = EventType1::where('user',$user_id)->get();
foreach ($events1 as $ev1) {
$output[] = [
"id" => $ev1->id,
"date" => $ev1->id,
"attribute3" => $ev1->attributeA
];
}
$events2 = EventType2::where('user',$user_id)->get();
foreach ($events2 as $ev2) {
$output[] = [
"id" => $ev2->id,
"date" => $ev2->id,
"attribute3" => $ev2->someOtherAttribute
];
}
// More fetches here....
// ...
// ...
usort($output, function ($a, $b) {
return strcmp($a["date"], $b["date"]);
});
return $output;
So right now I want to improve the performance by using Pagination. But the way I fetch the data, I don't think it will work?!
Can someone help me how to fetch, merge and paginate all the events the proper way?
Is there a way to union all the data using Eloquent?
Thanks
If you want with pagination you can follow this. I have used one of my projects for merging different model data as well as including pagination. If you don't need pagination just ignore pagination method calling part.
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Pagination\Paginator;
use Illuminate\Support\Collection;
public function events(){
$events1 = EventType1::where('user',$user_id)->get();
$events2 = EventType2::where('user',$user_id)->get();
$collection = new Collection();
$collection = $collection->merge($events1);
$collection = $collection->merge($events2);
$merge = $this->paginate($collection, $limit);
return $merge;
}
public function paginate($items, $perPage = 10, $page = null, $options = [])
{
$page = $page ?: (Paginator::resolveCurrentPage() ?: 1);
$items = $items instanceof Collection ? $items : Collection::make($items);
return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options);
}

Laravel collection filter by condition and split

I have a collection and each item has a featured attribute which is either true or false, so I want to get two variables $featured and $unfeatured.
I can do this:
$featured = $collection.filter(function($item){
return $item->featured;
});
$unfeatured = $collection.filter(function($item){
return !$item->featured;
});
But maybe there's a shorter way?
I know this is quite an old post. Thought I would add this for folks coming to this from search.
Since 5.3 you can use the partition method like so:
list($featured, $unfeatured) = $collection->partition(function($item) {
return $item->featured;
});
https://laravel.com/docs/5.3/collections#method-partition
You could use the each() method
$featured = [];
$unfeatured = [];
$collection->each(function ($item) use (&$featured, &$unfeatured) {
if ($item->featured) {
$featured[] = $item;
} else {
$unfeatured[] = $item;
}
}

How do I return literal values of a PHP string so it runs as code?

I am not sure I am using the right terms even here, but I will try and explain. I am using PHP's array_filter function to filter products and it calls back to a custom function I made where I add the filter(s). I can do this hard-coded very easy, but I obviously want it to be dynamic:
To cut a long story short, the custom filter function returns to the array_filter() function like so:
return ($arr['colour']=='Red' || $arr['colour']=='White');
This works fine if hardcoded like the above, and filters the array as expected to only show products that are red or white. However, I need this to be dynamic.
So how can I construct a string of values and then use this in the return statement?
For example:
$var = "$arr['colour'] == 'Red' || $arr['colour'] == 'White'";
return ($var);
It does not work. I have tried using eval() (I don't want to use this anyway!), and it didn't work still.
I have a loop as follows constructing the string from an array:
// $value=array of filters e.g colour=Black, colour=Red
$filterparts = explode("=", $value);
$filters[] = '$arr[\'' . $filterparts[0] . '\'] == \'' . $filterparts[1] . '\'';
// Creates array e.g $arr['colour'] = 'Red'
$imploded_filter = implode(" || ", $uniquefilters);
// Creates string, e.g. $arr['colour'] = 'Red' || $arr['colour'] = 'White'
So if I echo $imploded_filter I get the extract string I would like to return:
echo $imploded_filter;
// Outputs $arr['colour'] = 'Red' || $arr['colour'] = 'White'
However if I do
return($imploded_filter);
it obviously isn't evaluating the string as hard code, so what can I do? Do I need to do something to the string or return it a different way, or construct the code I need to return in a totally different way?
Array keys can be specified dynamically. There isn't any need for eval():
$value = $array[$key];
You can build a list of filters and match each of them in the array_filter() callback:
$filters = array(
array('colour', array('white', 'blue')), // Multiple accepted values (OR)
array('material', 'Fine Bone China'), // Single accepted value
);
$filtered = array_filter($products, function ($item) use ($filters) {
// Match all filters
foreach ($filters as $filter) {
// Detect multi-value filter
$isArrayFilter = is_array($filter[1]);
if (
// Check if multi-value filter doesn't match
$isArrayFilter && !in_array($item[$filter[0]], $filter[1])
// Check if a single-value filter doesn't match
|| !$isArrayFilter && $item[$filter[0]] != $filter[1]
) {
// Filter doesn't match - exclude the item
return false;
}
}
// All filters match - include the item
return true;
});
$colors = ['Red', 'White'];
$products = array_filter($products, function ($product) use ($colors) {
return in_array($product['color'], $colors);
});
There's virtually never any reason or need to "dynamically create PHP source code". There's always an operation that can do what you want on any number of elements without needing to concatenate || operators. Here in_array is a perfectly fine function to test one value against many. You can pass in the colors array dynamically using use ($colors).
The sanest workaround for ancient PHP versions is to approximate the anonymous callback with a class:
class InArrayFilterCallback {
public $data = array();
public $key;
public __construct($data, $key) {
$this->data = $data;
$this->key = $key;
}
public callback($item) {
return in_array($item[$this->key], $this->data);
}
}
$products = array_filter($products, array(new InArrayFilterCallback($colors, 'color'), 'callback'));
Of course, you could also just use a simple foreach loop instead...
Use the in_array function, like so:
$filters = [
'colour' => [
'red',
'blue',
]
];
array_filter($list, function ($item) use ($filters) {
foreach ($filters as $index => $filter) {
if (!in_array($item[$index], $filter)) {
return false;
}
}
return true;
});
It is never a good idea to make a string and eval'uate it.

Laravel Remove [data] from collection

I've tried to query using eloquent and fractal
$lists = Category::all();
$result = Fractal::collection($lists, new CategoryTransformer())->getArray();
and return it
return response()->json((['code' => "200", 'results' => $result]));
the json result is this:
{"code":"200","results":{"data":[{"id":"1","name":"Cafe","logo":null,"cover":""},{"id":"2","name":"SPA","logo":null,"cover":""},{"id":"3","name":"Hotel","logo":null,"cover":""}]}}
How to remove "data" after result?. So i can just get the array without "data".
I've tried:
$result = Fractal::collection($lists, new CategoryTransformer(), 'results')->getArray();
return (['code' => "200", $result]);
it return me :
{"code":"200","0":{"results":[{"id":"1","name":"Cafe","logo":"","cover":""},{"id":"2","name":"SPA","logo":"","cover":""},{"id":"3","name":"Hotel","logo":"","cover":""}]}}
There is leading '0' before results. how can i remove it?
Thanks
Try this:
return (['code' => "200", "results" => $result['results']);
I think the array method can't deal with a given array.
An other solution would be to add your results:
$result['code'] = 200;
return $result;
The data is just the key, I think it won't make any issues. If you still need to remove it, update getArray() function.
Put these Collection Macros in your AppServiceProvider::boot() method:
/**
* Remove the unnecessary nested 'data' keys
*
* #param string $case For consistency, define the type of keys that should be returned
*/
Collection::macro('fractal', function ($case = 'snake_case') {
//Handle this as a nested function to block access to the $depth flag.
//It's purpose is to indicate how deep the recursion is, and,
//more importantly, when it's handling the top-level instance
$recursion = function ($case = 'snake_case', array $items = [], $depth = 0) use (&$recursion) {
//If the array has only one element in it, and it's keyed off 'data', remove the wrapper.
//However, if it has a sibling element, such as 'meta', leave it alone
if (array_key_exists('data', $items) && count($items) == 1) {
$items = $items['data'];
}
$items = (new static($items))->mapWithKeys_v2(function ($item, $key) use (
$case,
$recursion,
$depth
) {
$key = $case ? $case($key) : $key;
//If the nested item is itself an array, recursively perform the same transformation
return is_array($item) ?
[$key => $recursion($case, $item, ++$depth)] : [$key => $item];
})->toArray();
//Maintain the top-level 'data' wrapper.
//This can easily be removed later in the controller if that's not needed either
$items = (!$depth && !array_key_exists('data', $items)) ?
['data' => $items] : $items;
return $items;
};
//Return the results in the form of an instance of Collection
return new static($recursion($case, $this->items));
});
/**
* Maintain non-sequential numeric keys when performing
* \Illuminate\Support\Collection::mapWithKeys() functionality
*
* Source: https://github.com/laravel/framework/issues/15409#issuecomment-247083776
*/
collect()->macro('mapWithKeys_v2', function ($callback) {
$result = [];
foreach ($this->items as $key => $value) {
$assoc = $callback($value, $key);
foreach ($assoc as $mapKey => $mapValue) {
$result[$mapKey] = $mapValue;
}
}
return new static($result);
});
Then run your Fractal results through it:
$results = collect($fractalResults)->fractal('camel_case')->get('data', []);

Php, check if object with property = value exists in array of objects

I think I could do this with a foreach loop like this:
foreach ($haystack as $item)
if (isset($item->$needle_field) && $item->$needle_field == $needle)
return true;
}
but i was wandering if it could be done without a loop?
something like:
if(in_array($item->$needle_field == $needle,$haystack)
return true;
Yes, in modern PHP you can determine if a specific object property contains a specific value without a classic loop by combining the forces of array_column() (which has evolved to also handle arrays of objects) and in_array().
Code: (Demo)
$objects = [
(object)['cats' => 2],
(object)['dogs' => 2],
(object)['fish' => 10],
(object)['birds' => 1],
];
$needleField = 'cats';
$needleValue = 2;
var_export(
in_array($needleValue, array_column($objects, $needleField))
);
// output: true
The advantage of this technique is the obviously concise syntax. This is a perfectly acceptable approach for relatively small volumes of data.
A possible disadvantage to this technique is that array_column() will be generating a new array of all of values that relate to the $needleField.
In my above demo, array_column() will only generate a single-element array because there is only one cats property in all of the objects. If we were processing a relatively large volume of data, then it would be inefficient to bother collecting all of the qualifying cats values and then run in_array() when only one match is necessary to return true.
For "large" volumes of data where performance is a primary criterion for script design, a classic foreach loop would be a better choice and as soon as an object satisfies the rules, then the loop should be halted via return or break.
Code: (Demo)
function hasPropertyValue(array $objects, $property, $value): bool {
foreach ($objects as $object) {
if (property_exists($object, $property) && $object->{$property} === $value) {
return true;
}
}
return false;
}
var_export(
hasPropertyValue($objects, $needleField, $needleValue)
);
It's possible, but it's not any better:
<?php
function make_subject($count, $success) {
$ret = array();
for ($i = 0; $i < $count; $i++) {
$object = new stdClass();
$object->foo = $success ? $i : null;
$ret[] = $object;
}
return $ret;
}
// Change true to false for failed test.
$subject = make_subject(10, true);
if (sizeof(array_filter($subject, function($value) {
return $value->foo === 3;
}))) {
echo 'Value 3 was found!';
} else {
echo 'Value 3 was not found.';
}
Outputs Value 3 was found!.
I advise you remain with the for loop: it's legible, unlike any tricks to save a line that you might find.
This will not work if the array you are searching is out of your control. But, if you are the one building the array of objects to be searched, you can structure it using the needle as array keys to be used with array_key_exists when you are searching.
For example, instead of making your $haystack array like this:
[
{
'needle_field' => $needle
},
...
]
Make it like this:
[
$needle => {
'needle_field' => $needle
},
...
]
And search like this:
if (array_key_exists($needle, $haystack)) {
return true;
}
Finally, if you need to, you can convert back to an integer indexed array by using array_values
$haystack = array_values($haystack);
This may not work in all situations but it worked great for me.
Maybe with array_key_exists:
if (array_key_exists($needle_field, $haystack) {
if ($haystack[$needle_field] == $needle) {
echo "$needle exists";
}
}

Categories