Php create Dynamic Xml in foreach - php

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);
}
}

Related

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 - XML Parsing (Trying to get property of non-object)

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

PHP Iterator like mysql iterator?

Already I extended and implemented from SPL iterator.
But if I want to use it, I should use it on a foreach.
I tried to use it in a while like this:
$news = new testClass();
while( $row = $news )
echo $row["name"];
It will create an infinite loop !
But with foreach, it works fine!
Here is top of my class:
class testClass implements \Iterator
Where is the mistake ?
Fist, bravo on using the SPL classes for this type of 'standard' problem. Too often have I seen inexperienced/sloppy developers (or even good ones that simply don't think ahead) reinvent the wheel in these types of situations.
You're missing some very important details about the implementation of the iterator interface.
see PHP:Iterator - Manual for more information, and the reference implementation from below.
First, you need to implement the, rewind, current, key, next, and valid functions. the reference implementation looks like this:
class myIterator implements Iterator {
private $position = 0;
private $array = array(
"firstelement",
"secondelement",
"lastelement",
);
public function __construct() {
$this->position = 0;
}
function rewind() {
var_dump(__METHOD__);
$this->position = 0;
}
function current() {
var_dump(__METHOD__);
return $this->array[$this->position];
}
function key() {
var_dump(__METHOD__);
return $this->position;
}
function next() {
var_dump(__METHOD__);
++$this->position;
}
function valid() {
var_dump(__METHOD__);
return isset($this->array[$this->position]);
}
}
)
And the code for traversing that implementation looks like this:
$it = new myIterator;
foreach($it as $key => $value) {
var_dump($key, $value);
echo "\n";
}
foreach is language construct that iterates through all elements. while executes block of code until given condition is true. To make it work you have to use your own function that checks for valid key and returns current element.
Finally I created a simple example of this:
<?php
/**
* #author Soroush Khosravi
* #copyright 2013
*/
class _Iterator
{
private $array;
public function setArray(array $data)
{
$this->array = $data;
}
public function reader()
{
if (is_null($this->array))
return false;
$elem = array_shift($this->array);
if (count ($this->array) > 0)
return $elem;
return false;
}
}
Class child extends _Iterator
{
function execute()
{
$this->setArray(array(1,2,3,4,5,6));
return $this;
}
}
$obj = new child;
$obj = $obj->execute();
while($row = $obj->reader())
echo $row;
?>

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);
}
}
}
}

How to get the parent object in PHP, from OUTSIDE the object itself?

I am using Reflections to adjust various values in objects, and I have an object who's parent I need to adjust.
For example:
class Ford extends Car
{
private $model;
}
class Car
{
private $color;
}
I can easily use Reflection to change the model, but how can I separate the parent from the child, so that I can use Reflection on the parent?
Some psuedo code for what I'm hoping is possible:
$ford = new Ford();
$manipulator = new Manipulator($ford);
$manipulator->set('model','F-150');
$manipulator->setParentValue('color','red');
class Manipulator
{
public function __construct($class) {
$this->class = $class;
$this->reflection = new \ReflectionClass($class);
}
public function set($property,$value) {
$property = $this->reflection->getProperty($property);
$property->setAccessible(true);
$property->setValue($this->class,$value);
}
public function setParentValue() {
$parent = $this->reflection->getParent();
$property = $this->reflection->getProperty($property);
$property->setAccessible(true);
// HOW DO I DO THIS?
$property->setValue($this->class::parent,$value);
}
}
Gist of the question:
In this case, how can I change the $color from outside the object altogether?
Is there something like Ford::parent() or get_parent_object($ford) available?
Note
The objects used above are not the exact scenario, but just used to illustrate the concept. In the real world case, I have a parent/child relationship, and I need to be able to access/change values in each from the outside.
ANSWER
Please check my answer below...I figured it out.
After extensive review, I have found that I can't access the parent of an object AS AN OBJECT outside of the object itself.
However, using Reflections, I was able to solve the example posted above:
<?php
class Car
{
private $color;
public function __construct()
{
$this->color = 'red';
}
public function color()
{
return $this->color;
}
}
class Ford extends Car
{
}
$ford = new Ford();
echo $ford->color(); // OUTPUTS 'red'
$reflection = new ReflectionClass($ford);
$properties = $reflection->getProperties();
foreach($properties as $property) {
echo $property->getName()."\n>";
}
$parent = $reflection->getParentClass();
$color = $parent->getProperty('color');
$color->setAccessible(true);
$color->setValue($ford,'blue');
echo $ford->color(); // OUTPUTS 'blue'
See it in action here: http://codepad.viper-7.com/R45LN0
See get_parent_class(): http://php.net/manual/en/function.get-parent-class.php
function getPrivateProperty(\ReflectionClass $class, $property)
{
if ($class->hasProperty($property)) {
return $class->getProperty($property);
}
if ($parent = $class->getParentClass()) {
return getPrivateProperty($parent, $property);
}
return null;
}
Here is the static version of the function I answered your other question with:
function getProperties($object) {
$properties = array();
try {
$rc = new \ReflectionClass($object);
do {
$rp = array();
/* #var $p \ReflectionProperty */
foreach ($rc->getProperties() as $p) {
$p->setAccessible(true);
$rp[$p->getName()] = $p->getValue($object);
}
$properties = array_merge($rp, $properties);
} while ($rc = $rc->getParentClass());
} catch (\ReflectionException $e) { }
return $properties;
}

Categories