I have an application that consumes a webservice. My app send request to webservice like this:
Request example:
https://mywebservice.com/interesting-route/?page=4&limit=30
So I receive just a slice of result, not the complete array, else I could use ArrayAdapter to paginate in my controller. I wanna use just the twig extension to generate DOM elements in my views.
Response example:
{
results:
[
{
title: 'Nice title',
body: 'Nice body'
},
...,
{
title: 'Nice title',
body: 'Nice body'
},
],
total: 1350,
limit: 30
]
What's the way, maybe using FixedAdapter?
Thanks
As you say, the FixedAdapter should do what you need here. Here's some sample controller code you could adapt:
public function someAction(Request $request)
{
$page = 1;
if ($request->get('page') !== null) {
$page = $request->get('page');
}
$totalResults = // call your webservice to get a total
$limit = 30;
$slice = $this->callMyWebserviceInterestingRoute($page, $limit);
$adapter = new FixedAdapter($totalResults, $slice);
$pagerfanta = new Pagerfanta($adapter);
$pagerfanta->setMaxPerPage($limit);
$pagerfanta->setCurrentPage($page);
return $this->render('default/pages.html.twig', [
'my_pager' => $pagerfanta,
]);
}
Hope this helps.
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 have this button and I'd like to make it so when I click on it, there's an ajax that goes directly into my create function in symfony but doesn't display any form (at this point I already have the informations I need). But I have no idea how to get the form that way.
I used to do
$livre = new Livre();
$livre->setUuid(Uuid::v4());
$form = $this->createForm(LivreType::class, $livre);
$form->handleRequest($request);
But obviously I can't use LivreType::class anymore cause I don't need the form.
I keep searching for information about this but I can't find anything
Any ideas?
You'll have multiple way of doing it.
I'm gonna show you a simple way to do it, and try to adapt or find a better way for doing it!
LivreController.php
/**
* #Route(path="/livre/create", methods={POST})
*/
public function createNewLivre(Request $request, EntityManagerInterface $em)
{
$json = $this->getJSON($request);
$newLivre = new Livre();
// Set to your new entity parameters...
$em->persist($newLivre);
$em->flush();
return $this->json([
'message' => 'A new Livre has been added.' // It could also be empty if you don't want to manage anything
]);
}
private function getJSON(Request $request)
{
$data = json_decode($request->getContent(), true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new HttpException(400, 'json invalid');
}
return $data;
}
script.js
let button = document.getElementById('livreCreatorButton');
button.addEventListener("click", e => {
e.preventDefault();
fetch('http://127.0.0.1:8000/livre/create', {
method: 'POST', // or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: '{}',
})
});
I try to add a custom user field to the user by using WPGraphQL. Therefore I tried to recreate the example in the official WPGraphQL documentation https://docs.wpgraphql.com/extending/fields/#register-fields-to-the-schema :
add_action('graphql_init', function () {
$hobbies = [
'type' => ['list_of' => 'String'],
'description' => __('Custom field for user mutations', 'your-textdomain'),
'resolve' => function ($user) {
$hobbies = get_user_meta($user->userId, 'hobbies', true);
return !empty($hobbies) ? $hobbies : [];
},
];
register_graphql_field('User', 'hobbies', $hobbies);
register_graphql_field('CreateUserInput', 'hobbies', $hobbies);
register_graphql_field('UpdateUserInput', 'hobbies', $hobbies);
});
I already changed the type from \WPGraphQL\Types::list_of( \WPGraphQL\Types::string() ) to ['list_of' => 'String'].
If I now execute the updateUser mutation my hobbies don't get updated. What am I dowing wrong?
Mutation:
mutation MyMutation {
__typename
updateUser(input: {clientMutationId: "tempId", id: "dXNlcjox", hobbies: ["football", "gaming"]}) {
clientMutationId
user {
hobbies
}
}
}
Output:
{
"data": {
"__typename": "RootMutation",
"updateUser": {
"clientMutationId": "tempId",
"user": {
"hobbies": []
}
}
}
}
Thanks to xadm, the only thing I forgot was to really mutate the field. I was a bit confused by the documentation, my fault. (I really am new to WPGraphQL btw)
Here's what has to be added:
add_action('graphql_user_object_mutation_update_additional_data', 'graphql_register_user_mutation', 10, 5);
function graphql_register_user_mutation($user_id, $input, $mutation_name, $context, $info)
{
if (isset($input['hobbies'])) {
// Consider other sanitization if necessary and validation such as which
// user role/capability should be able to insert this value, etc.
update_user_meta($user_id, 'hobbies', $input['hobbies']);
}
}
I want to upload a picture from react-native to PHP Symfony server. I pick the picture with ImagePicker.showImagePicker and send it with RNFetchBlob.fetch, but in Symfony the file seems to be empty. The $file->getMimeType() return "file does not exist or is not readable" and the content type of the file is octet-stream. It Should be image/jpeg.
Any Idea ?
Thanks for help :)
PHP code:
private function uploadFile(Request $request, $actualFilename = null)
{
$file = $request->files->get('userfile');
var_dump($file->getMimeType());
}
React-native code :
const options = {
title: 'Select Photo',
takePhotoButtonTitle: "Take photo title",
chooseFromLibraryButtonTitle: "Choose a photo",
quality: 1
};
ImagePicker.showImagePicker(options, response => {
deviceStorage.getItem('jwt').then(jwt => {
const endpoint = 'someEndpoint'
RNFetchBlob.fetch('POST', endpoint, {
Authorization : "Bearer " + jwt,
otherHeader : "foo",
'Content-Type' : 'multipart/form-data',
}, [
{ name : 'userfile', filename : 'image.jpg', type:'image/jpeg', data: response.data}
]).then((resp) => {
console.log(resp);
}).catch((err) => {
console.log(err);
});
});
});
You have passing the image data, so in the server you get a base64, to retrieve the image you will for example:
private function uploadFile(Request $request, $actualFilename = null){
$base64Image = $request->get('userfile');
$data = base64_decode($base64Image);
file_put_contents($imagePath, $data);
}
$imagePath is where you save the image.
If you use vichuploader to manage the images take a look here base64-image-to-image-file-with-symfony-and-vichuploader
I have a project in symfony that I would like to let my users upload an image for their "avatar" field. I have found many posts about how to "extend" the table which I have with the schema below:
Member:
inheritance:
type: column_aggregation
extends: sfGuardUser
columns:
idmember: { type: integer }
birthday: { type: date }
avatar: { type: string(255) }
bio: { type: string(255) }
The columns get added to the table just fine, but when I go to change the widget to a sfWidgetFormInputFileEditable it breaks. Here is the Form.class file:
$file_src = $this->getObject()->getAvatar();
if ($file_src == '')
{
$file_src = 'default_image.png';
}
$this->widgetSchema['avatar'] = new sfWidgetFormInputFileEditable(array(
'label' => ' ',
'file_src' => '/uploads/avatar/'.$file_src,
'is_image' => true,
'edit_mode' => true,
'template' => '<div>%file%<br />%input%</div>',
));
and "save" function of the form:
if($this->isModified())
{
$uploadDir = sfConfig::get('sf_upload_dir');
$thumbnail = new sfThumbnail(150, 150);
$thumbnail2 = new sfThumbnail(800, 800);
if($this->getAvatar())
{
$thumbnail->loadFile($uploadDir.'/avatar/'.$this->getAvatar());
$thumbnail->save($uploadDir.'/avatar/thumbnail/'. $this->getAvatar());
$thumbnail2->loadFile($uploadDir.'/avatar/'.$this->getAvatar());
$thumbnail2->save($uploadDir.'/avatar/big/'. $this->getAvatar());
}
}
When I submit the form, I get this error message:
This form is multipart, which means you need to supply a files array as the bind() method second argument.
In the action where you bind the form you should use something like this:
$form->bind($request->getParamater($form->getName()), $request->getFiles($form->getName()));
So you need to pass the uploaded files as the second parameter to the bind method.