Skip to content
kanduvisla edited this page Apr 20, 2012 · 2 revisions

As part of the Modules concept, a index class is created. This article explains what it does and how to use it.

Currently the development of the Modules-feature has only yet covered pages, so this article will use that as an example.

Lookup Tables

The first concept you need to understand is that pages are not stored in databases, but in seperate XML-files along your XSL-templates in your workspace/pages-folder. Therefore, these pages can be copied and reused in other installations, or easily edited or duplicated outside the Symphony UI.

Indexing

Now, that's great and all, but how is it actually used in the real world? Well, this is where things really get awesome: Indexing. Let me explain this step for step:

The XML-Files

So, you're building a site, and you've got some pages here and there. Each page has it's configuration in it's own XML-file:

<page>
    <title handle="news">News</title>
    <unique_hash>b747b6e5ffa3db6bff396b6759fddcd7</unique_hash>
    <parent>3b42206b9fe82adaa1066f0756c27213</parent>
    <path>media</path>
    <params>page</params>
    <datasources>
        <datasource>news-archive</datasource>
    </datasources>
    <events />
    <types>
        <type>hidden</type>
    </types>
    <sortorder>1</sortorder>
</page>

Smash it all down

Here's what the indexer does (and it also explains why the class is a Singleton): It grabs all the XML-files in your folder and smashes them into one internally used SimpleXMLElement. Now that's interesting! Because it means that you can go and do all kind of crazy stuff with the Lookup Class! For your picture, this is what the internal XML looks like for the system:

<pages>
    <page>
        ...
    </page>
    <page>
        ...
    </page>
    <page>
        ...
    </page>
</pages>

This is done only one time during execution, and that's when the class is instantiated (which is only once, since it's a Singleton).

Functions

xpath()

Now let's just start with the main reason why this is done with SimpleXMLElement: XPath! That's right! Our long beloved friend! Since the whole configuration of all our pages is merged into one SimpleXMLElement, we can go and do stuff like:

$_pages = Index::init(Index::INDEX_PAGES)->xpath("page[types/type='utilities']"); 

Now, this example will return all pages where types/type equals 'utilities'. But there's more! It also takes an optional second boolean parameter to let the function return a single value (for when you are expecting a single result):

$_pages = Index::init(Index::INDEX_PAGES)->xpath("page[types/type='index']", true); 

getMax()

Another handy function is getMax(). This gets the maximum value of a specific element:

$_pages = Index::init(Index::INDEX_PAGES)->getMax("sortorder"); // reflects to page/sortorder.

This function works only for integers.

hasDuplicateHashes()

This function returns false of no duplicate hashes are found (which is a good thing), or returns the hash in question which is used on multiple items (which is a bad thing, they're not called unique hashes for nothing!)

if($_hash = Index::init(Index::INDEX_PAGES)->hasDuplicateHashes())
{
    throw new Exception(__('Duplicate unique hash found in Pages: '.$_hash));
}

fetch()

Now, saving the best for the last: the fetch()-function. Having an xpath-expression is neat ofcourse, but sometimes there are also other tasks like filtering and ordering. The fetch()-function handles all that and returns an indexed, ordered array of the requested elements. It's almost like a database call, but for XML:

$_pages = Index::init(Index::INDEX_PAGES)->fetch($xpath = null, $orderBy = null, $orderDirection = 'asc', $sortNumeric = true);

// Example, get all datasource handles of a page with a specific hash:
$_datasource_handles = Index::init(Index::INDEX_PAGES)->fetch(
    'page[unique_hash=\'abc123\']/datasources/datasource', 'sortorder', 'asc', true);

reIndex()

The code still needs some optimization and to fit in the current flow of working. That's also the main reason why the following function is present:

Index::init(Index::INDEX_PAGES)->reIndex();

What this function does is re-read the directory with XML-files and regenerate the index. This is needed after one of the XML-files is changed (For example, after saving a page). The reIndex()-function takes an optional second parameter: $overwrite, which automatically replaces the cached index with the local index (the physical XML-files) and uses that instead. This is necessary for the SectionManager: otherwise fields could accidentally be deleted. Pages are automatically synced with the local XML files, since no database action is involved with that, so actions are easily reversible. But if a section XML is corrupt the last thing you want it to do is delete all your data. More on this in the 'Managers'-part of this page.

editValue()

This function allows you to edit a value in the index. Please note that editing a value doesn't save it to the corresponding XML-file of your page or section. So the editing of a value is only temporary until you write it to an XML file (the Page- Field and SectionManager have functions for this).

Index::init(Index::INDEX_PAGES)->editValue('/xpath/to/node', 'my-new-value');

editAttribute()

This function allows you to edit an attribute in the index. Please note that editing an attribute doesn't save it to the corresponding XML-file of your page or section. So the editing of an attribute is only temporary until you write it to an XML file (the Page- Field and SectionManager have functions for this).

Index::init(Index::INDEX_PAGES)->editAttribute('/xpath/to/node', 'name-of-attribute', 'my-new-value');

removeNode()

This function removes a node from the index. Please note that removing an node doesn't save the corresponding XML-file of your page or section. So the removal of a node is only temporary until you write the corresponding XML file (the Page- Field and SectionManager have functions for this).

Index::init(Index::INDEX_PAGES)->removeNode('/xpath/to/node-that-is-going-to-be-removed');

getFormattedXML()

This function outputs a formatted (human-readable) XML string according to an XPath-query. This function is mostly used by the Managers in their save-functions to store pretty indented XML, but you could also use it for debugging purposes.

$xmlString = Index::init(Index::INDEX_PAGES)->getFormattedXML('/xpath/to/node');

getIndex()

This function returns the cached index, if you would like to work with it. The Index is a SimpleXMLElement and is a collections of all the <page>- and <section>-nodes of your seperate files combined into one XML Element. This function is mostly used by other classes.

$indexXML = Index::init(Index::INDEX_PAGES)->getIndex();

getLocalIndex()

This function is the same as the getIndex()-function, only it returns the local index, and not the cached index. The local index is built from the seperate XML-files. You most likely won't ever need this function. This function is used in the diff-screen to find the differences between sections.

$indexXML = Index::init(Index::INDEX_PAGES)->getIndex();

isDirty()

This function returns if the index is 'dirty'. This means that the local index differs from the cached index. You most likely won't ever need this function. This function is used in the SectionManager to check if the sections needs to be checked in the diff-screen.

mergeXML()

This function is a helper function for the SimpleXMLElement, since the SimpleXMLElement-class lacks the support to add a SimpleXMLElement as a child. So if you would like to add a SimpleXMLElement as a child to another SimpleXMLElement, use this helper function.

Index::init(Index::INDEX_PAGES)->mergeXML(&$parentXMLElement, $childXMLElement);

Managers

The PageManager, SectionManager and FieldManager are very tightly coupled with the index. They use the index for XPath lookups for all their functions. There is however one important thing to notice: There are 3 managers which use the index: PageManager, SectionManager and FieldManager. However, there are only 2 indexes built and used: pages and sections. This is because the SectionManager and FieldManager both use the same index. If you think of it, it makes sense: fields are stored inside the sections' XML-files, so it would only be the most logical approach to use this index also for field lookups.

Another point to note, is just as with lookups, each manager also has a shortcut to get to their index:

PageManager::index();
SectionManager::index();
FieldManager::index();    // returns the section index

In Conclusion

Well, that's all you need to know about the Indexing-process. As said, the only example we have right now is the Pages, so if you want to study this concept more deeper, look at the work that is done in:

  • symphony/lib/toolkit/class.pagemanager.php
  • symphony/lib/toolkit/class.sectionmanager.php
  • symphony/lib/toolkit/class.fieldmanager.php
  • symphony/lib/content/content.blueprintspages.php
  • symphony/lib/content/content.blueprintssections.php