1+ <?php
2+ /**
3+ * Copyright (c) 2012-2013 Jurian Sluiman.
4+ * All rights reserved.
5+ *
6+ * Redistribution and use in source and binary forms, with or without
7+ * modification, are permitted provided that the following conditions
8+ * are met:
9+ *
10+ * * Redistributions of source code must retain the above copyright
11+ * notice, this list of conditions and the following disclaimer.
12+ *
13+ * * Redistributions in binary form must reproduce the above copyright
14+ * notice, this list of conditions and the following disclaimer in
15+ * the documentation and/or other materials provided with the
16+ * distribution.
17+ *
18+ * * Neither the names of the copyright holders nor the names of the
19+ * contributors may be used to endorse or promote products derived
20+ * from this software without specific prior written permission.
21+ *
22+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33+ * POSSIBILITY OF SUCH DAMAGE.
34+ *
35+ * @author Jurian Sluiman <jurian@juriansluiman.nl>
36+ * @copyright 2012-2013 Jurian Sluiman http://juriansluiman.nl.
37+ * @license http://www.opensource.org/licenses/bsd-license.php BSD License
38+ */
39+
40+ namespace SlmException \Mvc ;
41+
42+ use Zend \EventManager \ListenerAggregateInterface ;
43+ use Zend \EventManager \EventManagerInterface ;
44+ use Zend \Http \Request as HttpRequest ;
45+ use Zend \Mvc \MvcEvent ;
46+ use Zend \View \Model \JsonModel ;
47+ use Zend \View \Model \ModelInterface ;
48+
49+ class JsonProblemApiListener implements ListenerAggregateInterface
50+ {
51+ protected $ listeners ;
52+
53+ /**
54+ * {@inheritDoc}
55+ */
56+ public function attach (EventManagerInterface $ events )
57+ {
58+ $ this ->listeners [] = $ events ->attach (MvcEvent::EVENT_RENDER , array ($ this , 'onRender ' ), 1000 );
59+ $ this ->listeners [] = $ events ->attach (MvcEvent::EVENT_FINISH , array ($ this , 'onFinish ' ));
60+ }
61+
62+ /**
63+ * {@inheritDoc}
64+ */
65+ public function detach (EventManagerInterface $ events )
66+ {
67+ foreach ($ this ->listeners as $ key => $ listener ) {
68+ $ detached = false ;
69+ if ($ listener === $ this ) {
70+ continue ;
71+ }
72+ if ($ listener instanceof ListenerAggregateInterface) {
73+ $ detached = $ listener ->detach ($ events );
74+ } elseif ($ listener instanceof CallbackHandler) {
75+ $ detached = $ events ->detach ($ listener );
76+ }
77+
78+ if ($ detached ) {
79+ unset($ this ->listeners [$ key ]);
80+ }
81+ }
82+ }
83+
84+ public function onRender (MvcEvent $ e )
85+ {
86+ // must be an error
87+ if (!$ e ->isError ()) {
88+ return ;
89+ }
90+
91+ // Check the accept headers for application/json
92+ $ request = $ e ->getRequest ();
93+ if (!$ request instanceof HttpRequest) {
94+ return ;
95+ }
96+
97+ $ headers = $ request ->getHeaders ();
98+ if (!$ headers ->has ('Accept ' )) {
99+ return ;
100+ }
101+
102+ $ accept = $ headers ->get ('Accept ' );
103+ $ match = $ accept ->match ('application/json ' );
104+ if (!$ match || $ match ->getTypeString () == '*/* ' ) {
105+ // not application/json
106+ return ;
107+ }
108+
109+ // if we have a JsonModel in the result, then do nothing
110+ $ currentModel = $ e ->getResult ();
111+ if ($ currentModel instanceof JsonModel) {
112+ return ;
113+ }
114+
115+ // create a new JsonModel - use application/api-problem+json fields.
116+ $ response = $ e ->getResponse ();
117+ $ model = new JsonModel (array (
118+ "httpStatus " => $ response ->getStatusCode (),
119+ "title " => $ response ->getReasonPhrase (),
120+ ));
121+
122+ // Find out what the error is
123+ $ exception = $ currentModel ->getVariable ('exception ' );
124+
125+ if ($ currentModel instanceof ModelInterface && $ currentModel ->reason ) {
126+ switch ($ currentModel ->reason ) {
127+ case 'error-controller-cannot-dispatch ' :
128+ $ model ->detail = 'The requested controller was unable to dispatch the request. ' ;
129+ break ;
130+ case 'error-controller-not-found ' :
131+ $ model ->detail = 'The requested controller could not be mapped to an existing controller class. ' ;
132+ break ;
133+ case 'error-controller-invalid ' :
134+ $ model ->detail = 'The requested controller was not dispatchable. ' ;
135+ break ;
136+ case 'error-router-no-match ' :
137+ $ model ->detail = 'The requested URL could not be matched by routing. ' ;
138+ break ;
139+ default :
140+ $ model ->detail = $ currentModel ->message ;
141+ break ;
142+ }
143+ }
144+
145+ if ($ exception ) {
146+ if ($ exception ->getCode ()) {
147+ $ e ->getResponse ()->setStatusCode ($ exception ->getCode ());
148+ }
149+ $ model ->detail = $ exception ->getMessage ();
150+
151+ // find the previous exceptions
152+ $ messages = array ();
153+ while ($ exception = $ exception ->getPrevious ()) {
154+ $ messages [] = "* " . $ exception ->getMessage ();
155+ };
156+ if (count ($ messages )) {
157+ $ exceptionString = implode ("\n" , $ messages );
158+ $ model ->messages = $ exceptionString ;
159+ }
160+ }
161+
162+ // set our new view model
163+ $ model ->setTerminal (true );
164+ $ e ->setResult ($ model );
165+ $ e ->setViewModel ($ model );
166+
167+ // set our json renderer
168+ $ sm = $ e ->getApplication ()->getServiceManager ();
169+ $ view = $ sm ->get ('Zend\View\View ' );
170+ $ strategy = $ sm ->get ('ViewJsonStrategy ' );
171+ $ view ->getEventManager ()->attach ($ strategy , 100 );
172+ }
173+
174+ public function onFinish (MvcEvent $ e )
175+ {
176+ $ response = $ e ->getResponse ();
177+ $ headers = $ response ->getHeaders ();
178+
179+ if ($ headers ->has ('Content-Type ' )
180+ && strpos ($ headers ->get ('Content-Type ' )->getFieldValue (), 'application/json ' ) !== false
181+ && strpos ($ response ->getContent (), 'httpStatus ' ) !== false
182+ ){
183+ /**
184+ * We can almost be certain the response is an api problem,
185+ * since the JSON contains httpStatus
186+ */
187+ $ headers ->addHeaderLine ('Content-Type ' , 'application/api-problem+json ' );
188+ }
189+ }
190+ }
0 commit comments