EDIT: Clearer example from below comment.
home.blade.php:
@component('test')
// Not actually passing in null, this is a placeholder for something like
// "$myObj->getLink()", which sometimes returns null
{{null}}
@endcomponent
test.blade.php:
<div class="test">
@empty($slot)
{{$slot}}
@else
Slot not empty!
@endif
</div>
That will render the text, "Slot not empty!", when the value was originally null. This appears to be due to the slotted value being wrapped in an HtmlString
instance, which is evaluated against (and returns false, since it is a valid instance of an object).
To work around this, I had to do the following inside our component:
@if(isset($link) && !empty($link->toHtml()))
5.4 is not supported anymore, please test on 5.5 and report if this is still an issue, many things were fixed between 5.4 and 5.5
Just tested on 5.5.28 (and then upgraded to 5.5.34) and confirmed it is still an issue.
home.blade.php:
@component('test')
{{null}}
@endcomponent
test.blade.php:
<div class="test">
@empty($slot)
{{$slot}}
@else
Slot not empty!
@endif
</div>
That will produce the value, "Slot not empty!"
Conversely, switching that @empty
to @isset
will make the slot variable render (which is an empty string or null). This is because when it checks that value, it's actually an instance of HtmlString
, and therefore isn't considered empty or null.
I believe an easy fix here would be to override the magic methods on those classes to check their value for isset, or maybe even override the __toString
method. That would resolve @isset
, not positive about @empty
.
(ignore accidental closing and reopening, misclick ;) )
This is indeed the normal behavior of php's empty() function.
empty — Determine whether a variable is empty
In other words, it will return true if the variable is an empty string, false, array(), NULL, “0", 0, and an unset variable.
You are looking for php's isset() function.
isset — Determine if a variable is set and is not NULL
In other words, it returns true only when the variable is not null.
Laravel provides you the @isset
and @empty
directives Blade Templating: Control Structures.
@isset($variable)
// $variable is defined and is not null...
@endisset
@empty($variable)
// $variable is "empty"...
@endempty
Replacing your condition with @isset($variable)
will fix your bug, i guess.
A little bonus tip:
There is also a package called laravel-blade-directives, which provides you a collection of useful blade directives that might help you.
In this package you'll find the @isnotnull($variable) directive, which would also solve it.
Happy Coding :)
@brianwusu I believe you misunderstood. I am getting the opposite of what is supposed to happen.
I understand that if a variable is null, an empty string, empty array, false, etc., that empty will return true.
What is happening here, however, is that we have a value that is most certainly "empty" by that criteria being passed into a component, being passed into the @empty
directive, and being evaluated as not-empty.
Please see my followup example above ☝️ in the comments.
EDIT: Updated my original post. Had a typo in my original description - woops! Should be a lot clearer now.
We know that {{null}}
compiles to e(null)
which outputs an empty string as can be easily with something along the lines
Route::get('/', function () {
return e(null);
});
in routes/web.php
. So this certainly is not the issue here.
But my guess is that the formatting of the
@component('test')
{{null}}
@endcomponent
is the issue. To prove that, swap the content of test.blade.php
with the following
@php
dd($slot);
@endphp
As a result you will see
HtmlString {#433 ▼
#html: ""
}
in the browser. This tells us that $slot
is not a plain string but an object and therefore not empty as already explained by @brianwusu. And it looks kind of intended.
It seems to be possible to work around that issue by using @empty(e($slot))
instead of @empty($slot)
. Not really sure if this may introduce other issues though.
@Namoshek I think we agree here? That's the issue I'm pointing out here. One would expect that slot
would be considered empty, as it was a null value. However, because it is being wrapped in that HtmlString
object, it is not considered empty or null. That's the issue here - I would expect that if I do @component('test'){{null}}@endcomponent
and then did @empty($slot)Empty!@endif
it would be considered "empty".
Actually, I don't agree, because I don't see a use case for {{null}}
and an @empty($slot)
check on it. The only use case I see for passing {{null}}
would be if you had {{$someObj}}
instead (where $someObj
can be null
). But here you quite certainly would use @isset($slot)
and not @empty($slot)
. Or am I missing something?
But well, if you see a use case, you can still use the workaround I provided above anyways...
I would expect that if I do
@component('test'){{null}}@endcomponent
and then did@empty($slot)Empty!@endif
it would be considered "empty".
That looks like a dirty hack and not how the template syntax is meant to be used.
@Namoshek I'm not passing null into a blade component, that was just an example. My blade component invocation ends up kinda looking like this:
@component('myComponent')
@slot('link'){{$myObject->getLink()}}@endslot
{{$myObject->getDescription()}}
@endcomponent
Then in the component, something like this:
<div class="my-component">
@empty($link)
{{$slot}}
@else
<a href="{{$link}}">{{$link}}</a>
@endif
</div>
In the above example, $myObject->getLink()
will sometimes be null (as it is optional). When it is null, I would expect the @empty
directive to catch that, and show the slot instead. However, it instead will render an anchor tag with no href or content.
I understand that I shouldn't be passing null into a component directly - there'd be no valid reason to do that, it would make zero sense to do so. I definitely believe it's an issue that a value that is nullable / empty / empty is treated as not within a blade component, because it gets wrapped.
EDIT: I mention this as well in the OP, but using @isset
or @empty
both treat the value as not null / not empty. i.e. if the component is changed to:
<div class="my-component">
@isset($link)
<a href="{{$link}}">{{$link}}</a>
@else
{{$slot}}
@endif
</div>
It'll render the anchor tag instead.
If you use
@component('myComponent')
@slot('link', $myObject->getLink())
{{$myObject->getDescription()}}
@endcomponent
you get your desired behavior. If you don't pass complex content to a slot, the content is passed as-is... so in this case as string.
An optional field is considered "complex content"? Are you being serious right now?
I definitely do not get the desired behavior - I don't want my HTML to have empty anchor tags.
Not the field is complex, but the content (may be). Using enclosing blade template tags expects the content to be HTML and handles it as such. If you pass the content directly as parameter to the single opening template tag, which is absolutely sufficient for a string (and also more readable in my opinion), it is passed without changes, because blade doesn't expect HTML there. Not sure what is unclear about that.
I don't want my HTML to have empty anchor tags.
Demanding changes in a framework because of personal code style taste is kind of weird.
I'll quit the discussion here now and let the maintainers make their choice.
Re: @slot('link', $myObject->getLink())
, I've never seen this syntax before in the docs - a cursory glance shows the docs mention passing data in as part of the @component
directive, but not into a slot like that. You are correct in that it appears to resolve my issue - Using that syntax the null
is passed through without being coerced (and most importantly, handled properly).
I think the ☝️ syntax should be added to the docs, and I think it'd be a lot clearer to have a reference to why that alternate syntax is available. This doesn't seem like such a far-out use case, and I doubt I'm the only person who's scratched my head trying to figure out what's happening.
Facing same issue.
@mohd-isa Read the latest comments, they explain how to circumvent the "issue".
I'm facing this problem passing a timestamp string and converting it to a Carbon object in the view, so I had to force the $slot
to return a string like this:
{{ Carbon\Carbon::createFromTimestamp(''.$slot.'')->format('...') }}
Why was this closed, is it not considered an issue? I'm still using https://github.com/laravel/framework/issues/23049#issuecomment-363546052 solution, but it's more of a workaround tbh.
Hi.
I'm using:
@if($slot->isEmpty())
I'ts one of the methods of Illuminate\SupportHtmlString
I had a similar issue (v. 5.8.*) - I resolved by using casting: @empty((string) $slot)
.
@if($slot->isEmpty())
sadly breaks if a slot is passed using <x-foo :slot-name="$bar" />
, and possibly <x-foo slot-name="bar" />
although I have not checked.
The best way I can think of handling this right now is with a custom Htmlable
(and possible DeferringDisplayableValue
) aware is_empty()
helper that resolves the actual string and then checks it. The advantage of this is that I have a place to trim the string before checking it, which is most often appropriate.
@stefanfisk You could try casting the $slot
to a string
with blank()
or so:
@if(blank((string)$slot))
<span>No slot</span>
@endif
Most helpful comment
To work around this, I had to do the following inside our component: