My question is similar to this question Adding new property to Eloquent Collection, but I need some further explanation.
In many occasions it would be really useful to be able to construct your own collections and use Laravel collection methods. The docs only explain how to make a simple collection of nameless items (a 1 dimensional data set), but I'm interested in 2 dimensional data sets.
For example when you use Laravel validator, it takes your named array of inputs and spits out a Illuminate\Support\ValidatedInput object, which is a collection. Then you can access your properties as properties in any object and also use collection methods such as only() or except().
$safe = $request->safe(); //returns a Illuminate\Support\ValidatedInput collection
//$safe = $request->validate(); //returns an associative array
echo $safe->my_prop;
Model::create( $safe->only(['prop1', 'prop2']) );
My objective is to transform an associative array such as:
$a = [
'prop1' => 'val1',
'prop2' => 3656,
'prop3' => ['stuff', 'more']
];
Into a collection that can be used like the Illuminate\Support\ValidatedInput object.
PS I'm currently using arrays and array functions (like array_intersect_key() as a substitute to ->only()), but would rather use collections.
Using
$c = collect( $a );
you cannot access the properties of $c like $c->prop, you still need to use $c['prop']. Which is confusing, given that many objects in laravel are both objects (which allow $object->property) AND collections.
Related
I thought the data which is from DB::get() is Array.
However , the console says it is not array.
$fruitList = Food::where('id' => 300)->get(['id']);
shuffle($fruitList);
ErrorException: shuffle() expects parameter 1 to be array, object given in file
The return value of get() is not an array. it's Laravel array collection you can convert it to an array or use shuffle of array collection:
$fruitList = Food::where('id' => 300)->get(['id'])->toArray();
shuffle($fruitList);
with array collection:
$fruitList = Food::where('id' => 300)->get(['id'])->shuffle();
Just like #A.seddighi mentioned, using get() or all() gives you a collection. It may seem like an array when you output it using return or print but it is different.
Collections can be filtered, queried and so on. e.g
$fruitList->has('price')
etc.
To get an array simply called the toArray() method on it, you may also use flatMap(), mapWithKeys() etc. Make sure you follow the documentation that is suitable for your version of laravel.
I have a serialized String like this
$string = '[{"name":"FOO"},{"name":""},{"name":"BAR"}]';
I am trying to process it via Laravel Collection's filter method and eliminate items without a defined "name" property.
$collection = collect(\json_decode($string));
$collection = $collection->filter(function($v){
return !empty($v->name);
});
$string = \json_encode($collection->toArray());
dd($string);
Normally I am expecting something like this:
[{"name":"FOO"},{"name":"BAR"}]
But I'm getting something like this:
{"0":{"name":"FOO"},"2":{"name":"BAR"}}
Funny thing is, if I skip the filtering process or return true every time, I keep getting the string in the desired format. Removing the toArray() call has the same result. I don't want to keep the numeric indices as associative object keys.
Why this anomaly? And what should I do to get the serialized data in desired format?
In PHP arrays the index key must be unique.
In your case you have the key 'name' and collection automatically assigns the index key to all items in the collection.
To overcome that problem just call
$string = \json_encode($collection->values());
Using Laravel 5.3, I have a model with the following function
public function myData() {
return $this->hasMany(MyData::class);
}
and in my collection I have the following
$my_data = MyModel->myData()->get();
All good so far. If I return $my_data I get an eloquent collection with three items.
What I need now though is to create a duplicate of that collection but containing only three of the fields.
I have tried several different things, each of which return an error. The following is the closest I have got, but this returns an empty array - I assume because the fields are located one level deeper than the collection object.
$new_collection = $my_data->only(['field_1', 'field_2', 'field_3']);
What would be the correct way to create a new collection containing all three items, each with only the three selected fields?
Thanks for your help
You could use map:
$slimmed_down = $collection->map(function ($item, $key) {
return [
'field_1' => $item->field_1,
'field_2' => $item->field_2,
'field_3' => $item->field_3
];
});
This will return a new Collection with just the values you want. As far as I know there isn't any other method that does what you want, so iterating over every item and selecting the fields this way is one of the few solutions.
The advantage of using map instead of a standard foreach loop is that when you use map it returns a new instance of Collection.
Edit:
After some thoughts and research about this, the problem you'll have created is that the all the values in the Collection aren't instances of anything anymore. If you don't mind this effect, an even prettier and faster way would be to do this:
$slimmed_down = $collection->toArray()->only(['field_1', 'field_2', 'field_3']);
This basically has the same result.
Using Laravel 9, I just had the same issue :
$my_data->only(['field_1', 'field_2', 'field_3']);
returning an empty array.
I solved it with :
$my_data->map->only(['field_1', 'field_2', 'field_3']);
It's built dynamically, annotations are not an option. I have the constraints in an array.
It seems you need to manually construct the validator but I get stuck fast on the point where you need to construct a MetaDataFactory that needs an loader, but all the loaders have the job of loading meta data from a file.. I dont think I'm doing it right.
Simply said:
I have an array/object with keys/values. I want them validated by another array that contains the constraints.
It's not possible to iterate over the keys and call validate on each and every one of them, since some rely on the context values from other keys.
$array = [
'key1' => 'abc',
'key2' => 'def',
];
$constraints = [
'key1' => new All([
new Constraint..,
new CallbackConstraint.., // <- this one checks key2 for its value
]),
'key2' => new NotBlank
];
I can also build one array containing both.
Againt, the object/array is built dynamically, i cant validate the container itself. It's about the keys inside it that are definitions on its own.
So what i basically would want is:
$validator->validate($array, $constraints);
I can imagine you need a custom MetaDataFactory / Loader / Context class or something, the problem is simply that the callback validator needs to access $this->getRoot() to get to the other values
Nevermind that, you would need something like a CallbackLoader in which you create your own properties => constraints mapping. But the LoaderInterface requires a concrete implementation of ClassMetaData, which on his own has no way of dealing with arrays or ArrayObjects, unless you have methods/properties on that ArrayObject. But--since my container is built dynamically, i can't have that. Method methods are not an option because of the property/method_exists calls.
I have collection like this:
child{length:3, models:array[3], _byId:Object}
I wanna sort models array, I use lodash like this:
var array_of_objects = new ListCollection();
var data = _.sortByOrder(array_of_objects.models, ['id'], ['asc']);
And I get result only:
[child, child, child]
How to sort models arrays with keep length and the Object.
If you want to order the original collection, set collection.comparator to 'id' and then call collection.sort().
To order the models without affecting the collection do: _.sortBy(collection.models, 'id')
Note that these will order the Models, not native js arrays. If your looking to operate on a raw collection of arrays, get a copy of the collection with var models = JSON.parse(collection.toJSON()) and then follow the instructions for _.sortBy.