ID:               35104
 User updated by:  php at tjworld dot net
 Reported By:      php at tjworld dot net
 Status:           Assigned
 Bug Type:         Class/Object related
 Operating System: Windows 2003
 PHP Version:      5.1.0RC5
 Assigned To:      dmitry
 New Comment:

... and I should have added that returning a DOMDocumentFragment is not
practical because adding attributes and nodes to the new element would
then require new indirect code in the application - you can't add them
to the returned DOMDocumentFragment.

So returning the new sub-class object requires it be removed from the
DOMDocumentFragment *and* more importantly have a variable-reference to
it so that its reference counter isn't zero when the method exits -
otherwise it is destroyed and the caller can't do anything with it, and
will get a:

Warning: DOMElement::setAttribute(): Couldn't fetch extDOMElement
in...

trying to set an attribute on the new sub-classed object.


Previous Comments:
------------------------------------------------------------------------

[2005-11-05 02:56:43] php at tjworld dot net

Based on real-world experience the previous example is not sufficent -
the new Element is destroyed along with the DOMDocumentFragment when
the method exits.

The fix is to remove the new element from the DOMDocumentFragment
before returning it to the caller.

Here's an updated, tested example.

<?php
class extDOMDocument extends DOMDocument {
 public function createElement($name, $value=null) {
  $orphan = new extDOMElement($name, $value); // new  sub-class object
  $docFragment = $this->createDocumentFragment(); // lightweight
container maintains "ownerDocument"
  $docFragment->appendChild($orphan); // attach
  $ret = $docFragment->removeChild($orphan); // remove
  return $ret; // ownerDocument set; won't be destroyed on  method
exit
 }
 // .. more class definition
}

class extDOMElement extends DOMElement {
 function __construct($name, $value='', $namespaceURI=null) {
  parent::__construct($name, $value, $namespaceURI);
 }
  //  ... more class definition here
}

$doc = new extDOMDocument('test');
$el = $doc->createElement('tagname');

// append discards the DOMDocumentFragment and just adds its child
nodes, but ownerDocument is maintained.
$doc->appendChild($el); 
echo $doc->saveXML();
?>

------------------------------------------------------------------------

[2005-11-05 01:00:04] php at tjworld dot net

And finally... for completeness here's a worked example that solves the
DOM case of setting the ownerDocument property.

<?php
class extDOMDocument extends DOMDocument {
 public function createElement($name, $value=null) {
  $orphan = new extDOMElement($name, $value); // new  sub-class object
  $docFragment = $this->createDocumentFragment(); // lightweight
container maintains "ownerDocument"
  $docFragment->appendChild($orphan); // attach
  
  return $docFragment;
 }
 // .. more class definition
}

class extDOMElement extends DOMElement {
 function __construct($name, $value='', $namespaceURI=null) {
  parent::__construct($name, $value, $namespaceURI);
 }
  //  ... more class definition here
}

$doc = new extDOMDocument('test');
$el = $doc->createElement('tagname');

// append discards the DOMDocumentFragment and just adds its child
nodes, but ownerDocument is maintained.
$doc->appendChild($el); 
echo $doc->saveXML();
?>

TJ.

------------------------------------------------------------------------

[2005-11-05 00:02:41] php at tjworld dot net

Following on from my suggestion to provide a strong design template for
read-only properties and inheritence, I've put together the following
example.

It provides for inheritence of a read-only property so the property can
be modified from sub-classes.

<?php
class ReadOnly {
 const CLASS_READ_ONLY_PROPERTY_ERR = 1;
 protected $realProperty;
 private $dynamicProperty = array();
 function __construct() {
  $this->realProperty = 12;
  $this->test = 'read-only';
 }
 public function __set($name, $value) {
  if($name=='test') {
   if(get_class($this)==__CLASS__ &&
isset($this->dynamicProperty[$name]))
    throw new Exception(self::CLASS_READ_ONLY_PROPERTY_ERR);
   else
    $this->dynamicProperty[$name] = $value;
  }
 }
 public function __get($name) { return $this->dynamicProperty[$name];
}

 public function getReal() {return $this->realProperty; }
 public function getDynamic() {return $this->test; }
}

class Writeable extends ReadOnly {
 function __construct($value) {
  parent::__construct();
  $this->realProperty = 25; // ok
  $this->test = $value; // causes Fatal Error
 }
}

echo "Testing Writeable...\r\n";
$test = new Writeable('write to me');
echo 'real: '.$test->getReal()."\r\n";
echo 'dynamic: '.$test->getDynamic()."\r\n";

echo "Testing ReadOnly...\r\n";
$test = new ReadOnly();
echo 'real: '.$test->getReal()."\r\n";
echo 'dynamic: '.$test->getDynamic()."\r\n";
try {
  $test->test = "can't change me";
}
catch(Exception $e) {
  if ($e->getMessage() == ReadOnly::CLASS_READ_ONLY_PROPERTY_ERR) echo
"Read-only Property Exception";
}
?>

Thanks for your prompt and considered attention to this issue.
Hopefully this provides a solution that elegantly solves the issue for
all concerned.

TJ.
Nottingham, UK

------------------------------------------------------------------------

[2005-11-04 23:32:22] php at tjworld dot net

Thanks for the DOM-specific recommendations, I'll have a play about
with it.

The case for dynamic properties is a big issue in itself, since when
extending a class the developer doesn't always have control of the
internal design on the super-class.

This could lead to some frustrating situations where the developer has
every reasonable expectation they can extend the super-class, but in
use trying to modify a protected or public property causes this Fatal
Error.

It could also lead to intermitent results if the statement  attempting
the modification is in a little-used method of the sub-class, and only
occurs on rare occasions.

Without compile-time access-checking (unlike a strongly typed
pre-compiled environment) these kind of issues could occur easily,
especially for the more general developers who aren't so au-fait with
the technicalities of PHP OO dynamic properties.

I was wondering if the properties affected by __set() should be marked
final, which would prevent inconsistencies but reduce functionality to
a great extent.

Maybe its a case of stressing in the documentation *not* to rely on
__set() to produce read-only properties *unless* the __set() method
first checks that the calling class-instance is an instanceof the
super-class - in other words the read-only functionality is conditional
on the method being called on an instance of the super-class itself, not
a sub-class.

This puts the ball back in the developer's court, but you'd need some
good strong documentation across the platform to ensure developers use
this design template.

------------------------------------------------------------------------

[2005-11-04 22:54:02] [EMAIL PROTECTED]

Cant say about other internal classes, but DOM is written specifically
not to allow overriding properties (properties provide direct access to
libxml2 functionality which cant be done in userland). As far as casting
goes... already working on a way to allow returning extended classes
from all functions in dom (for PHP 6).

Current workaround for your issue create your extended class using new
keyword, and append it to a DOMDocumentFragment (if you dont want it
linked into the document tree) that was created using
createDocumentFragment. The appending updates the libxml2 pointers and
the extended object is now linked to the DOMDocument correctly.

Was this report specific to dom or is it to remain open for the
extneded uses object example?

------------------------------------------------------------------------

The remainder of the comments for this report are too long. To view
the rest of the comments, please view the bug report online at
    http://bugs.php.net/35104

-- 
Edit this bug report at http://bugs.php.net/?id=35104&edit=1

Reply via email to