provision.context.inc 10.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
<?php
// $Id$

/**
 * @file Provision named context base classes.
 */

/**
 * Store and access context objects by alias name.
 *
 * @param $name
 *   A Drush alias name, including leading @.
 * @param $_root_object
 *   Internal use only, set default object returned by d().
 *
 * @return
 *   provisionContext object.
 */
function & d($name = NULL, $_root_object = FALSE) {
  static $instances = null;
  static $default_instance = '@self';

  if (is_object($name)) {
    return $name;
  }

  if ($name == 'all') {
    return $instances;
  }

  if (is_null($name)) {
    return $instances[$default_instance];
  }
  else {
    if ($_root_object) {
      $default_instance = $name;
    }

    if (isset($instances[$name])) {
      return $instances[$name];
    }
    else {
      $instances[$name] = provision_context_factory($name);
      $instances[$name]->method_invoke('init');
      $instances[$name]->type_invoke('init');

      return $instances[$name];
    }
  }
}

/**
 * Simple access layer for drush_sitealias_get_record.
 *
 * Everytime sitealiases are fetched a lot of processing happens, but if the
 * file doesnt exist yet there's a whole lot of unnecesary stuff happening. 
 *
 * We cache the result locally here.
 */
function provision_sitealias_get_record($name) {
  static $cache = array();

  if (!isset($cache[$name])) {
    $cache[$name] = drush_sitealias_get_record($name);
  }

  return $cache[$name];
}

/**
 * Create a new context object and cache it in the d() static cache
 * function.
 */
function provision_context_factory($name) {
  $classes = array('server', 'platform', 'site');

  $type = 'server';

  $record = provision_sitealias_get_record($name);
80
  $options = array_merge(drush_get_context('stdin'), drush_get_context('options'), drush_get_context('cli'));
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

  if (isset($record['context_type'])) {
    $type = $record['context_type'];
  }
  elseif (isset($options['context_type'])) {
    $type = $options['context_type'];
  }
  $classname = "provisionContext_{$type}";

  return new $classname($name);
}

/**
 * Base context class.
 *
 * Contains magic getter/setter functions
 */
class provisionContext {
  /**
   * Name for saving aliases and referencing.
   */
  public $name = null;

  /**
   * 'server', 'platform', or 'site'.
   */
  public $type = null;

  /**
   * Properties that will be persisted by provision-save. Access as object
   * members, $envoronment->property_name. __get() and __set handle this. In
   * init(), set defaults with setProperty().
   */
  protected $properties = array();

  /**
   * Keeps track of properites that are names of provisionContext objects.
   * Set with is_oid().
   */
  protected $oid_map = array();

  protected $service_subs = array();
  protected $parent_key = null;

  /**
   * Retrieve value from $properties array if property does not exist in class
   * proper. Properties that refer to provisionContext objects will be run
   * through d(), see is_oid().
   *
   * TODO: consider returning a reference to the value, so we can do things like:
   *       `$this->options['option'] = 'value'` and it will correctly set it in the
   *       drush context cache.
   */
  function __get($name) {
    if ($name == 'options') {
136
      return array_merge(provision_sitealias_get_record($this->name), array_filter(drush_get_context('stdin')), array_filter(drush_get_context('options')), array_filte
137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
    }
    if (array_key_exists($name, $this->properties)) {
      if (isset($this->oid_map[$name]) && !empty($this->properties[$name])) {
        return d($this->properties[$name]);
      }
      else {
        return $this->properties[$name];
      }
    }
  }

  /**
   * Specify that a property contains a named context.
   */
  function is_oid($name) {
    $this->oid_map[$name] = TRUE;
  }

  /**
   * Store value in properties array if the property does not exist in class proper.
   */
  function __set($name, $value) {
    if (!property_exists($this, $name)) {
      $this->properties[$name] = $value;
    }
    else {
      $this->$name = $value;
    }
  }

  /**
   * Check the properties array if the property does not exist in the class proper.
   */
  function __isset($name) {
    return isset($this->properties[$name]) || property_exists($this, $name);
  }

  /**
   * Remove the value from the properties array if the property does not exist
   * in the class proper.
   */
  function __unset($name) {
    if (isset($this->properties[$name])) {
      unset($this->properties[$name]);
    }
    elseif (property_exists($this, $name)) {
      unset($this->$name);
    }
  }

  /**
   * Implement the __call magic method.
   *
   * This implementation is really simple. It simply return null if the
   * method doesn't exist.
   *
   * This is used so that we can create methods for drush commands, and
   * can fail safely.
   */
  function __call($name, $args) {
197
    return $this->method_invoke($name, $args);
198 199 200 201 202 203
  }

  /**
   * Execute a method on the object and all of it's associated
   * services.
   */
204 205
  function method_invoke($func, $args = array(), $services = TRUE) {
    provision::method_invoke($this, $func, $args);
206 207
    // Services will be invoked regardless of the existence of a 
    // implementation in the context class.
208 209 210
    if ($services) {
      $this->services_invoke($func, $args);
    }
211
  }
212

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
  /**
   * Execute the method for the current object type.
   *
   * This function is used to avoid having to conditionally
   * check the context objects type to execute the correct code.
   *
   * This will generate a function call like : $method_$type,
   * ie: $this->init_server().
   *
   * Additionally it will dispatch this function call to
   * all the currently enabled services.
   */
  function type_invoke($name, $args = array()) {
    $this->method_invoke("{$name}_{$this->type}");
  }

  /**
   * Allow a server to plug into a drush command that has been called.
   *
   * This method provides a general case for extending drush commands.
   * This allows the developer to not have to conditionally check the
   * context object type in all his methods, and reduces the need
   * to define drush_hook_$command methods for a lot of cases.
   * 
   * This will generate a function call like : $method_$type_cmd.
   */
  function command_invoke($command, $args = array()) {
    $this->method_invoke("{$command}_{$this->type}_cmd");
  }

  /**
   * Constructor for the context.
   */
  function __construct($name) {
    $this->name = $name;
  }

  /**
   * Init stub function.
   */
  function init() {
    preg_match("/^provisionContext_(.*)$/", get_class($this), $matches);
    $this->type = $matches[1];
    $this->setProperty('context_type', $this->type);

258 259 260 261 262 263 264 265 266 267
    // Set up the parent of this context object.
    if (!is_null($this->parent_key)) {
      $this->setProperty($this->parent_key);
      $this->is_oid($this->parent_key);
    }

    // $this->server is always @server_master
    $this->server = '@server_master';
    $this->is_oid('server');

268 269 270 271
    // Set up subscriptions for the available services.
    $service_list = drush_command_invoke_all('provision_services');
    foreach ($service_list as $service => $default) {
      $class = "provisionService_{$service}";
272
      $func = "subscribe_{$this->type}";
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
      if (method_exists($class, $func)) {
        call_user_func(array($class, $func), $this);
      }
    }
    return true;
  }

  /**
   * Check the $options property for a field, saving to the properties array.
   */
  function setProperty($field, $default = NULL, $array = FALSE) {
    if (isset($this->options[$field])) {
      if ($this->options[$field] === 'null') {
        $this->$field = $default;
      }
      elseif ($array && !is_array($this->options[$field])) {
        $this->$field = explode(',', $this->options[$field]);
      }
      else {
        $this->$field = $this->options[$field];
      }
    }
    else {
      $this->$field = $default;
    }
  }

  /**
   * Write out this named context to an alias file.
   */
  function write_alias() {
    $config = new provisionConfig_drushrc_alias($this->name, $this->properties);
    $config->write();
  }

  /**
   * Subscribe a service handler.
   *
   * All future calls to $this->service($service) will be redirected
   * to the context object of #name you specify.
   */
  function service_subscribe($service, $name) {
    $this->service_subs[$service] = $name;
  }

  /**
   * Return a service object for the specific service type.
   *
   * This will return a specifically subscribed service object when one has
   * been registered with service_subscribe, otherwise it will return the value
   * specified by the property specified by $this->parent_key.
   *
   * @param $service
   *   Service type, such as 'http' or 'db'
   * @param $name
   *   Override service owner with a context name as accepted by d().
   *
   * @return
   *   A provisionService object.
   */
  function service($service, $name = null) {
    if (isset($this->service_subs[$service])) {
      return d($this->service_subs[$service])->service($service, ($name) ? $name : $this->name);
    }
    elseif (!is_null($this->parent_key)) {
      return $this->{$this->parent_key}->service($service, ($name) ? $name : $this->name);
    }
    else {
      return new provisionService_null($this->name);
    }
  }

  /**
   * Call method $callback on each of the context's service objects.
   *
   * @param $callback
   *   A provisionService method.
   * @return
   *   An array of return values from method implementations.
   */
  function services_invoke($callback, $args = array()) {
354
    $results = array();
355 356 357 358
    // fetch the merged list of services.
    // These may be on different servers entirely.
    $services = $this->get_services();
    foreach (array_keys($services) as $service) {
359
      $results[$service] = provision::method_invoke($this->service($service), $callback, $args);
360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
    }
    return $results;
  }

  /**
   * Return the merged list of services and the associated servers supplying them.
   *
   * This function will check with the parent_key to retrieve any service subscription
   * it may have, and will add any additional subscriptions.
   *
   * Once the call chain reaches the @server_master object, it will provide the fallbacks
   * if no subscriptions were available.
   */
  function get_services() {
    $services = array();
    if (!is_null($this->parent_key)) {
      $services = $this->{$this->parent_key}->get_services();
    }

    if (sizeof($this->service_subs)) {
      foreach ($this->service_subs as $service => $server) {
        $services[$service] = $server;
      }
    }

    return $services;
  }

  /**
   * Return context-specific configuration options for help.
   *
   * @return
   *   array('--option' => 'description')
   */
  static function option_documentation() {
    return array();
  }
}