How to handle Undefined Offset in laravel?
The problem is that $blogs
is actually defined and its value is []
(i.e. empty array
) so it means that isset($blogs)
statement will evaluate to true
. Same thing is valid for collections. If a collection is empty (i.e. has no elements but it's defined) isset($blogs)
will still evaluate to true
but accessing $blogs[0]
will cause an Undefined offset: 0
error.
You could try the following solutions:
Using count
if(count($blogs)) { /* do something */ }
if $blogs = []
or $blogs = null
the function count
will return zero so that means that $blogs
is empty.
Using empty
if(!empty($blogs)) { /* do something */ }
This is the equivalent of writing !isset($var) || $var == false
as described in the PHP Manual - empty:
Returns FALSE if var exists and has a non-empty, non-zero value. Otherwise returns TRUE.
The following things are considered to be empty:
""
(an empty string)0
(0 as an integer)0.0
(0 as a float)"0"
(0 as a string)NULL
FALSE
array()
(an empty array)$var;
(a variable declared, but without a value)
Checking if a collection is empty
If $blogs
is a Collection
is sufficient to check if it is not empty using `isNotEmpty() method:
@if($blogs->isNotEmpty()) <!-- Do your stuff --> @endif
EDIT
I forgot to add the blade syntax:
@if(count($blogs)) <!-- Do whatever you like --> @endif
or
@if(!empty($blogs)) <!-- Do whatever you like --> @endif
EDIT 2
I'm adding more content to this answer in order to address some of the issues presented in the comments. I think that your problem is the following:
$blogs
is an empty collection, so it's defined but it has no elements. For this reason the if(isset($blogs))
statement will evaluate to true
passing the first if
condition. In your blade template you are making the check {{ $blogs[0]->title or '' }}
that is absolutely not equal to <?php isset($blogs[0]->title) ? $blogs[0]->title : '' ?>
as pointed out in the comments, but it is an expression that will return true
or false
, so it will never print out title
parameter even if $blogs[0]
exists. The problem here is that when checking the condition $blogs[0]->title
you are actually accessing the element 0
of the $blogs
collection that will trigger the exception Undefined offset: 0
because the collection is actually empty. What i was saying is that in addition to the
if(count($blogs)) { /* do something */ }
(that checks that $blogs
is set and that it's length is greater than 0
) in your template you should do
{{ isset($blogs[0]->title) ? $blogs[0]->title : '' }}
or more concisely
{{ $blogs[0]->title ?: '' }}
assuming that the control flow will arrive there only if the $blogs
passed the first if
. If the issue still persists the problem is elsewhere in your code IMHO.
You can simply solve this with the data_get()
helper.
For example:
php artisan tinkPsy Shell v0.8.11 (PHP 7.0.22-0ubuntu0.16.04.1 — cli) by Justin Hileman>>> >>> $a = collect([[], null, App\Models\User::find(1)]);=> Illuminate\Support\Collection {#887 all: [ [], null, App\Models\User {#896 id: 1, name: "user1", email: "user1@thisisdevelopment.nl", last_name: "Gabrielle", first_name: "Rempel", deleted_at: null, created_at: "2017-08-12 15:32:01", updated_at: "2017-09-05 12:23:54", }, ], }>>> data_get($a[0], 'name', 'nope');=> "nope">>> data_get($a[1], 'name', 'nope');=> "nope">>> data_get($a[2], 'name', 'nope');=> "user1">>>
So in this case:
{{ data_get($blogs[0], 'title', '') }}
data_get()
will work both on arrays and objects, returning the key or attribute defined in the second param (this can be laravel.dot.notation.style
, or just an array), the 3rd param will be the default return value if the object/array or the key/attribute does not exist, the default is null.
Edit:
Just saw the request for the extra explanation on why the original code wasn't working.
Index 0 simply does not exist on the array/collection that is passed to the view.
>>> $a = [1 => App\Models\User::find(1)];=> [ 1 => App\Models\User {#890 id: 1, name: "user1", // ... etc }, ]>>> $a[0]->name ?: 'nope';PHP error: Undefined offset: 0 on line 1>>> $a[1]->name ?: 'nope';=> "user1"
It doesn't matter if OP used the blade or default
, it doesn't even make it to the ternary statement because of the missing 0 index on $blogs
.
Edit 2 as requested:
So the reason you get the Undefined offset: x
error is because of the order in which PHP evaluates the code.
Blade's or default
is behind the scenes nothing more than a ternary statement:
return preg_replace('/^(?=\$)(.+?)(?:\s+or\s+)(.+?)$/si', 'isset($1) ? $1 : $2', $value);
So this will make:
isset($blogs[0]->title) ? $blogs[0]->title : ''
isset()
will check if title on the object is set, but to do so, it will require $blogs[0]
to be a valid object. In order to do that, it will try and get the object from the $blogs
array at index 0. But since this index does not exist, it will trigger the Exception with an Undefined offset: 0
.
In order to make this work with Blade's or default
, you would first have to ensure that $blogs[0]
is defined (and preferably also check that it's an object, otherwise you'll get the trying to get property of non-object error, please note that this should not be the responsibility of the view), after that you would be able to use the or default
as you would any other time.
@if (isset($blogs[0]) && is_object($blogs[0])) {{ $blogs[0]->title or '' }}@else // some other default placeholder@endif
Basically you will get the same offset error when using data_get()
, because index 0 still does not exist.
{{ data_get($blogs[0], 'title', '') }} // Undefined offset: 0
You could play dirty and do this (this would not pass any code review anywhere and I should not have typed this at all, this is just to illustrate)
{{ data_get($blogs, '0.title', '') }} // Will display '' as it will check if key 0 exists
Anyway, with data_get()
you would still end up doing something like this, as you would need to make sure $blogs[0]
is something you can work with:
@if (isset($blogs[0])) {{ data_get($blogs[0], 'title', '') }}@else // some other default placeholder@endif
Bottomline, the best option would be not to rely on indexes like this in your view, this is simply not the responsibility of your view.
Blade's or default
works perfectly on single variables, but when dealing with object attributes, you would just have to make sure the (parent) object exists when doing so.