Skip to content

Commit 4b132fa

Browse files
authored
Merge branch 'master' into release/v0.1.0.1
2 parents 39987b7 + 7813054 commit 4b132fa

File tree

11 files changed

+299
-222
lines changed

11 files changed

+299
-222
lines changed

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ public function registerBundles()
3333
}
3434
```
3535

36+
## Configure
37+
38+
``` yml
39+
api_response:
40+
defaults:
41+
serializer: json_encode
42+
serialize_groups: []
43+
cors_allow_origin_regex: https://.*\.mydomain\.com
44+
cors_allow_headers: Authorization, Content-Type
45+
cors_max_age: 86400
46+
paths:
47+
somename:
48+
pattern: ^/api/v1/
49+
serializer: jms_serializer
50+
othername:
51+
pattern: ^/api/v2/
52+
cors_allow_origin_regex: https://.*\.(mydomain|theirdomain)\.com
53+
```
54+
3655
## Usage
3756
3857
In your API controllers, just return whatever you want serialized in the response. The ApiResponseBundle takes care of

src/Compiler/ApiConfigCompiler.php

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use MattJanssen\ApiResponseBundle\Annotation\ApiResponse;
66
use MattJanssen\ApiResponseBundle\Model\ApiConfig;
77
use MattJanssen\ApiResponseBundle\Model\ApiConfigInterface;
8+
use MattJanssen\ApiResponseBundle\Model\ApiPathConfig;
89
use Symfony\Component\HttpFoundation\Request;
910

1011
/**
@@ -26,7 +27,7 @@ class ApiConfigCompiler
2627
*
2728
* Each relative URL path has its own CORS configuration settings in this array.
2829
*
29-
* @var ApiConfig[]
30+
* @var ApiPathConfig[]
3031
*/
3132
private $pathConfigs;
3233

@@ -43,8 +44,8 @@ public function __construct(
4344
$defaultConfig = $this->generateApiConfig($defaultConfigArray);
4445

4546
$pathConfigs = [];
46-
foreach ($pathConfigArrays as $path => $configArray) {
47-
$pathConfigs[$path] = $this->generateApiConfig($configArray);
47+
foreach ($pathConfigArrays as $configArray) {
48+
$pathConfigs[] = $this->generateApiPathConfig($configArray);
4849
}
4950

5051
$this->defaultConfig = $defaultConfig;
@@ -74,7 +75,8 @@ public function compileApiConfig(Request $request)
7475

7576
// Try to match the request origin to a path in the config.yml.
7677
$originPath = $request->getPathInfo();
77-
foreach ($this->pathConfigs as $pathRegex => $pathConfig) {
78+
foreach ($this->pathConfigs as $pathConfig) {
79+
$pathRegex = $pathConfig->getPattern();
7880
if (!preg_match('#' . str_replace('#', '\#', $pathRegex) . '#', $originPath)) {
7981
// No path match.
8082
continue;
@@ -113,8 +115,14 @@ public function compileApiConfig(Request $request)
113115
* @param ApiConfig $compiledConfig
114116
* @param ApiConfigInterface $configToMerge
115117
*/
116-
private function mergeConfig($compiledConfig, $configToMerge)
118+
private function mergeConfig(ApiConfig $compiledConfig, ApiConfigInterface $configToMerge)
117119
{
120+
if (null !== $configToMerge->getSerializer()) {
121+
$compiledConfig->setSerializer($configToMerge->getSerializer());
122+
}
123+
if (null !== $configToMerge->getGroups()) {
124+
$compiledConfig->setGroups($configToMerge->getGroups());
125+
}
118126
if (null !== $configToMerge->getCorsAllowOriginRegex()) {
119127
$compiledConfig->setCorsAllowOriginRegex($configToMerge->getCorsAllowOriginRegex());
120128
}
@@ -132,9 +140,39 @@ private function mergeConfig($compiledConfig, $configToMerge)
132140
* @return ApiConfig $this
133141
*/
134142
private function generateApiConfig(array $configArray)
143+
{
144+
$apiConfig = new ApiConfig();
145+
146+
$this->applyApiConfig($configArray, $apiConfig);
147+
148+
return $apiConfig;
149+
}
150+
151+
/**
152+
* @param array $configArray
153+
*
154+
* @return ApiConfig $this
155+
*/
156+
private function generateApiPathConfig(array $configArray)
157+
{
158+
$apiPathConfig = new ApiPathConfig();
159+
160+
$this->applyApiConfig($configArray, $apiPathConfig);
161+
162+
// @TODO Use PHP 7 null coalescing operator.
163+
$apiPathConfig->setPattern(isset($configArray['pattern']) ? $configArray['pattern'] : null);
164+
165+
return $apiPathConfig;
166+
}
167+
168+
/**
169+
* @param array $configArray
170+
* @param ApiConfig $apiConfig
171+
*/
172+
private function applyApiConfig(array $configArray, ApiConfig $apiConfig)
135173
{
136174
// @TODO Use PHP 7 null coalescing operator.
137-
return (new ApiConfig())
175+
$apiConfig
138176
->setSerializer(isset($configArray['serializer']) ? $configArray['serializer'] : null)
139177
->setGroups(isset($configArray['serialize_groups']) ? $configArray['serialize_groups'] : null)
140178
->setCorsAllowHeaders(isset($configArray['cors_allow_headers']) ? $configArray['cors_allow_headers'] : null)

src/DependencyInjection/Configuration.php

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515
class Configuration implements ConfigurationInterface
1616
{
17+
const SERIALIZER_ARRAY = 'array';
1718
const SERIALIZER_JSON_ENCODE = 'json_encode';
1819
const SERIALIZER_JSON_GROUP_ENCODE = 'json_group_encode';
1920
const SERIALIZER_JMS_SERIALIZER = 'jms_serializer';
@@ -27,47 +28,50 @@ public function getConfigTreeBuilder()
2728
$rootNode = $treeBuilder->root('api_response');
2829

2930
$this->buildConfigNode(
30-
$rootNode
31-
->children()
32-
->arrayNode('defaults')
31+
$rootNode->children()
32+
->arrayNode('defaults')
33+
->children()
34+
->arrayNode('pattern')
35+
->prototype('scalar')->isRequired()->end()
36+
->end()
3337
);
3438

3539
$this->buildConfigNode(
36-
$rootNode
37-
->children()
38-
->arrayNode('paths')
39-
->normalizeKeys(false)
40-
->prototype('array')
40+
$rootNode->children()
41+
->arrayNode('paths')
42+
->useAttributeAsKey('name')
43+
->prototype('array')
44+
->children()
45+
->scalarNode('pattern')->end()
4146
);
4247

4348
return $treeBuilder;
4449
}
4550

4651
/**
47-
* @param ArrayNodeDefinition $arrayNode
52+
* @param NodeBuilder $nodeBuilder
4853
*
4954
* @return ArrayNodeDefinition
5055
*/
51-
private function buildConfigNode(ArrayNodeDefinition $arrayNode)
56+
private function buildConfigNode(NodeBuilder $nodeBuilder)
5257
{
53-
return $arrayNode
54-
->children()
55-
->enumNode('serializer')
56-
->values([
57-
self::SERIALIZER_JSON_ENCODE,
58-
self::SERIALIZER_JSON_GROUP_ENCODE,
59-
self::SERIALIZER_JMS_SERIALIZER,
60-
])
61-
->end()
62-
->arrayNode('serialize_groups')
63-
->prototype('scalar')->end()
64-
->end()
65-
->scalarNode('cors_allow_origin_regex')->end()
66-
->arrayNode('cors_allow_headers')
67-
->prototype('scalar')->end()
68-
->end()
69-
->integerNode('cors_max_age')->end()
58+
return $nodeBuilder
59+
->enumNode('serializer')
60+
->values([
61+
self::SERIALIZER_ARRAY,
62+
self::SERIALIZER_JSON_ENCODE,
63+
self::SERIALIZER_JSON_GROUP_ENCODE,
64+
self::SERIALIZER_JMS_SERIALIZER,
65+
])
7066
->end()
67+
->arrayNode('serialize_groups')
68+
->prototype('scalar')->end()
69+
->end()
70+
->scalarNode('cors_allow_origin_regex')->end()
71+
->arrayNode('cors_allow_headers')
72+
->prototype('scalar')->end()
73+
->end()
74+
->integerNode('cors_max_age')->end()
7175
;
7276
}
7377
}

src/Factory/SerializerAdapterFactory.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ public function __construct(ContainerInterface $container)
4848
public function createSerializerAdapter($serializerName)
4949
{
5050
switch ($serializerName) {
51+
case Configuration::SERIALIZER_ARRAY:
52+
$serializerAdapter = new Adapter\ArraySerializerAdapter();
53+
break;
54+
5155
case Configuration::SERIALIZER_JSON_ENCODE:
5256
$serializerAdapter = new Adapter\JsonEncodeSerializerAdapter();
5357
break;

src/Model/ApiConfigInterface.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ interface ApiConfigInterface
1616
*/
1717
public function getSerializer();
1818

19-
2019
/**
2120
* Get Serializer Groups to Use
2221
*

src/Model/ApiPathConfig.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace MattJanssen\ApiResponseBundle\Model;
4+
5+
/**
6+
* @author Matt Janssen <matt@mattjanssen.com>
7+
*/
8+
class ApiPathConfig extends ApiConfig
9+
{
10+
/**
11+
* @var string
12+
*/
13+
private $pattern;
14+
15+
/**
16+
* @return string
17+
*/
18+
public function getPattern()
19+
{
20+
return $this->pattern;
21+
}
22+
23+
/**
24+
* @param string $pattern
25+
*
26+
* @return $this
27+
*/
28+
public function setPattern($pattern)
29+
{
30+
$this->pattern = $pattern;
31+
32+
return $this;
33+
}
34+
}

src/Model/ApiResponseResponseModel.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace MattJanssen\ApiResponseBundle\Model;
44

5+
use MattJanssen\ApiResponseBundle\Serializer\ArraySerializable;
6+
57
/**
68
* API Response Response Model
79
*
@@ -10,7 +12,7 @@
1012
*
1113
* @author Matt Janssen <matt@mattjanssen.com>
1214
*/
13-
class ApiResponseResponseModel implements \JsonSerializable
15+
class ApiResponseResponseModel implements \JsonSerializable, ArraySerializable
1416
{
1517
/**
1618
* Serializable API Response Data
@@ -29,14 +31,22 @@ class ApiResponseResponseModel implements \JsonSerializable
2931
/**
3032
* {@inheritdoc}
3133
*/
32-
function jsonSerialize()
34+
public function jsonSerialize(): array
3335
{
3436
return [
3537
'data' => $this->data,
3638
'error' => $this->error,
3739
];
3840
}
3941

42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function arraySerialize(array $group = [])
46+
{
47+
return $this->jsonSerialize();
48+
}
49+
4050
/**
4151
* Get the API Response Data
4252
*
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
namespace MattJanssen\ApiResponseBundle\Serializer\Adapter;
4+
5+
use MattJanssen\ApiResponseBundle\Serializer\ArraySerializable;
6+
7+
/**
8+
* Adapter for Serialization Using PHP json_encode
9+
*
10+
* Serialized classes should implement ArraySerializable.
11+
*
12+
* @see ArraySerializable
13+
*
14+
* @author Matt Janssen <matt@mattjanssen.com>
15+
*/
16+
class ArraySerializerAdapter implements SerializerAdapterInterface
17+
{
18+
/**
19+
* {@inheritdoc}
20+
*/
21+
public function serialize($data, array $groups = [])
22+
{
23+
$jsonString = json_encode($this->serializedMixed($data, $groups));
24+
25+
$jsonError = json_last_error();
26+
27+
if ($jsonError !== JSON_ERROR_NONE) {
28+
throw new \RuntimeException(sprintf(
29+
'ArraySerializerAdapter failed to serialize due to json_serialize error %s.',
30+
$jsonError
31+
));
32+
}
33+
34+
return $jsonString;
35+
}
36+
37+
/**
38+
* Create Serializable Array
39+
*
40+
* Similar to json_encode's traversal, but uses ArraySerializable interface.
41+
*
42+
* @param mixed $data
43+
* @param string[] $groups
44+
*
45+
* @return \stdClass|mixed[] Serializable array for json_encode()
46+
*/
47+
private function serializedMixed($data, array $groups)
48+
{
49+
if (is_array($data)) {
50+
return $this->serializeArray($data, $groups);
51+
}
52+
53+
if (is_object($data)) {
54+
if ($data instanceof ArraySerializable) {
55+
return $this->serializedMixed($data->arraySerialize($groups), $groups);
56+
}
57+
58+
// A non-serializable object returns as an empty \stdClass object.
59+
// This gets converted to {} by json_encode, where as an empty array is converted to [].
60+
return new \stdClass();
61+
}
62+
63+
// Scalar data is returned as-is.
64+
return $data;
65+
}
66+
67+
/**
68+
* Map Array Back to Recursive Serialize Function
69+
*
70+
* @param mixed[] $array
71+
* @param string[] $groups
72+
*
73+
* @return mixed[]
74+
*/
75+
private function serializeArray(array $array, array $groups)
76+
{
77+
return array_map(
78+
function ($value) use ($groups) {
79+
return $this->serializedMixed($value, $groups);
80+
},
81+
$array
82+
);
83+
}
84+
}

0 commit comments

Comments
 (0)