PHP - XML Parsing (Trying to get property of non-object) - php

Good morning!
I try to describe my problem: I have a language class and in the class there is a function Text(category, item).
so I can call the function like:
Language::Text("indexPage", "hello_world");
And the output would be:
Hello World!
The Values of the items are in a xml file.
Structure of my XML file:
<?xml version="1.0" encoding="UTF-8"?>
<languageBase>
<language filename="en" language="English">
<category name="indexPage">
<item name="hello_world"><![CDATA[Hello World!]]></item>
</category>
</language>
</languageBase>
But if I use the function Text(..), I get an error:
Trying to get property of non-object in
C:\xampp\htdocs\Mvc\system\classes\class.language.php on line 20
This is my class:
<?php
if (!defined("OK")) {
header("Location: ../");
}
class Language {
private static $_language = "de";
public static function params($string) {
$string = str_replace("%USERNAME%", User::GetUsername(User::$_id, 1), $string);
$string = str_replace("%#USERNAME%", User::GetUsername(User::$_id), $string);
return $string;
}
public static function Text($category, $item) {
$xml = new SimpleXMLElement(file_get_contents(self::GetPath()));
$category = "indexPage";
$item = "hello_world";
$obj = $xml->xpath("/languageBase/language/category[#name=\"{$category}\"]/item[#name=\"{$item}\"]");
return $obj->entry;
}
public static function Set($language) {
$_SESSION[SESSION_LANGUAGE] = $language;
}
public static function Get() {
if(isset($_SESSION[SESSION_LANGUAGE])) {
if(file_exists("system/lang/".$_SESSION[SESSION_LANGUAGE].".xml")) {
self::$_language = $_SESSION[SESSION_LANGUAGE];
}
} else {
$_SESSION[SESSION_LANGUAGE] = self::$_language;
}
return self::$_language;
}
public static function GetPath() {
return "system/lang/".self::Get().".xml";
}
}

You never define that you want to access the attribute called "name". There could be any number of other attributes like "id".
What you could do is use xpath like this:
public static function Text($category, $item) {
$xml = new SimpleXMLElement(file_get_contents(self::GetPath()));
$items = $xml->xpath("//category[#name='$category']/item[#name='$item']");
if ($items[0]) {
return (string)$items[0];
}
return null;
}
With this solution it is important to make sure that $category and $item never contains xpath control characters, i.e. $item = 'my[item]' would produce errors.
In that case, you would have to escape $item and $category somehow.
xpath is not always very straight forward to understand. A nice resource is: http://www.w3schools.com/xpath/xpath_syntax.asp

Related

what's wrong with my php code with recursive method?

Here is my php code, the test method not giving wanted output, and the other weird thing is var_dump('a') print 3 times;
my wanted output is array('qtggccc','qtff23sdf');
public function main()
{
$serverIds = array('ff23sdf','ggccc');
$res = $this->test($serverIds);
var_dump($res);
}
public function test($serverIds,$imgArray = array())
{
if(count($serverIds) > 0){
$media_id = array_pop($serverIds);
$imgUrl= $this->hh($media_id);
array_push($imgArray,$imgUrl);
var_dump($serverIds);
var_dump($imgArray);
$this->test($serverIds,$imgArray);
}
var_dump('a');
return $imgArray;
}
public function hh($m)
{
return 'qt'.$m;
}
Try this:
class MyClass{
private $imgArray = array();
public function main(){
$serverIds = array('ff23sdf','ggccc');
$res = $this->test($serverIds);
print_r($this->imgArray);
}
public function test($serverIds){
if(count($serverIds) > 0){
$media_id = end($serverIds);
$imgUrl= $this->hh($media_id);
array_push($this->imgArray,$imgUrl);
//remove last element
array_pop($serverIds);
$this->test($serverIds);
}
return;
}
public function hh($m){
return 'qt'.$m;
}
}
$obj = new MyClass();
echo '<pre>';
$obj->main();
Why use recursion? You are using a complicated solution for a simple problem.
public function main()
{
$serverIds = array('ff23sdf','ggccc');
$res = array();
//These three lines replace an entire recursive function, making the code easier and saving a chunk of memory once you start using real arrays
foreach ($serverIds as $media_id){
array_unshift($res, $this->hh($media_id));
}
var_dump($res);
}
public function hh($m)
{
return 'qt'.$m;
}

Php create Dynamic Xml in foreach

I built an basic xml class (using simple xml), so far I built simple xml nodes.
Now I want to create a function in which generate a specific number of nodes in a foreach, which I specify in the parameter.
it looks like this now:
class Xml
{
private $data = "";
protected $xml = "";
protected $xmlManager = "";
protected $menus = "";
protected $xmlMenus = "";
public function __construct($data = [])
{
$this->data = $data;
$this->xml = new \SimpleXmlElement('<test></test>');
$this->setMenus();
return $this->xml;
}
private function setMenus()
{
$this->xmlMenus = $this->xmlManager->addChild('menus');
}
public function setMenuNode($parameter)
{
$this->data->menu = []
foreach ($this->data->menus as $menuKey => $menuValue)
{
$this->xmlMenu = $this->xmlMenus->addChild('menu');
$menueValue->addAttribute($param1, $param2, $param3);
$menueValue->addAttribute($param1, $param2, $param3);
}
}
}
Later on I want to call it like this
Xml->setMenuNode($param1, $param2, $param3);
Which should create 3 menu nodes.
My xml should look like this later on.
<?xml version="1.0"?>
<menus>
<menu id="1" entry="title">
...
</menu>
<menu id="2" entry="title2">
...
</menu>
<menu id="3" entry="title2">
...
</menu>
</menus>
</dvd>
I am not quiet sure how to manager this in a good way.
This are two jobs, so you should split it into two classes. For a loose dependency define an interface. The menu and the items need to be append itself to their parent.
I use DOM because SimpleXMLElement does not support an empty XML document. This solution would work with SimpleXML, too. But only with an additional outer XML element node.
Interface
This is a contract, so you should define an interface for it.
interface DOMAppendable {
public function appendTo(\DOMNode $parent);
}
Item Class
For a single item implement the interface:
class MenuItem implements DOMAppendable {
private $_id = '';
private $_title = '';
public function __construct($id, $title) {
$this->_id = $id;
$this->_title = $title;
}
public function appendTo(\DOMNode $parent) {
$document = $parent->ownerDocument ?: $parent;
$item = $parent->appendChild($document->createElement('menu'));
$item->setAttribute('id', $this->_id);
$item->setAttribute('title', $this->_title);
}
}
The constructor stores the data into private properties. The method from the interface appends the nodes to the provided parent. The menu items contain more data, have subitems. As long as they implement the interface it will still work.
Menu/List Class
A class for the menu itself stores added menu items into an array property. It implements the interface, too. (So it can be appended to another menu). Implementing __toString() allows to cast the menu into a string.
class Menu implements DOMAppendable {
private $_items = [];
public function add(\DOMAppendable $item) {
foreach (func_get_args() as $item) {
if (!($item instanceOf DOMAppendable)) {
throw new \InvalidArgumentException("Invalid menu item.");
}
$this->_items[] = $item;
}
}
public function appendTo(\DOMNode $parent) {
$document = $parent->ownerDocument ?: $parent;
$menu = $parent->appendChild($document->createElement('menus'));
foreach ($this->_items as $item) {
$item->appendTo($menu);
}
}
public function __toString() {
$document = new \DOMDocument();
$document->formatOutput = TRUE;
$this->appendTo($document);
return $document->saveXml();
}
}
Use
With that you can create the menu using the new classes.
$menu = new Menu();
$menu->add(
new MenuItem(1, 'title1'),
new MenuItem(2, 'title2'),
new MenuItem(3, 'title3')
);
echo $menu;
Output:
<?xml version="1.0"?>
<menus>
<menu id="1" title="title1"/>
<menu id="2" title="title2"/>
<menu id="3" title="title3"/>
</menus>
The interface allows to use different menu item classes, even a menu can be an item:
$menu = new Menu();
$menu->add(
new MenuItem(1, 'title1'),
new MenuItem(2, 'title2')
);
$subMenu = new Menu();
$subMenu->add(new MenuItem(3.1, 'title3.1'));
$menu->add($subMenu);
echo $menu;
Output:
<?xml version="1.0"?>
<menus>
<menu id="1" title="title1"/>
<menu id="2" title="title2"/>
<menus>
<menu id="3.1" title="title3.1"/>
</menus>
</menus>
PHP >= 5.6
PHP 5.6 and later support the new variadics syntax. This allows to remove the func_get_args() call and simplify the Menu::add() method.
class Menu implements DOMAppendable {
private $_items = [];
public function add(\DOMAppendable ...$items) {
foreach ($items as $item) {
$this->_items[] = $item;
}
}
...
Based on what you described you would get (PHP 5.6):
function setMenuNode(...$parameters)
{
foreach ($parameters as $parameter) {
$this->xmlMenu = $this->xmlMenus->addChild('menu');
$menueValue->addAttribute('id', $parameter);
$menueValue->addAttribute('entry', 'title' . $parameter);
}
}

PHP OOP : Fluent interface and tree graphs

I'm trying to create a fluent interface for tree objects.
Here's a simplified example of what I currently do :
<?php
class node {
private $childs = array();
private $parent;
public function __construct($parent = null) {
$this->parent = $parent;
}
public function addChild($child) {
$this->childs[] = $child;
return $this;
}
public function createChild() {
return $this->addChild(new node($this));
}
public function setFoo() {
/* do something */
return $this;
}
}
$root = new node();
$root ->addChild((new node($root))
->setFoo()
)->addChild((new node($root))
->setFoo()
);
?>
I would like to reduce the part where I create the tree.
What I want to do is something like this :
$root->createChild()->setFoo();
$root->createChild()->setFoo();
in one line. And without having to explicitly create new nodes instances (like I did in the first code with new operators).
My goal is to be able to create any tree of any order, and its nodes of any degree without having to put a semi-colon in the code.
Rather than adding a createChild function I think you should change your constructor and addChild functions to consistently establish the parent / child relationship in the data. Once you've done that the addChild function and the constructor can be used to do what you described without a createChild function. Right now your constructor allow cross-linking between different trees and branches in the trees so it's something that will probably need to change anyway.
class node {
private $childs = array();
private $parent;
public function __construct(node $parent = null) {
if(!is_null($parent)) {
$parent->addChild($this);
}
}
public function addChild(node $child) {
$this->childs[] = $child;
$child->parent = $this;
return $this;
}
public function setFoo() {
/* do something */
return $this;
}
}
With this you can chain new objects into a tree:
$tree = (new node())->addChild(new node())
->addChild((new node())->setFoo())
->addChild((new node())->addChild(new node())
->addChild(new node())
->setFoo()
);
Trying to use a createChild function is a catch-22 situation where sometimes you need the parent and sometimes you need the child. You can solve it using a return object that contains both but I think it's a situation that is better avoided. If you don't like the "(new node())" syntax, a static function might be the way to go:
public static function create(node $parent = null) {
return new node($parent);
}
Which might be a little prettier depending on your tastes:
$tree = node::create()->addChild(node::create())
->addChild(node::create()->setFoo())
->addChild(node::create()->addChild(new node())
->addChild(new node())
->setFoo()
);
You can add this methods to create as child as you can.
public function createManyChild($nbrOfChild) {
for($i = 0; $i < $nbrOfChild; $i++){
$this->addChild(new node($this));
}
return $this;
}
And use the code like this.
$root = new node();
$root->createManyChild(3)->setFoo();

PHP SimpleXML to JSON Encode single element array

I am trying to parse xml with php into a single element json array.
This is what I have:
$t = array();
$a=array("url"=>$test->channel->item->link);
array_push($t,$a);
echo json_encode($t);;
Which gives me this:
[{"url":{"0":"http:www.example.com"}}]
But I am looking for this:
[{"url":"http:www.example.com"}]
It seems that $test->channel->item->link parses with curly brackets as {url}
but if I do echo $test->channel->item->link, I get: www.example.com without the curly brackets.
Not sure if this is what you're looking for, but it works :)
$xmlstr = '<?xml version=\'1.0\' standalone=\'yes\'?>
<container>
<channel>
<item>
<link>www.example.com</link>
</item>
</channel>
</container>';
$test = new SimpleXMLElement($xmlstr);
$t = array();
$a = array("url"=>$test->channel->item->link->__toString());
array_push($t,$a);
echo json_encode($t); // [{"url":"www.example.com"}]
Check out this implementation, this is working for me.
class eg{
public $link;
function __construct()
{
$this->link = "www.myweb.com";
}
}
class eg1{
public $item;
function __construct()
{
$this->item = new eg();
}
}
class eg2{
public $channel;
function __construct()
{
$this->channel = new eg1();
}
}
$test = new eg2();
$t = array();
$a=array("url"=>$test->channel->item->link);
array_push($t,$a);
echo json_encode($t);
And this will render the following string
[{"url":"www.myweb.com"}]

Build XML file from class in PHP

I'm wondering if anyone knows how to build an XML file, dynamically from a list of classes?
The classes contains public variables, and look like this.
class node {
public $elementname;
}
I do note that some of the classes are named the same as variables, and in this case, the node would be a subnode element of a node:
class data {
public $dataaset;
}
class dataset {
public $datasetint;
}
would be:
<data>
<dataset>datasetint</dataset>
</data>
Maybe something in SimpleXML or something?
The only solution i can think of linking 2 or more unrelated class is using Annotations.
Annotations is not supported by default in PHP but currently in RFC (Request for Comments: Class Metadata) but bending the time is supported or rejected you can create yours using ReflectionClass & Comments functionality
Example If you have 3 classes like this
class Data {
/**
*
* #var Cleaner
*/
public $a;
/**
*
* #var Extraset
*/
public $b;
public $runMe;
function __construct() {
$this->runMe = new stdClass();
$this->runMe->action = "RUN";
$this->runMe->name = "ME";
}
}
class Cleaner {
public $varInt = 2;
public $varWelcome = "Hello World";
/**
*
* #var Extraset
*/
public $extra;
}
class Extraset {
public $boo = "This is Crazy";
public $far = array(1,2,3);
}
Then you can run a code like this
$class = "Data";
$xml = new SimpleXMLElement("<$class />");
getVariablesXML($class, $xml);
header("Content-Type: text/xml");
$xml->asXML('data.xml');
echo $xml->asXML();
Output
<?xml version="1.0"?>
<Data>
<Cleaner name="a">
<varInt type="integer">2</varInt>
<varWelcome type="string">Hello World</varWelcome>
<Extraset name="extra">
<boo type="string">This is Crazy</boo>
<far type="serialized">a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}</far>
</Extraset>
</Cleaner>
<Extraset name="b">
<boo type="string">This is Crazy</boo>
<far type="serialized">a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}</far>
</Extraset>
<runMe type="serialized">O:8:"stdClass":2:{s:6:"action";s:3:"RUN";s:4:"name";s:2:"ME";}</runMe>
</Data>
Function Used
function getVariablesXML($class, SimpleXMLElement $xml) {
$reflect = new ReflectionClass($class);
foreach ( $reflect->getProperties(ReflectionProperty::IS_PUBLIC) as $property ) {
$propertyReflect = $reflect->getProperty($property->getName());
preg_match("/\#var (.*)/", $propertyReflect->getDocComment(), $match);
$match and $match = trim($match[1]);
if (empty($match)) {
$value = $property->getValue(new $class());
if (is_object($value) || is_array($value)) {
$type = "serialized";
$value = serialize($value);
} else {
$type = gettype($value);
}
$child = $xml->addChild($property->getName(), $value);
$child->addAttribute("type", $type);
} else {
$child = $xml->addChild($match);
$child->addAttribute("name", $property->getName());
if (class_exists($match)) {
getVariablesXML($match, $child);
}
}
}
}

Categories