how to wrap Laravel resource with swagger - laravel

I am learning how to use laravel with swagger, and I have this issue:
I have the user controller:
UserController.php
class UserController extends Controller
{
/**
* Shows authenticated user information
*
* #OA\Get(
* tags={"Authorize"},
* path="/user",
* summary="get user detail",
* security={{ "AuthBearer":{} }},
* #OA\Response(
* response="200",
* description="success",
* #OA\JsonContent(
* ref="#/components/schemas/UserResource"
* )
* ),
* #OA\Response(response="401", description="Unauthenticated")
* )
*
* #return \Illuminate\Http\Response
*/
public function user()
{
return new UserResource(auth()->user());
}
}
UserResource.php
/**
* Class UserResource
*
* #OA\Schema(
* #OA\Xml(name="UserResource")
* )
*/
class UserResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #OA\Property(format="int64", title="ID", default=1, description="ID", property="id"),
* #OA\Property(format="string", title="name", default="Demo", description="Name", property="name"),
* #OA\Property(format="string", title="username", default="demo", description="Username", property="username"),
* #OA\Property(format="string", title="avatar_path", default="https://via.placeholder.com/640x480.png/0000bb?text=avatar", description="Avatar Path", property="avatar_path")
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'username' => $this->username,
'avatar_path' => $this->avatar_path
];
}
}
this is working fine, but in the swagger docs, the example value for this route is this:
{
"id": 1,
"name": "demo",
"username": "demo",
"avatar_path": "https://via.placeholder.com/640x480.png/0000bb?text=avatar"
}
But when I execute the route in swagger, it returns this:
{
"data": {
"id": 1,
"name": "demo",
"username": "demo",
"avatar_path": "https://via.placeholder.com/640x480.png/0000bb?text=avatar"
}
}
This is wrapped inside a data property, I would like that the example has the same format, how could achieve that? thanks.

You can write in UserResource.php:
/**
* #OA\Schema(
* #OA\Xml(name="UserResource"),
* #OA\Property(property="data", type="array",
* #OA\Items(ref="#/components/schemas/User"))
* ),
* )
*/
And in model User.php
/**
* #OA\Schema(
* #OA\Xml(name="User"),
* #OA\Property(format="int64", title="ID", default=1, description="ID", property="id"),
* #OA\Property(format="string", title="name", default="Demo", description="Name", property="name"),
* #OA\Property(format="string", title="username", default="demo", description="Username", property="username"),
* #OA\Property(format="string", title="avatar_path", default="https://via.placeholder.com/640x480.png/0000bb?text=avatar", description="Avatar Path", property="avatar_path")
* )
*/

Related

How to combine a property and json response in Laravel + Swagger?

I have this a project that is using Laravel for the api and swagger for documentation
I have this method in my login controller:
/**
* Handle an incoming authentication request.
*
*
* #OA\Post(
* tags={"UnAuthorize"},
* path="/login",
* summary="User Login",
* #OA\RequestBody(
* #OA\MediaType(
* mediaType="application/json",
* #OA\Schema(
* type="object",
* ref="#/components/schemas/LoginRequest",
* )
* )
* ),
* #OA\Response(
* response="200",
* description="An example resource",
* #OA\JsonContent(
* type="object",
* #OA\Property(
* format="string",
* default="20d338931e8d6bd9466edeba78ea7dce7c7bc01aa5cc5b4735691c50a2fe3228",
* description="token",
* property="token"
* )
* ),
* #OA\JsonContent(ref="#/components/schemas/UserResource")
* ),
* #OA\Response(response="401", description="fail"),
* )
*
* #param \App\Http\Requests\Auth\LoginRequest $request
* #return \Illuminate\Http\JsonResponse
*/
public function store(LoginRequest $request)
{
$request->authenticate();
return response()->json(
[
"token" => $request->user()->createToken($request->email)->plainTextToken,
"user" => $request->user();
]
);
}
then, when I run this command: php artisan l5-swagger:generate ,it displays this error:
c:\myproject\vendor\zircote\swagger-php\src\Logger.php:40
36▕ $this->log = function ($entry, $type) {
37▕ if ($entry instanceof Exception) {
38▕ $entry = $entry->getMessage();
39▕ } ➜ 40▕ trigger_error($entry, $type);
41▕ };
42▕ }
When I remove this
#OA\JsonContent(ref="#/components/schemas/UserResource")
it works, but the user data is not displayed, maybe I should only return the token, and make another request with the token to get the information about the logged user. What can I do?, thanks
EDIT:
#OA\Response(
* response="200",
* description="An example resource",
* #OA\JsonContent(
* type="object",
* #OA\Property(
* format="string",
* default="20d338931e8d6bd9466edeba78ea7dce7c7bc01aa5cc5b4735691c50a2fe3228",
* description="token",
* property="token"
* ),
* #OA\Property(
* format="application/json",
* property="user",
* #OA\JsonContent(ref="#/components/schemas/UserResource")
* )
* ),
* )
I tried this, but it shows errors
Pretty close except...
use of 'format' instead of 'type'
you do not have to specify the content type if your property is already wrapped in #OA\JsonContent
you need to be careful with surplus trailing ','; doctrine can be picky with that
Here is my take which does work stand-alone (except for the missing #OA\Info):
<?php
use OpenApi\Annotations\OpenApi as OA;
/**
* #OA\Schema
*/
class LoginRequest{}
/**
* #OA\Schema
*/
class UserResource{}
/**
* Handle an incoming authentication request.
*
*
* #OA\Post(
* tags={"UnAuthorize"},
* path="/login",
* summary="User Login",
* #OA\RequestBody(
* #OA\MediaType(
* mediaType="application/json",
* #OA\Schema(
* type="object",
* ref="#/components/schemas/LoginRequest"
* )
* )
* ),
* #OA\Response(
* response="200",
* description="An example resource",
* #OA\JsonContent(
* type="object",
* #OA\Property(
* type="string",
* default="20d338931e8d6bd9466edeba78ea7dce7c7bc01aa5cc5b4735691c50a2fe3228",
* description="token",
* property="token"
* ),
* #OA\Property(
* property="user",
* ref="#/components/schemas/UserResource"
* )
* )
* ),
* #OA\Response(response="401", description="fail")
* )
*
*/
class Controller {}
The JsonContent you removed should be a property on the first JsonContent I think

Laravel Darkaonline/Swagger Validation Request Not Working

I am trying to create an API for the sendOTP function created in the controller. The package I'm using to do so is DarkaOnLine/L5-Swagger which generates swagger UI based on OpenApi 3.0. I am new to Laravel Swagger API and the problem is with validations. I have 2 fields that are supposed to be validated country_code & mobile. I pass the $request parameter to the LoginRequest to validate the data. Below are my controller and request code.
LoginController
/**
* #OA\POST(
* path="/api/sendLoginOTP",
* operationId="sendLoginOTP",
* tags={"LoginviaOTP"},
* summary="Send Otp to mobile lstest",
* description="Sends Otp to Mobile and Returns mobile number and country code with default country code",
*
* #OA\Parameter(
* name="country_code",
* description="country code",
* required=true,
* in="path",
* #OA\Schema(
* type="string"
* )
* ),
* #OA\Parameter(
* name="mobile",
* description="mobile number",
* required=true,
* in="path",
* #OA\Schema(
* type="integer"
* )
* ),
* #OA\Response(
* response=200,
* description="successful operation"
* ),
* #OA\Response(response=400, description="Bad request"),
* #OA\Response(
* response=401,
* description="Unauthenticated",
* ),
* #OA\Response(response=404, description="Resource Not Found"),
* security={
* {
* "oauth2_security_example": {"write:projects", "read:projects"}
* }
* },
* )
*/
public function sendOTP(LoginRequest $request)
{
return response()->json('Validated');
}
LoginRequest
public function authorize()
{
return true;
}
public function rules()
{
return [
'country_code' => [
'required',
'exists:countries,iso',
'exists:users,country_code',
],
'mobile' => [
'required',
'numeric',
'exists:users,mobile',
],
];
}
Swagger UI
Output I get After Entering Credentials
JSFIDDLE of whole output I am getting
Output am expecting
My problem: whenever I am trying to validate the parameters it's not getting validated. I am not sure where am I going wrong here. Am I not supposed to pass $request to LoginRequest? if not this method then how am I supposed to validate the data provided in the parameters?
I just changed the in value of parameters from path to query and it worked well for me
/**
* #OA\Parameter(
* ....
* in="query",
* ....
* ),
* #OA\Parameter(
* ....
* in="query",
* ....
* ),
*/

Laravel Swagger PHP Couldn't find constant array

darkaonline/l5-swagger: 8.0.2
PHP Version: 7.3.13
zircote/swagger-php: 3.1.0
OS: Windows
I created a Contract ref object.
Now in my ContractController I want to list an array of contracts.
How can I do it?
I got this error Couldn't find constant array when trying to add type=array in OA\Items
/**
* #OA\Info(title="Contract API", version="1")
*/
class ContractController extends Controller
{
/**
* #OA\Post(
* path="/api/v1/contract/list",
* tags={"contact"},
* summary="List Contract",
* operationId="list",
* #OA\Parameter(
* name="keyword",
* in="path",
* description="keyword to search contracts",
* required=false,
* #OA\Schema(
* type="string"
* )
* ),
* #OA\Parameter(
* name="lang_code",
* in="path",
* description="lang_code define language client used",
* required=false,
* #OA\Schema(
* type="string",
* )
* ),
* #OA\Response(
* response=200,
* description="successful",
* #OA\JsonContent(
* #OA\Items(
* type=array, #This is where I got the error
* ref="#/components/schemas/Contract"
* )
* )
* ),
* #OA\Response(
* response=400,
* description="Wrong"
* )
* )
*/
public function list(Request $request)
{
$contracts = Contract::factory()->count(10)->make();
return response()->json([
'message' => 'good',
'contracts' => $contracts
], 200);
}
}
/**
* #OA\Schema(
* description="Contract model",
* type="object",
* title="Contract model"
* )
*/
class Contract extends Model
{
use HasFactory;
/**
* The unique identifier of a product in our catalog.
*
* #var integer
* #OA\Property(format="int64", example=1)
*/
public $id;
/**
* #var string
* #OA\Property(format="string", example="contract 001")
*/
public $contract_title;
}
Use type="array", (with "s) instead of type=array,
I know its late but
Try to use
* #OA\MediaType(
* mediaType="application/json",
* #OA\Schema(
* type="array",
* #OA\Items(
* ref="#/components/schemas/Contract"
* ),
* )
* )
Insead of #OA\JsonContent

How to upload a file in swagger-php array

I want to uplaod a file in swagger-php in the json requestBody How can upload with the help of swagger anonations
Trying from lot of hours but not luck how can send and file in application/json array Can you help if any information about this so then i will solve my problem i have not concept about this
when this code generate in the terminal also not have any error and not shown in the request body in the swagger ui
/**
* #OA\Post(
* path="/products/save",
* tags={"Product"},
* summary="Post bulk products",
* description="Return bulk products",
* #OA\RequestBody(
* required=true,
* description="Bulk products Body",
* #OA\JsonContent(
* #OA\Property(
* property="products",
* #OA\Items(
* #OA\Property(property="first_name", type="string"),
* #OA\Property(property="last_name", type="string"),
* #OA\Property(property="email", type="string"),
* #OA\Property(property="phone", type="string"),
* #OA\Property(property="resume", type="string", format="base64"),
* ),
* )
* )
* ),
* )
*/
I want to this type of swagger-ui body so that user can fill attribut and
the resume add in base64 format
{
"products": [
{
"first_name": "string",
"last_name": "string",
"email": "string",
"phone": "string",
"resume": "string" ==> here i will send base64 format of resume file
}
]
}
``
You may use #OA\Property(property="file", type="string", format="binary"), to define a file property:
/**
* #OA\Schema(
* schema="ProductRequest",
* required={"products"},
* #OA\Property(
* property="products",
* type="array",
* #OA\Items(
* #OA\Property(property="first_name", type="string"),
* #OA\Property(property="last_name", type="string"),
* #OA\Property(property="email", type="string"),
* #OA\Property(property="phone", type="string"),
* #OA\Property(property="resume", type="string", format="binary"),
* ),
* )
* )
*/
Then you have to set a media type on your RequestBody using #OA\MediaType:
/**
* #OA\RequestBody(
* request="Product",
* required=true,
* description="Bulk products Body",
* #OA\MediaType(
* mediaType="multipart/form-data",
* #OA\Schema(ref="#/components/schemas/ProductRequest")
* )
* )
*/
And finally on your #OA\Post:
/**
* #OA\Post(
* path="/products/save",
* tags={"Product"},
* summary="Post bulk products",
* description="Return bulk products",
* #OA\RequestBody(ref="#/components/requestBodies/Product"),
* #OA\Response(response=200, ref="#/components/responses/Product")
* )
*/
See also Swagger docs on File data type and File upload for more info.
Update: If you don't want separate declarations just merge them like this:
/**
* #OA\Post(
* path="/products/save",
* tags={"Product"},
* summary="Post bulk products",
* description="Return bulk products",
* #OA\RequestBody(
* required=true,
* description="Bulk products Body",
* #OA\MediaType(
* mediaType="multipart/form-data",
* #OA\Schema(
* #OA\Property(
* property="products",
* type="array",
* #OA\Items(
* #OA\Property(property="first_name", type="string"),
* #OA\Property(property="last_name", type="string"),
* #OA\Property(property="email", type="string"),
* #OA\Property(property="phone", type="string"),
* #OA\Property(property="resume", type="string", format="binary"),
* )
* )
* )
* )
* )
* )
*/
You may also want an approach with PHP classes
So you can define a model like that:
/**
* #OA\Schema(
* schema="User",
* required={"first_name", "last_name" // and so on}
* )
*/
class User
{
/**
* #OA\Property(type="string")
*/
public $first_name;
/**
* #OA\Property(type="string")
*/
public $last_name;
// add your other fields bellow
}
after you can define for example the body of a POST request as follows:
<?php
/**
* #OA\Schema(
* schema="CreateUsers",
* required={"users"}
* )
*/
class CreateUsers
{
/**
* #var array
* #OA\Property(ref="#/components/schemas/User")
*/
public $users;
}
And lastly create the your Request in your documentation for example:
/**
* #OA\Post(
* path="YOUR ROUTE URL",
* operationId="createUsers",
* tags={"Users"},
* #OA\RequestBody(
* required=true,
* #OA\MediaType(
* mediaType="application/json",
* #OA\Schema(ref="#/components/schemas/CreateUsers")
* )
* ),
* summary="Create a collection of users",
* description="Create a collection of users"
* )
**/
EDIT 1:
If you want a request that have a file to the request body you way do:
/**
* #OA\Post(
* path="YOUR ROUTE URL",
* operationId="createUsers",
* tags={"Users"},
* #OA\RequestBody(
* required=true,
* #OA\MediaType(
* mediaType="multipart/form-data", // here we need to change from "application/json" to "multipart/form-data" in order to make our file visible
* #OA\Schema(ref="#/components/schemas/CreateUsers")
* )
* ),
* summary="Create a collection of users",
* description="Create a collection of users"
* )
**/
And make your field in your PHP class:
/**
* #OA\Schema(
* schema="User",
* required={"first_name", "last_name", "file" // and so on}
* )
*/
class User
{
/**
* #OA\Property(type="string")
*/
public $first_name;
/**
* #OA\Property(type="string")
*/
public $last_name;
/**
* #OA\Property(description="file to upload", type="string", format="file")
*/
public $file;
// add your other fields bellow
}
You can see an example here: swagger-php/Examples/petstore.swagger.io/controllers/PetController.php

ZF2 fileupload not required with annotationbuilder

I'm stuck at a problem with ZendFramework 2, annotationbuilder and fileupload.
for at contact form i want user the choice to upload a file.
I got everything to work except the file upload, if there is no file a get the
error:
File was not uploaded
I'm using annotationbuilder to create the form. some annotations is cut out with a space for testing. but did not help.
the Annotation class:
<?php
/**
* #Annotation\Name("message")
* #Annotation\Hydrator("Zend\Stdlib\Hydrator\ObjectProperty")
* #ORM\Entity
* #ORM\Table(name="contact_message")
*/
class Message {
/**
* #Annotation\Exclude()
*
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var integer
*/
private $id;
/**
* #Annotation\Type("Zend\Form\Element\Select")
* #Annotation\Flags({"priority": 600})
* #Annotation\Required({"required":"true" })
* #Annotation\Filter({"name":"StringTrim"})
* # Annotation\Validator({"name":"StringLength"})
* #Annotation\Options({"label":"About:"})
* #Annotation\Attributes({"options":{"1":"PlaceHolder","2":"Test"}})
*
* #ORM\Column(type="string")
* #var String
*/
private $about;
/**
* #Annotation\Type("Zend\Form\Element\Text")
* #Annotation\Flags({"priority": 500})
* #Annotation\Required({"required":"true" })
* #Annotation\Filter({"name":"StripTags"})
* # Annotation\Validator({"name":"EmailAddress"})
* #Annotation\Options({"label":"Name:"})
* #Annotation\Attributes({"required": true,"placeholder": "Your name ... "})
*
* #ORM\Column(type="string")
* #var String
*/
private $name;
/**
* #Annotation\Type("Zend\Form\Element\Text")
* #Annotation\Flags({"priority": 500})
* #Annotation\Required({"required":"true" })
* #Annotation\Filter({"name":"StripTags"})
* # Annotation\Validator({"name":"EmailAddress"})
* #Annotation\Options({"label":"Subject:"})
* #Annotation\Attributes({"required": true,"placeholder": "Subject ... "})
*
* #ORM\Column(type="string")
* #var String
*/
private $subject;
/**
* #Annotation\Type("Zend\Form\Element\File")
* #Annotation\Flags({"priority": 500})
* # Annotation\Required({"required":false })
* # Annotation\Filter({"name":"StringTrim","filerenameupload":{"target": "./img","randomize":true}})
* # Annotation\Filter({"name":"StripTags"})
* # Annotation\Validator({"name":"StringLength"})
* #Annotation\Options({"label":"File:"})
* #Annotation\Attributes({"required": false})
*
* #ORM\Column(type="string")
* #var String
*/
private $file;
/**
* #Annotation\Type("Zend\Form\Element\Textarea")
* #Annotation\Flags({"priority": 500})
* # Annotation\Required({"required":"true" })
* #Annotation\Filter({"name":"StripTags"})
* # Annotation\Validator({"name":"EmailAddress"})
* #Annotation\Options({"label":"Message:"})
* #Annotation\Attributes({"required": true,"placeholder": "Message ... "})
*
* #ORM\Column(type="string")
* #var String
*/
private $message;
/**
* WARNING USING THESE IS NOT SAFE. there is no checking on the data and you need to know what
* you are doing when using these.
* But it a great function for lazy people ;)
*
* #param ANY $value
* #param ANY $key
* #return $value
*/
public function __set($value,$key){
return $this->$key = $value;
}
/**
* WARNING USING THESE IS NOT SAFE. there is no checking on the data and you need to know what
* you are doing when using these.
* But it a great function for lazy people ;)
*
* #param ANY $value
* #param ANY $key
* #return $value
*/
public function __get($key){
return $this->$key;
}
/**
* WARNING USING THESE IS NOT SAFE. there is no checking on the data and you need to know what
* you are doing when using these.
* This is used to exchange data from form and more when need to store data in the database.
* and again ist made lazy, by using foreach without data checks
*
* #param ANY $value
* #param ANY $key
* #return $value
*/
public function populate($array){
foreach ($array as $key => $var){
$this->$key = $var;
}
}
/**
* Get an array copy of object
*
* #return array
*/
public function getArrayCopy()
{
return get_object_vars($this);
}
}
?>
and the add Action in my controller:
<?php
use Zend\View\Model\ViewModel;
use Zend\Form\Annotation\AnnotationBuilder;
use Contact\Entity\Contact;
use Contact\Entity\Company;
use Contact\Entity\Message;
use Contact\Controller\EntityUsingController;
class MessageController extends EntityUsingController {
public function addAction(){
$message = new Message();
$builder = new AnnotationBuilder();
$form = $builder->createForm($message);
$form->bind($message);
$request = $this->getRequest();
if ($request->isPost()) {
$form->bind($message);
$requestData = array_merge_recursive((array) $request->getPost(),(array) $request->getFiles());
$form->setData($requestData);
var_dump($request->getFiles());
if ($form->isValid()) {
$em = $this->getEntityManager();
$em->persist($message);
$em->flush();
$this->flashMessenger()->addMessage('Contact Saved');
return $this->redirect()->toRoute('contact');
}
}
return new ViewModel(array(
'form' => $form
));
}
private function storeFile($file){
if (!$this->getConfiguration('fileupload')){
return null;
}
$fileBank = $this->getServiceLocator()->get('FileRepository');
$entity = $fileBank->save('/tmp/myfile.jpg');
}
}
?>
I hope anyone can help my get around this problem.
This seems to be a bug in the annotation builder.
I found a lot of places on the net that confirmed that i should be correct.
so i ended up hacking it by adding the file input after the form was build.
if ($this->getConfiguration('fileupload')){
$companyForm->add(array(
'name' => 'file',
'priority' => 300,
'type' => 'file',
'options' => array(
'label' => 'File:',
),
),array('priority' => 300));
}
checks if the configuration allows for file upload. an extra feature I put in now that I could not set the file upload as an annotation
If you're still interested in another way:
You can try the AllowEmpty annotation:
* #Annotation\AllowEmpty(true)
Or you can try to disable the validator for that field in the controller:
$form->getInputFilter()->remove('file');

Resources