1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10:
11:
12: namespace Symfony\Component\HttpFoundation;
13:
14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: class JsonResponse extends Response
26: {
27: protected $data;
28: protected $callback;
29: protected $encodingOptions;
30:
31: 32: 33: 34: 35: 36: 37:
38: public function __construct($data = null, $status = 200, $headers = array())
39: {
40: parent::__construct('', $status, $headers);
41:
42: if (null === $data) {
43: $data = new \ArrayObject();
44: }
45:
46:
47: $this->encodingOptions = JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT;
48:
49: $this->setData($data);
50: }
51:
52: 53: 54:
55: public static function create($data = null, $status = 200, $headers = array())
56: {
57: return new static($data, $status, $headers);
58: }
59:
60: 61: 62: 63: 64: 65: 66: 67: 68:
69: public function setCallback($callback = null)
70: {
71: if (null !== $callback) {
72:
73: $pattern = '/^[$_\p{L}][$_\p{L}\p{Mn}\p{Mc}\p{Nd}\p{Pc}\x{200C}\x{200D}]*+$/u';
74: $parts = explode('.', $callback);
75: foreach ($parts as $part) {
76: if (!preg_match($pattern, $part)) {
77: throw new \InvalidArgumentException('The callback name is not valid.');
78: }
79: }
80: }
81:
82: $this->callback = $callback;
83:
84: return $this->update();
85: }
86:
87: 88: 89: 90: 91: 92: 93: 94: 95:
96: public function setData($data = array())
97: {
98: $errorHandler = null;
99: $errorHandler = set_error_handler(function () use (&$errorHandler) {
100: if (JSON_ERROR_NONE !== json_last_error()) {
101: return;
102: }
103:
104: if ($errorHandler) {
105: call_user_func_array($errorHandler, func_get_args());
106: }
107: });
108:
109: try {
110:
111: json_encode(null);
112:
113: $this->data = json_encode($data, $this->encodingOptions);
114:
115: restore_error_handler();
116: } catch (\Exception $exception) {
117: restore_error_handler();
118:
119: throw $exception;
120: }
121:
122: if (JSON_ERROR_NONE !== json_last_error()) {
123: throw new \InvalidArgumentException($this->transformJsonError());
124: }
125:
126: return $this->update();
127: }
128:
129: 130: 131: 132: 133:
134: public function getEncodingOptions()
135: {
136: return $this->encodingOptions;
137: }
138:
139: 140: 141: 142: 143: 144: 145:
146: public function setEncodingOptions($encodingOptions)
147: {
148: $this->encodingOptions = (int) $encodingOptions;
149:
150: return $this->setData(json_decode($this->data));
151: }
152:
153: 154: 155: 156: 157:
158: protected function update()
159: {
160: if (null !== $this->callback) {
161:
162: $this->headers->set('Content-Type', 'text/javascript');
163:
164: return $this->setContent(sprintf('/**/%s(%s);', $this->callback, $this->data));
165: }
166:
167:
168:
169: if (!$this->headers->has('Content-Type') || 'text/javascript' === $this->headers->get('Content-Type')) {
170: $this->headers->set('Content-Type', 'application/json');
171: }
172:
173: return $this->setContent($this->data);
174: }
175:
176: private function transformJsonError()
177: {
178: if (function_exists('json_last_error_msg')) {
179: return json_last_error_msg();
180: }
181:
182: switch (json_last_error()) {
183: case JSON_ERROR_DEPTH:
184: return 'Maximum stack depth exceeded.';
185:
186: case JSON_ERROR_STATE_MISMATCH:
187: return 'Underflow or the modes mismatch.';
188:
189: case JSON_ERROR_CTRL_CHAR:
190: return 'Unexpected control character found.';
191:
192: case JSON_ERROR_SYNTAX:
193: return 'Syntax error, malformed JSON.';
194:
195: case JSON_ERROR_UTF8:
196: return 'Malformed UTF-8 characters, possibly incorrectly encoded.';
197:
198: default:
199: return 'Unknown error.';
200: }
201: }
202: }
203: