diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..37a3f75 --- /dev/null +++ b/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PHPCR.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PHPCR.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/PHPCR" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PHPCR" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/README.md b/README.md index 8e8afa9..268d34f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ Documentation for the PHP Content Repository PHPCR -This repository contains the source files. Please visit [phpcr.github.com](http://phpcr.github.com) for a user friendly access to the documentation on PHPCR. \ No newline at end of file +This repository contains the source files. The documentation is hosted on +readthedocs.org: http://phpcr.readthedocs.org/en/latest/ + +You can also visit the official PHPCR website: phpcr.github.io diff --git a/_theme/phpcr/layout.html b/_theme/phpcr/layout.html new file mode 100644 index 0000000..5995b6c --- /dev/null +++ b/_theme/phpcr/layout.html @@ -0,0 +1,17 @@ +{% extends "pyramid/layout.html" %} +{% block extrahead %} + +{% endblock %} + +{% block header %} +{%- if logo %} + +{%- endif %} +{% endblock %} diff --git a/_theme/phpcr/static/logo_small.png b/_theme/phpcr/static/logo_small.png new file mode 100644 index 0000000..156f4c7 Binary files /dev/null and b/_theme/phpcr/static/logo_small.png differ diff --git a/_theme/phpcr/static/phpcr.css b/_theme/phpcr/static/phpcr.css new file mode 100644 index 0000000..09e2913 --- /dev/null +++ b/_theme/phpcr/static/phpcr.css @@ -0,0 +1,22 @@ +div.header {. + margin: 0; + background: #191818; + background-image: none; + color: #333; + text-align: left; + padding: 10px 10px 10px 10px; + border-bottom: 1px solid #eee; + } + +div.logo { + text-align: left; + padding-top: 0px; +} + +div.header a { + color: white; +} + +div.header .logo { + float: right; +} diff --git a/_theme/phpcr/theme.conf b/_theme/phpcr/theme.conf new file mode 100644 index 0000000..dc0c1e2 --- /dev/null +++ b/_theme/phpcr/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = pyramid +stylesheet = pyramid.css +pygments_style = sphinx diff --git a/book/conclusion.rst b/book/conclusion.rst new file mode 100644 index 0000000..b4d1886 --- /dev/null +++ b/book/conclusion.rst @@ -0,0 +1,12 @@ +Conclusion and further reading +============================== + +We hope this guide helps to get you started. If you miss anything, have suggestions or questions, please contact us on jackalope-dev@googlegroups.com or #jackalope on irc.freenode.net + +Further reading +--------------- + +Browse through the [API documentation](http://phpcr.github.com/doc/html/index.html) to see what each of the core elements mentioned in the introduction can do. + +To fully understand the concepts behind the content repository API, we suggest reading [the Java content repository specification](http://www.day.com/specs/jcr/2.0/index.html) and +then the [simplifications we did for PHP](https://github.com/phpcr/phpcr/blob/master/doc/JCR_TO_PHPCR.txt). diff --git a/book/getting_started.rst b/book/getting_started.rst new file mode 100644 index 0000000..9014053 --- /dev/null +++ b/book/getting_started.rst @@ -0,0 +1,108 @@ +Getting Stated +============== + +This aims to provide a rounded general reference to PHPCR. You will mostly see +code examples. It should work with any PHPCR implementation. We propose using +`Jackalope Jackrabbit `_ to +get started as it supports all features described here. + +Installing Jackalope +-------------------- + +Just follow the README of the +`jackalope-jackrabbit `_ +repository. + +Browser to see what is in the repository +---------------------------------------- + +There are currently two options for browsing and modifying the contents of the +PHPCR repository. + +- `PHPCR Shell `_: Aims to provide a full + command line shell interface to PHPCR content repositories. A pre-compiled + PHAR archive is available on the github homepage. + +- `Marmelab PHPCR Browser `_: + A user-friendly web based PHPCR browser. + +The shell is currently more feature complete, but the PHPCR Browser is more +user friendly. We suggest you try both. + +In a nutshell +------------- + +The shortest self-contained example should output a line with 'value': + +.. code-block:: php + + 'http://localhost:8080/server'); + + // end of implementation specific configuration + + // get a new PHPCR repository instance from the factory class defined above + $factory = new $factoryclass(); + $repository = $factory->getRepository($parameters); + + // create the credentials object to authenticate with the repository + $credentials = new \PHPCR\SimpleCredentials('admin','admin'); + + // login to the repository and retrieve the session + $session = $repository->login($credentials, 'default'); + + // retrieve the root node of the repository ("/") + $root = $session->getRootNode(); + + // add a new node + $node = $root->addNode('test', 'nt:unstructured'); + + // set a property on the newly created property + $node->setProperty('prop', 'value'); + + // save the session, i.e. persist the data + $session->save(); + + // retrieve the newly created node + $node = $session->getNode('/test'); + echo $node->getPropertyValue('prop'); // outputs "value" + + +Still with us? Good, lets get in a bit deeper... + +Get some data into the repository +--------------------------------- + +We will discuss the import feature in more detail later, but to have some +data, we just import something here. Create an XML file called `test.xml``: + +.. code-block:: xml + + + + + + + + + + + + + +Now import this into the repository: + +.. code-block:: php + + importXML('/', 'test.xml', \PHPCR\ImportUUIDBehaviorInterface::IMPORT_UUID_CREATE_NEW); + $session->save(); + +You may also use the PHPCR Shell to import data: + +.. code-block:: bash + + phpcrsh -pmyprofile -c "session:import-xml test.xml" diff --git a/book/import_export.rst b/book/import_export.rst new file mode 100644 index 0000000..f59fcf6 --- /dev/null +++ b/book/import_export.rst @@ -0,0 +1,67 @@ +Import and export data +====================== + +As promised, here are some more details on importing and exporting data. There +are two formats: + +* The *document view* translates the data into a XML document with node names + as xml elements and properties as attributes and thus very readable. Type + information is lost, and illegal XML characters are encoded. +* The *system view* is a more strict XML document defining the exact structure + of the repository with all type information. However, it is more verbose. + +As an analogy, think about an SQL dump file with SQL statements and the dump of +an SQL table into a ``csv`` file. You can restore the data from both, but the SQL +dump knows every detail about your field types and so on while the CSV just +knows the data. + +When exporting, you explictly call a method corresponding to the desired +format: + +.. code-block:: php + + exportDocumentView( + '/data/sibling', + $file, + true, // skip binary properties to not have large files in the dump + false // recursivly output the child nodes as well + ); + + fclose($file); + + $file = fopen('/tmp/system.xml', 'w+'); + // export the tree at /foo/bar into a system view xml file + $session->exportSystemView( + '/data/sibling', + $file, + false, // do not skip binary properties + false + ); + + fclose($file); + +Importing detects the format automatically. If the document is a valid JCR +system view, it is interpreted according to that format, otherwise if it is a +valid XML document it is imported as document: + +.. code-block:: php + + getRootNode()->addNode('imported_data', 'nt:unstructured'); + $session->importXML( + '/imported_data', // attach the imported data at this node + $filename, + ImportUUIDBehaviorInterface::IMPORT_UUID_CREATE_NEW + ); + +When importing nodes with a uuid, a couple of different behaviors can be used: + +* ``IMPORT_UUID_CREATE_NEW``: Create new UUIDs for nodes that are imported, so you never get collisions. +* ``IMPORT_UUID_COLLISION_THROW``: Throw an exception if a node with the same UUID already exists. +* ``IMPORT_UUID_COLLISION_REMOVE_EXISTING``: Remove an existing node if an imported node has the same UUID. +* ``IMPORT_UUID_COLLISION_REPLACE_EXISTING``: Replace existing node with the imported node. This can lead to the imported data being put in various places. diff --git a/book/index.rst b/book/index.rst new file mode 100644 index 0000000..474ff9b --- /dev/null +++ b/book/index.rst @@ -0,0 +1,23 @@ +The Book +======== + +.. toctree:: + + getting_started + introduction + reading_data_and_traversal + references + shareable_nodes + same_name_siblings + query + writing + orderable_child_nodes + locking + versioning + transactions + import_export + observation + node_types + search + performance + conclusion diff --git a/book/introduction.rst b/book/introduction.rst new file mode 100644 index 0000000..96d39da --- /dev/null +++ b/book/introduction.rst @@ -0,0 +1,37 @@ +Introduction +============ + +In the following chapters, we will show how to use the API. But first, you +need a very brief overview of the core elements of PHPCR. After reading this +guide, you should browse through the API documentation to get an idea what +operations you can do on each of those elements. See the conclusions for links +if you want to have more background. + +It is important to know about the following concepts: + +* **Node, Property**: An object model for data structured similar to XML. A + node is like the xml element, the property like the xml attribute. + Properties are acquired from their nodes or directly from the session by + their path. Nodes are acquired from parent nodes or from the session by + their path. Both are Item, sharing the methods of that base interface. Names + can be namespaced as in xml, and additionally may contain whitespaces or + other not xml-legal characters. +* **Session**: The authenticated connection to one workspace in the + repository. Repository and workspace are immutable inside the Session. The + session is the main interface to interact with the actual data. Sessions are + acquired from a repository +* **Repository**: Linking to one storage location with possibly many + workspaces. Repositories are created with the help of the repository + factory. +* **RepositoryFactory**: Create repository instances for your implementation + with implementation specific parameters. +* **Workspace**: Provides general operations on the workspace of the Session it is acquired from. + +Note on Implementation PHPCR Support +------------------------------------ + +PHPCR is a modular standard and has a built-in way to discover the +capabilities of your implementation. Therefore implementations do not +have to support all sections of the specification. + +TODO: Add a section about capability testing to show how to write portable code. diff --git a/book/locking.rst b/book/locking.rst new file mode 100644 index 0000000..7d239bc --- /dev/null +++ b/book/locking.rst @@ -0,0 +1,41 @@ +Locking +======= + +In PHPCR, you can lock nodes to prevent concurrency issues. There are two basic types of locks: + +* Session based locks are only kept until your session ends and released automatically on logout. +* If a lock is not session based, it is identified by a lock token and stays in place until it times out + +Note that Jackalope currently only implements session based locks: + +.. code-block:: php + + getNode('/data/sibling'); + //the node has to be lockable + $node->addMixin('mix:lockable'); + $session->save(); //node needs to be clean before locking + + // get the lock manager + $workspace = $session->getWorkspace(); + $lockManager = $workspace->getLockManager(); + var_dump($lockManager->isLocked('/data/sibling')); // should be false + $lockManager->lock('/data/sibling', true, true); // lock child nodes as well, release when session closed + // now only this session may change the node //sibling and its descendants + var_dump($lockManager->isLocked('/data/sibling')); // should be true + var_dump($lockManager->isLocked('/data/sibling/child1')); // should be true because we locked deep + + // getting the lock from LockManager is not yet implemented with jackalope-jackrabbit + $lock = $lockManager->getLock('/data/sibling'); + var_dump($lock->isLockOwningSession()); // true, this is our lock, not somebody else's + var_dump($lock->getSecondsRemaining()); // PHP_INT_MAX because this lock has no timeout + var_dump($lock->isLive()); // true + + $node = $lock->getNode(); // this gets us the node for /sibling + $node === $lockManager->getLock('/data/sibling')->getNode(); // getnode always returns the lock owning node + + // now unlock the node again + $lockManager->unlock('/data/sibling'); // we could also let $session->logout() unlock when using session based lock + var_dump($lockManager->isLocked('/data/sibling')); // false + var_dump($lock->isLive()); // false diff --git a/book/node_types.rst b/book/node_types.rst new file mode 100644 index 0000000..855eb98 --- /dev/null +++ b/book/node_types.rst @@ -0,0 +1,15 @@ +Node Types +========== + +PHPCR supports node types. Node types define what properties and children a node can or must have. The JCR specification explains exhaustivly what node types exist and what they are required to have or not: `JCR 2.0: 3.7.11 Standard Application Node Types `_ + +In a nutshell: + +* ``nt:unstructured`` does not define any required properties but allows any property or child. +* ``nt:file`` and ``nt:folder`` are built-in node types useful to map a file structure in the repository. (With jackalope-jackrabbit, files and folders are exposed over webdav) +* If you **do not** want to enforce a schema on your node, use + ``nt:unstructured``. +* If you need to store additional properties or children on existing node types like files, note that while a node can have only one primary type, every node can have any mixin types. Define a mixin type declaring your additional properties, register it with PHPCR and addMixin it to the nodes that need it. + +You can define your own node types if you want the equivalent of a strictly defined database structure. See `JCR 2.0: 3.7 Node Types `_ and `JCR 2.0: 19 Node Type Management `_ / `PHPCR Node Type Namespace `_. + diff --git a/book/observation.rst b/book/observation.rst new file mode 100644 index 0000000..2e7003a --- /dev/null +++ b/book/observation.rst @@ -0,0 +1,39 @@ +Observation +=========== + +Observation enables an application to receive notifications of persistent changes to a workspace. +JCR defines a general event model and specific APIs for asynchronous and journaled observation. +A repository may support asynchronous observation, journaled observation or both. + +Note that Jackrabbit supports the full observation API but Jackalope currently only implements event journal reading. + +Write operations in Jackalope will generate journal entries as expected. + +.. code-block:: php + + getWorkspace(); + $observationManager = $workspace->getObservationManager(); + + // Get the unfiltered event journal and go through its content + $journal = $observationManager->getEventJournal(); + $journal->skipTo(strtotime('-1 day')); // Skip all the events prior to yesterday + foreach ($journal as $event) { + // Do something with $event (it's a Jackalope\Observation\Event instance) + echo $event->getType() . ' - ' . $event->getPath(); + } + + // Filtering and using the journal as an iterator + // You can filter the event journal on several criteria, here we keep events for node and properties added + $journal = $observationManager->getEventJournal(EventInterface::NODE_ADDED | EventInterface::PROPERTY_ADDED); + + while ($journal->valid()) { + $event = $journal->current(); + // Do something with $event + $journal->next(); + } + + diff --git a/book/orderable_child_nodes.rst b/book/orderable_child_nodes.rst new file mode 100644 index 0000000..84f1643 --- /dev/null +++ b/book/orderable_child_nodes.rst @@ -0,0 +1,25 @@ +Orderable child nodes +===================== + +While moving is about changing the parent of a node, ordering is used to set the +position inside the child list. Preserving and altering order is an optional +feature of PHPCR. + +The only method needed is Node::orderBefore + +.. code-block:: php + + getNode('/data/node'); + + $node->addNode('first'); + $node->addNode('second'); // new nodes are added to the end of the list + // order is: first, second + + // ordering is done on the parent node. the first argument is the name of + // the child node to be reordered, the second the name of the node to moved + // node is placed before + $node->orderBefore('second', 'first'); + // now the order is: second, first + diff --git a/book/performance.rst b/book/performance.rst new file mode 100644 index 0000000..a52715b --- /dev/null +++ b/book/performance.rst @@ -0,0 +1,49 @@ +Performance considerations +========================== + +While PHPCR can perform reasonably well, you should be careful. You are +working with an object model mapping interlinked data. Implementations are +supposed to lazy load data only when necessary. But you should take care to +only request what you actually need. + +The implementations will also use some sort of storage backend (Jackrabbit, +(no)SQL database, ...). There might be a huge performance impact in +configuring that storage backend optimally. Look into your implementation +documentation if there are recommendations how to optimize storage. + +One thing *not* to worry about is requesting the same node with +Session::getNode or Node::getNode/s several times. You always get the same +object instance back without overhead. + +Only request what you need +-------------------------- + +emember that you can filter nodes on Node::getNodes if you only need a list of +specific nodes or all nodes in some namespace. + +The values of binary properties can potentially have a huge size and should +only loaded when really needed. If you just need the size, you can get the +property instance and do a $property->getSize() instead of +filesize($node->getPropertyValue). Any decent implementation will not preload +the binary stream when you access the property object. + +When getting the properties from a node, you can use +Node::getPropertiesValues(filter, false). This allows the implementation to +avoid instantiating Property objects for the property values (and saves you +coding). The second boolean parameter tells wheter to dereference reference +properties. If you do not need the referenced objects, pass false and you will +get the UUID or path strings instead of node objects.(If you need one of them, +you can still get it with Session::getNodeByIdentifier. But then the +implementation will certainly not be able to optimize if you get several +referenced nodes.) + +But request in one call as much as possible of what you need +------------------------------------------------------------ + +If you need to get several nodes where you know the paths, use +``Session::getNodes`` with an array of those nodes to get all of them in one +batch, saving round trip time to the storage backend. + +You can also use ``Node::getNodes`` with a list of nodes rather than repeatedly calling +``Node::getNode``. + diff --git a/book/query.rst b/book/query.rst new file mode 100644 index 0000000..bc0a541 --- /dev/null +++ b/book/query.rst @@ -0,0 +1,121 @@ +Query: Search the database +========================== + +.. code-block:: php + + getWorkspace(); + $queryManager = $workspace->getQueryManager(); + + $sql = "SELECT * FROM [nt:unstructured] + WHERE [nt:unstructured].[title] = 'Test' + ORDER BY [nt:unstructured].content"; + $query = $queryManager->createQuery($sql, 'JCR-SQL2'); + $query->setLimit(10); // limit number of results to be returned + $query->setOffset(1); // set an offset to skip first n results + $queryResult = $query->execute(); + + foreach ($queryResult->getNodes() as $path => $node) { + echo $node->getName(); + } + +Without building nodes +---------------------- + +There can be a little performance boost if you do not need to fetch the nodes +but just want to access one value of each node: + +.. code-block:: php + + $row) { + echo $path . ' scored ' . $row->getScore(); + + $row->getValue('a-value-you-know-exists'); + } + +Large search results can be dangerous for performance. See below for some +performance tips. + + +Using Query Object Model (QOM) for building complex queries +----------------------------------------------------------- + +PHPCR provides two languages to build complex queries. SQL2 and Query Object Model (QOM). While SQL2 expresses a query in a syntax similar to SQL, QOM expresses the query as a tree of PHPCR objects. + +In this section we will cover QOM. See the `JCR docs `_ for an exposition of both languages. + +You can access the QueryObjectModelFactory from the session: + +.. code-block:: php + + getWorkspace()->getQueryManager()->getQOMFactory(); + +The QOM factory has a method to build a QOM query given four parameters, and `provides methods `_ to build these four parameters: + +.. code-block:: php + + createQuery(SourceInterface source, ConstraintInterface constraint, array orderings, array columns); + +- ``source`` is made out of one or more selectors. Each selector selects a subset of nodes. Queries with more than one selector have joins. A query with two selectors will have a join, a query with three selectors will have two joins, and so on. + +``constraint`` filters the set of node-tuples to be retrieved. Constraint may be combined in a tree of constraints to perform a more complex filtering. Examples of constraints are: + + - Absolute or relative paths: nodes descendant of a path, nodes children of a path, nodes reachable by a path. + - Name of the node. + - Value of a property. + - Length of a property. + - Existence of a property. + - Full text search. + +- ``orderings`` determine the order in which the filtered node-tuples will appear in the query results. The relative order of two node-tuples is determined by evaluating the specified orderings, in list order, until encountering an ordering for which one node-tuple precedes the other. + +- ``columns`` are the columns to be included in the tabular view of query results. If no columns are specified, the columns available in the tabular view are implementation determined. In Jackalope include, for each selector, a column for each single-valued non-residual property of the selector's node type. + +The simplest case is to select all ``[nt:unstructured]`` nodes: + +.. code-block:: php + + selector('a', '[nt:unstructured]'); + $query = $qomFactory->createQuery($source, null, array(), array()); + $queryResult = $query->execute(); + + +The Query Builder: a fluent interface for QOM +--------------------------------------------- + +Sometimes you may prefer to build a query in several steps. For that reason, the phpcr-utils library provides a fluent wrapper for QOM: the QueryBuilder. It works with any PHPCR implementation. + +An example of query built with QueryBuilder: + +.. code-block:: php + + from($qomFactory->selector('a', 'nt:unstructured')) + //some composed constraint + ->andWhere($qf->comparison($qf->propertyValue('a', 'title'), + QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO, + $qf->literal('Test'))) + //orderings (descending by default) + ->orderBy($qf->propertyValue('a', 'content')) + //set an offset + ->setFirstResult(0) + //and the maximum number of node-tuples to retrieve + ->setMaxResults(25); + $result = $qb->execute(); + + foreach ($result->getNodes() as $node) { + echo $node->getName() . " has content: " . $node->getPropertyValue('content') . "\n"; + } + //node has content: This is some test content + //sibling has content: This is another test content diff --git a/book/reading_data_and_traversal.rst b/book/reading_data_and_traversal.rst new file mode 100644 index 0000000..408520d --- /dev/null +++ b/book/reading_data_and_traversal.rst @@ -0,0 +1,112 @@ +Reading data and traversal +========================== + +You can wrap any code into try catch blocks. See the `API doc +`_ for what exceptions to expect +on which calls. With PHPCR being ported from Java, there is a lot of +Exceptions defined. But as this is PHP, you don't have to catch them. As long +as your content is as the code expects, it won't matter. + +.. code-block:: php + + getNode('/data/node'); + echo $node->getName(); // will be 'node' + echo $node->getPath(); // will be '/data/node' + +Reading properties +------------------ + +.. code-block:: php + + getNode('/data/node'); + + // get the php value of a property (type automatically determined from stored information) + echo $node->getPropertyValue('title'); + + // get the Property object to operate on + $property = $node->getProperty('content'); + echo 'Size of '.$property->getPath().' is '.$property->getLength(); + + // read a property that could be very long + $property = $node->getProperty('content'); + + // if it is binary convert into string + $data = $property->getString(); + echo $data; + + // get binary stream. could be more performant with binary property + $stream = $property->getBinary(); + fpassthru($stream); + fclose($stream); + + // the above in short if you just want to dump a file that is in a binary propery: + // fpassthru($node->getPropertyValue('binary-prop')); + +Note: the backend stores the property types. When getting property values, they are returned +with that type, unless you use one of the explicit PropertyInterface::getXX methods. +For that case, type conversion is attempted and an exception thrown if this is not possible. + +See the API doc for a list of all supported types. + +.. code-block:: php + + getPropertiesValues() as $name => $value) { + echo "$name: $value\n"; + } + // get the properties of this node with a name starting with 't' + foreach ($node->getPropertiesValues("t*") as $name => $value) { + echo "$name: $value\n"; + } + +Traversing the hierarchy +------------------------ + +.. code-block:: php + + getNode('/data/node'); + + // getting a node by path relative to the node + $othernode = $node->getNode('../sibling'); // /sibling + + // get all child nodes. the $node is Iterable, the iterator being all children + $node = $session->getNode('/data/sibling'); + foreach ($node as $name => $child) { + if ($child->hasProperties()) { + echo "$name has properties\n"; + } else { + echo "$name does not have properties\n"; + } + } + + // get child nodes with the name starting with 'c' + foreach ($node->getNodes('c*') as $name => $child) { + echo "$name\n"; + } + + // get child nodes with the name starting with 'o' or ending with '2' or named 'yetanother' + foreach ($node->getNodes(array('o*', '*2', 'yetanother')) as $name => $child) { + echo "$name\n"; + } + + // get the parent node + $parent = $node->getParent(); // / + + // build a breadcrumb of the node ancestry + $node = $session->getNode('/data/sibling/yetanother'); + $i = 0; + $breadcrumb = array(); + do { + $i++; + $parent = $node->getAncestor($i); + $breadcrumb[$parent->getPath()] = $parent->getName(); + } while ($parent != $node); + var_dump($breadcrumb); + diff --git a/book/references.rst b/book/references.rst new file mode 100644 index 0000000..576fa15 --- /dev/null +++ b/book/references.rst @@ -0,0 +1,79 @@ +Node and property references +============================ + +Nodes can be referenced by unique id (if they are mix:referenceable) or by +path. getValue returns the referenced node instance. Properties can only be +referenced by path because they can not have a unique id. + +The test document we imported above does not contain the type information we +need to show this example. Lets create a special one and load it into the +repository with ``Session::importXML``: + +.. code-block:: xml + + + + nt:unstructured + + + + + nt:unstructured + + + mix:referenceable + + + 13543fc6-1abf-4708-bfcc-e49511754b40 + + + Some value + + + + + + nt:unstructured + + + 13543fc6-1abf-4708-bfcc-e49511754b40 + + + ../target/someproperty + + + + + + +Now import the contents of that file instead of the other one. With this data, you can do this: + +.. code-block:: php + + getNode('/idExample/source'); + // will return you a node if the property is of type REFERENCE or WEAKREFERENCE + $othernode = $node->getPropertyValue('reference'); + + // force a node + $property = $node->getProperty('reference'); + // will additionally try to resolve a PATH or NAME property and even work + // if the property is a STRING that happens to be a valid UUID or to + // denote an existing path + $othernode = $property->getNode(); + + // get a referenced property + $property = $node->getProperty('path'); + $otherproperty = $property->getProperty(); + echo $otherproperty->getName(); // someproperty + echo $otherproperty->getValue(); // Some value + diff --git a/book/same_name_siblings.rst b/book/same_name_siblings.rst new file mode 100644 index 0000000..9569754 --- /dev/null +++ b/book/same_name_siblings.rst @@ -0,0 +1,6 @@ +Same name siblings +================== + +Optional feature, not fully tested in Jackalope. + +Nodes with the same parent can have the same name. They are distinguished by an index, as in xpath. diff --git a/book/search.rst b/book/search.rst new file mode 100644 index 0000000..0e4ffd6 --- /dev/null +++ b/book/search.rst @@ -0,0 +1,14 @@ +Search +====== + +TODO: intelligent filtering criteria to do as little in-memory operations to apply criteria. + +If you do not need the node objects but just some value, query for that value +and use the result Row to avoid instantiating Node objects alltogether. If you +need the Node objects, help PHPCR to optimize by using QueryResult::getNodes +and iterating over the nodes instead of getting the rows, iterating over them +and calling getNode on each row. (Actually, if you first do the getNodes(), +you can then iterate over the rows and get the individual nodes and still use +the special row methods as the implementation should have prefetched data on +the getNodes.) + diff --git a/book/shareable_nodes.rst b/book/shareable_nodes.rst new file mode 100644 index 0000000..c192c84 --- /dev/null +++ b/book/shareable_nodes.rst @@ -0,0 +1,6 @@ +Shareable nodes +=============== + +Optional feature, not yet implemented in Jackalope. + +Graph structure instead of a tree, nodes can have more than one parent. diff --git a/book/transactions.rst b/book/transactions.rst new file mode 100644 index 0000000..d9a2915 --- /dev/null +++ b/book/transactions.rst @@ -0,0 +1,33 @@ +Transactions +============ + +The PHPCR API in itself uses some sort of 'transaction' model by only +persisting changes on session save. If you need transactions over more than one +save operation or including workspace operations that are dispatched immediatly, +you can use transactions. + +Note that Jackalope does not support the full transactions: + +.. code-block:: php + + getWorkspace(); + $transactionManager = $workspace->getTransactionManager(); + // start a transaction + $transactionManager->begin(); + $session->removeNode('/data/sibling'); + $session->getRootNode()->addNode('insideTransaction'); + $session->save(); // wrote to the backend but not yet visible to other sessions + $workspace->move('/data/node', '/new'); // will only move the new node if session has been saved. still not visible to other sessions + $transactionManager->commit(); // now everything become persistent and visible to others + + // you can abort a transaction + try { + ... + } catch(\Exception $e) { + if ($transactionManager->inTransaction()) { + $transactionManager->rollback(); + } + ... + } diff --git a/book/versioning.rst b/book/versioning.rst new file mode 100644 index 0000000..6963c49 --- /dev/null +++ b/book/versioning.rst @@ -0,0 +1,57 @@ +Versioning +========== + +Versioning is used to track changes in nodes with the possibility to get back +to older versions. + +A node with the mixin type `mix:versionable` or `mix:simpleVersionable` can be +versioned. Versioned nodes have a version history, containing the root version +and all versions created. Each version contains the meta data (previous +versions, next versions and creation date) and provides a snapshot of the node +at that point, called "frozen node". + +.. code-block:: php + + getNode('/data/node'); + + $node->setProperty('foo', 'fafa'); + // mark the node as versionable + $node->addMixin('mix:versionable'); + $session->save(); + + // version operations are done through the VersionManager + $versionManager = $session->getWorkspace()->getVersionManager(); + + // put the versionable node into edit mode + $versionManager->checkout($node->getPath()); + $node->setProperty('foo', 'bar'); // need a change to see something + $session->save(); // you can only create versions of saved nodes + // create a new version of the node with our changes + $version = $versionManager->checkin($node->getPath()); + // Version extends the Node interface. The version is the node with additional functionality + + // walk back the versions + $oldversion = $version->getLinearPredecessor(); + // the version objects are just the meta data. call getFrozenNode on them + // to get a snapshot of the data when the version was created + echo $version->getName() . ': ' . $version->getFrozenNode()->getPropertyValue('foo') . "\n"; // 1.1: bar + echo $oldversion->getName() . ': ' . $oldversion->getFrozenNode()->getPropertyValue('foo'); // 1.0: fafa + + // get the full version history + $history = $versionManager->getVersionHistory($node->getPath()); + foreach ($history->getAllFrozenNodes() as $node) { + if ($node->hasProperty('foo')) { + // the root version does not have the property + echo $node->getPropertyValue('foo') . "\n"; + } + } + + // restore an old version + $node->setProperty('foo', 'different'); + $versionManager->checkout($node->getPath()); + $session->save(); // restoring is only possible if the session is clean + $current = $versionManager->getBaseVersion($node->getPath()); + $versionManager->restore(true, $current); + echo $node->getPropertyValue('foo'); // fafa diff --git a/book/writing.rst b/book/writing.rst new file mode 100644 index 0000000..9ef993f --- /dev/null +++ b/book/writing.rst @@ -0,0 +1,70 @@ +Writing data +============ + +Creating and updating nodes +--------------------------- + +With PHPCR, you never use 'new'. The node works as a factory to create new +nodes and properties. This has the nice side effect that you can not add a +node where there is no parent. + +Everything you do on the Session, Node and Property objects is only visible +locally in this session until you save the session. + +.. code-block:: php + + getNode('/data/node'); + + // add a new node as child of $node + $newnode = $node->addNode('new node', 'nt:unstructured'); // until we have shown node types, just use nt:unstructured as type + + // set a property on the new node + $newproperty = $newnode->setProperty('my property', 'my value'); + + // persist the changes permanently. now they also become visible in other sessions + $session->save(); + + + // have a reference + $targetnode = $session->getNode('/data/sibling/yetanother'); + + // make sure the target node is referenceable. + $targetnode->addMixin('mix:referenceable'); + // depending on the implementation, you might need to save the session at + // this point to have the identifier generated + + // add a reference property to the node. because the property value is a + // Node, PHPCR will automatically detect that you want a reference + $node->setProperty('my reference', $targetnode); + + $session->save(); + +Moving and deleting nodes +------------------------- + +.. code-block:: php + + move('/data/sibling/yetanother', '/data/sibling/child1/yetanother'); + + // for this session, everything that was at /sibling/yetanother is now under /sibling/child1/yetanother + // i.e. /sibling/child1/yetanother/child + // once the session is saved, the move is persisted and visible in other sessions + // alternatively, you can immediatly move the node in the persistent storage + + // rename node child2 to child2_new + $workspace = $session->getWorkspace(); + $workspace->move('/data/sibling/child2', '/data/sibling/child2_new'); + + // copy a node and its children (only available on workspace, not inside session) + $workspace->copy('/data/sibling/yetanother', '/data/sibling/child1/yetanother'); + + // delete a node + $session->removeItem('/data/sibling/child1/yetanother'); + diff --git a/conf.py b/conf.py new file mode 100644 index 0000000..3c386f6 --- /dev/null +++ b/conf.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- +# +# PHPCR documentation build configuration file, created by +# sphinx-quickstart on Mon Jun 9 07:54:17 2014. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'PHPCR' +copyright = u'2014, PHPCR contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '2.1' +# The full version, including alpha/beta/rc tags. +release = '2.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'phpcr' +html_theme_path = ['_theme'] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = "static/logo_small.png" + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'PHPCRdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'PHPCR.tex', u'PHPCR Documentation', + u'David Buchmann, Daniel Leech', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'phpcr', u'PHPCR Documentation', + [u'David Buchmann, Daniel Leech'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'PHPCR', u'PHPCR Documentation', + u'David Buchmann, Daniel Leech', 'PHPCR', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/index.rst b/index.rst new file mode 100644 index 0000000..fb5ae43 --- /dev/null +++ b/index.rst @@ -0,0 +1,24 @@ +.. PHPCR documentation master file, created by + sphinx-quickstart on Mon Jun 9 09:03:59 2014. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to PHPCR's documentation! +================================= + +Contents: + +.. toctree:: + :maxdepth: 2 + + book/index + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..1f73085 --- /dev/null +++ b/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PHPCR.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PHPCR.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/tutorial/Tutorial.md b/tutorial/Tutorial.md deleted file mode 100644 index accecfb..0000000 --- a/tutorial/Tutorial.md +++ /dev/null @@ -1,775 +0,0 @@ -# PHPCR Tutorial - -This is an introduction into the PHP content repository. You will mostly see code examples. It should work with any PHPCR implementation. We propose using [Jackalope Jackrabbit](https://github.com/jackalope/jackalope-jackrabbit) to get started as it supports all features described here. - - -## Installing Jackalope - -Just follow the README of the [jackalope-jackrabbit](https://github.com/jackalope/jackalope-jackrabbit/blob/master/README.md) repository. - - -## Browser to see what is in the repository - -We recommend installing the [PhpcrBrowser](https://github.com/symfony-cmf/phpcrbrowser) so that you can see what data you currently have in your repository. TODO: update this to the new browser once we have it working. - - -# In a nutshell - -The shortest self-contained example should output a line with 'value': - - 'http://localhost:8080/server'); - // end of implementation specific configuration - - $factory = new $factoryclass(); - $repository = $factory->getRepository($parameters); - $credentials = new \PHPCR\SimpleCredentials('admin','admin'); - $session = $repository->login($credentials, 'default'); - $root = $session->getRootNode(); - $node = $root->addNode('test', 'nt:unstructured'); - $node->setProperty('prop', 'value'); - $session->save(); - - // data is stored now. in a follow-up request you can do - $node = $session->getNode('/test'); - echo $node->getPropertyValue('prop'); // outputs "value" - -Still with us? Good, lets get in a bit deeper... - -## Introduction - -In the following chapters, we will show how to use the API. But first, you need a very brief overview of the core elements of PHPCR. After reading this tutorial, you should browse through the API documentation to get an idea what operations you can do on each of those elements. See the conclusions for links if you want to have more background. - -* Node, Property: An object model for data structured similar to XML. A node is like the xml element, the property like the xml attribute. Properties are acquired from their nodes or directly from the session by their path. Nodes are acquired from parent nodes or from the session by their path. Both are Item, sharing the methods of that base interface. Names can be namespaced as in xml, and additionally may contain whitespaces or other not xml-legal characters. -* Session: The authenticated connection to one workspace in the repository. Repository and workspace are immutable inside the Session. The session is the main interface to interact with the actual data. Sessions are acquired from a repository -* Repository: Linking to one storage location with possibly many workspaces. Repositories are created with the help of the repository factory. -* RepositoryFactory: Create repository instances for your implementation with implementation specific parameters. -* Workspace: Provides general operations on the workspace of the Session it is acquired from. - - -Not every implementation has to support all chapters of the specification. PHPCR is a modular standard and has a built-in way to discover the capabilities of your implementation. -TODO: Add a section about capability testing to show how to write portable code. - - -### Bootstrapping - -You will need to make sure your php classes are available. Usually this means activating an autoloader. For a standalone project, just use the file generated by composer at vendor/.composer/autoload.php -PHPCR and Jackalope follow the PSR-0 standard. If you want your own autoloading, use a PSR-0 compatible autoloader and configure it to find the code folder. - -Once you have autoloading set up, bootstrap jackalope-jackrabbit like this: - - 'http://localhost:8080/server'); - - // end of implementation specific configuration - // from here on, the whole code does not need to be changed when using different implementations - - $factory = new $factoryclass(); - $repository = $factory->getRepository($parameters); - if (null === $repository) { - var_dump($parameters); - die('There where missing parameters, the factory could not create a repository'); - } - - // the login parameters would typically live in a configuration file - - $workspacename = 'default'; - $user = 'admin'; - $pass = 'admin'; - - // create credentials and log in to get a session - $credentials = new \PHPCR\SimpleCredentials($user, $pass); - try { - $session = $repository->login($credentials, $workspacename); - } catch(\PHPCR\LoginException $e) { - die('Invalid credentials: '.$e->getMessage()); - } catch(\PHPCR\NoSuchWorkspaceException $e) { - die("No workspace $workspacename: ".$e->getMessage()); - } - - // if we get here, we have a session object that can be used to read and write the repository - - -### Get some data into the repository - -We will discuss the import feature in more detail later, but to have some data, we just import something here. Create an XML file test.xml like this: - - - - - - - - - - - - - -Now import this into the repository: - - $session->importXML('/', 'test.xml', \PHPCR\ImportUUIDBehaviorInterface::IMPORT_UUID_CREATE_NEW); - $session->save(); - - -### Reading data and traversal - -You can wrap any code into try catch blocks. See the [API doc](http://phpcr.github.com/doc/html/index.html) for what exceptions to expect on which calls. With PHPCR being ported from Java, there is a lot of Exceptions defined. -But as this is PHP, you don't have to catch them. As long as your content is as the code expects, it won't matter. - - $node = $session->getNode('/data/node'); - echo $node->getName(); // will be 'node' - echo $node->getPath(); // will be '/data/node' - - -#### Reading properties - - //get the node from the session - $node = $session->getNode('/data/node'); - - // get the php value of a property (type automatically determined from stored information) - echo $node->getPropertyValue('title'); - - // get the Property object to operate on - $property = $node->getProperty('content'); - echo 'Size of '.$property->getPath().' is '.$property->getLength(); - - // read a property that could be very long - $property = $node->getProperty('content'); - - // if it is binary convert into string - $data = $property->getString(); - echo $data; - - // get binary stream. could be more performant with binary property - $stream = $property->getBinary(); - fpassthru($stream); - fclose($stream); - - // the above in short if you just want to dump a file that is in a binary propery: - // fpassthru($node->getPropertyValue('binary-prop')); - -Note: the backend stores the property types. When getting property values, they are returned -with that type, unless you use one of the explicit PropertyInterface::getXX methods. -For that case, type conversion is attempted and an exception thrown if this is not possible. - -See the API doc for a list of all supported types. - - // get all properties of this node - foreach ($node->getPropertiesValues() as $name => $value) { - echo "$name: $value\n"; - } - // get the properties of this node with a name starting with 't' - foreach ($node->getPropertiesValues("t*") as $name => $value) { - echo "$name: $value\n"; - } - - -#### Traversing the hierarchy - - //get the node from the session - $node = $session->getNode('/data/node'); - - // getting a node by path relative to the node - $othernode = $node->getNode('../sibling'); // /sibling - - // get all child nodes. the $node is Iterable, the iterator being all children - $node = $session->getNode('/data/sibling'); - foreach ($node as $name => $child) { - if ($child->hasProperties()) { - echo "$name has properties\n"; - } else { - echo "$name does not have properties\n"; - } - } - - // get child nodes with the name starting with 'c' - foreach ($node->getNodes('c*') as $name => $child) { - echo "$name\n"; - } - - // get child nodes with the name starting with 'o' or ending with '2' or named 'yetanother' - foreach ($node->getNodes(array('o*', '*2', 'yetanother')) as $name => $child) { - echo "$name\n"; - } - - // get the parent node - $parent = $node->getParent(); // / - - // build a breadcrumb of the node ancestry - $node = $session->getNode('/data/sibling/yetanother'); - $i = 0; - $breadcrumb = array(); - do { - $i++; - $parent = $node->getAncestor($i); - $breadcrumb[$parent->getPath()] = $parent->getName(); - } while ($parent != $node); - var_dump($breadcrumb); - - -#### Node and property references - -Nodes can be referenced by unique id (if they are mix:referenceable) or by path. getValue returns the referenced node instance. -Properties can only be referenced by path because they can not have a unique id. - -The test document we imported above does not contain the type information we -need to show this example. Lets create a special one and load it into the repository with Session::importXML: - - - - - nt:unstructured - - - - - nt:unstructured - - - mix:referenceable - - - 13543fc6-1abf-4708-bfcc-e49511754b40 - - - Some value - - - - - - nt:unstructured - - - 13543fc6-1abf-4708-bfcc-e49511754b40 - - - ../target/someproperty - - - - - - -Now import the contents of that file instead of the other one. With this data, you can do this: - - - $node = $session->getNode('/idExample/source'); - // will return you a node if the property is of type REFERENCE or WEAKREFERENCE - $othernode = $node->getPropertyValue('reference'); - - // force a node - $property = $node->getProperty('reference'); - // will additionally try to resolve a PATH or NAME property and even work - // if the property is a STRING that happens to be a valid UUID or to - // denote an existing path - $othernode = $property->getNode(); - - // get a referenced property - $property = $node->getProperty('path'); - $otherproperty = $property->getProperty(); - echo $otherproperty->getName(); // someproperty - echo $otherproperty->getValue(); // Some value - - -#### Shareable nodes - -Optional feature, not yet implemented in Jackalope. - -Graph structure instead of a tree, nodes can have more than one parent. - - -#### Same name siblings - -Optional feature, not fully tested in Jackalope. - -Nodes with the same parent can have the same name. They are distinguished by an index, as in xpath. - - -### Query: Search the database - - // get the query interface from the workspace - $workspace = $session->getWorkspace(); - $queryManager = $workspace->getQueryManager(); - - $sql = "SELECT * FROM [nt:unstructured] - WHERE [nt:unstructured].[title] = 'Test' - ORDER BY [nt:unstructured].content"; - $query = $queryManager->createQuery($sql, 'JCR-SQL2'); - $query->setLimit(10); // limit number of results to be returned - $query->setOffset(1); // set an offset to skip first n results - $queryResult = $query->execute(); - - foreach ($queryResult->getNodes() as $path => $node) { - echo $node->getName(); - } - - -#### Without building nodes - -There can be a little performance boost if you do not need to fetch the nodes -but just want to access one value of each node. - - foreach ($queryResult as $path => $row) { - echo $path . ' scored ' . $row->getScore(); - - $row->getValue('a-value-you-know-exists'); - } - -Large search results can be dangerous for performance. See below for some -performance tips. - - -#### Using Query Object Model (QOM) for building complex queries - -PHPCR provides two languages to build complex queries. SQL2 and Query Object Model (QOM). While SQL2 expresses a query in a syntax similar to SQL, QOM expresses the query as a tree of PHPCR objects. - -In this section we will cover QOM. See the [JCR docs](http://phpcr.github.com/doc/html/index.html) for an exposition of both languages. - -You can access the QueryObjectModelFactory from the session: - - $qomFactory = $mySession->getWorkspace()->getQueryManager()->getQOMFactory(); - -The QOM factory has a method to build a QOM query given four parameters, and [provides methods](http://phpcr.github.com/doc/html/phpcr/query/qom/queryobjectmodelfactoryinterface.html) to build these four parameters: - - $queryObjectModel = $QOMFactory->createQuery(SourceInterface source, ConstraintInterface constraint, array orderings, array columns); - -`source` is made out of one or more selectors. Each selector selects a subset of nodes. Queries with more than one selector have joins. A query with two selectors will have a join, a query with three selectors will have two joins, and so on. - -`constraint` filters the set of node-tuples to be retrieved. Constraint may be combined in a tree of constraints to perform a more complex filtering. Examples of constraints are: - -* Absolute or relative paths: nodes descendant of a path, nodes children of a path, nodes reachable by a path. -* Name of the node. -* Value of a property. -* Length of a property. -* Existence of a property. -* Full text search. - -`orderings` determine the order in which the filtered node-tuples will appear in the query results. The relative order of two node-tuples is determined by evaluating the specified orderings, in list order, until encountering an ordering for which one node-tuple precedes the other. - -`columns` are the columns to be included in the tabular view of query results. If no columns are specified, the columns available in the tabular view are implementation determined. In Jackalope include, for each selector, a column for each single-valued non-residual property of the selector's node type. - -The simplest case is to select all `[nt:unstructured]` nodes: - - $source = $qomFactory->selector('a', '[nt:unstructured]'); - $query = $qomFactory->createQuery($source, null, array(), array()); - $queryResult = $query->execute(); - - -#### The Query Builder: a fluent interface for QOM - -Sometimes you may prefer to build a query in several steps. For that reason, the phpcr-utils library provides a fluent wrapper for QOM: the QueryBuilder. It works with any PHPCR implementation. - -An example of query built with QueryBuilder: - - - use PHPCR\Query\QOM\QueryObjectModelConstantsInterface; - use PHPCR\Util\QOM\QueryBuilder; - - $qf = $qomFactory; - $qb = new QueryBuilder($qomFactory); - //add the source - $qb->from($qomFactory->selector('a', 'nt:unstructured')) - //some composed constraint - ->andWhere($qf->comparison($qf->propertyValue('a', 'title'), - QueryObjectModelConstantsInterface::JCR_OPERATOR_EQUAL_TO, - $qf->literal('Test'))) - //orderings (descending by default) - ->orderBy($qf->propertyValue('a', 'content')) - //set an offset - ->setFirstResult(0) - //and the maximum number of node-tuples to retrieve - ->setMaxResults(25); - $result = $qb->execute(); - - foreach ($result->getNodes() as $node) { - echo $node->getName() . " has content: " . $node->getPropertyValue('content') . "\n"; - } - //node has content: This is some test content - //sibling has content: This is another test content - - -### Writing data - -With PHPCR, you never use 'new'. The node works as a factory to create new nodes and properties. This has the nice side effect that you can not add a node where there is no parent. - -Everything you do on the Session, Node and Property objects is only visible locally in this session until you save the session. - - //get the node from the session - $node = $session->getNode('/data/node'); - - // add a new node as child of $node - $newnode = $node->addNode('new node', 'nt:unstructured'); // until we have shown node types, just use nt:unstructured as type - - // set a property on the new node - $newproperty = $newnode->setProperty('my property', 'my value'); - - // persist the changes permanently. now they also become visible in other sessions - $session->save(); - - - // have a reference - $targetnode = $session->getNode('/data/sibling/yetanother'); - - // make sure the target node is referenceable. - $targetnode->addMixin('mix:referenceable'); - // depending on the implementation, you might need to save the session at - // this point to have the identifier generated - - // add a reference property to the node. because the property value is a - // Node, PHPCR will automatically detect that you want a reference - $node->setProperty('my reference', $targetnode); - - $session->save(); - - -#### Moving and deleting nodes - - // move the node yetanother and all its children from its parent /sibling to - // the new parent /sibling/child1 - // the target parent must already exist, it is not automatically created - // as the move includes the target name, it can also be used to rename nodes - $session->move('/data/sibling/yetanother', '/data/sibling/child1/yetanother'); - - // for this session, everything that was at /sibling/yetanother is now under /sibling/child1/yetanother - // i.e. /sibling/child1/yetanother/child - // once the session is saved, the move is persisted and visible in other sessions - // alternatively, you can immediatly move the node in the persistent storage - - // rename node child2 to child2_new - $workspace = $session->getWorkspace(); - $workspace->move('/data/sibling/child2', '/data/sibling/child2_new'); - - // copy a node and its children (only available on workspace, not inside session) - $workspace->copy('/data/sibling/yetanother', '/data/sibling/child1/yetanother'); - - // delete a node - $session->removeItem('/data/sibling/child1/yetanother'); - - -#### Orderable child nodes - -While moving is about changing the parent of a node, ordering is used to set the -position inside the child list. Preserving and altering order is an optional -feature of PHPCR. - -The only method needed is Node::orderBefore - - //get the node from the session - $node = $session->getNode('/data/node'); - - $node->addNode('first'); - $node->addNode('second'); // new nodes are added to the end of the list - // order is: first, second - - // ordering is done on the parent node. the first argument is the name of - // the child node to be reordered, the second the name of the node to moved - // node is placed before - $node->orderBefore('second', 'first'); - // now the order is: second, first - - -### Versioning - -Versioning is used to track changes in nodes with the possibility to get back to older versions. - -A node with the mixin type mix:versionable or mix:simpleVersionable can be -versioned. Versioned nodes have a version history, containing the root version -and all versions created. Each version contains the meta data (previous -versions, next versions and creation date) and provides a snapshot of the node -at that point, called "frozen node". - - //get the node from the session - $node = $session->getNode('/data/node'); - - $node->setProperty('foo', 'fafa'); - // mark the node as versionable - $node->addMixin('mix:versionable'); - $session->save(); - - // version operations are done through the VersionManager - $versionManager = $session->getWorkspace()->getVersionManager(); - - // put the versionable node into edit mode - $versionManager->checkout($node->getPath()); - $node->setProperty('foo', 'bar'); // need a change to see something - $session->save(); // you can only create versions of saved nodes - // create a new version of the node with our changes - $version = $versionManager->checkin($node->getPath()); - // Version extends the Node interface. The version is the node with additional functionality - - // walk back the versions - $oldversion = $version->getLinearPredecessor(); - // the version objects are just the meta data. call getFrozenNode on them - // to get a snapshot of the data when the version was created - echo $version->getName() . ': ' . $version->getFrozenNode()->getPropertyValue('foo') . "\n"; // 1.1: bar - echo $oldversion->getName() . ': ' . $oldversion->getFrozenNode()->getPropertyValue('foo'); // 1.0: fafa - - // get the full version history - $history = $versionManager->getVersionHistory($node->getPath()); - foreach ($history->getAllFrozenNodes() as $node) { - if ($node->hasProperty('foo')) { - // the root version does not have the property - echo $node->getPropertyValue('foo') . "\n"; - } - } - - // restore an old version - $node->setProperty('foo', 'different'); - $versionManager->checkout($node->getPath()); - $session->save(); // restoring is only possible if the session is clean - $current = $versionManager->getBaseVersion($node->getPath()); - $versionManager->restore(true, $current); - echo $node->getPropertyValue('foo'); // fafa - - -### Locking - -In PHPCR, you can lock nodes to prevent concurrency issues. There is two basic types of locks: - -* Session based locks are only kept until your session ends and released automatically on logout. -* If a lock is not session based, it is identified by a lock token and stays in place until it times out - -Note that jackalope currently only implements session based locks. - - //get the node from the session - $node = $session->getNode('/data/sibling'); - //the node has to be lockable - $node->addMixin('mix:lockable'); - $session->save(); //node needs to be clean before locking - - // get the lock manager - $workspace = $session->getWorkspace(); - $lockManager = $workspace->getLockManager(); - var_dump($lockManager->isLocked('/data/sibling')); // should be false - $lockManager->lock('/data/sibling', true, true); // lock child nodes as well, release when session closed - // now only this session may change the node //sibling and its descendants - var_dump($lockManager->isLocked('/data/sibling')); // should be true - var_dump($lockManager->isLocked('/data/sibling/child1')); // should be true because we locked deep - - // getting the lock from LockManager is not yet implemented with jackalope-jackrabbit - $lock = $lockManager->getLock('/data/sibling'); - var_dump($lock->isLockOwningSession()); // true, this is our lock, not somebody else's - var_dump($lock->getSecondsRemaining()); // PHP_INT_MAX because this lock has no timeout - var_dump($lock->isLive()); // true - - $node = $lock->getNode(); // this gets us the node for /sibling - $node === $lockManager->getLock('/data/sibling')->getNode(); // getnode always returns the lock owning node - - // now unlock the node again - $lockManager->unlock('/data/sibling'); // we could also let $session->logout() unlock when using session based lock - var_dump($lockManager->isLocked('/data/sibling')); // false - var_dump($lock->isLive()); // false - - -### Transactions - -The PHPCR API in itself uses some sort of 'transaction' model by only -persisting changes on session save. If you need transactions over more than one -save operation or including workspace operations that are dispatched immediatly, -you can use transactions. - -Note that Jackalope does not support the full transactions. - - // get the transaction manager. - $workspace = $session->getWorkspace(); - $transactionManager = $workspace->getTransactionManager(); - // start a transaction - $transactionManager->begin(); - $session->removeNode('/data/sibling'); - $session->getRootNode()->addNode('insideTransaction'); - $session->save(); // wrote to the backend but not yet visible to other sessions - $workspace->move('/data/node', '/new'); // will only move the new node if session has been saved. still not visible to other sessions - $transactionManager->commit(); // now everything become persistent and visible to others - - // you can abort a transaction - try { - ... - } catch(\Exception $e) { - if ($transactionManager->inTransaction()) { - $transactionManager->rollback(); - } - ... - } - - -### Import and export data - -As promised, here are some more details on importing and exporting data. There -are two formats: - -* The *document view* translates the data into a XML document with node names - as xml elements and properties as attributes and thus very readable. Type - information is lost, and illegal XML characters are encoded. -* The *system view* is a more strict XML document defining the exact structure - of the repository with all type information. However, it is more verbose. - -As an analogy, think about an SQL dump file with SQL statements and the dump of -an SQL table into a csv file. You can restore the data from both, but the SQL -dump knows every detail about your field types and so on while the CSV just -knows the data. - -When exporting, you tell explicitly to which format you want to export. - - $file = fopen('/tmp/document.xml', 'w+'); - - // dump the tree at /foo/bar into a document view file - $session->exportDocumentView( - '/data/sibling', - $file, - true, // skip binary properties to not have large files in the dump - false // recursivly output the child nodes as well - ); - - fclose($file); - - $file = fopen('/tmp/system.xml', 'w+'); - // export the tree at /foo/bar into a system view xml file - $session->exportSystemView( - '/data/sibling', - $file, - false, // do not skip binary properties - false - ); - - fclose($file); - -Importing detects the format automatically. If the document is a valid JCR -system view, it is interpreted according to that format, otherwise if it is a -valid XML document it is imported as document. - - $filename = 'dump.xml'; - $session->getRootNode()->addNode('imported_data', 'nt:unstructured'); - $session->importXML( - '/imported_data', // attach the imported data at this node - $filename, - ImportUUIDBehaviorInterface::IMPORT_UUID_CREATE_NEW - ); - -When importing nodes with a uuid, a couple of different behaviors can be used: - -* IMPORT_UUID_CREATE_NEW: Create new UUIDs for nodes that are imported, so you never get collisions. -* IMPORT_UUID_COLLISION_THROW: Throw an exception if a node with the same UUID already exists. -* IMPORT_UUID_COLLISION_REMOVE_EXISTING: Remove an existing node if an imported node has the same UUID. -* IMPORT_UUID_COLLISION_REPLACE_EXISTING: Replace existing node with the imported node. This can lead to the imported data being put in various places. - - -### Observation - -Observation enables an application to receive notifications of persistent changes to a workspace. -JCR defines a general event model and specific APIs for asynchronous and journaled observation. -A repository may support asynchronous observation, journaled observation or both. - -Note that Jackrabbit supports the full observation API but Jackalope currently only implements event journal reading. - -Write operations in Jackalope will generate journal entries as expected. - - use PHPCR\Observation\EventInterface; // Contains the constants for event types - - // Get the observation manager - $workspace = $session->getWorkspace(); - $observationManager = $workspace->getObservationManager(); - - // Get the unfiltered event journal and go through its content - $journal = $observationManager->getEventJournal(); - $journal->skipTo(strtotime('-1 day')); // Skip all the events prior to yesterday - foreach ($journal as $event) { - // Do something with $event (it's a Jackalope\Observation\Event instance) - echo $event->getType() . ' - ' . $event->getPath(); - } - - // Filtering and using the journal as an iterator - // You can filter the event journal on several criteria, here we keep events for node and properties added - $journal = $observationManager->getEventJournal(EventInterface::NODE_ADDED | EventInterface::PROPERTY_ADDED); - - while ($journal->valid()) { - $event = $journal->current(); - // Do something with $event - $journal->next(); - } - - -### Node Types - -PHPCR supports node types. Node types define what properties and children a node can or must have. The JCR specification explains exhaustivly what node types exist and what they are required to have or not: [JCR 2.0: 3.7.11 Standard Application Node Types](http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.7.11%20Standard%20Application%20Node%20Types) - -In a nutshell: -* nt:unstructured does not define any required properties but allows any property or child. -* nt:file and nt:folder are built-in node types useful to map a file structure in the repository. (With jackalope-jackrabbit, files and folders are exposed over webdav) -* for your own things, use nt:unstructured and PHPCR will behave like a NoSQL database -* if you need to store additional properties or children on existing node types like files, note that while a node can have only one primary type, every node can have any mixin types. Define a mixin type declaring your additional properties, register it with PHPCR and addMixin it to the nodes that need it. - -You can define your own node types if you want the equivalent of a strictly defined database structure. See [JCR 2.0: 3.7 Node Types](http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.7%20Node%20Types) and [JCR 2.0: 19 Node Type Management](http://www.day.com/specs/jcr/2.0/19_Node_Type_Management.html) / [PHPCR Node Type Namespace](http://phpcr.github.io/doc/html/index.html). - - -## Performance considerations - -While PHPCR can perform reasonably well, you should be careful. You are working with an object model mapping interlinked data. Implementations are supposed to lazy load data only when necessary. But you should take care to only request what you actually need. - -The implementations will also use some sort of storage backend (Jackrabbit, (no)SQL database, ...). There might be a huge performance impact in configuring that storage backend optimally. Look into your implementation documentation if there are recommendations how to optimize storage. - -One thing *not* to worry about is requesting the same node with Session::getNode or Node::getNode/s several times. You always get the same object instance back without overhead. - - -### Only request what you need - -Remember that you can filter nodes on Node::getNodes if you only need a list of specific nodes or all nodes in some namespace. - -The values of binary properties can potentially have a huge size and should only loaded when really needed. If you just need the size, you can get the property instance and do a $property->getSize() instead of filesize($node->getPropertyValue). Any decent implementation will not preload the binary stream when you access the property object. - -When getting the properties from a node, you can use Node::getPropertiesValues(filter, false). This allows the implementation to avoid instantiating Property objects for the property values (and saves you coding). The second boolean parameter tells wheter to dereference reference properties. If you do not need the referenced objects, pass false and you will get the UUID or path strings instead of node objects.(If you need one of them, you can still get it with Session::getNodeByIdentifier. But then the implementation will certainly not be able to optimize if you get several referenced nodes.) - - -### But request in one call as much as possible of what you need - -If you need to get several nodes where you know the paths, use Session::getNodes with an array of those nodes to get all of them in one batch, saving round trip time to the storage backend. - -Also use Node::getNodes with a list of nodes rather than repeatedly calling Node::getNode. - - -### Search - -TODO: intelligent filtering criteria to do as little in-memory operations to apply criteria. - -If you do not need the node objects but just some value, query for that value and use the result Row to avoid instantiating Node objects alltogether. If you need the Node objects, help PHPCR to optimize by using QueryResult::getNodes and iterating over the nodes instead of getting the rows, iterating over them and calling getNode on each row. (Actually, if you first do the getNodes(), you can then iterate over the rows and get the individual nodes and still use the special row methods as the implementation should have prefetched data on the getNodes.) - - -## Conclusions - -We hope this tutorial helps to get you started. If you miss anything, have suggestions or questions, please contact us on jackalope-dev@googlegroups.com or #jackalope on irc.freenode.net - -### Further reading - -Browse through the [API documentation](http://phpcr.github.com/doc/html/index.html) to see what each of the core elements mentioned in the introduction can do. - -To fully understand the concepts behind the content repository API, we suggest reading [the Java content repository specification](http://www.day.com/specs/jcr/2.0/index.html) and -then the [simplifications we did for PHP](https://github.com/phpcr/phpcr/blob/master/doc/JCR_TO_PHPCR.txt). - - -### Not yet implemented - -A couple of other advanced functionalities are defined by the API. They are not yet implemented in any PHPCR implementation. This document will be updated once there is an implementation for them. - -* Permissions and capabilities -* Access control management -* Lifecycle managment -* Retention and hold