1: <?php
2:
3: namespace Charcoal\App\Action;
4:
5: // Dependencies from `PHP`
6: use InvalidArgumentException;
7:
8: // PSR-7 (http messaging) dependencies
9: use Psr\Http\Message\RequestInterface;
10: use Psr\Http\Message\ResponseInterface;
11:
12: // PSR-3 (logger) dependencies
13: use Psr\Log\LoggerAwareInterface;
14: use Psr\Log\LoggerAwareTrait;
15:
16: // Dependencies from `Pimple`
17: use Pimple\Container;
18:
19: // Module `charcoal-config` dependencies
20: use Charcoal\Config\AbstractEntity;
21:
22: // Intra-module (`charcoal-app`) dependencies
23: use Charcoal\App\Action\ActionInterface;
24:
25: /**
26: * Default implementation, as abstract class, of the `ActionInterface`.
27: *
28: * Actions respond to a (PSR7-style) request and response and returns back the response.
29: *
30: * Typical implementations only need to implement the following 2 _abstract_ methods:
31: *
32: * ``` php
33: * // Returns an associative array of results
34: * public function results();
35: * // Gets a psr7 request and response and returns a response
36: * public function run(RequestInterface $request, ResponseInterface $response);
37: * ```
38: * Actions can be invoked (with the magic `__invoke()` method) which automatically call the:
39: */
40: abstract class AbstractAction extends AbstractEntity implements
41: ActionInterface,
42: LoggerAwareInterface
43: {
44: use LoggerAwareTrait;
45:
46: const MODE_JSON = 'json';
47: const MODE_XML = 'xml';
48: const MODE_REDIRECT = 'redirect';
49: const DEFAULT_MODE = self::MODE_JSON;
50:
51: /**
52: * @var string $mode
53: */
54: private $mode = self::DEFAULT_MODE;
55:
56: /**
57: * @var boolean $success
58: */
59: private $success = false;
60:
61: /**
62: * @var string $successUrl
63: */
64: private $successUrl;
65:
66: /**
67: * @var string $failureUrl
68: */
69: private $failureUrl;
70:
71: /**
72: * @param array|\ArrayAccess $data The dependencies (app and logger).
73: */
74: public function __construct($data = null)
75: {
76: $this->setLogger($data['logger']);
77:
78: if (isset($data['container'])) {
79: $this->setDependencies($data['container']);
80: }
81: }
82:
83: /**
84: * Initialize the action with a request.
85: *
86: * @param RequestInterface $request The request to initialize.
87: * @return boolean Success / Failure.
88: */
89: public function init(RequestInterface $request)
90: {
91: // This method is a stub. Reimplement in children methods to ensure template initialization.
92: return true;
93: }
94:
95: /**
96: * Give an opportunity to children classes to inject dependencies from a Pimple Container.
97: *
98: * Does nothing by default, reimplement in children classes.
99: *
100: * The `$container` DI-container (from `Pimple`) should not be saved or passed around, only to be used to
101: * inject dependencies (typically via setters).
102: *
103: * @param Container $container A dependencies container instance.
104: * @return void
105: */
106: public function setDependencies(Container $container)
107: {
108: // This method is a stub. Reimplement in children action classes.
109: }
110:
111: /**
112: * @param RequestInterface $request A PSR-7 compatible Request instance.
113: * @param ResponseInterface $response A PSR-7 compatible Response instance.
114: * @return ResponseInterface
115: * @see self::run()
116: */
117: final public function __invoke(RequestInterface $request, ResponseInterface $response)
118: {
119: $response = $this->run($request, $response);
120:
121: switch ($this->mode()) {
122: case self::MODE_JSON:
123: $response->getBody()->write(json_encode($this->results()));
124: $response = $response->withHeader('Content-Type', 'application/json');
125: break;
126:
127: case self::MODE_XML:
128: $response->getBody()->write($this->results());
129: $response = $response->withHeader('Content-Type', 'text/xml');
130: break;
131:
132: case self::MODE_REDIRECT:
133: $response = $response
134: ->withStatus(301)
135: ->withHeader('Location', $this->redirectUrl());
136: break;
137: }
138:
139: return $response;
140: }
141:
142: /**
143: * @param string $mode The action mode.
144: * @throws InvalidArgumentException If the mode argument is not a string.
145: * @return ActionInterface Chainable
146: */
147: public function setMode($mode)
148: {
149: if (!is_string($mode)) {
150: throw new InvalidArgumentException(
151: 'Mode needs to be a string'
152: );
153: }
154: $this->mode = $mode;
155: return $this;
156: }
157:
158: /**
159: * @return string
160: */
161: public function mode()
162: {
163: return $this->mode;
164: }
165:
166: /**
167: * @param boolean $success Success flag (true / false).
168: * @throws InvalidArgumentException If the success argument is not a boolean.
169: * @return ActionInterface Chainable
170: */
171: public function setSuccess($success)
172: {
173: $this->success = !!$success;
174: return $this;
175: }
176:
177: /**
178: * @return boolean
179: */
180: public function success()
181: {
182: return $this->success;
183: }
184:
185: /**
186: * @param string|null $url The success URL.
187: * @throws InvalidArgumentException If the URL parameter is not a string.
188: * @return ActionInterface Chainable
189: */
190: public function setSuccessUrl($url)
191: {
192: if ($url === null) {
193: $this->successUrl = null;
194: return $this;
195: }
196: if (!is_string($url)) {
197: throw new InvalidArgumentException(
198: 'Success URL must be a string'
199: );
200: }
201: $this->successUrl = $url;
202: return $this;
203: }
204:
205: /**
206: * @return string
207: */
208: public function successUrl()
209: {
210: if ($this->successUrl === null) {
211: return '';
212: }
213: return $this->successUrl;
214: }
215:
216: /**
217: * @param string|null $url The success URL.
218: * @throws InvalidArgumentException If the URL parameter is not a string.
219: * @return ActionInterface Chainable
220: */
221: public function setFailureUrl($url)
222: {
223: if ($url === null) {
224: $this->failureUrl = null;
225: return $this;
226: }
227: if (!is_string($url)) {
228: throw new InvalidArgumentException(
229: 'Failure URL must be a string'
230: );
231: }
232: $this->failureUrl = $url;
233: return $this;
234: }
235:
236: /**
237: * @return string
238: */
239: public function failureUrl()
240: {
241: if ($this->failureUrl === null) {
242: return '';
243: }
244: return $this->failureUrl;
245: }
246:
247: /**
248: * @return string
249: */
250: public function redirectUrl()
251: {
252: if ($this->success() === true) {
253: $url = $this->successUrl();
254: } else {
255: $url = $this->failureUrl();
256: }
257:
258: return $url;
259: }
260:
261: /**
262: * Returns an associative array of results (set after being invoked / run).
263: *
264: * The raw array of results will be called from `__invoke()`.
265: *
266: * @return array
267: */
268: abstract public function results();
269:
270: /**
271: * Gets a psr7 request and response and returns a response.
272: *
273: * Called from `__invoke()` as the first thing.
274: *
275: * @param RequestInterface $request A PSR-7 compatible Request instance.
276: * @param ResponseInterface $response A PSR-7 compatible Response instance.
277: * @return ResponseInterface
278: */
279: abstract public function run(RequestInterface $request, ResponseInterface $response);
280: }
281: