File Upload in Rest API yii2 - rest

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

Related

Zend_Cache understanding issue

I try to use Zend_Cache (first try) to save information about user grants. The idea and most of the source code comes from Oleg Krivtsovs tutorial.
I get an error, if I try to retrieve my cache.
Call to a member function getItem() on array
Here the implementation of FilesystemCache, in my global.php
'caches' => [
'FilesystemCache' => [
'adapter' => [
'name' => Filesystem::class,
'options' => [
// Store cached data in this directory.
'cache_dir' => './data/cache',
// Store cached data for 1 hour.
'ttl' => 60*60*1
],
],
'plugins' => [
[
'name' => 'serializer',
'options' => [
],
],
],
],
],
Here my factory class:
<?php
namespace User\Service;
use User\Controller\Plugin\AuthPlugin;
use User\Model\GrantsTable;
use User\Model\UserTable;
use Zend\Authentication\AuthenticationService;
use Zend\ServiceManager\Factory\FactoryInterface;
use Interop\Container\ContainerInterface;
class AccessControlFactory implements FactoryInterface {
public function __invoke(ContainerInterface $container, $requestedName, array $options = null) {
$config = $container->get('config');
$userTable = $container->get(UserTable::class);
$grantsTable = $container->get(GrantsTable::class);
$cache = $config['caches']['FilesystemCache'];
$userplugin = $container->get(AuthPlugin::class);
// $authentication = $container->get( \Zend\Authentication\AuthenticationService::class);
return new AccessControl($userTable, $grantsTable, $cache, $userplugin);//, $authentication
}
}
Now in the init function within my AccessControl Service, I try to retrieve from the cache:
$this->cache->getItem('rbac_container', $result);
There I get the above error.
Any help with a bit of explanation would be appreciated.
What you're injecting to the AccessControl constructor is an array, not a cache implementation, because $config['caches']['FilesystemCache'] returns an array of FilesystemCache options (adapter, plugins, etc.). What you're supposed to do is fetch the cache implementation via the ContainerInterface, like this:
$cache = $container->get('FilesystemCache');
Then the ContainerInterface will depend on StorageCacheAbstractServiceFactory to find your requested cache configs and return the class for you.

Data not posted for custom operation

I've got very big trouble with custom operations in Laravel backpack.
The documentated setup is clear but lack a real exemple with a form.
In my case I wanted to use the form engine to create a form for a relationship.
First step I did this :
public function getProtocoleForm($id)
{
// Config base
$this->crud->hasAccessOrFail('update');
$this->crud->setOperation('protocole');
//
$this->crud->addFields([
[ 'name' => 'codeCim',
'type' => 'text',
'label' => 'Code CIM',
],
]);
$this->crud->addSaveAction([
'name' => 'save_action_protocole',
'visible' => function($crud) {
return true;
},
'button_text' => 'Ajouter le procotole',
'redirect' => function($crud, $request, $itemId) {
return $crud->route;
},
]);
// get the info for that entry
$this->data['entry'] = $this->crud->getEntry($id);
$this->data['crud'] = $this->crud;
$this->data['saveAction'] = $this->crud->getSaveAction();
$this->data['title'] = 'Protocole ' . $this->crud->entity_name;
return view('vendor.backpack.crud.protocoleform', $this->data);
}
This is working fine, the form appears on the screen, then I did a setup for a post route like this :
Route::post($segment . '/{id}/protocolestore', [
'as' => $routeName . '.protocolestore',
'uses' => $controller . '#storeProtocole',
'operation' => 'protocole',
]);
The route appears correctly when I execute the artisan command but the storeProtocole function is never called. I checked the generated HTML and the form action is correct and checking in the "network" panel of the browser is also targeting the correct route.
Can you help me and tell me where I missed something ?
[Quick update] I made a mistake, the form route is not good in the HTML, it takes the route of the main controller.

cakephp formhelper : getting current file from input file on edit

I have a form where images can be submitted, this isn't originally my code, I'm inheriting it and I'm trying to figure it out and fix the problem.
When editing the datas from this form, the input file is empty as opposed to the other inputs. So saving actually replaces the current file to "array" in database.
I would like to be able to keep the current one if it is unchanged ?
Form edit :
<h1>Edit</h1>
<?php
echo $this->Form->create($template,['enctype' => 'multipart/form-data']);
echo $this->Form->input('title');
echo $this->Form->input('works_id');
echo $this->Form->input('photo',['type' => 'file']);
echo $this->Form->input('text', ['rows' => '4']);
echo $this->Form->input('url');
echo $this->Form->button(__('Save'));
echo $this->Form->end();
?>
edit function from controller :
public function edit($id = null){
$template = $this->Templates->get($id);
if ($this->request->is(['post', 'put'])) {
$template = $this->Templates->patchEntity($template, $this->request->data,['associated' => ['Works']]);
if ($this->Templates->save($template)) {
$this->Flash->success(__('Your template has been updated.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('Unable to update your template.'));
}
$this->set('template', $template);
$works = $this->Templates->Works->find('list');
$this->set(compact('works'));
}
In my database it saves on two colomns :
file_dir and filename, so it confuses me even more on how to get the file in the edit form.
edit : adding TemplatesTable :
<?php
namespace App\Model\Table;
use Cake\ORM\Table;
use Cake\Validation\Validator;
class TemplatesTable extends Table
{
protected function _initializeSchema(\Cake\Database\Schema\Table $table){
$table->columnType('photo', 'proffer.file');
return $table;
}
public function initialize(array $config){
$this->addBehavior('Timestamp');
$this->addBehavior('Proffer.Proffer', [
'photo' => [
'root' => WWW_ROOT.'files',
'dir' => 'photo_dir',
'thumbnailSizes' => [
'square' => ['w' => 200, 'h' => 200],
'portrait' => ['w' => 100, 'h' => 300, 'crop' => true],
],
'thumbnailMethod' => 'gd' // Options are Imagick, Gd or Gmagick
]
]);
$this->belongsTo(
'Works',
[
'foreignKey' => 'work_id',
]
);
}
public function validationDefault(Validator $validator){
$validator
->notEmpty('work_id')
->notEmpty('title');
return $validator;
}
}
According to the docs, the Proffer behavior can handle empty uploads out of the box, and you just have to configure the validation rules for the field accordingly to allow it to be empty on update:
If you want your users to submit a file when creating a record, but
not when updating it, you can configure this using the basic Cake
rules.
$validator
->requirePresence('image', 'create')
->allowEmpty('image', 'update');
So now your users do not need to upload a file every time they update
a record.
https://github.com/davidyell/CakePHP3-Proffer/blob/0.8.2/docs/validation.md
https://github.com/davidyell/CakePHP3-Proffer/blob/0.8.2/src/Model/Behavior/ProfferBehavior.php#L56
So in your case that would be:
->requirePresence('photo', 'create')
->allowEmpty('photo', 'update')

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;
}

How to use Yii2 debugger when developing RESTful application?

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;}