Im essentially trying to see if there is a more efficient or proper way of accessing route parameters in the views of a nested resource. The code below demonstrates what I'm doing, catching all parameters from the route: /schools/1/classes/2/teachers/4/assignments
into the controller index method, and then making a view and passing it all of those parameters so that within the view I can make forms and links that use the same route format & parameters. Is there a better way? Laravel Paste
//
// app/routes.php
//------------------------------------------------------
Route::resource('schools.classes.teachers.assignments', 'AssignmentsController');
//
// app/controllers/AssignmentsController.php
//-------------------------------------------------------
public function index($school_id,$class_id,$teacher_id)
{
$routes = array($school_id,$class_id,$teacher_id);
$assignments = $this->assignment->all();
return View::make('assignments.index', compact('assignments'))
->with('routes', $routes);
}
//
// app/views/assignments/index.blade.php
// ------------------------------------------------------------
<p>{{ link_to_route('schools.classes.teachers.assignments.index', 'All Assignments', array($routes[0],$routes[1],$routes[2])) }}</p>
//
// app/views/assignments/edit.blade.php
// -------------------------------------------------------------
{{ Form::model($assignment, array('method' => 'PATCH', 'route' => 'schools.classes.teachers.assignments.update', $routes[0],$routes[1],$routes[2],$route[3]))) }}
-
You always need to pass the parameters and this is simple but I think it would be better if you use an associative array like this instead:
$routes = compact('school_id', 'class_id', 'teacher_id');
So it'll become:
$routes = array(
'school_id' => $school_id,
'class_id' => $class_id,
'teacher_id' => $teacher_id
);
So, you can use:
{{ Form::model($assignment, array('method' => 'PATCH', 'route' => 'schools.classes.teachers.assignments.update', $routes['school_id'], ['class_id'], ['teacher_id']))) }}
Looks more readable and easy to understand.
Related
I have a form class with several ChoiceType fields that contain an array of options with a key:value pair. When the form is submitted the value is saved. However when I'm rendering the object I would like to show the Key value instead.
Example: 'Monthly' => '1month'. 1month is stored, prefer output to be Monthly.
I'm trying to avoid conditionals to check the value and changing the output to the key value.
I wasn't able to find any documentation about best practices for this sort of thing. I'm thinking about creating a service that stores all the choice options arrays and build a twig filter for changing the rendered output based on the array from the service.
Am I on the right track or is there an easier way?
I tried the service solution and got it working. I'm not sure if it is the most elegant or efficient way but it did the job. The form was a form class type and I injected a service that contained the choice arrays.
I created a choices.php class file inside my Form folder next to the formType file. It acts as a service where it returns the choices to the formType and a custom twig extension filter I created. The formType I had to set up as a service in order to inject the choices service.
/*choices.php*/
public function getChoices($choice)
{
$choices = array('paymentFrequency' => array('Monthly' => '1month',
'Bi-weekly' => '2weeks'),
'compounding' => array('Monthly' => 'monthly',
'Daily' => 'daily')
);
return $choices[$choice];
}
/*formType.php*/
->add('paymentFrequency', ChoiceType::class, array(
'label' => 'Payment Frequency:',
'choices' => $this->choicesService->getChoices('paymentFrequency'),
))
->add('compounding', ChoiceType::class, array(
'label' => 'Compounding:',
'choices' => $this->choicesService->getChoices('compounding'),
))
I then created a custom twig filter function where the choices service is injected into it.
/*twigExtension.php*/
public function renderChoicesFilter($value, $type)
{
$choices = $this->choicesService->getChoices($type);
return array_search($value, $choices);
}
/*twig template*/
{{ object.paymentFrequency|renderChoices('paymentFrequency') }}
You can create enumerator class and use it in your template, like so:
class MyChoicesEnum {
private static $choices = array(
'Monthly' => '1month',
'Quarterly' => '4month',
// etc...
);
public static function choices() {
return self::$choices;
}
}
Then you pass the class method result to template, in the returned array:
...
'form' => $form->createView()
'my_choices' => MyChoicesEnum::choices()
And in twig:
{{ my_choices.key }}
I want to pass some user data to a view so it can be displayed in the profile page. There is quite a lot of it but I don't want to just pass everything, because there are some things the view shouldn't have access to. So my code looks like this:
return view('profile', [
'username' => Auth::user()->username,
'email' => Auth::user()->email,
'firstname' => Auth::user()->firstname,
'country' => Auth::user()->country,
'city' => Auth::user()->city->name,
'sex' => Auth::user()->sex,
'orientation' => Auth::user()->orientation,
'age' => Auth::user()->age,
'children' => Auth::user()->children,
'drinking' => Auth::user()->drinking,
'smoking' => Auth::user()->smoking,
'living' => Auth::user()->living,
'about' => Auth::user()->about,
]);
My question is: Can this be written shorter/simpler?
Thanks!
EDIT:
I don't want this: {{ Auth::user()->firstname }} because there is a logic in a view, which is bad - I think, there should be just plain variables to be displayed, in view, not anything else.
So I'm looking for something like:
return view('profile', Auth::user()->only(['firstname', 'email', ...]));
You could create by yourself a method named like getPublicData and then return all those properties you need.
...
public function getPublicData() {
return [
'property_name' => $this->property_name
];
}
...
... and then use it in your controller/views. Maybe it's not an optimal solution, but you can isolate this thing in the model and avoid too much code in the controller.
Another advanced approach could be the override of the __get method. However, I am not the first in this case.
Hope it helps!
You don't need to pass these variable to the view, you can directly access them in the view using blade
<h1>{{ Auth::user()->username }}</h1>
I have to support url friendly structure for a project.
There is multiple tables with a slug column, in cakephp how can I route the slug to a controller in the most efficient way.
At first I was checking if slug exist in a table, if slug exist use the route:
$c = TableRegistry::get('cateogories');
$result= $c->find()->select(['id'])->where(['url'=>$slug])->toArray();
if(count($result) > 0) {
$routes->connect(
'/:slug',
['controller' => 'Categories', 'action' => 'index', 'id' => $result[0]['id']]
);
}
The problem being that I have multiple checks like the one above and each one is being ran even if a route prior matches (doesn't need to be ran so extra querys are being called).
So how can I add a conditional statement of some sort so that it only checks if the route matches if none of the prior ones have.
I'd suggest to go for a custom route class that handles this. While you could query the data in your routes files, this is
not overly test friendly
not very DRY
not safe for reverse routing
The latter point means that when not connecting all routes, trying to generate a URL from a route array for a non-connected route might trigger an exception, or match the wrong route.
With a custom route class you could simply pass the model in the options when connecting the routes, and in the route class after parsing the URL, query that model for the given slug, and return false or the parsed data accordingly.It's really simple, just have a look at what the existing route classes do.
Here's a very basic example which should be pretty self-explantory.
src/Routing/Route/SlugRoute.php
namespace App\Routing\Route;
use Cake\Routing\Route\Route;
use Cake\ORM\Locator\LocatorAwareTrait;
class SlugRoute extends Route
{
use LocatorAwareTrait;
public function parse($url)
{
$params = parent::parse($url);
if (!$params ||
!isset($this->options['model'])
) {
return false;
}
$count = $this
->tableLocator()
->get($this->options['model'])
->find()
->where([
'slug' => $params['slug']
])
->count();
if ($count !== 1) {
return false;
}
return $params;
}
}
This example assumes that in the controller, you'd use the slug to retrieve the record. If you'd wanted to have the ID passed, then instead of using count(), you could fetch the ID and pass it along in the parsed data, like:
$params['pass'][] = $id;
It would then end up being passed as the second argument of the controller action.
routes.php
$routes->connect(
'/:slug',
['controller' => 'Articles', 'action' => 'view'],
[
'pass' => ['slug'],
'routeClass' => 'SlugRoute',
'model' => 'Articles'
]
);
$routes->connect(
'/:slug',
['controller' => 'Categories', 'action' => 'view'],
[
'pass' => ['slug'],
'routeClass' => 'SlugRoute',
'model' => 'Categories'
]
);
// ...
This would first check the Articles model, then the Categories model, etc., and stop once one of the routes finds a record for the given slug.
See also
Cookbook > Routing > Custom Route Classes
API > \Cake\Routing\Route::parse()
Source > \Cake\Routing\Route
My articles URL contains both ID and slug, in this format: /articles/ID/slug. Only ID is used for record lookup, the slug is just there for SEO and is not stored in the database.
At the moment I am doing this in my view (inside a foreach loop):
$url = URL::route('articles.show', array('id' => $article->id, 'slug' => Str::slug($article->title)));
To generate the complete URL, e.g: articles/1/accusamus-quos-et-facilis-quia, but; I do not want to do this in the view. I want to do it in the controller and pass it to the view, but I can't figure out how.
Edit: I am passing an array of multiple articles from the controller to the view, and all of them have unique URLs depending on their respective ID and slug.
The best way of doing something like this is to use a view presenter:
{{ $article->present()->url() }}
And in your presenter:
public function url()
{
URL::route('articles.show', array('id' => $this->id, 'slug' => Str::slug($this->title)));
}
But you can create an acessor in your model:
public function getUrlAttribute()
{
URL::route('articles.show', array('id' => $this->id, 'slug' => Str::slug($this->title)));
}
And use as:
{{ $article->url }}
I have one route that looks like this:
Router::connect('/Album/:slug/:id',array('controller' => 'albums', 'action' => 'photo'),array('pass' => array('slug','id'),'id' => '[0-9]+'));
and another like this:
Router::connect('/Album/:slug/*',array('controller' => 'albums','action' => 'contents'),array('pass' => array('slug')));
for what doesn't match the first. In the 'contents' action of the 'albums' controller, I take care of pagination myself - meaning I retrieve the named parameter 'page'.
A URL for the second route would look like this:
http://somesite.com/Album/foo-bar/page:2
The Above URL indeed works, but when I try to use the HTML Helper (url,link) to output a url like this, it appends the controller and action to the beginning, like this:
http://somesite.com/albums/contents/Album/foo-bar/page:2
Which i don't like.
The code that uses the HtmlHelper is as such:
$html->url(array('/Album/' . $album['Album']['slug'] . '/page:' . $next))
See below url it is very help full to you
http://book.cakephp.org/2.0/en/development/routing.html
Or read it
Passing parameters to action
When connecting routes using Route elements you may want to have routed elements be passed arguments instead. By using the 3rd argument of Router::connect() you can define which route elements should also be made available as passed arguments:
<?php
// SomeController.php
public function view($articleId = null, $slug = null) {
// some code here...
}
// routes.php
Router::connect(
'/blog/:id-:slug', // E.g. /blog/3-CakePHP_Rocks
array('controller' => 'blog', 'action' => 'view'),
array(
// order matters since this will simply map ":id" to $articleId in your action
'pass' => array('id', 'slug'),
'id' => '[0-9]+'
)
);
And now, thanks to the reverse routing capabilities, you can pass in the url array like below and Cake will know how to form the URL as defined in the routes:
// view.ctp
// this will return a link to /blog/3-CakePHP_Rocks
<?php
echo $this->Html->link('CakePHP Rocks', array(
'controller' => 'blog',
'action' => 'view',
'id' => 3,
'slug' => 'CakePHP_Rocks'
));