About

Table of content

  1. Preface
  2. Definition of a "signal"
  3. How does it work?
  4. Implemented emitters
    1. RestEmitter
  5. How to integrate the module
  6. Connecting and Disconnecting

Preface

The signals module was originally inspired by qt's signals and slots mechanism. Of course, the concept couldn't be portet 1:1 and the module was written carefully to comply with the semantics of the yii framework.

Yii already has an event mechanism, and there's no reason to replace it. Although it provides some features that don't fit exactly to my definitions of a signal, I don't think these differences are big enough to introduce a whole new mechanism. Instead, the signals module uses the existing event mechanism, and extends it to provide signal emitting.

Definition of a "signal"

The basic ideas of this module are:

  1. signals are emitted
  2. we dont care how they are emitted
  3. we dont care if or who listens to them
  4. and this does not only apply to our application

Looking at the the first three of these basic thoughts, we can say "good enough, yii already handles them". We can raise events, we don't have to worry how they work, we don't need to know nor do we care if or who will later handle them.

The fourth point is the one, the signals module will deal with. It extends the existing event mechanism, so that events might leave the context of the application.

A signal is an event, that can leave the context of you application.

So, if this is what is possible with Yii:


Then this is what is possible with Yii and the signals module:


And in both scenarios, everything Component A does is:

 
  public function onSomeEvent( CEvent $event )
  {
    $this->raiseEvent('someEvent', $event );
  }
 
  public function foo()
  {
    // do something
 
    // raise the Event (or the signal)
    $this->onSomeEvent( new CEvent($this, $eventParams) );
 
    // do something else
  }

How does it work?

The signal behavior, which comes as part of the signals module, uses the event interceptor, to receive notifications whenever the observed subject raises an event. It will then lookup observers from a persistent storage, that have connected to the intercepted event. For each of these observers, the emitter factory will create an appropriate emitter instance, which will notify the registered observer.

Implemented emitters

One of the key concepts of the signals module is that a component, emitting a signal, doesn't care about how the emitting process is implemented. It can be a registered callback (the way Yii implements its events), it could be a request to any server, REST or SOAP, it could be the invocation of an external program, or anything else you can think of. The emitting component doesn't care.

The details of how to emit a certain signal isn't bound to the signal itself. Instead, this is configured per emitter, when connecting to the signal. A connection is defined between the signal, and an endpoint which I'll call slot, according to the terms of the qt signals and slots mechanism.

A slot is the the target, which will be triggered by a signal.

The following sections describe, what kinds of emitters are available.

RestEmitter

The Rest emitter implements the signal emitting process as a POST request. It is configured with the URL of a web resource, which defines the slot for the connection. The URL can point to an action of a Yii application, or to any other web resource accepting POST requests:

  • web applications using other frameworks (PHP, Java, ASP, ...)
  • CGIs
  • ...

The Rest emitter will only write the request. Since we don't know what processing the external applications will do once they received the signal, it won't wait for an answer, but close the connection as soon as possible.

The fact that the Rest emitter triggers new requests, without waiting for them to finish could be used to trigger long running processes in a new request. While this new request would still be processed, the application emitting the signal would quickly finish processing its request and could be kept responsive.

The mechanism might also be used to parallelize some jobs, each in its own request. However, since the emitting component doesn't wait for response, there's no way to validate or even access the results of these jobs.

Connecting a signal from my local development server to a slot on the same local development server caused the server to hang! I experienced the same problem when developing SOAP-Services, where both server and client ran locally, so this might be a general problem on Windows/ XAMPP/ WAMP.

How to integrate the module

  1. Download the latest version from the Yii extensions page .
  2. Extract the module into the modules folder of your application.
  3. Create the database for the module. The SQL script can be found at [signalsModule]/sql/signals-schema-sqlite.sql
  4. configure the module in your application configuration:
     
        return array(
     
        [...]
     
        'modules'=>array(
     
          [...]
     
          // configure the signals module
          'signals' => array(
     
            // class path to the module in alias notation
            'class'=>'application.modules.signals-1_0.SignalsModule',
     
            // Configure the db connection for the module.
            // It can use its own connection, or you can create the tables in your
            // application's db. If you don't use a seperate connection, you can
            // skip this configuration.
            'components'=>array(
              'db' => array(
                'class'=>'CDbConnection',
                'connectionString'=>'sqlite:'.dirname(__FILE__).'/../data/signals.s3db',
              ),
            ),
     
            // configure the signals, to which the outside world can connect
            // (we will cover this later)
            'signals'=>array(
              'Post\\onPostPublished',
            ),
          ),
        ),
     
        [...]
     
        );
  5. Attach the signal behavior to components, that you want to emit signals. This demo application emitts signals for published posts:
     
        class Post extends CActiveRecord
        {
          [...]
     
          // ensure the signal behavior is attached to this component,
          // so that its events can be emitted as signals.
          public function behaviors()
          {
            return array(
     
              // configure the signal behavior
              'signalBehavior' => array(
     
                // class path to the behavior in alias notation
                'class' => 'signals.behaviors.SignalBehavior',
     
                // This is optional. If we don't provide it, every event this
                // component raises might be emitted as a signal. But since we
                // don't want to emit onAfterConstruct-, onAfterFind-,
                // onAfterXyz-Signals (those are too low-level), we can save some
                // overhead by explicitly specifying only the onPostPublished-Event
                // to be emitted as a signal.
                'events' => array(
                  'onPostPublished',
                ),
              ),
            );
          }
     
          // event definition
          public function onPostPublished( CEvent $event )
          {
            $this->raiseEvent( 'onPostPublished', $event );
          }
     
          protected function afterSave()
          {
            [...]
     
            // if the post has been saved and its status is published, raise
            // the onPostPublished event, which contains the post id as a parameter
            if ( intval($this->status) === self::STATUS_PUBLISHED)
              $this->onPostPublished( new CEvent($this, array('id'=>$this->id)) );
          }
     
        }

Well, that's it. We use yii's event mechanism to send out a notification, and we configured the component with the signal behavior to allow its events being emitted as signals. Nothing else is necessary.

connecting and disconnecting

The signals module comes with an SOAP service, that allows people to connect to emitted signals. The WSDL describing the service is available under http://domain/yourWebApp/index.php?r=[signalsModule]/signal/wsdl.

To access the service of this demo app, use its wsdl.

The service defines four operations:

  1. fetching a list of signalIds available for connections
  2. requesting an authentication key
  3. connecting to a signal
  4. disconnecting from a signal

I use an authentication aproach, which doesn't require any form of registration, although that might be a valid aproach, depending on the kind of signals you want to provide. Maybe you only want to allow registered users to connect to your signals. For this demo however, everybody is allowed to connect. What the authentication mechanism validates is, that you don't connect the emitted signals to other people servers.

The basic workflow for connecting is:

  1. fetch the list of signalIds available for connections (I can tell you, there's only one: "Post\onPostPublished")
  2. request an authentication key
  3. create a file named like the authentication key on your server
  4. connect to the signal (the service will check the existence of the authentication key before it establishes the connection)

The basic workflow for disconnecting is:

  1. request an authentication key
  2. create a file named like the authentication key on your server
  3. disconnect from the signal (the service will check the existence of the authentication key before it deletes the connection)

Before we go to an example, let's have a look at the exact API:

 
    class SignalControll extends Controller
    {
      /**
       * @return SignalDTO[] list of available signal ids
       * @soap
       */
      public function getSignals();
 
      /**
       * This method generates a connection key for a given url, and assiciates it
       * with the hostname portion of the url. The connection key is used to
       * connect and disconnect from signals. At any time, there can only be one
       * connection key associated with a given hostname.
       *
       * @param string $url for which a connection key shall be generated
       * @return string the key for the hostname
       * @soap
       */
      public function getConnectionKey( $url );
 
      /**
       * @param string $signalId to which you want to connect
       * @param string $url that shall receive notifications
       * @param string $connectionKeyPath path to the key file. Defaults to an
       *        empty string, which means that the key file won't be created in a
       *        specified folder.
       * @return bool if the connection has been established
       * @soap
       */
      public function connectToSignal( $signalId, $url, $connectionKeyPath='' );
 
      /**
       * @param string $signalId from which you want to disconnect
       * @param string $url to which the signal is connected
       * @param string $connectionKeyPath path to the key file. Defaults to an
       *        empty string, which means that the key file won't be created in a
       *        specified folder.
       * @return bool if the connection has been removed
       * @soap
       */
      public function disconnectFromSignal( $signalId, $url, $connectionKeyPath='' );
    }

So, let's have a look at a PHP script running on "http://example.com", which wants to connect to the signal provided by this demo, and which wants to connect the signal to "http://example.com/slot.php". Let's assume the script is "http://example.com/connect.php", and that there is a web accessable folder "http://example.com/keys" to which the script can write:

 
    $client = new SoapClient( 'http://www.trunk-is-golden.com/SignalsModuleDemo/index.php?r=signals/signal/wsdl' );
    
    // key will be some hash, like "e10b8d9d43fb431839bfd3c995b6daf2"
    $key = $client->getConnectionKey( 'http://example.com' );
 
    // create a file named like the key, so the SOAP service can validate we
    // don't connect the signal to any server but our own
    touch( dirname(__FILE__).'/keys/'.$key );
 
    // connect the signal to our slot, and give the service a hint where we
    // placed the key file
    $result = $client->connectToSignal( 'Post\\onPostPublished', 'http://example.com/slot.php', 'keys' );
 
    // if the connection has been established, we can remove the key file
    if ($result === true)
      unlink( dirname(__FILE__).'/keys/'.$key );

From now on, whenever a post is published on this demo application, the slot http://example.com/slot.php would receive a notification in form of a POST request. The request would contain the id of the published post as a POST-paramter, just like the raised event contained this id. Based on these information, the slot can start doing whatever it was designed for. Maybe it requests the text of the new post, extracts a preview and sends an eMail notification containing that preview to some people.