forcing access to __PHP_Incomplete_Class object properties - php

I'm writing a module for a php cms. In a function (a callback) I can access an object that comes from the framework code.
This object is of type __PHP_Incomplete_Class because the needed header file is not included before the session starts. I cannot include it without hacking the core cms code.
I wonder if is possibile to access the object properties anyway (casting to array does not work). I ask this because I can see the values with var_dump() but using $object->var I always get nulls.

This issue appends when you un serialize an object of a class that hasn't been included yet.
For exemple, if you call session_start before include the class.
A PHPIncompleteClass object can't be accessed directly, but it's ok with foreach, serialize and gettype.
Calling is_object with an PHPIncompleteClass object will result false.
So, if you find a '__PHP_Incomplete_Class' object in your session and you've included your class after the session_load, you can use this function :
function fixObject (&$object)
{
if (!is_object ($object) && gettype ($object) == 'object')
return ($object = unserialize (serialize ($object)));
return $object;
}
This will results a usable object :
fixObject($_SESSION['member']);

I found this hack which will let you cast an object:
function casttoclass($class, $object)
{
return unserialize(preg_replace('/^O:\d+:"[^"]++"/', 'O:' . strlen($class) . ':"' . $class . '"', serialize($object)));
}
From http://blog.adaniels.nl/articles/a-dark-corner-of-php-class-casting/
So you can do:
$obj = casttoclass('stdClass', $incompleteObject);
and then access properties as normal.
You could also define an unserialize_callback_func in a .htaccess/Apache configuration file. That way you wouldn't need to hack any PHP but you could include the file on demand.

As an addition here is my version of the fix_object() function:
The main change is step 3 in the code: Make all properties public.
When PHP serializes an object, all private and protected properties are prefixed with two null-bytes! These null-bytes are the actual reason, why the property cannot be accessed via $obj->key because actually it is something like $obj->{NULL*NULL}key.
/**
* Takes an __PHP_Incomplete_Class and casts it to a stdClass object.
* All properties will be made public in this step.
*
* #since 1.1.0
* #param object $object __PHP_Incomplete_Class
* #return object
*/
function fix_object( $object ) {
// preg_replace_callback handler. Needed to calculate new key-length.
$fix_key = create_function(
'$matches',
'return ":" . strlen( $matches[1] ) . ":\"" . $matches[1] . "\"";'
);
// 1. Serialize the object to a string.
$dump = serialize( $object );
// 2. Change class-type to 'stdClass'.
$dump = preg_replace( '/^O:\d+:"[^"]++"/', 'O:8:"stdClass"', $dump );
// 3. Make private and protected properties public.
$dump = preg_replace_callback( '/:\d+:"\0.*?\0([^"]+)"/', $fix_key, $dump );
// 4. Unserialize the modified object again.
return unserialize( $dump );
}
var_dump will not display these NULL byte prefixes to you, but you can see them with this code:
class Test {
private $AAA = 1;
protected $BBB = 2;
public $CCC = 3;
}
$test = new Test();
echo json_encode( serialize( $test ) );
// Output:
// "O:4:\"Test\":3:{s:9:\"\u0000Test\u0000AAA\";i:1;s:6:\"\u0000*\u0000BBB\";i:2;s:3:\"CCC\";i:3;}"
$test2 = fix_object( $test );
echo json_encode( serialize( $test2 ) );
// Output:
// "O:8:\"stdClass\":3:{s:3:\"AAA\";i:1;s:3:\"BBB\";i:2;s:3:\"CCC\";i:3;}"
There you see:
The private property is prefixed with NULL + classname + NULL
The protected property is prefixed with NULL + "*" + NULL

None of the above answers actually worked for me, except this solution:
$object = unserialize(serialize($object));
$object->function();
Hope it helps someone

I tried the answer of Tom Haigh here, but discovered 2 problems.
when you have other "Incomplete_Class" objects as properties of the top-level-class they stay untouched as __PHP_Incomplete_Class Object
if you have private properties, they will still be private in your stdClass object
So I rewrote the function handle this:
/**
* #see: https://stackoverflow.com/a/965704/2377961
*
* #param object $object The object that should be casted
* #param String $class The name of the class
* #return mixed The new created object
*/
function casttoclass($object, $class = 'stdClass')
{
$ser_data = serialize($object);
# preg_match_all('/O:\d+:"([^"]++)"/', $ser_data, $matches); // find all classes
/*
* make private and protected properties public
* privates is stored as "s:14:\0class_name\0property_name")
* protected is stored as "s:14:\0*\0property_name")
*/
$ser_data = preg_replace_callback('/s:\d+:"\0([^\0]+)\0([^"]+)"/',
function($prop_match) {
list($old, $classname, $propname) = $prop_match;
return 's:'.strlen($propname) . ':"' . $propname . '"';
}, $ser_data);
// replace object-names
$ser_data = preg_replace('/O:\d+:"[^"]++"/', 'O:' . strlen($class) . ':"' . $class . '"', $ser_data);
return unserialize($ser_data);
}
I switch the function arguments too, so that you can use
$obj = casttoclass($incompleteObject);
And you get an stdClass object with only public properties.
Even if you have objects in childclasses they are converted to stdClass with public properties too.

If you just need to access raw data (like class variables) from a PHP_Incomplete_Class object, you can use the foreach hack, or you can also do:
$result_array = (array)$_SESSION['incomplete_object_index'];
echo $result_array['desired_item'];

Put the session_start() after your require to the class of the object you are trying to read from the SESSION

I've read a lot of suggestions on how to fix incomplete classobjects and I actually needed to fix those problems myself, in a ecommerce-project.
One suggestion I've found is to simply use json_decode/json_encode to convert incomplete classes without preloading anything. However, I didn't want to take the risk using this, if there are older PHP versions that are dependent in for example PECL, that is described at http://php.net/manual/en/function.json-encode.php - so I finally succeeded to make my own solution.
However, the code is a way to get the data out of the object properly, so it may not fit all needs - and it will primarily, use the json-solution first, if it is available in the environment and fail over to manual handling if needed.
It also works recursively, which in my own case is required, to save the whole array.
/**
* Convert a object to a data object (used for repairing __PHP_Incomplete_Class objects)
* #param array $d
* #return array|mixed|object
*/
function arrayObjectToStdClass($d = array())
{
/**
* If json_decode and json_encode exists as function, do it the simple way.
* http://php.net/manual/en/function.json-encode.php
*/
if (function_exists('json_decode') && function_exists('json_encode')) {
return json_decode(json_encode($d));
}
$newArray = array();
if (is_array($d) || is_object($d)) {
foreach ($d as $itemKey => $itemValue) {
if (is_array($itemValue)) {
$newArray[$itemKey] = (array)$this->arrayObjectToStdClass($itemValue);
} elseif (is_object($itemValue)) {
$newArray[$itemKey] = (object)(array)$this->arrayObjectToStdClass($itemValue);
} else {
$newArray[$itemKey] = $itemValue;
}
}
}
return $newArray;
}

Related

Using $this when not in object context error within array_walk

I am experiencing an odd problem with using array_walk with closures within a class. The problem does not arise within my development environment using php version 5.4.7 but it does on my deployment environment 5.3.3.
The following code runs fine on my production box, but crashes on my deployment environment:
<?php
error_reporting(-1);
Class TestArrayWalk
{
/** #var null|array */
protected $userInput = null;
/**
* This expects to be passed an array of the users input from
* the input fields.
*
* #param array $input
* #return void
*/
public function setUserInput( array $input )
{
$this->userInput = $input;
// Lets explode the users input and format it in a way that this class
// will use for marking
array_walk( $this->userInput, function( &$rawValue )
{
$rawValue = array(
'raw' => $rawValue,
'words' => $this->splitIntoKeywordArray( $rawValue ),
'marked' => false,
'matched' => array()
);
}
);
}
public function getUserInput()
{
return $this->userInput;
}
protected function splitIntoKeywordArray( $input )
{
if ( ! is_string( $input )){ return array(); }
return preg_split('/(\s|[\.,\/:;!?])/', $input, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
}
}
$testArrayWalk = new TestArrayWalk();
$testArrayWalk->setUserInput(
array(
'This is a test input',
'This is another test input'
)
);
var_dump( $testArrayWalk->getUserInput() );
The error I am getting is: Using $this when not in object context on line 26 which is the only usage of $this within that test class. I am assuming something changed between the versions I am using that has made the above code possible in my development environment.
I also assume that as I cant change the deployment environment (its the clients and they wont change it) that I am going to have to use a foreach rather than array_walk.
My question is this: Given the above, is this possible in 5.3.3 using array_walk if not how do I use foreach in the same way that I am using array_walk ( more specifically the &$rawValue bit)?
My environments are:
My development environment is PHP version 5.4.7
My server (deployment) environment is PHP version 5.3.3
Thanks.
Edit 2
Thanks to everyone who helped. With your help I got this working and have posted my working code to https://gist.github.com/carbontwelve/6727555 for future reference.
This is described in PHP manual:
Version Description
5.4.0 $this can be used in anonymous functions.
Anonymous functions
Possible workaround would be to re-assign this to another variable and pass it via use:
$_this = $this;
function() use($_this) { ... }
but keep in mind you will not be able to access private and protected members so you will have to make splitIntoKeywordArray public
You may also use if you are using PHP < 5.4
public function setUserInput( array $input )
{
$this->userInput = $input;
$userInput_array = &$this->userInput;
array_walk( &$userInput_array, function( &$rawValue ) use (&$userInput_array) {
// use $userInput_array instaed of $this->userInput_array
});
}
I had a similar problem with a lambda function defined in a private function within a class:
MyClass{
private $str="doesn't work :-(";
private function foo(){
$bar=function(){
echo $this->str; // triggers an "Using $this when not in object context" error
};
$bar();
}
}
Solution for PHP 5.3.0: declare a variable $obj=&$this in your parent scope (ie. the private function) and pass it to the anonymouse function by using the use language construct. Also make sure the function/variable that you access has a public visibility (protected|private might not work).
MyClass{
public $str="it just works :-)";
private function foo(){
$obj=&$this;
$bar=function() use(&$obj){
echo $this->str; // it works!
};
$bar();
}
}

Add caching to Zend\Form\Annotation\AnnotationBuilder

As I've finally found a binary of memcache for PHP 5.4.4 on Windows, I'm speeding up the application I'm currently developing.
I've succeeded setting memcache as Doctrine ORM Mapping Cache driver, but I need to fix another leakage: Forms built using annotations.
I'm creating forms according to the Annotations section of the docs. Unfortunately, this takes a lot of time, especially when creating multiple forms for a single page.
Is it possible to add caching to this process? I've browsed through the code but it seems like the Zend\Form\Annotation\AnnotationBuilder always creates the form by reflecting the code and parsing the annotations. Thanks in advance.
You might wanna try something like this:
class ZendFormCachedController extends Zend_Controller_Action
{
protected $_formId = 'form';
public function indexAction()
{
$frontend = array(
'lifetime' => 7200,
'automatic_serialization' => true);
$backend = array('cache_dir' => '/tmp/');
$cache = Zend_Cache::factory('Core', 'File', $frontend, $backend);
if ($this->getRequest()->isPost()) {
$form = $this->getForm(new Zend_Form);
} else if (! $form = $cache->load($this->_formId)) {
$form = $this->getForm(new Zend_Form);
$cache->save($form->__toString(), $this->_formId);
}
$this->getHelper('layout')->setLayout('zend-form');
$this->view->form = $form;
}
Found here.
Louis's answer didn't work for me so what I did was simply extend AnnotationBuilder's constructor to take a cache object and then modified getFormSpecification to use that cache to cache the result. My function is below..
Very quick work around...sure it could be improved. In my case, I was limited to some old hardware and this took the load time on a page from 10+ seconds to about 1 second
/**
* Creates and returns a form specification for use with a factory
*
* Parses the object provided, and processes annotations for the class and
* all properties. Information from annotations is then used to create
* specifications for a form, its elements, and its input filter.
*
* MODIFIED: Now uses local cache to store parsed annotations
*
* #param string|object $entity Either an instance or a valid class name for an entity
* #throws Exception\InvalidArgumentException if $entity is not an object or class name
* #return ArrayObject
*/
public function getFormSpecification($entity)
{
if (!is_object($entity)) {
if ((is_string($entity) && (!class_exists($entity))) // non-existent class
|| (!is_string($entity)) // not an object or string
) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects an object or valid class name; received "%s"',
__METHOD__,
var_export($entity, 1)
));
}
}
$formSpec = NULL;
if ($this->cache) {
//generate cache key from entity name
$cacheKey = (is_string($entity) ? $entity : get_class($entity)) . '_form_cache';
//get the cached form annotations, try cache first
$formSpec = $this->cache->getItem($cacheKey);
}
if (empty($formSpec)) {
$this->entity = $entity;
$annotationManager = $this->getAnnotationManager();
$formSpec = new ArrayObject();
$filterSpec = new ArrayObject();
$reflection = new ClassReflection($entity);
$annotations = $reflection->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureForm($annotations, $reflection, $formSpec, $filterSpec);
}
foreach ($reflection->getProperties() as $property) {
$annotations = $property->getAnnotations($annotationManager);
if ($annotations instanceof AnnotationCollection) {
$this->configureElement($annotations, $property, $formSpec, $filterSpec);
}
}
if (!isset($formSpec['input_filter'])) {
$formSpec['input_filter'] = $filterSpec;
}
//save annotations to cache
if ($this->cache) {
$this->cache->addItem($cacheKey, $formSpec);
}
}
return $formSpec;
}

How to serialize/save a DOMElement in $_SESSION?

I'm pretty new to PHP, DOM, and the PHP DOM implementation. What I'm trying to do is save the root element of the DOMDocument in a $_SESSION variable so I can access it and modify it on subsequent page loads.
But I get an error in PHP when using $_SESSION to save state of DOMElement:
Warning: DOMNode::appendChild() [domnode.appendchild]: Couldn't fetch DOMElement
I have read that a PHP DOMDocument object cannot be saved to $_SESSION natively. However it can be saved by saving the serialization of the DOMDocument (e.g. $_SESSION['dom'] = $dom->saveXML()).
I don't know if the same holds true for saving a DOMElement to a $_SESSION variable as well, but that's what I was trying. My reason for wanting to do this is to use an extended class of DOMElement with one additional property. I was hoping that by saving the root DOMElement in $_SESSION that I could later retrieve the element and modify this additional property and perform a test like, if (additionalProperty === false) { do something; }. I've also read that by saving a DOMDocument, and later retrieving it, all elements are returned as objects from native DOM classes. That is to say, even if I used an extended class to create elements, the property that I subsequently need will not be accessible, because the variable holding reference to the extended-class object has gone out of scope--which is why I'm trying this other thing. I tried using the extended class (not included below) first, but got errors...so I reverted to using a DOMElement object to see if that was the problem, but I'm still getting the same errors. Here's the code:
<?php
session_start();
$rootTag = 'root';
$doc = new DOMDocument;
if (!isset($_SESSION[$rootTag])) {
$_SESSION[$rootTag] = new DOMElement($rootTag);
}
$root = $doc->appendChild($_SESSION[$rootTag]);
//$root = $doc->appendChild($doc->importNode($_SESSION[$rootTag], true));
$child = new DOMElement('child_element');
$n = $root->appendChild($child);
$ct = 0;
foreach ($root->childNodes as $ch) echo '<br/>'.$ch->tagName.' '.++$ct;
$_SESSION[$rootTag] = $doc->documentElement;
?>
This code gives the following errors (depending on whether I use appendChild directly or the commented line of code using importNode):
Warning: DOMNode::appendChild() [domnode.appendchild]: Couldn't fetch DOMElement in C:\Program Files\wamp_server_2.2\www\test2.php on line 11
Warning: DOMDocument::importNode() [domdocument.importnode]: Couldn't fetch DOMElement in C:\Program Files\wamp_server_2.2\www\test2.php on line 12
I have several questions. First, what is causing this error and how do I fix it? Also, if what I'm trying to do isn't possible, then how can I accomplish my general objective of saving the 'state' of a DOM tree while using a custom property for each element? Note that the additional property is only used in the program and is not an attribute to be saved in the XML file. Also, I can't just save the DOM back to file each time, because the DOMDocument, after a modification, may not be valid according to a schema I'm using until later when additional modificaitons/additions have been performed to the DOMDocument. That's why I need to save a temporarily invalid DOMDocument. Thanks for any advice!
EDITED:
After trying hakre's solution, the code worked. Then I moved on to trying to use an extended class of DOMElement, and, as I suspected, it did not work. Here's the new code:
<?php
session_start();
//$_SESSION = array();
$rootTag = 'root';
$doc = new DOMDocument;
if (!isset($_SESSION[$rootTag])) {
$root = new FreezableDOMElement($rootTag);
$doc->appendChild($root);
} else {
$doc->loadXML($_SESSION[$rootTag]);
$root = $doc->documentElement;
}
$child = new FreezableDOMElement('child_element');
$n = $root->appendChild($child);
$ct = 0;
foreach ($root->childNodes as $ch) {
$frozen = $ch->frozen ? 'is frozen' : 'is not frozen';
echo '<br/>'.$ch->tagName.' '.++$ct.': '.$frozen;
//echo '<br/>'.$ch->tagName.' '.++$ct;
}
$_SESSION[$rootTag] = $doc->saveXML();
/**********************************************************************************
* FreezableDOMElement class
*********************************************************************************/
class FreezableDOMElement extends DOMElement {
public $frozen; // boolean value
public function __construct($name) {
parent::__construct($name);
$this->frozen = false;
}
}
?>
It gives me the error Undefined property: DOMElement::$frozen. Like I mentioned in my original post, after saveXML and loadXML, an element originally instantiated with FreezableDOMElement is returning type DOMElement which is why the frozen property is not recognized. Is there any way around this?
You can not store a DOMElement object inside $_SESSION. It will work at first, but with the next request, it will be unset because it can not be serialized.
That's the same like for DOMDocument as you write about in your question.
Store it as XML instead or encapsulate the serialization mechanism.
You are basically facing three problems here:
Serialize the DOMDocument (you do this to)
Serialize the FreezableDOMElement (you do this to)
Keep the private member FreezableDOMElement::$frozen with the document.
As written, serialization is not available out of the box. Additionally, DOMDocument does not persist your FreezableDOMElement even w/o serialization. The following example demonstrates that the instance is not automatically kept, the default value FALSE is returned (Demo):
class FreezableDOMElement extends DOMElement
{
private $frozen = FALSE;
public function getFrozen()
{
return $this->frozen;
}
public function setFrozen($frozen)
{
$this->frozen = (bool)$frozen;
}
}
class FreezableDOMDocument extends DOMDocument
{
public function __construct()
{
parent::__construct();
$this->registerNodeClass('DOMElement', 'FreezableDOMElement');
}
}
$doc = new FreezableDOMDocument();
$doc->loadXML('<root><child></child></root>');
# own objects do not persist
$doc->documentElement->setFrozen(TRUE);
printf("Element is frozen (should): %d\n", $doc->documentElement->getFrozen()); # it is not (0)
As PHP does not so far support setUserData (DOM Level 3), one way could be to store the additional information inside a namespaced attribute with the element. This can also be serialized by creating the XML string when serializing the object and loading it when unserializing (see Serializable). This then solves all three problems (Demo):
class FreezableDOMElement extends DOMElement
{
public function getFrozen()
{
return $this->getFrozenAttribute()->nodeValue === 'YES';
}
public function setFrozen($frozen)
{
$this->getFrozenAttribute()->nodeValue = $frozen ? 'YES' : 'NO';
}
private function getFrozenAttribute()
{
return $this->getSerializedAttribute('frozen');
}
protected function getSerializedAttribute($localName)
{
$namespaceURI = FreezableDOMDocument::NS_URI;
$prefix = FreezableDOMDocument::NS_PREFIX;
if ($this->hasAttributeNS($namespaceURI, $localName)) {
$attrib = $this->getAttributeNodeNS($namespaceURI, $localName);
} else {
$this->ownerDocument->documentElement->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:' . $prefix, $namespaceURI);
$attrib = $this->ownerDocument->createAttributeNS($namespaceURI, $prefix . ':' . $localName);
$attrib = $this->appendChild($attrib);
}
return $attrib;
}
}
class FreezableDOMDocument extends DOMDocument implements Serializable
{
const NS_URI = '/frozen.org/freeze/2';
const NS_PREFIX = 'freeze';
public function __construct()
{
parent::__construct();
$this->registerNodeClasses();
}
private function registerNodeClasses()
{
$this->registerNodeClass('DOMElement', 'FreezableDOMElement');
}
/**
* #return DOMNodeList
*/
private function getNodes()
{
$xp = new DOMXPath($this);
return $xp->query('//*');
}
public function serialize()
{
return parent::saveXML();
}
public function unserialize($serialized)
{
parent::__construct();
$this->registerNodeClasses();
$this->loadXML($serialized);
}
public function saveBareXML()
{
$doc = new DOMDocument();
$doc->loadXML(parent::saveXML());
$xp = new DOMXPath($doc);
foreach ($xp->query('//#*[namespace-uri()=\'' . self::NS_URI . '\']') as $attr) {
/* #var $attr DOMAttr */
$attr->parentNode->removeAttributeNode($attr);
}
$doc->documentElement->removeAttributeNS(self::NS_URI, self::NS_PREFIX);
return $doc->saveXML();
}
public function saveXMLDirect()
{
return parent::saveXML();
}
}
$doc = new FreezableDOMDocument();
$doc->loadXML('<root><child></child></root>');
$doc->documentElement->setFrozen(TRUE);
$child = $doc->getElementsByTagName('child')->item(0);
$child->setFrozen(TRUE);
echo "Plain XML:\n", $doc->saveXML(), "\n";
echo "Bare XML:\n", $doc->saveBareXML(), "\n";
$serialized = serialize($doc);
echo "Serialized:\n", $serialized, "\n";
$newDoc = unserialize($serialized);
printf("Document Element is frozen (should be): %s\n", $newDoc->documentElement->getFrozen() ? 'YES' : 'NO');
printf("Child Element is frozen (should be): %s\n", $newDoc->getElementsByTagName('child')->item(0)->getFrozen() ? 'YES' : 'NO');
It's not really feature complete but a working demo. It's possible to obtain the full XML without the additional "freeze" data.

Joomla PHP error within page content

Warning: Parameter 3 to showBlogSection() expected to be a reference, value given in /home/smartsta/public_html/includes/Cache/Lite/Function.php on line 100
I'm getting the above error displaying within my content areas on my Joomla site all a sudden, any suggestions?
Update: No such luck finding access to defined file and directory within godaddy ftp file directory, ftp, or Joomal C-panel.
Within FTP, I cannot find access to this particular file to investigate what is on line 100.
Within the Joomla panel, in Global Configurations, I was able to toggle 'error message' to none for atleast this error to be hidden. Within the cache directory I do not see any options to get into the folder, though it displays.
I also see this at the bottom of that c-panel screen, but just links to a joomla help site, and within the fields I do not see described area to toggle 'ON or OFF'
"Following PHP Server Settings are not optimal for Security and it is recommended to change them:
PHP register_globals setting is ON instead of OFF
"
Update2!:
I've found the file in question, below is the code. Line 100 only states:
global $$object_123456789;
application/x-httpd-php Function.php
PHP script text
<?php
/**
* This class extends Cache_Lite and can be used to cache the result and output of functions/methods
*
* This class is completly inspired from Sebastian Bergmann's
* PEAR/Cache_Function class. This is only an adaptation to
* Cache_Lite
*
* There are some examples in the 'docs/examples' file
* Technical choices are described in the 'docs/technical' file
*
* #package Cache_Lite
* #version $Id: Function.php 47 2005-09-15 02:55:27Z rhuk $
* #author Sebastian BERGMANN <sb#sebastian-bergmann.de>
* #author Fabien MARTY <fab#php.net>
*/
// no direct access
defined( '_VALID_MOS' ) or die( 'Restricted access' );
require_once( $mosConfig_absolute_path . '/includes/Cache/Lite.php' );
class Cache_Lite_Function extends Cache_Lite
{
// --- Private properties ---
/**
* Default cache group for function caching
*
* #var string $_defaultGroup
*/
var $_defaultGroup = 'Cache_Lite_Function';
// --- Public methods ----
/**
* Constructor
*
* $options is an assoc. To have a look at availables options,
* see the constructor of the Cache_Lite class in 'Cache_Lite.php'
*
* Comparing to Cache_Lite constructor, there is another option :
* $options = array(
* (...) see Cache_Lite constructor
* 'defaultGroup' => default cache group for function caching (string)
* );
*
* #param array $options options
* #access public
*/
function Cache_Lite_Function($options = array(NULL))
{
if (isset($options['defaultGroup'])) {
$this->_defaultGroup = $options['defaultGroup'];
}
$this->Cache_Lite($options);
}
/**
* Calls a cacheable function or method (or not if there is already a cache for it)
*
* Arguments of this method are read with func_get_args. So it doesn't appear
* in the function definition. Synopsis :
* call('functionName', $arg1, $arg2, ...)
* (arg1, arg2... are arguments of 'functionName')
*
* #return mixed result of the function/method
* #access public
*/
function call()
{
$arguments = func_get_args();
$id = serialize($arguments); // Generate a cache id
if (!$this->_fileNameProtection) {
$id = md5($id);
// if fileNameProtection is set to false, then the id has to be hashed
// because it's a very bad file name in most cases
}
$data = $this->get($id, $this->_defaultGroup);
if ($data !== false) {
$array = unserialize($data);
$output = $array['output'];
$result = $array['result'];
} else {
ob_start();
ob_implicit_flush(false);
$target = array_shift($arguments);
if (strstr($target, '::')) { // classname::staticMethod
list($class, $method) = explode('::', $target);
$result = call_user_func_array(array($class, $method), $arguments);
} else if (strstr($target, '->')) { // object->method
// use a stupid name ($objet_123456789 because) of problems when the object
// name is the same as this var name
list($object_123456789, $method) = explode('->', $target);
global $$object_123456789;
$result = call_user_func_array(array($$object_123456789, $method), $arguments);
} else { // function
$result = call_user_func_array($target, $arguments);
}
$output = ob_get_contents();
ob_end_clean();
$array['output'] = $output;
$array['result'] = $result;
$this->save(serialize($array), $id, $this->_defaultGroup);
}
echo($output);
return $result;
}
}
?>
It is not exactly an error. It is a warning.
Suddenly? Perhaps you have upgraded/updated your PHP version. Or changed PHP configuration to "strict mode".
The message "expected to be a reference, value given" means the called function expected to receive a reference, not a value. Look:
$something = 9;
show_section($something);
// here you are passing a variable
// this will be accepted as a reference
show_section(9);
// here you are NOT passing a reference
// here you are passing a VALUE
When you pass "by reference", the function can change the variable value... in the example above:
function show_section(&$parameter) {
$parameter = 'changed!';
}
Note the ampersand symbol & before the $parameter - this is how we specify a function requires a REFERENCE.
AFTER the function call, in the example above, the variable $something value will be the changed! string.
The line throwing the error is NOT the "global" one. It is the next:
$result = call_user_func_array(array($$object_123456789, $method), $arguments);
The problem here is that the function is being called indirectly by using the "call_user_func_array" function.
A solution would be transforming all arguments into references. Suggestion:
foreach ($arguments as $count => $value)
{
$param = 'param' . $count;
$$param = $value;
$arguments[$count] = &$$param;
}
Put the code above in the beginning of the call function, right after the following line:
$id = serialize($arguments);
Give this a try!

PHP Class Function List in Comments

Whenever I write a class in PHP, I like to put a list of public functions at the top of the class like this:
// add_project($apikey, $post, $email)
// create_user($apikey, $email, $firstname, $lastname, $rolename, $pid)
// user_join_project($apikey, $email, $pid)
I was wondering if there were any tools that can automate this process. Like perhaps I could load the class file and it could generate a list of the function names, variables and such?
Thanks!
Try phpDocumentor. You use DocBlock syntax for comments (similar to Javadoc and in other languages) then pass your PHP source files through the phpDocumentor parser, and it generates API documentation for you.
A rough example:
/**
* Adds a project.
*
* #param string $apikey The API key.
* #param object $post The post.
* #param string $email A supplied email address.
* #return void
*/
function add_project($apikey, $post, $email) {
...
}
There's a not great deal of value in this approach.
First off, if you're not already doing so you should mark each of your methods (and indeed member variables) with the appropriate visibility (public/protected/private). You could then use a solution such as phpDoc, which to properly document each method argument in addition to providing an overall purpose for each method/class, etc.
You can then automatically generate documentation for your project in HTML format (amongst others).
There are various tools for this. They are called PHP documentor's. I use an IDE called PHPStorm that has a documentor integrated.
Here is some more information on PHPDoc.
This can be done pretty easily with Parsekit.
Using this ("tmp.php") as sample data:
<?php
class Fruit
{
public function apple($cored) {}
public function orange($peeled) {}
public function grape($colour, $seedless) {}
}
Here's a simple Parsekit example to dump a class's functions:
<?php
$parsed = parsekit_compile_file($argv[1]);
foreach ($parsed['class_table'] as $class => $classdat) {
foreach ($classdat['function_table'] as $func => $funcdat) {
echo "{$class}::{$func}(";
$first = true;
foreach ($funcdat['arg_info'] as $arg => $argdat) {
if (!$first) {
echo ', ';
}
echo "\${$argdat['name']}";
$first = false;
}
echo ")\n";
}
}
…and here it is in use:
$ php parse.php tmp.php
Fruit::grape($colour, $seedless)
Fruit::orange($peeled)
Fruit::apple($cored)

Categories