Yii2: How to redirect on invalid routes? - php

Instead of showing an error page I'd like to redirect the user to the start page in general, if the route is invalid (there is no controller/action like requested). To make it sound the redirection should only happen on 'normal' page requests. Though AJAX calls can be invalid as well, I don't want to send a redirect to the browser here.
How can I do this efficiently?

You can override ErrorAction class and will implement necessary logic. For example:
class MyErrorAction extends \yii\web\ErrorAction
{
public $redirectRoute;
public function run()
{
if (!Yii::$app->getRequest()->getIsAjax()) {
Yii::$app->getResponse()->redirect($this->redirectRoute)->send();
return;
}
return parent::run();
}
}
After it, you should add this action in a controller and configure it
class SiteController
{
public function actions()
{
return [
'error' => [
'class' => MyErrorAction::class
'redirectRoute' => 'site/my-page'
],
];
}
}
and configure the route of error action in the error handler
'errorHandler' => [
'errorAction' => 'site/error',
]

Related

How to POST action REST API in Yii2 Advanced Template

i Have a question for you guys :) I'm trying to add an API on my yii2 advanced template as i want my wordpress website send datas to my yii2 app.
My system : 1) Yii2 advanced template 2) wordpress website 3) my wordpress plugin with vuejs and axios to create a new entry in my yii2 app via API
So what i allready did :
common/config/main.php (as i use AccessController, i added orders/* to allow it)
'as access' => [
'class' => 'mdm\admin\components\AccessControl',
'allowActions' => [
'orders/*',
],
frontend/config/main.php
'components' => [
'request' => [
'parsers' => [
'application/json' => 'yii\web\JsonParser',
],
and (in urlManager array)
['class'=>'yii\rest\UrlRule','controller'=>'Orders'],
[‘class'=>'yii\rest\UrlRule','controller'=>'Contacts']
Then my controller :
<?php
namespace frontend\controllers;
use yii\rest\ActiveController; use yii\filters\auth\HttpBasicAuth;
class OrdersController extends ActiveController {
public $modelClass = 'common\models\Vctorders';
public function behaviors()
{
$behaviors = parent::behaviors();
// remove authentication filter
$auth = $behaviors['authenticator'];
unset($behaviors['authenticator']);
// add CORS filter
$behaviors['corsFilter'] = [
'class' => \yii\filters\Cors::className(),
];
// re-add authentication filter
$behaviors['authenticator'] = $auth;
// avoid authentication on CORS-pre-flight requests (HTTP OPTIONS method)
$behaviors['authenticator']['except'] = ['options'];
return $behaviors;
}
public function actions()
{
$actions = parent::actions();
unset($actions['create']);
unset($actions['update']);
unset($actions['delete']);
unset($actions['view']);
//unset($actions['index']);
return $actions;
}
protected function verbs(){
return [
'create' => ['POST'],
'new' => ['POST'],
'update' => ['PUT', 'PATCH','POST'],
'delete' => ['DELETE'],
'view' => ['GET'],
//'index'=>['GET'],
];
}
public function actionCreate()
{
$model = new Vctorders();
$model->date_creation = date('Y-m-d H:i:s',strtotime('now'));
$model->etat = 0;
if($model->save()){
return 'OK';
} else{
return 'error';
}
}
}
So, i a use Postman with : http://localhost:8888/SD/sdms/orders/ i get a record, no problem
But when i do a POST with :
http://localhost:8888/SD/sdms/orders/create?livre=L'Arbre Musicien&langue=Allemand&nom=Perroud&prenom=LIttledave&nombre=2&npa=1221&pays=suisse&accept_pc=1&mail=post#post.ch&etat=1&message=lbablalbal&tel=01201201212
the answer is
{"name":"Exception","message":"Class 'frontend\\controllers\\Vctorders' not found","code":0,"type":"Error","file":"/Applications/MAMP/htdocs/SD/sdms/frontend/controllers/OrdersController.php","line":58,"stack-trace":["#0 [internal function]: frontend\\controllers\\OrdersController->actionCreate()","#1 /Applications/MAMP/htdocs/SD/sdms/vendor/yiisoft/yii2/base/InlineAction.php(57): call_user_func_array(Array, Array)","#2 /Applications/MAMP/htdocs/SD/sdms/vendor/yiisoft/yii2/base/Controller.php(157): yii\\base\\InlineAction->runWithParams(Array)","#3 /Applications/MAMP/htdocs/SD/sdms/vendor/yiisoft/yii2/base/Module.php(528): yii\\base\\Controller->runAction('create', Array)","#4 /Applications/MAMP/htdocs/SD/sdms/vendor/yiisoft/yii2/web/Application.php(103): yii\\base\\Module->runAction('orders/create', Array)","#5 /Applications/MAMP/htdocs/SD/sdms/vendor/yiisoft/yii2/base/Application.php(386): yii\\web\\Application->handleRequest(Object(yii\\web\\Request))","#6 /Applications/MAMP/htdocs/SD/sdms/frontend/web/index.php(17): yii\\base\\Application->run()","#7 {main}"]}
The problem is you are sending your request in POSTMAN as a GET REQUEST, and your ACTION in the CONTROLLER expects a POST REQUEST.
In your POSTMAN client at the left side, there is a selector where you can choose what kind of request you are doing.
The second error you get is because once you remove the requisite of POST, the GET request enters the action and goes here:
} elseif (!\Yii::$app->request->isPost) {
$model->load(Yii::$app->request->get());
}
And tries to load the model with the data in the GET parameters, but fails as there is no way the default $model->load() knows how to map the data in your GET petition.
In any case (GET or POST) the $model->load() will not work, as if you check the load() function you will find that it searches for the Object modal name inside the array to load the parameters, so you must do like:
http://localhost:8888/SD/sdms/orders/create?Orders%5Blivre%5=L'Arbre
But for each parameter, the strange characters you see in the result of stringify ['Orders' => ['livre' => 'Larbre']].

Prestashop - REST endpoints for my module

I'm developing Prestashop module, it will export customer data and orders, it will contain hooks for customer synchronization, cart and order events - generally module which will be an integration with CRM-like service.
My module contains it's own views, made in vue.js - single page, async. There are register, login, settings, etc. pages. Communication with backend is made by GET/POST requests on {baseUrl}/mymodule/actionname routes and simple json responses which vue views depend on. Simply I need to create REST endpoints for my module, something like examples below.
Wordpress custom RestApi:
class RestApi
{
public function __construct()
{
add_action('rest_api_init', array(get_class($this),
'register_endpoints'));
}
public static function register_endpoints()
{
register_rest_route('mymodule', '/login', array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array('RestApi', 'login' ),
));
}
}
SugarCRM custom RestApi:
class ModuleRestApi extends SugarApi
{
public function registerApiRest()
{
return [
'moduleLogin' => [
'reqType' => 'POST',
'noLoginRequired' => true,
'path' => [
'mymodule', 'login'
],
'method' => 'login'
],
];
}
}
I cannot find similar solution in PrestaShop, there is no word about custom endpoints in presta docs, I tried to use FrontModuleControllers with friendly url's but it doesn't seem to work for me, it throws a lot of stuff in response which is useless for me and when I try to override init() method it requires a lot of stuff too to actually initiate the controller. I need simple REST solution where I can put logic for recieving data from my views, pass it to my CRM service and return json responses to my views. I don't need any more templates or views rendering, just routing for cummunication.
PrestaShop doesn't support this out of the box. You can however do it with a module and front controllers.
This is a basic example of doing it.
1. Module to register friendly URLs
class RestApiModule extends Module
{
public function __construct()
{
$this->name = 'restapimodule';
$this->tab = 'front_office_features';
$this->version = '1.0';
parent::__construct();
}
public function install()
{
return parent::install() && $this->registerHook('moduleRoutes');
}
public function hookModuleRoutes()
{
return [
'module-restapimodule-login' => [
'rule' => 'restapimodule/login',
'keywords' => [],
'controller' => 'login',
'params' => [
'fc' => 'module',
'module' => 'restapimodule'
]
]
];
}
}
2. Create an abstract REST controller
Create an abstract controller so that actual endpoints can extend from it. Create it in your module controllers folder lets name it AbstractRestController.php
abstract class AbstractRestController extends ModuleFrontController
{
public function init()
{
parent::init();
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
$this->processGetRequest();
break;
case 'POST':
$this->processPostRequest();
break;
case 'PATCH': // you can also separate these into their own methods
case 'PUT':
$this->processPutRequest();
break;
case 'DELETE':
$this->processDeleteRequest();
break;
default:
// throw some error or whatever
}
}
abstract protected function processGetRequest();
abstract protected function processPostRequest();
abstract protected function processPutRequest();
abstract protected function processDeleteRequest();
}
3. Create an actual front controller
Create the front controller in your module controllers/front folder and name it login.php.
require_once __DIR__ . '/../AbstractRestController.php';
class RestApiModuleLoginModuleFrontController extends AbstractRestController
{
protected function processGetRequest()
{
// do something then output the result
$this->ajaxDie(json_encode([
'success' => true,
'operation' => 'get'
]));
}
protected function processPostRequest()
{
// do something then output the result
$this->ajaxDie(json_encode([
'success' => true,
'operation' => 'post'
]));
}
protected function processPutRequest()
{
// do something then output the result
$this->ajaxDie(json_encode([
'success' => true,
'operation' => 'put'
]));
}
protected function processDeleteRequest()
{
// do something then output the result
$this->ajaxDie(json_encode([
'success' => true,
'operation' => 'delete'
]));
}
}
Install the module and now you can hit http://example.com/restapimodule/login and depending on the request type it's going to do whatever you want and you get back JSON response.
To add more endpoints add another module-restapimodule-endpointname entry into hookModuleRoutes array and a front controller that extends from AbstractRestController.
If you also want proper response codes etc. you're going to have to set headers with native php functions as PrestaShop afaik doesn't have any utilities to do it for you or use some kind of library.
Same goes for any other headers you might want to set such as content-type (by default it is text/html).
It is possible to use the Prestashop Webservice, that allows to add resources from modules. This solution could save some time in terms of standards and security.
The documentation regarding module resources in Prestashop Webservice is in this link:
https://webkul.com/blog/creating-prestashop-module-webservice-api/

How to perform additional tasks in Yii2 restful controller?

Here is how my RESTful controller looks like.
<?php
namespace backend\controllers;
use yii\rest\Controller;
use yii;
use yii\web\Response;
use yii\helpers\ArrayHelper;
class UserController extends \yii\rest\ActiveController
{
public function behaviors()
{
return ArrayHelper::merge(parent::behaviors(), [
[
'class' => 'yii\filters\ContentNegotiator',
'only' => ['view', 'index'], // in a controller
// if in a module, use the following IDs for user actions
// 'only' => ['user/view', 'user/index']
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
'languages' => [
'en',
'de',
],
],
[
'class' => \yii\filters\Cors::className(),
'cors' => [
'Origin' => ['*'],
'Access-Control-Request-Method' => ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'],
'Access-Control-Request-Headers' => ['*'],
'Access-Control-Allow-Credentials' => true,
'Access-Control-Max-Age' => 86400,
],
],
]);
}
public $modelClass = 'backend\models\User';
public function actions()
{
}
public function sendMail(){
//Need to call this function on every create
//This should also have the information about the newly created user
}
}
It works very well with default behavior but it is not very practical that you will just create the user and exit. You need to send email with verification link SMS etc, may be update some other models based on this action.
I do not want to completely override the create method as it works well to save data and return back JSON.
I just want to extend its functionality by adding a callback kind of a function which can accept the newly created user and send an email to the person.
Take a look here: https://github.com/githubjeka/yii2-rest/blob/bf034d26f90faa3023e5831d1eb165854c5c7aaf/rest/versions/v1/controllers/PostController.php
As you can see this is using the prepareDataProvider to change the normal way the index action is using. This is very handy. You can find the definition of prepareDataProvider here: http://www.yiiframework.com/doc-2.0/yii-rest-indexaction.html#prepareDataProvider()-detail
Now as you can see there are 2 additional methods afterRun() and beforeRun() that are also available for the create action. http://www.yiiframework.com/doc-2.0/yii-rest-createaction.html
You may be able to use these 2 functions and declare them similar to prepareDataProvider to do more things like sending an email. I have not tried them myself but I believe that should be the way to go.
The easiest way would be getting benefit from afterSave() method in your model. This method will be called after each save process.
public function afterSave($insert, $changedAttributes) {
//calling a send mail function
return parent::afterSave($insert, $changedAttributes);
}
Another advantage of this method is the data you have stored in your object model. For example accessing email field:
public function afterSave($insert, $changedAttributes) {
//calling a send mail function
\app\helpers\EmailHelper::send($this->email);
return parent::afterSave($insert, $changedAttributes);
}
the value of $this->email is containing the saving value into database.
Note
You can benefit from $this->isNewRecord to detect whether the model is saving new record into database or updating an existing record. Take a look:
public function afterSave($insert, $changedAttributes) {
if($this->isNewRecord){
//calling a send mail function
\app\helpers\EmailHelper::send(**$this->email**);
}
return parent::afterSave($insert, $changedAttributes);
}
Now, it only sends mail if new record is being saved into database.
Please note that you can also benefit from Yii2's EVENTS.
As official Yii2's documentation:
This method is called at the end of inserting or updating a record.
The default implementation will trigger an EVENT_AFTER_INSERT event when $insert is true, or an EVENT_AFTER_UPDATE event if $insert is false. The event class used is yii\db\AfterSaveEvent. When overriding this method, make sure you call the parent implementation so that the event is triggered.

Yii not detecting camel case actions

Yii is giving me 404 Error if I declare an action like this:
SiteController.php
public function actionRegisterUser()
This is how I call it in the main.php
['label' => 'Register User', 'url' => ['/site/RegisterUser']],
I tried several different combinations. The only combination that will work is this naming convention in both places:
public function actionRegisteruser
'url' => ['/site/registeruser']
I used to work on another Yii project (Yii 1.0) and I could name my actions in camel case and call them without any problem. Do I need to turn on some sort of setting to do this?
I also tried playing with the rules of the Controller but that didn't solve anything.
In some cases you need camelcase link. For example, for SEO purposes (keep inbound links). You could create rewrite rule on web server side or add inline rule to URL manager on app side. Example:
'urlManager' => [
'rules' => [
'<controller:RegisterUser>/<action:\w+>'=>'register-user/<action>',
],
],
Also it's possible to write custom URL rule. Example:
namespace app\components;
use yii\web\UrlRuleInterface;
use yii\base\Object;
class CarUrlRule extends Object implements UrlRuleInterface
{
public function createUrl($manager, $route, $params)
{
if ($route === 'car/index') {
if (isset($params['manufacturer'], $params['model'])) {
return $params['manufacturer'] . '/' . $params['model'];
} elseif (isset($params['manufacturer'])) {
return $params['manufacturer'];
}
}
return false; // this rule does not apply
}
public function parseRequest($manager, $request)
{
$pathInfo = $request->getPathInfo();
if (preg_match('%^(\w+)(/(\w+))?$%', $pathInfo, $matches)) {
// check $matches[1] and $matches[3] to see
// if they match a manufacturer and a model in the database
// If so, set $params['manufacturer'] and/or $params['model']
// and return ['car/index', $params]
}
return false; // this rule does not apply
}
}
And use the new rule class in the [[yii\web\UrlManager::rules]] configuration:
[
// ...other rules...
[
'class' => 'app\components\CarUrlRule',
// ...configure other properties...
],
]
You need to specify your action like this ['/site/register-user']. As documentation says about Inline Actions:
index becomes actionIndex, and hello-world becomes actionHelloWorld

Route in Yii2 doesn't work

I cant to make request to my controller in Yii2
I have controller /controllers/IndexController.php
class IndexController extends Controller
{
public function actionIndex()
{
return $this->render('index');
}
public function actionCreateAccount()
{
return Json::encode(array('status'=>'ok'));
}
}
In my config/web.php
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false
],
When I try to make request http://account.ll/Index/CreateAccount
I receive an error
Unable to resolve the request "Index/CreateAccount".
When I try to make request http://account.ll/Index I got the same error
Whats wrong?
It should be:
http://account.li/index/index or just http://account.li/index (because index is the default action). If the default controller is IndexController, you can access it like that - http://account.li/.
http://account.li/index/create-account
Controller and action names in actual url should be in lowercase. Action names containing more than one word are transformed with hyphens in between words.
Try to change
public function actionCreateAccount()
to
public function actionCreateaccount()
Just need to change in url from http://account.ll/Index/CreateAccount to http://account.ll/Index/create-account

Categories