PHP DomDocument appendChild() on a non object - php

I have a PHP class that is used to generate some HTML in the following way:
public $rName;
public $cName;
public $rMonth;
function __construct(){
$this->report = new DOMDocument;
$this->report->loadHTMLFile('template.php');
}
private function addComponent($tag, $content){
$parent = $this->report->getElementById('content');
$child = $this->report->createElement($tag, $content);
$parent->appendChild($child);
}
function addSection($header){
$this->addComponent('h2', $header);
}
function addSubHeader($subHeader){
$this->addComponent('h3', $subHeader);
}
function addContent($content){
$this->addComponent('p', $content);
}
which is being called like this:
$report = new Report;
$outputType = $_GET['outputType'];
$report->rName = 'rName';
$report->cName = $_GET['cName'];
$report->rMonth = $_GET['rMonth'];
$report->addSection('Section');
$report->addSubHeader('SubHeader');
$report->addContent('Content');
Using XAMPP on Windows, this code works absolutely fine. However on a centos environment I get the error:
Call to a member function appendChild() on a non-object on line 16
Line 16 is:
$parent->appendChild($child);
The template.php file appears to be loading, and there is a div with the id of "content", however a gettype() on $parent shows it as NULL.
Pretty stumped at the moment. Any ideas?

In older versions of PHP, getElementById requires that an id attribute has been specified in a DTD.
Inserting <!DOCTYPE html> at the start of the HTML may be enough, in this case.

Related

Understanding PHP External Classes

I want to access a method from an external class, within another file.
I have an external class, let's say:
external/file.php
class ExternalClass {
private $myClient;
const CONSTANT = 'some/path';
public function _constructor($myClient) {
$this->myClient = $myClient;
}
public function getSome($information) { // Need to access this function
$data = new StdObject();
$data->information = $information;
$result = $this->myClient->post(
self::CONSTANT,
$data
);
return($result['code'] == 200 ? json_decode($result['body']) : false);
}
public static function instance($SETTINGS) {
return new ExternalClass(new MyClient($SETTINGS['externalclass']['host']));
}
}
... I would like to reference this class in another file.
internal/file.php
include_once('external/file.php');
$externalClassInstance = ExternalClass::instance($SETTINGS); // line 3
$externalClass = new ExternalClass(); // line 4
$externalClassGetSome = $externalClass->getSome($information); // line 5
The problem is, I'm not sure if I'm correctly referencing the external methods inside the internal file.
Is "Line 3" even necessary?
Also, the addition of Line 5 code breaks any code after it.
Firstly I don't know why you would need line 3.
Secondly you seem to have the answer right there
include_once('external/file.php');
$externalClass = new ExternalClass(); // line 4
$externalClassGetSome = $externalClass->getSome($information);
try to change from
include_once('external/file.php');
to
include_once('../external/file.php');
becauase its in external directory and your file is in internal directory.

creating multiple instances of splFileObject

I have a class similar to this
class x {
function __construct($file){
$this->readData = new splFileObject($file);
}
function a (){
//do something with $this->readData;
}
function b(){
//do something with $this->readData;
}
}
$o = new x('example.txt');
echo $o->a(); //this works
echo $o->b(); //this does not work.
it seems if which ever method called first only works, if they are called together only the first method that is called will work. I think the problem is tied to my lack of understand how the new object gets constructed.
The construct is loaded into the instance of the class. And you're instantiating it only once. And accessing twice. Are different actions. If you want to read the file is always taken, should create a method that reads this file, and within all other trigger this method.
I tested your code and it worked normal. I believe it should look at the logs and see if any error appears. If the file does not exist your code will stop.
Find for this error in your apache logs:
PHP Fatal error: Uncaught exception 'RuntimeException' with message 'SplFileObject::__construct(example.txt): failed to open stream
Answering your comment, this can be a way:
<?php
class x {
private $defaultFile = "example.txt";
private function readDefaultFile(){
$file = $this->defaultFile;
return new splFileObject($file);
}
function a (){
$content = $this->readDefaultFile();
return $content ;
}
function b(){
$content = $this->readDefaultFile();
return $content ;
}
}
$o = new x();
echo $o->a();
echo $o->b();
Both methods will return an object splFile.

php undefined variable error when calling class method

I have been writing procedural php for years and am very comfortable with it. Recently I decided to rework an existing site and switch to PDO and OOP. Everyone is telling me that this is a better way to go but the learning curve is killing me. When trying to call a class, I get the following.
Menu Builder
vbls: 5 1
Notice: Undefined variable: Menu in /home/lance/DallyPost/projectWebSite/trunk/1/core/modules/menuBuilder.php on line 9
Fatal error: Call to a member function menuFramework() on a non-object in /home/lance/DallyPost/projectWebSite/trunk/1/core/modules/menuBuilder.php on line 9
The procedure is that I have included menu.php at the top of index.php, prior to including the following script:
<?php
//menuBuilder.php
echo"<h2>$pageTitle</h2>";
$pub = $_URI_KEY['PUB'];
$dir = $_URI_KEY['DIRECTORY'];
echo"vbls: $pub $dir";
if($Menu->menuFramework("$pub", "$dir") === false) {
echo"The base menu framework failed to build correctly.";
}
else{
echo"<p>The base menu framework has been successfully constructed.</p>";
}
?>
As you can see, the above script calls a method in the Menu class:
<?php
//menu.php
class Menu{
private $db;
public function __construct($database) {
$this->db = $database;
}
public function menuFramework($pub, $directory){
$link = "/" . $directory . "/index.php/" . $pub . "/home/0/Home-Page/";
$inc = "core/menus/" . $pub . "category.php";
$file = "core/menus/" . $pub . "menuFramework.php";
$text = "<nav class=\"top-bar\" data-topbar>";
$text .= "<ul class=\"title-area\">";
$text .= "<li class=\"name\">";
$text .= "<h1>Home Page</h1>";
$text .= "</li>";
$text .= "</ul>";
$text .= "include($inc)";
$text .= "</nav>";
//write text to a file
if(file_put_contents($file, $text)){
return true;
}
else{
return false;
}
}
... rest of file not shown
Can you help me understand why I am getting this error. My understanding is that the variable was or should have been defined when I included menu.php, which was done before which was called a the top if index.php
Thanks
add this at the top of the script
$menu = new Menu($dbHandle);
That's not how classes work. You can't reference an entire class, Menu, with a variable, $Menu, to invoke instance methods.
You need to create an instance of your class on which to invoke methods:
$menu = new Menu(...);
You can create class-level "static" methods which are invoked on the class itself, but that syntax doesn't involve $Menu. You would use Menu::method_name() for that.
You are calling $Menu->
so your are a calling a variable called Menu, instead of the class Menu.
anyways, that function is not static, so you need to instantiate an object.
For that, add a this line:
$menu = new Menu($db);
where $db is your database object, if you really need it, or null if you dont (i cannot say with that code fragment)
and then call
$menu->menuFramework(...)

can't load simple-html-dom class two times in one class

When I try to load the file in a dom object for the secont time(i clear it befor), the dom variable is empty :(
How can I use simple-html-dom class for two times in my class?
<?php
require_once 'simple_html_dom.php';
class RO{
public $dom;
public $dom2;
public function __construct(){
$this->dom = new simple_html_dom();
$this->dom2 = new simple_html_dom();
}
function GetDatumInUroPodatkovInTabKrajev($lang){
$this->dom->load_file("http://www...diferentadresthenbelow...html");
$tabelaTempInKrajev = $this->dom->find('table[#class="online"]');
//some code...
//some code...
return $this->functon2("minkjaaa");
}
function functon2($ik){
$this->dom2->load_file("http://www.arso.gov.si/vreme/napovedi%20in%20podatki/vreme_si.html");
$tabelaVremRazm = $this->dom2->find('table[#class="online"]'); // this line trows an error, becaus dom2 is empty
return"";
}
}
?>
and the trowen error:
[Fatal error: Call to a member function find() on a non-object in C:\..\simple_html_dom.php on line ..]
Find("SELECTORS", index) Find returns an array of objects unless an optional index is specified, then it will return the Nth object... This is probably what you're missing... Here's a working example:
// includes Simple HTML DOM Parser
include "simple_html_dom.php";
$url = "http://www.arso.gov.si/vreme/napovedi%20in%20podatki/vreme_si.html";
//Create a DOM object
$dom2 = new simple_html_dom();
// Load HTML from a string
$dom2->load_file($url);
// Find the link with the appropriate selectors
$table = $dom2->find('table.online', 0);
// Find succeeded
if ($table)
echo $table->outertext;
else
echo "Find function failed !";
// Clear DOM object (needed essentially when using many)
$dom2->clear();
unset($dom2);
Online DEMO

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.

Categories