My simple controller:
class GeofencesController extends Controller
{
public function indexAction()
{
$json = '[
{
"id": 123,
"name": "muh",
"latitude": 32.121456,
"longitude": -19.238573,
"radius": 500
},
{
"id": 532,
"name": "blah",
"latitude": 32.121456,
"longitude": -19.238573,
"radius": 100
},
{
"id": 720,
"name": "bleh",
"latitude": 32.121456,
"longitude": -19.238573,
"radius": 200
}
]
';
$json = json_decode($json, true);
$response = new Response();
$response->setContent(json_encode($json));
$response->headers->set('Content-Type', 'application/json');
return $response;
}
is giving me a malformed chunked response. For example in java:
org.apache.http.MalformedChunkCodingException: Chunked stream ended unexpectedly
Chrome refuses to show the response, in Firefox i can see the response and Fiddler2 detects there is a Malformation with the response.
EDIT:
Also tried:
class GeofencesController extends Controller
{
public function indexAction()
{
$json = '[{"id": 123,"name": "bleh","latitude": 32.121456,"longitude": -19.238573,"radius": 500}]';
$json = json_decode($json, true);
$response = new JsonResponse($json);
return $response;
}
and still same problem. Is there a possibility this is related to Apache? Or Symfony2 config?
Try using JsonResponse object. I know it's supposed to be same" but I have seen some difference in Firefox...
Related
I'm trying to make a collection of subresources selectable in GraphQL (with pagination). I'd like to be able to query:
query {
getA(id: '/api/A/1') {
aId
subresources {
totalCount
pageInfo {
endCursor
startCursor
hasNextPage
hasPreviousPage
}
edges {
node {
bId
}
}
}
}
}
and get the result:
{
aId: 1,
subresources: {
"totalCount": XX,
"pageInfo": {
"endCursor": "MQ==",
"startCursor": "MA==",
"hasNextPage": true,
"hasPreviousPage": false
},
edges: [
{
node: {
bId: 11
}
},
{
node: {
bId: 12
}
},
{
node: {
bId: 13
}
}
]
}
}
I'm not using Doctrine at all- I'm using custom data providers. The problem I'm encountering is that even when I return an A entity from DataProvider::getItem() that has an array of B subresources, I get an empty array for subresources in GraphQL. I get the correct data in REST though.
I'm following the instructions given in SymfonyCasts and I found a related API Platform issue, but I'm still having no luck.
I traced through API Platform core and I think it has to do with how the entity is normalized in GraphQL. Specifically, an empty array is returned in ItemNormalizer::normalizeCollectionOfRelations(). However, there's a comment saying "to-many are handled directly by the GraphQL resolver" but I'm not sure what that refers to.
Here's the entity code.
use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use Symfony\Component\Serializer\Annotation\Groups;
#[ApiResource(
graphql: ['item_query', 'collection_query', 'create', 'update', 'delete'],
collectionOperations: ['get', 'post'],
itemOperations: ['get', 'put', 'patch', 'delete'],
normalizationContext: ['groups' => ['read']],
denormalizationContext: ['groups' => ['write']],
)]
class A {
#[ApiProperty(identifier: true)]
#[Groups(['read', 'write'])]
public ?int $aId = null,
/** #var B[] */
#[ApiProperty(readableLink: true, writableLink: true)]
#[Groups(['read', 'write'])]
public $subresources = []
}
And:
#[ApiResource(
graphql: ['item_query', 'collection_query', 'create', 'update', 'delete'],
collectionOperations: ['get', 'post'],
itemOperations: ['get', 'put', 'patch', 'delete'],
normalizationContext: ['groups' => ['read']],
denormalizationContext: ['groups' => ['write']],
)]
class B {
#[ApiProperty(identifier: true)]
#[Groups(['read', 'write'])]
public ?int $bId = null,
}
My ADataProvider:
public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): A {
$bs = $this->bDataProvider->getCollection(B::class, null, []);
return new A(123, $bs);
}
My BDataProvider:
/**
* #return ArrayPaginator<B>
*/
public function getCollection(string $resourceClass, string $operationName = null, array $context = []): ArrayPaginator {
return ArrayPaginator::fromList([new B(11), new B(12), new B(13)]);
}
ArrayPaginator implements IteratorAggregate and PaginatorInterface.
Specifically I see this error:
{
"errors": [
{
"debugMessage": "Collection returned by the collection data provider must implement ApiPlatform\\Core\\DataProvider\\PaginatorInterface or ApiPlatform\\Core\\DataProvider\\PartialPaginatorInterface.",
"message": "Internal server error",
"extensions": {
"category": "internal"
},
"locations": [
{
"line": 29,
"column": 5
}
],
"path": [
"a",
"b"
],
"trace": [
{
"file": "/homedir/core/src/GraphQl/Resolver/Stage/SerializeStage.php",
"line": 100,
"call": "ApiPlatform\\Core\\GraphQl\\Resolver\\Stage\\SerializeStage::serializeCursorBasedPaginatedCollection(array(0), array(5), array(6))"
},
TLDR: How does one use annotations (or YAML) to make attributes that are collections of subresources selectable in GraphQL?
Any help/ideas are appreciated, thanks for reading!
Found a solution: the ApiPlatform\Core\DataProvider\SubresourceDataProviderInterface needs to be implemented by the BDataProvider.
It gets used in the ReadStage of api platform's graphql resolver. Surprisingly, it's found nowhere in the REST resolver, so this won't get called on a REST request.
The only method that needs to be implemented is getSubresource(). My basic first implementation looks like this:
public function getSubresource(string $resourceClass, array $identifiers, array $context, string $operationName = null) {
if ($context['collection']) {
return $this->getCollection($resourceClass, $operationName, $context);
}
$id = // get your id from $identifiers;
return $this->getItem($resourceClass, $id, $operationName, $context);
}
This isn't found in the docs unfortunately, but there are a few pulls (1, 2) open to add it.
I'd like to have advice on the best practice to achieve something.
I've a JSON output of an entity called "Session" with 3 related entities (platform, user, course ) and I'd like to use the nested way to create those 3 related if they don't exist.
But If they do exist I'd like to add their IRI (or ID) to the JSON output before API Platform does the magic. (or another way to achieve this behavior)
I naively thought that I should bind to the pre_denormalization event but I've no idea how to return the data to the event.
Here is what I've got so far.
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onSessionDenormalize', EventPriorities::PRE_DESERIALIZE],
];
}
public function onSessionDenormalize(RequestEvent $event)
{
$data = $event->getRequest()->getContent();
}
public function modifyPayLoad($data) {
$dataObject = json_decode($data);
$platform = $dataObject->platform;
$user = $dataObject->user;
$course = $dataObject->course;
if($this->platformRepository->findOneBy(['slug' => $platform->slug])) {
$platformID = $this->courseRepository->findOneBy(['slug' => $platform->slug])->getId();
$dataObject->id = $platformID;
if($this->userRepository->findOneBy(['email' => $user->slug])) {
$dataObject->id = $this->userRepository->findOneBy(['email' => $user->slug])->getId();
$dataObject->user->platform->id = $platformID;
}
if($this->courseRepository->findOneBy(['slug' => $course->slug])) {
$dataObject->id = $this->courseRepository->findOneBy(['slug' => $course->slug])->getId();
$dataObject->course->platform->id = $platformID;
}
}
return json_encode($dataObject);
}
And the JSON:
{
"user": {
"firstname": "string",
"lastname": "string",
"address": "string",
"city": "string",
"email": "string",
"zipCode": int,
"hubspotID": int
},
"course": {
"id": "string",
"title": "string",
"platform": {
"slug": "string",
"name": "string"
}
},
"startDate": "2022-01-09T23:59:00.000Z",
"endDate": "2022-02-09T23:59:00.000Z",
"hubspotDealId": int
}
I can't get the ID in this JSON since those information are provided by a Puppeteer APP, or I should do 3 request to check if the related entity exist first, which is not adviced I think.
I also tried to change the identifier on the user, course and platform but In both cases, I've duplicate entries in database
I manage to do what I want with a custom denormalizer.
So I can post and update data comming from tierce source without ID.
class SessionDenormalizer implements DenormalizerAwareInterface, ContextAwareDenormalizerInterface
{
use DenormalizerAwareTrait;
public function __construct(
private UserRepository $userRepository,
private PlatformRepository $platformRepository,
private CourseRepository $courseRepository,
private SessionRepository $sessionRepository,
)
{
}
private const ALREADY_CALLED = 'SESSION_DENORMALIZER_ALREADY_CALLED';
public function supportsDenormalization($data, string $type, string $format = null, array $context = []): bool
{
if (isset($context[self::ALREADY_CALLED])) {
return false;
}
return $type === Session::class;
}
public function denormalize($data, string $type, string $format = null, array $context = [])
{
if (isset(
$data["user"]["email"],
$data["course"]["slug"],
$data["course"]["platform"]["slug"],
)) {
$user = $this->userRepository->findOneBy(["email" => $data["user"]["email"]]);
$course = $this->courseRepository->findOneBy(["slug" => $data["course"]["slug"]]);
$platform = $this->platformRepository->findOneBy(["slug" => $data["course"]["platform"]["slug"]]);
if ($user && $course && $platform) {
$data["user"]["#id"] = "/v1/users/" . $user?->getId();
$data["course"]["#id"] = "/v1/courses/" . $course?->getId();
$data["course"]["platform"]["#id"] = "/v1/platforms/" . $platform?->getId();
$session = $this->sessionRepository->findOneBy(["cpfID" => $data["cpfID"]]);
if($session) {
$data["#id"] = "/v1/sessions/" . $session->getId();
if(isset($context["collection_operation_name"])) {
$context["collection_operation_name"] = "put";
}
if(isset($context['api_allow_update'])) {
$context['api_allow_update'] = true;
}
}
}
}
$context[self::ALREADY_CALLED] = true;
return $this->denormalizer->denormalize($data , $type , $format , $context);
}
}
Services.yaml :
'app.session.denormalizer.json':
class: 'App\Serializer\Denormalizer\SessionDenormalizer'
tags:
- { name: 'serializer.normalizer', priority: 64 }
I am sorry for the title being so non-descriptive but the question I am asking is way to broad to fit in 10 words.
when i use symfony entity createAt and updateAt type is datetime, when i wrote api,the result is bad.I not want change createAt and updateAt to string type.thanks
entity defiend is like this:
/**
* #var \DateTime
*
* #ORM\Column(name="updateAt", type="datetime", options={"comment":"更新时间"})
*/
private $updateAt;
the method in repository :
public function getList($userId, $isMime)
{
$qb = $this->createQueryBuilder('c')
->select('c.title,c.nodeName as node_name, c.node, c.updateAt as datetime');
if($isMime == 0){
$qb->leftJoin(ApprovalInformation::class,'a','WITH','a.flowId = 11 and a.itemId = c.id');
$qb->where('a.approverId = :uid');
}else{
$qb->where('c.userId = :uid');
}
$qb->setParameter('uid', $userId);
return $qb->getQuery()->getResult();
}
controller
$list = $cardRepo->getList($this->user->getId(), $mime);
return $this->json([
'code' => 1,
'msg' => '获取成功',
'data' => $list
]);
{
"code": 1,
"msg": "获取成功",
"data": [
{
"title": "测试报告",
"node_name": "处长批示",
"node": 11,
"datetime": {
"date": "2019-05-07 19:04:00.000000",
"timezone_type": 3,
"timezone": "Asia/Shanghai"
}
},
{
"title": "测试报告2222",
"node_name": "处长审批",
"node": 2,
"datetime": {
"date": "2019-05-07 19:28:14.000000",
"timezone_type": 3,
"timezone": "Asia/Shanghai"
}
}
]
}
my except result like is:
{
"code": 1,
"msg": "获取成功",
"data": [
{
"title": "测试报告",
"node_name": "处长批示",
"node": 11,
"datetime": "2019-05-07 19:04:00",
},
{
"title": "测试报告2222",
"node_name": "处长审批",
"node": 2,
"datetime": "2019-05-07 19:28:14",
}
]
}
I want to know an efficient solution.thanks
You are using $this->json() in your controller which will serialize your entity using json_encode(). This is what is causing the datetime to be returned like this:
$ php -a
php > echo json_encode(new DateTime());
{"date":"2019-05-07 14:20:22.137677","timezone_type":3,"timezone":"UTC"}
There is multiple ways around this. You can create an array from your data before calling $this->json():
return $this->json(
array_map(
function ($entity) {
return [
'title' => $entity->getTitle(),
...
'updateAt' => $entity->getUpdateAt()->format('Y-m-d H:i:s'),
];
},
$list
)
);
Alternatively you could use the Symfony Serializer: https://symfony.com/doc/current/components/serializer.html#serializing-an-object
If you use the Serializer, make sure you also have the DateTimeNormalizer registered.
Just do it following code in your getUpadateAt function in entity:
return $this->updateAt->format('Y-m-d h:I:s);
I'm following the facebook messenger develop QuickStart to create a Node.js project, and I improved it to work in quick reply. Then when I tried the Generic Template and List Template, but it didn't work.
As the following source code, when I input the work "generic" or "list", the messenger should reply me with the template messege. But there was nothing happened.
} else if (received_message.text === 'generic') {
console.log('generic in');
response = {
"attachment":{
"type":"template",
"payload":{
"template_type":"generic",
"elements":[
{
"title":"Welcome!",
"image_url":"http://webapplication120181023051009.azurewebsites.net/colorcar1.jpg",
"subtitle":"We have the right hat for everyone.",
"default_action": {
"type": "web_url",
"url": "https://www.taobao.com/",
"messenger_extensions": false,
"webview_height_ratio": "tall",
"fallback_url": "https://www.taobao.com/"
},
"buttons":[
{
"type":"web_url",
"url":"https://www.taobao.com/",
"title":"View Website"
},{
"type":"postback",
"title":"Start Chatting",
"payload":"DEVELOPER_DEFINED_PAYLOAD"
}
]
}
]
}
}
}
// Sends the response message
callSendAPI(sender_psid, response);
// Sends response messages via the Send API
function callSendAPI(sender_psid, response) {
// Construct the message body
let request_body = {
"recipient": {
"id": sender_psid
},
"message": response
}
console.log('PAGE_ACCESS_TOKEN:');
console.log(PAGE_ACCESS_TOKEN);
console.log('request body:');
console.log(request_body);
// Send the HTTP request to the Messenger Platform
request({
"uri": "https://graph.facebook.com/v2.6/me/messages?access_token=" + PAGE_ACCESS_TOKEN,
"qs": { "access_token": PAGE_ACCESS_TOKEN },
"method": "POST",
"json": request_body
}, (err, res, body) => {
if (!err) {
console.log('message sent!')
} else {
console.error("Unable to send message:" + err);
}
});
}
Sorry, I forgot to add the url into whiltelist.
I'm retrieving a POSTed file with:
$this->app->post(Extension::API_PREFIX . "profile/picture", array($this, 'post_profile_pic'))
->bind('post_profile_pic');
public function post_profile_pic(Request $request) {
$response = $this->app->json(array(
'file' => $request
));
return $response;
}
I'm using Postman to upload the file (see screenshot), but the request is empty:
{
"file": {
"attributes": { },
"request": { },
"query": { },
"server": { },
"files": { },
"cookies": { },
"headers": { }
}
}
Yet it obviously knows that there should be a file there. So how do I access the file?
Uploaded files in PHP are not sent with $_POST but in a separate variable called $_FILES ( http://php.net/manual/en/features.file-upload.post-method.php ) - so in your case you should take a look at
$this->app->files
If you use the default form components from symfony the documentation is at http://symfony.com/doc/current/reference/forms/types/file.html#basic-usage
In the simpleforms module files are retrieved by the lines following
$files = $this->app['request']->files->get($form->getName());
Which is using the basic structure that the Bolt, Silex and Symfony components provide.
(See also https://github.com/jadwigo/SimpleForms/blob/master/Extension.php#L505)