Rewriting route depending of parameter value Yii - php

I have several rules in Yii that allows me to rewrite some routes, where every will be pass to the action as a get parameter.
'<department>' => 'products/index',
'<department>/<category>' => 'products/index',
I want to explicitly write a rule that depending of the parameter value will change the url to whatever I want
example, right now I have an URL like this
www.mysite.com/Books+%26+Pencils which was rewritten because of this rule '<department>' => 'products/index', which is ok
I want to change that URL to www.mysite.com/books-pencils , if anyone know how to write a rule that compares the value of the deparment attribute and then rewrites it to whatever I want.
THanks

You can use a custom class to handle you special requests.
I have used sth like this, to get my custom URLs out of a database:
'urlManager'=>array(
'rules'=>array(
array(
'class' => 'application.components.UrlRule',
),
),
),
Then you create your custo class similar to this:
<?php
Yii::import("CBaseRule");
class UrlRule extends CBaseUrlRule
{
public function createUrl($manager,$route,$params,$ampersand)
{
// check for my special case of URL route, if not found, then return the unchaged route
preg_match("/^(.+)\/(.+)$/", $route, $r);
if(!is_array($r) or !isset($r[1]) or !isset($r[2])) {
return $route;
}
// handle your own route request, and create your url
$url = 'my-own-url/some-thing';
// check for any params, which i also want to add
$urlParams = $manager->createPathInfo($params,"=","&");
$return = trim($url,'/');
$return.= $urlParams ? "?" . $urlParams : "";
return $return;
}
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
// handle my special url request
$controller = '....';
$action = '.....';
// return the controller/action that should be used
return lcfirst($controller)."/".$action;
}
}
I do not know if this was what you wanted, but at least in this class you can do everything you need with the URL requested.
If you would e.g. like to redirect a lot of similar URLs with a 301 Redirect to 1 URL, you could think of sth like this in the parseUrl function
// check my route and params, and if I need to redirect
$request->redirect('/your/new/url/?params=bla',true,'301');

First of all, if you want to change a URL, you should do a redirect (in this case 301). To implement this logic you can use custom URL rule class.
Url manager configuration:
'rules' => array(
// custom url rule class
array(
'class' => 'application.components.MyUrlRule',
),
)
MyUrlRule class:
class MyUrlRule extends CBaseUrlRule
{
public function createUrl($manager,$route,$params,$ampersand)
{
// Logic used to create url.
// If you do not create urls using Yii::app()->createUrl() in your app,
// you can leave it empty.
}
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
// modify url
$pathInfoCleaned = strtolower(preg_replace('+%26+', '-', $pathInfo));
// redirect if needed
if ($pathInfo !== $pathInfoCleaned) {
$request->redirect($pathInfoCleaned, true, 301);
}
// parse params from url
$params = explode('/', $pathInfo);
if (isset($params[0])) {
$_GET['department'] = $params[0];
if (isset($params[1])) {
$_GET['category'] = $params[1];
}
}
return 'products/index';
}
}

Related

CodeIgniter 4: How do you enable user defined routes?

I'm just wondering how I can redirect to StudProfile after using the UpStudProf function. After running UpStudProf function, the URL became http://localhost/csms/public/index.php/Home/StudProfile, but it should be http://localhost/Home/StudProfile and is it possible to remove the Controllers name Home on the URL?
public function StudProfile(){
$crudModel = new Mod_Stud();
$data = [];
$data['user_data'] = $crudModel->orderBy('s_id', 'ASC')->findAll();
$data['title'] = 'SMS | STUDENT PROFILE';
$data['heading'] = 'Welcome to SMS';
$data['main_content'] = 'stud-prof'; // page name
return view('innerpages/template', $data);
}
public function UpStudProf(){
$crudModel = new Mod_Stud();
$s_id = $this->request->getPost('s_id');
$data = array(
's_lrn' => $this->request->getPost('s_lrn'),
's_fname' => $this->request->getPost('s_fname'),
's_mname' => $this->request->getPost('s_mname'),
's_lname' => $this->request->getPost('s_lname'),
);
$crudModel->upStud($data, $s_id);
return redirect()->to('Home/StudProfile'); //return to StudProfile
}
Routes.php
$routes->setDefaultNamespace('App\Controllers');
$routes->setDefaultController('Home');
$routes->setDefaultMethod('index');
$routes->setTranslateURIDashes(false);
$routes->set404Override();
$routes->setAutoRoute(true);
... is it possible to remove the Controllers name Home on the URL?
Use Defined Routes Only
When no defined route is found that matches the URI, the system will
attempt to match that URI against the controllers and methods as
described above. You can disable this automatic matching, and restrict
routes to only those defined by you, by setting the setAutoRoute()
option to false:
$routes->setAutoRoute(false);
Secondly, after disabling automatic matching, declare your user-defined route:
app/Config/Routes.php
$routes->get('student-profiles', 'Home::StudProfile');
Lastly: \App\Controllers\Home::UpStudProf,
redirect(string $route)
Parameters: $route (string) – The reverse-routed or named route to
redirect the user to.
Instead of:
// ...
return redirect()->to('Home/StudProfile'); //return to StudProfile ❌
// ...
Use this:
// ...
return redirect()->to('/student-profiles'); ✅
// ...

CodeIgniter 4 - Validation Custom Rule Function Quandry

In my CI4 learning, I have started by trying to simulate user sign in functionality. I have a Controller, two Views (not shown here, but really simply pages- one a pretty much just single form, and the other one a “blank” success HTML page), a set of custom rules in the Validation.php file, and a CustomRule.php file with the first of the methods that will implement all my custom rules (which, ultimately, I’d like to have all set in the Validation.php file). For lack of a better idea, I’ve stuck the CustomRules.php file in the app\Config\ folder.
Here is my problem:
For the life of me, I can’t figure out how to get the Validation service to pass additional parameters (from the form) to my custom rules function called ‘user_validated’. The CI4 documentation describes what the custom function needs to cater for when accepting additional parameters, but not how to trigger the Validation service to pass these additional parameters to one’s custom function… so although ‘user_validated’ is called, only ‘user_email_offered’ is ever passed as in as a string- nothing else goes in, from what I can tell. How do I get around this?
I have tried inserting < $validation->setRuleGroup('user_signin'); > before the call to validate, but found that I could move the setting of the rule group into the call to validate, using: $validationResult = $this->validate('user_signin'), which seemed to do the same, and which doesn't seem to work without the rule-group as a parameter (?). This still doesn't seem to be what triggers the additional data to be passed to the custom rule's method.
Extracts from my hack are appended below.
I’d be very grateful one of you knowledgeable folk could please point me in the right direction.
In app\Controllers\SignupTest.php:
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
class SignupTest extends BaseController
{
public function index() { // redirection from the default to signup(), signin(), ...
return $this->signup();
}
public function signup() {
helper(['form']);
$validation = \Config\Services::validation();
if ($this->request->getPost()) { // still TBD: any different to using $this->request->getGetPost() ?
$validationResult = $this->validate('user_signin'); // set the rules to use: 'user_signin', 'user_signup'
if (!$validationResult) {
$validationErrors = $validation->getErrors();
return view('SignupTestView', $validationErrors); // redisplay simple html form view with list of validation errors
} else {
return view('SignupTestViewSuccess'); // display view to show success
}
} else {
return view('SignupTestView'); // initial display, in the event of there being no POST data
}
}
}
In \app\Config\CustomRules.php:
<?php
namespace Config;
use App\Models\UserModel;
//--------------------------------------------------------------------
// Custom Rule Functions
//--------------------------------------------------------------------
class CustomRules
{
public function user_validated(string $str, string $fields = NULL, array $data = NULL, string &$error = NULL) : bool{
$user_email_offered = $str;
$user_password_offered = ''; // to be extracted using $fields = explode(',', $fields), but $fields is never provided in the call to this user_validated method
if (($user_email_offered !== NULL) && ($user_password_offered !== NULL)) {
$usermodel = new UserModel(); // intended to create a UserEntity to permit connectivity to the database
$user_found = $usermodel->find($user_email_offered); // we're going to assume that user_email is unique (which is a rule configured in the database table)
if ($user_found === NULL) { // check if user exists before doing the more involved checks in the else-if section below, which may throw exceptions if there's nothing to compare (?)
...
}
}
In \app\Config\Validation.php:
?php
namespace Config;
class Validation
{
//--------------------------------------------------------------------
// Setup
//--------------------------------------------------------------------
/**
* Stores the classes that contain the
* rules that are available.
*
* #var array
*/
public $ruleSets = [
\CodeIgniter\Validation\Rules::class,
\CodeIgniter\Validation\FormatRules::class,
\CodeIgniter\Validation\FileRules::class,
\CodeIgniter\Validation\CreditCardRules::class,
\Config\CustomRules::class,
];
/**
* Specifies the views that are used to display the
* errors.
*
* #var array
*/
public $templates = [
'list' => 'CodeIgniter\Validation\Views\list',
'single' => 'CodeIgniter\Validation\Views\single',
];
//--------------------------------------------------------------------
// Custom Rules
//--------------------------------------------------------------------
/* configurable limits for validation rules array below*/
const user_email_min_lenth = 9;
const user_email_max_lenth = 50;
const user_password_min_lenth = 6;
const user_password_max_lenth = 25;
public $user_signin = [
'user_email' => [
'label' => 'e-mail address',
'rules' => 'trim|required|valid_email|user_validated', // user_validated is custom rule, that will have a custom error message
'errors' => [
'required' => 'You must provide an {field}',
'valid_email' => 'Please enter a valid {field}',
]
],
'user_password' => [
'label' => 'password',
'rules' => 'trim|required',
'errors' => [
'required' => 'Enter a {field} to sign in',
'user_password_check' => 'No such user/{field} combination found',
]
Calling custom rule with parameters should be exactly the same as calling CI4's regular rules. Let's get for example "required_without". You use it like in this example:
$validation->setRule('username', 'Username', 'required_without[id,email]');
And the function is declared as so:
public function required_without($str = null, string $fields, array $data): bool
{
$fields = explode(',', $fields);
//...
}
where $str - this is your main field, $fields - string, packing a comma-separated array.
As for Grouping rules, you do not need to group rules to be able to use custom rules with parameters.
If you have only 2 fields to test against you can go a bit cheaper, which will not be perfect but still works:
Function:
public function myrule(string $mainfield, string $fieldtotestwith): bool
{
//doing stuff
}
Validating rule:
$validation->setRule('somemainfield', 'Something', 'myrule[somesecondfield]');

pass multiple parameters to controller from route in laravel5

I want to pass multiple parameters from route to controller in laravel5.
ie,My route is ,
Route::get('quotations/pdf/{id}/{is_print}', 'QuotationController#generatePDF');
and My controller is,
public function generatePDF($id, $is_print = false) {
$data = array(
'invoice' => Invoice::findOrFail($id),
'company' => Company::firstOrFail()
);
$html = view('pdf_view.invoice', $data)->render();
if ($is_print) {
return $this->pdf->load($html)->show();
}
$this->pdf->filename($data['invoice']->invoice_number . ".pdf");
return $this->pdf->load($html)->download();
}
If user want to download PDF, the URL will be like this,
/invoices/pdf/26
If user want to print the PDF,the URL will be like this,
/invoices/pdf/26/print or /invoices/print/26
How it is possibly in laravel5?
First, the url in your route or in your example is invalid, in one place you use quotations and in the other invoices
Usually you don't want to duplicate urls to the same action but if you really need it, you need to create extra route:
Route::get('invoices/print/{id}', 'QuotationController#generatePDF2');
and add new method in your controller
public function generatePDF2($id) {
return $this->generatePDF($id, true);
}

Creating yii2 dynamic pages with url: www.example.com/pageName

In my system, users need to have their profile pages. It is requested from me that these pages will be displayed in url like this:
www.example.com/John-Doe
www.example.com/Mary-Smith
How to achieve these URLs in yii2 ? These John-Doe and Mary-Smith can be user usernames or profile names. For example I have field in user table called "name" and it will hold names "John Doe", "Mary Smith". Pay attention that I need SEO friendly URLs with "-" instead of blank spaces.
URLs like this:
www.example.com/profile/view?id=1
are not an option.
www.example.com/John-Doe
www.example.com/Mary-Smith
I think there is no normal way to use these urls because at first controller (in your case it's ProfileController) needs to be determined. From these urls it's impossible to do.
Second problem with the urls you provided - uniqueness is not guaranteed. What if another user with name John Doe will sign up on site?
Look for example at your profile link at Stack Overflow:
http://stackoverflow.com/users/4395794/black-room-boy
It's not http://stackoverflow.com/black-room-boy and not even http://stackoverflow.com/users/black-room-boy.
Combining id and name is more widespread and robust approach. Also they can be combined with dash like this: http://stackoverflow.com/users/4395794-black-room-boy
Yii 2 has built-in behavior for this, it's called SluggableBehavior.
Attach it to your model:
use yii\behaviors\SluggableBehavior;
public function behaviors()
{
return [
[
'class' => SluggableBehavior::className(),
'attribute' => 'name',
// In case of attribute that contains slug has different name
// 'slugAttribute' => 'alias',
],
];
}
For your specific url format you can also specify $value:
'value' => function ($event) {
return str_replace(' ', '-', $this->name);
}
This is just an example of generating custom slug. Correct it according to your name attribute features and validation / filtering before save.
Another way of achieving unique url is setting $ensureUnique property to true.
So in case of John-Doe existense John-Doe-1 slug will be generated and so on.
Note that you can also specify your own unique generator by setting $uniqueSlugGenerator callable.
Personally I don't like this approach.
If you choose the option similar to what Stack Overflow uses, then add this to your url rules:
'profile/<id:\d+>/<slug:[-a-zA-Z]+>' => 'profile/view',
In ProfileController:
public function actionView($id, $slug)
{
$model = $this->findModel($id, $slug);
...
}
protected function findModel($id, $slug)
{
if (($model = User::findOne(['id' => $id, 'name' => $slug]) !== null) {
return $model;
} else {
throw new NotFoundHttpException('User was not found.');
}
}
But actually id is enough to find user. Stack Overflow does redirect if you access with correct id but different slug. The redirects occurs when you are completely skipping the name too.
For example http://stackoverflow.com/users/4395794/black-room-bo redirects to original page http://stackoverflow.com/users/4395794/black-room-boy to avoid content duplicates that are undesirable for SEO.
If you want use this as well, modify findModel() method like so:
protected function findModel($id)
{
if (($model = User::findOne($id) !== null) {
return $model;
} else {
throw new NotFoundHttpException('User was not found.');
}
}
And actionView() like so:
public function actionView($id, $slug = null)
{
$model = $this->findModel($id);
if ($slug != $model->slug) {
return $this->redirect(['profile/view', ['id' => $id, 'slug' => $model->slug]]);
}
}

Yii Url Manager change integer parameter to equivalent string

Im trying to make url parameter string to it's integer equivalent
I want user/admins to route on index.php?r=user/admin&lvl=2, user/employees on index.php?r=user/admin&lvl=3 etc but it seems that it is not possible in yii?
I made the following rule but it's not working
'rules' => array(
'user/admins' => 'user/admin/<lvl:2>',
'user/employees' => 'user/admin/<lvl:3>'
);
I think this how I made it on CodeIgniter:
$route['user/admins'] = "user/admin/lvl/2";
$route['user/employees'] = "user/admin/lvl/3";
but I'm not sure in yii
I don't think that you can do that with the default rules provided by CUrlManager. But it's possible by using custom rules. For more information about their implementation see here.
In this case you should insert the following line in rules array in your config file:
array('class' => 'application.components.CustomRule'),
And you CustomRule file should look like:
class CustomRule extends CBaseUrlRule {
public function createUrl($manager, $route, $params, $ampersand) {
if ($route === 'user/admin') {
if ($params['lvl'] == 2) {
return 'user/admin';
} else if ($params['lvl'] == 3) {
return 'user/employees';
}
}
return FALSE;
}
public function parseUrl($manager, $request, $pathInfo, $rawPathInfo) {
if ('user/admins' == $pathInfo) {
$_GET['lvl'] = 2;
return 'user/admin';
} else if ('user/employees' == $pathInfo) {
$_GET['lvl'] = 3;
return 'user/admin';
}
return FALSE;
}
}
Variables are simply mapped after the action definition and separated with a slash:
'rules' => array(
'user/admins' => 'user/admin/lvl/2',
'user/employees' => 'user/admin/lvl/3'
);
You can now acces url user/admins which will be routed to user controller and action admin with a $_GET variable "lvl" and value 2. Note that the value then will be a string instead of an integer, but you can easily cast it.

Categories