I have a Zend Framework 2 project using Apigility, and I want to be able to send an array of objects via POST to create multiple entities at once. However, ZF/Rest/Resource automatically converts arrays to objects when making a POST. To make the logic a bit cleaner, I would like to convert the data array to an object of my liking (putting the array into a key such as 'storage') before it reaches the Resource.
// ZF\Rest\RestController
public function create($data) //$data is an array
{
$events = $this->getEventManager();
$events->trigger('create.pre', $this, array('data' => $data));
try {
// I want to convert $data to an object by this point
$entity = $this->getResource()->create($data);
} catch (\Exception $e) {
return new ApiProblem($this->getHttpStatusCodeFromException($e), $e);
}
I thought there must be a way to hook into the create.pre event to do this. I've attached a method which gets the Request from the Event, and gets, converts, and sets the Content of the Request, but my debugger says the Resource is still receiving the original array. I've also tried $event->setParam('data', $object), and this didn't work either. (I assume because the parameters are an array and not passed by reference.) Am I going about this the wrong way, or is this not possible?
Related
I use the symfony serializer to deserialize REST server answers to objects.
The data returned by the server is someting like this (pseudocode, the answer itself is JSON):
// Endpoint 1
class Paginated {
public items:Object1[]
public page:int
}
// Endpoint 2
class Paginated {
public: items:Object2[]
public page:int
}
So, every answer is wrapped in the same "Paginated" object.
Sice I don't want to repeat the common members in every object I want to implement the objects in my Symfony app the same way as described in this pseudo code.
The problem is, that PHP isn't supporting generics to typehint the "items" member and the symfony serializer doesn't seem to offer something similar.
So, whats the best way to tackle this problem?
An easy way to solve the issue is by using the callbacks context option. In that callback you can just deserialize the objects you are going to pass into Paginated yourself. You would then have different callbacks for each object-type you want to support in Paginated and register it. It doesn't have to be a closure like shown in the docs, you can also use a class with __invoke() to make it easier to reuse in different places.
Another way to solve this more generically is, by writing a custom Denormalizer that implements the DenormalizerAwareInterface (so it can delegate denormalization of the nested items back to the serializer.
Much like Symfony can recognize something like Object[] as a list of Objects, you can created your own custom type convention to simulate a Generic.
Assuming you want this to be something like Paginated<Object1>, then your serializer call would probably look something like this:
$serializer->deserialize($json, Paginated::class . '<' . Object1::class . '>', 'json');
Your (de)normalizer will then support the type matching the regex. Inside the denormalize method you would then take the array structure of your json, call something like `$denormalizedItems = $this->denormalizer->denormalize($data['items'], Object1::class . '[]'); and then put them into your Paginated object. Roughly like this:
public function denormalize($data, string $type, string $format = null, array $context = [])
{
$extractedObjectType = ...; #extract class name inside <>
$data['items'] = $this->denormalizer->denormalize($data['items'], $extractedType, $format, $context);
// Option 1: Delegate denormalizing Paginated with the adjusted data
return $this->denormalizer->denormalize($data, Paginated::class, $format, $context);
// Option 2: Denormalize Paginated yourself and pass adjusted data as argument
return new Paginated($data['items'], (int) $data['page']);
}
I'm very new to Laravel and I was given a Laravel project, where I need to add some new features. The person, who has previously worked on that project hadn't left even a single comment in the code and now I must make my own scenarios about the features.
I have a controller, defined with some functions (dashboard, show_project, save_project etc.) and in one of my function, I need to use the result of calling other function.
In the concrete example, the call is made from "http://127.0.0.1:8000/username/project_slug" - there is a button "Save" and post function, called on onClick event. The function, whose output I need is normally called on "http://127.0.0.1:8000/username/project_slug/svg", which returns a view.
For better understanding, there's an example of the flow:
The user wants to save his/her project (an UML diagram) but in order to have a thumbnail, a function which generates a view (SVG format) will be called and the idea is, to take the HTML content of the page, which is on "http://127.0.0.1:8000/username/project_slug/svg" and to pass it to another API in order an image to be generated.
So far, I tried with cURL, file_get_contents, file_get_html, render methods but when I return the output, the server just keeps waiting and shows no error messages.
//The both functions are in ProjectController.php
/**
* A function, for saving the json file, where the whole of the diagram
* components are described. From the frontend we receive the project_id and
* the project_data(the json content).
*/
public function save_project(Request $request) {
$input = $request->only(['project_id', 'project_data']);
/*
Here we need to call the other function, to render the HTML content
and to pass it to the other API. Then we save the result with the
other information.
*/
/*
What I've tried?
$new_link = 'http://' . $_SERVER['HTTP_HOST'] . "/$username"
."/$project_slug" . "/svg";
$contents = file_get_contents($new_link);
return $contents;
*/
//In the same way with cURL.
$project = Project::where('user_id',session('userid'))
->where('id',$input['project_id'])->first();
$project->project_data = json_encode($input['project_data']);
if($project->save()) {
return ["status"=>"saved"];
}
else {
return ["status"=>"error"];
}
}
/**
* A function, which takes the the Json content (project_data) from the
* database and passes it to the view, where the Json is transformed in HTML
* tags.
*/
public function generate_svg(Request $request,$username,$project_slug) {
if(session('username')!=$username) {
return redirect("/");
}
$userid = session('userid');
$project = Project::where([
'user_id' => $userid,
'slug' => $project_slug,
])->first();
if(!is_null($project)) {
return view('svg',compact('project'));
}
}
I've read about some possible ways, including Guzzle request but maybe I haven't understood correctly the idea:
If I need to make a Guzzle request from my controller to the other function inside my controller, do I need an API configuration?
What I mean? Example:
Before saving the project, the user is on this URL address "http://127.0.0.1:8000/hristo/16test". Inside the controller, I have in session variables the token, the username(hristo) and i can get the project_name(16test) from the URL but after passing this URL to the generate_svg function, there is no indication of error or success.
So I'm missing some kind of token information?
If you just need the response of the other function you can just use
$response = $this->generate_svg($request, $username, $project_slug);
If you'll need to use this function from a different controller you can use this
app('App\Http\Controllers\UsernameController')->generate_svg($request, $username, $project_slug);
I have an API to consume for a service that provides finance quotations on used cars. My app is written in PHP and I have Guzzle 5 added via Composer.
I have used other APIs previously that have take XML or just an Array of POST parameters to send, but this one is more complex.
This API uses DTO objects and the documentation says this:
relies heavily on DTOs to carry data between client and server. The following
sections detail the DTOs. Each web service will serialise and transfer them in their own
formats/methods. It is the responsibility of the client application to correctly construct requests and
parse responses. It is suggested that object serialization and deserialization be used for easier usage.
So I have no idea how to achieve this with Guzzle. Some of the enumeration types are things such as "RequestAssetMotorVehicle". Would you use StdClass or Arrays doing this in PHP? Or classes? How would I serialise it?
Guzzle Docs
Without the API's documentation this is difficult to express. But I'll try. We'll use a generic JSON based REST API
DTO standards are usually per company and sometimes per application. In short: A DTO is a serialized object.
Let's say this is a POST request ( we're creating a new user)
{
'name':'john',
'foo':'bar',
'site':'stackoverflow.com'
}
That JSON is a DTO. Now let's do a GET
{
'error':false,
'results':2,
'data': [{'name':'john','foo':'bar','site':'stackoverflow.com'},
{'name':'mark','foo':'bar','site':'notstackoverflow.com'}]
}
the array of 'data' is an array of DTO.
So what the dox are telling you is that you need to familiarize your application with the API by creating a layer for that data to pass through to be formed into objects on your side and the same layer should take an object and turn it into a DTO. In some cases you can just handle the responses from API's with simple code, however in a situation were the GET request would return more than 10 results you are going to want to parse it with some class. Essentially creating an ORM for DTOs.
As far as guzzle goes: set the body to what ever the results of pushing the data through the layer.
public function createUserWithSomeApi()
{
$g= new \Guzzle\Client();
$response = $g->post('http://api.some.place/v1/users', [
'body' => (new strangeApiDtoParser)->prepare($new_user_data)
]);
return ApiDtoParser::fromDTO($response->getBody());
}
And receive
public function getUsersFromSomeApi()
{
$g= new \Guzzle\Client();
$response = $g->get('http://api.some.place/v1/users', [
'query' => ['foo' => 'bar']
]);
return ApiDtoParser::fromDTO($response->getBody());
}
Now your parser:
class ApiDtoParser
{
public static function fromDto($raw)
{
$returnArray=[];
$decoded =json_decode($data,true);
foreach($decoded as $one){
$obj = new DtoObj;
foreach ($one as $key => $value) {
$meth = "set". ucfirst(strtolower($key));
$obj->{$meth}($var);
}
$returnArray[]=$obj;
}
return $returnArray;
}
}
Judging by the context of you excerpt, You will need to create a request based parser though
I'm implementing a customized priority queue based on PHP's SPLPriorityQueue in a Zend Application. It contains custom objects, PriorityQueueItens, instead of pure values aside the priority value. When storing the queue in APC (or memcache) I have to make sure the queue and its items are serializable, so I've set them to implement the Serializable interface using code from the upcoming Zend Framework 2.
public function serialize()
{
$data = array();
while ($this->valid()) {
$data[] = $this->current();
$this->next();
}
foreach ($data as $item) {
$this->insert($item['data'], $item['priority']);
}
return serialize($data);
}
public function unserialize($data)
{
foreach (unserialize($data) as $item) {
$this->insert($item['data'], $item['priority']);
}
}
After fetching the queue from APC and retrieving the top item in the priority queue, using $this->extract(), I don't get the item but the array that is created during serialization.
So, instead of a PriorityQueueItem, the base class I use for objects stored in the queue, I get an associative array with indices data and priority (similar to the array in the serialize function). To get the actual item I need to retrive the data part of the array instead of treating the returned item as an item, which is how it works when not storing the queue in APC and how I assumed it would work now as well.
Is this a feature of serialization of objects or am I approaching this in a wrong way?
Update: The issue here was that I had a separate function that did extra cruft besides the extract(). This function returned the item as an array, but as soon as I called extract() explicitly I got the item as expected. Are there certain precautions to take with public functions in objects that have been serialized?
You mixed/switched this probably:
In your code you are serializing the $data array, not "your object". I'm not entirely sure of this because I do not know what the insert() function is for.
But for the serialize in an object with the serializable interface you will get back what has been returned from object::serialize().
As you serialize an array, you will get the serialized array back. PHP in the background is taking care that this was stored as your object.
I'm developing front-end code for a web application, and ran into an odd piece of trouble with a custom object. When I request the object and use print_r() I get this (the object is a lot bigger; just cut it to the relevant code):
MemberInfo Object
(
[meta_data] => Array
(
[email_recommendations] => true
[email_updates] => false
)
)
To change something in the MemberInfo object, I just update its properties and send it back to the backend with a second function. So for instance, the page is loaded once (which gives us the object shown above), then I send a POST request with changes on a second load of the page. During the second load, I fetch the object above, set one field differently based on the POST with something like $memberInfo->meta_data['email_recommendations'] = 'false'; and then use that version of the object to populate the page after running the update function (which is something like updateMember($memberInfo);). However, once I've changed the object property value print_r() shows me something different:
MemberInfo Object
(
[meta_data] => {\"email_recommendations\":\"false\",\"email_updates\":\"false\"}
)
I'm sure I'm overlooking something very stupid; does anyone have a good idea of what I should be looking for? I checked, and the backend code isn't passing by reference (the function call is updateMember(MemberInfo $memberInfo);) but I'm a little shaky on my PHP 5 object handling so I'm not sure what might be going wrong.
I don't expect in-depth debugging; I just need to know the general direction I should be looking for what is causing this change in a property that by all rights should be an array.
Thanks in advance!
so are you using the object after calling updateMember()? PHP5 objects are passed by reference by default, so if you're calling json_encode() on the meta_data property, it'll exhibit the behaviour you describe.
you may want to post the updateMember() function to confirm, but it sounds like that's what's going on.
ie:
class MemberInfo {
function __construct() {
$this->meta_data = array(
'email_recommendations' => true,
'email_updates' => false,
);
}
}
function updateMember($meminfo) {
$meminfo->meta_data = json_encode($meminfo->meta_data);
// do stuff
}
$meminfo = new MemberInfo();
updateMember($meminfo);
print_r($meminfo); // you'll see the json encoded value for "meta_data"