Laravel - Model const in a blade? - php

Is it bad practice to use Model:CONST in a blade view or what is other approach?
For example in the model, I have like this:
class ServiceType extends Eloquent
{
protected $table = 'service_type';
const TYPE_LANDLINE = 1;
const TYPE_SIP = 4;
}
and in the controller:
if ($packageDb->service_type_id == ServiceType::TYPE_SIP) {
$summary[service_type] = $packageDb->service_type_id;
}
if ($packageDb->service_type_id == ServiceType::TYPE_LANDLINE) {
$summary[service_type] = $packageDb->service_type_id;
}
return View::make("order.order-billing")->with('summary', $summary);
In blade I could do something like this (not tested):
#if ($summary['service_type'] == ServiceType::TYPE_SIP)
..
#endif

tl;dr
It is up to you.
Alternate solution
You could create a variable for your constant and pass it to the view:
$roleNames = User::ROLE_NAMES;
return View::make("membership.edit", compact('roleNames'));
Then in the view:
<td>#lang("app.{$roleNames[$member->pivot->role_id]}")</td>
Advantages
Shorter: You don't have to write the fully qualified name of the model. This is pretty handy if you have a deep model structure or long model names.
You can "rename" the constant: Sometimes constant names are general. By passing a variable you can give a more descriptive name for the variable that can tell how exactly you use those constants in that given context.
It is clearer what the view works with: The controller's job is to provide the needed resources (for the view) to generate a response to the request. If you pass the constants to the view in the controller, you can see what resources the view works with.
Disadvantages
Of course there can be down sides too, when using this method is cumbersome. If you have many constants (for example for each user role) then probably you don't want to pass all of them to the view, because you will end up with something like this:
$noRole = User::NO_ROLE;
$memberRole = User::MEMBER_ROLE;
$adminRole = User::ADMIN_ROLE;
$moderatorRole = User::MODERATOR_ROLE;
$reviewerRole = User::REVIEWER_ROLE;
$publisherRole = User::PUBLISHER_ROLE;
return View::make("membership.edit", compact(
'noRole',
'memberRole',
'adminRole',
'moderatorRole',
'reviewerRole',
'publisherRole'
));
The main problems with this:
Lot of unnecessary code for a trivial functionality.
Hard to maintain, especially if your view uses only a few of them.
Violates DRY, especially if you need to do this in almost all function that returns a view.
Of course you could refactor this, create helper functions, but why would you deal with all of this hassle when (in this case) using the constants directly in the view is straightforward and easy to understand:
#if ($user->role === App\User::ADMIN_ROLE)
The rule of thumb is to use the solution that is easier to read and understand. Except if you have a style guide, then you should follow that.

In your blade file you can inject the model
#inject('ServiceTypeModel', 'App\Models\ServiceType')
and then use constants like this
{{ ServiceTypeModel::SIP }}
or
#if ($x < ServiceTypeModel::SIP)...

Related

What is more effective: filling variables with twig filters or passing them through the controller?

I've recently been using my own custom made twig filters. I've also been looking at ways to improve my performance.
I struggle in being able to distinguish when I should build a function in a Service, that then gets used by a Controller or if i should make it a twig filter instead. I am wondering which one executes faster or if there is any difference at all? To clarify my question I want to show how I fill the variable photos with both.
The twig template will look like this. It will just make image elements with the array of photos.
/content/photos.html.twig
{% for photo in photos %}
<img class="lozad card-img read-more-card-item-img" data-src="{{ photo }}">
{% endfor %}
Method 1: Pass the photos variable in a controller.
The controller would probably use a service called PhotoService and call the getReadMore function, while passing this to the twig template:
function controllerAction(){
$response = $this->render(
'/content/photos.html.twig',
array(
'photos' => $photoService->getReadMorePhotos($posts),
'loadMoreUrl' => $url,
'limit' => $limit
)
}
Method 2: Use a custom twig filter instead:
public function getFilters()
{
return array (new \Twig\TwigFilter('readMorePhotos', array($this, 'getReadMorePhotos')));
}
//Twig filter
public function getReadMorePhotos($posts)
{
...
$photos = [];
foreach ($posts as $post) {
$image = $this->getAbsoluteThumbsPath($post->getImage());
if ($image != null && $image != "" && strpos($image, '/img/posts/default-post.png') === false) {
$gallery[] = $image;
}
}
...
return $photos;
}
In the twig template the photos variable will be filled like this:
{{set photos = posts|readMorePhotos}}
Is there any difference in both methods?
very opinionated, so this answer essentially ... is an opinion.
the way I see it: the more non-standard functions/filters are used in a template, the worse off you're semantically. On one hand, whoever is editing or create the templates has to know of those functions/filter, which - in my opinion - is not desirable.
Also, as DarkBee already mentioned in the question's comment, filters are for transforming data, not fetching it. a twig function would be more appropriate, but I don't like it either because of the reason above (it also exposes all templates to that function, unless precautions are taken, on the other hand, it is expected to exist in all templates).
On the other hand, the template editor/creator also have to know about which variables may exist in the current context. So ultimately there is some prior knowledge required.
I want to propose a slightly different (and in my arrogant but humble opinion also better) option ...
As far as I see it, it would be better, if you could just use
{% for photo in post.photos %}
{# display of photos #}
{% endfor %}
because they are semantically connected to the post, and also it makes sense to assume they are also structurally connected. Since your use case seems a little different, it would possibly be:
{% for photo in posts.photos %}
which of course has different semantics and really screws with posts being an array, where photos be one element. However, imaginge the collection being an object, that has extra functions (I'll shamelessly extend doctrine's ArrayCollection):
<?php
namespace App\Collection;
use Doctrine\Common\Collections\ArrayCollection;
use App\Service\PhotoService;
class PostCollection extends ArrayCollection {
public $photoService;
public function __construct(array $elements, PhotoService $photoService) {
parent::__construct($elements);
$this->photoService = $photoService;
}
public function getPhotos() {
return $this->photoService->getReadMorePhotos($this->toArray())
}
}
and this is then to be called in your controller:
$response = $this->render(
'/content/photos.html.twig',
array(
'posts' => new PostCollection($posts, $photoService),
'loadMoreUrl' => $url,
'limit' => $limit
)
);
and can then be used as written above.
Why does this work? ArrayCollection implements a few interfaces among which there is ArrayAccess as well as IteratorAggregate (which extends Traversable), which lets you use it in a loop, and it will provide the collection it was given in its constructor (or which is changed in the modification methods), if the need arises, you can always retrieve an array via ArrayCollection::toArray.
Why do this? To get clean code.
Do I do this? No, tbh. But this is partly due to my habit of realizing almost all relations in the database, which in combination with ORM give me some of this for free.

Laravel Blade template how to return null instead of ErrorException when trying to get property of non-object

I'm writing some Laravel Blade templates, and I have models that may have null objects. I would very much like to just try and get the object property and if there is an error, just return null.
So instead of having to write this:
#if ($model->child_object_that_may_be_null)
{{ $model->child_object_that_may_be_null->interesting_property }}
#endif
I could just write this:
{{ $model->child_object_that_may_be_null->interesting_property }}
and if the child object is null, then the result evaluates to null.
I want to only do this with my blade templates, and not everywhere in the application (I'd still like to get the error in the controllers for example).
Is there an easy way to accomplish this? I also really don't want to have to try/catch the exception each time in the blade templates either.
Basically, I'm trying to make the blade templates easier/shorter to write when I don't care that I child is null, if it is null then its non-existent property should just be null also for the rendering portion.
You can use array_get as:
{{ array_get($model, 'child_object_that_may_be_null.interesting_property') }}
The array_get function retrieves a value from a deeply nested array using "dot" notation.
Note - It may only work with Laravel's model objects
You might check for nullable values in your object and initialize them as stdClass.
$properties = get_object_vars($model);
foreach ($properties as $k => $v) {
if ($v === null) $model->$k = new stdClass;
}
I get bitten by this all the time. I started adding displayAttribute accessors on my objects when I know they'll have this kind of data display. Especially useful when it comes to dates or multiple levels of relationship.
So like if I have a Service model that's related to a Contract, and that contract has a start_date I want to display, I'll add a getDisplayContractStartDateAttribute to my Service model and do the check there. That way I can also choose to have a message displayed if there is no contract. Like so:
public function getDisplayContractStartDateAttribute(){
if ($this->contract && $this->contract->starts_at){
return $this->contract->starts_at->format('m/d/Y # g:i a');
} else {
return 'Start date or contract missing';
}
}
So now anywhere in my Blade templates, I can access $service->displayContractStartDate without fear of throwing that horrible error.
If you're worried about extending your models too much with these non-database-related methods, consider using a Trait to contain all of our displayAttribute methods for a given model.
Or, if you're worried about separation of concerns, you could use a decorator and access it like $service->decorator->contractStartDate(), but I personally think that's too convoluted.

What is a quick way to check if a user has a particular role in my Laravel application?

I'm currently building a web application using Laravel 4 PHP Framework, and part of my app is only visible to subscribers and administrators; and obviously I don't want guests to see these sections of my site.
Each user has a role_id which corresponds to a value in an abstract static class in acts as an enum:
abstract class UserRole {
const Unauthenticated = 1;
const Member = 2;
const Subscriber = 3;
const CharterSubscriber = 4;
const Moderator = 5;
const Administrator = 6;
}
Because a lot of my content is dynamic depending on the role a user has; I'm finding I'm checking if a user is of a particular role or greater using the Blade PHP syntax in my Views.
To do this, I'm having to use a rather verbose syntax:
#if (Auth::user() && Auth::user()->role_id == UserRole::Administrator)
...is a good example. The problem here is I first have to check if Auth::user() exists; if it doesn't, the second logical comparison will fail, so I can't even do this:
#if (Auth::user()->role_id == UserRole::Administrator)
...which is cleaner, but still pretty verbose. Ideally, I'd like to do something like:
#if (Auth::isAdmin())
...or...
#if (Auth::isSubscriberOrGreater())
How can I accomplish this? I don't know that much about Laravel's IOC container, so any answers are going to have to be reasonably well explained.
I can propose a solution, though it might be a little bit ugly:
Auth::user() method returns you the User model or NULL, so in the user model you can check if the user is admin with isAdmin method (or you can perform any other check) like this:
public function isAdmin(){
if( $this->role_id == 6){
return True;
}
return False;
}
(really you can call method any other name and perform any other check there).
and in then in blade template you'll have something like this:
#if (#Auth::isAdmin())
To avoid throwing a notice you simply put '#' which is an Error Control Operator in front of the variable.

Make view depend on variables in laravel

It might just be me coming from a more restricted java background, but I'm feeling the connection between views and controllers in Laravel is error prone. For example, consider a controller looking something like this
ReceiptController extends BaseController {
...
public function show() {
$data = array($receipt, $offer);
View::make('registration', $data);
}
}
and a view which depends on a receipt object and an offer string
...
<div id="receipt">
<h1>Receipt</h1>
{{$receipt->items}}
#if ($receipt->price > 10000)
<p>{{$offer}}</p>
#endif;
</div>
...
What if a controller somewhere don't include the offer string? Worst case scenario it might go unnoticed until someone purchase something with a price over 10000. How would I go about throwing an error if the controller doesn't pass all variables required to make the view? Bonus if it also makes an IDE such as PHPStorm recognize the variables.
You could use a view composer, and ensure that whenever your receipt view is called, a offer is always included. That way you know the object is always passed.
View::composer('receipt', function($view)
{
$view->with('offer', Offer::get(1));
});
Or you could just handle it directly in your view
<div id="receipt">
<h1>Receipt</h1>
{{$receipt->items}}
#if ($receipt->price > 10000)
<p>{{$offer or 'Sorry - no special available'}}</p>
#endif;
</div>
Lastly - the 'best' option is to always test your code, and check your view is always called with a $offer variable
$this->assertViewHas('offer');
That kind of logic if intented to work in the Controller, if the price is higher than a value, set a $var = false and check if in the view.
if($receipt->price > 10000) {
$offer = $offer;
} else {
$offer = false;
}
And about the compatibility between PHPStorm and Blade Template Engine, PHPStorm 7.1 doesn't support it, but you help to build a better world voting for it for the next version: http://youtrack.jetbrains.com/issue/WI-14172

Simple way to replace a php switch statement in mustache.php?

I'm working on figuring out how to do common things with Mustache.php.
At this point, I'm trying to replace a native php switch statement in the template.
Given a "status" flag, e.g. 'A' for Active,
and a set of display options like A => Active, I => Inactive, P=>Pending, D=>Deleted,
how would I make a nice display string in Mustache.php by modifying the data in the template?
Example data for a table:
$users = array(
array('username'=>'william', 'status'=>'A', 'date_created'=>'7-01-2012'),
array('username'=>'john', 'status'=>'P', 'date_created'=>'5-17-2012')
);
The whole point of Mustache, is having your template clean from all logic. Mustache templates should be logic-less. This is quite a different and confusing methodology for newcomers.
To solve your problem, you will need to re-process the $users array to contain everything your Mustache template will need, before-hand. e.g. if the status field switch statement was intended for displaying a human-readable status, then your View class should look something like:
class View_User {
public $_users;
public function users()
{
$users = array();
foreach ($this->_users as $user)
{
$user->status_label = // Use your 'switch' statement here
$users[] = $user;
}
return $users;
}
}
Then all your Mustache template needs to do, is output {{status_label}} like so:
<ul>
{{#users}}
<li>Status: {{status_label}}</li>
{{/users}}
</ul>
Contain your logic in View classes, and leave your Mustache templates logic-less. UI code that is separated this way makes it so much easier to maintain and re-factor later on.

Categories