1<?php
2
3/**
4 * Hoa
5 *
6 *
7 * @license
8 *
9 * New BSD License
10 *
11 * Copyright © 2007-2017, Hoa community. All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions are met:
15 *     * Redistributions of source code must retain the above copyright
16 *       notice, this list of conditions and the following disclaimer.
17 *     * Redistributions in binary form must reproduce the above copyright
18 *       notice, this list of conditions and the following disclaimer in the
19 *       documentation and/or other materials provided with the distribution.
20 *     * Neither the name of the Hoa nor the names of its contributors may be
21 *       used to endorse or promote products derived from this software without
22 *       specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34 * POSSIBILITY OF SUCH DAMAGE.
35 */
36
37namespace Hoa\Event;
38
39use Hoa\Consistency;
40
41/**
42 * Class \Hoa\Event\Event.
43 *
44 * Events are asynchronous at registration, anonymous at use (until we
45 * receive a bucket) and useful to largely spread data through components
46 * without any known connection between them.
47 *
48 * @copyright  Copyright © 2007-2017 Hoa community
49 * @license    New BSD License
50 */
51class Event
52{
53    /**
54     * Event ID key.
55     *
56     * @const int
57     */
58    const KEY_EVENT  = 0;
59
60    /**
61     * Source object key.
62     *
63     * @const int
64     */
65    const KEY_SOURCE = 1;
66
67    /**
68     * Static register of all observable objects, i.e. \Hoa\Event\Source
69     * object, i.e. object that can send event.
70     *
71     * @var array
72     */
73    private static $_register = [];
74
75    /**
76     * Callables, i.e. observer objects.
77     *
78     * @var array
79     */
80    protected $_callable      = [];
81
82
83
84    /**
85     * Privatize the constructor.
86     *
87     */
88    private function __construct()
89    {
90        return;
91    }
92
93    /**
94     * Manage multiton of events, with the principle of asynchronous
95     * attachments.
96     *
97     * @param   string  $eventId    Event ID.
98     * @return  \Hoa\Event\Event
99     */
100    public static function getEvent($eventId)
101    {
102        if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
103            self::$_register[$eventId] = [
104                self::KEY_EVENT  => new self(),
105                self::KEY_SOURCE => null
106            ];
107        }
108
109        return self::$_register[$eventId][self::KEY_EVENT];
110    }
111
112    /**
113     * Declare a new object in the observable collection.
114     * Note: Hoa's libraries use hoa://Event/AnID for their observable objects;
115     *
116     * @param   string                    $eventId    Event ID.
117     * @param   \Hoa\Event\Source|string  $source     Observable object or class.
118     * @return  void
119     * @throws  \Hoa\Event\Exception
120     */
121    public static function register($eventId, $source)
122    {
123        if (true === self::eventExists($eventId)) {
124            throw new Exception(
125                'Cannot redeclare an event with the same ID, i.e. the event ' .
126                'ID %s already exists.',
127                0,
128                $eventId
129            );
130        }
131
132        if (is_object($source) && !($source instanceof Source)) {
133            throw new Exception(
134                'The source must implement \Hoa\Event\Source ' .
135                'interface; given %s.',
136                1,
137                get_class($source)
138            );
139        } else {
140            $reflection = new \ReflectionClass($source);
141
142            if (false === $reflection->implementsInterface('\Hoa\Event\Source')) {
143                throw new Exception(
144                    'The source must implement \Hoa\Event\Source ' .
145                    'interface; given %s.',
146                    2,
147                    $source
148                );
149            }
150        }
151
152        if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
153            self::$_register[$eventId][self::KEY_EVENT] = new self();
154        }
155
156        self::$_register[$eventId][self::KEY_SOURCE] = $source;
157
158        return;
159    }
160
161    /**
162     * Undeclare an object in the observable collection.
163     *
164     * @param   string  $eventId    Event ID.
165     * @param   bool    $hard       If false, just delete the source, else,
166     *                              delete source and attached callables.
167     * @return  void
168     */
169    public static function unregister($eventId, $hard = false)
170    {
171        if (false !== $hard) {
172            unset(self::$_register[$eventId]);
173        } else {
174            self::$_register[$eventId][self::KEY_SOURCE] = null;
175        }
176
177        return;
178    }
179
180    /**
181     * Attach an object to an event.
182     * It can be a callable or an accepted callable form (please, see the
183     * \Hoa\Consistency\Xcallable class).
184     *
185     * @param   mixed   $callable    Callable.
186     * @return  \Hoa\Event\Event
187     */
188    public function attach($callable)
189    {
190        $callable                              = xcallable($callable);
191        $this->_callable[$callable->getHash()] = $callable;
192
193        return $this;
194    }
195
196    /**
197     * Detach an object to an event.
198     * Please see $this->attach() method.
199     *
200     * @param   mixed   $callable    Callable.
201     * @return  \Hoa\Event\Event
202     */
203    public function detach($callable)
204    {
205        unset($this->_callable[xcallable($callable)->getHash()]);
206
207        return $this;
208    }
209
210    /**
211     * Check if at least one callable is attached to an event.
212     *
213     * @return  bool
214     */
215    public function isListened()
216    {
217        return !empty($this->_callable);
218    }
219
220    /**
221     * Notify, i.e. send data to observers.
222     *
223     * @param   string             $eventId    Event ID.
224     * @param   \Hoa\Event\Source  $source     Source.
225     * @param   \Hoa\Event\Bucket  $data       Data.
226     * @return  void
227     * @throws  \Hoa\Event\Exception
228     */
229    public static function notify($eventId, Source $source, Bucket $data)
230    {
231        if (false === self::eventExists($eventId)) {
232            throw new Exception(
233                'Event ID %s does not exist, cannot send notification.',
234                3,
235                $eventId
236            );
237        }
238
239        $data->setSource($source);
240        $event = self::getEvent($eventId);
241
242        foreach ($event->_callable as $callable) {
243            $callable($data);
244        }
245
246        return;
247    }
248
249    /**
250     * Check whether an event exists.
251     *
252     * @param   string  $eventId    Event ID.
253     * @return  bool
254     */
255    public static function eventExists($eventId)
256    {
257        return
258            array_key_exists($eventId, self::$_register) &&
259            self::$_register[$eventId][self::KEY_SOURCE] !== null;
260    }
261}
262
263/**
264 * Flex entity.
265 */
266Consistency::flexEntity('Hoa\Event\Event');
267