For buiding restful API using Yii2, does anyone has good example on how to add a new action in a controller? Thanks.
I am not sure if you are asking for extra actions beside CRUD or just for CRUD, so I write in details for both cases.
Firstly, the framework includes \yii\rest\ActiveController that provides typical restful API operation and URL management.
Basically, the controller predefines the CRUD operations as followed:
POST /resource -> actionCreate -> Create the resource
GET /resource/{id} -> actionView -> Read the resource
PUT, PATCH /resource/{id} -> actionUpdate -> Update the resource
DELETE /resource/{id} -> actionDelete -> Delete the resource
GET /resource -> actionIndex -> List all the resources
The URL routing rules and actions definition can be found in \yii\rest\ActiveController, \yii\rest\UrlRule and the respective \yii\rest\*Action.
Secondly, if you want to add extra restful API in the controller, you can simply write your extra actionXxxxx(), and in configuration, add the following url rules under urlManager:
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [
[
'class' => 'yii\rest\UrlRule',
'controller' => ['resource'],
'pluralize' => false,
'extraPatterns' => [
'POST {id}/your_preferred_url' => 'xxxxx', // 'xxxxx' refers to 'actionXxxxx'
],
],
],
],
Effectively, this will generate a new routing rule, requesting POST /resource/{id}/your_preferred_url will invoke actionXxxxx of your ResourceController.
Here is a good example using Yii 2 advanced application template
https://github.com/deerawan/yii2-advanced-api
more detail of this project http://budiirawan.com/setup-restful-api-yii2/
also you can use Yii 2 basic application template if you want.
what you have to do is follow this kind of folder structure (v1 for version) (Yii doc -A module may consist of sub-modules.)(GiovanniDerks - backend sub-modules)
-modules
--api
---v1
----controllers
----models
Related
How do I create access/api/auth tokens for a different model? From most questions, docs, or tutorials, most are using the default User model.
I read the default doc but it doesn't really say where to substitute the default Model class or how to verify against non-Model classes.
Any suggestions?
To use a different model than User for Laravel Sanctum API authentication.
This is for Laravel 8.
Create new model, php artisan make:model ModelName -m
the flag m is used to instantiate a migration file for this model.
Go to the Model class file and extend it with Illuminate\Foundation\Auth\User, ensure it uses HasApiTokens, and list out your fillable fields for record creation.
...
use Illuminate\Foundation\Auth\User as Authenticatable;
class ModelName extends Authenticatable{
use ..., HasApiTokens;
protected $fillable = [...]
}
Go to config/auth.php and add new provider and new guard.
'guards' => [
...
,
'api' => [
'driver' => 'sanctum',
'provider' => 'model-name',
'hash' => false,
]
],
'providers' => [
...
,
'model-name' => [
'driver' => 'eloquent',
'model' => App\Models\ModelName::class,
]
]
Go to your api routes and wrap your routes as below.
Route::middleware(['auth:sanctum'])->group(function(){
Route::get('/whatever-route-name',function(){
return 'Authenticated';
});
});
Download Postman or your favored API testing tool, send a GET request to [http://localhost:8000/api/whatever-route-name](http://localhost:8000/api/whatever-route-name) , in Headers, ensure Accept has a value of applcation/json, send the request, and it should return an {”message”: “Unauthenticated.”}
Go to your public routes, create a dummy route to create a record for ModelName
After creation ensure that you call $model_name→createToken($model_name→whatever_field)→plaintTextToken; to get the plain text api key.
Go to back to your API test tool, under Authorization, choose Bearer Token and supply the api key returned from above.
The route wrapped in auth:sanctum is now accessible.
I’ve been trying to find a way to get a user’s roles back through the Moodle webservice API.
I know there are no endpoints to do this but I cannot retrieve them directly from the database because I do not have access to the client’s database.
Is there another way to do that?
You may code a solution along this lines:
Create a barebones plugin with the usual boilerplate code (version.php and so on), for example a local plugin. You can use this other plugin to generate the boilerplate code: https://moodle.org/plugins/tool_pluginskel
In 'db/services.php' register a new external function and expose it in a new or existing service. Docs here: https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin and here https://docs.moodle.org/dev/Web_services_API . For example:
$functions = [
'local_myplugin_get_user_roles' => [
'classname' => external::class,
'methodname' => 'get_user_roles',
'description' => 'gets user roles',
'type' => 'read',
],
];
$services = [
'My services' => [
'functions' => [
'local_myplugin_get_user_roles',
],
'enabled' => 1,
'restrictedusers' => 0,
'shortname' => 'local_myplugin',
'downloadfiles' => 0,
'uploadfiles' => 0,
],
];
Write an external class (for example in a external.php file) for your plugin (referenced in the function definition you coded before). In this class write the code for the defined external function (that will fetch the roles for a user given a user id, for example), including the input and output handlers. Example here: https://docs.moodle.org/dev/External_functions_API#externallib.php
Within your external function, to get a list of user roles given a context, userid, etc. you may use the global helper get_user_roles. Do not forget to write in this external function the code needed to validate input parameters and so on.
To properly expose your new service and external function to an external system you need to follow this settings guidelines as a Moodle admin: YOUR_MOODLE_INSTANCE_URL/admin/settings.php?section=webservicesoverview . At the end you will generate a user (web service consumer) and a token, that you can set in your external system to consume the Moodle service.
Happy coding.
I am trying to implement controller integration testing in CakePHP 3.6 using its testing tools. I assumed that this would be handled by making a 'real' (as in CURL) HTTP request against the running webserver, but it looks like it isn't. Below is the test case code I'm using.
The problems I'm running into:
The test case is somehow managing to access the controler action,
even when the webserver is not running at all (Apache down and no
dev webserver running).
When running this test, the controller does not have access to
$_SERVER (see below) and any of the $postData defined in the test case appears empty on the controller side.
When I place exit; in the controller code, the whole test case
stops, which suggests that the controller code is run directly, not
via a HTTP request.
Question: How can I make a 'real' HTTP requests when testing controllers, apart from resorting to using CURL and handling the requests manually?
Clearly, I am either not understanding how the controller testing is done, or I'm doing something wrong.
Test case I'm using:
/tests/TestCase/Controller/JobsControllerTest.php
<?php
namespace App\Test\TestCase\Controller;
use Cake\ORM\TableRegistry;
use Cake\TestSuite\IntegrationTestCase;
/**
* App\Controller\JobsController Test Case
*/
class JobsControllerTest extends IntegrationTestCase
{
/**
* Test add method
*
* #return void
*/
public function testAdd()
{
$this->useHttpServer(true);
$this->configRequest([
'headers' => [
'Content-Type' => 'application/json',
'X-Api-Key' => '8f083c8f083c8f083c8f083c'
]
]);
$postData = [
'user_id' => 3,
'job_status' => 'New'
];
$this->post('/jobs/add', $postData);
$this->assertResponseSuccess();
$jobs = TableRegistry::get('Jobs');
$query = $jobs->find()->where(['user_id' => $postData['user_id']]);
$this->assertEquals(1, $query->count());
}
}
The dump of $_SERVER global from the controller that I'm testing:
Array
(
[LS_COLORS] => rs=0:di=01;34 [...]
[LANG] => en_US.UTF-8
[HOME] => /home/tomasz
[TERM] => screen
[PATH] => /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/snap/bin
[MAIL] => /var/mail/root
[LOGNAME] => root
[USER] => root
[USERNAME] => root
[SHELL] => /bin/bash
[SUDO_COMMAND] => vendor/bin/phpunit --verbose
[SUDO_USER] => tomasz
[SUDO_UID] => 1000
[SUDO_GID] => 1000
[PHP_SELF] => vendor/bin/phpunit
[SCRIPT_NAME] => vendor/bin/phpunit
[SCRIPT_FILENAME] => vendor/bin/phpunit
[PATH_TRANSLATED] => vendor/bin/phpunit
[DOCUMENT_ROOT] =>
[REQUEST_TIME_FLOAT] => 1546631688.0758
[REQUEST_TIME] => 1546631688
[argv] => Array
(
[0] => vendor/bin/phpunit
[1] => --verbose
)
[argc] => 2
)
CakePHP integration tests do not issue actual HTTP requests, they simulate them, it's very fast, allows for certain mocking, inspecting session contents, accessing exception details, etc., all sorts of things that wouldn't really be possible (at least not easily) when using real HTTP requests. If you really need to issue actual requests, then you should look into using other utilities, like for example Codeception (specifically acceptance tests).
When using CakePHP, it is advised that you do not access PHP superglobals directly, but that you retrieve the data from the abstracted APIs provided by CakePHP! Breaking your integration tests is one of the reasons for this. The simulated request will receive a request object that has been prepared with the data from your test case, that is where you need to look it up.
For example if you want to access POST data in your app, maybe in your controller, then you do it like this:
$user_id = $this->request->getData('user_id');
See also
Cookbook > Testing > Controller Integration Testing
Cookbook > Request & Response Objects
I read a lot of articles and watched a lot of videos about Laravel passport but still can not understand some things.
I have an application which works with Laravel 5.5 + vueJs. All requests to the back-end are sent via axios. All my routes are located in api.php
Route::middleware('api')->group(function(){
Route::get('/prepare/', 'CompgenApiController#prepareDefault');
Route::post('/replace/', 'CompgenApiController#replaceImage');
Route::get('/replaceall/', 'CompgenApiController#replaceAllImages');
Route::get('/collage/', 'CompgenApiController#collage'); //#todo переделать на POST
Route::get('/generate/', 'CompgenApiController#generate');
Route::post('/upload/', 'CompgenApiController#userUpload');
Route::post('/reupload/', 'CompgenApiController#moderationReupload');
});
Also I have a VK bot that sends requests to the same routes.
At the moment I have some difficulties. For some routes, I need to check that the user is authorized (but this is an optional condition) and if it is true I need to write user id to the database. For me it was a surprise that
Auth :: check
returned false though I was authorized. After some searches I learned that the session that starts after authorization is not connected with the API and I was recommended to use Passport. I can not understand the following things
Do I need to use a passport if requests are sent from my application from vueJs?
How to register users? Do I have to issue my token for each new user?
How can I verify that the user is authorized?
In some cases I need to check that the user is authorized but if it is not so then do not interfere with the request. How can I do that?
Maybe in my case I do not need a passport at all?
Passport is an oAuth2 server implementation, essentially, it allows you to authenticate users by passing a token with each request. If you do not want to authenticate a user, then you do not need to pass the token and passport doesn't get involved.
In terms of a Laravel app, if you are consuming your API from your own frontend, you probably just want to use the implicit grant. Here's how you set that up:
1) Install passport and add it the PassportServiceProvider to config/app.php
2) php artisan migrate to setup the passport migrations
3) php artisan passport:install - to set up your oAuth server
4) In the User model add the Laravel\Passport\HasApiTokens trait, like so:
namespace App;
use Laravel\Passport\HasApiTokens;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasApiTokens, Notifiable;
// Other model methods
}
5) Setup the passport routes by adding the following to the boot method of your app\Providers\AuthServiceProviders.php:
\Laravel\Passport\Passport::routes();
6) Change your api driver in config/auth.php to token:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'passport',
'provider' => 'users',
],
],
That sets up passport, now all you need to do to allow your app to consume your api is to add the CreateFreshApiToken Middleware to web in app/Http/Kernel.php, which handles all the token logic:
'web' => [
// ..Other middleware...
\Laravel\Passport\Http\Middleware\CreateFreshApiToken::class,
],
Now all you need to do to protect a route is to use the auth:api middleware on that route:
Route::middleware('auth:api')->get('/profile','Api\UsersController#edit');
That should all work fine, but you'll also want to register a new user. All you do is add the auth routes as normal to web.php:
Route::post('/login', 'Auth\LoginController#login');
Route::post('/logout', 'Auth\LoginController#logout');
Route::post('/register', 'Auth\RegisterController#register');
You can then simply maks a post request to those routes and Laravel will handle all the token stuff for you.
That handles api routes that require authentication, however, you also mentioned that you want to check if a user is authenticated, but not necessarily lock the route, to do that you can simply use:
Auth::guard('api')->user();
Which will get the authenticated user, so you could do something like:
public function getGreeting(){
$user = Auth::guard('api')->user();
if($user !== null) {
return response()->json(["message" => "Hello {$user->name}"], 200);
}
return response()->json(["message" => "Hello Guest"], 200);
}
That's it. Hopefully, I've covered everything there.
I am developing an API on CakePHP 3 using the CRUD Plugin and ADmad's JWT plugin. I've created fixtures for all tables by importing the schema and then defining some dummy records. Now I'd like to know the best way to test my API.
Namely:
How do I set an Authorized user in my tests?
How do I call API methods in the test framework?
I don't have any code to show at the moment because I'm really not sure how to go about this in the correct way.
The one way I see to test API endpoints is by using post() method of the IntegrationTestCase suite. A very basic example:
public function testLogin()
{
// You can add this to the setUp() function to make it global
$this->configRequest([
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'x-www-form-urlencoded'
]
]);
$this->post('/auth/token', ['username' => $username, 'password' => $password]);
$this->assertResponseOk();
$this->assertResponseContains('access_token');
}
Store that access token (or pre-generate one) & use that to set authorization header.
You can send your authorization tokens like so (before EVERY request):
$this->configRequest([
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'x-www-form-urlencoded',
'Authorization' => 'Bearer ' . $token
]
]);
TIP: You can Configure::write() the Security.salt values in bootstrap.php of the test - this way the password salting works! I also found saving the salted password value in your fixture helpful.
More details in CakePHP Cookbook.