1: <?php
2: /**
3: * Generic entity
4: *
5: * @author Yannick Huerre <dev@sheoak.fr>
6: * @copyright 2015 (c) Oasiswork
7: * @license https://opensource.org/licenses/MIT MIT
8: */
9: namespace Crunchmail\Entities;
10:
11: use Crunchmail\Client;
12: use Crunchmail\Resources\GenericResource;
13:
14: /**
15: * Generic entity class
16: */
17: class GenericEntity
18: {
19: /**
20: * Caller resource
21: *
22: * @var object
23: */
24: protected $_resource;
25:
26: /**
27: * Entity body
28: *
29: * @var stdClass
30: */
31: protected $_body;
32:
33: protected static $exposeLinks = [];
34:
35: /**
36: * Links remapping
37: *
38: * @var array
39: */
40: protected static $links = [
41: 'recipients' => 'mails'
42: ];
43:
44: /**
45: * Resource mapping
46: *
47: * @var array
48: */
49: protected static $resources = [];
50:
51: /**
52: * Some links could lead to confusion, because they would not return
53: * a proper resource: let's blacklist them
54: *
55: * Untested resources are also here
56: *
57: * @var array
58: */
59: private static $blacklistLinks = [
60: 'preview.html',
61: 'preview.txt'
62: ];
63:
64: /**
65: * Create a new entity
66: *
67: * @param GenericResource $resource caller resource
68: * @param stdClass $data entity data
69: *
70: * @return Crunchmail\Entity\GenericEntity
71: */
72: public function __construct(GenericResource $resource, $data)
73: {
74: $this->_resource = $resource;
75: $this->_body = $data;
76: }
77:
78: /**
79: * Generic conversion to string
80: *
81: * @return string
82: */
83: public function __toString()
84: {
85: return isset($this->_body->url) ? $this->url : '';
86: }
87:
88: /**
89: * Return Entity body
90: *
91: * @return stdClass
92: */
93: public function getBody()
94: {
95: if (empty((array) $this->_body))
96: {
97: return null;
98: }
99:
100: $copy = clone $this->_body;
101: unset($copy->_links);
102:
103: foreach (static::$exposeLinks as $key)
104: {
105: $copy->$key = $this->getLink($key);
106: }
107: return $copy;
108: }
109:
110: /**
111: * Catch get, post, put… methods
112: *
113: * @param string $name method name
114: * @param array $args arguments
115: *
116: * @return Crunchmail\Entity\GenericEntity
117: *
118: * @method mixed get() get() get entity (refresh)
119: * @method mixed delete() delete() delete entity
120: * @method mixed post() post(array $values, string $format='json') post values
121: * @method mixed put() put(array $values, string $format='json') put values
122: * @method mixed patch() patch(array $values, string $format='json') patch values
123: */
124: public function __call($method, $args)
125: {
126: if (!isset($this->_body->url))
127: {
128: throw new \RuntimeException('Entity has no url');
129: }
130:
131: // allow use of rest actions, if a link is actually an action
132: // and not a classic rest resource.
133: // ex: $queue->consume() instead of $queue->consume->post()
134: if (isset($this->_body->_links) &&
135: array_key_exists($method, (array) $this->_body->_links))
136: {
137: return call_user_func_array([$this->$method, 'post'], $args);
138: }
139:
140: return $this->_resource->callRequest($method, $args, $this->url);
141: }
142:
143: /**
144: * Access entity or resources with object properties
145: *
146: * Note that this technic could lead to conflict if a resource and a body
147: * field have the same name
148: *
149: * Ex:
150: * echo $message->title
151: * $arr = $message->recipients->current();
152: *
153: * @param string $name resource name
154: *
155: * @return mixed resource
156: */
157: public function __get($name)
158: {
159: // forbidden resource ie. : $entity->forbidden->post();
160: if (in_array($name, self::$blacklistLinks))
161: {
162: throw new \RuntimeException('Direct access to ' . $name . ' is
163: prohibited');
164: }
165:
166: $body = $this->getBody();
167:
168: if (is_null($body))
169: {
170: throw new \RuntimeException('Entity body is empty');
171: }
172:
173: // shortcut to body fields, when no resource was found
174: // ex: echo $entity->fielname;
175: if (property_exists($body, $name))
176: {
177: return $body->$name;
178: }
179:
180: // a subresource was found, create and return it
181: // assign url to the subresource url (may need mapping)
182: if ($url = $this->getLink($name))
183: {
184: $resourceName = $this->getResourceName($name);
185: // save it to $this->$name, no need to create a new one each time
186: $this->$name = $this->_resource->client->createResource(
187: $resourceName,
188: $url
189: );
190: return $this->$name;
191: }
192:
193: throw new \RuntimeException('Entity has no resource "' . $name . '"');
194: }
195:
196: /**
197: * Check if the resource name is registered has belonging to a special
198: * resource class, ie. 'ContactList'
199: *
200: * @param string $name resource name
201: * @return string
202: */
203: private function getResourceName($name)
204: {
205: return isset(static::$resources[$name]) ? static::$resources[$name] : $name;
206: }
207:
208: /**
209: * Allow use of isset on _body fields
210: *
211: * @param string $key key to check
212: *
213: * @return boolean
214: */
215: public function __isset($key)
216: {
217: return isset($this->_body->$key);
218: }
219:
220: /**
221: * Allow use of isset on _body fields
222: *
223: * @param string $key key to check
224: *
225: * @return boolean
226: */
227: public function __unset($key)
228: {
229: throw new \RuntimeException('unset() is disabled');
230: }
231:
232: /**
233: * Get the content of the links attribute, mapping the name first
234: *
235: * @param string $name name of the field
236: *
237: * @return string
238: */
239: public function getLink($name)
240: {
241: // access to collections
242: $map = isset(self::$links[$name]) ? self::$links[$name] : $name;
243:
244: return isset($this->_body->_links->$map) ?
245: $this->_body->_links->$map->href : null;
246: }
247: }
248: