Skip to content

Instantly share code, notes, and snippets.

@nebkam
Last active February 13, 2020 20:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nebkam/81de7b5460d99049e73f12037222cae9 to your computer and use it in GitHub Desktop.
Save nebkam/81de7b5460d99049e73f12037222cae9 to your computer and use it in GitHub Desktop.
<?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
Copy link

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
Copy link
Author

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
Copy link

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
Copy link
Author

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
Copy link

parijke commented Jan 27, 2020 via email

@parijke
Copy link

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
Copy link

parijke commented Feb 10, 2020

Any tips what I am doing wrong?

@nebkam
Copy link
Author

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
Copy link
Author

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
Copy link

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
Copy link
Author

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
Copy link

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