Tech Is Hard

Credibility = Talent x Years of experience + Proven hardcore accomplishment

PHP Property Manager: _pm class


class __pm {

    // internal meta-management

    /**
     * Unsets the indicated properties in client class
     *
     * @param  object  $z
     */
    static function init($z) {
        $oReflectClass = new ReflectionClass(get_class($z));

        // For each public property
        foreach ($oReflectClass->getProperties(ReflectionProperty::IS_PUBLIC) as $oReflectProperty) {
            if (!($sDocBlock = $oReflectProperty->getDocComment()) || false === strpos($sDocBlock, " @pm")) continue;

            $propertyInfo = self::getDocBlockInfo($sDocBlock, $pn = $oReflectProperty->getName());
            unset($z->{$pn});
        }
    }

    /**
     * Returns an array of info from a docblock
     *
     * @param  string $sDocComment  docblock
     * @param  string $pn           property name
     * @return array                parsed docblock
     */
    public function getDocBlockInfo($sDocComment, $pn) {

        // get the "short description" from docblock (start in position 3)
        preg_match("/\.*\s\*\s*([^\n]*)/m", $sDocComment, $desc, 0, 3);
        $return = array('label' => isset($desc[1]) ? $desc[1] : $pn);

        preg_match_all("/[^@]+@(\S+)\s*(\S+)?\s*([^@]+)?\n/", $sDocComment, $matches, PREG_SET_ORDER, 3);
        foreach ($matches as $tokens) {
            // token name
            $token = $tokens[1];

            // if we only have the @token, then its value is true
            if (count($tokens) == 2) {
                $return[$token] = true;
            }
            // otherwise we need arguments to @token as the value
            else {
                array_shift($tokens); array_shift($tokens);
                $return[$token] = $tokens;
            }
        }
        return $return;
    }

    // property accessors

    /**
     * Public property accessor
     *
     * @param  mixed   $z
     * @param  string  $pn
     * @return mixed
     */
    static function get($z, $pn) {
        self::trace();
        return null;
    }

    /**
     * Public property accessor
     *
     * @param  object  $z
     * @param  string  $pn
     * @param  mixed   $pv
     * @return mixed   Passed value
     */
    function set($z, $pn, $pv) {
        self::trace();
        return $pv;
    }

init should be called during construction of your object to set up everything for the properties in the calling class that are marked with @pm.

getDocBlockInfo extracts metadata about the property from the docblock and returns an array.  This metadata combined with other reflection will control how we set up this property. So far it looks like:

(
    [label] => Distribution Center
    [pm] => 1
    [var] => Array
        (
            [0] => DistCenter
        )
)

trace() will log a formatted stack trace using trace_line().  (I’m only putting this functionality in __pm for convenience.)

    // -- logging

    /**
     *
     * Logs a trace to the point where called
     *
     * @param  mixed  String to print | TODO function that returns
     *                a string.
     *                (Arguments and return values are
     *                AUTOMATICALLY printed.)
     * @return void
     */
    static function trace() {

        $argc = count($argv = func_get_args());

        $backtrace = debug_backtrace();
        // default order is inside out, so this puts it in "top to bottom"
        $backtrace = array_reverse($backtrace);

        $indent = 0;

        // get rid of weird google analytic request var
        $request = $_REQUEST;
//      unset($request['XXX']); // get rid of anything you don't always want logged

        $file = isset($backtrace[0]['file']) ? $backtrace[0]['file'] : '';
        $description = "<?php {$file}" . ($argc && count($backtrace) < 2 ? " [{$backtrace[0]['line']}] - {$argv[0]}" : '');

        if (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest')
            $description = "AJAX request - " . $description;

            self::line("{$description} \$_REQUEST: " . self::prin_r($request, true));

        // get rid of the last stack entry for having called the log method, itself
        array_pop($backtrace);
	if (count($backtrace) > 0) {
            for ($i = 0, $stackCount = count($backtrace) - 1; $i++) {
                self::line(self::trace_line($indent, $backtrace[$i]));
            }
            self::line($argc ? self::trace_line($indent, $backtrace[$i], $argv[0]) : self::trace_line($indent, $backtrace[$i]));
        }
    }

    /**
     * Returns a single entry.
     *
     * @param  integer  $indent
     * @param  array    $trace_entry
     * @param  string   $argv[2]
     * @return string
     */
    private static function trace_line(&$indent, $trace_entry) {

        $line = array_key_exists('line', $trace_entry) ? "[{$trace_entry['line']}] " : '';
        $verb = $trace_entry['function'];

        if (array_key_exists('class', $trace_entry)) {
            $classfile = $trace_entry['class'];
            $verb = "->" . $verb;
            if (array_key_exists('object', $trace_entry) and $object = $trace_entry['object']) {
                $id = property_exists($object, 'id') ? $object->id : false;
                $class = "" : '>');
                if ($classfile != $classname)
                    $class .= " {$classfile}";
                $verb = $class . $verb;
            }
        else
            $verb = $classfile . $verb;
        }
        else {
            $file = isset($trace_entry['file']) ? substr($trace_entry['file'], self::$_baseDirLength) : 'unknown file';
            $verb = "{$file} " . $verb;
        }
        $s = str_repeat(" ", $indent) . "{$line}{$verb}(" . self::prin_r($trace_entry['args']) . ")";
        $indent += strlen($line);

        if (count($argv = func_get_args()) > 2)
            return $s . "=" . self::prin_r($argv[2], true);
        else
            return $s;
    }

With a couple useful output and formatting functions:

    static function line($string) {
        $line_start = date("m/d/Y H:i:s ");
        error_log("{$line_start}{$string}\n", 3, "__pm.log");
    }

    static function prin_r($arg) {
        $oneLine = preg_replace("/\s*\n\s*/" , ' ', print_r($arg, true));
        return gettype($arg) == 'object' ? '<'.$oneLine.'>' : $oneLine;
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: