-
-
Save Korbeil/7451afbff73da572f0500375d6f74805 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class PimListener | |
{ | |
public const CACHE_DIRECTORY = '%s/Proxies/'; | |
public const PROXY_NAMESPACE = 'PimProxy'; | |
private HttpClientInterface $pimHttpClient; | |
private LazyLoadingGhostFactory $factory; | |
private array $proxiesBuffer = []; | |
private array $arrayBuffer = []; | |
private array $arrayResults = []; | |
public function __construct( | |
private SerializerInterface $serializer, | |
private string $cacheDir, | |
private string $pimApiUrl, | |
) { | |
$this->buildHttpClient(); | |
$this->factory = new LazyLoadingGhostFactory($this->makeConfiguration($cacheDir)); | |
} | |
private function buildHttpClient(): void | |
{ | |
if (isset($this->pimHttpClient)) { | |
unset($this->pimHttpClient); // destroy it cleanly | |
} | |
$options = new HttpOptions(); | |
$options->setBaseUri($this->pimApiUrl); | |
$options->setHeaders([ | |
'User-Agent' => 'PimListener', // a clean User-Agent so you can track HTTP calls | |
'Accept' => 'application/json', | |
]); | |
$options->setTimeout(120); | |
// this is something that came later on, to make this service resettable I make | |
// the CurlHttpClient inside the PimListener, for some very heavy exports this is required. | |
$this->pimHttpClient = new CurlHttpClient($options->toArray()); | |
} | |
private function makeRequest(Endpoint $endpoint): ResponseInterface | |
{ | |
$queryString = $endpoint->getQueryString(); | |
$uriGlue = false === \mb_strpos($endpoint->getUri(), '?') ? '?' : '&'; | |
$uri = $queryString !== '' ? $endpoint->getUri() . $uriGlue . $queryString : $endpoint->getUri(); | |
try { | |
return $this->pimHttpClient->request($endpoint->getMethod(), $uri); | |
} catch (ClientException $exception) { | |
throw new PimException('Pim Client Exception', 0, $exception); | |
} | |
} | |
public function postLoad(PostLoadEventArgs $args): void | |
{ | |
/** @var object|PimInterface $entity */ | |
$entity = $args->getObject(); | |
if (!$entity instanceof PimInterface) { | |
return; | |
} | |
/** @var string $uuid */ | |
$uuid = $entity->getPimUuid(); | |
$entity->setPimObject($this->fetchProxy($entity, $uuid)); | |
} | |
/** | |
* @param PimInterface[] $entities | |
*/ | |
public function preload(array $entities): void | |
{ | |
foreach ($entities as $entity) { | |
[$modelClass, $endpointClass] = $entity->getPimModelConfiguration(); | |
if (!\array_key_exists($modelClass, $this->arrayBuffer)) { | |
$this->arrayBuffer[$modelClass] = []; | |
$this->arrayResults[$modelClass] = []; | |
} | |
if (\array_key_exists($entity->getPimUuid(), $this->arrayResults[$modelClass])) { | |
continue; // Do not fetch data we already have | |
} | |
$endpoint = new $endpointClass($entity->getPimUuid()); | |
$response = $this->makeRequest($endpoint); | |
$this->arrayResults[$modelClass][$entity->getPimUuid()] = new Result($response, $modelClass, $this->serializer); | |
} | |
} | |
private function fetchProxy(PimInterface $entity, string $uuid): object | |
{ | |
[$modelClass, ] = $entity->getPimModelConfiguration(); | |
if (!\array_key_exists($modelClass, $this->proxiesBuffer)) { | |
$this->proxiesBuffer[$modelClass] = []; | |
} | |
if (!\array_key_exists($uuid, $this->proxiesBuffer[$modelClass])) { | |
$this->proxiesBuffer[$modelClass][$uuid] = $this->makeProxy($entity, $uuid); | |
} | |
return $this->proxiesBuffer[$modelClass][$uuid]; | |
} | |
/** | |
* Ghost Proxy object factory, the idea is to generate a proxy object that will lazy-load PIM data when needed. | |
* | |
* We have an initializer method that is fired when we try to access any object property and ProxyManager factory | |
* is used to generate needed proxy. | |
* The proxy class is cached (in order to avoid repeated Reflection calls), see `makeConfiguration` for more details | |
*/ | |
private function makeProxy(PimInterface $entity, string $uuid): object | |
{ | |
$pimModelConfiguration = $entity->getPimModelConfiguration(); | |
$initializer = function ( | |
GhostObjectInterface $ghostObject, | |
string $method, | |
array $parameters, | |
&$initializer, | |
array $properties, | |
) use ($pimModelConfiguration, $uuid) { | |
$initializer = null; // disable initialization | |
// We only want to initialize objects that fit our entity modelClass | |
[$modelClass, ] = $entity->getPimModelConfiguration(); | |
if (!$ghostObject instanceof $modelClass) { | |
return false; | |
} | |
$model = $this->loadModel($uuid, $entity); | |
// Checking if we got a result from PIM API | |
if (!$model instanceof $modelClass) { | |
return false; | |
} | |
/* | |
* For all object properties, we take property name then we guess the mutator method | |
* and we use it for each property in order to hydrate the object | |
*/ | |
foreach (\array_keys($properties) as $property) { | |
$parts = explode("\0", $property); | |
/** @var string $variable */ | |
$variable = \array_pop($parts); | |
$mutator = sprintf('get%s', \ucfirst($variable)); | |
$callable = [$model, $mutator]; | |
if (\is_callable($callable)) { | |
$properties[$property] = \call_user_func($callable); | |
} | |
} | |
return true; | |
}; | |
[$modelClass, ] = $entity->getPimModelConfiguration(); | |
$ghostedPimModel = $this->factory->createProxy($modelClass, $initializer); | |
return $ghostedPimModel; | |
} | |
protected function loadModel(string $uuid, object $entity): object | |
{ | |
[$modelClass, $endpointClass] = $entity->getPimModelConfiguration(); | |
if (!\array_key_exists($modelClass, $this->arrayBuffer)) { | |
$this->arrayBuffer[$modelClass] = []; | |
$this->arrayResults[$modelClass] = []; | |
} | |
if (!\array_key_exists($uuid, $this->arrayResults[$modelClass])) { | |
$endpoint = new $endpointClass($uuid); | |
$response = $this->makeRequest($endpoint); | |
$this->arrayResults[$modelClass][$uuid] = new Result($response, $modelClass, $this->serializer); | |
} | |
if (!\array_key_exists($uuid, $this->arrayBuffer[$modelClass])) { | |
/** @var Result $result */ | |
$result = $this->arrayResults[$modelClass][$uuid]; | |
try { | |
$this->arrayBuffer[$modelClass][$uuid] = $result->toObject(); | |
} catch (ClientException $exception) { | |
throw new PimException(sprintf('Pim Client Exception (%s: #%s)', $modelClass, $uuid), 0, $exception); | |
} | |
} | |
return $this->arrayBuffer[$modelClass][$uuid]; | |
} | |
private function makeConfiguration(string $cacheDir): Configuration | |
{ | |
$directory = sprintf(self::CACHE_DIRECTORY, $cacheDir); | |
if (!\is_dir($directory)) { | |
\mkdir($directory); | |
} | |
$configuration = new Configuration(); | |
$configuration->setProxiesTargetDir($directory); | |
$configuration->setProxiesNamespace(self::PROXY_NAMESPACE); | |
$configuration->setGeneratorStrategy(new FileWriterGeneratorStrategy( | |
new FileLocator($directory), | |
)); | |
return $configuration; | |
} | |
public function clear(bool $clearHttpClient = false, bool $clearOnlyProductCache = false): void | |
{ | |
if ($clearOnlyProductCache) { | |
unset($this->arrayResults[ProductDTO::class]); | |
unset($this->arrayBuffer[ProductDTO::class]); | |
unset($this->proxiesBuffer[ProductDTO::class]); | |
} else { | |
$this->arrayResults = $this->arrayBuffer = $this->proxiesBuffer = []; | |
} | |
if ($clearHttpClient) { | |
$this->buildHttpClient(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class PimResult | |
{ | |
private bool $initialized; | |
public function __construct( | |
private ResponseInterface $response, | |
private string $modelClass, | |
private SerializerInterface $serializer, | |
) { | |
$this->initialized = false; | |
} | |
public function isInitialized(): bool | |
{ | |
return $this->initialized; | |
} | |
public function getStatusCode(): int | |
{ | |
$this->initialized = true; | |
return $this->response->getStatusCode(); | |
} | |
public function toObject(): ?object | |
{ | |
/** @var null|object $deserialized */ | |
$deserialized = $this->serializer->deserialize($this->getContents(), $this->modelClass, 'json'); | |
return $deserialized; | |
} | |
public function getContents(): string | |
{ | |
$this->initialized = true; | |
return $this->response->getContent(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment