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.
Related
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.
How "escape" the output of a string so js on the search line will not work? In Zend Framework1(v. 1.12.3).
<div id="search-box">
<input type="text" placeholder="<?php echo $this->languageText('TEXT_SEARCH_OUR_SITE',"Search Our Site"); ?>" name="query" id="query" />
<div class="search-box-bk"></div>
</div>
I tried this, but it didn't work
placeholder="<?php echo $this->escape($this->languageText('TEXT_SEARCH_OUR_SITE',"Search Our Site"));
Sadly Zend 1 does not have good escaping mechanism by default. ->escape() only uses standard PHP method htmlspecialchars.
You can set what escape method to use in Controller by getting View and using method setEscape('htmlentities') ( setEscape(array('Class/Object','method)))
(response for comment)
In one of my projects I've used Zend2 Escaper. First I've copied Escaper to my folder in Library (i renamed it to Adx_Escaper so it fits in existing library - I'm lazy ;)). Next I've created ViewHelper (resources.view.helperPath.Adx_View_Helper = "Adx/View/Helper/"(application.ini)) with content like this:
class Adx_View_Helper_Escaper extends Zend_View_Helper_Abstract
{
protected $escaper = null;
public function Escaper()
{
if ($this->escaper === null) {
$this->escaper = new Adx_Escaper();
}
return $this;
}
public function html($string)
{
return $this->escaper->escapeHtml($string);
}
/* ... */
Then i could just use $this->escaper()->html('SomeText') (now I see it would be even simpler to just return Escaper in constructor, but works eiter way).
I'm currently working with OctoberCMS and am creating a plugin which has a custom page with a widget which shows a grid
As you can see in this image this grid can be managed and can be saved with the "Save changes" button. This will send a POST request to the server, but I have problems with "listening" for this POST request. Since the documentation of octoberCMS is not very good I'm trying to do this the way it would be done in Laravel. But even that doesn't work like it should.
Unresolvable dependency resolving [Parameter #0 [ <required> $app ]] in class Illuminate\Support\ServiceProvider
ftsf/grid/routes.php
<?php
Route::post('/backend/ftsf/grid', 'Ftsf\Grid\Widgets\GridManager#saveGrid');
ftsf/grid/widgets/GridManager.php
<?php namespace Ftsf\Grid\Widgets;
use App;
use Backend\Classes\WidgetBase;
use Cms\Classes\Content;
use Cms\Classes\Controller;
use Cms\Classes\Theme;
use Cms\Twig\Extension;
use Ftsf\Grid\Models\PatternOrder;
use Illuminate\Http\Request;
use System\Twig\Engine as TwigEngine;
class GridManager extends WidgetBase {
protected $defaultAlias = 'ftsf_grid_manager';
public function init() {
}
public function render() {
$env = App::make('twig.environment');
$env->addExtension(new Extension(new Controller(Theme::getActiveTheme())));
return (new TwigEngine($env))->get($this->getViewPath('_gridmanager.htm'),
['patterns' => PatternOrder::orderBy('order')->with('pages')->get(),
'contents' => Content::all()]);
}
public function loadAssets() {
$this->addCss('css/gridmanager.css', 'Ftsf.Grid');
$this->addJs('js/gridmanager.js', 'Ftsf.Grid');
}
public function saveGrid(Request $request){
return dd($request);
}
}
If more information is needed just tell me what.
You should use the Octobers native AJAX handlers. The documentation is quite good for that in my opinion.
In that case the handler should look like this:
public function onSaveGrid(){
return dd(post());
}
You can make the request like this:
$.request('onSaveGrid', {
success: function() {
console.log('Finished!');
}
})
Of course you could also use the data-attributes API or call the handler on a DOM element. The documentation covers all these cases.
As I'm beggining with Laravel, this should be a simple one:
How can I define a custom view to be rendered when my route model binding just can't find the given ID?
Here's my route:
Route::get('/empresa/edit/{empresa}', 'EmpresaController#edit');
Here's my controller's method:
public function edit(Empresa $empresa)
{
if ((!isset($empresa)) || ($empresa == null)):
//I get that this won't work...
return 'Empresa não encontrada';
endif;
return view('Empresa.dadosEmpresa')->with('empresa', $empresa)->with('action', URL::route('empresa_update', ['id', $empresa->id]))->with('method', 'PATCH');
}
Here's my "attempt" to use an error handler:
public function render($request, Exception $exception)
{
if ($e instanceof ModelNotFoundException)
{
//this is just giving me a completely blank response page
return 'Empresa não encontrada';
}
return parent::render($request, $exception);
}
How is this really done?
1. The formal way (but would it be really needed to customize in this way?)
First of all, what Laravel does is, if there is not Model Row in DB with the given id, it sends 404 response, automatically.
If a matching model instance is not found in the database, a 404 HTTP response will be automatically generated.
So if you wanna show your customized view, you need to customize error handling.
So in RouteServiceProvider file, make sure it throws custom exception using 3rd param like follwoing:
public function boot()
{
parent::boot();
Route::model('empresa', App\Empresa::class, function () {
throw new NotFoundEmpresaModelException;
});
}
And then do same thing in the render function as you tried before.
2. The casual way - Pretty easy to go
I d rather suggest that you do not use model injection ability, but handle the request yourself.
So take the empresa id value as it is, and then try to find the right data, and if not found, then make your custom logic. That should be pretty easy to go.
public function edit(Request $request, $empresa)
{
$empresaObj = Empresa::find($empresa);
if (!$empresa) {
return 'Empresa não encontrada';
}
return view('Empresa.dadosEmpresa')->with('empresa', $empresa)->with('action', URL::route('empresa_update', ['id', $empresa->id]))->with('method', 'PATCH');
}
I'm using PHP 5.3's class_alias to help process my Symfony 1.4 (Doctrine) forms. I use a single action to process multiple form pages but using a switch statement to choose a Form Class to use.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
class_alias('MyFormPage1Form', 'FormAlias');
break;
...
}
$this->form = new FormAlias($obj);
}
This works brilliantly when browsing the website, but fails my functional tests, because when a page is loaded more than once, like so:
$browser->info('1 - Edit Form Page 1')->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end()->
get('/myforms/edit')->
with('response')->begin()->
isStatusCode(200)->
end();
I get a 500 response to the second request, with the following error:
last request threw an uncaught exception RuntimeException: PHP sent a warning error at /.../apps/frontend/modules/.../actions/actions.class.php line 225 (Cannot redeclare class FormAlias)
This makes it very hard to test form submissions (which typically post back to themselves).
Presumably this is because Symfony's tester hasn't cleared the throughput in the same way.
Is there a way to 'unalias' or otherwise allow this sort of redeclaration?
As an alternate solution you can assign the name of the class to instantiate to a variable and new that:
public function executeEdit(sfWebRequest $request) {
$formType;
switch($request->getParameter('page')) {
case 'page-1':
$formType = 'MyFormPage1Form';
break;
...
}
$this->form = new $formType();
}
This doesn't use class_alias but keeps the instantiation in a single spot.
I do not know for sure if it is possible, but judging from the Manual, I'd say no. Once the class is aliased, there is no way to reset it or redeclare it with a different name. But then again, why do use the alias at all?
From your code I assume you are doing the aliasing in each additional case block. But if so, you can just as well simply instantiate the form in those blocks, e.g.
public function executeEdit(sfWebRequest $request) {
switch($request->getParameter('page')) {
case 'page-1':
$form = new MyFormPage1Form($obj);
break;
...
}
$this->form = $form;
}
You are hardcoding the class names into the switch/case block anyway when using class_alias. There is no advantage in using it. If you wanted to do it dynamically, you could create an array mapping from 'page' to 'className' and then simply lookup the appropriate class.
public function executeEdit(sfWebRequest $request) {
$mapping = array(
'page-1' => 'MyFormPage1Form',
// more mappings
);
$form = NULL;
$id = $request->getParameter('page');
if(array_key_exists($id, $mapping)) {
$className = $mapping[$id];
$form = new $className($obj);
}
$this->form = $form;
}
This way, you could also put the entire mapping in a config file. Or you could create FormFactory.
public function executeEdit(sfWebRequest $request) {
$this->form = FormFactory::create($request->getParameter('page'), $obj);
}
If you are using the Symfony Components DI Container, you could also get rid of the hard coded factory dependency and just use the service container to get the form. That would be the cleanest approach IMO. Basically, using class_alias just feels inappropriate here to me.
function class_alias_once($class, $alias) {
if (!class_exists($alias)) {
class_alias($class, $alias);
}
}
This doesn't solve the problem itself, but by using this function it is ensured that you don't get the error. Maybe this will suffice for your purpose.