Skip to content

Instantly share code, notes, and snippets.

@mkherlakian
Last active August 29, 2015 14:07
Show Gist options
  • Save mkherlakian/4746a319c0b7a534ae99 to your computer and use it in GitHub Desktop.
Save mkherlakian/4746a319c0b7a534ae99 to your computer and use it in GitHub Desktop.

Extending Z-Ray

This is a guide aimed at providing some details on Z-ray extensibility API, and some methodologies/examples on how to write extensions.

Disclaimer: This an early DRAFT, information might be outdated by the time it is being read, or might just be plain wrong. At the time of writing there is no official documentation yet, so this is aimed at filling the blanks.

#Files

Extensions reside in zend-install-path/var/zray/extensions/

The folder structure is simple, one folder per application you would like to extend:

extensions/
  Magento/
  ZF1/
  ZF2/
  Worpdress/
  Drupal/
Z-Ray, upon loading, looks for a single file, called zray.php. This is where the extension is defined.

#Code

The file structure is completely open. First one declares a new extension:

<?php
$zre = new ZRayExtension('magento')
$zre->setMetadata(array('logo' => base64_encode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'logo.png')),
));

The second line is the logo to load.

Then the extension has have a trigger - This is typically a function that is sure to be hit when you want the extension to be active. For Magento, it can be Mage::run, or for Drupal it could be drupal_bootstrap...

<?php
$zre->setEnabledAfter("Mage::run");
or
$zre->setEnabledAfter("drupal_bootstrap");

Or you could enable it by default for any request:

<?php
$zre->setEnabled(true); //not sure if true argument is needed

Then the next step would be to define callbacks for functions or files.

A callback is a Callable that gets executed right before or right after a function gets called, and provides information about the execution if it is executed after.

For this we use:

<?php
$zre->traceFunction(string $function, Callable $before, Callable $after);

$function is the function you want to trace. $before is the callback function that you would like to call right before $function is executed. $after is the callback function that you would like to call right before $function is executed.

Best practice, at least for now, is to declare a class in that file, and declare public methods that act as hooks. The class instance can hold some state variables if needed. Then from there, the argument(s) to traceFunction become a 2-element array, with the first being the class, and the second being the callback function:

<?php
class Magento {
  public function befGenerateBlocks($context, &$storage) {

  }

  public function aftGenerateBlocks($context, &$storage) {

  }
}

$zrayMage = new Magento();
$zre = new ZrayExtension('magento');
$zre->setEnbledAfter('Mage::run');

$zre->traceFunction('Mage_Core_Model_Layout::generateBlocks', array($zrayMage, 'befGenerateBlocks'), array($zrayMage, 'aftGenerateBlocks'));
If only one callback is desired (before or after) declare an empty function for the other one:

$zre->traceFunction('Mage_Core_Model_Layout::generateBlocks', function(){}, array($zrayMage, 'aftGenerateBlocks'));

or

<?php
$zre->traceFunction('Mage_Core_Model_Layout::generateBlocks', array($zrayMage, 'befGenerateBlocks'), function(){});
The callback expects 2 arguments - $context and $storage. Both are discussed below.

#Context

The context is exactly what its name implies, a context of right before/after the function is called.

The context is an array having the following structure:

<?php
$context = array (
  'functionName' => 'function', //the function name
  'functionArgs' => array( //array of function arguments - type is the type of the argument 
    $arg1,
    $arg2
  ),
  'exceptionThrown' => false, // bool, whether exception is thrown 
  'exception' => Exception(), //the exception
  'this' => $instance, //The class instance from which the callback is triggered
  'returnValue' => $return, //the return value, populated in after callback only
  'locals' => array($locals), //an array of the variables that are local in scope to the function being examined, populated in after callback only
  'timesCalled' => int, //number of times a function was called 
  'durationInclusive' => int, //duration for the function only in microsecs, available in after callback only
  'durationExclusive' => int, //duration for the function and its children in microsecs, available in after callback only
  'calledFromFile' => string, //file where function is called
  'calledFromLine' => int, //line where function is called
  'extension' => string, //zray extension instance, useful if using multiple extensions, and setting up traces from within traces (dynamic tracing)
);

##Notes:

You can pass $context by reference - if you do so, you will be able to affect/edit arguments(monkey patching), which is really not recommended - so don't do it. All keys are always populated, but some are only available in the after callback. Context potentially be a very large object.

#Storage

Storage gives you access to the storage API, which is used to store the results of a request.

It is passed by reference to the callback, and is flushed after every call - meaning that when accessed at any point in the callbacks, it will be empty, and it is up to you to fill it.

Generally speaking, it is an array for which:

The first dimension's element is the tab in ZRay in which the result is to be stored The second dimension's element is a row in that table The third element is the row itself, or an array if you wish to display a tree structure (see SUPERGOLBALS in request, $_SESSION for instance) i.e. in a callback, you could do something like:

<?php
//...
  public function beforeGenerateBlocks($context, &$storage) {
    $node = $context['functionArgs'][0]; //fetch the first argument with which Mage_Core_Model_Layout::generateBlocks($parent); was called
    
    //store results we're interested in
    $storage['blocks_stats'][] = array( //tab: blocks_stats, []: add row, array is the row
      'name' => $node->getBlockName(),
      'type' => $node->getBlock(),
      'time (microsecs)' => $context['durationExclusive'],
    );
  }
//...

And get: https://www.dropbox.com/s/cjas6m1fhs83dyi/Screenshot%202014-10-14%2015.43.29.png?dl=0

##Notes:

Ensure that you always pass $storage by reference The ZRayExtension API

Probably lacking - here is what we know:

<?php
class ZRayExtension {
  public function __construct();
  public function traceFunction(string $function, Callable $before, Callable $after);
  public function hasTraceFunction();
  public function traceFile(string $path, Callable $before, Callable $after);
  public function setEnabledAfter(string $function); //enable after a function
  public function setEnabled(bool $enabled); //enable unconditionally
  public function isEnabled(); //returns boolean if extension is enabled
  public function getMetadata(); //returns metadata
  public function setMetadata(array $metadata); //an array of metadata, logo is the only element
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment