Laravel Extending Blade Templates - error in $errors collection - php

I am using Laravel 5.3. My first Laravel project/learning experience
In my blade file I use the following snippet to show the errors below a field after a PUT or POST request.
In this case the database field is called firstName
#if ($errors->has('firstName'))
<span class="help-block">
<strong>{{ $errors->first('firstName') }}</strong>
</span>
#endif
Now since I have lot of fields, I have keep repeatings this block for every field. I looked up the Laravel docs on Blade templates (Extending Blade section) and thought I could do the following in the AppServiceProvider class (AppServiceProvider .php)
public function boot()
{
//
Blade::directive('showIfError', function($fieldName) {
if ($errors->has('$fieldName')) {
echo "<span class='help-block'>
<strong> $errors->first('$fieldName') </strong>
</span>";
}
});
}
and then use
#showIfError('firstName')
But no luck...I get the error 'Undefined variable: errors'
Looks like the Laravel error collection is not accessible in this view file.
Appreciate any help. Thanks.

This is late reply, but hopefully it will help another person who comes along. A custom blade directive is supposed to return a string php code that will be evaluated when the template is rendered. Because the $errors variable is only available when the response is made, it won't work trying to evaluate it in the directive. The solution is this :
// custom blade directive to render the error block if input has error
// put this inside your service provider's boot method
\Blade::directive('errorBlock', function ($input) {
return
'<?php if($errors->has('.$input.')):?>
<div class=\'form-control-feedback\'>
<i class=\'icon-cancel-circle2\'></i>
</div>
<span class=\'help-block\'>
<strong><?php echo $errors->first('.$input.') ?></strong>
</span>
<?php endif;?>';
});

The thing is $errors in not accessible in closure. Also, you can't pass whole object as directive closure accepts string only. With simple data you can implode() and then explode() it, but not with an object or a collection.
What you can do is to create $errors manually inside the closure.
I've tested it and it works as expected:
Blade::directive('showIfError', function($fieldName) {
$errors = session('errors');
if ($errors->has($fieldName)) {
return "<span class='help-block'>".$errors->first($fieldName)."</span>";
}
});

The issue is that the $errors variable is only available within the views. If you take a look at the Middleware that shares the variable (https://github.com/laravel/framework/blob/5.0/src/Illuminate/View/Middleware/ShareErrorsFromSession.php) you will see that it is stored in the session.
So you can access it as follows:
$errors = session()->get('errors');
Note in your example you do have a couple of other issues; the $fieldName variable should not be in quotes. Eg:
public function boot() {
Blade::directive('showIfError', function($fieldName) {
$errors = session()->get('errors');
if ($errors->has($fieldName)) {
echo "<span class='help-block'> <strong>". $errors->first($fieldName). "</strong> </span>";
}
});
}

I finally wrote a PHP function inside my view and call it with various field names.
I hope this is a good approach. Not sure what is the best way to implement this.
function showIfError($fieldName)
{
$errors=session('errors');
if ( count( $errors)>0) {
if (session('errors')->has($fieldName)) {
$msg=$errors->first($fieldName);
echo '<span class="help-block">
<strong>'. $msg.' </strong>
</span>';
}
}
}

Related

enum class in ternary expression

Enums were introduced to PHP very recently. I'm trying them out in a laravel project. I've got my enum class here:
namespace App\Enums;
enum AlertType
{
case SUCCESS;
case ERROR;
}
I'm trying to create an alert class that will take the enum in the constructor to set the severity of the alert, which will decide what colour it is rendered as to the user. Here is that class:
<?php
namespace App\View\Components;
use App\Enums\AlertType;
use Illuminate\View\Component;
class Alert extends Component
{
public string $contextClass;
public function __construct(public string $message, AlertType $alertType = AlertType::SUCCESS)
{
$this->setContextClassFromAlertType($alertType);
}
public function setContextClassFromAlertType(AlertType $alertType)
{
$this->contextClass = ($alertType === AlertType::SUCCESS ? 'success' : 'error');
}
public function getClassListFromType()
{
return [
'border-' . $this->contextClass,
'text-' . $this->contextClass
];
}
public function render()
{
return view('components.alert', [
'class' => implode(' ', $this->getClassListFromType())
]);
}
}
This alert will be used in a login form which is built using Laravel Livewire and blade components:
<form class="grid grid-cols-min-auto-1 gap-x-2" id="login" action="/login" method="post">
<x-alert :message="$errorMessage"/>
#csrf
<label for="email" class="mb-2 text-lg text-sans w-min mt-2 font-thin">Email</label>
<x-basic-input wire:model="email" placeholder="{{ $emailPlaceholder }}"/>
<label for="password" class="mb-2 text-lg text-sans w-min mt-2 font-thin">Password</label>
<x-basic-input wire:model="password"/>
</form>
When I come to display the login form I am getting the following error:
Cannot instantiate enum App\Enums\AlertType (View: /resources/views/livewire/forms/login.blade.php)
I think there is something wrong with my enum usage in the Alert component, but I'm not sure where. Can anyone point me in the right direction. I've looked at the rfc for enums but I can't see anything obvious that I'm doing wrong
I was able to reproduce this error; in my case the stack trace led back to the barryvdh/laravel-debugbar package, not sure if this is the same for you. I was able to resolve it by changing the enum to a backed enum.
I'd recommend making this change regardless, as I expect in a lot of cases strings will be easier to work with than enum instances. (Though TBH this looks like trying to use a new feature just because it's there, not because it makes sense.)
namespace App\Enums;
enum AlertType: string
{
case SUCCESS = 'success';
case ERROR = 'error';
case INFO = 'info';
}
In a backed enum, each item has a string representation that can be accessed using the value property, which we do in the constructor:
<?php
namespace App\View\Components;
use App\Enums\AlertType;
use Illuminate\View\Component;
class Alert extends Component
{
public string $contextClass;
public function __construct(
public string $message,
AlertType $alertType = AlertType::SUCCESS,
)
{
$this->contextClass = $alertType->value;
}
public function getClassListFromType()
{
return [
'border-' . $this->contextClass,
'text-' . $this->contextClass
];
}
public function render()
{
return view('components.alert', [
'class' => implode(' ', $this->getClassListFromType())
]);
}
}
You're now able to use the from() or tryFrom() methods which can add flexibility with alert types saved in a variable. For example:
<x-alert :message="$errorMessage" :type="App\Enums\AlertType::from($errorType)"/>
I think the problem here is due to dependency injection. In the constructor I'm typehinting an enum, so the Laravel container is trying to create an instance of that class to pass in which doesn't work because enums are instantiable.
If I update the container to manually resolve the typehinted instance like this:
$this->app->bind(Alert::class, function ($app) {
return new Alert('this is a message', AlertType::SUCCESS);
});
Then the issue is resolved and it works as expected. I'll need to change the way I'm using enums here as #miken32 suggests to use a backed enum and rely on the strings instead. Otherwise I'd need to override the container injection for every method where I wanted to pass an enum.

Laravel use a controller in blade

I just start to learning Laravel, I stuck in a part, I want to get data from database and pass it to a blade (view) file then use this view in another view file, and want to pass one variable to a controller but I got this error:
"Class 'adsController' not found"
web.php
Route::get('seller/panel', 'sellerController#panel');
panel.blade.php
#include('seller\hook\ads')
sellerController.php
public function panel(){
$ads = DB::table('ads')->get();
return view('hook\ads', ['ads' => $ads]);
}
adsController.php
class adsController extends Controller {
public function getStatus($p) {
if ($p == 'something') {
$status = 'yeah';
} else {
$status = 'blah blahe';
}
return view('hook\ads', $status);
}
}
ads.blade.php
<div class="ads">
#foreach ($ads as $ad)
{{ $ad->name }}
{{ adsController::getStatus($ad->approved) }}
#endforeach
</div>
So, as you see I am tring to get data from database in sellerController.php then pass it to ads.blade.php then I want to use adsController.php 's function in ads.blade.php, but it can't find adsController
Sorry I am newbie to laravel
As everyone said, it's not recommended to call the controller from the view.
Here's what I would do :
In your model :
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Ad extends Model{
public function getStatus()
{
return $this->approved == 'something' ? 'yeah' : 'blah blaehe';
}
}
In your view :
<div class="ads">
#foreach ($ads as $ad)
{{ $ad->name }}
#include('hook.ads', ['status' => $ad->getStatus()])
#endforeach
</div>
In your controller :
public function panel(){
$ads = \App\Ad::all();
return view('hook\ads', ['ads' => $ads]);
}
At the beginning of the blade file you could include the following
#php
use App\Http\Controllers\adsController as adsController;
#endphp
then on your blade template you could use the controller as you have used here.
But it is a really bad habit to use one directly.
Since you are a newbie to Laravel change that coding practice.
You could use a service provider of a sort if you want that data which needs to be shown on a particular blade view all the time.
You should try this despite I don't recommend the approach you are trying to achieve.
{!! app('App\Http\Controllers\adsController')->getStatus($ad->approved) !!}
It should work but that's very wrong.
Most often when you get this error when your namespace declaration in the controller is wrong. Typical scenario is where you generate controller stub with Artisan and then move it somewhere else from the default location.
In some cases you need to run:
composer dumpautoload
To generate new optimized class loader map.

Laravel Blade - User Allowed to Input Variable

I'm making an admin setting section of my laravel 5.2 app using the storage package from thetispro/laravel5-setting.
I'd like my admin users to be able to update email copy that get sent out to the user, but some of the emails include variables such as the users name. "Thanks for shopping with us, CUSTOMER NAME".
I can easily store the following in a setting, but when blade outputs it it just prints it out as a string instead of a variable. I've tried escaped and nonescaped the characters with {{}} and {{!! !!}. Here's what I have:
Email message an admin user can edit:
<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>
In my view I have:
{!! Setting::get('emailuserinvite') !!}
<br /><br />
<!-- Testing both escaped and nonescaped versions -->
{{ Setting::get('emailuserinvite') }}
What blade renders is just:
echo "<h2>Hi, {{ $user->name }}</h2>
<p>Welcome to my web app</p>";
I was trying to make a custom blade directive that could close the echo, display the variable and open the echo back up, but that doesn't seem to be working correctly either.
// AppServiceProvider
Blade::directive('echobreak', function ($expression) {
// echo "my string " . $var . " close string";
$var = $expression;
return "' . $var . '";
});
// Admin user settings
Hi #echobreak($user->name)
Welcome to my web app
Any advice would be appreciated! Thanks.
Update
I mocked up a simple test case using #abdou-tahiri's example but I'm still getting errors with the eval()'d code.
ErrorException in SettingController.php(26) : eval()'d code line 1: Undefined variable: user
And here is my simple controller:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Http\Requests;
use Blade;
class SettingController extends Controller
{
public function index() {
$user = [
"fname" => "Sam",
"lname" => "yerkes"];
$str = '{{ $user }}';
return $this->bladeCompile($str, $user);
}
private function bladeCompile($value, array $args = [])
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
try {
eval('?>'.$generated);
}
catch (\Exception $e) {
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
}
You may need to compile the string using Blade , check this helper function :
function blade_compile($value, array $args = array())
{
$generated = \Blade::compileString($value);
ob_start() and extract($args, EXTR_SKIP);
// We'll include the view contents for parsing within a catcher
// so we can avoid any WSOD errors. If an exception occurs we
// will throw it out to the exception handler.
try
{
eval('?>'.$generated);
}
// If we caught an exception, we'll silently flush the output
// buffer so that no partially rendered views get thrown out
// to the client and confuse the user with junk.
catch (\Exception $e)
{
ob_get_clean(); throw $e;
}
$content = ob_get_clean();
return $content;
}
so in your view file :
{!! blade_compile(Setting::get('emailuserinvite'),compact('user')) !!}
Check this Is there any way to compile a blade template from a string?
<h2>Hi, $user->name</h2>
<p>Welcome to my web app</p>
Is this what you are trying to do?
<h2>Hi, {{$user->name}}</h2>
<p>Welcome to my web app</p>

Laravel empty page with no error calling route's method

I have a methods that return base64 data image to a view after making a GET request to www.website.com/preview/{id}.
It is called by an <a> tag inside view.blade.php:
<a class="image-popup-vertical-fit" href="{{url(Config::get("app.previewPath") , $encrypted)}}" >
<img class="issue_img" src="{{App\Http\Classes\RepositoryUtil::getSmallImage($encrypted)}}" alt="{{ $name }}">
</a>
It work well if I declare a GET route with the code function inside routes.php:
Route::get(Config::get("app.previewPath") . "/{id}", function(\Request $request, $encrypted){
// ... some code ...
$base64 = \App\Http\Classes\RepositoryUtil::retriveImage($encrypted);
#readfile($base64);
});
But if I move the same code inside a controller's method, it return a blank page!
Route::get(Config::get("app.previewPath") . "/{id}", "MyController#getPreview");
MyController.php
public static function getPreview(\Request $request, $encrypted){
// ... same code as routes.php ...
$base64 = \App\Http\Classes\RepositoryUtil::retriveImage($encrypted);
#readfile($base64);
}
Where am I wrong?
I figure it out, it was a "distraction error".
I leave a middleware enabled in all methods of the controller, and it fails printing dd("Not authorized!"); without return before it.
It didn't return nothing without any errors!
I restrincted the middleware only to selected actions.
Thanks for support.

Trying to get property of non-object in laravel

I have following code
In controller
public function ViewPost($id)
{
$viewpost= Post::find($id);
return view('Guest.viewpost', ['viewpost'=>$viewpost]);
}
In view
#foreach($viewpost as $val)
<br>
Post title=
<a href="viewpost/{{$val->id}}" >{{$val->post_title}}</a>
<br>
Post content={{$val->post_content}}
<br> <br>
Featured Image=<img src="{{$val->featured_image}}" height="150" width="150">
#endforeach
But above code throw an error Trying to get property of non-object. so i tried following way
$val['post_title'];
The above code wont throw an error nor displaying output.
If i print in controller it display output but same in view if i print it give error
print_r($viewpost);
I am using laravel 5.1. Can any one tell us what i am doing wrong ?
Thank you.
Update
after the suggestion of #CodeRomeos.i can able to display data image not loading.
<img src="{{$val->featured_image}}" height="150" width="150">
<img src="uploads/penguin.jpg">
same works in show view
Update your controller like this
public function ViewPost($id)
{
$viewpost= Post::find($id)->first();
return view('Guest.viewpost', ['viewpost'=>$viewpost]);
}
or
public function ViewPost($id)
{
$viewpost= Post::find($id)->get();
return view('Guest.viewpost', ['viewpost'=>$viewpost]);
}
Do come back if you still face the issue.!

Categories