Skip to content

Instantly share code, notes, and snippets.

@nebkam

nebkam/api_client.php

Last active Feb 13, 2020
Embed
What would you like to do?
<?php
class ApiClient
{
/**
* @return Project[]
*/
public function getProjects(): array
{
$projects = [];
$response = $this->client->get('https://some-awesome.api/projects');
$projectCollection = $this->serializer->deserialize($response->getBody()->getContent(), ProjectCollection::class, 'json');
if (!empty($collection->getResult()))
{
foreach ($collection->getResult() as $project)
{
if ($project->isOnSale())
{
$projects[] = $project;
}
}
}
return $projects;
}
public function addNewProject(Project $project)
{
$this->client->post('https://some-awesome.api/projects', [
'body' => $this->serializer->serialize($project, 'json')
]);
}
}
<?php
use Symfony\Component\Serializer\Annotation\SerializedName;
class Project
{
/**
* @SerializedName("Id")
* @var int
*/
private $internalId;
/**
* @SerializedName("Name")
* @var string
*/
private $title;
/**
* @SerializedName("Quality")
* @var float
*/
private $quality;
// getters and setters
}
<?php
use Symfony\Component\Serializer\Annotation\SerializedName;
class ProjectCollection
{
/**
* @SerializedName("Result")
* @var Project[]
*/
private $result;
/**
* @return Project[]|null
*/
public function getResult(): ?array
{
return $this->result;
}
/**
* @param Project[] $results
*/
public function setResult(array $result)
{
$this->result = $result;
}
}
@parijke

This comment has been minimized.

Copy link

@parijke parijke commented Jan 18, 2020

I ca'nt seem to get it to work.

Whats does the getResult function looks like in the ProjectCollection class?

@nebkam

This comment has been minimized.

Copy link
Owner Author

@nebkam nebkam commented Jan 20, 2020

Just a simple getter:

/**
 * @return Project[]|null
 */
public function getResult(): ?array
	{
	return $this->result;
	}

What kind of problems are you running into?

@parijke

This comment has been minimized.

Copy link

@parijke parijke commented Jan 26, 2020

The code works but... It returns an array of key-value pairs instead of Project (in my case Contact) objects. So somewhere I mist the part that you said:
"Have you noticed I didn’t have to tell the Serializer to map to the Project class?". I could not find out what is the key part in this.

@nebkam

This comment has been minimized.

Copy link
Owner Author

@nebkam nebkam commented Jan 27, 2020

Have you required symfony/property-info ?

After debugging a while, it seems that Serializer uses PropertyInfo component in order to "guess" from annotations which classes should be instantiated.
When I removed symfony/property-info as a test from my project, the response wasn't typecasted, just like you've said.
That component was an indirect dependency in my project, that's why I haven't noticed it was required.
I'll update the article after you confirm it was causing it.
Thanks for catching that! 👍 :)

@parijke

This comment has been minimized.

Copy link

@parijke parijke commented Jan 27, 2020

@parijke

This comment has been minimized.

Copy link

@parijke parijke commented Jan 28, 2020

Hmm still not working :-(

Here's my gist with the test https://gist.github.com/parijke/05ac5d4332c1633ef23be25027a24965

I think you also made a typo using $projectCollection and $collection

I probably do not understand enough what is or should happening

@parijke

This comment has been minimized.

Copy link

@parijke parijke commented Feb 10, 2020

Any tips what I am doing wrong?

@nebkam

This comment has been minimized.

Copy link
Owner Author

@nebkam nebkam commented Feb 10, 2020

I've been banging my head on it for an hour and finally found the culprit - another hidden dependency
composer require phpdocumentor/reflection-docblock

  • Serializer uses Property Info
  • Property Info uses extractors
  • since the only piece of information that your $result is an array of Projects is the Project[]|null PHP DocBlock, the corresponding extractor is needed to parse that
  • I've reproduced the bug with your gist (thank you) and it disappeared after requiring the package from above

I'll update my article with those hidden dependencies. Thanks again

@nebkam

This comment has been minimized.

Copy link
Owner Author

@nebkam nebkam commented Feb 10, 2020

PS I recommend that you inject the serializer in your controller/service (typecast it to SerializerInterface) instead of constructing it, like in your gist. That way, you'll be sure the extractor is registered in the Serializer

@parijke

This comment has been minimized.

Copy link

@parijke parijke commented Feb 12, 2020

Yes, finally I got Project objects returned now it works like I expected. Gonna try to fix the mapping. I also changed the example to use the SerializerInterface type hint as you suggested. The default serializer-pack of the website-skeleton installs all (hidden) dependencies.

array:3 [▼
  0 => App\Model\Project {#699 ▼
    -title: "Project A"
    -sale: "1"
  }
  1 => App\Model\Project {#693 ▼
    -title: "Project B"
    -sale: "1"
  }
  2 => App\Model\Project {#692 ▼
    -title: "Project C"
    -sale: "0"
  }
]
@nebkam

This comment has been minimized.

Copy link
Owner Author

@nebkam nebkam commented Feb 13, 2020

Great! I'm glad it worked! I learned a lot through your use case, thanks!
I've updated the article to suggest symfony/serializer-pack and mentioned you in the DocBlock parsing ;)

@parijke

This comment has been minimized.

Copy link

@parijke parijke commented Feb 13, 2020

Thx I learned a lot as well. And appreciate the mention. I am going to use this further in my project.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.