-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from DocPlanner/IN-633/guzzle-compatibility-issue
[IN-633] Guzzle compatibility issues.
- Loading branch information
Showing
9 changed files
with
1,987 additions
and
1 deletion.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# REPLACING GUZZLE BUILD_QUERY OCCURRENCES | ||
|
||
* Deciders: Monika Lipińska | ||
* DATE: 2023-11-16 | ||
|
||
## Context and Problem Statement | ||
We had compatibility issues concerning `Guzzle`. | ||
|
||
We were using a **3.0.24** version of a **swagger-codegen** that was openly using `GuzzleHttp\Psr7::build_query` method that was removed from `GuzzleHttp\Psr7` library since its **2.0** version. | ||
|
||
## Considered Options | ||
|
||
* Using the latest (**3.0.50**) version of **swagger-codegen**. | ||
|
||
* Changing generated SDK code using mustache templates so instead of a `build_query` from `Guzzle` we use a private method `buildQuery` in each of `*Api` classes. | ||
|
||
## Decision Outcome | ||
With the newest version of **swagger-codegen** we would end up with code using new `GuzzleHttp\Psr7\Query::build`... but we would also lose support for **PHP 5** that we still have to maintain. | ||
|
||
Using mustache templates in order to change the code generated is not a perfect solution (as now we have the same method repeated among `*Api.php` files), but it does the job. | ||
|
||
### Changes made | ||
|
||
- `api-docs-generator.yml` | ||
Changing code generation command call by specifying **-t** parameter which is for templates folder | ||
- introducing `swagger-codegen-templates` folder with: | ||
- - `api.mustache` | ||
Copied from **swagger-codegen** repository and changed: | ||
- - * whenever a placeholder `{{invokerPackage}}` was preceded with `\` I changed it into `DocPlanner\Client `as otherwise it was not being replaced in code generation process | ||
- - * replacing `\GuzzleHttp\Psr7\Query::build($formParams);` with `$this->buildQuery($formParams); `and putting new protected method `protected function buildQuery($params, $encoding = PHP_QUERY_RFC3986)` in the end (body of that method comes from Guzzle) | ||
- - * wherever new generated code would be different than the one we already had (and that would not be our Guzzle build query case) I was preventing those changes by using our (existing) version of code | ||
- - `partial_header.mustache` | ||
Copied from **swagger-codegen** repository and unchanged. It seems that it just has to be there as without it I had error in the generation process saying that it's missing. | ||
- - `ObjectSerializer.mustache` | ||
Copied from `swagger-codegen` repository and changed because I couldn't make the generator to create exactly same lines using `ObjectSerializer::toQueryValue`. It was creating them with only one parameter when we had two before but as I checked the second one was never used in our code anyway. | ||
|
||
Original mustache files that I was changing were taken from [here](https://github.com/swagger-api/swagger-codegen/tree/953805cb9eb750204c965a54ae326c4f308a68d2/modules/swagger-codegen/src/main/resources/php) and can be found in [swagger-codegen-original-templates](swagger-codegen-original-templates) folder together with a [reverse-mustaches.diff](swagger-codegen-original-templates/reverse-mustaches.diff) file that shows what exactly changed. | ||
|
||
|
||
|
312 changes: 312 additions & 0 deletions
312
.adr/swagger-codegen-original-templates/ObjectSerializer.mustache
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,312 @@ | ||
<?php | ||
/** | ||
* ObjectSerializer | ||
* | ||
* PHP version 5 | ||
* | ||
* @category Class | ||
* @package {{invokerPackage}} | ||
* @author Swagger Codegen team | ||
* @link https://github.com/swagger-api/swagger-codegen | ||
*/ | ||
|
||
{{>partial_header}} | ||
/** | ||
* NOTE: This class is auto generated by the swagger code generator program. | ||
* https://github.com/swagger-api/swagger-codegen | ||
* Do not edit the class manually. | ||
*/ | ||
|
||
namespace {{invokerPackage}}; | ||
|
||
/** | ||
* ObjectSerializer Class Doc Comment | ||
* | ||
* @category Class | ||
* @package {{invokerPackage}} | ||
* @author Swagger Codegen team | ||
* @link https://github.com/swagger-api/swagger-codegen | ||
*/ | ||
class ObjectSerializer | ||
{ | ||
/** | ||
* Serialize data | ||
* | ||
* @param mixed $data the data to serialize | ||
* @param string $type the SwaggerType of the data | ||
* @param string $format the format of the Swagger type of the data | ||
* | ||
* @return string|object serialized form of $data | ||
*/ | ||
public static function sanitizeForSerialization($data, $type = null, $format = null) | ||
{ | ||
if (is_scalar($data) || null === $data) { | ||
return $data; | ||
} elseif ($data instanceof \DateTime) { | ||
return ($format === 'date') ? $data->format('Y-m-d') : $data->format(\DateTime::ATOM); | ||
} elseif (is_array($data)) { | ||
foreach ($data as $property => $value) { | ||
$data[$property] = self::sanitizeForSerialization($value); | ||
} | ||
return $data; | ||
} elseif ($data instanceof \stdClass) { | ||
foreach ($data as $property => $value) { | ||
$data->$property = self::sanitizeForSerialization($value); | ||
} | ||
return $data; | ||
} elseif (is_object($data)) { | ||
$values = []; | ||
$formats = $data::swaggerFormats(); | ||
foreach ($data::swaggerTypes() as $property => $swaggerType) { | ||
$getter = $data::getters()[$property]; | ||
$value = $data->$getter(); | ||
if ($value !== null | ||
&& !in_array($swaggerType, [{{&primitives}}], true) | ||
&& method_exists($swaggerType, 'getAllowableEnumValues') | ||
&& !in_array($value, $swaggerType::getAllowableEnumValues(), true)) { | ||
$imploded = implode("', '", $swaggerType::getAllowableEnumValues()); | ||
throw new \InvalidArgumentException("Invalid value for enum '$swaggerType', must be one of: '$imploded'"); | ||
} | ||
if ($value !== null) { | ||
$values[$data::attributeMap()[$property]] = self::sanitizeForSerialization($value, $swaggerType, $formats[$property]); | ||
} | ||
} | ||
return (object)$values; | ||
} else { | ||
return (string)$data; | ||
} | ||
} | ||
|
||
/** | ||
* Sanitize filename by removing path. | ||
* e.g. ../../sun.gif becomes sun.gif | ||
* | ||
* @param string $filename filename to be sanitized | ||
* | ||
* @return string the sanitized filename | ||
*/ | ||
public static function sanitizeFilename($filename) | ||
{ | ||
if (preg_match("/.*[\/\\\\](.*)$/", $filename, $match)) { | ||
return $match[1]; | ||
} else { | ||
return $filename; | ||
} | ||
} | ||
|
||
/** | ||
* Take value and turn it into a string suitable for inclusion in | ||
* the path, by url-encoding. | ||
* | ||
* @param string $value a string which will be part of the path | ||
* | ||
* @return string the serialized object | ||
*/ | ||
public static function toPathValue($value) | ||
{ | ||
return rawurlencode(self::toString($value)); | ||
} | ||
|
||
/** | ||
* Take value and turn it into a string suitable for inclusion in | ||
* the query, by imploding comma-separated if it's an object. | ||
* If it's a string, pass through unchanged. It will be url-encoded | ||
* later. | ||
* | ||
* @param string[]|string|\DateTime $object an object to be serialized to a string | ||
* | ||
* @return string the serialized object | ||
*/ | ||
public static function toQueryValue($object) | ||
{ | ||
if (is_array($object)) { | ||
return implode(',', $object); | ||
} else { | ||
return self::toString($object); | ||
} | ||
} | ||
|
||
/** | ||
* Take value and turn it into a string suitable for inclusion in | ||
* the header. If it's a string, pass through unchanged | ||
* If it's a datetime object, format it in ISO8601 | ||
* | ||
* @param string $value a string which will be part of the header | ||
* | ||
* @return string the header string | ||
*/ | ||
public static function toHeaderValue($value) | ||
{ | ||
return self::toString($value); | ||
} | ||
|
||
/** | ||
* Take value and turn it into a string suitable for inclusion in | ||
* the http body (form parameter). If it's a string, pass through unchanged | ||
* If it's a datetime object, format it in ISO8601 | ||
* | ||
* @param string|\SplFileObject $value the value of the form parameter | ||
* | ||
* @return string the form string | ||
*/ | ||
public static function toFormValue($value) | ||
{ | ||
if ($value instanceof \SplFileObject) { | ||
return $value->getRealPath(); | ||
} else { | ||
return self::toString($value); | ||
} | ||
} | ||
|
||
/** | ||
* Take value and turn it into a string suitable for inclusion in | ||
* the parameter. If it's a string, pass through unchanged | ||
* If it's a datetime object, format it in ISO8601 | ||
* | ||
* @param string|\DateTime $value the value of the parameter | ||
* | ||
* @return string the header string | ||
*/ | ||
public static function toString($value) | ||
{ | ||
if ($value instanceof \DateTime) { // datetime in ISO8601 format | ||
return $value->format(\DateTime::ATOM); | ||
} else { | ||
return $value; | ||
} | ||
} | ||
|
||
/** | ||
* Serialize an array to a string. | ||
* | ||
* @param array $collection collection to serialize to a string | ||
* @param string $collectionFormat the format use for serialization (csv, | ||
* ssv, tsv, pipes, multi) | ||
* @param bool $allowCollectionFormatMulti allow collection format to be a multidimensional array | ||
* | ||
* @return string | ||
*/ | ||
public static function serializeCollection(array $collection, $collectionFormat, $allowCollectionFormatMulti = false) | ||
{ | ||
if ($allowCollectionFormatMulti && ('multi' === $collectionFormat)) { | ||
// http_build_query() almost does the job for us. We just | ||
// need to fix the result of multidimensional arrays. | ||
return preg_replace('/%5B[0-9]+%5D=/', '=', http_build_query($collection, '', '&')); | ||
} | ||
switch ($collectionFormat) { | ||
case 'pipes': | ||
return implode('|', $collection); | ||
case 'tsv': | ||
return implode("\t", $collection); | ||
case 'ssv': | ||
return implode(' ', $collection); | ||
case 'csv': | ||
// Deliberate fall through. CSV is default format. | ||
default: | ||
return implode(',', $collection); | ||
} | ||
} | ||
|
||
/** | ||
* Deserialize a JSON string into an object | ||
* | ||
* @param mixed $data object or primitive to be deserialized | ||
* @param string $class class name is passed as a string | ||
* @param string[] $httpHeaders HTTP headers | ||
* @param string $discriminator discriminator if polymorphism is used | ||
* | ||
* @return object|array|null an single or an array of $class instances | ||
*/ | ||
public static function deserialize($data, $class, $httpHeaders = null) | ||
{ | ||
if (null === $data) { | ||
return null; | ||
} elseif (substr($class, 0, 4) === 'map[') { // for associative array e.g. map[string,int] | ||
$inner = substr($class, 4, -1); | ||
$deserialized = []; | ||
if (strrpos($inner, ",") !== false) { | ||
$subClass_array = explode(',', $inner, 2); | ||
$subClass = $subClass_array[1]; | ||
foreach ($data as $key => $value) { | ||
$deserialized[$key] = self::deserialize($value, $subClass, null); | ||
} | ||
} | ||
return $deserialized; | ||
} elseif (strcasecmp(substr($class, -2), '[]') === 0) { | ||
$subClass = substr($class, 0, -2); | ||
$values = []; | ||
foreach ($data as $key => $value) { | ||
$values[] = self::deserialize($value, $subClass, null); | ||
} | ||
return $values; | ||
} elseif ($class === 'object') { | ||
settype($data, 'array'); | ||
return $data; | ||
} elseif ($class === '\DateTime') { | ||
// Some API's return an invalid, empty string as a | ||
// date-time property. DateTime::__construct() will return | ||
// the current time for empty input which is probably not | ||
// what is meant. The invalid empty string is probably to | ||
// be interpreted as a missing field/value. Let's handle | ||
// this graceful. | ||
if (!empty($data)) { | ||
return new \DateTime($data); | ||
} else { | ||
return null; | ||
} | ||
} elseif (in_array($class, [{{&primitives}}], true)) { | ||
settype($data, $class); | ||
return $data; | ||
} elseif ($class === '\SplFileObject') { | ||
/** @var \Psr\Http\Message\StreamInterface $data */ | ||
// determine file name | ||
if (array_key_exists('Content-Disposition', $httpHeaders) && | ||
preg_match('/inline; filename=[\'"]?([^\'"\s]+)[\'"]?$/i', $httpHeaders['Content-Disposition'], $match)) { | ||
$filename = Configuration::getDefaultConfiguration()->getTempFolderPath() . DIRECTORY_SEPARATOR . self::sanitizeFilename($match[1]); | ||
} else { | ||
$filename = tempnam(Configuration::getDefaultConfiguration()->getTempFolderPath(), ''); | ||
} | ||
|
||
$file = fopen($filename, 'w'); | ||
while ($chunk = $data->read(200)) { | ||
fwrite($file, $chunk); | ||
} | ||
fclose($file); | ||
|
||
return new \SplFileObject($filename, 'r'); | ||
} elseif (method_exists($class, 'getAllowableEnumValues')) { | ||
if (!in_array($data, $class::getAllowableEnumValues(), true)) { | ||
$imploded = implode("', '", $class::getAllowableEnumValues()); | ||
throw new \InvalidArgumentException("Invalid value for enum '$class', must be one of: '$imploded'"); | ||
} | ||
return $data; | ||
} else { | ||
// If a discriminator is defined and points to a valid subclass, use it. | ||
$discriminator = $class::DISCRIMINATOR; | ||
if (!empty($discriminator) && isset($data->{$discriminator}) && is_string($data->{$discriminator})) { | ||
$subclass = '\{{invokerPackage}}\Model\\' . $data->{$discriminator}; | ||
if (is_subclass_of($subclass, $class)) { | ||
$class = $subclass; | ||
} | ||
} | ||
$instance = new $class(); | ||
foreach ($instance::swaggerTypes() as $property => $type) { | ||
$propertySetter = $instance::setters()[$property]; | ||
if (!isset($propertySetter) || !isset($data->{$instance::attributeMap()[$property]})) { | ||
continue; | ||
} | ||
|
||
$propertyValue = $data->{$instance::attributeMap()[$property]}; | ||
if (isset($propertyValue)) { | ||
$instance->$propertySetter(self::deserialize($propertyValue, $type, null)); | ||
} | ||
} | ||
return $instance; | ||
} | ||
} | ||
} |
Oops, something went wrong.