# Containers

Containers have methods for adding, retrieving, deleting and iterating pages. Containers implement
the [SPL](http://php.net/spl) interfaces `RecursiveIterator` and `Countable`, meaning that a
container can be iterated using the SPL `RecursiveIteratorIterator` class.

## Creating containers

`Zend\Navigation\AbstractContainer` can not be instantiated directly. Use
`Zend\Navigation\Navigation` if you want to instantiate a container.

`Zend\Navigation\Navigation` can be constructed entirely empty, or take an array or a
`Zend\Config\Config` object with pages to put in the container. Each page in the given array/config
will eventually be passed to the `addPage()` method of the container class, which means that each
element in the array/config can be an array, a config object or a
`Zend\Navigation\Page\AbstractPage` instance.

### Creating a container using an array

```php
/*
 * Create a container from an array
 *
 * Each element in the array will be passed to
 * Zend\Navigation\Page\AbstractPage::factory() when constructing.
 */
$container = new Zend\Navigation\Navigation(array(
    array(
        'label' => 'Page 1',
        'id' => 'home-link',
        'uri' => '/',
    ),
    array(
        'label' => 'Zend',
        'uri' => 'http://www.zend-project.com/',
        'order' => 100,
    ),
    array(
        'label' => 'Page 2',
        'controller' => 'page2',
        'pages' => array(
            array(
                'label' => 'Page 2.1',
                'action' => 'page2_1',
                'controller' => 'page2',
                'class' => 'special-one',
                'title' => 'This element has a special class',
                'active' => true,
            ),
            array(
                'label' => 'Page 2.2',
                'action' => 'page2_2',
                'controller' => 'page2',
                'class' => 'special-two',
                'title' => 'This element has a special class too',
            ),
        ),
    ),
    array(
        'label' => 'Page 2 with params',
        'action' => 'index',
        'controller' => 'page2',
        // specify a param or two,
        'params' => array(
            'format' => 'json',
            'foo' => 'bar',
        )
    ),
    array(
        'label' => 'Page 2 with params and a route',
        'action' => 'index',
        'controller' => 'page2',
        // specify a route name and a param for the route
        'route' => 'nav-route-example',
        'params' => array(
            'format' => 'json',
        ),
    ),
    array(
        'label' => 'Page 3',
        'action' => 'index',
        'controller' => 'index',
        'module' => 'mymodule',
        'reset_params' => false,
    ),
    array(
        'label' => 'Page 4',
        'uri' => '#',
        'pages' => array(
            array(
                'label' => 'Page 4.1',
                'uri' => '/page4',
                'title' => 'Page 4 using uri',
                'pages' => array(
                    array(
                        'label' => 'Page 4.1.1',
                        'title' => 'Page 4 using mvc params',
                        'action' => 'index',
                        'controller' => 'page4',
                        // let's say this page is active
                        'active' => '1',
                    )
                ),
            ),
        ),
    ),
    array(
        'label' => 'Page 0?',
        'uri' => '/setting/the/order/option',
        // setting order to -1 should make it appear first
        'order' => -1,
    ),
    array(
        'label' => 'Page 5',
        'uri' => '/',
        // this page should not be visible
        'visible' => false,
        'pages' => array(
            array(
                'label' => 'Page 5.1',
                'uri' => '#',
                'pages' => array(
                    array(
                        'label' => 'Page 5.1.1',
                        'uri' => '#',
                        'pages' => array(
                            array(
                                'label' => 'Page 5.1.2',
                                'uri' => '#',
                                // let's say this page is active
                                'active' => true,
                            ),
                        ),
                    ),
                ),
            ),
        ),
    ),
    array(
        'label' => 'ACL page 1 (guest)',
        'uri' => '#acl-guest',
        'resource' => 'nav-guest',
        'pages' => array(
            array(
                'label' => 'ACL page 1.1 (foo)',
                'uri' => '#acl-foo',
                'resource' => 'nav-foo',
            ),
            array(
                'label' => 'ACL page 1.2 (bar)',
                'uri' => '#acl-bar',
                'resource' => 'nav-bar',
            ),
            array(
                'label' => 'ACL page 1.3 (baz)',
                'uri' => '#acl-baz',
                'resource' => 'nav-baz',
            ),
            array(
                'label' => 'ACL page 1.4 (bat)',
                'uri' => '#acl-bat',
                'resource' => 'nav-bat',
            ),
        ),
    ),
    array(
        'label' => 'ACL page 2 (member)',
        'uri' => '#acl-member',
        'resource' => 'nav-member',
    ),
    array(
        'label' => 'ACL page 3 (admin',
        'uri' => '#acl-admin',
        'resource' => 'nav-admin',
        'pages' => array(
            array(
                'label' => 'ACL page 3.1 (nothing)',
                'uri' => '#acl-nada',
            ),
        ),
    ),
    array(
        'label' => 'Zend Framework',
        'route' => 'zf-route',
    ),
));
```

### Creating a container using a config object

```php
/* CONTENTS OF /path/to/navigation.xml:
<?xml version="1.0" encoding="UTF-8"?>
<nav>

  <zend>
      <label>Zend</label>
      <uri>http://www.zend-project.com/</uri>
      <order>100</order>
  </zend>

  <page1>
      <label>Page 1</label>
      <uri>page1</uri>
      <pages>

          <page1_1>
              <label>Page 1.1</label>
              <uri>page1/page1_1</uri>
          </page1_1>

      </pages>
  </page1>

  <page2>
      <label>Page 2</label>
      <uri>page2</uri>
      <pages>

          <page2_1>
              <label>Page 2.1</label>
              <uri>page2/page2_1</uri>
          </page2_1>

          <page2_2>
              <label>Page 2.2</label>
              <uri>page2/page2_2</uri>
              <pages>

                  <page2_2_1>
                      <label>Page 2.2.1</label>
                      <uri>page2/page2_2/page2_2_1</uri>
                  </page2_2_1>

                  <page2_2_2>
                      <label>Page 2.2.2</label>
                      <uri>page2/page2_2/page2_2_2</uri>
                      <active>1</active>
                  </page2_2_2>

              </pages>
          </page2_2>

          <page2_3>
              <label>Page 2.3</label>
              <uri>page2/page2_3</uri>
              <pages>

                  <page2_3_1>
                      <label>Page 2.3.1</label>
                      <uri>page2/page2_3/page2_3_1</uri>
                  </page2_3_1>

                  <page2_3_2>
                      <label>Page 2.3.2</label>
                      <uri>page2/page2_3/page2_3_2</uri>
                      <visible>0</visible>
                      <pages>

                              <page2_3_2_1>
                                  <label>Page 2.3.2.1</label>
                                  <uri>page2/page2_3/page2_3_2/1</uri>
                                  <active>1</active>
                              </page2_3_2_1>

                              <page2_3_2_2>
                                  <label>Page 2.3.2.2</label>
                                  <uri>page2/page2_3/page2_3_2/2</uri>
                                  <active>1</active>

                                  <pages>
                                      <page_2_3_2_2_1>
                                          <label>Ignore</label>
                                          <uri>#</uri>
                                          <active>1</active>
                                      </page_2_3_2_2_1>
                                  </pages>
                              </page2_3_2_2>

                      </pages>
                  </page2_3_2>

                  <page2_3_3>
                      <label>Page 2.3.3</label>
                      <uri>page2/page2_3/page2_3_3</uri>
                      <resource>admin</resource>
                      <pages>

                              <page2_3_3_1>
                                  <label>Page 2.3.3.1</label>
                                  <uri>page2/page2_3/page2_3_3/1</uri>
                                  <active>1</active>
                              </page2_3_3_1>

                              <page2_3_3_2>
                                  <label>Page 2.3.3.2</label>
                                  <uri>page2/page2_3/page2_3_3/2</uri>
                                  <resource>guest</resource>
                                  <active>1</active>
                              </page2_3_3_2>

                      </pages>
                  </page2_3_3>

              </pages>
          </page2_3>

      </pages>
  </page2>

  <page3>
      <label>Page 3</label>
      <uri>page3</uri>
      <pages>

          <page3_1>
              <label>Page 3.1</label>
              <uri>page3/page3_1</uri>
              <resource>guest</resource>
          </page3_1>

          <page3_2>
              <label>Page 3.2</label>
              <uri>page3/page3_2</uri>
              <resource>member</resource>
              <pages>

                  <page3_2_1>
                      <label>Page 3.2.1</label>
                      <uri>page3/page3_2/page3_2_1</uri>
                  </page3_2_1>

                  <page3_2_2>
                      <label>Page 3.2.2</label>
                      <uri>page3/page3_2/page3_2_2</uri>
                      <resource>admin</resource>
                  </page3_2_2>

              </pages>
          </page3_2>

          <page3_3>
              <label>Page 3.3</label>
              <uri>page3/page3_3</uri>
              <resource>special</resource>
              <pages>

                  <page3_3_1>
                      <label>Page 3.3.1</label>
                      <uri>page3/page3_3/page3_3_1</uri>
                      <visible>0</visible>
                  </page3_3_1>

                  <page3_3_2>
                      <label>Page 3.3.2</label>
                      <uri>page3/page3_3/page3_3_2</uri>
                      <resource>admin</resource>
                  </page3_3_2>

              </pages>
          </page3_3>

      </pages>
  </page3>

  <home>
      <label>Home</label>
      <order>-100</order>
      <module>default</module>
      <controller>index</controller>
      <action>index</action>
  </home>

</nav>
 */

$reader = new Zend\Config\Reader\Xml();
$config = $reader->fromFile('/path/to/navigation.xml');
$container = new Zend\Navigation\Navigation($config);
```

## Adding pages

Adding pages to a container can be done with the methods `addPage()`, `addPages()`, or `setPages()`.
See examples below for explanation.

```php
// create container
$container = new Zend\Navigation\Navigation();

// add page by giving a page instance
$container->addPage(
    Zend\Navigation\Page\AbstractPage::factory(
        array(
            'uri' => 'http://www.example.com/',
        )
    )
);

// add page by giving an array
$container->addPage(
    array(
        'uri' => 'http://www.example.com/',
    )
);

// add page by giving a config object
$container->addPage(
    new Zend\Config\Config(
        array(
            'uri' => 'http://www.example.com/',
        )
    )
);

$pages = array(
    array(
        'label'  => 'Save',
        'action' => 'save',
    ),
    array(
        'label' =>  'Delete',
        'action' => 'delete',
    )
);

// add two pages
$container->addPages($pages);

// remove existing pages and add the given pages
$container->setPages($pages);
```

## Removing pages

Removing pages can be done with `removePage()` or `removePages()`. The first method accepts a an
instance of a page, or an integer. The integer corresponds to the `order` a page has. The latter
method will remove all pages in the container.

```php
$container = new Zend\Navigation\Navigation(array(
    array(
        'label'  => 'Page 1',
        'action' => 'page1',
    ),
    array(
        'label'  => 'Page 2',
        'action' => 'page2',
        'order'  => 200,
    ),
    array(
        'label'  => 'Page 3',
        'action' => 'page3',
    )
));

// remove page by implicit page order
$container->removePage(0);      // removes Page 1

// remove page by instance
$page3 = $container->findOneByAction('page3');
$container->removePage($page3); // removes Page 3

// remove page by explicit page order
$container->removePage(200);    // removes Page 2

// remove all pages
$container->removePages();      // removes all pages
```

## Finding pages

Containers have finder methods for retrieving pages. They are `findOneBy($property, $value)`,
`findAllBy($property, $value)`, and `findBy($property, $value, $all = false)`. Those methods will
recursively search the container for pages matching the given `$page->$property == $value`. The
first method, `findOneBy()`, will return a single page matching the property with the given value,
or `NULL` if it cannot be found. The second method will return all pages with a property matching
the given value. The third method will call one of the two former methods depending on the `$all`
flag.

The finder methods can also be used magically by appending the property name to `findBy`,
`findOneBy`, or `findAllBy`, e.g. `findOneByLabel('Home')` to return the first matching page with
label 'Home'. Other combinations are `findByLabel(...)`, `findOneByTitle(...)`,
`findAllByController(...)`, etc. Finder methods also work on custom properties, such as
`findByFoo('bar')`.

```php
$container = new Zend\Navigation\Navigation(array(
    array(
        'label' => 'Page 1',
        'uri'   => 'page-1',
        'foo'   => 'bar',
        'pages' => array(
            array(
                'label' => 'Page 1.1',
                'uri'   => 'page-1.1',
                'foo'   => 'bar',
            ),
            array(
                'label' => 'Page 1.2',
                'uri'   => 'page-1.2',
                'class' => 'my-class',
            ),
            array(
                'type'   => 'uri',
                'label'  => 'Page 1.3',
                'uri'    => 'page-1.3',
                'action' => 'about',
            )
        )
    ),
    array(
        'label'      => 'Page 2',
        'id'         => 'page_2_and_3',
        'class'      => 'my-class',
        'module'     => 'page2',
        'controller' => 'index',
        'action'     => 'page1',
    ),
    array(
        'label'      => 'Page 3',
        'id'         => 'page_2_and_3',
        'module'     => 'page3',
        'controller' => 'index',
    ),
));

// The 'id' is not required to be unique, but be aware that
// having two pages with the same id will render the same id attribute
// in menus and breadcrumbs.
$found = $container->findBy('id',
                            'page_2_and_3');      // returns Page 2
$found = $container->findOneBy('id',
                               'page_2_and_3');   // returns Page 2
$found = $container->findBy('id',
                            'page_2_and_3',
                            true);                // returns Page 2 and Page 3
$found = $container->findById('page_2_and_3');    // returns Page 2
$found = $container->findOneById('page_2_and_3'); // returns Page 2
$found = $container->findAllById('page_2_and_3'); // returns Page 2 and Page 3

// Find all matching CSS class my-class
$found = $container->findAllBy('class',
                               'my-class');       // returns Page 1.2 and Page 2
$found = $container->findAllByClass('my-class');  // returns Page 1.2 and Page 2

// Find first matching CSS class my-class
$found = $container->findOneByClass('my-class');  // returns Page 1.2

// Find all matching CSS class non-existent
$found = $container->findAllByClass('non-existent'); // returns array()

// Find first matching CSS class non-existent
$found = $container->findOneByClass('non-existent'); // returns null

// Find all pages with custom property 'foo' = 'bar'
$found = $container->findAllBy('foo', 'bar'); // returns Page 1 and Page 1.1

// To achieve the same magically, 'foo' must be in lowercase.
// This is because 'foo' is a custom property, and thus the
// property name is not normalized to 'Foo'
$found = $container->findAllByfoo('bar');

// Find all with controller = 'index'
$found = $container->findAllByController('index'); // returns Page 2 and Page 3
```

## Iterating containers

`Zend\Navigation\AbstractContainer` implements `RecursiveIterator`, and can be iterated using any
`Iterator` class. To iterate a container recursively, use the `RecursiveIteratorIterator` class.

```php
/*
 * Create a container from an array
 */
$container = new Zend\Navigation\Navigation(array(
    array(
        'label' => 'Page 1',
        'uri'   => '#',
    ),
    array(
        'label' => 'Page 2',
        'uri'   => '#',
        'pages' => array(
            array(
                'label' => 'Page 2.1',
                'uri'   => '#',
            ),
            array(
                'label' => 'Page 2.2',
                'uri'   => '#',
            )
        )
    )
    array(
        'label' => 'Page 3',
        'uri'   => '#',
    ),
));

// Iterate flat using regular foreach:
// Output: Page 1, Page 2, Page 3
foreach ($container as $page) {
    echo $page->label;
}

// Iterate recursively using RecursiveIteratorIterator
$it = new RecursiveIteratorIterator(
        $container, RecursiveIteratorIterator::SELF_FIRST);

// Output: Page 1, Page 2, Page 2.1, Page 2.2, Page 3
foreach ($it as $page) {
    echo $page->label;
}
```

## Other operations

The method `hasPage(Zend\Navigation\Page\AbstractPage $page)` checks if the container has the given
page. The method `hasPages()` checks if there are any pages in the container, and is equivalent to
`count($container) > 0`.

The `toArray()` method converts the container and the pages in it to an array. This can be useful
for serializing and debugging.

### Converting a container to an array

```php
$container = new Zend\Navigation\Navigation(array(
    array(
        'label' => 'Page 1',
        'uri'   => '#',
    ),
    array(
        'label' => 'Page 2',
        'uri'   => '#',
        'pages' => array(
            array(
                'label' => 'Page 2.1',
                'uri'   => '#',
            ),
            array(
                'label' => 'Page 2.2',
               'uri'   => '#',
            ),
        ),
    ),
));

var_dump($container->toArray());

/* Output:
array(2) {
  [0]=> array(15) {
    ["label"]=> string(6) "Page 1"
    ["id"]=> NULL
    ["class"]=> NULL
    ["title"]=> NULL
    ["target"]=> NULL
    ["rel"]=> array(0) {
    }
    ["rev"]=> array(0) {
    }
    ["order"]=> NULL
    ["resource"]=> NULL
    ["privilege"]=> NULL
    ["active"]=> bool(false)
    ["visible"]=> bool(true)
    ["type"]=> string(23) "Zend\Navigation\Page\Uri"
    ["pages"]=> array(0) {
    }
    ["uri"]=> string(1) "#"
  }
  [1]=> array(15) {
    ["label"]=> string(6) "Page 2"
    ["id"]=> NULL
    ["class"]=> NULL
    ["title"]=> NULL
    ["target"]=> NULL
    ["rel"]=> array(0) {
    }
    ["rev"]=> array(0) {
    }
    ["order"]=> NULL
    ["resource"]=> NULL
    ["privilege"]=> NULL
    ["active"]=> bool(false)
    ["visible"]=> bool(true)
    ["type"]=> string(23) "Zend\Navigation\Page\Uri"
    ["pages"]=> array(2) {
      [0]=> array(15) {
        ["label"]=> string(8) "Page 2.1"
        ["id"]=> NULL
        ["class"]=> NULL
        ["title"]=> NULL
        ["target"]=> NULL
        ["rel"]=> array(0) {
        }
        ["rev"]=> array(0) {
        }
        ["order"]=> NULL
        ["resource"]=> NULL
        ["privilege"]=> NULL
        ["active"]=> bool(false)
        ["visible"]=> bool(true)
        ["type"]=> string(23) "Zend\Navigation\Page\Uri"
        ["pages"]=> array(0) {
        }
        ["uri"]=> string(1) "#"
      }
      [1]=>
      array(15) {
        ["label"]=> string(8) "Page 2.2"
        ["id"]=> NULL
        ["class"]=> NULL
        ["title"]=> NULL
        ["target"]=> NULL
        ["rel"]=> array(0) {
        }
        ["rev"]=> array(0) {
        }
        ["order"]=> NULL
        ["resource"]=> NULL
        ["privilege"]=> NULL
        ["active"]=> bool(false)
        ["visible"]=> bool(true)
        ["type"]=> string(23) "Zend\Navigation\Page\Uri"
        ["pages"]=> array(0) {
        }
        ["uri"]=> string(1) "#"
      }
    }
    ["uri"]=> string(1) "#"
  }
}
*/
```
