I am having a simple REST API developed with Symfony 6. I am using the following serializer for my application:
$encoders = [new JsonEncoder()];
$extractor = new PropertyInfoExtractor([], [new PhpDocExtractor(), new ReflectionExtractor()]);
$responseNormalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter(),null, $extractor);
$dateTimeNormalizer = new DateTimeNormalizer([DateTimeNormalizer::FORMAT_KEY => DateTimeType::DEFAULT_FORMAT]);
$arrayDenormalizer = new ArrayDenormalizer();
$normalizers = [$arrayDenormalizer, $dateTimeNormalizer, $responseNormalizer];
$serializer = new Serializer($normalizers, $encoders);
return $serializer->denormalize($data, $className);
I realized my response times are very slow and figured out that the ->denormalize() call is the bottleneck. Even though these tests were made on local environment I noticed that when I make simple GET request for products, this returns 20 items per page (paginated response) and denormalize call for each of these 20 items takes around 300-400ms on average.
My question is, is there anything I am doing obviously wrong, a way to optimize my Serializer definition? I found in couple of posts that ObjectNormalizer is the problematic slow one but didn't manage to find any concrete solutions how to optimize it?
Related
I have a standard symonfy(6) app and api-platform(3) configured for some vue apps that are embedded on specific pages of my symfony app.
I want to use the json-ld serialization used by api-platform in non api code.
I can use the symfony serializer, and this works:
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizer = new ObjectNormalizer();
$serializer = new Serializer([$normalizer], ['json' => new JsonEncoder()]);
$output = $serializer->serialize($entity, 'json', ['groups' => 'read'])
but that lacks some useful features, like the iri's of relatet entities ..
i would like to have the same serialization output, that apiPlatform generates.
How can i call the serializer to get exact the same result as i get from a configured url/endpoint?
Why do i want this: i have an vue app that needs to collect data from many endpoints for initialization. i want to prepare and give that data via the symfony-template that holds the entrypoint for the vue app.
Update:
I figured out a partial solution after some debugging the normal api process, but i'm sure i didnt see all consequences. you can use the api-platform serializer service in your services.
I build a wrapper service around the api-platform serializer, so i can autowire it in my controller:
App\Utils\Api\OfflineApi:
arguments: ['#api_platform.serializer']
controller:
public function action(EntityClass $entity, OfflineApi $offlineApi): Response {
$offlineApi->serialize($entity, ['groups' => 'read']),
Imagine situation where I have a real client which I want to use in my consumer unit test, and this client is making two requests in background.
Like:
$service = new \TestClient($config->getBaseUri());
$serviceResponse = $service->getUserDataById(1);
And getUserDataById method is sending two calls to provider before returning user data
GET someprovider/v1/username/{id}
GET someprovider/v1/email/{id}
and if I want to use this client in unit test I need to mock both endpoints
How to do this in one interaction?
Example with one consumer response https://github.com/mattermack/pact-php-example/blob/master/example-one/test/ExampleOneMeetupAPIClientTest.php
You can try registering two interactions within 1 unit test
For example:
$interaction1 = new InteractionBuilder(config);
$interaction2 = new InteractionBuilder(config);
$consumerRequest1 = new ConsumerRequest();
$consumerRequest1
->setPath('someprovider/v1/username/{id}');
$providerResponse1 = new ProviderResponse();
$providerResponse1
->setBody(/* body here */);
$interaction1
->uponReceiving('request to /someprovider/v1/username/{id}')
->with($consumerRequest1)
->willRespondWith($providerResponse1);
And same for second interaction
Then you can call the
$service = new \TestClient($config->getBaseUri());
$serviceResponse = $service->getUserDataById(1);
It will hit both endpoints and trigger both interactions.
At least that what I have done in my tests
I am using the PHP-DI 5 dependency injection container and I have already read the documentation about the definitions caching. Though I am still not sure in this regard... So I would like to ask you:
1) If I directly set an object as an entry value in the container, will the entry be cached?
$builder = new ContainerBuilder();
$builder->setDefinitionCache(new ApcCache());
$container = $builder->build();
$response = new Response();
// Will this entry be cached?
$container->set(ResponseInterface::class, $response);
2) Now let's say the object is already defined in the container, in a definitions file:
return [
'response' => function () {
return new Response();
},
];
If I perform the following:
$builder = new ContainerBuilder();
$builder->setDefinitionCache(new ApcCache());
$container = $builder->build();
// Will this entry be cached?
$container->set(ResponseInterface::class, DI\get('response'));
will the entry be cached, or
will an error be raised, or
will the entry be "silently" not cached?
Thank you very much.
It seems you are confused as to what "caching" means.
What is cached are definitions. A definition describes how to create an object. It is cached because reading the configuration files, or reading PHP's reflection, or reading annotations can be expensive.
1) If I directly set an object as an entry value in the container, will the entry be cached?
Since the object is set directly there is no definition. So there is nothing cached.
2) Now let's say the object is already defined in the container, in a definitions file:
If the definition is a closure (anonymous function) like in your example then it will not be cached because closures cannot be stored into a cache.
If you use something else than a closure then the definition will be cached to avoid reading the configuration file at runtime on every HTTP request.
Are you confusing the cache with "singletons"? Maybe this documentation can help.
I am new to google datastore, and I cannot get all entities under a kind. Here is my code.
use Google\Cloud\Datastore\DatastoreClient;
use Google\Cloud\Datastore\Query\Query;
function get_datastore_client(){
# Your Google Cloud Platform project ID
$projectId = 'my-project-id';
# Instantiates a client
$datastore = new DatastoreClient([
'projectId' => $projectId
]);
return $datastore;
}
function get_athlete_list(DatastoreClient $datastore){
$transaction = $datastore->transaction();
$query = $datastore->query()
->kind('Athlete')
->order('country');
$result = $transaction->runQuery($query);
$transaction->commit();
return $result;
}
$datastore = get_datastore_client();
$all_athletes = get_athlete_list($datastore);
And when I ran this, I got empty result. Please help me to find out what is wrong.
Update:
I have tried using datastore instead of transaction and it didn't work, too. Function without transaction is below.
function get_athlete_list(DatastoreClient $datastore){
$query = $datastore->query()
->kind('Athlete')
->order('country');
$result = $datastore->runQuery($query);
return $result;
}
And the strange thing is I can get one entity by its ID, function is below. I just cannot get all entities.
(This function works.)
function lookup_athlete(DatastoreClient $datastore, $athlete_id){
$transaction = $datastore->transaction();
$kind = 'Athlete';
$athleteKey = $datastore->key($kind, $athlete_id);
$athlete = $transaction->lookup($athleteKey);
if(empty($athlete)){
$transaction->rollback();
}
return $athlete;
}
So I think that maybe a setting issue, but I just cannot find out. I ran this in a compute engine instance and I have an APP engine instance runnning, according to this document, Accessing the Cloud Datastore API from a Compute Engine instance.
Update with solution:
After hours of research, I found that in datastore library, "yield" is used instead of "return". This means the returned variable is just a generator object. And I didn't use the generator, so I cannot get the generated data. And that is why I got the issue. BTW, all my code is fine. :)
Thank you for all the replies.
I'm not a PHP developer, but looks like you are using a transaction to load all records from an entity. This is not permitted because queries inside a transaction should be ancestor queries. My guess is that you are probably getting an exception/error indicating the same, but is being swallowed somewhere. If all you need to read the entities, you don't need a transaction, just use the datastore.
I am not certain if I am maybe misunderstanding, something in the examples I see, but any time I try to make sure of nsComplexObject, I get an error that it does not exist.
I am specifically trying to create a sales order. I set up my array of values, but when I try to do the following, I get an error:
<?php
require_once '../PHPToolkit/NetSuiteService.php';
$service = new NetSuiteService();
$salesOrder = new nsComplexObject('SalesOrder');
?>
I think you're looking at older Toolkit examples.
The newer versions (I think since 2012_2) use a different method for instantiating objects, such as:
$salesOrder = new SalesOrder();
You can still use the setFields method to populate object properties, but you can also populate them directly now:
$salesOrder->entity = $someRecordRefObject;