I'm new to Symfony2 and am learning by making a concert finder application.
Currently I have a couple of yaml routes:
london_hello_bands:
path: /hello/{band}
defaults: { _controller: LondonHelloBundle:Hello:band }
london_hello_multiple:
path: /hello/{venue}/{band}
defaults: { _controller: LondonHelloBundle:Hello:more }
These are mapped to the following Actions within my HelloController:
public function bandAction($band)
{
$repository = $this->getDoctrine()
->getRepository('LondonHelloBundle:Gig');
$bandinfo = $repository->findByArtist($band);
return $this->render(
'LondonHelloBundle:Hello:band.html.twig',
array('band'=>$bandinfo)
);
}
public function moreAction($venue, $band)
{
$repository = $this->getDoctrine()
->getRepository('LondonHelloBundle:Gig');
$venueinfo = $repository->findBy(
array('venueName'=>$venue, 'artist'=>$band)
);
return $this->render(
'LondonHelloBundle:Hello:venue.html.twig',
array('venues'=>$venueinfo)
);
}
This seems to all work fine and I can use Doctrine to pull out all the info about a band from the database at /hello/blur (for example) and I can show all the info about a particular band at a particular venue at hello/02/blur (for example - 02 is the name of an arena in the UK).
However I also want to be able to show all the bands playing a particular venue at a URL like hello/02, however this conflicts with my first route:
path: /hello/{band}
Is there a way I can associate a route with a particular field in the database (I was thinking maybe something like conditions https://symfony.com/doc/current/routing.html#matching-expressions)? or do I just have to live with this and put some logic in the Twig template to handle things?
Unless you can use some rules to distinguish between band and venue names (eg. band names start with lowercase, venues uppercase) you have to use logic in your controller (not template!).
public function bandOrVenueAction($band_or_venue)
{
$repository = $this->getDoctrine()
->getRepository('LondonHelloBundle:Gig');
$bandinfo = $repository->findByArtist($band_or_venue);
if(empty($bandinfo) === false) {
return $this->render(
'LondonHelloBundle:Hello:band.html.twig',
array('band'=>$bandinfo)
);
}
$venueinfo = $repository->findByArtist($band_or_venue);
if(empty($venueinfo) === false) {
return $this->render(
'LondonHelloBundle:Hello:venue.html.twig',
array('venueinfo'=>$venueinfo)
);
}
// display 404
}
Related
I'm using a REST API to receive the data.
The data model is polymorphic related, similar to the one on the documentation:
https://laravel.com/docs/5.4/eloquent-relationships#polymorphic-relations
posts
id - integer
title - string
body - text
videos
id - integer
title - string
url - string
comments
id - integer
body - text
commentable_id - integer
commentable_type - string
Let's say, for example, the API is receiving this new comment:
{
"body": "This a test comment",
"commentable_type": "posts",
"commentable_id": "1"
}
How can I validate if the received commentable_type exists and is valid?
If I correctly understand your question, you are trying to validate that the object of the polymorphic relation exists, for the given commentable_type and commentable_id.
If that is the case, there is no existing validation rule to do so, but you can create one.
Based on the documentation, here is what you could do:
First, add the new rule in the boot method of a service provider (e.g. AppServiceProvider):
Validator::extend('poly_exists', function ($attribute, $value, $parameters, $validator) {
if (!$objectType = array_get($validator->getData(), $parameters[0], false)) {
return false;
}
return !empty(resolve($objectType)->find($value));
});
And this is how you would use it:
'commentable_id' => 'required|poly_exists:commentable_type
What the rule does is it tries and fetches the commentable type from the input values (based on the parameter passed on to the rule, i.e. commentable_type in our case), and then resolves the object and tries to find a record for the given ID ($value).
Please note that for this to work however, the value of commentable_type must be the fully qualified class name (e.g. App\Models\Post).
Hope this helps!
Better approach that includes morphs map:
Validator::extend('poly_exists', function ($attribute, $value, $parameters, $validator) {
if (! $type = array_get($validator->getData(), $parameters[0], false)) {
return false;
}
if (Relation::getMorphedModel($type)) {
$type = Relation::getMorphedModel($type);
}
if (! class_exists($type)) {
return false;
}
return ! empty(resolve($type)->find($value));
});
You can dynamically define a model_exists rule in your Request class. Something like this:
public function rules()
{
$polymorphExistsRule = '';
if ($this->has('commentable_type')) {
$polymorphExistsRule .= '|exists:' . $this->commentable_type . ',id';
}
return [
'commentable_type' => 'required_with:commentable_id',
'commentable_id' => 'required_with:commentable_type' . $polymorphExistsRule,
];
}
Edit
I might've misunderstood the first time. If you want to check that the model saved in commentable_type exists you could do something like this:
$type = $comment->commentable_type;
if(class_exists($type)) echo "it exists";
Depending on your needs you could do additional checking for it's inheritance (for example that it extends class Model). Or anything else that fits your needs really.
Edit2
This is what I would do if I were you. I would add property protected $allRelations to your Comment model and manually put all the relationships in. Then make some helper models to check if it's in the array.
Simple example:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Comment extends Model
{
// ..
protected $allRelations= [
'posts' => '\App\Post',
'videos' => '\App\Video',
];
public static function validateRelationNs($ns) {
return in_array($ns, $this->allRelations);
}
public static function validateRelationName($name) {
return array_key_exists($name, $this->allRelations);
}
// ...
}
Old answer:
Laravel expects full namespace name of the model for polymorphic type columns (in your case commentable_type should be \Full\Ns\Post, not posts).
The easiest way to ensure correctness is to always save it through the relationship. For example:
$post = Post::first();
$comment = new Comment($attributes);
$post->comments()->save($comment).
This will automatically set both commentable_id and commentable_type correctly (assuming your relationsare correctly defined).
Additional checking
Other then that you could check through model events. You could validate it before saving to the database.
My final version work for validate type and id:
Validator::extend('poly_exists', function ($attribute, $value, $parameters, $validator) {
if (!$objectType = array_get($validator->getData(), $parameters[0], false)) {
return false;
}
if (!class_exists($objectType)) {
return false;
}
return !empty(resolve($objectType)->find($value));
});
I'm continuing my adventure with Laravel 5.1. I have a small project I'm working on and I'm looking to find a nice way to load a User's gecko ideally without having the user id in the URI.
This is my current URI which works as it is:
Route::get('gecko/{user_id}/{name}', 'GeckoController#show');
As you can see i'm holding the user id in the URI and then i'm querying it to find the right gecko. As shown below:
public function show($user_id, $name)
{
$name = str_replace('-', ' ', $name);
$gecko = Gecko::where(compact('user_id', 'name'))->first();
return view('gecko.show', compact('gecko'));
}
So to get this to work, I would do project.dev/gecko/1/Zilly - It works, but having the User ID in there kind of sucks. I decided that having the User ID was important in case there were multiple users who have geckos with the same name.
Any help on this is greatly appreciated, and if you need any extra code let me know :)
Andy
If you would like to use username instead of user_id:
Routes:
Route::get('gecko/{username}/{geckoname}', 'GeckoController#show');
Controller:
public function show($username, $geckoname) {
$user_id = User::where('username', $username)->first()->id;
$geckoname = str_replace('-', ' ', $geckoname);
$gecko = Gecko::where(compact('user_id', 'geckoname'))->first();
return view('gecko.show', compact('gecko'));
}
If the user authenticated you can use Auth::user()->id and you should add only the gecko id.
For example:
Routes:
Route::get('gecko/{gecko_id}', 'GeckoController#show');
Controller:
public function show($id) {
$gecko = Gecko::find($id)->where('user_id', Auth::user()->id)->first();
return view('gecko.show', compact('gecko'));
}
If you would like to use geckoname:
Routes:
Route::get('gecko/{geckoname}', 'GeckoController#show');
Controller:
public function show($geckoname) {
$gecko_id= Gecko::where('geckoname',$geckoname)->first()->id;
$gecko = Gecko::find($gecko_id)->where('user_id', Auth::user()->id)->first();
return view('gecko.show', compact('gecko'));
}
I've created a form which adds a category of product in a Categories table (for example Sugar Products or Beer), and each user has their own category names.
The Categories table has the columns id, category_name, userId, created_At, updated_At.
I've made the validation and every thing is okay. But now I want every user to have a unique category_name. I've created this in phpMyAdmin and made a unique index on (category_name and userId).
So my question is this: when completing the form and let us say that you forgot and enter a category twice... this category exist in the database, and eloquent throws me an error. I want just like in the validation when there is error to redirect me to in my case /dash/warehouse and says dude you are trying to enter one category twice ... please consider it again ... or whatever. I am new in laravel and php, sorry for my language but is important to me to know why is this happens and how i solve this. Look at my controller if you need something more i will give it to you.
class ErpController extends Controller{
public function __construct()
{
$this->middleware('auth');
}
public function index()
{
return view('pages.erp.dash');
}
public function getWarehouse()
{
$welcome = Auth::user()->fName . ' ' . Auth::user()->lName;
$groups = Group::where('userId',Auth::user()->id)->get();
return view('pages.erp.warehouse', compact('welcome','groups'));
}
public function postWarehouse(Request $request)
{
$input = \Input::all();
$rules = array(
'masterCategory' => 'required|min:3|max:80'
);
$v = \Validator::make($input, $rules);
if ($v->passes()) {
$group = new Group;
$group->group = $input['masterCategory'];
$group->userId = Auth::user()->id;
$group->save();
return redirect('dash/warehouse');
} else {
return redirect('dash/warehouse')->withInput()->withErrors($v);
}
}
}
You can make a rule like this:
$rules = array(
'category_name' => 'unique:categories,category_name'
);
What I basically want is user permissions.
I've got an table called 'accounts' in my database. There is a column called 'group_id'.
I want to set it when the 'group_id' = 3, then the user is admin. Then he can view special sites, buttons, and things like that. I've tried to implement something like that:
public function ($roleName) {
$role = $this->roles;
if ($role->name == $roleName) {
return true;
}
return false;
}
Also, I don't know what and how the model is needed, do I need an new one and things like that.
Old post, but maybe someone will find this useful
Add a method to your User model that returns true if the user is an admin. In our case here, it's simply "is our group_id equal to 3?"
// models/User.php
class User extends Eloquent
{
...
public function isAdmin()
{
return $this->group_id == 3;
}
}
Next add a filter that can be used to protect routes
// filters.php
Route::filter('admin', function($route, $request)
{
if ( ! Auth::user()->isAdmin())
{
return App::abort(401, 'You are not authorized.');
}
});
Finally use the filter to protect a group of routes. I this simplified case, only an admin user could access /admin
// routes.php
Route::group(array('before' => array('auth|admin')), function()
{
Route::get('/admin', function()
{
return Response::make("You're an admin!");
}
});
Based on this post:
http://laravelsnippets.com/snippets/admin-route-filter
I suggest Authority for Laravel 4
I personally use Verify package for user management.
I have a search function on my website implemented with elasticsearch.
Now I have a little question about design.
I have a searchAction with route /search that takes a parameter in the query string. Like /search?terms=....
I would like to make the results list filterable, but I have some doubts about the right design to achieve this.
What is the best solution to make a list of filtered results?
If I pass the filter parameter I need to specify a form action with the current url and append the current query string like a link, right?
Example:
<form action="{{ current_pat }} ~ {{ query_string }}" method="post">
<input type="checkbox" name="filter_one">....
In this case the url will like: /search?terms=... and in $post I have the filter. Is this the right solution, or is a list of links better?
Example:
<ul>
<li><a href="{{current_path}} ~ {{ query_string }} ~ {{ this_filter }}">...
<li><a href="{{current_path}} ~ {{ query_string }} ~ {{ this_another_filter }}">
...
In this case the url will be like: /search?terms=...&this_filter=...
In the form case with the get parameter and post filter I need to take both type of parameters in the search action. Is this good?
Instead the link will now have all parameters in the $get request, but I don't like to build the url with query strings in the template.
What's the best way?
Use KNP paginator bundle with search action. On search form make the post action to your searchAction in Controller and after sorting data with matching criteria, re-render the page.
public function searchAction(Request $request,array $arguments = array())
{
$em = $this->getDoctrine()->getManager();
$paginator = $this->get('knp_paginator');
$parameter = $request->get('board_search');
$boardRepo = $this->getDoctrine()->getRepository('PNCMISDashboardBundle:ExaminationBoards')->loadBoardByName($parameter);
$boards = $paginator->paginate($boardRepo, $this->get('request')->query->get('page', 1), 10);
return $this->render('PNCMISDashboardBundle:ExaminationBoards/CRUD:index.html.twig', array(
'boards'=> $boards,
)
);
}
public function loadBoardByName($name)
{
$q = $this
->createQueryBuilder('boards')
->where('upper(boards.name) LIKE upper(:search)')
->setParameter('search', '%'.$name.'%')
->getQuery()
;
try {
// The Query::getSingleResult() method throws an exception
// if there is no record matching the criteria.
$user = $q->getResult();
} catch (NoResultException $e) {
throw new UsernameNotFoundException(sprintf('Unable to find an board identified by "%s".', $name), null, 0, $e);
}
return $user;
}
I prefer to have all the parameters in the query string which will allow the user to bookmark the url or send it in an email.
It depends.
1 If you don't need to index (SEO for instance - google bot) the filtered results page, in my opinion you should use AJAX to achieve this.
Routing:
search_result:
pattern: /search
defaults: { _controller: AcmeExampleBundle:Ajax:searchResult }
requirements:
_method: POST
JavaScript (pseudo Code)
filterResults() {
var queryString = $('input#toSearch').val();
var filters = new Object();
filters['someKey'] = someVal;
$.post('/search', {filters: filters, queryString: queryString}, function(data) {
$('#resultList').html(data);
});)
}
Of course, you should not harcode url '/search'.
Controller:
// src/Acme/ExampleBundle/Controller/AjaxController.php
// ...
public function searchResultAction()
{
$filters = $this->getRequest()->get('filters', array());
$searchObject = new searchObj();
// or smt like $this->get('service_name'); if you use search object as service
// you can also use entity manager
$searchObject->setQueryString($this->getRequest()->get('queryString'));
$searchObject->setFilters($filters);
return $this->render('AcmeExampleBundle:Ajax:searchResult.html.twig', array(
'records' => $searchObject->getResults()
));
}
Of course you can return json response, but in my opinion, return ready to inject part of template is simplest to manage
And View:
{% for record in records %}
<div class="record">{{ record.title }}</div>
{% endfor %}
2 If you wanna index filtred results page you should use user-friendly URLs instead AJAX methods
Routing:
search_result:
pattern: /search/{queryString}/{make}/{model}
defaults: { _controller: AcmeExampleBundle:Ajax:searchResult }
requirements:
_method: GET
Controller:
// src/Acme/ExampleBundle/Controller/AjaxController.php
// ...
public function searchResultAction($queryString, $make, $model)
{
$filters = array('make' => $make, 'model' => $model);
$searchObject = new searchObj();
// or smt like $this->get('service_name'); if you use search object as service
// you can also use entity manager
$searchObject->setQueryString($queryString);
$searchObject->setFilters($filters);
return $this->render('AcmeExampleBundle:Ajax:searchResult.html.twig', array(
'records' => $searchObject->getResults()
));
}