Laravel 5 make routing with unique parameter - php

I am currently using a route with a parameter nameto query the profile of a user in my application.
So for example: /members/johnwill show the profile of John.
You see the problem here, if there are 2 John's then it's gonna be a problem.
I know I could do something like this /members/idsince id is unique but I want the url to look pretty with the user's name and not a random number.
So my question is if there is a way to use the id to make it unique but to display the name in url?
my route:
Route::get('/members/{name}', 'UserController#usersProfile');
My usersProfile method:
/**
* Returns a users profile.
*
* #param string $name
*
* #return \Illuminate\Http\Response
*/
public function usersProfile($name)
{
$profile = $this->userService->getProfile($name);
if ($profile == null) {
return redirect('members')->with('status', 'Whoops, looks like that member does not exist (yet).');
}
return view('members/profile', ['profile' => $profile]);
}

you can use some package for slug your model name :
https://github.com/spatie/laravel-sluggable
https://github.com/cviebrock/eloquent-sluggable
these packages automatically doing that for you.
if you have same name this will happen:
http://example.com/post/my-dinner-with-andre-francois
http://example.com/post/my-dinner-with-andre-francois-1
http://example.com/post/my-dinner-with-andre-francois-2
just in production i have some issue with scopes...
when you apply global scope on model , it may create duplicate slug for that ... you can fix that by add this to your model :
public function scopeFindSimilarSlugs(Builder $query, Model $model, $attribute, $config, $slug)
{
$separator = $config['separator'];
return $query->withoutGlobalScopes()->where(function (Builder $q) use ($attribute, $slug, $separator) {
$q->where($attribute, '=', $slug)
->orWhere($attribute, 'LIKE', $slug . $separator . '%');
});
}

You should add another column to your user table with name 'slug' or 'username' and map that column in your route.
In your users model, make a static function to generate unique slug with a number attached and save it in that new column to provide users with public/private profile URLs.
This below sample will stop returning slug/username after 100 users with the same name.
public static function getUniqueSlug($name) {
$original_slug = getSlug($name); // Default getter to get value in model
$slug = str_slug($original_slug); //Convert a string into url friendly string with hyphens
while(true) {
$count = User::where('slug', $slug)->count();
if($count > 0) {
$slug = $original_slug."-".rand(1,99);
} else {
return $slug;
}
}
}

Related

Laravel Form best way to store polymorphic relationship

I have a notes model. Which has a polymorphic 'noteable' method that ideally anything can use. Probably up to 5 different models such as Customers, Staff, Users etc can use.
I'm looking for the best possible solution for creating the note against these, as dynamically as possible.
At the moment, i'm adding on a query string in the routes. I.e. when viewing a customer there's an "Add Note" button like so:
route('note.create', ['customer_id' => $customer->id])
In my form then i'm checking for any query string's and adding them to the post request (in VueJS) which works.
Then in my controller i'm checking for each possible query string i.e.:
if($request->has('individual_id'))
{
$individual = Individual::findOrFail($request->individual_id_id);
// store against individual
// return note
}elseif($request->has('customer_id'))
{
$customer = Customer::findOrFail($request->customer_id);
// store against the customer
// return note
}
I'm pretty sure this is not the best way to do this. But, i cannot think of another way at the moment.
I'm sure someone else has come across this in the past too!
Thank you
In order to optimize your code, dont add too many if else in your code, say for example if you have tons of polymorphic relationship then will you add tons of if else ? will you ?,it will rapidly increase your code base.
Try instead the follwing tip.
when making a call to backend do a maping e.g
$identifier_map = [1,2,3,4];
// 1 for Customer
// 2 for Staff
// 3 for Users
// 4 for Individual
and so on
then make call to note controller with noteable_id and noteable_identifier
route('note.create', ['noteable_id' => $id, 'noteable_identifier' => $identifier_map[0]])
then on backend in your controller you can do something like
if($request->has('noteable_id') && $request->has('noteable_identifier'))
{
$noteables = [ 'Customers', 'Staff', 'Users','Individual']; // mapper for models,add more models.
$noteable_model = app('App\\'.$noteables[$request->noteable_identifier]);
$noteable_model::findOrFail($request->noteable_id);
}
so with these lines of code your can handle tons of polymorphic relationship.
Not sure about the best way but I have a similar scenario to yours and this is the code that I use.
my form actions looks like this
action="{{ route('notes.store', ['model' => 'Customer', 'id' => $customer->id]) }}"
action="{{ route('notes.store', ['model' => 'User', 'id' => $user->id]) }}"
etc..
And my controller looks this
public function store(Request $request)
{
// Build up the model string
$model = '\App\Models\\'.$request->model;
// Get the requester id
$id = $request->id;
if ($id) {
// get the parent
$parent = $model::find($id);
// validate the data and create the note
$parent->notes()->create($this->validatedData());
// redirect back to the requester
return Redirect::back()->withErrors(['msg', 'message']);
} else {
// validate the data and create the note without parent association
Note::create($this->validatedData());
// Redirect to index view
return redirect()->route('notes.index');
}
}
protected function validatedData()
{
// validate form fields
return request()->validate([
'name' => 'required|string',
'body' => 'required|min:3',
]);
}
The scenario as I understand is:
-You submit noteable_id from the create-form
-You want to remove if statements on the store function.
You could do that by sending another key in the request FROM the create_form "noteable_type". So, your store route will be
route('note.store',['noteableClass'=>'App\User','id'=>$user->id])
And on the Notes Controller:
public function store(Request $request)
{
return Note::storeData($request->noteable_type,$request->id);
}
Your Note model will look like this:
class Note extends Model
{
public function noteable()
{
return $this->morphTo();
}
public static function storeData($noteableClass,$id){
$noteableObject = $noteableClass::find($id);
$noteableObject->notes()->create([
'note' => 'test note'
]);
return $noteableObject->notes;
}
}
This works for get method on store. For post, form submission will work.
/**
* Store a newly created resource in storage.
*
* #param \Illuminate\Http\Requests\NoteStoreRequest $request
* #return \Illuminate\Http\Response
*/
public function store(NoteStoreRequest $request) {
// REF: NoteStoreRequest does the validation
// TODO: Customize this suffix on your own
$suffix = '_id';
/**
* Resolve model class name.
*
* #param string $name
* #return string
*/
function modelNameResolver(string $name) {
// TODO: Customize this function on your own
return 'App\\Models\\'.Str::ucfirst($name);
}
foreach ($request->all() as $key => $value) {
if (Str::endsWith($key, $suffix)) {
$class = modelNameResolver(Str::beforeLast($key, $suffix));
$noteable = $class::findOrFail($value);
return $noteable->notes()->create($request->validated());
}
}
// TODO: Customize this exception response
throw new InternalServerException;
}

Symfony RedirectToRoute with array paramater

I'm working on an upload system based on Symfony 4 and PHPSpreadsheet.
My user uploads the excel file. I then create the products/categories... At the end I want to get all categories created. The following Doctrine query :
/**
* #param $user_id
* #return array
* #throws \Doctrine\ORM\NonUniqueResultException
*/
public function checkIfNew($user_id): ?array {
return $this->createQueryBuilder('c')
->andWhere('c.categorie_parent_id is NULL')
->andWhere('c.created_by = :val')
->setParameter('val', $user_id)
->getQuery()
->getResult()
;
}
gets all my categories where created_by is by the user ID, and where Parent is null.
What I want to do is to get an array of all those categories and then redirect the user to a rendered Twig template page where he can make the link.
But I don't really understand how to use the parameters...
In my Upload I've set this :
$isNew = $this->getDoctrine()
->getRepository(Categorie::class)
->checkIfNew($this->getUser()->getId());
if (!is_null($isNew)){
$this->addFlash('success', 'Catalogue crée avec succès');
return $this->redirectToRoute('admin.categorie.link', $this->getUser()->getId());
}
I don't understand how I can use the request to redirect the user correctly using the route :
/**
* #Route("/admin/categories/import", name="admin.categorie.link", methods={"GET|POST"})
* #param Categorie $categories
*/
public function linkImport()
{
// What TODO here ?
return $this->render('admin/categories/link.html.twig', compact('categories'));
}
Well I suppose I have to add $isNew as a parameter for my request ? But did I reuse the array after to use this in my twig, and display the element from this array inside my twig.
There is a small error:
If your route is "/admin/categories/import" and you want to transfer a value like "$this->getUser()->getId()" then this should be like
Route "/admin/categories/import/{userId}" and your return would be:
return $this->redirectToRoute('admin.categorie.link', ['userId' => $this->getUser()->getId()]);
and your controller could take the userId as var:
/**
* #Route("/admin/categories/import/{userId}", name="admin.categorie.link", methods={"GET|POST"})
*/
public function linkImport(int $userId, EntityManagerInterface $em) // var name must be the same as in the route
{
// What TODO here ?
$categories = $em->getRepository(Categories::class)->findByUserId($userId);
return $this->render('admin/categories/link.html.twig', ['categories' => $categories]);
}
Now you can access your categories in twig with {{categories}}

How do i pass number to middleware "can" in Laravel Authorization

based on documentation https://laravel.com/docs/5.5/authorization#via-middleware
use App\Post;
Route::put('/post/{post}', function (Post $post) {
// The current user may update the post...
})->middleware('can:update,post');
the 'post' in 'can:update,post' is variable passed from '{post}'.
im trying to use middleware('can:update,1'). its not working. maybe its search for '$1' variable, how to pass number '1' to 'can' middleware?
update this is the gate:
Gate::define('update', function ($user, $role){
$id = $user->id;
$roles = $user::find($id)->roles()->get();
$roleid = $roles[0]->pivot->role_id;
//above code just for get role id, and $role_id is 1
return $roleid === $role;
});
Probably you don't have Policy created for Post.
You can create by command:
php artisan make:policy PostPolicy --model=Post
and then implement method update in that policy.
I to had this same problem so I did some digging into the 'can' middleware (Which maps to Illuminate\Auth\Middleware\Authorize)
Once in the class we see the following code
/**
* Get the model to authorize.
*
* #param \Illuminate\Http\Request $request
* #param string $model
* #return \Illuminate\Database\Eloquent\Model|string
*/
protected function getModel($request, $model)
{
if ($this->isClassName($model)) {
return trim($model);
} else {
return $request->route($model, null) ?:
((preg_match("/^['\"](.*)['\"]$/", trim($model), $matches)) ? $matches[1] : null);
}
}
What this means is...
If our string passed in is a class name then return that class name
If it is not a class name then...
1 Try to get it from the route, then return the route param
2 Try to get the model from the string via the regex "/^['\"](.*)['\"]$/"
So now lets say we have the middleware call of
$this->middleware(sprintf("can:create,%s,%s", User::class, Role::SUPPORT));
This will not work because the Role::SUPPORT does not match the regex
To match it we simply need to place the Role::SUPPORT into quotes.
TAKE NOTE OF THE "'" around the second %s
$this->middleware(sprintf("can:create,%s,'%s'", User::class, Role::SUPPORT));
To answer your question specifically, quote your '1' value
middleware("can:update,'1'").

Laravel Eloquent validation insert exception?

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'
);

Laravel 5: one form to update 2 linked tables. How to?

I have the 2 simple tables below:
CUSTOMERS
id, email
CLAIMS
id, customer_id(fk), description
I created the related models (Customers.php and Claims.php) and set-up relationships: hasOne() and belongsTo().
I also have my related RESTful controllers ready: CustomersController.php and ClaimsController.php.
What would be the best solution if I need to create/update records in both tables by submitting one form? Create one general controller? Mix models?
I have been searching in Laravel docs and on Google and still have no idea how to achieve this.
Customer model
public function claims(){
return $this->hasMany('App\Claims');
}
Claims model
public function customer(){
return $this->belongsTo('App\Customer');
}
Now in controller u need to send request in store action
Something like this
class CreateCustomerClaim extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'email' => 'required',
'description'=> 'required'
];
}
}
Now in store action send your request, grab data from request and insert it in db
public function store(CreateCustomerClainsRextends $request)
{
//example
$customer= new Customer($request->all());
Auth::user()->claims()->save($customer);
}
if u need to update use same request in update function, when u grab data from request just use update. Here is example where i update 3 different tables from one request
public function update($id,ArtikalUpdateRequest $request)
{
$article = Artikal::findOrFail($id);
if($article !== null){
$article->update($request->all());
\DB::table('artikal_podkategorija')
->where('artikal_id', $article->id)
->update(array('podkategorija_id' => $request['podkategorija']));
\DB::table('arikalslike')
->where('artikal_id', $article->id)
->update(array('NazivSlike' => $request['NazivSlike']));
$slika = \DB::table('arikalslike')
->where('artikal_id', $article->id)->first();
$image = Request::file('image');
//dd($image);
if($image != null){
$destinationPath = 'uploads/artiklislike/';
$thumb = $slika->SifraSlike;
$fileName = $thumb;
$nazivthumb = $slika->NazivThumb;
$slika->NazivSlike = $request['NazivSlike'];
$slika->NazivThumb = $nazivthumb;
$slika->SifraSlike = $fileName;
$slika->artikal_id = $article->id;
//Snima sliku
$img = Image::make(Input::file('image'));
$destinationPath = $destinationPath.$fileName;
Image::make($img)->save($destinationPath);
// Snima sliku u manjem formatu thumb
$destinationPath = 'uploads/artiklislike/';
$img = Image::make(Input::file('image'));
$destinationPath = $destinationPath.$nazivthumb;
Image::make($img)->resize(300, 200)->save($destinationPath);
}
}
return redirect('artikli')->with(['flash_message' => 'Uspiješno ste obrisali artikal!']);
}

Categories