Api Platform custom swagger/openapi context body - symfony

I am using Api Platform with Symfony 4, and I want to create a custom endpoint. Everything works fine, but I cannot change 2 things: body and response format (in openapi documentation).
Parameters and responses status codes works just fine.
* "login"={
* "route_name"="api_login",
* "method" = "post",
* "openapi_context" = {
* "parameters" = {},
* "body" = {
* "description" ="Username and password",
* "schema" = {
* "type" = "object",
* "required" = {"email","password"},
* "properties" = {
* "email" = {
* "type" = "string"
* },
* "password" = {
* "type" = "string"
* }
* }
* }
* },
* "responses" = {
* "200" = {
* "description" = "User logged in",
* "schema" = {
* "type" = "object",
* "required" = {
* "token",
* "refresh_token"
* },
* "properties" = {
* "token" = {
* "type" = "string"
* },
* "refresh_token" = {
* "type" = "string"
* }
* }
* }
* },
* "401" = {
* "description" = "invalid password or email"
* }
* },
* "summary" = "Login user in application",
* "consumes" = {
* "application/json",
* "text/html",
* },
* "produces" = {
* "application/json"
* }
* }
* }

This works for me, please see the documentation.
https://swagger.io/docs/specification/describing-request-body/
* #ApiResource(
* collectionOperations={
* "get": {
* "method": "GET",
* "access_control": "is_granted('ROLE_USER', object)",
* },
* "post": {
* "method": "POST",
* "access_control": "is_granted('ROLE_USER', object)",
* "openapi_context": {
* "requestBody": {
* "content": {
* "application/ld+json": {
* "schema": {
* "type": "object",
* "properties": {
* "token": {"type": "string", "example": "email#example.com"},
* "refresh_token": {"type": "string", "example": "123456"},
* },
* },
* },
* },
* },
* },
* },
* }
* )

Take a look at this response on api-platform issue (documentation here is really nicely formatted in yaml instead of keeping them in php arrays, quite nice idea) and read the docs, they will probably help You to decorate documentation in any way You want to.

Related

How can i write this JSON swagger-php below with annotations?

I'm using OpenAPI 3.0 to document my Symfony API. this is the JSON code that authenticates the user to send requests:
"securitySchemes": {
"Bearer": {
"type": "http",
"description": "Entrer le token JST",
"scheme": "bearer",
"bearerFormat": "JWT"
}
}
},
"security": [
{
"Bearer": []
}
]
How can i write this with annotations in controller ?
thanks
You almost can; currently swagger-php does not support the bearerFormat property.
=> https://github.com/zircote/swagger-php/issues/1258
The rest would look something like this globally.
/**
* #OA\SecurityScheme(
* securityScheme="bearerAuth",
* type="http",
* scheme="bearer",
* description="Entrer le token JST"
* )
*/
For a controller to require security you'd add this:
/**
* #OA\Get(
* path="/api/endpoint",
* ...
* security={{ "bearerAuth": {} }}
* )
*/

Save entity with subresource into redis with Symfony & Api Platform

I have 2 entities, Event & User. So basically A user can have multiple event.
I have an EventItemDataProvider and in the getItem method I did something like that:
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?Event
{
if ($event = $this->cache->get($id, Event::class)) {
return $event;
}
$event = $this->em->getRepository(Event::class)->find($id);
if($event) {
$this->cache->set($id, $event, 120);
}
return $event;
}
When the data is returned without the cache I have this result that is correct with the right username:
{
"#context": "/contexts/Event",
"#id": "/events/20",
"#type": "Event",
"title": "new global event 3",
"description": "A big global event 3",
"createdAt": "2019-07-05T09:20:48+00:00",
"owner": {
"#id": "/users/3",
"#type": "User",
"username": "test3"
}
}
But if I hit the same method a second time, it will retrieve the data from the cache (redis) but this time username will be empty like that:
{
"#context": "/contexts/Event",
"#id": "/events/20",
"#type": "Event",
"title": "new global event 3",
"description": "A big global event 3",
"createdAt": "2019-07-05T09:20:48+00:00",
"owner": {
"#id": "/users/3",
"#type": "User",
"username": ""
}
}
Why is my username empty when I retrieve it from redis?
Basically my cache class has these methods:
public function get(string $uniq, string $className)
{
$value = $this->cache->getItem('test_key')->get();
return $value;
}
public function set(string $uniq, object $entity, int $expires = 60)
{
$save = $this->cache->getItem('test_key');
$save->set($entity);
$save->expiresAfter($expires);
$saved = $this->cache->save($save);
return $saved;
}
The owner field in my Event entity:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\User", inversedBy="events")
* #ORM\JoinColumn(nullable=false)
* #Groups({"event:read", "event:write", "event:update"})
* #Assert\Valid()
*/
private $owner;
The username field in my User entity:
/**
* #ORM\Column(type="string", length=25, unique=true)
* #Groups({"user:read", "user:write", "user:update", "event:item:get", "event:update"})
* #Assert\NotBlank(message="Please provide a username")
*/
private $username;
My redis cache configuration:
cache:
app: cache.adapter.redis
default_redis_provider: "redis://redis"
pools:
custom.cache.entity:
adapter: cache.app
default_lifetime: 120
Why my username is filled when I retrieve it from Doctrine Repository but empty when I retrieve it from Redis?
With Xdebug I can see that if I retrieve the data from Doctrine I have something like that:
But if I retrieve the data from the cache I have something like that:
In this second case, initializer -> this seems infinite, so I think that the problem is probably here, but what should I do to solve this issue?

Geofire query events not firing

I am making a Polymer web component for using Geofire's queries. I am using them in a separate app successfully (using js), but while testing the component, the query is not firing any events. The Firebase db does contain data.
Below is the relevant code, and here is the demo.
geofire-query.html
<link rel="import" href="../polymer/polymer.html">
<link rel="import" href="scripts.html">
<link rel="import" href="geofire-elements-behavior.html">
<!--
`geofire-query`
An element for working with GeoFire's [`GeoQuery`](https://github.com/firebase/geofire-js/blob/master/docs/reference.md#geoquery)s.
#polymerBehavior GeofireElementsBehavior
#demo demo/index.html
-->
<dom-module id="geofire-query">
<template>
<style>
:host {
/*display: block;*/
}
</style>
</template>
<script>
Polymer({
is: 'geofire-query',
properties: {
/**
* The latitude for the query's center.
*/
lat: Number,
/**
* The longitude for the query's center.
*/
lng: Number,
/**
* The radius in kilometers around the query's center.
*/
radius: Number,
/**
* Whether the query is active and listening for GeoQuery events.
*/
idle: {
type: Boolean,
value: false,
reflectToAttribute: true,
// observer: '_idleChanged'
},
/**
* An array managed by the GeoQuery events.
* Each element is an object with properties `key, lat, lng, location, distance`.
*/
resultsArray: {
type: Array,
notify: true
},
/**
* An object managed by the GeoQuery events.
* It is a map of key => {`lat, lng, location, distance`}.
*/
resultsObject: {
type: Object,
notify: true
}
},
observers: [
'_idleChanged(idle, _query)',
'_queryChanged(lat, lng, radius)'
],
behaviors: [
GeofireElementsBehavior
],
_idleChanged: function(idle, query) {
if (!query) return;
console.log('we have a query', query.center(), query.radius(), idle, query, 'app=', this.app);
if (!idle) {
this.resultsArray = [];
this.resultsObject = {};
console.log('setting listeners...');
this.onKeyEnteredRegistration = query.on('key_entered', function(key, location, distance) {
console.log('key-entered', key, location, distance);
this.resultsObject[key] = {
lat: location[0],
lng: location[1],
location: location,
distance: distance
};
var data = {
key: key,
lat: location[0],
lng: location[1],
location: location,
distance: distance
};
this.resultsArray.push(data);
this.fire('key-entered', data);
}.bind(this));
this.onKeyExitedRegistration = query.on("key_exited", function(key, location, distance) {
this.resultsObject[key] = null;
for (var i = 0; i < this.resultsArray.length; i++) {
if (this.resultsArray[i].key == key) {
this.resultsArray.splice(i, 1);
break;
}
}
this.fire('key-exited', {
key: key,
lat: location[0],
lng: location[1],
location: location,
distance: distance
});
}.bind(this));
this.onKeyMovedRegistration = query.on("key_moved", function(key, location, distance) {
this.resultsObject[key] = {
lat: location[0],
lng: location[1],
location: location,
distance: distance
};
var data = {
key: key,
lat: location[0],
lng: location[1],
location: location,
distance: distance
};
for (var i = 0; i < this.resultsArray.length; i++) {
if (this.resultsArray[i].key == key) {
this.resultsArray[i] = data;
break;
}
}
this.fire('key-moved', data);
}.bind(this));
this.onReadyRegistration = query.on("ready", function() {
console.log('ready');
this.fire('ready');
}.bind(this));
} else {
query.cancel();
query = null;
console.log('set query to null?', this._query === null);
}
},
_queryChanged: function(lat, lng, radius) {
if ((lat || lat === 0) && (lng || lng === 0) && radius) {
console.log('querying [', lat, lng, ']', radius, 'km');
var criteria = {
center: [lat, lng],
radius: radius
};
if (this._query) {
this._query.updateCriteria(criteria);
} else {
this._query = this._geofire.query(criteria);
}
}
}
/**
* Corresponds to the GeoQuery's `key_entered` event. Fires after updating the internal results.
* #event key-entered
* #param {key} string The location's identifier.
* #param {lat} number Latitude.
* #param {lng} number Longitude.
* #param {location} array [lat, lng] shorthand.
* #param {distance} number Distance from the query's center, in kilometers.
*/
/**
* Corresponds to the GeoQuery's `key_exited` event. Fires after updating the internal results.
* #event key-exited
* #param {key} string The location's identifier.
* #param {lat} number Latitude.
* #param {lng} number Longitude.
* #param {location} array [lat, lng].
* #param {distance} number Distance from the query's center, in kilometers.
*/
/**
* Corresponds to the GeoQuery's `key_moved` event. Fires after updating the internal results.
* #event key-moved
* #param {key} string The location's identifier.
* #param {lat} number Latitude.
* #param {lng} number Longitude.
* #param {location} array [lat, lng].
* #param {distance} number Distance from the query's center, in kilometers.
*/
/**
* Corresponds to the GeoQuery's `ready` event. Fired when the initial result set of the query is ready.
* #event ready
*/
});
</script>
</dom-module>
geofire-elements-behavior.html
<script>
/**
* #polymerBehavior
*/
GeofireElementsBehavior = {
properties: {
/**
* The Firebase app's name. If not specified, the default Firebase app will be used.
*/
app: String,
/**
* The path to this geofire's data inside the Firebase database.
*/
path: String
},
// observers: [
// '_geofireChanged(app, path)'
// ],
//
// _geofireChanged: function(app, path) {
// this._geofire = new GeoFire(firebase.app(app).database().ref(path));
// },
/**
* The [GeoFire](https://github.com/firebase/geofire-js/blob/master/docs/reference.md#geofire) instance.
*/
get _geofire() {
if (!this.geofireInstance) {
console.log('init geofire. app=', this.app, 'path=', this.path);
this.geofireInstance = new GeoFire(firebase.app(this.app).database().ref(this.path));
}
return this.geofireInstance;
}
};
</script>
It was a silly rules mistake :(
I had
{
"rules": {
"geofire1": {
"$key": {
".read": "true",
".write": "true"
}
}
}
}
But I needed
{
"rules": {
"geofire1": {
".read": "true",
".indexOn": "g",
"$key": {
".write": "true"
}
}
}
}

How to have absolute url with Hateoas Bundle on Symfony2

I just fresh installed the bundle from Willdurant's github and I got relative url like this:
"_links": {
"self": {
"href": "/1.0/users/?page=1&limit=10"
},
"first": {
"href": "/1.0/users/?page=1&limit=10"
},
"last": {
"href": "/1.0/users/?page=2&limit=10"
},
"next": {
"href": "/1.0/users/?page=2&limit=10"
}
}
For my Hateoas url, I really prefer absolute url but I don't find anything on google to change that. 2 hours of search, trying multiple keywords and nothing...
Thanks for your help.
You can use it many ways. All depends on the way of generating links.
If you use #Route annotation to generate link, it has a parameter absolute that need to be set to true:
/**
* #Hateoas\Relation(
* name = "self",
* href = #Hateoas\Route(
* "user_get",
* parameters = { "id" = "expr(object.getId())" },
* absolute = true
* )
* )
*/
If you use expression language to generate link you can pass true as third parameter to the link() function:
/**
* #Hateoas\Relation(
* "user",
* href = "expr(link(object.getUser(), 'self', true))"
* )
*/

JMS #Discriminator filed doesn't appear if specific group is serializing

I'm using Symfony 2.8, FOSRestBundle and JMSSerializerBundle.
Problem
Discriminator field type of Document entity doesn't apear in serialized model when I serialize specific group ("api" group in folowing example) of entity Citizen.
Doctrine Entities
Document:
namespace MyBundle\Entity;
use JMS\Serializer\Annotation as JMS;
…
/**
* #JMS\Discriminator(field = "type", map = {
* "doc1" = "MyBundle\Entity\Document1",
* "doc2" = "MyBundle\Entity\Document2"
* })
*/
class Document
…
Citizen:
class Citizen
{
…
/**
* #var ArrayCollection
*
* #ORM\OneToMany(
* targetEntity="MyBundle\Entity\Document",
* cascade={ "PERSIST", "REMOVE" },
* orphanRemoval=true,
* mappedBy="citizen"
* )
*
* #JMS\Groups({"api"})
*/
private $documents;
…
What I get
{
…
"documents": [
{
"number": "000000",
"date": "01.01.1970",
"serial": "0000",
"place": ""
}
],
…
}
What I need
{
…
"documents": [
{
"type": "doc1",
"number": "000000",
"date": "01.01.1970",
"serial": "0000",
"place": ""
}
],
…
}
If I remove specific serialization group, then type field is present in serialized output.
Thanks in advance
Just found issue on github.
Seems for now, workaround with Default group is needed, see lordelph's comment

Resources