The SignalSlot component implements a mechanism for inter and intra object
communication through the use of signals and slots. Signals are emitted through
instances of the ezcSignalCollection class and connected to as many functions as you
like. A function that is connected to a signal is called a slot. When the
signal is emitted all connected slots will be run in the order they are
connected or in the order of the priorities specified when connecting to the signals.
The SignalSlot library supports signals with arguments that are passed on to
the slot. Slots must accept all the parameters that are sent to it. The
SignalSlot library also support prioritized slots. Using this mechanism you can
ensure that a slot is run either before or after other slots.
There are many different variations of the Signal/Slot model. The original
implementations required you to derive from the Signal class in order to have
signals or slots. This is acceptable for languages that support multiple
inheritance. Newer implemtentations circumvent this problem by representing signals
through one instance of the Signal class. In other words, a class that has two
signals will have member two variables holding instances of the Signals class.
This approach can lead to the creation of a lot signal objects which can be
especially problematic for scripting languages. As a result our signal class
can represent several signals.
This first example shows the simplest possible usage of the signal system. We
simply create a new ezcSignalCollection class, connect the "sayHello" signal to
the function "hello" and emit the signal.
1. <?php
2. function hello()
3. {
4. echo "Hello world\n";
5. }
6.
7. $signals = new ezcSignalCollection();
8. $signals->connect( "sayHello", "hello" );
9. $signals->emit( "sayHello" );
10. ?>
The output of this example yields the classic "Hello world".
Slots are not limited to be normal functions only. They are specified using the
pseudo type callback. You can specify normal functions class methods and
static class methods as shown by these variations of the above example.
- functions
1. <?php
2. function hello()
3. {
4. echo "Hello world\n";
5. }
6.
7. $signals = new ezcSignalCollection();
8. $signals->connect( "sayHello", "hello" );
9. $signals->emit( "sayHello" );
10. ?>
- class methods
1. <?php
2. class HelloClass
3. {
4. public function hello()
5. {
6. echo "Hello world\n";
7. }
8. }
9.
10. $signals = new ezcSignalCollection();
11. $signals->connect( "sayHello", array( new HelloClass(), "hello" ) );
12. $signals->emit( "sayHello" );
13. ?>
- static class methods
1. <?php
2. class HelloClass
3. {
4. public function hello()
5. {
6. echo "Hello world\n";
7. }
8. }
9.
10. $signals = new ezcSignalCollection();
11. $signals->connect( "sayHello", array( new HelloClass(), "hello" ) );
12. $signals->emit( "sayHello" );
13. ?>
Typical usage of signals is to have classes emit them when certain events
happen. The following example shows a data object that emits a signal when its
data is changed. The signal is connected to the caching system that clears the
cache when the data is changed.
This example also displays how you can delay the construction of the
ezcSignalCollection object. This can be useful in applications where signals
are used only rarely and you want to avoid the overhead of creating the object.
1. <?php
2. class Data
3. {
4. private $signals = null;
5. public function signals()
6. {
7. if ( $this->signals == null ) $this->signals = new ezcSignalCollection();
8. return $this->signals;
9. }
10.
11. public function manipulate()
12. {
13. // change the data here
14. $this->signals()->emit( "dataChanged" );
15. }
16. }
17.
18. class Cache
19. {
20. public function deleteCache()
21. {
22. echo "Deleting cache\n";
23. }
24. }
25.
26. $cache = new Cache();
27. $data = new Data();
28. $data->signals()->connect( "dataChanged", array( $cache, "deleteCache" ) );
29.
30. $data->manipulate();
31. ?>
Signals can have parameters that will be passed on to the receiving slots. The
next example is an extension of the previous example where the data object
gives some information about the data that was changed. This information is
passed to the receiving slots as a parameter.
1. <?php
2. class Data
3. {
4. private $signals = null;
5.
6. public function signals()
7. {
8. if ( $this->signals == null ) $this->signals = new ezcSignalCollection();
9. return $this->signals;
10. }
11.
12. public function manipulate()
13. {
14. // change the data here
15. $this->signals()->emit( "dataChanged", "calender" );
16. }
17. }
18.
19. class Cache
20. {
21. public function deleteCache( $type )
22. {
23. echo "Deleting cache for ID: {$type}\n";
24. }
25. }
26.
27. $cache = new Cache();
28. $data = new Data();
29. $data->signals()->connect( "dataChanged", array( $cache, "deleteCache" ) );
30.
31. $data->manipulate();
32. ?>
Note that slots must accept the parameters that are passed to them. You will
get errors / warnings if you provide to few or to many parameters to the slots
you have connected to a signal.
Also note that it is not possible to pass signal parameters by
reference except for object types which are always references in PHP 5+.
It is possible to connect several slots to one signal. This just a matter of
calling connect several times. You can freely mix the different slot types.
The following example adds another connection to our previous example that
regenerates the cache after it has been deleted.
1. <?php
2. class Data
3. {
4. private $signals = null;
5.
6. public function signals()
7. {
8. if ( $this->signals == null ) $this->signals = new ezcSignalCollection();
9. return $this->signals;
10. }
11.
12. public function manipulate()
13. {
14. // change the data here
15. $this->signals()->emit( "dataChanged", "calender" );
16. }
17. }
18.
19. class Cache
20. {
21. public function deleteCache( $type )
22. {
23. echo "Deleting cache for ID: {$type}\n";
24. }
25. }
26.
27. class CacheGenerator
28. {
29. public function generateCache( $identifier )
30. {
31. echo "Generating cache for ID: {$identifier}\n";
32. }
33. }
34.
35. $cache = new Cache();
36. $cacheGenerator = new CacheGenerator();
37.
38. $data = new Data();
39. $data->signals()->connect( "dataChanged", array( $cache, "deleteCache" ) );
40. $data->signals()->connect( "dataChanged", array( $cacheGenerator, "generateCache" ) );
41.
42. $data->manipulate();
43. ?>
The previous example shows how to connect several slots to one signal and how
they are executed one after another. The example code is also dependent on the
order the slots are executed. Normally slots are executed in the order they are
connected. Sometimes however it is not possible to connect slots in the order
you want them to be executed. To ensure that some slots are executed before
others you can use the priority system. When connecting you can specify a
priority for that connection. Connections with a higher priority will be
executed before connections with a lower priority. Priority numbers can be
specified using any positive integer. The lower the number the higher the
priority. By default connections are made with a priority of 1000.
This example shows how the connections in Multiple Slots could have been
specified to ensure the order the slots are called.
1. <?php
2. class Data
3. {
4. private $signals = null;
5.
6. public function signals()
7. {
8. if ( $this->signals == null ) $this->signals = new ezcSignalCollection();
9. return $this->signals;
10. }
11.
12. public function manipulate()
13. {
14. // change the data here
15. $this->signals()->emit( "dataChanged", "calender" );
16. }
17. }
18.
19. class Cache
20. {
21. public function deleteCache( $type )
22. {
23. echo "Deleting cache for ID: {$type}\n";
24. }
25. }
26.
27. class CacheGenerator
28. {
29. public function generateCache( $identifier )
30. {
31. echo "Generating cache for ID: {$identifier}\n";
32. }
33. }
34.
35. $cache = new Cache();
36. $cacheGenerator = new CacheGenerator();
37.
38. $data = new Data();
39. $data->signals()->connect( "dataChanged", array( $cacheGenerator, "generateCache" ), 20 );
40. $data->signals()->connect( "dataChanged", array( $cache, "deleteCache" ), 10 );
41.
42. $data->manipulate();
43. ?>
Excessive usage of the priority system can lead to unmaintainable code since it
is hard to track the various priorities that are in use in a system. We don't
recommend using it unless absolutely neccessary.
Sometimes it is useful to connect a signal with a specific name regardless of
the sender ezcSignalCollection to a slot. E.g consider what would happen if the
Data class from our previous example was extended to a DataObject class with a
potential of thousand of instances. If you wanted the caching system to work
you would have to connect each one to the caching system upon creation.
This is both unpractical and time consuming. The solution is to use static
connections. When creating ezcSignalCollection objects you can provide an
identifier to the constructor. Using the ezcSignalStaticConnections you can
connect to all signals from a source with a specific identifier.
The following example shows how to use static connections to connect the signal
"dataChanged" from all objects of the type "DataObject" to the caching system.
1. <?php
2. class DataObject
3. {
4. private $signals = null;
5. private $identifier = null;
6.
7. public function __construct( $id )
8. {
9. $this->identifier = $id;
10. }
11.
12. public function signals()
13. {
14. if ( $this->signals == null ) $this->signals = new ezcSignalCollection( __CLASS__ );
15. return $this->signals;
16. }
17.
18. public function manipulate()
19. {
20. // change the data here
21. $this->signals()->emit( "dataChanged", $this->identifier );
22. }
23. }
24.
25. class Cache
26. {
27. public function deleteCache( $identifier )
28. {
29. echo "Deleting cache for ID: {$identifier}\n";
30. }
31. }
32.
33. $cache = new Cache();
34. ezcSignalStaticConnections::getInstance()->connect( "DataObject",
35. "dataChanged", array( $cache, "deleteCache" ) );
36.
37. $data1 = new DataObject( 1 );
38. $data2 = new DataObject( 2 );
39. $data1->manipulate();
40. $data2->manipulate();
41. ?>
You can mix static connectons and normal connections freely. Static connections
with the same priority as normal connections are executed after the normal connections.