How to use Yii2 debugger when developing RESTful application? - rest

Like in the guide, I have created RESTful controller UserController.
namespace app\controllers;
use yii\rest\ActiveController;
class UserController extends ActiveController
{
public $modelClass = 'app\models\User';
}
And when I make request GET /users, it works.
But I have no idea what queries does Yii2 execute behind the scene, and I do not know how long do they last.
Can I somehow use Yii2 debugger to debug and profile queries ? If not, what is the alternative for this ?

To see requests in Debugger for APIs
Add this in you API config file -
$config = [
'id' => 'app-api',
'basePath' => dirname(__DIR__),
'bootstrap' => ['log'],
......
....
]
if (YII_ENV_DEV) {
// configuration adjustments for 'dev' environment
$config['bootstrap'][] = 'debug';
$config['modules']['debug'] = [
'class' => 'yii\debug\Module',
'allowedIPs' => ['your_ip_address'], // accessible to this ip address only
];
$config['bootstrap'][] = 'gii';
$config['modules']['gii'] = [
'class' => 'yii\gii\Module',
];
}
return $config;
In web/index.php of API folder -
defined('YII_DEBUG') or define('YII_DEBUG', true);
defined('YII_ENV') or define('YII_ENV', 'dev');
Access debugger by below URL-
http://localhost/yii2-app/api/web/debug/default/view
To change API's default actions like - create,update,view,index,delete write below code in controller
/* Declare actions supported by APIs (Added in api/modules/v1/components/controller.php too) */
public function actions(){
$actions = parent::actions();
unset($actions['create']);
unset($actions['update']);
unset($actions['delete']);
unset($actions['view']);
unset($actions['index']);
return $actions;
}
/* Declare methods supported by APIs */
protected function verbs(){
return [
'create' => ['POST'],
'update' => ['PUT', 'PATCH','POST'],
'delete' => ['DELETE'],
'view' => ['GET'],
'index'=>['GET'],
];
}
public function actionCreate(){echo "in create action";die;}

Related

Using OAuth2 and ZF3-MVC to protect REST API

I'm trying to get https://github.com/zfcampus/zf-oauth2 working with my ZF3-MVC Application (ok, one solution could be to wait Apigility update).
I have successfully implemented the oauth2-server-php (https://github.com/bshaffer/oauth2-server-php), its zf-oauth2 module support (https://github.com/zfcampus/zf-oauth2) and adapted zf-oauth2 client for ZF3 (https://github.com/API-Skeletons/zf-oauth2-client).
However, I'm totaly stuck now trying to protect my API y following zf-oauth2 module's recommandation:
You can protect your API using the following code (for instance, at the top of a controller):
if (!$this->server->verifyResourceRequest(OAuth2Request::createFromGlobals()))
{
// Not authorized return 401 error
$this->getResponse()->setStatusCode(401);
return;
}
where $this->server is an instance of OAuth2\Server (see the AuthController.php).
I've read this post (Using ZF2 Oauth2) but it's not compliant with ZF3. I guess there's a more efficient way rather than copying/pasting zf-oauth2 module's controller and factory to instantiate the server from scratch.
Would anyone have a clue on how to implement the instance of OAuth2\Server in my API controller?
I finally did it by my own. As I spent a significant amount time on this and saw that others where also looking for a solution, here is how I did it.
At first, I suggest you read https://docs.zendframework.com/tutorials/in-depth-guide/models-and-servicemanager/ if you're not familiar with Dependency Injection and Factories (this was my case).
module.config.php
// In module/YourModule/config/module.config.php:
namespace YourAppNamespace;
use Zend\ServiceManager\Factory\InvokableFactory;
return [
'controllers' => [
'factories' => [
Controller\YourController::class => Factory\YourControllerFactory::class,
],
],
'service_manager' => [ /** Your Service Manager Config **/ ]
'router' => [ /** Your Router Config */ ]
'view_manager' => [ /** Your ViewManager Config */ ],
];
YourControllerFactory.php
// In module/YourModule/src/Controller/YourControllerFactory.php:
namespace YourAppNamespace\Factory;
use YourAppNamespace\Controller\YourController;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class YourControllerFactory implements FactoryInterface
{
/**
* #param ContainerInterface $container
* #param string $requestedName
* #param null|array $options
*
* #return YourController
*/
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$controllerPluginManager = $container;
$serviceManager = $controllerPluginManager->get('ServiceManager');
// Requires zf-campus/zf-oauth2
$server = $serviceManager->get('ZF\OAuth2\Service\OAuth2Server');
$provider = $serviceManager->get('ZF\OAuth2\Provider\UserId');
return new YourController($server, $provider);
}
}
YourController.php
// In module/YourModule/src/Controller/YourController.php:
namespace YourAppNamespace\Controller;
use ZF\OAuth2\Controller\AuthController;
use OAuth2\Request as OAuth2Request;
use ZF\OAuth2\Provider\UserId\UserIdProviderInterface;
class YourController extends AuthController
{
public function __construct($serverFactory, UserIdProviderInterface $userIdProvider)
{
parent::__construct($serverFactory, $userIdProvider);
}
public function indexAction()
{
$server = call_user_func($this->serverFactory, "oauth");
if (!$server->verifyResourceRequest(OAuth2Request::createFromGlobals())) {
// Failure
$response = $server->getResponse();
return $this->getApiProblemResponse($response);
}
// Success
echo json_encode(array('success' => true, 'message' => 'It works!'));
}
}
Hope it helps!

Injecting variables into Forms __construct function Zend 2

I am trying to prepopulate a form's drop down options via data stored statically in the module.config.php in Zend 2 and am running into a problem which entails:
I try to get the Servicemanager in the __construct() function but it is unavailable.
I move the element declarations to another function within the form class so I can pass variables into it but the view controller cannot find the elements.
I currently call the form via a Servicemanager Invokable. How can I pass these arrays into the form's __construct() function?
Here is the code:
class ILLForm extends Form
{
public function __construct($fieldsetName, $campuses, $ILLTypes, $getFromOptions)
{
parent::__construct('create_ill');
$this
->setAttribute('method', 'post')
->setHydrator(new ClassMethodsHydrator(false))
->setInputFilter(new InputFilter())
;
$ill = new ILLFieldset('ill', $campuses, $ILLTypes, $getFromOptions);
$ill->setName('ill')
->setOptions(array(
'use_as_base_fieldset' => true,
));
$captcha = new Element\Captcha('captcha');
$captchaAdapter = new Captcha\Dumb();
$captchaAdapter->setWordlen(5);
$captcha->setCaptcha($captchaAdapter)
->setLabelAttributes(array('class' => 'sq-form-question-title'))
->setAttribute('class', 'sq-form-field required')
->setLabel("* Captcha")
->setAttribute('title', 'Help to prevent SPAM');
$submit = new Element\Submit('submit');
$submit->setAttribute('value', 'Submit ILL')
->setAttribute('class', 'sq-form-submit')
->setAttribute('title', 'Submit ILL');
$this->add($ill)
->add($captcha)
->add($submit);
}
}
The Indexcontroller Factory that calls the Form:
class IndexControllerFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $controllers)
{
$allServices = $controllers->getServiceLocator();
$sm = $allServices->get('ServiceManager');
$smConfig = $allServices->get('config');
$campuses = $smConfig['campuses'];
$illCategories = $smConfig['ILLCategories'];
$getFromOptions = $smConfig['getFromOptions'];
$controller = new IndexController();
$controller->setCampuses($campuses);
$controller->setILLCategories($illCategories);
$controller->setGetFromOptions($getFromOptions);
$controller->setILLForm($sm->get('ILL-form'));
$controller->setILLFormFilter($sm->get('ILL-form-filter'));
//$controller->setParams($sm->get('params'));
return $controller;
}
}
and the relevant module.config.php excerpt:
'service_manager' => array(
'abstract_factories' => array(
'Zend\Cache\Service\StorageCacheAbstractServiceFactory',
'Zend\Log\LoggerAbstractServiceFactory',
),
'invokables' => array(
'ILL-form-filter' => 'ILL\Form\ILLFormFilter',
'ILL-form' => 'ILL\Form\ILLForm',
),
I ended up taking the form out of the service manager invokables in module.config.php (removed line for 'ILL-form') and call it from the indexControllerFactory.php instead
$create_ill = new Form\ILLForm('create_ill', $campuses, $illCategories, $getFromOptions);
$controller->setILLForm($create_ill);
instead of
$controller->setILLForm($sm->get('ILL-form'));

File Upload in Rest API yii2

my controller file inside api/v1/controller/
class ProfileController extends ActiveController
{
public $modelClass = 'app\models\Profile';
public function behaviors()
{
return [
[
'class' => 'yii\filters\ContentNegotiator',
'only' =>
['index', 'view', 'createnew','update','search'],
'formats' =>
['application/json' => Response::FORMAT_JSON,],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'index' => ['get'],
'view' => ['get'],
'createnew' => ['post'],
'update' => ['put'],
'delete' => ['delete'],
'deleteall' => ['post'],
'search' => ['get']
],
]
];
}
public function actionCreatenew() {
$model = new Profile();
$model->load(Yii::$app->request->post());
$model->asset = UploadedFile::getInstance($model, 'asset');
$name = $model->user_id;
if($model->asset) {
$model->asset->saveAs('uploads/'.$name.'.
'.$model->asset->extension);
$model->asset = $model->asset->name.'.'.
$model->asset->extension;
}
if($model->save()) {
echo json_encode(array('status'=>"Success",
'data'=>$model->attributes),JSON_PRETTY_PRINT);
} else {
echo json_encode(array('status'=>"Failure",
'error_code'=>400,
'errors'=>$model->errors),JSON_PRETTY_PRINT);
}
}
}
When I try to use access this from Postman like:
POST http://localhost/myapp/api/v1/profiles
I get Invalid Parameter – yii\base\InvalidParamException
Response content must not be an array.
What is the issue?? help would be grateful!! Thanks
You can easily receive single / multi-uploaded files using HTTP POST with form-data encoding in Yii2, directly in your Yii2 Controller / action.
Use this code:
$uploads = UploadedFile::getInstancesByName("upfile");
if (empty($uploads)){
return "Must upload at least 1 file in upfile form-data POST";
}
// $uploads now contains 1 or more UploadedFile instances
$savedfiles = [];
foreach ($uploads as $file){
$path = //Generate your save file path here;
$file->saveAs($path); //Your uploaded file is saved, you can process it further from here
}
If you use Postman API client to test how your API is working, you can configure the upload endpoint to work like this for multi-file uploads:
Note: The upfile[] square brackets are important! Postman will happily let you select multiple files for upload in one slot, but this will not actually work. Doing it the way shown in the screenshot makes an array of files available to the Yii2 action, through the UploadedFile mechanism. This is roughly equivalent to the standard PHP $_FILES superglobal variable but with easier handling.
Single files can be uploaded with or without the [] square brackets after the key name. And of course you can name upfile whatever you like, for your convention.
You should use \yii\web\UploadedFile::getInstanceByName('asset'); instead of getInstance() checkout this Link

Yii2 Rest API PUT method call is not working

In my controller:
namespace app\api\modules\v1\controllers;
use yii\rest\ActiveController;
use yii\filters\VerbFilter;
use yii\web\Response;
class CountryController extends ActiveController
{
public $modelClass = 'app\models\Country';
public function behaviors()
{
return [
[
'class' => 'yii\filters\ContentNegotiator',
'only' => ['index', 'view','create','update','search'],
'formats' => ['application/json' =>Response::FORMAT_JSON,],
],
'verbs' => [
'class' => VerbFilter::className(),
'actions' => [
'index'=>['get'],
'view'=>['get'],
'create'=>['post'],
'update'=>['PUT'],
'delete' => ['delete'],
'deleteall'=>['post'],
'search' => ['get']
],
]
];
}
}`
I try from my POSTMAN App
For create I use POST http://localhost/myapp/api/v1/countries Works fine.But For Update I use PUT http://localhost/myapp/api/v1/countries/16 it returns 16 the record as JSON output not updating as expected.
What was wrong? Thanks!!
In POSTMAN App, open the Request body tab and select x-www-form-urlencoded instead of form-data. That worked for me.
Here is another option if you feel comfortable using it. Instead of behaviors() you can add something like this and it will serve the same purpose and you wont have any problem.
public function actions()
{
$actions = parent::actions();
unset($actions['index']);
unset($actions['create']);
unset($actions['delete']);
unset($actions['update']);
unset($actions['view']);
return $actions;
}

Zend Framework: How and where to create custom routes in a 1.8+ application?

I've never created a custom route before, but I finally have a need for one. My question is: How can I create a custom route, and where should I create it? I'm using Zend Framework 1.9.6.
Here's how I did it. There may be a better way, but I think this is as simple as it gets:
# /application/Bootstrap.php
protected function _initSpecialRoutes()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$router->addRoute(
'verify', new Zend_Controller_Router_Route('verify/:token', array(
'controller' => 'account',
'action' => 'verify'))
);
$router->addRoute(
'arbitrary-route-name', new Zend_Controller_Router_Route('special/route/:variablename', array(
'controller' => 'defaultcontrollername',
'action' => 'defaultactionname'))
);
}
Here's where I define my custom routes:
// in bootstrap
Zend_Controller_Front::getInstance()->registerPlugin( new Prontiso_Controller_Plugin_Routes() );
...
<?php
// Prontiso/Controller/Plugin/Routes.php
class Prontiso_Controller_Plugin_Routes extends Zend_Controller_Plugin_Abstract
{
public function routeStartup( Zend_Controller_Request_Abstract $request )
{
Prontiso_Routes::addAll();
}
}
...
<?php
// Prontiso/Routes.php
class Prontiso_Routes extends Zend_Controller_Plugin_Abstract
{
public static function addAll()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
/**
* Define routes from generic to specific.
*/
/**
* Example: Simple controller/action route, guaranteed to eliminate any extra URI parameters.
*/
$router->addRoute(
'barebones', new Zend_Controller_Router_Route(':controller/:action')
);
}
}
See the Router documentation for the how. And I would make your routes in the bootstrap. Either program the routes by hand or load a configuration file.
I know there's already an accepted answer by #Andrew but I wanted to post this method that is very similar to his but I find cleaner.
If you dig into Zend_Controller_Router_Rewrite you will find an addRoutes() method which just iterates over each key and value and calls addRoute().
So here's my solution:
# /application/Bootstrap.php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected function _initRoutes()
{
$router = Zend_Controller_Front::getInstance()->getRouter();
$loginRoute = new Zend_Controller_Router_Route('login', array('controller' => 'auth', 'action' => 'login'));
$logoutRoute = new Zend_Controller_Router_Route('logout', array('controller' => 'auth', 'action' => 'logout'));
$routesArray = array('login' => $loginRoute, 'logout' => $logoutRoute);
$router->addRoutes($routesArray);
}
}