Yii2 restful - cannot use the costom action in the ActiveController - php

I am new on Yii2 and I'm trying to build a restful API.
I need create few custom actions for getting data.
When I call https://localhost/api/v1/record/GetAll using GET method, which will return 404.
Here is the log
[yii\web\HttpException:404] yii\base\InvalidRouteException: Unable to resolve the request: v1/record/getall in /home/demo/vendor/yiisoft/yii2/base/Controller.php:149
Stack trace:
#0 /home/demo/vendor/yiisoft/yii2/base/Module.php(552): yii\base\Controller->runAction()
#1 /home/demo/vendor/yiisoft/yii2/web/Application.php(103): yii\base\Module->runAction()
#2 /home/demo/vendor/yiisoft/yii2/base/Application.php(384): yii\web\Application->handleRequest()
#3 /home/demo/api/web/index.php(18): yii\base\Application->run()
#4 {main}
Next yii\web\NotFoundHttpException: Page not found. in /home/demo/vendor/yiisoft/yii2/web/Application.php:115
Stack trace:
#0 /home/demo/vendor/yiisoft/yii2/base/Application.php(384): yii\web\Application->handleRequest()
#1 /home/demo/api/web/index.php(18): yii\base\Application->run()
#2 {main}
Here is the project directory structure
api
-config
--main.php
-modules
--v1
---controllers
----RecordController.php
---Module.php
-runtime
-web
frontend
backend
common
...
Here is my code
api\config\main.php
...
'urlManager' => [
'class'=>'yii\web\UrlManager',
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['v1/record'=>'v1/record'],
//'prefix' => 'record/<id:\\w+>',
'pluralize' => false,
'extraPatterns' => [
'GET,HEAD getAll'=>'getall',
'OPTIONS getAll'=>'options',
],
],
],
],
...
api\modules\v1\controllers\RecordController.php
namespace api\modules\v1\controllers;
use yii\rest\ActiveController;
use common\components\JsonSerializer;
use yii\filters\ContentNegotiator;
use yii\web\Response;
use yii\web\ForbiddenHttpException;
use frontend\models\Record;
class RecordController extends ActiveController
{
public $modelClass = 'frontend\models\Record';
public $serializer = [
'class' => 'common\components\JsonSerializer',
'collectionEnvelope' => 'items',
];
protected function verbs(){
return [
'getAll' => [ 'GET' ],
];
//return $verbs;
}
public function actions()
{
$actions = parent::actions();
$actions['options'] = [
'class' => 'yii\rest\OptionsAction',
];
return $actions;
}
//the custom action - getAll
public function actiongetAll(){
$result = Record::find()
->all();
return $result;
}
}
The default actions[index,view,update,create,delete] are OK, but the custom action cannot get the data.
How can I fix it.
Thanks.

First I would change the action function in your controller to
public function actionGetAll()
Then your URL will be https://localhost/api/v1/record/get-all
and please use this get-all in your rules and verbs.
If you override the url-rule I think you have to use this
'GET,HEAD getAll'=>'v1/record/get-all',

Related

Setting Routes for RestApi in Yii2

I am trying to make rest api with my methods.
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['ApiController'],
'patterns' => [
'PUT,PATCH api/{id}/update' => 'update',
'DELETE api/{id}/delete' => 'delete',
'GET,HEAD api/{id}' => 'get',
'POST api/{id}/create' => 'create',
'GET,HEAD' => 'api/index',
'{id}' => 'options',
'' => 'options',
]
],
Api controller:
/**
* Displays homepage.
*
* #return string
*/
public function actionIndex()
{
// $id = Yii::$app->request->getQueryParam("id"); //
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return "ok";
}
/**
* Displays homepage.
*
* #return string
*/
public function actionGet($id)
{
// $id = Yii::$app->request->getQueryParam("id"); //
Yii::$app->response->format = \yii\web\Response::FORMAT_JSON;
return "get";
}
Url api returns index action, but url api/1 doesn't return get action.
How to configure routing?
If you are ok with the default actions provided by yii you can simplify your code quite a bit to make it work.
Configure the response type on the application configuration, then you won't need to do it in each method.
Remove the 'patterns' element from your rules, yii automatically matches the patterns that you are trying to use.
Decide if you want to pluralize your rules or not, if you don't want to pluralize them you need to add 'pluralize' => false to your configuration rules.
web.config
// configure json response globally
'response' => [
'format' => Response::FORMAT_JSON,
'formatters' => [
Response::FORMAT_JSON => [
'class' => '\yii\web\JsonResponseFormatter',
'prettyPrint' => YII_DEBUG,
'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,
]
],
],
// Add ApiController rules
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => 'api',
// uncoment the next line if you want to request '/api/2' instead of '/apis/2'
// 'pluralize' => false
],
// ... more rules here
]
ApiController
<?php
namespace app\controllers;
use yii\rest\Controller;
class ApiController extends Controller
{
public function actionIndex()
{
return 'Api index action';
}
public function actionView($id)
{
return "Get item $id";
}
}
Using the configuration provided you can request the index route sending a GET request to the /apis endpoint, to control the result customize actionIndex, you can provide a dataProvider as the response and the formatter element will deal with it correctly.
To request one element of the collection, send a GET request to the /apis/5 endpoint, where 5 is just an example $id, if you return a model, the formatter will deal with it using the fields attribute of the model.
If you want to use endpoints like in your question, i.e. without the plural form, uncomment the pluralize line on the example, the endpoints will be /api and /api/5.
There are multiple examples of this on the official documentation, the quick start and building a REST API pages make for some good reading and are packed with examples.
Personally I would recommend not naming a controller ApiController, it seems confusing, your API probably has api already on the url so you will end up with urls like https://api.mydomain.com/api/5

Yii2: Simultaneous authentication between frontend and API

I've turned the backend into an API. And the API controllers have HttpBasicAuth type authentication.
The problem is that even after authentication in the frontend, whenever a request is made to the API, the authentication window appears.
How can I do so that when the user authenticates in the frontend, is not requested again the username and password of access when a request is made to the API?
Example a controller in API:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => CompositeAuth::className(),
'authMethods' => [
[
'class' => HttpBasicAuth::className(),
'auth' => function($username, $password) {
$out = null;
$user = \common\models\User::findByUsername($username);
if ($user != null) {
if ($user->validatePassword($password)) $out = $user;
}
return $out;
}
],
],
];
return $behaviors;
}
}
It is called sharing sessions. It also depends on if your tier apps (frontend and api) are both in the same domain. If it is, configure your frontend and api settings (<app>/frontend/config/main.php and <app>/api/config/main.php) as follow:
'components' => [
...
'request' => [
'csrfParam' => '_csrf-shared',
],
...
'user' => [
'identityClass' => 'common\models\User',
'enableAutoLogin' => true,
'identityCookie' => ['name' => '_identity-shared', 'httpOnly' => true],
],
...
'session' => [
'name' => 'advanced-shared',
],
...
It means you save cookies and session with the same name, so that when you login in frontend, and go to backend/api, the backend side fetches same cookies, therefore you'll be detected as authenticated user.
Here one important note, in order to enableAutoLogin work for both tiers, you should set same cookieValidationKey for both main-local.php settings. You can just set them manually, or edit init.php file to generate one cookieValidationKey for all tiers. (Just make sure you know what you're doing).
By the way, I think it's not a good idea to make simultaneous authentication between frontend and api. If it's frontend and backend then it's still bearable, but api interaction is different compared to frontend.
I suggest to use headers like Authorization: Bearer <token>.. You can get more information about it here Yii2 Rest Authentication
Update
I assume this is what you need.. Create a class, i.e. ApiAuth in common/components folder and paste the following code:
<?php
namespace common\components;
use yii\filters\auth\HttpBasicAuth;
class ApiAuth extends HttpBasicAuth
{
/**
* #inheritdoc
*/
public function authenticate($user, $request, $response)
{
if ($user->identity) {
return $user->identity;
}
return parent::authenticate($user, $request, $response);
}
}
This class extends from yii\filters\auth\HttpBasicAuth. Before calling a browser prompt it checks whether user->dentity is populated. If so, no promt is required.
In your Controller behavior replace HttpBasicAuth with ApiAuth class:
use common\components\ApiAuth;
...
'authMethods' => [
[
'class' => ApiAuth::className(),
'auth' => function($username, $password) {
...
As the user is already authenticated, then I just set the "AccessControl" for connected users. If they are not connected they will receive code 403 instead of 401.
This solves the problem:
class CategoryController extends ActiveController
{
public $modelClass = 'api\models\Category';
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['access'] = [
'class' => \yii\filters\AccessControl::className(),
'rules' => [
[
'allow' => true,
'roles' => ['#'],
],
],
];
return $behaviors;
}
}

Yii2 own filter "An Error occurred while handling another error"

Good afternoon.
I have created my own filter in Yii2 basic project:
class LanguageFilter extends Behavior
{
/**
* #var string
*/
public $shortLanguage;
/**
* Declares event handlers for the [[owner]]'s events.
* #return array events (array keys) and the corresponding event handler methods (array values).
*/
public function events()
{
return [Controller::EVENT_BEFORE_ACTION => 'beforeAction'];
}
/**
* #param ActionEvent $event
* #return bool
* #throws BadRequestHttpException
*/
public function beforeAction($event)
{
if (null === $this->shortLanguage){
throw new BadRequestHttpException('Parameter "shortLanguage" is not defined.');
}
$langModel = Language::find()->where([
'shortName' => $this->shortLanguage
])->one();
if (null === $langModel){
throw new BadRequestHttpException('There are not any data for language: '.$this->shortLanguage);
}
Yii::$app->language = $langModel->locale;
return true;
}
}
And use it in controller:
class BaseController extends Controller
{
/**
* #var string
*/
protected $shortLanguage;
/**
* Initialize.
*/
public function init()
{
$this->defaultAction = 'index';
$this->layout = '#app/views/layouts/base';
$this->shortLanguage = Yii::$app->request->get('shortLanguage');
$this->view->params['shortLanguage'] = $this->shortLanguage;
$this->view->params['pages'] = Page::getMenu();
parent::init();
}
/**
* #inheritdoc
*/
public function behaviors()
{
return [
'language' => [
'class' => LanguageFilter::class,
'shortLanguage' => $this->shortLanguage
],
'access' => [
'class' => AccessControl::class,
'rules' => [
[
'allow' => true,
'actions' => ['reg', 'login'],
'roles' => ['?'],
],
[
'allow' => true,
'actions' => ['logout'],
'roles' => ['#'],
],
[
'allow' => true,
'actions' => ['index', 'about', 'contact'],
'roles' => ['?', '#'],
],
[
'allow' => true,
'actions' => ['error'],
'roles' => ['?', '#'],
],
],
],
'verbs' => [
'class' => VerbFilter::class,
'actions' => [
'index' => ['get'],
'logout' => ['post', 'get'],
],
],
];
}
/**
* #inheritdoc
*/
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
];
}
}
In web configuration file error handler:
'components' => [
...
...
'errorHandler' => [
'errorAction' => 'base/error',
],
...
...
]
But when the filter throws an exception, the error handler displays an error message WITHOUT TEMPLATE !!! And with another mistake.
An Error occurred while handling another error:
yii\web\BadRequestHttpException: There are not any data for language: fr in C:\xampp\htdocs\pack-develop\filters\LanguageFilter.php:44
Stack trace:
#0 [internal function]: app\filters\LanguageFilter->beforeAction(Object(yii\base\ActionEvent))
#1 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Component.php(627): call_user_func(Array, Object(yii\base\ActionEvent))
#2 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Controller.php(274): yii\base\Component->trigger('beforeAction', Object(yii\base\ActionEvent))
#3 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\web\Controller.php(164): yii\base\Controller->beforeAction(Object(yii\web\ErrorAction))
#4 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Controller.php(155): yii\web\Controller->beforeAction(Object(yii\web\ErrorAction))
#5 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Module.php(528): yii\base\Controller->runAction('error', Array)
#6 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\web\ErrorHandler.php(108): yii\base\Module->runAction('base/error')
#7 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\ErrorHandler.php(111): yii\web\ErrorHandler->renderException(Object(yii\web\BadRequestHttpException))
#8 [internal function]: yii\base\ErrorHandler->handleException(Object(yii\web\BadRequestHttpException))
#9 {main}
Previous exception:
yii\web\BadRequestHttpException: There are not any data for language: fr in C:\xampp\htdocs\pack-develop\filters\LanguageFilter.php:44
Stack trace:
#0 [internal function]: app\filters\LanguageFilter->beforeAction(Object(yii\base\ActionEvent))
#1 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Component.php(627): call_user_func(Array, Object(yii\base\ActionEvent))
#2 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Controller.php(274): yii\base\Component->trigger('beforeAction', Object(yii\base\ActionEvent))
#3 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\web\Controller.php(164): yii\base\Controller->beforeAction(Object(yii\base\InlineAction))
#4 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Controller.php(155): yii\web\Controller->beforeAction(Object(yii\base\InlineAction))
#5 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Module.php(528): yii\base\Controller->runAction('index', Array)
#6 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\web\Application.php(103): yii\base\Module->runAction('home/index', Array)
#7 C:\xampp\htdocs\pack-develop\vendor\yiisoft\yii2\base\Application.php(386): yii\web\Application->handleRequest(Object(yii\web\Request))
#8 C:\xampp\htdocs\pack-develop\web\index.php(33): yii\base\Application->run()
#9 {main}
The strange thing is that when other filters (AccessControl, VerbFilter) issue exceptions, the error handler displays an error message through the view template normally.
Please help me to understand the reason of it!
It's a filter not behavior, I have modified your filter that works.
use Yii;
use yii\web\Controller;
use yii\base\ActionFilter;
use yii\web\BadRequestHttpException;
class LanguageFilter extends ActionFilter
{
/**
* #var string
*/
public $shortLanguage;
/**
* #param ActionEvent $action
* #return bool
* #throws BadRequestHttpException
*/
public function beforeAction($action)
{
if ($this->shortLanguage === null && !$action instanceof yii\web\ErrorAction)) {
throw new BadRequestHttpException('Parameter "shortLanguage" is not defined.');
}
$langModel = Language::find()->where([
'shortName' => $this->shortLanguage,
])->one();
if ($langModel === null && !$action instanceof yii\web\ErrorAction) {
throw new BadRequestHttpException('There are not any data for language: ' . $this->shortLanguage);
}
Yii::$app->language = $langModel->locale;
return true; //return parent::beforeAction($action);
}
}

Yii2 Allow action access only by POST request

I have created a yii2 controller, which meant to display statistics from database, for a specific user. There is a ajax request, performed to my controller action, but i want to restrict to allow only POST method for this action.
<?php
use yii\web\Response;
namespace app\controllers;
use Yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\Response;
use yii\filters\VerbFilter;
use app\models\StatsModel;
class DataController extends Controller
{
/**
* {#inheritdoc}
*/
public function behaviors()
{
return [
[
'class' => 'yii\filters\ContentNegotiator',
'only' => ['stats'],
'formats' => [
'application/json' => Response::FORMAT_JSON
],
],
];
}
/**
* {#inheritdoc}
*/
public function actions()
{
return [
'error' => [
'class' => 'yii\web\ErrorAction',
],
'captcha' => [
'class' => 'yii\captcha\CaptchaAction',
'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
],
];
}
public function actionStats()
{
//how can i restrict this action to only POST http method?
return StatsModel::find()->all();
}
}
I need to restrict actionStats() to HTTP Post method only.
Usually you'd allow post only adding something like this to your behaviors:
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'stats' => ['POST'],
],
],
And if you are accessing this action only through ajax, in your action you could add the following check
if(Yii::$app->request->isAjax)
{
//in case you want to return JSON formatted response
Yii:$app->response->format = Response::FORMAT_JSON;
}
You can check this cookbook as well:
https://books.google.com.sv/books?id=CJrcDgAAQBAJ&pg=PA193&lpg=PA193&dq=yii2+isajax&source=bl&ots=lRFEiPbN3K&sig=MFGo7VostVkxNZDbXGemXrm-qA8&hl=es&sa=X&ved=0ahUKEwjE9ZXSh7nbAhWPk1kKHW3wCeEQ6AEIYTAF#v=onepage&q=yii2%20isajax&f=false
Finally, you can just make the check for post in your action like this
public function actionStats()
{
if(Yii::$app->request->isPost())
{
//your logic here
return StatsModel::find()->all();
}
else
//throw an exception or return false
}

"NetworkError: 405 Method Not Allowed" in YII2 rest API

I am working on Yii2 rest API, When I call create action of enquiryontroller then I am getting this error : "NetworkError: 405 Method Not Allowed".
And also I go through YII2 documentation but not able to trace my issue.
Please check and revert, it will be a great help.
Here is controller code that is EnquiryController.php :
<?php
namespace frontend\controllers;
use Yii;
use common\models\Enquiry;
use yii\filters\ContentNegotiator;
use yii\web\Response;
use yii\filters\AccessControl;
use yii\rest\ActiveController;
use yii\filters\auth\HttpBearerAuth;
use yii\filters\VerbFilter;
use yii\data\ActiveDataProvider;
class EnquiryController extends ActiveController
{
/**
* #inheritdoc
*/
public $modelClass = 'common\models\Enquiry';
public $serializer = [
'class' => 'yii\rest\Serializer',
'collectionEnvelope' => 'items',
];
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['authenticator'] = [
'class' => HttpBearerAuth::className(),
];
$behaviors['contentNegotiator'] = [
'class' => ContentNegotiator::className(),
'formats' => [
'application/json' => Response::FORMAT_JSON,
],
];
return $behaviors;
}
public function actions()
{
$actions = parent::actions();
// disable the "delete" and "create" actions
unset($actions['create']);
unset($actions['delete'], $actions['view']);
unset($actions['index']);
// customize the data provider preparation with the "prepareDataProvider()" method
return $actions;
}
public function actionCreate()
{
$model = new Enquiry();
return Yii::$app->getRequest()->getBodyParams();
if ($model->load(Yii::$app->getRequest()->getBodyParams(), '') && $model->validate()) {
$model->slug = \common\components\Helper::slugify($model->title);
$model->user_id = Yii::$app->user->id;
$model->save();
//mail functionality
return true;
}
return $model;
}
}
and code in config/main-local.php :
'urlManager' => [
'class' => 'yii\web\UrlManager',
'baseUrl' => $baseUrl,
'enablePrettyUrl' => true,
'showScriptName' => false,
//'enableStrictParsing' => true,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' =>['api'], 'pluralize'=>true],
],
],
],
'as access' => [
'class' => 'mdm\admin\components\AccessControl',
'allowActions' => [
'site/*',
'api/login',
'profile/*',
'api/activate-user',
'api/contact',
'home/*',
'post/*',
'pages/*',
'categories/*',
'guestbook/*',
'faq/*',
'news/*',
'events/*',
'enquiry/*',
'partners/*',
'api/signup'// add or remove allowed actions to this list
]
],
Have a look to this guide
// disable the "delete" and "create" actions
unset($actions['delete'], $actions['create']);
because in your code you disable the create, delete, view and index action
public function actions()
{
$actions = parent::actions();
// disable the "delete" and "create" actions ?????
unset($actions['create']); ////????
unset($actions['delete'], $actions['view']); /// ????
unset($actions['index']); ////????
// customize the data provider preparation with the "prepareDataProvider()" method
return $actions;
}

Categories