How to implement a generic view presenter in Laravel - laravel

I am currently using laracasts/Presenter to handle logic related to my views, this implementation is connected to a model. But I also have some generic view logic that I would like to implement, what is the best practice for creating this?
I have tried two methods, but neither feels right:
Custom class ViewHelper with static functions, called with ViewHelper::Method.
Blade include files, called with #include('includes.navicon')
Is there a better way of doing this? Or is one (or both) of the above acceptable?
Edit: We're talking simple stuff here like insert page title, run text though Markdown parser. This is an example of a function that I use on all pages, it just creates and returns a page header.
public static function PageTitle($level, $title, $small = null)
{
if ($small != null) $title = $title . " <small>" . $small . "</small>";
$html = "<div class=\"page-header\" style=\"margin-top: 0px\"><h%1\$d>%2\$s</h%1\$d></div>";
return sprintf($html, $level, $title);
}
The view presenter that I have installed makes use of the model, so to get a formatted URL for example I would use the command: {{ $article->present()->url }}, while this generic view logic should be available in all views without having to add it to all the models.

I ended up creating a base class that all view presenters extend, and move everything generic there. Functions that is required on views that do not have a model I simply added as static.
Model, I added $presenterInfo to pass information to load the correct view(s) and use as a title prefix. The rest is required by the view presenter.
use Laracasts\Presenter\PresentableTrait;
...
use PresentableTrait;
protected $presenter = 'ArticlePresenter';
public $presenterInfo = ['view' =>'articles', 'category' => 'Article'];
Baseclass, everything generic goes here. So basically everything that might be useful on multiple classes and their views.
use Laracasts\Presenter\Presenter;
class BasePresenter extends Presenter {
public static function pageHeader($level, $title, $small = null)
{
if ($small != null) $title .= " <small>" . $small . "</small>";
$html = "<div class=\"page-header\" style=\"margin-top: 0px\"><h%1\$d>$this->presenterInfo['category']: %2\$s</h%1\$d></div>";
return sprintf($html, $level, $title);
}
public function url()
{
return URL::route($this->presenterInfo['view'] . '.show', array('id' => $this->id, 'slug' => Str::slug($this->title)));
}
}
Viewclass, functions that will only be available for the selected class; in this case Article.
class ArticlePresenter extends BasePresenter {
// Example function only needed by the article class.
public function stump()
{
return Str::limit($this->content, 500);
}
}
Examples, loading view presenter data:
// Show page header level 2
{{ BasePresenter::pageHeader(2, 'Articles') }}
// Enumerate the articles and show title, stump and read more link
#foreach($articles as $article)
<article>
<h3>{{ HTML::link($article->present()->url, $article->title) }}</h3>
<div class="body">
<p>{{ $article->present()->stump }}</p>
<p>Read more...
</div>
</article>
#endforeach

Related

New to Laravel - How to pass model data to a blade view?

Ok, I am totally re-writing this question, now that I am a bit more familiar with larval.
Here is my situation: I have a guitar lessons site based on larval 5.2.36, where each lesson belongs to a category, and within a lesson are several exercises. An exercise table does not have a category id as it is linked to a lesson which has a category.
Goal What I am trying to figure out is how to pass the category of the currently displayed lesson or exercise to a menu sidebar view that displays the categories, so that the category of the lesson or exercise is highlighted. For this, I need to understand how to do such a task in laravel.
From what I gathered, this is often done via controllers. However, there is no menu controller, but rather a menu composer. It contains a function
class MenuComposer
{
public function compose(View $view)
{
$minutes = 6 * 60;
$value = Cache::remember('menu-categories', $minutes, function() {
return \App\Category::with('parent')->with('children')->get();
});
$view->with('categories', $value);
}
}
Then in the menu blade file we have
#foreach ($categories as $category)
<?php $category = $category->present(); ?>
#if ($category->parent == null)
<li>{{ $category->title }}</li>
#foreach ($category->children as $child)
<?php $child = $child->present() ?>
<li class="level1">{{ $child->title }}</li>
<?php
/*
#foreach ($child->children as $grandChild)
<?php $grandChild = $grandChild->present() ?>
<li class="level2">{{ $grandChild->title }}</li>
#endforeach
*/
?>
#endforeach
#endif
#endforeach
So this is clear. I see that I can use the menu composer to pass additional data with a $view->with() call.
The question is how do I get the current category? For exercises and lessons, the routes don't have category data. They are of form
lesson/lessonid/lessontitle
or
exercise/exid/extitle
So I know I could do some sort of query of the model. But seems that wouldn't make sense, since I know there are other places in the process flow where the current cat is being passed. For instance, on an exercise page, the view is retrieving category as
$exercise->lesson->category->title
It is being passed this in exercise controller as
public function index($id, $name = null)
{
//$this->hit($id);
$exercise = $this->apiController->get($id);
$authorized = $this->isUserAuthorized();
return view('exercise/index', [
'exercise' => $exercise->present(),
'authorized' => $authorized,
]);
}
Similarly, a lesson controller passes $lesson object to lesson view as
public function index($id, $name = null)
{
//$this->hit($id);
$lesson = $this->apiController->get($id);
$subscribed = $this->request->user() && $this->request->user()->subscribed('premium');
return view('lesson/index', [
'lesson' => $lesson->present(),
'subscribed' => $subscribed,
]);
}
Based on above, seems I could modify the return statements in the lesson and exercise controller to pass the category to the menu view, but I don't see in the documentation how to do that, and I suspect the menu view is rendered before the lesson and exercise controller are called...
Also read about using service providers. middleware, etc, here: How to pass data to all views in Laravel 5?
But all these approaches seem overkill. I don't need every view to have the data. Seems to me, I need to do this somehow in the menu composer. But I don't know what method to use from the menu composer to retrieve the current lesson or exercise category. In the menu composer after debugging in phpstorm I see that the $view object for a lesson has $view->$data->$lesson->$entity.
So what I did was edited the menu composer to pass category to view:
$d=$view->getdata();
$s=array_key_exists ('lesson' , $d );
if ($s ==1) $attr = collect($d)->get('lesson');
$cat=$attr->cat();
This works since in the LessonPresenter I added function
public function cat()
{
$cat = $this->entity->category['attributes']['title'];
return $cat;
}
This works, but I feel like it is a hack. And I will have to do this for the Exercise Presenter as well. Being new to larval I suspect there has to be a more elegant way to do this. So can someone please explain how this should be done?
thanks,
Brian
You can use Facades of Laravel directly in blade templates.
Just use {! !} syntax to try and echo it. e.g: {!! Route::current() !!}
There are also similar functions of Route facade you can use.
Then, you can check your category with #if() ... #endif blocks and add something like class name within it.
Note: Don't put lots of logic in your blade files. Do it in your controller file (even in your other service classes) and pass simplest variables (e.g $isCurrentCategory) as an array to your template files using View::make() function's 2nd parameter.
Maybe this can help you
<a href="#" class="{{ (\Request::route()->getName() == 'routename') ? 'active' : '' }}">
You can also get the route prefix for example, you can check this out here:
Laravel API Docs Routing

Laravel eager model loading with custom attribute

Is there any possible way to get a custom attribute value through eager load
For instance, given this custom attribute on a model:
class User extends Model {
protected $appends = ['is_member'];
public function getIsMemberAttribute() {
return 'yes';
}
}
and related model
class Awards extends Model {
public function owner(){
return $this->belongsTo('App\User');
}
}
I would love to be able to get is_member attribute in collection using request below:
$users=Awards::orderBy('created_at')->with('owner')->get();
According to Laravel's documentations, $appends is used for appending in array and JSON only...
Once the attribute has been added to the appends list, it will be included in both the model's array and JSON forms.
So, when you do something like dd($user). You will not be able to see the is_member field, but when you do something like $user->toArray() or $user->toJson() you will.
Basically, for places wherever is_member field is always present. All you need to do to access it is (say in Page View/Blade etc)
public function show($id) {
$user = User::get($id);
return view('users.show', ['user' => $user->toArray()]);
}
And then do,
Is Member? : {{ $user['is_member'] }}
// Or if you don't like blade you can do this
// Is Member? : <?php echo $user['is_member'] ?>
But as written in the comments by #Amit, there is no use case of this until you are using it for the sole purpose of APIs. For blade etc, you should prefer doing this
Is Member? : {{ $user->is_member ? 'Yes' : 'No' }}
// Again if you don't like blade you can do this
// Is Member? : <?php echo $user['is_member'] ? 'Yes' : 'No'; ?>
Hope this clears your doubts :)

Laravel passing data to page

I am trying to pass data to Controller of Laravel. here is how I do that :
in PagesController::
class PagesController extends Controller
{
public function contact(){
$data="some random ";
return view('contact',compact("data"));
}
}
now in contact.blade.php :
contact pages {{ $data }}
and it s hows
Whoops, looks like something went wrong.
What may be a problam?
try this
public function contact()
{
$data = 'some random';
return View('contact')->with('data' , $data );
}
make sure that the view works fine (if its inside a folder use return View('foldername.contact')
make sure that your view name is contact.blade.php (check uppercase letters)
and inside your view
this is my {{ $data }}

how to check if a model is tagged by a specific tag

I have model Entity and related model Tag. The later serves as tags, so the relation is serviced by a pivot table
I guess it is quite easy, but I am lost.
public function tags()
{
return $this->belongsToMany('App\Models\Tag', 'entity_tags', 'entity_id', 'tag_id');
}
Now, in my view I can list all tags:
They are defined
{!!
join(', ',
array_map(function($o) {
return link_to_route('entities.profile',
$o->name,
[$o->id],
['class' => 'ui blue tag button']
);},
$object->tags->all())
) !!}
My question:
how in BLADE I can check if the Entity object has a specific capacity?
in my controller SHOW method I get one single Entity:
$object = Entity::find(34);
and then i wish to do sth if the entity is tagged by a certain tag
#if($object->capacities .... has tag= 3
// do things here
#endif
Thx
You can check if an Entity has a certain tag like this:
#if($entity->tags()->where('id', 3)->exists()) //.... has tag= 3
// do things here
#endif
You could add a public method to your Entity class wich would let you check for an existing tag on this entity :
<?php
public function hasTag($tagToMatch)
{
foreach ($this->tags as $tag)
{
if ($tag->id == $tagToMatch)
return (true);
}
return (false);
}
This would allow you to use the following code in your views :
#if ($entity->hasTag(3))
Do something
#endif

Laravel 4 specific view not loading

I am new to laravel and following a tutorial for a basic app. So far the app has a default view layouts/default.blade.php, a partial _partials/errors.blade.php and three other views questions/index.blade.php, users/new.blade.php and users/login.blade.php
The routes are defined like so
// home get route
Route::get('/', array('as'=>'home', 'uses'=>'QuestionsController#get_index'));
//user register get route
Route::get('register', array('as'=>'register', 'uses'=>'usersController#get_new'));
// user login get route
Route::get('login', array('as'=>'login', 'uses'=>'usersController#get_login'));
//user register post route
Route::post('register', array('before'=>'csrf', 'uses'=>'usersController#post_create'));
// user login post route
Route::post('login', array('before'=>'csrf', 'uses'=>'usersController#post_login'));
questions/index.blade.php and users/new.blade.php load fine and within default.blade.php
when I call /login a blank page is loaded not even with default.blade.php. I am guessing that there is a problem in my blade syntax in login.blade.php given the fact that the default.blade.php works on the other routes and as far as I can see everything else is the same but if that was teh case wouldnt the default.blade.php route at least load?
the controller method this route is calling is as follows
<?php
Class UsersController extends BaseController {
public $restful = 'true';
protected $layout = 'layouts.default';
public function get_login()
{
return View::make('users.login')
->with('title', 'Make It Snappy Q&A - Login');
}
public function post_login()
{
$user = array(
'username'=>Input::get('username'),
'password'=>Input::get('password')
);
if (Auth::attempt($user)) {
return Redirect::Route('home')->with('message', 'You are logged in!');
} else {
return Redirect::Route('login')
->with('message', 'Your username/password combination was incorrect')
->withInput();
}
}
}
?>
finally login.blade.php
#section('content')
<h1>Login</h1>
#include('_partials.errors')
{{ Form::open(array('route' => 'register', 'method' => 'POST')) }}
{{ Form::token() }}
<p>
{{ Form::label('username', 'Username') }}
{{ Form::text('username', Input::old('username')) }}
</p>
<p>
{{ Form::label('password', 'Password') }}
{{ Form::text('password') }}
</p>
<p>
{{ Form::submit('Login') }}
</p>
{{ Form::close()}}
#stop
You could also define the layout template directly from the Controller , this approach provides more flexibility , as the same View can be used with multiple layout templates .
<?php namespace App\Controllers ;
use View , BaseController ;
class RegisterController extends BaseController {
protected $layout = 'layouts.master';
public function getIndex()
{
// Do your stuff here
// --------- -------
// Now call the view
$this->layout->content = View::make('registration-form');
}
}
My example uses Namespaced Controller but the same concepts are applicable on non-Namespaced Controllers .
Notice : Our RegisterController extends Laravel's default BaseController , which makes a bit of preparation for us , see code below :
<?php
class BaseController extends Controller {
/**
* Setup the layout used by the controller.
*
* #return void
*/
protected function setupLayout()
{
if ( ! is_null($this->layout))
{
$this->layout = View::make($this->layout);
}
}
}
If a custom "Basecontroller" is defined , make sure that it also implements the "preparation" code .
I don't know what concepts are new to you , so let me make a couple arbitrary assumptions . If "namespace" and "Basecontroller" are << strange words >> , let me try to demystify these words .
Namespace : PHP's documentation is pretty well documented on this subject . My oversimplified explanation is as follows : Two skilled developers (JohnD and Irish1) decide to build their own PHP Logging Library and release the code as open source to the community .Most likely they will name their library "Log"
Now another developer would like to implement both libraries into his/her project (because JohnD's code uses MongoDB as storage medium while Irish1's code uses Redis ) . How would PHP's interpreter distinguishes the two code-bases from each other ? Simply prepend each library with a vendor name (JhonD/Log and Irish1/Log ) .
Basecontroller : Most likely your Controllers will share common functionality (a database connection , common before/after filters , a common template for a View ...... ) . It is a good practice not to define this "common functionality" into each Controller separately, but define a "Parent" Controller , from which all other Controllers will inherit its functionality . So later on , if you decide to make changes on the code , only one place should be edited .
My previous example uses " class RegisterController extends BaseController " , that BaseController is just checking if our (or any other) Child-controller has defined a property with the name of " $layout " , and if so , the View that it will instantiate will be encapsulated into that specified layout . See Laravel's flexibility , a group of Controllers share common functionality (by extending Basecontroller) but also are free to choose their own layout (if they desire to do so ) .
I have found my error
I did not have #extends('layouts.default') at the beginning of the login.blade.php template

Resources