I have a laravel collection object.
I want to use the nth model within it.
How do I access it?
Edit:
I cannot find a suitable method in the laravel documentation. I could iterate the collection in a foreach loop and break when the nth item is found:
foreach($collection as $key => $object)
{
if($key == $nth) {break;}
}
// $object is now the nth one
But this seems messy.
A cleaner way would be to perform the above loop once and create a simple array containing all the objects in the collection. But this seems like unnecessary duplication.
In the laravel collection class documentation, there is a fetch method but I think this fetches an object from the collection matching a primary key, rather than the nth one in the collection.
Seeing as Illuminate\Support\Collection implements ArrayAccess, you should be able to simply use square-bracket notation, ie
$collection[$nth]
This calls offsetGet internally which you can also use
$collection->offsetGet($nth)
and finally, you can use the get method which allows for an optional default value
$collection->get($nth)
// or
$collection->get($nth, 'some default value')
#Phil's answer doesn't quite obtain the nth element, since the keys may be unordered. If you've got an eloquent collection from a db query it'll work fine, but if your keys aren't sequential then you'll need to do something different.
$collection = collect([0 => 'bish', 2 => 'bash']); $collection[1] // Undefined index
Instead we can do $collection->values()[1] // string(4) bash
which uses array_values()
Or even make a macro to do this:
Collection::macro('nthElement', function($offset, $default = null) {
return $this->values()->get($offset, $default);
}):
Example macro usage:
$collection = collect([0 => 'bish', 2 => 'bash']);
$collection->nthElement(1) // string(4) 'bash'
$collection->nthElement(3) // undefined index
$collection->nthElement(3, 'bosh') // string (4) bosh
I am late to this question, but I thought this might be a useful solution for someone.
Collections have the slice method with the following parameters:
$items->slice(whereToStartSlice, sizeOfSlice);
Therefore, if you set the whereToStartSlice parameter at the nth item and the sizeOfSlice to 1 you retrieve the nth item.
Example:
$nthItem = $items->slice($nth,1);
If you are having problems with the collection keeping the indices after sorting... you can make a new collection out of the values of that collection and try accessing the newly indexed collection like you would expect:
e.g. Get the second highest priced item in a collection
$items = collect(
[
"1" => ["name" => "baseball", "price" => 5],
"2" => ["name"=> "bat", "price" => 15],
"3" => ["name" => "glove", "price" => 10]
]
);
collect($items->sortByDesc("price")->values())[1]["name"];
// Result: glove
Similar to morphs answer but not the same. Simply using values() after a sort will not give you the expected results because the indices remain coupled to each item.
Credit to #howtomakeaturn for this solution on the Laravel Github:
https://github.com/laravel/framework/issues/1335
Related
I want to combine my ordered products and display the order list.
Controller :
$orders = Order::where('customer_id', 1)->pluck('products');
print_r($orders);
This is what I receive:
Array (
[0] =>
[
{"id":3,"product_id":3,"size":"47","quantity":7,"name":"Simple Regular T-shirt","price":2200,"thumbnail":"Thumbnail_614291597.jpg"},
{"id":7,"product_id":4,"size":"47","quantity":8,"name":"Simple Regular Shirt","price":123,"thumbnail":"Thumbnail_91520734.jpg"}
]
[1] =>
[
{"id":9,"product_id":3,"size":"45","quantity":2,"name":"Simple Regular T-shirt","price":2200,"thumbnail":"Thumbnail_614291597.jpg"}
]
)
But I want.
Array (
[0] =>
[
{"id":3,"product_id":3,"size":"47","quantity":7,"name":"Simple Regular T-shirt","price":2200,"thumbnail":"Thumbnail_614291597.jpg"},
{"id":7,"product_id":4,"size":"47","quantity":8,"name":"Simple Regular Shirt","price":123,"thumbnail":"Thumbnail_91520734.jpg"},
{"id":9,"product_id":3,"size":"45","quantity":2,"name":"Simple Regular T-shirt","price":2200,"thumbnail":"Thumbnail_614291597.jpg"}
]
)
How I can do this?
I already tried a different way, but I can't do this. Firstly I was trying to convert it array and then use the array_marge() function for those arrays. but that array needs only two arrays but for my case, it is not specified how many arrays the user has given. And try to solve it with a loop (I just tried). I am new in this field.
You could try
$orders = Order::where('customer_id', 1)
->pluck('products')
->values()
->flatten(1);
The pluck will return a collection, flatten with a depth of 1 will remove the nesting. values will reset the keys to sequential - (it's not strictly necessary here)
Laravel Docs - Collections - Values
Laravel Docs - Collections - Flatten
Just add flatten() after applying the query (with ->get()):
$orders = Order::where('customer_id', 1)->get()->pluck('products')->flatten();
print_r($orders);
Side note:
Instead of print_r() You can use dump() to pretty print the output in your browser
$orders->dump(); //only dump
$orders->dd(); //dump and exit
I have a line of code similar to the following:
Sport::pluck('id', 'name)
I am dealing with frontend JavaScript that expects a list in this format:
var list = [
{ text: 'Football', value: 1 },
{ text: 'Basketball', value: 2 },
{ text: 'Volleyball', value: 3 }
...
]
I am trying to figure out how I can somehow transform the id and name values that I pluck from my model to a format similar to the Javascript list.
If that's unclear, I am looking to end up with an associative array that contains two keys: text and value, where text represents the name field on my model, and where value represents the id of the model - I hope this makes sense.
How would I approach this?
I initially tried something like this (without checking the documentation)
Sport::pluck(["id" => "value", "name" => "text]);
But that isn't how you do it, which is quite clear now. I've also tried some map-related snippet, which I cannot seem to Ctrl-z to.
Any suggestions?
Another method is to use map->only():
Sport::all()->map->only('id', 'name');
The purpose of pluck is not what you intend to do,
Please have a look at below examples,
Sport::selectRaw("id as value, name as text")->pluck("text","value");
// ['1' => 'Football', '2'=>'BasketBall','3'=>'Volleyball',...]
Syntax
$plucked = $collection->pluck('name', 'product_id');
// ['prod-100' => 'Desk', 'prod-200' => 'Chair']
Please see the documentation.
Your output is possible using simple code.
Sport::selectRaw('id as value, name as text')->get();
You could use map.(https://laravel.com/docs/5.8/collections#method-map)
$mapped = Sport::all()->map(function($item, $index) {
return [
"id" => $item["id"],
"name" => $item["text"]
];
});
This is the easiest way. Actually Laravel offers a better way for it. You can use api resources to transform your data from eloquent for the frontend:
https://laravel.com/docs/5.8/eloquent-resources
Try with toArray function:
Sport::pluck('id', 'name)->toArray();
Then you can return your result with json_encode php function;
Given a variable that holds this string:
$property = 'parent->requestdata->inputs->firstname';
And an object:
$obj->parent->requestdata->inputs->firstname = 'Travis';
How do I access the value 'Travis' using the string? I tried this:
$obj->{$property}
But it looks for a property called 'parent->requestdata->inputs->firstname' not the property located at $obj->parent->requestdtaa->inputs->firstname`
I've tried various types of concatenation, use of var_export(), and others. I can explode it into an array and then loop the array like in this question.
But the variable '$property' can hold a value that goes 16 levels deep. And, the data I'm parsing can have hundreds of properties I need to import, so looping through and returning the value at each iteration until I get to level 16 X 100 items seems really inefficient; especially given that I know the actual location of the property at the start.
How do I get the value 'Travis' given (stdClass)$obj and (string)$property?
My initial searches didn't yield many results, however, after thinking up a broader range of search terms I found other questions on SO that addressed similar problems. I've come up with three solutions. All will work, but not all will work for everyone.
Solution 1 - Looping
Using an approach similar to the question referenced in my original question or the loop proposed by #miken32 will work.
Solution 2 - anonymous function
The string can be exploded into an array. The array can then be parsed using array_reduce() to produce the result. In my case, the working code (with a check for incorrect/non-existent property names/spellings) was this (PHP 7+):
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
$property = 'parent->requestdata->inputs->firstname';
$name = array_reduce(explode('->', $property), function ($previous, $current) {
return is_numeric($current) ? ($previous[$current] ?? null) : ($previous->$current ?? null); }, $obj);
var_dump($name); //outputs Travis
see this question for potentially relevant information and the code I based my answer on.
Solution 3 - symfony property access component
In my case, it was easy to use composer to require this component. It allows access to properties on arrays and objects using simple strings. You can read about how to use it on the symfony website. The main benefit for me over the other options was the included error checking.
My code ended up looking like this:
//create object - this comes from and external API in my case, but I'll include it here
//so that others can copy and paste for testing purposes
//don't forget to include the component at the top of your class
//'use Symfony\Component\PropertyAccess\PropertyAccess;'
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
//string representing the property we want to get on the object
//NOTE: syfony uses dot notation. I could not get standard '->' object notation to work.
$property = 'parent.requestdata.inputs.firstname';
//create symfony property access factory
$propertyAccessor = PropertyAccess::createPropertyAccessor();
//get the desired value
$name = $propertyAccessor->getValue($obj, $property);
var_dump($name); //outputs 'Travis'
All three options will work. Choose the one that works for you.
You're right that you'll have to do a loop iteration for each nested object, but you don't need to loop through "hundreds of properties" for each of them, you just access the one you're looking for:
$obj = (object)[
'parent' => (object)[
'requestdata' => (object)[
'inputs' => (object)[
'firstname' => 'Travis'
]
]
]
];
$property = "parent->requestdata->inputs->firstname";
$props = explode("->", $property);
while ($props && $obj !== null) {
$prop = array_shift($props);
$obj = $obj->$prop ?? null;
}
var_dump($obj);
Totally untested but seems like it should work and be fairly performant.
My app reads from a DB that get's written by another API, now in some outlandish cases (that actually happened today) it wrote a customer id of 0, which ofcourse, does not exist.
I am looking for an elegant 'from-the-top' model or even presenter solution for handling erroneous ID's that do not exist.
So instead of finding every $whatever->customer->id in my app and then writing in an isset()/empty() ternary function, I am looking to pacify this error in a more elegant way where any customer instantiation/eloquent object would send the string "NA" to a non existent object, so even if an email/phone/etc or any other column of customer model, it would return a simple "NA" string.
I am struggling to find an eloquent solution that would provide 1 point of change.
you can use withDefault() modifier on your relationship.
example:
use Illuminate\Database\Eloquent\Model;
class Whatever extends Model {
public function customer() {
return $this->belongsTo(Customer::class, 'customer_id', 'id')
->withDefault([
'id' => 'NA',
'name' => 'Unknown'
// etc
]);
}
}
I would suggest you take a look at a Laravel class that most people don't know about. That is Fluent.
It allows you to do stuff like this:
$fluent = new Fluent([
'one' => 1,
'two => 2,
]);
echo $fluent->get('one'); // returns 1
echo $fluent->get('three'); // returns null
echo $fluent->get('three', 3); // returns 3
As you can imagine, it's perfect to use with third-party APIs and data that sometimes provide unexpected results. You can also do a lot more with Fluent.
Alternatively, you could look into Laravel helpers such as array_get(). From the documentation:
The array_get function retrieves a value from a deeply nested array using "dot" notation:
$array = ['products' => ['desk' => ['price' => 100]]];
$price = array_get($array, 'products.desk.price');
// 100
The array_get function also accepts a default value, which will be returned if the specific key is not found:
$discount = array_get($array, 'products.desk.discount', 0);
// 0
I am using Laravel Collections methods and am trying to key my query results (which are a collection) by the id. The problem is I have multiple entries with the same id, but point to different countries and I want to have all of the values, not just the last one.
Here is my code that i am using so far:
$allCountries = new Collection($allCountries);
$offerCountries = $allCountries->keyBy('id');
dd($offerCountries);
foreach ($offer as $o) {
$o->countries = $allCountries->get($o->id);
}
To explain, my query puts the results in $allCountries which contains ids and countries and those results looks something like this
id=>225, country=>US
id=>225, country=>IT
id=>3304, country=>NZ
Just to give you a quick idea. I want to key this by the id which results in $offerCountries. I then loop thru a previous Collection that contains offers which have a certain ID that relates to the country result by id. So for the offer 225, the countries it contains are US and IT. I loop thru each offer and set the countries object equal to all the $allCountries id that it equals. The problem I have here is keyBy overwrites the value and only takes the last one. I am hoping to get some results like this:
[
225 => countries: {'id' => 225, 'country' => 'US'}, {'id' =>
'225', 'country' => 'IT'}
3304 => ['id' => 3304, 'country' => 'NZ'],
]
Is there a laravel method to do this, or do I need to write my own keyBy so it does not overwrite. If so, how can I get started to write this method?
Thanks
Instead of using keyBy, use groupBy:
$countriesById = collect($allCountries)->groupBy('id');
You could use filter and create a custom filter
$filtered = $allCountries->filter(function ($item) use ($id) {
return $item->id == $id;
});
$filtered->all();