How do i get a b c d e from the below xml markup...
<api:field name="test">
<api:text>a</api:text>
<api:text>b</api:text>
<api:text>c</api:text>
<api:text>d</api:text>
<api:text>e</api:text>
</api:field>
I am trying to use this for loop:
foreach ($xml->xpath('//api:field[#name="test"]') as $item)
{
foreach ($item->children() as $child) {
...
}
}
but i do not know how to access the child nodes which contain no attributes.
I need to get the child values for the parent node "test" specifically so please don't give me $xml->xpath("//api:text"); as an answer. The problem with this answer is that we might see under other parent nodes and i only want to get the child values from a specific parent node. In this case name="test".
There are a couple of ways you can achieve this. Either just extend your xpath expression to return the child nodes themselves:
foreach ($sxml->xpath('/api:field[#name="test"]/api:text') as $item) {
echo (string) $item, PHP_EOL;
}
Alternatively if you did want to use two loops (or it just better fits your use case), you just need to pass the namespace prefix into the children() method:
foreach ($sxml->xpath('/api:field[#name="test"]') as $item) {
foreach ($item->children('api', true) as $child) {
echo (string) $child, PHP_EOL;
}
}
(If the namespace prefix is likely to change, you can use the registerXPathNamespace method to register a persistent one, according to the defined namespace URL.)
Both of these approaches will yield the same result:
a
b
c
d
e
See https://eval.in/954569 for a full example
Related
I currently have a code snippet where for each category, it would find the sub-categories:
$categories = array_map(
function($child)
{
$child['children'] =
$this->getChildren(
$child['id'],
!empty($this->request->get['language_id']) ?
$this->request->get['language_id'] : 1
);
return $child;
}, $categories);
getChildren() would recursively get the children of one category:
private function getChildren($parent_id, $language_id) {
$this->load->model('official/category');
$children =
$this->model_official_category->getCategoriesByParentId(
$parent_id,
$language_id
);
// For each child, find the children.
foreach ($children as $child) {
$child['children'] = $this->getChildren(
$child['id'],
$language_id
);
}
return $children;
}
Currently, using my lambda function within the array_map(), only the sub-category's children would be retrieve, so if each sub-category has its own sub-sub-category, it would not be saved into its children.
How could I show the sub-sub-category given the sub-category we have?
What I wanted to do with my code was to take a parent, get its children, and then treat each of those children as a parent and get its children recursively, however my JSON output does not reflect that. Only the parent has children - the children has no children (despite my database having them).
The problem is that your recursion foreach loop assigns the children that it retrieves to a copy of the child data, rather than the child data itself.
To resolve this you could use foreach loop that references the child data, like so:
foreach ($children as &$child) {
However, due to a number of reasons related to how foreach is implemented internally in PHP (more info if you're interested), it would be considerably more memory efficient to use a for loop instead, as this will avoid quite a few copy-on-write copies of the child data:
for ($i = 0; isset($children[$i]); $i++) {
$children[$i]['children'] = $this->getChildren(
$children[$i]['id'],
$language_id
);
}
This is one place where using objects instead of arrays to represent the child data might be a good idea, because objects are always passed by reference (kind of) and the behaviour would be more like what you were expecting initially.
I have an Entity Category, which is linked to itself in order to form a tree (a category can have a category as a parent and a category can have a bunch of categories as children). These are marked as private inside the Entity and not exposed to the serializer.
When I do $category->getChildren()->toArray(), I get an array of the children, but when I do $this->getDoctrine()->getRepsitory('PmbLicensing:Category')->findByParent($category)->toArray(), I get an error that toArray() is not defined. I need to use the latter because the top level categories have their parent set to null, so I cannot use the former method. How do I convert the collection of categories obtained in the latter method to an array?
Also, when trying to trouble shoot, I often would like to print out variables, but when I do something like print_r($categories);, print_r((array)$categories); or var_dump($categories); the call just runs for about two minutes and then returns null. I assume it is because of the relational mapping that goes into an infinate loop, but how do I stop this from happening?
Edit: I want to convert the object (or collection of objects) to an array, because I want to build a recursive function where the children categories of the supplied category can be retrieved up to n-depth. If the supplied category can be null, in order to retrieve from the main level of categories (with parent set to null). Here is my function:
private function getRecursiveChildren(Category $category = null, $depth, $iteration)
{
$children = $this->getDoctrine()->getRepository('PmbLicensingBundle:Category')->findByParent($category);
// \Doctrine\Common\Util\Debug::dump($children); die();
if ($depth > $iteration)
foreach ($children as $child) {
$child['children'] = $this->getRecursiveChildren($child, $depth, $iteration+1);
}
return $children;
}
On the line that has $child['children'], is says that I cannot use an object as an array.
If you need the results as an array you can return them from the database as arrays.
In your CategoryRepository class:
public function findArrayByParent($categoryId)
{
// it's a good adivce from #i.am.michiel to pass only the `id` here.
// You don't need the whole category object.
$query = $this->getEntityManager()->createQuery('...')
->setParameters(array('categoryId' => $categroyId));
return $query->getArrayResult();
}
They are never converted to objects after being retrieved from the DB so you also save time and memory.
Actually your
$children = $this->getDoctrine()->getRepository('PmbLicensingBundle:Category')
->findByParent($category);`
already returns an array so you don't have to (and can't) use ->toArray().
When you loop through your categories with
foreach ($children as $child)
$child["..."] = ...
You are treating an obect $child like an array with ["..."]. That's what your error message is about.
If you can, you should probably use doctrine and let it fill the related child and parent categories. See the Doctrine Documentation on this. Then you have automatically all your Children and can access them like $category->getChildren() (This one will return an ArrayCollection). This will save you a lot of work.
Your call simply returns no categories. Btw, I think you should pass the id of the category, not the entity.
$this->getDoctrine()
->getRepository('PmbLicensing:Category')
->findByParent($category->getId());
And why use the toArray() function? ArrayCollection already are arrays with a few additionnal methods? You should be able to use an ArrayCollection whenever you used an array.
I would like to use the RecursiveIteratorIterator class to build a menu.
Actually I'm using Zend_Navigation container to build main menu and breadcrumbs, however it doesn't suit my other needs, so I'm using container with iterator:
$navigation = $this->view->navigation()->getContainer();
$iterator = new RecursiveIteratorIterator( $navigation,
RecursiveIteratorIterator::SELF_FIRST);
Then I like to know, two things - current level and if item has more children.
First one can I achieve by getDepth() method and for second one I tried to use
callHasChildren(), but this one is always returning TRUE, even if item has not more children.
foreach ($iterator as $page) {
echo $iterator->getDepth() . $iterator->callHasChildren()) . '<br />';
}
Any idea how to use this method?
I've got this notice:
ArrayIterator::next(): Array was modified outside object and internal position is no longer valid in /var/www...
which is produced by this code, at the begining of the foreach loop. Together with the notice, the foreach loop starts iterating all over again. In other words, the internal position is reset whenever this thing happens. But acording to php manual, ArrayObject is using ArrayIterator by default.
And manual says this about ArrayIterator
This iterator allows to unset and modify values and keys while iterating over Arrays and Objects.
Am I missing something here? I found some bugreports about ArratIterator, but not this kind. Is it a bug or is it my bad?
version: PHP Version 5.3.10-1ubuntu3.4
<?php
//file 1:
// no namespace
abstract class holder extends \ArrayObject{
// abstract function init();
public function __construct($init){
parent::__construct($init, 1);
}
}?>
<?php
//file 2:
namespace troops;
class holder extends \holder{
public function __construct(){
parent::__construct($this->init());
}
private function init(){
return array( /*... some data from db ...*/ );
}
public function saveData(){
foreach($this as $k => $v){
$this->save($v);
if($v->number_of_items==0) {
unset($k);
// $this->offsetUnset($k); // tryed both
}
}
}
}
?>
ArrayObject implements IteratorAggregate which means it has a method called getIterator() which returns an iterator.
php's foreach loop will automatically retrieve an iterator by calling the getIterator() method for you to retrieve an iterator to iterate over. This is convenient, but you need to get a reference to this iterator in order to call the offsetUnset() method on the iterator itself. The key thing here is you must call the iterators offsetUnset() method, not the ArrayObjects offsetUnset() method.
$ao = new ArrayObject();
$ao[] = 9;
$iter = $ao->getIterator();
foreach ($iter as $k => $v)
$iter->offsetUnset($k); // no error
The underlying ArrayObject that the iterator is iterating over will be mutated, so if you have more than one active iterator at the same time over the same Arrayobject, you'll still encounter the same error.
The rationale for this is likely so that the iterators can be memory efficient and not have to copy the underlying ArrayObject, because a copy is the only simple solution to dealing with the complexity of deciding what the current iterator position should be when things are added to or deleted from the underlying array.
In case you remove the last record from array than the next foreach loop can fail (internaly calling $this->next() )
Forexample when i have array of one item and I will unset it, than next foreach fails.
So it helped me to test the validy and break in this case the next loop.
deleteOffset = true;
foreach ($iterator as $key => $value)
{
if ($deleteOffset && $iterator->offsetExists($key) )
{
$iterator->offsetUnset($key);
}
//if remove last record than the foreach ( $this->next() ) fails so
//we have to break in this case the next ->next call
//after any ->offsetUnset calls
if (!$iterator->valid()) break;
}
This took me so long! I used the recipe on this page to (unsuccessfully) clear-out the contents of an ArrayObject's storage, then I found this little diddy..
$this->exchangeArray(array());
magic!
Is it possible to remove the parent node from a Tree using CakePHP Tree Behavior?. Let's say for example I have a node like this:
<Node A>
- child 1 node A
- child 2 node A
- child 3 node A
- <Node B> (which is also a child 4 of Node A)
- child 1 node B
- child 2 node B
Is it possible to get all the chidren of Node A (using the chidren() or any other function of the Tree behavior in cakePHP), but exclude a node which has children from the result (in our case Node B)?
Any idea please?
Thanks in advance
You can but you'll need to get your hands a bit dirty because I don't think the behavior allows anything like this.
The key is that all nodes that do not have children should have a left and right value that are in sequence. You'll need to whip up a query like this:
SELECT * FROM items WHERE left > (parent's left) AND right < (parent's right) AND right = left + 1 AND parent_id = (parent's ID)
That way we're asking that all returned values are children of our parent and that their left and right values are in sequence, which they won't be if a node has children.
Looking at specifications there is no specific method for this, so you must build your own function for that using children() and childCount(). Here's the code template (I don't use Cake PHP):
$children = <call TreeBehavior children() method with $id = id of Node A and $direct = true>;
$children_without_children = array();
foreach ($children as $child) {
if (<call TreeBehavior childCount() method with $id = $child->id and $direct = true> === 0) {
$children_without_children[] = $child;
}
}
Then $children_without_children should contain what you want.
you can use this code:
$this->Node->removeFromTree($id, true);
here is code from my cakephp 2.x project:
public function delete($id = null) {
$this->ProductCategory->id = $id;
if (!$this->ProductCategory->exists()) {
throw new NotFoundException(__('Invalid product category'));
}
$this->request->allowMethod('post', 'delete');
if ($this->ProductCategory->removeFromTree($id, TRUE)) {
$this->Session->setFlash(__('The product category has been deleted.'));
} else {
$this->Session->setFlash(__('The product category could not be deleted. Please, try again.'));
}
return $this->redirect(array('action' => 'index'));
}
Using this method (e.i. removeFromTree ()) will either delete or move a node but retain its sub-tree, which will be reparented one level higher. It offers more control than delete, which for a model using the tree behavior will remove the specified node and all of its children.