Laravel: Passing custom URLs with both ID and slug to the view - php

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 }}

Related

Store slug and path based on post ID which is created before in Laravel

I have a quite complicated form, which has a lot of fields - everything works fine, but I also need to store a slug and path to thumbnail. The problem is, that when I am using store method and create a post, I need to dynamically create that slug and path based on the post ID (which is not created yet and therefore I don't have an ID).
This is the chunk of the code I use to store the main post data:
Post::forceCreate([
'title' => $title,
'slug' => create_url_slug($title, $id),
'thumbnail' => thumbPath($id),
'description' => request('description'),
'password' => bcrypt(request('password')),
'user_id' => get_user_id()
]);
Here I passed two functions -> create_url_slug and thumbPath. If I put these functions above this chunk of code, the error will be thrown because the ID does not exist yet. On the other hand, if I put these functions under this code, the error will also appear, because those functions would be undefined. Can I somehow use closures or divide this method to two parts?
Thanks anybody in advance.
A way to do this is to create the model and right after assigning the values for example
$post = Post::forceCreate([
'title' => $title,
'description' => request('description'),
'password' => bcrypt(request('password')),
'user_id' => get_user_id()
]);
$post->slug = create_url_slug($title, $post->id);
$post->thumbnail = thumbPath($post->id);
$post->save();
I do not recall if make creates an id for the Model else you could use Post::make and save a call to the database. Worth a try.

Mapping slugs from database in routing

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

Laravel nested resource route parameters in html forms & urls

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.

Codeigniter passing data from model to controller parser

I am about to implement the parser class to my codeigniter project and would like some guidance in passing the data from my model to the parser array. Which is the better more efficient way to do it.
My model gets the data and returns it to the controller. How can I get it into the array in the controller?
Model:
function getSeo($data){
$this->db->select('seo_title, seo_description, seo_keywords');
$this->db->from('content');
$this->db->where($data);
$query = $this->db->get();
$data = array();
foreach ($query->result() as $row) {
$data[] = array(
'seo_title' => $row->seo_title,
'seo_description' => $row->seo_description,
'seo_keywords' => $row->seo_keywords
);
}
return $data;
}
Controller:
$viewdata['pageseo'] = $this->Content_model->getSeo($data);
$viewdata = array(
'seo_title' => 'My seo Title',
'seo_description' => 'My seo description',
'seo_keywords' => 'My seo keywords',
);
What is the best way to get the data from my model into the '$viewdata' array, how is it done????
Since the getSeo function from your model returns an array, the Controller will store this information to your $viewdata array as well.
If you try a print_r($viewdata) you'll see that the structure is as expected. Which is $viewdata['seo_title'] => 'My seo Title'
and so on...
There is function called result_array() which is used to get the result set as array and the below link may help you. This is the core library function.
Plz refer,
http://codeigniter.com/user_guide/database/results.html

Custom values in sfWidgetFormDoctrineChoice

I have a User model with name and surname columns. I'm trying to create a selector in a form using sfWidgetFormDoctrineChoice, which by default only display the name values.
How can I modify the widget to display both the name and the surname? I know you can pass a query option when initializing the widget, but I cannot make it work:
$this->setWidget('user_id', new sfWidgetFormDoctrineChoice(array(
'model' => $this->getRelatedModelName('User'),
'query' => Doctrine_Query::create()->select('u.name, u.surname')->from('User u'),
'add_empty' => false)
));
Thanks!
Use the method option and create a public function of whatever method name that you want in your User model. An example might be:
$this->setWidget('user_id', new sfWidgetFormDoctrineChoice(array(
'model' => $this->getRelatedModelName('User'),
'method' => 'getFullName',
'add_empty' => false)
));
And then in your lib/model/Doctrine/User.class.php file
public function getFullName() {
return "{$this->getName()} {$this->getSurname()}";
}

Categories