diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..59cd005a9 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,4 @@ +en/_exts/configurationblock.pyc +build +en/_build +.idea diff --git a/docs/.gitmodules b/docs/.gitmodules new file mode 100644 index 000000000..e38d44b0a --- /dev/null +++ b/docs/.gitmodules @@ -0,0 +1,3 @@ +[submodule "en/_theme"] + path = en/_theme + url = https://github.com/doctrine/doctrine-sphinx-theme.git diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..4315116f8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,8 @@ +# Doctrine ORM Documentation + +## How to Generate + +1. Run ./bin/install-dependencies.sh +2. Run ./bin/generate-docs.sh + +It will generate the documentation into the build directory of the checkout. \ No newline at end of file diff --git a/docs/bin/generate-docs.sh b/docs/bin/generate-docs.sh new file mode 100755 index 000000000..7d06d2a8d --- /dev/null +++ b/docs/bin/generate-docs.sh @@ -0,0 +1,10 @@ +#!/bin/bash +EXECPATH=`dirname $0` +cd $EXECPATH +cd .. + +rm build -Rf +sphinx-build en build + +sphinx-build -b latex en build/pdf +rubber --into build/pdf --pdf build/pdf/Doctrine2ORM.tex \ No newline at end of file diff --git a/docs/bin/install-dependencies.sh b/docs/bin/install-dependencies.sh new file mode 100644 index 000000000..86b3bdff7 --- /dev/null +++ b/docs/bin/install-dependencies.sh @@ -0,0 +1,4 @@ +#!/bin/bash +sudo apt-get install python25 python25-dev texlive-full rubber +sudo easy_install pygments +sudo easy_install sphinx \ No newline at end of file diff --git a/docs/en/Makefile b/docs/en/Makefile new file mode 100644 index 000000000..a6f6fce62 --- /dev/null +++ b/docs/en/Makefile @@ -0,0 +1,89 @@ +# 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) . + +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes linkcheck doctest + +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 " 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 " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @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." + +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/Doctrine2ORM.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Doctrine2ORM.qhc" + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \ + "run these through (pdf)latex." + +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/docs/en/_exts/configurationblock.py b/docs/en/_exts/configurationblock.py new file mode 100644 index 000000000..36ca61f5b --- /dev/null +++ b/docs/en/_exts/configurationblock.py @@ -0,0 +1,93 @@ +#Copyright (c) 2010 Fabien Potencier +# +#Permission is hereby granted, free of charge, to any person obtaining a copy +#of this software and associated documentation files (the "Software"), to deal +#in the Software without restriction, including without limitation the rights +#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +#copies of the Software, and to permit persons to whom the Software is furnished +#to do so, subject to the following conditions: +# +#The above copyright notice and this permission notice shall be included in all +#copies or substantial portions of the Software. +# +#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +#THE SOFTWARE. + +from docutils.parsers.rst import Directive, directives +from docutils import nodes +from string import upper + +class configurationblock(nodes.General, nodes.Element): + pass + +class ConfigurationBlock(Directive): + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + formats = { + 'html': 'HTML', + 'xml': 'XML', + 'php': 'PHP', + 'yaml': 'YAML', + 'jinja': 'Twig', + 'html+jinja': 'Twig', + 'jinja+html': 'Twig', + 'php+html': 'PHP', + 'html+php': 'PHP', + 'ini': 'INI', + 'php-annotations': 'Annotations', + } + + def run(self): + env = self.state.document.settings.env + + node = nodes.Element() + node.document = self.state.document + self.state.nested_parse(self.content, self.content_offset, node) + + entries = [] + for i, child in enumerate(node): + if isinstance(child, nodes.literal_block): + # add a title (the language name) before each block + #targetid = "configuration-block-%d" % env.new_serialno('configuration-block') + #targetnode = nodes.target('', '', ids=[targetid]) + #targetnode.append(child) + + innernode = nodes.emphasis(self.formats[child['language']], self.formats[child['language']]) + + para = nodes.paragraph() + para += [innernode, child] + + entry = nodes.list_item('') + entry.append(para) + entries.append(entry) + + resultnode = configurationblock() + resultnode.append(nodes.bullet_list('', *entries)) + + return [resultnode] + +def visit_configurationblock_html(self, node): + self.body.append(self.starttag(node, 'div', CLASS='configuration-block')) + +def depart_configurationblock_html(self, node): + self.body.append('\n') + +def visit_configurationblock_latex(self, node): + pass + +def depart_configurationblock_latex(self, node): + pass + +def setup(app): + app.add_node(configurationblock, + html=(visit_configurationblock_html, depart_configurationblock_html), + latex=(visit_configurationblock_latex, depart_configurationblock_latex)) + app.add_directive('configuration-block', ConfigurationBlock) diff --git a/docs/en/_theme b/docs/en/_theme new file mode 160000 index 000000000..5aa024c92 --- /dev/null +++ b/docs/en/_theme @@ -0,0 +1 @@ +Subproject commit 5aa024c9284228ed76c7a9aa785229cf19f396e3 diff --git a/docs/en/conf.py b/docs/en/conf.py new file mode 100644 index 000000000..4d6ec244e --- /dev/null +++ b/docs/en/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# Doctrine 2 ORM documentation build configuration file, created by +# sphinx-quickstart on Fri Dec 3 18:10:24 2010. +# +# 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.append(os.path.abspath('_exts')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['configurationblock'] + +# 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' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Doctrine 2 ORM' +copyright = u'2010-12, Doctrine Project Team' + +# 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.2' +# The full version, including alpha/beta/rc tags. +release = '2.2' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +language = 'en' + +# 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 documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_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 = True + +# 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. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'doctrine' + +# 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 = ['_theme'] + +# 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 = None + +# 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_use_modindex = 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, 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 = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Doctrine2ORMdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'Doctrine2ORM.tex', u'Doctrine 2 ORM Documentation', + u'Doctrine Project Team', '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 + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/docs/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst b/docs/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst new file mode 100644 index 000000000..bc24cd308 --- /dev/null +++ b/docs/en/cookbook/advanced-field-value-conversion-using-custom-mapping-types.rst @@ -0,0 +1,256 @@ +Advanced field value conversion using custom mapping types +========================================================== + +.. sectionauthor:: Jan Sorgalla + +When creating entities, you sometimes have the need to transform field values +before they are saved to the database. In Doctrine you can use Custom Mapping +Types to solve this (see: :ref:`reference-basic-mapping-custom-mapping-types`). + +There are several ways to achieve this: converting the value inside the Type +class, converting the value on the database-level or a combination of both. + +This article describes the third way by implementing the MySQL specific column +type `Point `_. + +The ``Point`` type is part of the `Spatial extension `_ +of MySQL and enables you to store a single location in a coordinate space by +using x and y coordinates. You can use the Point type to store a +longitude/latitude pair to represent a geographic location. + +The entity +---------- + +We create a simple entity with a field ``$point`` which holds a value object +``Point`` representing the latitude and longitude of the position. + +The entity class: + +.. code-block:: php + + point = $point; + } + + /** + * @return \Geo\ValueObject\Point + */ + public function getPoint() + { + return $this->point; + } + + /** + * @param string $address + */ + public function setAddress($address) + { + $this->address = $address; + } + + /** + * @return string + */ + public function getAddress() + { + return $this->address; + } + } + +We use the custom type ``point`` in the ``@Column`` docblock annotation of the +``$point`` field. We will create this custom mapping type in the next chapter. + +The point class: + +.. code-block:: php + + latitude = $latitude; + $this->longitude = $longitude; + } + + /** + * @return float + */ + public function getLatitude() + { + return $this->latitude; + } + + /** + * @return float + */ + public function getLongitude() + { + return $this->longitude; + } + } + +The mapping type +---------------- + +Now we're going to create the ``point`` type and implement all required methods. + +.. code-block:: php + + getLongitude(), $value->getLatitude()); + } + + return $value; + } + + public function canRequireSQLConversion() + { + return true; + } + + public function convertToPHPValueSQL($sqlExpr, AbstractPlatform $platform) + { + return sprintf('AsText(%s)', $sqlExpr); + } + + public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform) + { + return sprintf('PointFromText(%s)', $sqlExpr); + } + } + +We do a 2-step conversion here. In the first step, we convert the ``Point`` +object into a string representation before saving to the database (in the +``convertToDatabaseValue`` method) and back into an object after fetching the +value from the database (in the ``convertToPHPValue`` method). + +The format of the string representation format is called `Well-known text (WKT) +`_. The advantage of this format +is, that it is both human readable and parsable by MySQL. + +Internally, MySQL stores geometry values in a binary format that is not +identical to the WKT format. So, we need to let MySQL transform the WKT +representation into its internal format. + +This is where the ``convertToPHPValueSQL`` and ``convertToDatabaseValueSQL`` +methods come into play. + +This methods wrap a sql expression (the WKT representation of the Point) into +MySQL functions `PointFromText `_ +and `AsText `_ +which convert WKT strings to and from the internal format of MySQL. + +.. note:: + + When using DQL queries, the ``convertToPHPValueSQL`` and + ``convertToDatabaseValueSQL`` methods only apply to identification variables + and path expressions in SELECT clauses. Expressions in WHERE clauses are + **not** wrapped! + + If you want to use Point values in WHERE clauses, you have to implement a + :doc:`user defined function ` for + ``PointFromText``. + +Example usage +------------- + +.. code-block:: php + + getConnection()->getDatabasePlatform()->registerDoctrineTypeMapping('point', 'point'); + + // Store a Location object + use Geo\Entity\Location; + use Geo\ValueObject\Point; + + $location = new Location(); + + $location->setAddress('1600 Amphitheatre Parkway, Mountain View, CA'); + $location->setPoint(new Point(37.4220761, -122.0845187)); + + $em->persist($location); + $em->flush(); + $em->clear(); + + // Fetch the Location object + $query = $em->createQuery("SELECT l FROM Geo\Entity\Location WHERE l.address = '1600 Amphitheatre Parkway, Mountain View, CA'"); + $location = $query->getSingleResult(); + + /* @var Geo\ValueObject\Point */ + $point = $location->getPoint(); diff --git a/docs/en/cookbook/aggregate-fields.rst b/docs/en/cookbook/aggregate-fields.rst new file mode 100644 index 000000000..5d0981b06 --- /dev/null +++ b/docs/en/cookbook/aggregate-fields.rst @@ -0,0 +1,376 @@ +Aggregate Fields +================ + +.. sectionauthor:: Benjamin Eberlei + +You will often come across the requirement to display aggregate +values of data that can be computed by using the MIN, MAX, COUNT or +SUM SQL functions. For any ORM this is a tricky issue +traditionally. Doctrine 2 offers several ways to get access to +these values and this article will describe all of them from +different perspectives. + +You will see that aggregate fields can become very explicit +features in your domain model and how this potentially complex +business rules can be easily tested. + +An example model +---------------- + +Say you want to model a bank account and all their entries. Entries +into the account can either be of positive or negative money +values. Each account has a credit limit and the account is never +allowed to have a balance below that value. + +For simplicity we live in a world were money is composed of +integers only. Also we omit the receiver/sender name, stated reason +for transfer and the execution date. These all would have to be +added on the ``Entry`` object. + +Our entities look like: + +.. code-block:: php + + no = $no; + $this->maxCredit = $maxCredit; + $this->entries = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + /** + * @Entity + */ + class Entry + { + /** @Id @GeneratedValue @Column(type="integer") */ + private $id; + + /** + * @ManyToOne(targetEntity="Account", inversedBy="entries") + */ + private $account; + + /** + * @Column(type="integer") + */ + private $amount; + + public function __construct($account, $amount) + { + $this->account = $account; + $this->amount = $amount; + // more stuff here, from/to whom, stated reason, execution date and such + } + + public function getAmount() + { + return $this->amount; + } + } + +Using DQL +--------- + +The Doctrine Query Language allows you to select for aggregate +values computed from fields of your Domain Model. You can select +the current balance of your account by calling: + +.. code-block:: php + + createQuery($dql) + ->setParameter(1, $myAccountId) + ->getSingleScalarResult(); + +The ``$em`` variable in this (and forthcoming) example holds the +Doctrine ``EntityManager``. We create a query for the SUM of all +amounts (negative amounts are withdraws) and retrieve them as a +single scalar result, essentially return only the first column of +the first row. + +This approach is simple and powerful, however it has a serious +drawback. We have to execute a specific query for the balance +whenever we need it. + +To implement a powerful domain model we would rather have access to +the balance from our ``Account`` entity during all times (even if +the Account was not persisted in the database before!). + +Also an additional requirement is the max credit per ``Account`` +rule. + +We cannot reliably enforce this rule in our ``Account`` entity with +the DQL retrieval of the balance. There are many different ways to +retrieve accounts. We cannot guarantee that we can execute the +aggregation query for all these use-cases, let alone that a +userland programmer checks this balance against newly added +entries. + +Using your Domain Model +----------------------- + +``Account`` and all the ``Entry`` instances are connected through a +collection, which means we can compute this value at runtime: + +.. code-block:: php + + entries AS $entry) { + $balance += $entry->getAmount(); + } + return $balance; + } + } + +Now we can always call ``Account::getBalance()`` to access the +current account balance. + +To enforce the max credit rule we have to implement the "Aggregate +Root" pattern as described in Eric Evans book on Domain Driven +Design. Described with one sentence, an aggregate root controls the +instance creation, access and manipulation of its children. + +In our case we want to enforce that new entries can only added to +the ``Account`` by using a designated method. The ``Account`` is +the aggregate root of this relation. We can also enforce the +correctness of the bi-directional ``Account`` <-> ``Entry`` +relation with this method: + +.. code-block:: php + + assertAcceptEntryAllowed($amount); + + $e = new Entry($this, $amount); + $this->entries[] = $e; + return $e; + } + } + +Now look at the following test-code for our entities: + +.. code-block:: php + + assertEquals(0, $account->getBalance()); + + $account->addEntry(500); + $this->assertEquals(500, $account->getBalance()); + + $account->addEntry(-700); + $this->assertEquals(-200, $account->getBalance()); + } + + public function testExceedMaxLimit() + { + $account = new Account("123456", $maxCredit = 200); + + $this->setExpectedException("Exception"); + $account->addEntry(-1000); + } + } + +To enforce our rule we can now implement the assertion in +``Account::addEntry``: + +.. code-block:: php + + getBalance() + $amount; + $allowedMinimalBalance = ($this->maxCredit * -1); + if ($futureBalance < $allowedMinimalBalance) { + throw new Exception("Credit Limit exceeded, entry is not allowed!"); + } + } + } + +We haven't talked to the entity manager for persistence of our +account example before. You can call +``EntityManager::persist($account)`` and then +``EntityManager::flush()`` at any point to save the account to the +database. All the nested ``Entry`` objects are automatically +flushed to the database also. + +.. code-block:: php + + addEntry(500); + $account->addEntry(-200); + $em->persist($account); + $em->flush(); + +The current implementation has a considerable drawback. To get the +balance, we have to initialize the complete ``Account::$entries`` +collection, possibly a very large one. This can considerably hurt +the performance of your application. + +Using an Aggregate Field +------------------------ + +To overcome the previously mentioned issue (initializing the whole +entries collection) we want to add an aggregate field called +"balance" on the Account and adjust the code in +``Account::getBalance()`` and ``Account:addEntry()``: + +.. code-block:: php + + balance; + } + + public function addEntry($amount) + { + $this->assertAcceptEntryAllowed($amount); + + $e = new Entry($this, $amount); + $this->entries[] = $e; + $this->balance += $amount; + return $e; + } + } + +This is a very simple change, but all the tests still pass. Our +account entities return the correct balance. Now calling the +``Account::getBalance()`` method will not occur the overhead of +loading all entries anymore. Adding a new Entry to the +``Account::$entities`` will also not initialize the collection +internally. + +Adding a new entry is therefore very performant and explicitly +hooked into the domain model. It will only update the account with +the current balance and insert the new entry into the database. + +Tackling Race Conditions with Aggregate Fields +---------------------------------------------- + +Whenever you denormalize your database schema race-conditions can +potentially lead to inconsistent state. See this example: + +.. code-block:: php + + find('Bank\Entities\Account', $accId); + + // request 2 account + $account2 = $em->find('Bank\Entities\Account', $accId); + + $account1->addEntry(-200); + $account2->addEntry(-200); + + // now request 1 and 2 both flush the changes. + +The aggregate field ``Account::$balance`` is now -200, however the +SUM over all entries amounts yields -400. A violation of our max +credit rule. + +You can use both optimistic or pessimistic locking to save-guard +your aggregate fields against this kind of race-conditions. Reading +Eric Evans DDD carefully he mentions that the "Aggregate Root" +(Account in our example) needs a locking mechanism. + +Optimistic locking is as easy as adding a version column: + +.. code-block:: php + + find('Bank\Entities\Account', $accId, LockMode::PESSIMISTIC_READ); + +Keeping Updates and Deletes in Sync +----------------------------------- + +The example shown in this article does not allow changes to the +value in ``Entry``, which considerably simplifies the effort to +keep ``Account::$balance`` in sync. If your use-case allows fields +to be updated or related entities to be removed you have to +encapsulate this logic in your "Aggregate Root" entity and adjust +the aggregate field accordingly. + +Conclusion +---------- + +This article described how to obtain aggregate values using DQL or +your domain model. It showed how you can easily add an aggregate +field that offers serious performance benefits over iterating all +the related objects that make up an aggregate value. Finally I +showed how you can ensure that your aggregate fields do not get out +of sync due to race-conditions and concurrent access. + + diff --git a/docs/en/cookbook/decorator-pattern.rst b/docs/en/cookbook/decorator-pattern.rst new file mode 100644 index 000000000..49d13b719 --- /dev/null +++ b/docs/en/cookbook/decorator-pattern.rst @@ -0,0 +1,273 @@ +Persisting the Decorator Pattern +================================ + +.. sectionauthor:: Chris Woodford + +This recipe will show you a simple example of how you can use +Doctrine 2 to persist an implementation of the +`Decorator Pattern `_ + +Component +--------- + +The ``Component`` class needs to be persisted, so it's going to +be an ``Entity``. As the top of the inheritance hierarchy, it's going +to have to define the persistent inheritance. For this example, we +will use Single Table Inheritance, but Class Table Inheritance +would work as well. In the discriminator map, we will define two +concrete subclasses, ``ConcreteComponent`` and ``ConcreteDecorator``. + +.. code-block:: php + + id; + } + + /** + * Set name + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * Get name + * @return string $name + */ + public function getName() + { + return $this->name; + } + + } + +ConcreteComponent +----------------- + +The ``ConcreteComponent`` class is pretty simple and doesn't do much +more than extend the abstract ``Component`` class (only for the +purpose of keeping this example simple). + +.. code-block:: php + + setDecorates($c); + } + + /** + * (non-PHPdoc) + * @see Test.Component::getName() + */ + public function getName() + { + return 'Decorated ' . $this->getDecorates()->getName(); + } + + /** + * the component being decorated + * @return Component + */ + protected function getDecorates() + { + return $this->decorates; + } + + /** + * sets the component being decorated + * @param Component $c + */ + protected function setDecorates(Component $c) + { + $this->decorates = $c; + } + + } + +All operations on the ``Decorator`` (i.e. persist, remove, etc) will +cascade from the ``Decorator`` to the ``Component``. This means that +when we persist a ``Decorator``, Doctrine will take care of +persisting the chain of decorated objects for us. A ``Decorator`` can +be treated exactly as a ``Component`` when it comes time to +persisting it. + +The ``Decorator's`` constructor accepts an instance of a +``Component``, as defined by the ``Decorator`` pattern. The +setDecorates/getDecorates methods have been defined as protected to +hide the fact that a ``Decorator`` is decorating a ``Component`` and +keeps the ``Component`` interface and the ``Decorator`` interface +identical. + +To illustrate the intended result of the ``Decorator`` pattern, the +getName() method has been overridden to append a string to the +``Component's`` getName() method. + +ConcreteDecorator +----------------- + +The final class required to complete a simple implementation of the +Decorator pattern is the ``ConcreteDecorator``. In order to further +illustrate how the ``Decorator`` can alter data as it moves through +the chain of decoration, a new field, "special", has been added to +this class. The getName() has been overridden and appends the value +of the getSpecial() method to its return value. + +.. code-block:: php + + special = $special; + } + + /** + * Get special + * @return string $special + */ + public function getSpecial() + { + return $this->special; + } + + /** + * (non-PHPdoc) + * @see Test.Component::getName() + */ + public function getName() + { + return '[' . $this->getSpecial() + . '] ' . parent::getName(); + } + + } + +Examples +-------- + +Here is an example of how to persist and retrieve your decorated +objects + +.. code-block:: php + + setName('Test Component 1'); + $em->persist($c); // assigned unique ID = 1 + + // create a new concrete decorator + $c = new ConcreteComponent(); + $c->setName('Test Component 2'); + + $d = new ConcreteDecorator($c); + $d->setSpecial('Really'); + $em->persist($d); + // assigns c as unique ID = 2, and d as unique ID = 3 + + $em->flush(); + + $c = $em->find('Test\Component', 1); + $d = $em->find('Test\Component', 3); + + echo get_class($c); + // prints: Test\Component\ConcreteComponent + + echo $c->getName(); + // prints: Test Component 1 + + echo get_class($d) + // prints: Test\Component\ConcreteDecorator + + echo $d->getName(); + // prints: [Really] Decorated Test Component 2 + diff --git a/docs/en/cookbook/dql-custom-walkers.rst b/docs/en/cookbook/dql-custom-walkers.rst new file mode 100644 index 000000000..2887a5d68 --- /dev/null +++ b/docs/en/cookbook/dql-custom-walkers.rst @@ -0,0 +1,217 @@ +Extending DQL in Doctrine 2: Custom AST Walkers +=============================================== + +.. sectionauthor:: Benjamin Eberlei + +The Doctrine Query Language (DQL) is a proprietary sql-dialect that +substitutes tables and columns for Entity names and their fields. +Using DQL you write a query against the database using your +entities. With the help of the metadata you can write very concise, +compact and powerful queries that are then translated into SQL by +the Doctrine ORM. + +In Doctrine 1 the DQL language was not implemented using a real +parser. This made modifications of the DQL by the user impossible. +Doctrine 2 in contrast has a real parser for the DQL language, +which transforms the DQL statement into an +`Abstract Syntax Tree `_ +and generates the appropriate SQL statement for it. Since this +process is deterministic Doctrine heavily caches the SQL that is +generated from any given DQL query, which reduces the performance +overhead of the parsing process to zero. + +You can modify the Abstract syntax tree by hooking into DQL parsing +process by adding a Custom Tree Walker. A walker is an interface +that walks each node of the Abstract syntax tree, thereby +generating the SQL statement. + +There are two types of custom tree walkers that you can hook into +the DQL parser: + + +- An output walker. This one actually generates the SQL, and there + is only ever one of them. We implemented the default SqlWalker + implementation for it. +- A tree walker. There can be many tree walkers, they cannot + generate the sql, however they can modify the AST before its + rendered to sql. + +Now this is all awfully technical, so let me come to some use-cases +fast to keep you motivated. Using walker implementation you can for +example: + + +- Modify the AST to generate a Count Query to be used with a + paginator for any given DQL query. +- Modify the Output Walker to generate vendor-specific SQL + (instead of ANSI). +- Modify the AST to add additional where clauses for specific + entities (example ACL, country-specific content...) +- Modify the Output walker to pretty print the SQL for debugging + purposes. + +In this cookbook-entry I will show examples on the first two +points. There are probably much more use-cases. + +Generic count query for pagination +---------------------------------- + +Say you have a blog and posts all with one category and one author. +A query for the front-page or any archive page might look something +like: + +.. code-block:: sql + + SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now in this query the blog post is the root entity, meaning its the +one that is hydrated directly from the query and returned as an +array of blog posts. In contrast the comment and author are loaded +for deeper use in the object tree. + +A pagination for this query would want to approximate the number of +posts that match the WHERE clause of this query to be able to +predict the number of pages to show to the user. A draft of the DQL +query for pagination would look like: + +.. code-block:: sql + + SELECT count(DISTINCT p.id) FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ... + +Now you could go and write each of these queries by hand, or you +can use a tree walker to modify the AST for you. Lets see how the +API would look for this use-case: + +.. code-block:: php + + createQuery($dql); + $query->setFirstResult( ($pageNum-1) * 20)->setMaxResults(20); + + $totalResults = Paginate::count($query); + $results = $query->getResult(); + +The ``Paginate::count(Query $query)`` looks like: + +.. code-block:: php + + setHint(Query::HINT_CUSTOM_TREE_WALKERS, array('DoctrineExtensions\Paginate\CountSqlWalker')); + $countQuery->setFirstResult(null)->setMaxResults(null); + + return $countQuery->getSingleScalarResult(); + } + } + +It clones the query, resets the limit clause first and max results +and registers the ``CountSqlWalker`` customer tree walker which +will modify the AST to execute a count query. The walkers +implementation is: + +.. code-block:: php + + _getQueryComponents() AS $dqlAlias => $qComp) { + if ($qComp['parent'] === null && $qComp['nestingLevel'] == 0) { + $parent = $qComp; + $parentName = $dqlAlias; + break; + } + } + + $pathExpression = new PathExpression( + PathExpression::TYPE_STATE_FIELD | PathExpression::TYPE_SINGLE_VALUED_ASSOCIATION, $parentName, + $parent['metadata']->getSingleIdentifierFieldName() + ); + $pathExpression->type = PathExpression::TYPE_STATE_FIELD; + + $AST->selectClause->selectExpressions = array( + new SelectExpression( + new AggregateExpression('count', $pathExpression, true), null + ) + ); + } + } + +This will delete any given select expressions and replace them with +a distinct count query for the root entities primary key. This will +only work if your entity has only one identifier field (composite +keys won't work). + +Modify the Output Walker to generate Vendor specific SQL +-------------------------------------------------------- + +Most RMDBS have vendor-specific features for optimizing select +query execution plans. You can write your own output walker to +introduce certain keywords using the Query Hint API. A query hint +can be set via ``Query::setHint($name, $value)`` as shown in the +previous example with the ``HINT_CUSTOM_TREE_WALKERS`` query hint. + +We will implement a custom Output Walker that allows to specify the +SQL\_NO\_CACHE query hint. + +.. code-block:: php + + createQuery($dql); + $query->setHint(Query::HINT_CUSTOM_OUTPUT_WALKER, 'DoctrineExtensions\Query\MysqlWalker'); + $query->setHint("mysqlWalker.sqlNoCache", true); + $results = $query->getResult(); + +Our ``MysqlWalker`` will extend the default ``SqlWalker``. We will +modify the generation of the SELECT clause, adding the +SQL\_NO\_CACHE on those queries that need it: + +.. code-block:: php + + getQuery()->getHint('mysqlWalker.sqlNoCache') === true) { + if ($selectClause->isDistinct) { + $sql = str_replace('SELECT DISTINCT', 'SELECT DISTINCT SQL_NO_CACHE', $sql); + } else { + $sql = str_replace('SELECT', 'SELECT SQL_NO_CACHE', $sql); + } + } + + return $sql; + } + } + +Writing extensions to the Output Walker requires a very deep +understanding of the DQL Parser and Walkers, but may offer your +huge benefits with using vendor specific features. This would still +allow you write DQL queries instead of NativeQueries to make use of +vendor specific features. + diff --git a/docs/en/cookbook/dql-user-defined-functions.rst b/docs/en/cookbook/dql-user-defined-functions.rst new file mode 100644 index 000000000..77d66ad82 --- /dev/null +++ b/docs/en/cookbook/dql-user-defined-functions.rst @@ -0,0 +1,240 @@ +DQL User Defined Functions +========================== + +.. sectionauthor:: Benjamin Eberlei + +By default DQL supports a limited subset of all the vendor-specific +SQL functions common between all the vendors. However in many cases +once you have decided on a specific database vendor, you will never +change it during the life of your project. This decision for a +specific vendor potentially allows you to make use of powerful SQL +features that are unique to the vendor. + +It is worth to mention that Doctrine 2 also allows you to handwrite +your SQL instead of extending the DQL parser. Extending DQL is sort of an +advanced extension point. You can map arbitrary SQL to your objects +and gain access to vendor specific functionalities using the +``EntityManager#createNativeQuery()`` API as described in +the :doc:`Native Query <../reference/native-sql>` chapter. + + +The DQL Parser has hooks to register functions that can then be +used in your DQL queries and transformed into SQL, allowing to +extend Doctrines Query capabilities to the vendors strength. This +post explains the Used-Defined Functions API (UDF) of the Dql +Parser and shows some examples to give you some hints how you would +extend DQL. + +There are three types of functions in DQL, those that return a +numerical value, those that return a string and those that return a +Date. Your custom method has to be registered as either one of +those. The return type information is used by the DQL parser to +check possible syntax errors during the parsing process, for +example using a string function return value in a math expression. + +Registering your own DQL functions +---------------------------------- + +You can register your functions adding them to the ORM +configuration: + +.. code-block:: php + + addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The ``$name`` is the name the function will be referred to in the +DQL query. ``$class`` is a string of a class-name which has to +extend ``Doctrine\ORM\Query\Node\FunctionNode``. This is a class +that offers all the necessary API and methods to implement a UDF. + +In this post we will implement some MySql specific Date calculation +methods, which are quite handy in my opinion: + +Date Diff +--------- + +`Mysql's DateDiff function `_ +takes two dates as argument and calculates the difference in days +with ``date1-date2``. + +The DQL parser is a top-down recursive descent parser to generate +the Abstract-Syntax Tree (AST) and uses a TreeWalker approach to +generate the appropriate SQL from the AST. This makes reading the +Parser/TreeWalker code manageable in a finite amount of time. + +The ``FunctionNode`` class I referred to earlier requires you to +implement two methods, one for the parsing process (obviously) +called ``parse`` and one for the TreeWalker process called +``getSql()``. I show you the code for the DateDiff method and +discuss it step by step: + +.. code-block:: php + + match(Lexer::T_IDENTIFIER); // (2) + $parser->match(Lexer::T_OPEN_PARENTHESIS); // (3) + $this->firstDateExpression = $parser->ArithmeticPrimary(); // (4) + $parser->match(Lexer::T_COMMA); // (5) + $this->secondDateExpression = $parser->ArithmeticPrimary(); // (6) + $parser->match(Lexer::T_CLOSE_PARENTHESIS); // (3) + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATEDIFF(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', ' . + $this->secondDateExpression->dispatch($sqlWalker) . + ')'; // (7) + } + } + +The Parsing process of the DATEDIFF function is going to find two +expressions the date1 and the date2 values, whose AST Node +representations will be saved in the variables of the DateDiff +FunctionNode instance at (1). + +The parse() method has to cut the function call "DATEDIFF" and its +argument into pieces. Since the parser detects the function using a +lookahead the T\_IDENTIFIER of the function name has to be taken +from the stack (2), followed by a detection of the arguments in +(4)-(6). The opening and closing parenthesis have to be detected +also. This happens during the Parsing process and leads to the +generation of a DateDiff FunctionNode somewhere in the AST of the +dql statement. + +The ``ArithmeticPrimary`` method call is the most common +denominator of valid EBNF tokens taken from the +`DQL EBNF grammar `_ +that matches our requirements for valid input into the DateDiff Dql +function. Picking the right tokens for your methods is a tricky +business, but the EBNF grammar is pretty helpful finding it, as is +looking at the Parser source code. + +Now in the TreeWalker process we have to pick up this node and +generate SQL from it, which apparently is quite easy looking at the +code in (7). Since we don't know which type of AST Node the first +and second Date expression are we are just dispatching them back to +the SQL Walker to generate SQL from and then wrap our DATEDIFF +function call around this output. + +Now registering this DateDiff FunctionNode with the ORM using: + +.. code-block:: php + + addCustomStringFunction('DATEDIFF', 'DoctrineExtensions\Query\MySql\DateDiff'); + +We can do fancy stuff like: + +.. code-block:: sql + + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATEDIFF(CURRENT_TIME(), p.created) < 7 + +Date Add +-------- + +Often useful it the ability to do some simple date calculations in +your DQL query using +`MySql's DATE\_ADD function `_. + +I'll skip the blah and show the code for this function: + +.. code-block:: php + + match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->firstDateExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_COMMA); + $parser->match(Lexer::T_IDENTIFIER); + + $this->intervalExpression = $parser->ArithmeticPrimary(); + + $parser->match(Lexer::T_IDENTIFIER); + + /* @var $lexer Lexer */ + $lexer = $parser->getLexer(); + $this->unit = $lexer->token['value']; + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + + public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker) + { + return 'DATE_ADD(' . + $this->firstDateExpression->dispatch($sqlWalker) . ', INTERVAL ' . + $this->intervalExpression->dispatch($sqlWalker) . ' ' . $this->unit . + ')'; + } + } + +The only difference compared to the DATEDIFF here is, we +additionally need the ``Lexer`` to access the value of the +``T_IDENTIFIER`` token for the Date Interval unit, for example the +MONTH in: + +.. code-block:: sql + + SELECT p FROM DoctrineExtensions\Query\BlogPost p WHERE DATE_ADD(CURRENT_TIME(), INTERVAL 4 MONTH) > p.created + +The above method now only supports the specification using +``INTERVAL``, to also allow a real date in DATE\_ADD we need to add +some decision logic to the parsing process (makes up for a nice +exercise). + +Now as you see, the Parsing process doesn't catch all the possible +SQL errors, here we don't match for all the valid inputs for the +interval unit. However where necessary we rely on the database +vendors SQL parser to show us further errors in the parsing +process, for example if the Unit would not be one of the supported +values by MySql. + +Conclusion +---------- + +Now that you all know how you can implement vendor specific SQL +functionalities in DQL, we would be excited to see user extensions +that add vendor specific function packages, for example more math +functions, XML + GIS Support, Hashing functions and so on. + +For 2.0 we will come with the current set of functions, however for +a future version we will re-evaluate if we can abstract even more +vendor sql functions and extend the DQL languages scope. + +Code for this Extension to DQL and other Doctrine Extensions can be +found +`in my Github DoctrineExtensions repository `_. + + diff --git a/docs/en/cookbook/entities-in-session.rst b/docs/en/cookbook/entities-in-session.rst new file mode 100644 index 000000000..2fd771e5d --- /dev/null +++ b/docs/en/cookbook/entities-in-session.rst @@ -0,0 +1,68 @@ +Entities in the Session +======================= + +There are several use-cases to save entities in the session, for example: + +1. User object +2. Multi-step forms + +To achieve this with Doctrine you have to pay attention to some details to get +this working. + +Merging entity into an EntityManager +------------------------------------ + +In Doctrine an entity objects has to be "managed" by an EntityManager to be +updateable. Entities saved into the session are not managed in the next request +anymore. This means that you have to register these entities with an +EntityManager again if you want to change them or use them as part of +references between other entities. You can achieve this by calling +``EntityManager#merge()``. + +For a representative User object the code to get turn an instance from +the session into a managed Doctrine object looks like this: + +.. code-block:: php + + merge($user); + } + +.. note:: + + A frequent mistake is not to get the merged user object from the return + value of ``EntityManager#merge()``. The entity object passed to merge is + not necessarily the same object that is returned from the method. + +Serializing entity into the session +----------------------------------- + +Entities that are serialized into the session normally contain references to +other entities as well. Think of the user entity has a reference to his +articles, groups, photos or many other different entities. If you serialize +this object into the session then you don't want to serialize the related +entities as well. This is why you should call ``EntityManager#detach()`` on this +object or implement the __sleep() magic method on your entity. + +.. code-block:: php + + find("User", 1); + $em->detach($user); + $_SESSION['user'] = $user; + +.. note:: + + When you called detach on your objects they get "unmanaged" with that + entity manager. This means you cannot use them as part of write operations + during ``EntityManagr#flush()`` anymore in this request. + diff --git a/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst b/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst new file mode 100644 index 000000000..4eb2b717b --- /dev/null +++ b/docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst @@ -0,0 +1,112 @@ +Implementing ArrayAccess for Domain Objects +=========================================== + +.. sectionauthor:: Roman Borschel (roman@code-factory.org) + +This recipe will show you how to implement ArrayAccess for your +domain objects in order to allow more uniform access, for example +in templates. In these examples we will implement ArrayAccess on a +`Layer Supertype `_ +for all our domain objects. + +Option 1 +-------- + +In this implementation we will make use of PHPs highly dynamic +nature to dynamically access properties of a subtype in a supertype +at runtime. Note that this implementation has 2 main caveats: + + +- It will not work with private fields +- It will not go through any getters/setters + +.. code-block:: php + + $offset); + } + + public function offsetSet($offset, $value) { + $this->$offset = $value; + } + + public function offsetGet($offset) { + return $this->$offset; + } + + public function offsetUnset($offset) { + $this->$offset = null; + } + } + +Option 2 +-------- + +In this implementation we will dynamically invoke getters/setters. +Again we use PHPs dynamic nature to invoke methods on a subtype +from a supertype at runtime. This implementation has the following +caveats: + + +- It relies on a naming convention +- The semantics of offsetExists can differ +- offsetUnset will not work with typehinted setters + +.. code-block:: php + + {"get$offset"}(); + return $value !== null; + } + + public function offsetSet($offset, $value) { + $this->{"set$offset"}($value); + } + + public function offsetGet($offset) { + return $this->{"get$offset"}(); + } + + public function offsetUnset($offset) { + $this->{"set$offset"}(null); + } + } + +Read-only +--------- + +You can slightly tweak option 1 or option 2 in order to make array +access read-only. This will also circumvent some of the caveats of +each option. Simply make offsetSet and offsetUnset throw an +exception (i.e. BadMethodCallException). + +.. code-block:: php + + `_ +for all our domain objects. + +Implementing NotifyPropertyChanged +---------------------------------- + +The NOTIFY policy is based on the assumption that the entities +notify interested listeners of changes to their properties. For +that purpose, a class that wants to use this policy needs to +implement the ``NotifyPropertyChanged`` interface from the +``Doctrine\Common`` namespace. + +.. code-block:: php + + _listeners[] = $listener; + } + + /** Notifies listeners of a change. */ + protected function _onPropertyChanged($propName, $oldValue, $newValue) { + if ($this->_listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + } + +Then, in each property setter of concrete, derived domain classes, +you need to invoke \_onPropertyChanged as follows to notify +listeners: + +.. code-block:: php + + data) { // check: is it actually modified? + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +The check whether the new value is different from the old one is +not mandatory but recommended. That way you can avoid unnecessary +updates and also have full control over when you consider a +property changed. + + diff --git a/docs/en/cookbook/implementing-wakeup-or-clone.rst b/docs/en/cookbook/implementing-wakeup-or-clone.rst new file mode 100644 index 000000000..6a8ef9c91 --- /dev/null +++ b/docs/en/cookbook/implementing-wakeup-or-clone.rst @@ -0,0 +1,78 @@ +Implementing Wakeup or Clone +============================ + +.. sectionauthor:: Roman Borschel (roman@code-factory.org) + +As explained in the +`restrictions for entity classes in the manual `_, +it is usually not allowed for an entity to implement ``__wakeup`` +or ``__clone``, because Doctrine makes special use of them. +However, it is quite easy to make use of these methods in a safe +way by guarding the custom wakeup or clone code with an entity +identity check, as demonstrated in the following sections. + +Safely implementing \_\_wakeup +------------------------------ + +To safely implement ``__wakeup``, simply enclose your +implementation code in an identity check as follows: + +.. code-block:: php + + id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +Safely implementing \_\_clone +----------------------------- + +Safely implementing ``__clone`` is pretty much the same: + +.. code-block:: php + + id) { + // ... Your code here as normal ... + } + // otherwise do nothing, do NOT throw an exception! + } + + //... + } + +Summary +------- + +As you have seen, it is quite easy to safely make use of +``__wakeup`` and ``__clone`` in your entities without adding any +really Doctrine-specific or Doctrine-dependant code. + +These implementations are possible and safe because when Doctrine +invokes these methods, the entities never have an identity (yet). +Furthermore, it is possibly a good idea to check for the identity +in your code anyway, since it's rarely the case that you want to +unserialize or clone an entity with no identity. + + diff --git a/docs/en/cookbook/integrating-with-codeigniter.rst b/docs/en/cookbook/integrating-with-codeigniter.rst new file mode 100644 index 000000000..1c06a34e2 --- /dev/null +++ b/docs/en/cookbook/integrating-with-codeigniter.rst @@ -0,0 +1,140 @@ +Integrating with CodeIgniter +============================ + +This is recipe for using Doctrine 2 in your +`CodeIgniter `_ framework. + +.. note:: + + This might not work for all CodeIgniter versions and may require + slight adjustments. + + +Here is how to set it up: + +Make a CodeIgniter library that is both a wrapper and a bootstrap +for Doctrine 2. + +Setting up the file structure +----------------------------- + +Here are the steps: + + +- Add a php file to your system/application/libraries folder + called Doctrine.php. This is going to be your wrapper/bootstrap for + the D2 entity manager. +- Put the Doctrine folder (the one that contains Common, DBAL, and + ORM) inside that same libraries folder. +- Your system/application/libraries folder now looks like this: + + system/applications/libraries -Doctrine -Doctrine.php -index.html + +- If you want, open your config/autoload.php file and autoload + your Doctrine library. + + register(); + $entitiesClassLoader = new ClassLoader('models', rtrim(APPPATH, "/" )); + $entitiesClassLoader->register(); + $proxiesClassLoader = new ClassLoader('Proxies', APPPATH.'models/proxies'); + $proxiesClassLoader->register(); + + // Set up caches + $config = new Configuration; + $cache = new ArrayCache; + $config->setMetadataCacheImpl($cache); + $driverImpl = $config->newDefaultAnnotationDriver(array(APPPATH.'models/Entities')); + $config->setMetadataDriverImpl($driverImpl); + $config->setQueryCacheImpl($cache); + + $config->setQueryCacheImpl($cache); + + // Proxy configuration + $config->setProxyDir(APPPATH.'/models/proxies'); + $config->setProxyNamespace('Proxies'); + + // Set up logger + $logger = new EchoSQLLogger; + $config->setSQLLogger($logger); + + $config->setAutoGenerateProxyClasses( TRUE ); + + // Database connection information + $connectionOptions = array( + 'driver' => 'pdo_mysql', + 'user' => $db['default']['username'], + 'password' => $db['default']['password'], + 'host' => $db['default']['hostname'], + 'dbname' => $db['default']['database'] + ); + + // Create EntityManager + $this->em = EntityManager::create($connectionOptions, $config); + } + } + +Please note that this is a development configuration; for a +production system you'll want to use a real caching system like +APC, get rid of EchoSqlLogger, and turn off +autoGenerateProxyClasses. + +For more details, consult the +`Doctrine 2 Configuration documentation `_. + +Now to use it +------------- + +Whenever you need a reference to the entity manager inside one of +your controllers, views, or models you can do this: + +.. code-block:: php + + doctrine->em; + +That's all there is to it. Once you get the reference to your +EntityManager do your Doctrine 2.0 voodoo as normal. + +Note: If you do not choose to autoload the Doctrine library, you +will need to put this line before you get a reference to it: + +.. code-block:: php + + load->library('doctrine'); + +Good luck! + + diff --git a/docs/en/cookbook/mysql-enums.rst b/docs/en/cookbook/mysql-enums.rst new file mode 100644 index 000000000..69f0a351b --- /dev/null +++ b/docs/en/cookbook/mysql-enums.rst @@ -0,0 +1,189 @@ +Mysql Enums +=========== + +The type system of Doctrine 2 consists of flyweights, which means there is only +one instance of any given type. Additionally types do not contain state. Both +assumptions make it rather complicated to work with the Enum Type of MySQL that +is used quite a lot by developers. + +When using Enums with a non-tweaked Doctrine 2 application you will get +errors from the Schema-Tool commands due to the unknown database type "enum". +By default Doctrine does not map the MySQL enum type to a Doctrine type. +This is because Enums contain state (their allowed values) and Doctrine +types don't. + +This cookbook entry shows two possible solutions to work with MySQL enums. +But first a word of warning. The MySQL Enum type has considerable downsides: + +- Adding new values requires to rebuild the whole table, which can take hours + depending on the size. +- Enums are ordered in the way the values are specified, not in their "natural" order. +- Enums validation mechanism for allowed values is not necessarily good, + specifying invalid values leads to an empty enum for the default MySQL error + settings. You can easily replicate the "allow only some values" requirement + in your Doctrine entities. + +Solution 1: Mapping to Varchars +------------------------------- + +You can map ENUMs to varchars. You can register MySQL ENUMs to map to Doctrine +varchars. This way Doctrine always resolves ENUMs to Doctrine varchars. It +will even detect this match correctly when using SchemaTool update commands. + +.. code-block:: php + + getConnection(); + $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); + +In this case you have to ensure that each varchar field that is an enum in the +database only gets passed the allowed values. You can easily enforce this in your +entities: + +.. code-block:: php + + status = $status; + } + } + +If you want to actively create enums through the Doctrine Schema-Tool by using +the **columnDefinition** attribute. + +.. code-block:: php + + values); + + return "ENUM(".implode(", ", $values).") COMMENT '(DC2Type:".$this->name.")'"; + } + + public function convertToPHPValue($value, AbstractPlatform $platform) + { + return $value; + } + + public function convertToDatabaseValue($value, AbstractPlatform $platform) + { + if (!in_array($value, $this->values)) { + throw new \InvalidArgumentException("Invalid '".$this->name."' value."); + } + return $value; + } + + public function getName() + { + return $this->name; + } + } + +With this base class you can define an enum as easily as: + +.. code-block:: php + + addResolveTargetEntity('Acme\\InvoiceModule\\Model\\InvoiceSubjectInterface', + 'Acme\\CustomerModule\\Entity\\Customer', array()); + + // Add the ResolveTargetEntityListener + $evm->addEventSubscriber($rtel); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); + +Final Thoughts +-------------- + +With the ``ResolveTargetEntityListener``, we are able to decouple our +bundles, keeping them usable by themselves, but still being able to +define relationships between different objects. By using this method, +I've found my bundles end up being easier to maintain independently. + + diff --git a/docs/en/cookbook/sql-table-prefixes.rst b/docs/en/cookbook/sql-table-prefixes.rst new file mode 100644 index 000000000..6ca73c275 --- /dev/null +++ b/docs/en/cookbook/sql-table-prefixes.rst @@ -0,0 +1,80 @@ +SQL-Table Prefixes +================== + +This recipe is intended as an example of implementing a +loadClassMetadata listener to provide a Table Prefix option for +your application. The method used below is not a hack, but fully +integrates into the Doctrine system, all SQL generated will include +the appropriate table prefix. + +In most circumstances it is desirable to separate different +applications into individual databases, but in certain cases, it +may be beneficial to have a table prefix for your Entities to +separate them from other vendor products in the same database. + +Implementing the listener +------------------------- + +The listener in this example has been set up with the +DoctrineExtensions namespace. You create this file in your +library/DoctrineExtensions directory, but will need to set up +appropriate autoloaders. + +.. code-block:: php + + prefix = (string) $prefix; + } + + public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + $classMetadata->setTableName($this->prefix . $classMetadata->getTableName()); + foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { + if ($mapping['type'] == \Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY) { + $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; + $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = $this->prefix . $mappedTableName; + } + } + } + + } + +Telling the EntityManager about our listener +-------------------------------------------- + +A listener of this type must be set up before the EntityManager has +been initialised, otherwise an Entity might be created or cached +before the prefix has been set. + +.. note:: + + If you set this listener up, be aware that you will need + to clear your caches and drop then recreate your database schema. + + +.. code-block:: php + + addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix); + + $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm); + + diff --git a/docs/en/cookbook/strategy-cookbook-introduction.rst b/docs/en/cookbook/strategy-cookbook-introduction.rst new file mode 100644 index 000000000..d9934f577 --- /dev/null +++ b/docs/en/cookbook/strategy-cookbook-introduction.rst @@ -0,0 +1,254 @@ +Strategy-Pattern +================ + +This recipe will give you a short introduction on how to design +similar entities without using expensive (i.e. slow) inheritance +but with not more than \* the well-known strategy pattern \* event +listeners + +Scenario / Problem +------------------ + +Given a Content-Management-System, we probably want to add / edit +some so-called "blocks" and "panels". What are they for? + + +- A block might be a registration form, some text content, a table + with information. A good example might also be a small calendar. +- A panel is by definition a block that can itself contain blocks. + A good example for a panel might be a sidebar box: You could easily + add a small calendar into it. + +So, in this scenario, when building your CMS, you will surely add +lots of blocks and panels to your pages and you will find yourself +highly uncomfortable because of the following: + + +- Every existing page needs to know about the panels it contains - + therefore, you'll have an association to your panels. But if you've + got several types of panels - what do you do? Add an association to + every panel-type? This wouldn't be flexible. You might be tempted + to add an AbstractPanelEntity and an AbstractBlockEntity that use + class inheritance. Your page could then only confer to the + AbstractPanelType and Doctrine 2 would do the rest for you, i.e. + load the right entities. But - you'll for sure have lots of panels + and blocks, and even worse, you'd have to edit the discriminator + map *manually* every time you or another developer implements a new + block / entity. This would tear down any effort of modular + programming. + +Therefore, we need something that's far more flexible. + +Solution +-------- + +The solution itself is pretty easy. We will have one base class +that will be loaded via the page and that has specific behaviour - +a Block class might render the front-end and even the backend, for +example. Now, every block that you'll write might look different or +need different data - therefore, we'll offer an API to these +methods but internally, we use a strategy that exactly knows what +to do. + +First of all, we need to make sure that we have an interface that +contains every needed action. Such actions would be rendering the +front-end or the backend, solving dependencies (blocks that are +supposed to be placed in the sidebar could refuse to be placed in +the middle of your page, for example). + +Such an interface could look like this: + + +.. code-block:: php + + blockStrategy. Will not be persisted by Doctrine 2. + * + * @var BlockStrategyInterface + */ + protected $strategyInstance; + + /** + * Returns the strategy that is used for this blockitem. + * + * The strategy itself defines how this block can be rendered etc. + * + * @return string + */ + public function getStrategyClassName() { + return $this->strategyClassName; + } + + /** + * Returns the instantiated strategy + * + * @return BlockStrategyInterface + */ + public function getStrategyInstance() { + return $this->strategyInstance; + } + + /** + * Sets the strategy this block / panel should work as. Make sure that you've used + * this method before persisting the block! + * + * @param BlockStrategyInterface $strategy + */ + public function setStrategy(BlockStrategyInterface $strategy) { + $this->strategyInstance = $strategy; + $this->strategyClassName = get_class($strategy); + $strategy->setBlockEntity($this); + } + +Now, the important point is that $strategyClassName is a Doctrine 2 +field, i.e. Doctrine will persist this value. This is only the +class name of your strategy and not an instance! + +Finishing your strategy pattern, we hook into the Doctrine postLoad +event and check whether a block has been loaded. If so, you will +initialize it - i.e. get the strategies classname, create an +instance of it and set it via setStrategyBlock(). + +This might look like this: + +.. code-block:: php + + view = $view; + } + + public function getSubscribedEvents() { + return array(ORM\Events::postLoad); + } + + public function postLoad(ORM\Event\LifecycleEventArgs $args) { + $blockItem = $args->getEntity(); + + // Both blocks and panels are instances of Block\AbstractBlock + if ($blockItem instanceof Block\AbstractBlock) { + $strategy = $blockItem->getStrategyClassName(); + $strategyInstance = new $strategy(); + if (null !== $blockItem->getConfig()) { + $strategyInstance->setConfig($blockItem->getConfig()); + } + $strategyInstance->setView($this->view); + $blockItem->setStrategy($strategyInstance); + } + } + } + +In this example, even some variables are set - like a view object +or a specific configuration object. + + diff --git a/docs/en/cookbook/validation-of-entities.rst b/docs/en/cookbook/validation-of-entities.rst new file mode 100644 index 000000000..a09b218e0 --- /dev/null +++ b/docs/en/cookbook/validation-of-entities.rst @@ -0,0 +1,137 @@ +Validation of Entities +====================== + +.. sectionauthor:: Benjamin Eberlei + +Doctrine 2 does not ship with any internal validators, the reason +being that we think all the frameworks out there already ship with +quite decent ones that can be integrated into your Domain easily. +What we offer are hooks to execute any kind of validation. + +.. note:: + + You don't need to validate your entities in the lifecycle + events. Its only one of many options. Of course you can also + perform validations in value setters or any other method of your + entities that are used in your code. + + +Entities can register lifecycle event methods with Doctrine that +are called on different occasions. For validation we would need to +hook into the events called before persisting and updating. Even +though we don't support validation out of the box, the +implementation is even simpler than in Doctrine 1 and you will get +the additional benefit of being able to re-use your validation in +any other part of your domain. + +Say we have an ``Order`` with several ``OrderLine`` instances. We +never want to allow any customer to order for a larger sum than he +is allowed to: + +.. code-block:: php + + customer->getOrderLimit(); + + $amount = 0; + foreach ($this->orderLines AS $line) { + $amount += $line->getAmount(); + } + + if ($amount > $orderLimit) { + throw new CustomerOrderLimitExceededException(); + } + } + } + +Now this is some pretty important piece of business logic in your +code, enforcing it at any time is important so that customers with +a unknown reputation don't owe your business too much money. + +We can enforce this constraint in any of the metadata drivers. +First Annotations: + +.. code-block:: php + + + + + + + + + + +YAML needs some little change yet, to allow multiple lifecycle +events for one method, this will happen before Beta 1 though. + +Now validation is performed whenever you call +``EntityManager#persist($order)`` or when you call +``EntityManager#flush()`` and an order is about to be updated. Any +Exception that happens in the lifecycle callbacks will be cached by +the EntityManager and the current transaction is rolled back. + +Of course you can do any type of primitive checks, not null, +email-validation, string size, integer and date ranges in your +validation callbacks. + +.. code-block:: php + + plannedShipDate instanceof DateTime)) { + throw new ValidateException(); + } + + if ($this->plannedShipDate->format('U') < time()) { + throw new ValidateException(); + } + + if ($this->customer == null) { + throw new OrderRequiresCustomerException(); + } + } + } + +What is nice about lifecycle events is, you can also re-use the +methods at other places in your domain, for example in combination +with your form library. Additionally there is no limitation in the +number of methods you register on one particular event, i.e. you +can register multiple methods for validation in "PrePersist" or +"PreUpdate" or mix and share them in any combinations between those +two events. + +There is no limit to what you can and can't validate in +"PrePersist" and "PreUpdate" as long as you don't create new entity +instances. This was already discussed in the previous blog post on +the Versionable extension, which requires another type of event +called "onFlush". + +Further readings: :doc:`Lifecycle Events <../reference/events>` diff --git a/docs/en/cookbook/working-with-datetime.rst b/docs/en/cookbook/working-with-datetime.rst new file mode 100644 index 000000000..fc548dac0 --- /dev/null +++ b/docs/en/cookbook/working-with-datetime.rst @@ -0,0 +1,168 @@ +Working with DateTime Instances +=============================== + +There are many nitty gritty details when working with PHPs DateTime instances. You have know their inner +workings pretty well not to make mistakes with date handling. This cookbook entry holds several +interesting pieces of information on how to work with PHP DateTime instances in Doctrine 2. + +DateTime changes are detected by Reference +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When calling ``EntityManager#flush()`` Doctrine computes the changesets of all the currently managed entities +and saves the differences to the database. In case of object properties (@Column(type="datetime") or @Column(type="object")) +these comparisons are always made **BY REFERENCE**. That means the following change will **NOT** be saved into the database: + +.. code-block:: php + + updated->modify("now"); + } + } + +The way to go would be: + +.. code-block:: php + + updated = new \DateTime("now"); + } + } + +Default Timezone Gotcha +~~~~~~~~~~~~~~~~~~~~~~~ + +By default Doctrine assumes that you are working with a default timezone. Each DateTime instance that +is created by Doctrine will be assigned the timezone that is currently the default, either through +the ``date.timezone`` ini setting or by calling ``date_default_timezone_set()``. + +This is very important to handle correctly if your application runs on different serves or is moved from one to another server +(with different timezone settings). You have to make sure that the timezone is the correct one +on all this systems. + +Handling different Timezones with the DateTime Type +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you first come across the requirement to save different you are still optimistic to manage this mess, +however let me crush your expectations fast. There is not a single database out there (supported by Doctrine 2) +that supports timezones correctly. Correctly here means that you can cover all the use-cases that +can come up with timezones. If you don't believe me you should read up on `Storing DateTime +in Databases `_. + +The problem is simple. Not a single database vendor saves the timezone, only the differences to UTC. +However with frequent daylight saving and political timezone changes you can have a UTC offset that moves +in different offset directions depending on the real location. + +The solution for this dilemma is simple. Don't use timezones with DateTime and Doctrine 2. However there is a workaround +that even allows correct date-time handling with timezones: + +1. Always convert any DateTime instance to UTC. +2. Only set Timezones for displaying purposes +3. Save the Timezone in the Entity for persistence. + +Say we have an application for an international postal company and employees insert events regarding postal-package +around the world, in their current timezones. To determine the exact time an event occurred means to save both +the UTC time at the time of the booking and the timezone the event happened in. + +.. code-block:: php + + format($platform->getDateTimeFormatString(), + (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC')) + ); + } + + public function convertToPHPValue($value, AbstractPlatform $platform) + { + if ($value === null) { + return null; + } + + $val = \DateTime::createFromFormat( + $platform->getDateTimeFormatString(), + $value, + (self::$utc) ? self::$utc : (self::$utc = new \DateTimeZone('UTC')) + ); + if (!$val) { + throw ConversionException::conversionFailed($value, $this->getName()); + } + return $val; + } + } + +This database type makes sure that every DateTime instance is always saved in UTC, relative +to the current timezone that the passed DateTime instance has. To be able to transform these values +back into their real timezone you have to save the timezone in a separate field of the entity +requiring timezoned datetimes: + +.. code-block:: php + + localized = true; + $this->created = $createDate; + $this->timezone = $createDate->getTimeZone()->getName(); + } + + public function getCreated() + { + if (!$this->localized) { + $this->created->setTimeZone(new \DateTimeZone($this->timezone)); + } + return $this->created; + } + } + +This snippet makes use of the previously discussed "changeset by reference only" property of +objects. That means a new DateTime will only be used during updating if the reference +changes between retrieval and flush operation. This means we can easily go and modify +the instance by setting the previous local timezone. diff --git a/docs/en/index.rst b/docs/en/index.rst new file mode 100644 index 000000000..9536ee56d --- /dev/null +++ b/docs/en/index.rst @@ -0,0 +1,127 @@ +Welcome to Doctrine 2 ORM's documentation! +========================================== + +The Doctrine documentation is comprised of tutorials, a reference section and +cookbook articles that explain different parts of the Object Relational mapper. + +Doctrine DBAL and Doctrine Common both have their own documentation. + +Getting Help +------------ + +If this documentation is not helping to answer questions you have about +Doctrine ORM don't panic. You can get help from different sources: + +- There is a :doc:`FAQ ` with answers to frequent questions. +- The `Doctrine Mailing List `_ +- Internet Relay Chat (IRC) in `#doctrine on Freenode `_ +- Report a bug on `JIRA `_. +- On `Twitter `_ with ``#doctrine2`` +- On `StackOverflow `_ + +If you need more structure over the different topics you can browse the :doc:`table +of contents `. + +Getting Started +--------------- + +* **Tutorial**: + :doc:`Code First ` | + :doc:`Model First ` | + :doc:`Database First ` + +* **Introduction**: + :doc:`In 10 quick steps ` | + :doc:`Architecture ` + +* **Setup**: + :doc:`Installation ` | + :doc:`Configuration ` | + :doc:`Tools ` + +* :doc:`Limitations and knowns issues ` + +Mapping Objects onto a Database +------------------------------- + +* **Mapping**: + :doc:`Objects ` | + :doc:`Associations ` | + :doc:`Inheritance ` + +* **Drivers**: + :doc:`Docblock Annotations ` | + :doc:`XML ` | + :doc:`YAML ` | + :doc:`PHP ` + +Working with Objects +-------------------- + +* **Basic Reference**: + :doc:`Entities ` | + :doc:`Associations ` | + :doc:`Events ` + +* **Query Reference**: + :doc:`DQL ` | + :doc:`QueryBuilder ` | + :doc:`Native SQL ` + +* **Internals**: + :doc:`Internals explained ` | + :doc:`Associations ` + +Advanced Topics +--------------- + + * :doc:`Transactions and Concurrency ` + * :doc:`Filters ` + * :doc:`NamingStrategy ` + * :doc:`Improving Performance ` + * :doc:`Caching ` + * :doc:`Partial Objects ` + * :doc:`Change Tracking Policies ` + * :doc:`Best Practices ` + * :doc:`Metadata Drivers ` + +Tutorials +--------- + + * :doc:`Indexed associations ` + * :doc:`Extra Lazy Associations ` + * :doc:`Composite Primary Keys ` + * :doc:`Ordered associations ` + * :doc:`Pagination ` + * :doc:`Override Field/Association Mappings In Subclasses ` + +Cookbook +-------- + +* **Patterns**: + :doc:`Aggregate Fields ` | + :doc:`Decorator Pattern ` | + :doc:`Strategy Pattern ` + +* **DQL Extension Points**: + :doc:`DQL Custom Walkers ` | + :doc:`DQL User-Defined-Functions ` + +* **Implementation**: + :doc:`Array Access ` | + :doc:`Notify ChangeTracking Example ` | + :doc:`Using Wakeup Or Clone ` | + :doc:`Working with DateTime ` | + :doc:`Validation ` | + :doc:`Entities in the Session ` + +* **Integration into Frameworks/Libraries** + :doc:`CodeIgniter ` + +* **Hidden Gems** + :doc:`Prefixing Table Name ` + +* **Custom Datatypes** + :doc:`MySQL Enums ` + :doc:`Advanced Field Value Conversion ` + diff --git a/docs/en/make.bat b/docs/en/make.bat new file mode 100644 index 000000000..53c40c912 --- /dev/null +++ b/docs/en/make.bat @@ -0,0 +1,113 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +set SPHINXBUILD=sphinx-build +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% +) + +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. 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. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + 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 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "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. + goto end +) + +if "%1" == "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\Doctrine2ORM.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Doctrine2ORM.ghc + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "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. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/en/reference/annotations-reference.rst b/docs/en/reference/annotations-reference.rst new file mode 100644 index 000000000..f705cb034 --- /dev/null +++ b/docs/en/reference/annotations-reference.rst @@ -0,0 +1,1113 @@ +Annotations Reference +===================== + +In this chapter a reference of every Doctrine 2 Annotation is given +with short explanations on their context and usage. + +Index +----- + +- :ref:`@Column ` +- :ref:`@ColumnResult ` +- :ref:`@ChangeTrackingPolicy ` +- :ref:`@DiscriminatorColumn ` +- :ref:`@DiscriminatorMap ` +- :ref:`@Entity ` +- :ref:`@EntityResult ` +- :ref:`@FieldResult ` +- :ref:`@GeneratedValue ` +- :ref:`@HasLifecycleCallbacks ` +- :ref:`@Index ` +- :ref:`@Id ` +- :ref:`@InheritanceType ` +- :ref:`@JoinColumn ` +- :ref:`@JoinTable ` +- :ref:`@ManyToOne ` +- :ref:`@ManyToMany ` +- :ref:`@MappedSuperclass ` +- :ref:`@NamedNativeQuery ` +- :ref:`@OneToOne ` +- :ref:`@OneToMany ` +- :ref:`@OrderBy ` +- :ref:`@PostLoad ` +- :ref:`@PostPersist ` +- :ref:`@PostRemove ` +- :ref:`@PostUpdate ` +- :ref:`@PrePersist ` +- :ref:`@PreRemove ` +- :ref:`@PreUpdate ` +- :ref:`@SequenceGenerator ` +- :ref:`@SqlResultSetMapping ` +- :ref:`@Table ` +- :ref:`@UniqueConstraint ` +- :ref:`@Version ` + +Reference +--------- + +.. _annref_column: + +@Column +~~~~~~~ + +Marks an annotated instance variable as "persistent". It has to be +inside the instance variables PHP DocBlock comment. Any value hold +inside this variable will be saved to and loaded from the database +as part of the lifecycle of the instance variables entity-class. + +Required attributes: + +- **type**: Name of the Doctrine Type which is converted between PHP + and Database representation. + +Optional attributes: + +- **name**: By default the property name is used for the database + column name also, however the 'name' attribute allows you to + determine the column name. + +- **length**: Used by the "string" type to determine its maximum + length in the database. Doctrine does not validate the length of a + string values for you. + +- **precision**: The precision for a decimal (exact numeric) column + (Applies only for decimal column) + +- **scale**: The scale for a decimal (exact numeric) column (Applies + only for decimal column) + +- **unique**: Boolean value to determine if the value of the column + should be unique across all rows of the underlying entities table. + +- **nullable**: Determines if NULL values allowed for this column. + +- **columnDefinition**: DDL SQL snippet that starts after the column + name and specifies the complete (non-portable!) column definition. + This attribute allows to make use of advanced RMDBS features. + However you should make careful use of this feature and the + consequences. SchemaTool will not detect changes on the column correctly + anymore if you use "columnDefinition". + + Additionally you should remember that the "type" + attribute still handles the conversion between PHP and Database + values. If you use this attribute on a column that is used for + joins between tables you should also take a look at + :ref:`@JoinColumn `. + +Examples: + +.. code-block:: php + + ` +can be found in the configuration section. + +Example: + +.. code-block:: php + + = 2.1) Specifies that this entity is marked as read only and not + considered for change-tracking. Entities of this type can be persisted + and removed though. + +Example: + +.. code-block:: php + + `. This +annotation is optional and only has meaning when used in +conjunction with @Id. + +If this annotation is not specified with @Id the NONE strategy is +used as default. + +Required attributes: + + +- **strategy**: Set the name of the identifier generation strategy. + Valid values are AUTO, SEQUENCE, TABLE, IDENTITY, UUID, CUSTOM and NONE. + +Example: + +.. code-block:: php + + ` annotation on +the entity-class level. It allows to hint the SchemaTool to +generate a database index on the specified table columns. It only +has meaning in the SchemaTool schema generation context. + +Required attributes: + + +- **name**: Name of the Index +- **columns**: Array of columns. + +Example: + +.. code-block:: php + + ` and +:ref:`@DiscriminatorColumn ` annotations. + +Examples: + +.. code-block:: php + + `, :ref:`@OneToOne ` fields +and in the Context of :ref:`@JoinTable ` nested inside +a @ManyToMany. This annotation is not required. If its not +specified the attributes *name* and *referencedColumnName* are +inferred from the table and primary key names. + +Required attributes: + + +- **name**: Column name that holds the foreign key identifier for + this relation. In the context of @JoinTable it specifies the column + name in the join table. +- **referencedColumnName**: Name of the primary key identifier that + is used for joining of this relation. + +Optional attributes: + + +- **unique**: Determines if this relation exclusive between the + affected entities and should be enforced so on the database + constraint level. Defaults to false. +- **nullable**: Determine if the related entity is required, or if + null is an allowed state for the relation. Defaults to true. +- **onDelete**: Cascade Action (Database-level) +- **columnDefinition**: DDL SQL snippet that starts after the column + name and specifies the complete (non-portable!) column definition. + This attribute allows to make use of advanced RMDBS features. Using + this attribute on @JoinColumn is necessary if you need slightly + different column definitions for joining columns, for example + regarding NULL/NOT NULL defaults. However by default a + "columnDefinition" attribute on :ref:`@Column ` also sets + the related @JoinColumn's columnDefinition. This is necessary to + make foreign keys work. + +Example: + +.. code-block:: php + + ` or :ref:`@OneToOne ` +relation with an entity that has multiple identifiers. + +.. _annref_jointable: + +@JoinTable +~~~~~~~~~~~~~~ + +Using :ref:`@OneToMany ` or +:ref:`@ManyToMany ` on the owning side of the relation +requires to specify the @JoinTable annotation which describes the +details of the database join table. If you do not specify +@JoinTable on these relations reasonable mapping defaults apply +using the affected table and the column names. + +Required attributes: + + +- **name**: Database name of the join-table +- **joinColumns**: An array of @JoinColumn annotations describing the + join-relation between the owning entities table and the join table. +- **inverseJoinColumns**: An array of @JoinColumn annotations + describing the join-relation between the inverse entities table and + the join table. + +Example: + +.. code-block:: php + + ` is an +additional, optional annotation that has reasonable default +configuration values using the table and names of the two related +entities. + +Required attributes: + + +- **targetEntity**: FQCN of the referenced target entity. Can be the + unqualified class name if both classes are in the same namespace. + *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- **mappedBy**: This option specifies the property name on the + targetEntity that is the owning side of this relation. Its a + required attribute for the inverse side of a relationship. +- **inversedBy**: The inversedBy attribute designates the field in the + entity that is the inverse side of the relationship. +- **cascade**: Cascade Option +- **fetch**: One of LAZY, EXTRA_LAZY or EAGER +- **indexBy**: Index the collection by a field on the target entity. + +.. note:: + + For ManyToMany bidirectional relationships either side may + be the owning side (the side that defines the @JoinTable and/or + does not make use of the mappedBy attribute, thus using a default + join table). + +Example: + +.. code-block:: php + + `. + +Optional attributes: + + +- **repositoryClass**: (>= 2.2) Specifies the FQCN of a subclass of the EntityRepository. + That will be inherited for all subclasses of that Mapped Superclass. + +Example: + +.. code-block:: php + + ` with one additional option that can +be specified. The configuration defaults for +:ref:`@JoinColumn ` using the target entity table and +primary key column names apply here too. + +Required attributes: + + +- **targetEntity**: FQCN of the referenced target entity. Can be the + unqualified class name if both classes are in the same namespace. + *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- **cascade**: Cascade Option +- **fetch**: One of LAZY or EAGER +- **orphanRemoval**: Boolean that specifies if orphans, inverse + OneToOne entities that are not connected to any owning instance, + should be removed by Doctrine. Defaults to false. +- **inversedBy**: The inversedBy attribute designates the field in the + entity that is the inverse side of the relationship. + +Example: + +.. code-block:: php + + ` or :ref:`@OneToMany ` +annotation to specify by which criteria the collection should be +retrieved from the database by using an ORDER BY clause. + +This annotation requires a single non-attributed value with an DQL +snippet: + +Example: + +.. code-block:: php + + ` annotation on +the entity-class level. It allows to hint the SchemaTool to +generate a database unique constraint on the specified table +columns. It only has meaning in the SchemaTool schema generation +context. + +Required attributes: + + +- **name**: Name of the Index +- **columns**: Array of columns. + +Example: + +.. code-block:: php + + ` annotations that have the type integer or +datetime. Combining @Version with :ref:`@Id ` is not supported. + +Example: + +.. code-block:: php + + `. +- An entity class must not implement ``__wakeup`` or + :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`. + Also consider implementing + `Serializable `_ + instead. +- Any two entity classes in a class hierarchy that inherit + directly or indirectly from one another must not have a mapped + property with the same name. That is, if B inherits from A then B + must not have a mapped field with the same name as an already + mapped field that is inherited from A. +- An entity cannot make use of func_get_args() to implement variable parameters. + Generated proxies do not support this for performance reasons and your code might + actually fail to work when violating this restriction. + +Entities support inheritance, polymorphic associations, and +polymorphic queries. Both abstract and concrete classes can be +entities. Entities may extend non-entity classes as well as entity +classes, and non-entity classes may extend entity classes. + +.. note:: + + The constructor of an entity is only ever invoked when + *you* construct a new instance with the *new* keyword. Doctrine + never calls entity constructors, thus you are free to use them as + you wish and even have it require arguments of any type. + + +Entity states +~~~~~~~~~~~~~ + +An entity instance can be characterized as being NEW, MANAGED, +DETACHED or REMOVED. + + +- A NEW entity instance has no persistent identity, and is not yet + associated with an EntityManager and a UnitOfWork (i.e. those just + created with the "new" operator). +- A MANAGED entity instance is an instance with a persistent + identity that is associated with an EntityManager and whose + persistence is thus managed. +- A DETACHED entity instance is an instance with a persistent + identity that is not (or no longer) associated with an + EntityManager and a UnitOfWork. +- A REMOVED entity instance is an instance with a persistent + identity, associated with an EntityManager, that will be removed + from the database upon transaction commit. + +.. _architecture_persistent_fields: + +Persistent fields +~~~~~~~~~~~~~~~~~ + +The persistent state of an entity is represented by instance +variables. An instance variable must be directly accessed only from +within the methods of the entity by the entity instance itself. +Instance variables must not be accessed by clients of the entity. +The state of the entity is available to clients only through the +entity’s methods, i.e. accessor methods (getter/setter methods) or +other business methods. + +Collection-valued persistent fields and properties must be defined +in terms of the ``Doctrine\Common\Collections\Collection`` +interface. The collection implementation type may be used by the +application to initialize fields or properties before the entity is +made persistent. Once the entity becomes managed (or detached), +subsequent access must be through the interface type. + +Serializing entities +~~~~~~~~~~~~~~~~~~~~ + +Serializing entities can be problematic and is not really +recommended, at least not as long as an entity instance still holds +references to proxy objects or is still managed by an +EntityManager. If you intend to serialize (and unserialize) entity +instances that still hold references to proxy objects you may run +into problems with private properties because of technical +limitations. Proxy objects implement ``__sleep`` and it is not +possible for ``__sleep`` to return names of private properties in +parent classes. On the other hand it is not a solution for proxy +objects to implement ``Serializable`` because Serializable does not +work well with any potential cyclic object references (at least we +did not find a way yet, if you did, please contact us). + +The EntityManager +~~~~~~~~~~~~~~~~~ + +The ``EntityManager`` class is a central access point to the ORM +functionality provided by Doctrine 2. The ``EntityManager`` API is +used to manage the persistence of your objects and to query for +persistent objects. + +Transactional write-behind +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An ``EntityManager`` and the underlying ``UnitOfWork`` employ a +strategy called "transactional write-behind" that delays the +execution of SQL statements in order to execute them in the most +efficient way and to execute them at the end of a transaction so +that all write locks are quickly released. You should see Doctrine +as a tool to synchronize your in-memory objects with the database +in well defined units of work. Work with your objects and modify +them as usual and when you're done call ``EntityManager#flush()`` +to make your changes persistent. + +The Unit of Work +~~~~~~~~~~~~~~~~ + +Internally an ``EntityManager`` uses a ``UnitOfWork``, which is a +typical implementation of the +`Unit of Work pattern `_, +to keep track of all the things that need to be done the next time +``flush`` is invoked. You usually do not directly interact with a +``UnitOfWork`` but with the ``EntityManager`` instead. + + diff --git a/docs/en/reference/association-mapping.rst b/docs/en/reference/association-mapping.rst new file mode 100644 index 000000000..3a597f6db --- /dev/null +++ b/docs/en/reference/association-mapping.rst @@ -0,0 +1,1144 @@ +Association Mapping +=================== + +This chapter introduces association mappings which are used to explain +references between objects and are mapped to a relational database using +foreign keys. + +Instead of working with the foreign keys directly you will always work with +references to objects: + +- A reference to a single object is represented by a foreign key. +- A collection of objects is represented by many foreign keys pointing to the object holding the collection + +This chapter is split into three different sections. + +- A list of all the possible association mapping use-cases is given. +- :ref:`association_mapping_defaults` are explained that simplify the use-case examples. +- :ref:`collections` are introduced that contain entities in associations. + +To master associations you should also learn about :doc:`owning and inverse sides of associations ` + +One-To-One, Unidirectional +-------------------------- + +A unidirectional one-to-one association is very common. Here is an +example of a ``Product`` that has one ``Shipping`` object +associated to it. The ``Shipping`` side does not reference back to +the ``Product`` so it is unidirectional. + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + .. code-block:: yaml + + Product: + type: entity + oneToOne: + shipping: + targetEntity: Shipping + joinColumn: + name: shipping_id + referencedColumnName: id + +Note that the @JoinColumn is not really necessary in this example, +as the defaults would be the same. + +Generated MySQL Schema: + +.. code-block:: sql + + CREATE TABLE Product ( + id INT AUTO_INCREMENT NOT NULL, + shipping_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Shipping ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Product ADD FOREIGN KEY (shipping_id) REFERENCES Shipping(id); + +One-To-One, Bidirectional +------------------------- + +Here is a one-to-one relationship between a ``Customer`` and a +``Cart``. The ``Cart`` has a reference back to the ``Customer`` so +it is bidirectional. + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + .. code-block:: yaml + + Customer: + oneToOne: + cart: + targetEntity: Cart + mappedBy: customer + Cart: + oneToOne: + customer: + targetEntity: Customer + inversedBy: cart + joinColumn: + name: customer_id + referencedColumnName: id + +Note that the @JoinColumn is not really necessary in this example, +as the defaults would be the same. + +Generated MySQL Schema: + +.. code-block:: sql + + CREATE TABLE Cart ( + id INT AUTO_INCREMENT NOT NULL, + customer_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Customer ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Cart ADD FOREIGN KEY (customer_id) REFERENCES Customer(id); + +See how the foreign key is defined on the owning side of the +relation, the table ``Cart``. + +One-To-One, Self-referencing +---------------------------- + +You can easily have self referencing one-to-one relationships like +below. + +.. code-block:: php + + phonenumbers = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + + /** @Entity **/ + class Phonenumber + { + // ... + } + + .. code-block:: xml + + + + + + + + + + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToMany: + phonenumbers: + targetEntity: Phonenumber + joinTable: + name: users_phonenumbers + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + phonenumber_id: + referencedColumnName: id + unique: true + + +Generates the following MySQL Schema: + +.. code-block:: sql + + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE users_phonenumbers ( + user_id INT NOT NULL, + phonenumber_id INT NOT NULL, + UNIQUE INDEX users_phonenumbers_phonenumber_id_uniq (phonenumber_id), + PRIMARY KEY(user_id, phonenumber_id) + ) ENGINE = InnoDB; + + CREATE TABLE Phonenumber ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + ALTER TABLE users_phonenumbers ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE users_phonenumbers ADD FOREIGN KEY (phonenumber_id) REFERENCES Phonenumber(id); + + +Many-To-One, Unidirectional +--------------------------- + +You can easily implement a many-to-one unidirectional association +with the following: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToOne: + address: + targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id + + +.. note:: + + The above ``@JoinColumn`` is optional as it would default + to ``address_id`` and ``id`` anyways. You can omit it and let it + use the defaults. + + +Generated MySQL Schema: + +.. code-block:: sql + + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + address_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + CREATE TABLE Address ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + + ALTER TABLE User ADD FOREIGN KEY (address_id) REFERENCES Address(id); + +One-To-Many, Bidirectional +-------------------------- + +Bidirectional one-to-many associations are very common. The +following code shows an example with a Product and a Feature +class: + +.. configuration-block:: + + .. code-block:: php + + features = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + /** @Entity **/ + class Feature + { + // ... + /** + * @ManyToOne(targetEntity="Product", inversedBy="features") + * @JoinColumn(name="product_id", referencedColumnName="id") + **/ + private $product; + // ... + } + + .. code-block:: xml + + + + + + + + + + + + + .. code-block:: yaml + + Product: + type: entity + oneToMany: + features: + targetEntity: Feature + mappedBy: product + Feature: + type: entity + manyToOne: + product: + targetEntity: Product + inversedBy: features + joinColumn: + name: product_id + referencedColumnName: id + + +Note that the @JoinColumn is not really necessary in this example, +as the defaults would be the same. + +Generated MySQL Schema: + +.. code-block:: sql + + CREATE TABLE Product ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE Feature ( + id INT AUTO_INCREMENT NOT NULL, + product_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Feature ADD FOREIGN KEY (product_id) REFERENCES Product(id); + +One-To-Many, Self-referencing +----------------------------- + +You can also setup a one-to-many association that is +self-referencing. In this example we setup a hierarchy of +``Category`` objects by creating a self referencing relationship. +This effectively models a hierarchy of categories and from the +database perspective is known as an adjacency list approach. + +.. configuration-block:: + + .. code-block:: php + + children = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + .. code-block:: xml + + + + + + + + + .. code-block:: yaml + + Category: + type: entity + oneToMany: + children: + targetEntity: Category + mappedBy: parent + manyToOne: + parent: + targetEntity: Category + inversedBy: children + +Note that the @JoinColumn is not really necessary in this example, +as the defaults would be the same. + +Generated MySQL Schema: + +.. code-block:: sql + + CREATE TABLE Category ( + id INT AUTO_INCREMENT NOT NULL, + parent_id INT DEFAULT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE Category ADD FOREIGN KEY (parent_id) REFERENCES Category(id); + +Many-To-Many, Unidirectional +---------------------------- + +Real many-to-many associations are less common. The following +example shows a unidirectional association between User and Group +entities: + +.. configuration-block:: + + .. code-block:: php + + groups = new \Doctrine\Common\Collections\ArrayCollection(); + } + } + + /** @Entity **/ + class Group + { + // ... + } + + .. code-block:: xml + + + + + + + + + + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToMany: + groups: + targetEntity: Group + joinTable: + name: users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + +Generated MySQL Schema: + +.. code-block:: sql + + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE users_groups ( + user_id INT NOT NULL, + group_id INT NOT NULL, + PRIMARY KEY(user_id, group_id) + ) ENGINE = InnoDB; + CREATE TABLE Group ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + ALTER TABLE users_groups ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE users_groups ADD FOREIGN KEY (group_id) REFERENCES Group(id); + +.. note:: + + Why are many-to-many associations less common? Because + frequently you want to associate additional attributes with an + association, in which case you introduce an association class. + Consequently, the direct many-to-many association disappears and is + replaced by one-to-many/many-to-one associations between the 3 + participating classes. + +Many-To-Many, Bidirectional +--------------------------- + +Here is a similar many-to-many relationship as above except this +one is bidirectional. + +.. configuration-block:: + + .. code-block:: php + + groups = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + + /** @Entity **/ + class Group + { + // ... + /** + * @ManyToMany(targetEntity="User", mappedBy="groups") + **/ + private $users; + + public function __construct() { + $this->users = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + + .. code-block:: xml + + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToMany: + groups: + targetEntity: Group + inversedBy: users + joinTable: + name: users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + + Group: + type: entity + manyToMany: + users: + targetEntity: User + mappedBy: groups + +The MySQL schema is exactly the same as for the Many-To-Many +uni-directional case above. + +Picking Owning and Inverse Side +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For Many-To-Many associations you can chose which entity is the +owning and which the inverse side. There is a very simple semantic +rule to decide which side is more suitable to be the owning side +from a developers perspective. You only have to ask yourself, which +entity is responsible for the connection management and pick that +as the owning side. + +Take an example of two entities ``Article`` and ``Tag``. Whenever +you want to connect an Article to a Tag and vice-versa, it is +mostly the Article that is responsible for this relation. Whenever +you add a new article, you want to connect it with existing or new +tags. Your create Article form will probably support this notion +and allow to specify the tags directly. This is why you should pick +the Article as owning side, as it makes the code more +understandable: + +.. code-block:: php + + addArticle($this); // synchronously updating inverse side + $this->tags[] = $tag; + } + } + + class Tag + { + private $articles; + + public function addArticle(Article $article) + { + $this->articles[] = $article; + } + } + +This allows to group the tag adding on the ``Article`` side of the +association: + +.. code-block:: php + + addTag($tagA); + $article->addTag($tagB); + +Many-To-Many, Self-referencing +------------------------------ + +You can even have a self-referencing many-to-many association. A +common scenario is where a ``User`` has friends and the target +entity of that relationship is a ``User`` so it is self +referencing. In this example it is bidirectional so ``User`` has a +field named ``$friendsWithMe`` and ``$myFriends``. + +.. code-block:: php + + friendsWithMe = new \Doctrine\Common\Collections\ArrayCollection(); + $this->myFriends = new \Doctrine\Common\Collections\ArrayCollection(); + } + + // ... + } + +Generated MySQL Schema: + +.. code-block:: sql + + CREATE TABLE User ( + id INT AUTO_INCREMENT NOT NULL, + PRIMARY KEY(id) + ) ENGINE = InnoDB; + CREATE TABLE friends ( + user_id INT NOT NULL, + friend_user_id INT NOT NULL, + PRIMARY KEY(user_id, friend_user_id) + ) ENGINE = InnoDB; + ALTER TABLE friends ADD FOREIGN KEY (user_id) REFERENCES User(id); + ALTER TABLE friends ADD FOREIGN KEY (friend_user_id) REFERENCES User(id); + +.. _association_mapping_defaults: + +Mapping Defaults +---------------- + +Before we introduce all the association mappings in detail, you +should note that the @JoinColumn and @JoinTable definitions are +usually optional and have sensible default values. The defaults for +a join column in a one-to-one/many-to-one association is as +follows: + +:: + + name: "_id" + referencedColumnName: "id" + +As an example, consider this mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + .. code-block:: yaml + + Product: + type: entity + oneToOne: + shipping: + targetEntity: Shipping + +This is essentially the same as the following, more verbose, +mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + .. code-block:: yaml + + Product: + type: entity + oneToOne: + shipping: + targetEntity: Shipping + joinColumn: + name: shipping_id + referencedColumnName: id + +The @JoinTable definition used for many-to-many mappings has +similar defaults. As an example, consider this mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToMany: + groups: + targetEntity: Group + +This is essentially the same as the following, more verbose, +mapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + + + .. code-block:: yaml + + User: + type: entity + manyToMany: + groups: + targetEntity: Group + joinTable: + name: User_Group + joinColumns: + User_id: + referencedColumnName: id + inverseJoinColumns: + Group_id: + referencedColumnName: id + +In that case, the name of the join table defaults to a combination +of the simple, unqualified class names of the participating +classes, separated by an underscore character. The names of the +join columns default to the simple, unqualified class name of the +targeted class followed by "\_id". The referencedColumnName always +defaults to "id", just as in one-to-one or many-to-one mappings. + +If you accept these defaults, you can reduce the mapping code to a +minimum. + +.. _collections: + +Collections +----------- + +In all the examples of many-valued associations in this manual we +will make use of a ``Collection`` interface and a corresponding +default implementation ``ArrayCollection`` that are defined in the +``Doctrine\Common\Collections`` namespace. Why do we need that? +Doesn't that couple my domain model to Doctrine? Unfortunately, PHP +arrays, while being great for many things, do not make up for good +collections of business objects, especially not in the context of +an ORM. The reason is that plain PHP arrays can not be +transparently extended / instrumented in PHP code, which is +necessary for a lot of advanced ORM features. The classes / +interfaces that come closest to an OO collection are ArrayAccess +and ArrayObject but until instances of these types can be used in +all places where a plain array can be used (something that may +happen in PHP6) their usability is fairly limited. You "can" +type-hint on ``ArrayAccess`` instead of ``Collection``, since the +Collection interface extends ``ArrayAccess``, but this will +severely limit you in the way you can work with the collection, +because the ``ArrayAccess`` API is (intentionally) very primitive +and more importantly because you can not pass this collection to +all the useful PHP array functions, which makes it very hard to +work with. + +.. warning:: + + The Collection interface and ArrayCollection class, + like everything else in the Doctrine namespace, are neither part of + the ORM, nor the DBAL, it is a plain PHP class that has no outside + dependencies apart from dependencies on PHP itself (and the SPL). + Therefore using this class in your domain classes and elsewhere + does not introduce a coupling to the persistence layer. The + Collection class, like everything else in the Common namespace, is + not part of the persistence layer. You could even copy that class + over to your project if you want to remove Doctrine from your + project and all your domain classes will work the same as before. + + + +Initializing Collections +------------------------ + +You have to be careful when using entity fields that contain a +collection of related entities. Say we have a User entity that +contains a collection of groups: + +.. code-block:: php + + groups; + } + } + +With this code alone the ``$groups`` field only contains an +instance of ``Doctrine\Common\Collections\Collection`` if the user +is retrieved from Doctrine, however not after you instantiated a +fresh instance of the User. When your user entity is still new +``$groups`` will obviously be null. + +This is why we recommend to initialize all collection fields to an +empty ``ArrayCollection`` in your entities constructor: + +.. code-block:: php + + groups = new ArrayCollection(); + } + + public function getGroups() + { + return $this->groups; + } + } + +Now the following code will work even if the Entity hasn't +been associated with an EntityManager yet: + +.. code-block:: php + + find('Group', $groupId); + $user = new User(); + $user->getGroups()->add($group); + diff --git a/docs/en/reference/basic-mapping.rst b/docs/en/reference/basic-mapping.rst new file mode 100644 index 000000000..0dcb46f03 --- /dev/null +++ b/docs/en/reference/basic-mapping.rst @@ -0,0 +1,675 @@ +Basic Mapping +============= + +This chapter explains the basic mapping of objects and properties. +Mapping of associations will be covered in the next chapter +"Association Mapping". + +Mapping Drivers +--------------- + +Doctrine provides several different ways for specifying +object-relational mapping metadata: + + +- Docblock Annotations +- XML +- YAML + +This manual usually mentions docblock annotations in all the examples +that are spread throughout all chapters, however for many examples +alternative YAML and XML examples are given as well. There are dedicated +reference chapters for XML and YAML mapping, respectively that explain them +in more detail. There is also an Annotation reference chapter. + +.. note:: + + If you're wondering which mapping driver gives the best + performance, the answer is: They all give exactly the same performance. + Once the metadata of a class has + been read from the source (annotations, xml or yaml) it is stored + in an instance of the ``Doctrine\ORM\Mapping\ClassMetadata`` class + and these instances are stored in the metadata cache. Therefore at + the end of the day all drivers perform equally well. If you're not + using a metadata cache (not recommended!) then the XML driver might + have a slight edge in performance due to the powerful native XML + support in PHP. + + +Introduction to Docblock Annotations +------------------------------------ + +You've probably used docblock annotations in some form already, +most likely to provide documentation metadata for a tool like +``PHPDocumentor`` (@author, @link, ...). Docblock annotations are a +tool to embed metadata inside the documentation section which can +then be processed by some tool. Doctrine 2 generalizes the concept +of docblock annotations so that they can be used for any kind of +metadata and so that it is easy to define new docblock annotations. +In order to allow more involved annotation values and to reduce the +chances of clashes with other docblock annotations, the Doctrine 2 +docblock annotations feature an alternative syntax that is heavily +inspired by the Annotation syntax introduced in Java 5. + +The implementation of these enhanced docblock annotations is +located in the ``Doctrine\Common\Annotations`` namespace and +therefore part of the Common package. Doctrine 2 docblock +annotations support namespaces and nested annotations among other +things. The Doctrine 2 ORM defines its own set of docblock +annotations for supplying object-relational mapping metadata. + +.. note:: + + If you're not comfortable with the concept of docblock + annotations, don't worry, as mentioned earlier Doctrine 2 provides + XML and YAML alternatives and you could easily implement your own + favourite mechanism for defining ORM metadata. + + +Persistent classes +------------------ + +In order to mark a class for object-relational persistence it needs +to be designated as an entity. This can be done through the +``@Entity`` marker annotation. + +.. configuration-block:: + + .. code-block:: php + + + + + + + + .. code-block:: yaml + + MyPersistentClass: + type: entity + # ... + +By default, the entity will be persisted to a table with the same +name as the class name. In order to change that, you can use the +``@Table`` annotation as follows: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + .. code-block:: yaml + + MyPersistentClass: + type: entity + table: my_persistent_class + # ... + +Now instances of MyPersistentClass will be persisted into a table +named ``my_persistent_class``. + +Doctrine Mapping Types +---------------------- + +A Doctrine Mapping Type defines the mapping between a PHP type and +an SQL type. All Doctrine Mapping Types that ship with Doctrine are +fully portable between different RDBMS. You can even write your own +custom mapping types that might or might not be portable, which is +explained later in this chapter. + +For example, the Doctrine Mapping Type ``string`` defines the +mapping from a PHP string to an SQL VARCHAR (or VARCHAR2 etc. +depending on the RDBMS brand). Here is a quick overview of the +built-in mapping types: + + +- ``string``: Type that maps an SQL VARCHAR to a PHP string. +- ``integer``: Type that maps an SQL INT to a PHP integer. +- ``smallint``: Type that maps a database SMALLINT to a PHP + integer. +- ``bigint``: Type that maps a database BIGINT to a PHP string. +- ``boolean``: Type that maps an SQL boolean to a PHP boolean. +- ``decimal``: Type that maps an SQL DECIMAL to a PHP string. +- ``date``: Type that maps an SQL DATETIME to a PHP DateTime + object. +- ``time``: Type that maps an SQL TIME to a PHP DateTime object. +- ``datetime``: Type that maps an SQL DATETIME/TIMESTAMP to a PHP + DateTime object. +- ``text``: Type that maps an SQL CLOB to a PHP string. +- ``object``: Type that maps a SQL CLOB to a PHP object using + ``serialize()`` and ``unserialize()`` +- ``array``: Type that maps a SQL CLOB to a PHP array using + ``serialize()`` and ``unserialize()`` +- ``float``: Type that maps a SQL Float (Double Precision) to a + PHP double. *IMPORTANT*: Works only with locale settings that use + decimal points as separator. +- ``guid``: Type that maps a database GUID/UUID to a PHP string. Defaults to + varchar but uses a specific type if the platform supports it. + +.. note:: + + Doctrine Mapping Types are NOT SQL types and NOT PHP + types! They are mapping types between 2 types. + Additionally Mapping types are *case-sensitive*. For example, using + a DateTime column will NOT match the datetime type that ships with + Doctrine 2. + +.. note:: + + DateTime and Object types are compared by reference, not by value. Doctrine updates this values + if the reference changes and therefore behaves as if these objects are immutable value objects. + +.. warning:: + + All Date types assume that you are exclusively using the default timezone + set by `date_default_timezone_set() `_ + or by the php.ini configuration ``date.timezone``. Working with + different timezones will cause troubles and unexpected behavior. + + If you need specific timezone handling you have to handle this + in your domain, converting all the values back and forth from UTC. + There is also a :doc:`cookbook entry <../cookbook/working-with-datetime>` + on working with datetimes that gives hints for implementing + multi timezone applications. + + +Property Mapping +---------------- + +After a class has been marked as an entity it can specify mappings +for its instance fields. Here we will only look at simple fields +that hold scalar values like strings, numbers, etc. Associations to +other objects are covered in the chapter "Association Mapping". + +To mark a property for relational persistence the ``@Column`` +docblock annotation is used. This annotation usually requires at +least 1 attribute to be set, the ``type``. The ``type`` attribute +specifies the Doctrine Mapping Type to use for the field. If the +type is not specified, 'string' is used as the default mapping type +since it is the most flexible. + +Example: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + .. code-block:: yaml + + MyPersistentClass: + type: entity + fields: + id: + type: integer + name: + length: 50 + +In that example we mapped the field ``id`` to the column ``id`` +using the mapping type ``integer`` and the field ``name`` is mapped +to the column ``name`` with the default mapping type ``string``. As +you can see, by default the column names are assumed to be the same +as the field names. To specify a different name for the column, you +can use the ``name`` attribute of the Column annotation as +follows: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + .. code-block:: yaml + + MyPersistentClass: + type: entity + fields: + name: + length: 50 + column: db_name + +The Column annotation has some more attributes. Here is a complete +list: + + +- ``type``: (optional, defaults to 'string') The mapping type to + use for the column. +- ``column``: (optional, defaults to field name) The name of the + column in the database. +- ``length``: (optional, default 255) The length of the column in + the database. (Applies only if a string-valued column is used). +- ``unique``: (optional, default FALSE) Whether the column is a + unique key. +- ``nullable``: (optional, default FALSE) Whether the database + column is nullable. +- ``precision``: (optional, default 0) The precision for a decimal + (exact numeric) column. (Applies only if a decimal column is used.) +- ``scale``: (optional, default 0) The scale for a decimal (exact + numeric) column. (Applies only if a decimal column is used.) + +.. _reference-basic-mapping-custom-mapping-types: + +Custom Mapping Types +-------------------- + +Doctrine allows you to create new mapping types. This can come in +handy when you're missing a specific mapping type or when you want +to replace the existing implementation of a mapping type. + +In order to create a new mapping type you need to subclass +``Doctrine\DBAL\Types\Type`` and implement/override the methods as +you wish. Here is an example skeleton of such a custom type class: + +.. code-block:: php + + getConnection(); + $conn->getDatabasePlatform()->registerDoctrineTypeMapping('db_mytype', 'mytype'); + +Now using Schema-Tool, whenever it detects a column having the +``db_mytype`` it will convert it into a ``mytype`` Doctrine Type +instance for Schema representation. Keep in mind that you can +easily produce clashes this way, each database type can only map to +exactly one Doctrine mapping type. + +Custom ColumnDefinition +----------------------- + +You can define a custom definition for each column using the "columnDefinition" +attribute of ``@Column``. You have to define all the definitions that follow +the name of a column here. + +.. note:: + + Using columnDefinition will break change-detection in SchemaTool. + +Identifiers / Primary Keys +-------------------------- + +Every entity class needs an identifier/primary key. You designate +the field that serves as the identifier with the ``@Id`` marker +annotation. Here is an example: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + .. code-block:: yaml + + MyPersistentClass: + type: entity + id: + id: + type: integer + fields: + name: + length: 50 + +Without doing anything else, the identifier is assumed to be +manually assigned. That means your code would need to properly set +the identifier property before passing a new entity to +``EntityManager#persist($entity)``. + +A common alternative strategy is to use a generated value as the +identifier. To do this, you use the ``@GeneratedValue`` annotation +like this: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + .. code-block:: yaml + + MyPersistentClass: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + length: 50 + +This tells Doctrine to automatically generate a value for the +identifier. How this value is generated is specified by the +``strategy`` attribute, which is optional and defaults to 'AUTO'. A +value of ``AUTO`` tells Doctrine to use the generation strategy +that is preferred by the currently used database platform. See +below for details. + +Identifier Generation Strategies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The previous example showed how to use the default identifier +generation strategy without knowing the underlying database with +the AUTO-detection strategy. It is also possible to specify the +identifier generation strategy more explicitly, which allows to +make use of some additional features. + +Here is the list of possible generation strategies: + + +- ``AUTO`` (default): Tells Doctrine to pick the strategy that is + preferred by the used database platform. The preferred strategies + are IDENTITY for MySQL, SQLite and MsSQL and SEQUENCE for Oracle + and PostgreSQL. This strategy provides full portability. +- ``SEQUENCE``: Tells Doctrine to use a database sequence for ID + generation. This strategy does currently not provide full + portability. Sequences are supported by Oracle and PostgreSql. +- ``IDENTITY``: Tells Doctrine to use special identity columns in + the database that generate a value on insertion of a row. This + strategy does currently not provide full portability and is + supported by the following platforms: MySQL/SQLite + (AUTO\_INCREMENT), MSSQL (IDENTITY) and PostgreSQL (SERIAL). +- ``TABLE``: Tells Doctrine to use a separate table for ID + generation. This strategy provides full portability. + ***This strategy is not yet implemented!*** +- ``NONE``: Tells Doctrine that the identifiers are assigned (and + thus generated) by your code. The assignment must take place before + a new entity is passed to ``EntityManager#persist``. NONE is the + same as leaving off the @GeneratedValue entirely. + +Sequence Generator +^^^^^^^^^^^^^^^^^^ + +The Sequence Generator can currently be used in conjunction with +Oracle or Postgres and allows some additional configuration options +besides specifying the sequence's name: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + .. code-block:: yaml + + MyPersistentClass: + type: entity + id: + id: + type: integer + generator: + strategy: SEQUENCE + sequenceGenerator: + sequenceName: tablename_seq + allocationSize: 100 + initialValue: 1 + +The initial value specifies at which value the sequence should +start. + +The allocationSize is a powerful feature to optimize INSERT +performance of Doctrine. The allocationSize specifies by how much +values the sequence is incremented whenever the next value is +retrieved. If this is larger than 1 (one) Doctrine can generate +identifier values for the allocationSizes amount of entities. In +the above example with ``allocationSize=100`` Doctrine 2 would only +need to access the sequence once to generate the identifiers for +100 new entities. + +*The default allocationSize for a @SequenceGenerator is currently 10.* + +.. caution:: + + The allocationSize is detected by SchemaTool and + transformed into an "INCREMENT BY " clause in the CREATE SEQUENCE + statement. For a database schema created manually (and not + SchemaTool) you have to make sure that the allocationSize + configuration option is never larger than the actual sequences + INCREMENT BY value, otherwise you may get duplicate keys. + + +.. note:: + + It is possible to use strategy="AUTO" and at the same time + specifying a @SequenceGenerator. In such a case, your custom + sequence settings are used in the case where the preferred strategy + of the underlying platform is SEQUENCE, such as for Oracle and + PostgreSQL. + + +Composite Keys +~~~~~~~~~~~~~~ + +Doctrine 2 allows to use composite primary keys. There are however +some restrictions opposed to using a single identifier. The use of +the ``@GeneratedValue`` annotation is only supported for simple +(not composite) primary keys, which means you can only use +composite keys if you generate the primary key values yourself +before calling ``EntityManager#persist()`` on the entity. + +To designate a composite primary key / identifier, simply put the +@Id marker annotation on all fields that make up the primary key. + +Quoting Reserved Words +---------------------- + +It may sometimes be necessary to quote a column or table name +because it conflicts with a reserved word of the particular RDBMS +in use. This is often referred to as "Identifier Quoting". To let +Doctrine know that you would like a table or column name to be +quoted in all SQL statements, enclose the table or column name in +backticks. Here is an example: + +.. code-block:: php + + setStatus('user'); + $user->setUsername('user' . $i); + $user->setName('Mr.Smith-' . $i); + $em->persist($user); + if (($i % $batchSize) == 0) { + $em->flush(); + $em->clear(); // Detaches all objects from Doctrine! + } + } + +Bulk Updates +------------ + +There are 2 possibilities for bulk updates with Doctrine. + +DQL UPDATE +~~~~~~~~~~ + +The by far most efficient way for bulk updates is to use a DQL +UPDATE query. Example: + +.. code-block:: php + + createQuery('update MyProject\Model\Manager m set m.salary = m.salary * 0.9'); + $numUpdated = $q->execute(); + +Iterating results +~~~~~~~~~~~~~~~~~ + +An alternative solution for bulk updates is to use the +``Query#iterate()`` facility to iterate over the query results step +by step instead of loading the whole result into memory at once. +The following example shows how to do this, combining the iteration +with the batching strategy that was already used for bulk inserts: + +.. code-block:: php + + createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach($iterableResult AS $row) { + $user = $row[0]; + $user->increaseCredit(); + $user->calculateNewBonuses(); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all updates. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + +.. note:: + + Iterating results is not possible with queries that + fetch-join a collection-valued association. The nature of such SQL + result sets is not suitable for incremental hydration. + + +Bulk Deletes +------------ + +There are two possibilities for bulk deletes with Doctrine. You can +either issue a single DQL DELETE query or you can iterate over +results removing them one at a time. + +DQL DELETE +~~~~~~~~~~ + +The by far most efficient way for bulk deletes is to use a DQL +DELETE query. + +Example: + +.. code-block:: php + + createQuery('delete from MyProject\Model\Manager m where m.salary > 100000'); + $numDeleted = $q->execute(); + +Iterating results +~~~~~~~~~~~~~~~~~ + +An alternative solution for bulk deletes is to use the +``Query#iterate()`` facility to iterate over the query results step +by step instead of loading the whole result into memory at once. +The following example shows how to do this: + +.. code-block:: php + + createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + while (($row = $iterableResult->next()) !== false) { + $em->remove($row[0]); + if (($i % $batchSize) == 0) { + $em->flush(); // Executes all deletions. + $em->clear(); // Detaches all objects from Doctrine! + } + ++$i; + } + +.. note:: + + Iterating results is not possible with queries that + fetch-join a collection-valued association. The nature of such SQL + result sets is not suitable for incremental hydration. + + +Iterating Large Results for Data-Processing +------------------------------------------- + +You can use the ``iterate()`` method just to iterate over a large +result and no UPDATE or DELETE intention. The ``IterableResult`` +instance returned from ``$query->iterate()`` implements the +Iterator interface so you can process a large result without memory +problems using the following approach: + +.. code-block:: php + + _em->createQuery('select u from MyProject\Model\User u'); + $iterableResult = $q->iterate(); + foreach ($iterableResult AS $row) { + // do stuff with the data in the row, $row[0] is always the object + + // detach from Doctrine, so that it can be Garbage-Collected immediately + $this->_em->detach($row[0]); + } + +.. note:: + + Iterating results is not possible with queries that + fetch-join a collection-valued association. The nature of such SQL + result sets is not suitable for incremental hydration. + + + diff --git a/docs/en/reference/best-practices.rst b/docs/en/reference/best-practices.rst new file mode 100644 index 000000000..f58291d18 --- /dev/null +++ b/docs/en/reference/best-practices.rst @@ -0,0 +1,127 @@ +Best Practices +============== + +The best practices mentioned here that affect database +design generally refer to best practices when working with Doctrine +and do not necessarily reflect best practices for database design +in general. + + +Don't use public properties on entities +--------------------------------------- + +It is very important that you don't map public properties on +entities, but only protected or private ones. The reason for this +is simple, whenever you access a public property of a proxy object +that hasn't been initialized yet the return value will be null. +Doctrine cannot hook into this process and magically make the +entity lazy load. + +This can create situations where it is very hard to debug the +current failure. We therefore urge you to map only private and +protected properties on entities and use getter methods or magic +\_\_get() to access them. + +Constrain relationships as much as possible +------------------------------------------- + +It is important to constrain relationships as much as possible. +This means: + + +- Impose a traversal direction (avoid bidirectional associations + if possible) +- Eliminate nonessential associations + +This has several benefits: + + +- Reduced coupling in your domain model +- Simpler code in your domain model (no need to maintain + bidirectionality properly) +- Less work for Doctrine + +Avoid composite keys +-------------------- + +Even though Doctrine fully supports composite keys it is best not +to use them if possible. Composite keys require additional work by +Doctrine and thus have a higher probability of errors. + +Use events judiciously +---------------------- + +The event system of Doctrine is great and fast. Even though making +heavy use of events, especially lifecycle events, can have a +negative impact on the performance of your application. Thus you +should use events judiciously. + +Use cascades judiciously +------------------------ + +Automatic cascades of the persist/remove/merge/etc. operations are +very handy but should be used wisely. Do NOT simply add all +cascades to all associations. Think about which cascades actually +do make sense for you for a particular association, given the +scenarios it is most likely used in. + +Don't use special characters +---------------------------- + +Avoid using any non-ASCII characters in class, field, table or +column names. Doctrine itself is not unicode-safe in many places +and will not be until PHP itself is fully unicode-aware (PHP6). + +Don't use identifier quoting +---------------------------- + +Identifier quoting is a workaround for using reserved words that +often causes problems in edge cases. Do not use identifier quoting +and avoid using reserved words as table or column names. + +Initialize collections in the constructor +----------------------------------------- + +It is recommended best practice to initialize any business +collections in entities in the constructor. Example: + +.. code-block:: php + + addresses = new ArrayCollection; + $this->articles = new ArrayCollection; + } + } + +Don't map foreign keys to fields in an entity +--------------------------------------------- + +Foreign keys have no meaning whatsoever in an object model. Foreign +keys are how a relational database establishes relationships. Your +object model establishes relationships through object references. +Thus mapping foreign keys to object fields heavily leaks details of +the relational model into the object model, something you really +should not do. + +Use explicit transaction demarcation +------------------------------------ + +While Doctrine will automatically wrap all DML operations in a +transaction on flush(), it is considered best practice to +explicitly set the transaction boundaries yourself. Otherwise every +single query is wrapped in a small transaction (Yes, SELECT +queries, too) since you can not talk to your database outside of a +transaction. While such short transactions for read-only (SELECT) +queries generally don't have any noticeable performance impact, it +is still preferable to use fewer, well-defined transactions that +are established through explicit transaction boundaries. + + diff --git a/docs/en/reference/caching.rst b/docs/en/reference/caching.rst new file mode 100644 index 000000000..a008aa558 --- /dev/null +++ b/docs/en/reference/caching.rst @@ -0,0 +1,493 @@ +Caching +======= + +Doctrine provides cache drivers in the ``Common`` package for some +of the most popular caching implementations such as APC, Memcache +and Xcache. We also provide an ``ArrayCache`` driver which stores +the data in a PHP array. Obviously, the cache does not live between +requests but this is useful for testing in a development +environment. + +Cache Drivers +------------- + +The cache drivers follow a simple interface that is defined in +``Doctrine\Common\Cache\Cache``. All the cache drivers extend a +base class ``Doctrine\Common\Cache\AbstractCache`` which implements +the before mentioned interface. + +The interface defines the following methods for you to publicly +use. + + +- fetch($id) - Fetches an entry from the cache. +- contains($id) - Test if an entry exists in the cache. +- save($id, $data, $lifeTime = false) - Puts data into the cache. +- delete($id) - Deletes a cache entry. + +Each driver extends the ``AbstractCache`` class which defines a few +abstract protected methods that each of the drivers must +implement. + + +- \_doFetch($id) +- \_doContains($id) +- \_doSave($id, $data, $lifeTime = false) +- \_doDelete($id) + +The public methods ``fetch()``, ``contains()``, etc. utilize the +above protected methods that are implemented by the drivers. The +code is organized this way so that the protected methods in the +drivers do the raw interaction with the cache implementation and +the ``AbstractCache`` can build custom functionality on top of +these methods. + +APC +~~~ + +In order to use the APC cache driver you must have it compiled and +enabled in your php.ini. You can read about APC +`in the PHP Documentation `_. It will give +you a little background information about what it is and how you +can use it as well as how to install it. + +Below is a simple example of how you could use the APC cache driver +by itself. + +.. code-block:: php + + save('cache_id', 'my_data'); + +Memcache +~~~~~~~~ + +In order to use the Memcache cache driver you must have it compiled +and enabled in your php.ini. You can read about Memcache +` on the PHP website `_. It will +give you a little background information about what it is and how +you can use it as well as how to install it. + +Below is a simple example of how you could use the Memcache cache +driver by itself. + +.. code-block:: php + + connect('memcache_host', 11211); + + $cacheDriver = new \Doctrine\Common\Cache\MemcacheCache(); + $cacheDriver->setMemcache($memcache); + $cacheDriver->save('cache_id', 'my_data'); + +Xcache +~~~~~~ + +In order to use the Xcache cache driver you must have it compiled +and enabled in your php.ini. You can read about Xcache +`here `_. It will give you a little +background information about what it is and how you can use it as +well as how to install it. + +Below is a simple example of how you could use the Xcache cache +driver by itself. + +.. code-block:: php + + save('cache_id', 'my_data'); + +Redis +~~~~~ + +In order to use the Redis cache driver you must have it compiled +and enabled in your php.ini. You can read about what is Redis +`from here `_. Also check +`here `_ for how you can use +and install Redis PHP extension. + +Below is a simple example of how you could use the Redis cache +driver by itself. + +.. code-block:: php + + connect('redis_host', 6379); + + $cacheDriver = new \Doctrine\Common\Cache\RedisCache(); + $cacheDriver->setRedis($redis); + $cacheDriver->save('cache_id', 'my_data'); + +Using Cache Drivers +------------------- + +In this section we'll describe how you can fully utilize the API of +the cache drivers to save cache, check if some cache exists, fetch +the cached data and delete the cached data. We'll use the +``ArrayCache`` implementation as our example here. + +.. code-block:: php + + save('cache_id', 'my_data'); + +The ``save()`` method accepts three arguments which are described +below. + + +- ``$id`` - The cache id +- ``$data`` - The cache entry/data. +- ``$lifeTime`` - The lifetime. If != false, sets a specific + lifetime for this cache entry (null => infinite lifeTime). + +You can save any type of data whether it be a string, array, +object, etc. + +.. code-block:: php + + 'value1', + 'key2' => 'value2' + ); + $cacheDriver->save('my_array', $array); + +Checking +~~~~~~~~ + +Checking whether some cache exists is very simple, just use the +``contains()`` method. It accepts a single argument which is the ID +of the cache entry. + +.. code-block:: php + + contains('cache_id')) { + echo 'cache exists'; + } else { + echo 'cache does not exist'; + } + +Fetching +~~~~~~~~ + +Now if you want to retrieve some cache entry you can use the +``fetch()`` method. It also accepts a single argument just like +``contains()`` which is the ID of the cache entry. + +.. code-block:: php + + fetch('my_array'); + +Deleting +~~~~~~~~ + +As you might guess, deleting is just as easy as saving, checking +and fetching. We have a few ways to delete cache entries. You can +delete by an individual ID, regular expression, prefix, suffix or +you can delete all entries. + +By Cache ID +^^^^^^^^^^^ + +.. code-block:: php + + delete('my_array'); + +You can also pass wild cards to the ``delete()`` method and it will +return an array of IDs that were matched and deleted. + +.. code-block:: php + + delete('users_*'); + +By Regular Expression +^^^^^^^^^^^^^^^^^^^^^ + +If you need a little more control than wild cards you can use a PHP +regular expression to delete cache entries. + +.. code-block:: php + + deleteByRegex('/users_.*/'); + +By Prefix +^^^^^^^^^ + +Because regular expressions are kind of slow, if simply deleting by +a prefix or suffix is sufficient, it is recommended that you do +that instead of using a regular expression because it will be much +faster if you have many cache entries. + +.. code-block:: php + + deleteByPrefix('users_'); + +By Suffix +^^^^^^^^^ + +Just like we did above with the prefix you can do the same with a +suffix. + +.. code-block:: php + + deleteBySuffix('_my_account'); + +All +^^^ + +If you simply want to delete all cache entries you can do so with +the ``deleteAll()`` method. + +.. code-block:: php + + deleteAll(); + +Counting +~~~~~~~~ + +If you want to count how many entries are stored in the cache +driver instance you can use the ``count()`` method. + +.. code-block:: php + + count(); + +Namespaces +~~~~~~~~~~ + +If you heavily use caching in your application and utilize it in +multiple parts of your application, or use it in different +applications on the same server you may have issues with cache +naming collisions. This can be worked around by using namespaces. +You can set the namespace a cache driver should use by using the +``setNamespace()`` method. + +.. code-block:: php + + setNamespace('my_namespace_'); + +Integrating with the ORM +------------------------ + +The Doctrine ORM package is tightly integrated with the cache +drivers to allow you to improve performance of various aspects of +Doctrine by just simply making some additional configurations and +method calls. + +Query Cache +~~~~~~~~~~~ + +It is highly recommended that in a production environment you cache +the transformation of a DQL query to its SQL counterpart. It +doesn't make sense to do this parsing multiple times as it doesn't +change unless you alter the DQL query. + +This can be done by configuring the query cache implementation to +use on your ORM configuration. + +.. code-block:: php + + setQueryCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Result Cache +~~~~~~~~~~~~ + +The result cache can be used to cache the results of your queries +so that we don't have to query the database or hydrate the data +again after the first time. You just need to configure the result +cache implementation. + +.. code-block:: php + + setResultCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now when you're executing DQL queries you can configure them to use +the result cache. + +.. code-block:: php + + createQuery('select u from \Entities\User u'); + $query->useResultCache(true); + +You can also configure an individual query to use a different +result cache driver. + +.. code-block:: php + + setResultCacheDriver(new \Doctrine\Common\Cache\ApcCache()); + +.. note:: + + Setting the result cache driver on the query will + automatically enable the result cache for the query. If you want to + disable it pass false to ``useResultCache()``. + + :: + + useResultCache(false); + + +If you want to set the time the cache has to live you can use the +``setResultCacheLifetime()`` method. + +.. code-block:: php + + setResultCacheLifetime(3600); + +The ID used to store the result set cache is a hash which is +automatically generated for you if you don't set a custom ID +yourself with the ``setResultCacheId()`` method. + +.. code-block:: php + + setResultCacheId('my_custom_id'); + +You can also set the lifetime and cache ID by passing the values as +the second and third argument to ``useResultCache()``. + +.. code-block:: php + + useResultCache(true, 3600, 'my_custom_id'); + +Metadata Cache +~~~~~~~~~~~~~~ + +Your class metadata can be parsed from a few different sources like +YAML, XML, Annotations, etc. Instead of parsing this information on +each request we should cache it using one of the cache drivers. + +Just like the query and result cache we need to configure it +first. + +.. code-block:: php + + setMetadataCacheImpl(new \Doctrine\Common\Cache\ApcCache()); + +Now the metadata information will only be parsed once and stored in +the cache driver. + +Clearing the Cache +------------------ + +We've already shown you previously how you can use the API of the +cache drivers to manually delete cache entries. For your +convenience we offer a command line task for you to help you with +clearing the query, result and metadata cache. + +From the Doctrine command line you can run the following command. + +.. code-block:: php + + $ ./doctrine clear-cache + +Running this task with no arguments will clear all the cache for +all the configured drivers. If you want to be more specific about +what you clear you can use the following options. + +To clear the query cache use the ``--query`` option. + +.. code-block:: php + + $ ./doctrine clear-cache --query + +To clear the metadata cache use the ``--metadata`` option. + +.. code-block:: php + + $ ./doctrine clear-cache --metadata + +To clear the result cache use the ``--result`` option. + +.. code-block:: php + + $ ./doctrine clear-cache --result + +When you use the ``--result`` option you can use some other options +to be more specific about what queries result sets you want to +clear. + +Just like the API of the cache drivers you can clear based on an +ID, regular expression, prefix or suffix. + +.. code-block:: php + + $ ./doctrine clear-cache --result --id=cache_id + +Or if you want to clear based on a regular expressions. + +.. code-block:: php + + $ ./doctrine clear-cache --result --regex=users_.* + +Or with a prefix. + +.. code-block:: php + + $ ./doctrine clear-cache --result --prefix=users_ + +And finally with a suffix. + +.. code-block:: php + + $ ./doctrine clear-cache --result --suffix=_my_account + +.. note:: + + Using the ``--id``, ``--regex``, etc. options with the + ``--query`` and ``--metadata`` are not allowed as it is not + necessary to be specific about what you clear. You only ever need + to completely clear the cache to remove stale entries. + + +Cache Slams +----------- + +Something to be careful of when utilizing the cache drivers is +cache slams. If you have a heavily trafficked website with some +code that checks for the existence of a cache record and if it does +not exist it generates the information and saves it to the cache. +Now if 100 requests were issued all at the same time and each one +sees the cache does not exist and they all try and insert the same +cache entry it could lock up APC, Xcache, etc. and cause problems. +Ways exist to work around this, like pre-populating your cache and +not letting your users requests populate the cache. + +You can read more about cache slams +`in this blog post `_. + + diff --git a/docs/en/reference/change-tracking-policies.rst b/docs/en/reference/change-tracking-policies.rst new file mode 100644 index 000000000..d0f099895 --- /dev/null +++ b/docs/en/reference/change-tracking-policies.rst @@ -0,0 +1,151 @@ +Change Tracking Policies +======================== + +Change tracking is the process of determining what has changed in +managed entities since the last time they were synchronized with +the database. + +Doctrine provides 3 different change tracking policies, each having +its particular advantages and disadvantages. The change tracking +policy can be defined on a per-class basis (or more precisely, +per-hierarchy). + +Deferred Implicit +~~~~~~~~~~~~~~~~~ + +The deferred implicit policy is the default change tracking policy +and the most convenient one. With this policy, Doctrine detects the +changes by a property-by-property comparison at commit time and +also detects changes to entities or new entities that are +referenced by other managed entities ("persistence by +reachability"). Although the most convenient policy, it can have +negative effects on performance if you are dealing with large units +of work (see "Understanding the Unit of Work"). Since Doctrine +can't know what has changed, it needs to check all managed entities +for changes every time you invoke EntityManager#flush(), making +this operation rather costly. + +Deferred Explicit +~~~~~~~~~~~~~~~~~ + +The deferred explicit policy is similar to the deferred implicit +policy in that it detects changes through a property-by-property +comparison at commit time. The difference is that Doctrine 2 only +considers entities that have been explicitly marked for change detection +through a call to EntityManager#persist(entity) or through a save +cascade. All other entities are skipped. This policy therefore +gives improved performance for larger units of work while +sacrificing the behavior of "automatic dirty checking". + +Therefore, flush() operations are potentially cheaper with this +policy. The negative aspect this has is that if you have a rather +large application and you pass your objects through several layers +for processing purposes and business tasks you may need to track +yourself which entities have changed on the way so you can pass +them to EntityManager#persist(). + +This policy can be configured as follows: + +.. code-block:: php + + _listeners[] = $listener; + } + } + +Then, in each property setter of this class or derived classes, you +need to notify all the ``PropertyChangedListener`` instances. As an +example we add a convenience method on ``MyEntity`` that shows this +behaviour: + +.. code-block:: php + + _listeners) { + foreach ($this->_listeners as $listener) { + $listener->propertyChanged($this, $propName, $oldValue, $newValue); + } + } + } + + public function setData($data) + { + if ($data != $this->data) { + $this->_onPropertyChanged('data', $this->data, $data); + $this->data = $data; + } + } + } + +You have to invoke ``_onPropertyChanged`` inside every method that +changes the persistent state of ``MyEntity``. + +The check whether the new value is different from the old one is +not mandatory but recommended. That way you also have full control +over when you consider a property changed. + +The negative point of this policy is obvious: You need implement an +interface and write some plumbing code. But also note that we tried +hard to keep this notification functionality abstract. Strictly +speaking, it has nothing to do with the persistence layer and the +Doctrine ORM or DBAL. You may find that property notification +events come in handy in many other scenarios as well. As mentioned +earlier, the ``Doctrine\Common`` namespace is not that evil and +consists solely of very small classes and interfaces that have +almost no external dependencies (none to the DBAL and none to the +ORM) and that you can easily take with you should you want to swap +out the persistence layer. This change tracking policy does not +introduce a dependency on the Doctrine DBAL/ORM or the persistence +layer. + +The positive point and main advantage of this policy is its +effectiveness. It has the best performance characteristics of the 3 +policies with larger units of work and a flush() operation is very +cheap when nothing has changed. + + diff --git a/docs/en/reference/configuration.rst b/docs/en/reference/configuration.rst new file mode 100644 index 000000000..65d047b8b --- /dev/null +++ b/docs/en/reference/configuration.rst @@ -0,0 +1,590 @@ +Configuration +============= + +Bootstrapping Doctrine is a relatively simple procedure that +roughly exists of four steps: + +- `Installation ` +- Making sure Doctrine class files can be loaded on demand. +- Obtaining an EntityManager instance. +- Optional: Configuration of the Console Tool + +Class loading +------------- + +Composer +^^^^^^^^ + +Autoloading is taken care of by Composer. You just have to include the composer autoload file in your project: + +.. code-block:: php + + 'pdo_mysql', + 'user' => 'root', + 'password' => '', + 'dbname' => 'foo', + ); + + $config = Setup::createAnnotationMetadataConfiguration($paths, $isDevMode); + $em = EntityManager::create($dbParams, $config); + + // or if you prefer yaml or xml + $config = Setup::createXMLMetadataConfiguration($paths, $isDevMode); + $config = Setup::createYAMLMetadataConfiguration($paths, $isDevMode); + +These setup commands make several assumptions: + +- If `$devMode` is true always use an ``ArrayCache`` and set ``setAutoGenerateProxyClasses(true)``. +- If `$devMode` is false, check for Caches in the order APC, Xcache, Memcache (127.0.0.1:11211), Redis (127.0.0.1:6379) unless `$cache` is passed as fourth argument. +- If `$devMode` is false, set ``setAutoGenerateProxyClasses(false)`` +- If third argument `$proxyDir` is not set, use the systems temporary directory. + +.. note:: + + You can learn more about the connection configuration in the + `Doctrine DBAL connection configuration reference `_. + +Full Configuration Example +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The configuration of the EntityManager requires a +``Doctrine\ORM\Configuration`` instance as well as some database +connection parameters. This example shows all the potential +steps of configuration. + +.. code-block:: php + + setMetadataCacheImpl($cache); + $driverImpl = $config->newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); + $config->setMetadataDriverImpl($driverImpl); + $config->setQueryCacheImpl($cache); + $config->setProxyDir('/path/to/myproject/lib/MyProject/Proxies'); + $config->setProxyNamespace('MyProject\Proxies'); + + if ($applicationMode == "development") { + $config->setAutoGenerateProxyClasses(true); + } else { + $config->setAutoGenerateProxyClasses(false); + } + + $connectionOptions = array( + 'driver' => 'pdo_sqlite', + 'path' => 'database.sqlite' + ); + + $em = EntityManager::create($connectionOptions, $config); + +.. note:: + + Do not use Doctrine without a metadata and query cache! + Doctrine is optimized for working with caches. The main + parts in Doctrine that are optimized for caching are the metadata + mapping information with the metadata cache and the DQL to SQL + conversions with the query cache. These 2 caches require only an + absolute minimum of memory yet they heavily improve the runtime + performance of Doctrine. The recommended cache driver to use with + Doctrine is `APC `_. APC provides you with + an opcode-cache (which is highly recommended anyway) and a very + fast in-memory cache storage that you can use for the metadata and + query caches as seen in the previous code snippet. + +Configuration Options +--------------------- + +The following sections describe all the configuration options +available on a ``Doctrine\ORM\Configuration`` instance. + +Proxy Directory (***REQUIRED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + setProxyDir($dir); + $config->getProxyDir(); + +Gets or sets the directory where Doctrine generates any proxy +classes. For a detailed explanation on proxy classes and how they +are used in Doctrine, refer to the "Proxy Objects" section further +down. + +Proxy Namespace (***REQUIRED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + setProxyNamespace($namespace); + $config->getProxyNamespace(); + +Gets or sets the namespace to use for generated proxy classes. For +a detailed explanation on proxy classes and how they are used in +Doctrine, refer to the "Proxy Objects" section further down. + +Metadata Driver (***REQUIRED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + setMetadataDriverImpl($driver); + $config->getMetadataDriverImpl(); + +Gets or sets the metadata driver implementation that is used by +Doctrine to acquire the object-relational metadata for your +classes. + +There are currently 4 available implementations: + + +- ``Doctrine\ORM\Mapping\Driver\AnnotationDriver`` +- ``Doctrine\ORM\Mapping\Driver\XmlDriver`` +- ``Doctrine\ORM\Mapping\Driver\YamlDriver`` +- ``Doctrine\ORM\Mapping\Driver\DriverChain`` + +Throughout the most part of this manual the AnnotationDriver is +used in the examples. For information on the usage of the XmlDriver +or YamlDriver please refer to the dedicated chapters +``XML Mapping`` and ``YAML Mapping``. + +The annotation driver can be configured with a factory method on +the ``Doctrine\ORM\Configuration``: + +.. code-block:: php + + newDefaultAnnotationDriver('/path/to/lib/MyProject/Entities'); + $config->setMetadataDriverImpl($driverImpl); + +The path information to the entities is required for the annotation +driver, because otherwise mass-operations on all entities through +the console could not work correctly. All of metadata drivers +accept either a single directory as a string or an array of +directories. With this feature a single driver can support multiple +directories of Entities. + +Metadata Cache (***RECOMMENDED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + setMetadataCacheImpl($cache); + $config->getMetadataCacheImpl(); + +Gets or sets the cache implementation to use for caching metadata +information, that is, all the information you supply via +annotations, xml or yaml, so that they do not need to be parsed and +loaded from scratch on every single request which is a waste of +resources. The cache implementation must implement the +``Doctrine\Common\Cache\Cache`` interface. + +Usage of a metadata cache is highly recommended. + +The recommended implementations for production are: + + +- ``Doctrine\Common\Cache\ApcCache`` +- ``Doctrine\Common\Cache\MemcacheCache`` +- ``Doctrine\Common\Cache\XcacheCache`` +- ``Doctrine\Common\Cache\RedisCache`` + +For development you should use the +``Doctrine\Common\Cache\ArrayCache`` which only caches data on a +per-request basis. + +Query Cache (***RECOMMENDED***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + setQueryCacheImpl($cache); + $config->getQueryCacheImpl(); + +Gets or sets the cache implementation to use for caching DQL +queries, that is, the result of a DQL parsing process that includes +the final SQL as well as meta information about how to process the +SQL result set of a query. Note that the query cache does not +affect query results. You do not get stale data. This is a pure +optimization cache without any negative side-effects (except some +minimal memory usage in your cache). + +Usage of a query cache is highly recommended. + +The recommended implementations for production are: + + +- ``Doctrine\Common\Cache\ApcCache`` +- ``Doctrine\Common\Cache\MemcacheCache`` +- ``Doctrine\Common\Cache\XcacheCache`` +- ``Doctrine\Common\Cache\RedisCache`` + +For development you should use the +``Doctrine\Common\Cache\ArrayCache`` which only caches data on a +per-request basis. + +SQL Logger (***Optional***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + setSQLLogger($logger); + $config->getSQLLogger(); + +Gets or sets the logger to use for logging all SQL statements +executed by Doctrine. The logger class must implement the +``Doctrine\DBAL\Logging\SQLLogger`` interface. A simple default +implementation that logs to the standard output using ``echo`` and +``var_dump`` can be found at +``Doctrine\DBAL\Logging\EchoSQLLogger``. + +Auto-generating Proxy Classes (***OPTIONAL***) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +Gets or sets whether proxy classes should be generated +automatically at runtime by Doctrine. If set to ``FALSE``, proxy +classes must be generated manually through the doctrine command +line task ``generate-proxies``. The strongly recommended value for +a production environment is ``FALSE``. + +Development vs Production Configuration +--------------------------------------- + +You should code your Doctrine2 bootstrapping with two different +runtime models in mind. There are some serious benefits of using +APC or Memcache in production. In development however this will +frequently give you fatal errors, when you change your entities and +the cache still keeps the outdated metadata. That is why we +recommend the ``ArrayCache`` for development. + +Furthermore you should have the Auto-generating Proxy Classes +option to true in development and to false in production. If this +option is set to ``TRUE`` it can seriously hurt your script +performance if several proxy classes are re-generated during script +execution. Filesystem calls of that magnitude can even slower than +all the database queries Doctrine issues. Additionally writing a +proxy sets an exclusive file lock which can cause serious +performance bottlenecks in systems with regular concurrent +requests. + +Connection Options +------------------ + +The ``$connectionOptions`` passed as the first argument to +``EntityManager::create()`` has to be either an array or an +instance of ``Doctrine\DBAL\Connection``. If an array is passed it +is directly passed along to the DBAL Factory +``Doctrine\DBAL\DriverManager::getConnection()``. The DBAL +configuration is explained in the +`DBAL section <./../../../../../dbal/2.0/docs/reference/configuration/en>`_. + +Proxy Objects +------------- + +A proxy object is an object that is put in place or used instead of +the "real" object. A proxy object can add behavior to the object +being proxied without that object being aware of it. In Doctrine 2, +proxy objects are used to realize several features but mainly for +transparent lazy-loading. + +Proxy objects with their lazy-loading facilities help to keep the +subset of objects that are already in memory connected to the rest +of the objects. This is an essential property as without it there +would always be fragile partial objects at the outer edges of your +object graph. + +Doctrine 2 implements a variant of the proxy pattern where it +generates classes that extend your entity classes and adds +lazy-loading capabilities to them. Doctrine can then give you an +instance of such a proxy class whenever you request an object of +the class being proxied. This happens in two situations: + +Reference Proxies +~~~~~~~~~~~~~~~~~ + +The method ``EntityManager#getReference($entityName, $identifier)`` +lets you obtain a reference to an entity for which the identifier +is known, without loading that entity from the database. This is +useful, for example, as a performance enhancement, when you want to +establish an association to an entity for which you have the +identifier. You could simply do this: + +.. code-block:: php + + getReference('MyProject\Model\Item', $itemId); + $cart->addItem($item); + +Here, we added an Item to a Cart without loading the Item from the +database. If you invoke any method on the Item instance, it would +fully initialize its state transparently from the database. Here +$item is actually an instance of the proxy class that was generated +for the Item class but your code does not need to care. In fact it +**should not care**. Proxy objects should be transparent to your +code. + +Association proxies +~~~~~~~~~~~~~~~~~~~ + +The second most important situation where Doctrine uses proxy +objects is when querying for objects. Whenever you query for an +object that has a single-valued association to another object that +is configured LAZY, without joining that association in the same +query, Doctrine puts proxy objects in place where normally the +associated object would be. Just like other proxies it will +transparently initialize itself on first access. + +.. note:: + + Joining an association in a DQL or native query + essentially means eager loading of that association in that query. + This will override the 'fetch' option specified in the mapping for + that association, but only for that query. + + +Generating Proxy classes +~~~~~~~~~~~~~~~~~~~~~~~~ + +Proxy classes can either be generated manually through the Doctrine +Console or automatically by Doctrine. The configuration option that +controls this behavior is: + +.. code-block:: php + + setAutoGenerateProxyClasses($bool); + $config->getAutoGenerateProxyClasses(); + +The default value is ``TRUE`` for convenient development. However, +this setting is not optimal for performance and therefore not +recommended for a production environment. To eliminate the overhead +of proxy class generation during runtime, set this configuration +option to ``FALSE``. When you do this in a development environment, +note that you may get class/file not found errors if certain proxy +classes are not available or failing lazy-loads if new methods were +added to the entity class that are not yet in the proxy class. In +such a case, simply use the Doctrine Console to (re)generate the +proxy classes like so: + +.. code-block:: php + + $ ./doctrine orm:generate-proxies + +Autoloading Proxies +------------------- + +When you deserialize proxy objects from the session or any other storage +it is necessary to have an autoloading mechanism in place for these classes. +For implementation reasons Proxy class names are not PSR-0 compliant. This +means that you have to register a special autoloader for these classes: + +.. code-block:: php + + addDriver($xmlDriver, 'Doctrine\Tests\Models\Company'); + $chain->addDriver($yamlDriver, 'Doctrine\Tests\ORM\Mapping'); + +Based on the namespace of the entity the loading of entities is +delegated to the appropriate driver. The chain semantics come from +the fact that the driver loops through all namespaces and matches +the entity class name against the namespace using a +``strpos() === 0`` call. This means you need to order the drivers +correctly if sub-namespaces use different metadata driver +implementations. + + +Default Repository (***OPTIONAL***) +----------------------------------- + +Specifies the FQCN of a subclass of the EntityRepository. +That will be available for all entities without a custom repository class. + +.. code-block:: php + + setDefaultRepositoryClassName($fqcn); + $config->getDefaultRepositoryClassName(); + +The default value is ``Doctrine\ORM\EntityRepository``. +Any repository class must be a subclass of EntityRepository otherwise you got an ORMException + +Setting up the Console +---------------------- + +Doctrine uses the Symfony Console component for generating the command +line interface. You can take a look at the ``bin/doctrine.php`` +script and the ``Doctrine\ORM\Tools\Console\ConsoleRunner`` command +for inspiration how to setup the cli. + +If you installed Doctrine 2 through Composer, then the Doctrine command is +available to you in the bin-dir, by default at ``vendor/.bin/doctrine``. + +If you installed Doctrine 2 through PEAR, the ``doctrine`` command +line tool should already be available to you. + +In general the required code looks like this: + +.. code-block:: php + + setCatchExceptions(true); + $cli->setHelperSet($helperSet); + Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli); + $cli->run(); + diff --git a/docs/en/reference/dql-doctrine-query-language.rst b/docs/en/reference/dql-doctrine-query-language.rst new file mode 100644 index 000000000..8a9229a0a --- /dev/null +++ b/docs/en/reference/dql-doctrine-query-language.rst @@ -0,0 +1,1667 @@ +Doctrine Query Language +=========================== + +DQL stands for Doctrine Query Language and is an Object +Query Language derivate that is very similar to the Hibernate +Query Language (HQL) or the Java Persistence Query Language (JPQL). + +In essence, DQL provides powerful querying capabilities over your +object model. Imagine all your objects lying around in some storage +(like an object database). When writing DQL queries, think about +querying that storage to pick a certain subset of your objects. + +.. note:: + + A common mistake for beginners is to mistake DQL for + being just some form of SQL and therefore trying to use table names + and column names or join arbitrary tables together in a query. You + need to think about DQL as a query language for your object model, + not for your relational schema. + + +DQL is case in-sensitive, except for namespace, class and field +names, which are case sensitive. + +Types of DQL queries +-------------------- + +DQL as a query language has SELECT, UPDATE and DELETE constructs +that map to their corresponding SQL statement types. INSERT +statements are not allowed in DQL, because entities and their +relations have to be introduced into the persistence context +through ``EntityManager#persist()`` to ensure consistency of your +object model. + +DQL SELECT statements are a very powerful way of retrieving parts +of your domain model that are not accessible via associations. +Additionally they allow to retrieve entities and their associations +in one single SQL select statement which can make a huge difference +in performance in contrast to using several queries. + +DQL UPDATE and DELETE statements offer a way to execute bulk +changes on the entities of your domain model. This is often +necessary when you cannot load all the affected entities of a bulk +update into memory. + +SELECT queries +-------------- + +DQL SELECT clause +~~~~~~~~~~~~~~~~~ + +The select clause of a DQL query specifies what appears in the +query result. The composition of all the expressions in the select +clause also influences the nature of the query result. + +Here is an example that selects all users with an age > 20: + +.. code-block:: php + + createQuery('SELECT u FROM MyProject\Model\User u WHERE u.age > 20'); + $users = $query->getResult(); + +Lets examine the query: + + +- ``u`` is a so called identification variable or alias that + refers to the ``MyProject\Model\User`` class. By placing this alias + in the SELECT clause we specify that we want all instances of the + User class that are matched by this query to appear in the query + result. +- The FROM keyword is always followed by a fully-qualified class + name which in turn is followed by an identification variable or + alias for that class name. This class designates a root of our + query from which we can navigate further via joins (explained + later) and path expressions. +- The expression ``u.age`` in the WHERE clause is a path + expression. Path expressions in DQL are easily identified by the + use of the '.' operator that is used for constructing paths. The + path expression ``u.age`` refers to the ``age`` field on the User + class. + +The result of this query would be a list of User objects where all +users are older than 20. + +The SELECT clause allows to specify both class identification +variables that signal the hydration of a complete entity class or +just fields of the entity using the syntax ``u.name``. Combinations +of both are also allowed and it is possible to wrap both fields and +identification values into aggregation and DQL functions. Numerical +fields can be part of computations using mathematical operations. +See the sub-section on +`DQL Functions, Aggregates and Operations <#dqlfn>`_ on more +information. + +Joins +~~~~~ + +A SELECT query can contain joins. There are 2 types of JOINs: +"Regular" Joins and "Fetch" Joins. + +**Regular Joins**: Used to limit the results and/or compute +aggregate values. + +**Fetch Joins**: In addition to the uses of regular joins: Used to +fetch related entities and include them in the hydrated result of a +query. + +There is no special DQL keyword that distinguishes a regular join +from a fetch join. A join (be it an inner or outer join) becomes a +"fetch join" as soon as fields of the joined entity appear in the +SELECT part of the DQL query outside of an aggregate function. +Otherwise its a "regular join". + +Example: + +Regular join of the address: + +.. code-block:: php + + createQuery("SELECT u FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); + +Fetch join of the address: + +.. code-block:: php + + createQuery("SELECT u, a FROM User u JOIN u.address a WHERE a.city = 'Berlin'"); + $users = $query->getResult(); + +When Doctrine hydrates a query with fetch-join it returns the class +in the FROM clause on the root level of the result array. In the +previous example an array of User instances is returned and the +address of each user is fetched and hydrated into the +``User#address`` variable. If you access the address Doctrine does +not need to lazy load the association with another query. + +.. note:: + + Doctrine allows you to walk all the associations between + all the objects in your domain model. Objects that were not already + loaded from the database are replaced with lazy load proxy + instances. Non-loaded Collections are also replaced by lazy-load + instances that fetch all the contained objects upon first access. + However relying on the lazy-load mechanism leads to many small + queries executed against the database, which can significantly + affect the performance of your application. **Fetch Joins** are the + solution to hydrate most or all of the entities that you need in a + single SELECT query. + + +Named and Positional Parameters +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +DQL supports both named and positional parameters, however in +contrast to many SQL dialects positional parameters are specified +with numbers, for example "?1", "?2" and so on. Named parameters +are specified with ":name1", ":name2" and so on. + +When referencing the parameters in ``Query#setParameter($param, $value)`` +both named and positional parameters are used **without** their prefixes. + +DQL SELECT Examples +~~~~~~~~~~~~~~~~~~~ + +This section contains a large set of DQL queries and some +explanations of what is happening. The actual result also depends +on the hydration mode. + +Hydrate all User entities: + +.. code-block:: php + + createQuery('SELECT u FROM MyProject\Model\User u'); + $users = $query->getResult(); // array of User objects + +Retrieve the IDs of all CmsUsers: + +.. code-block:: php + + createQuery('SELECT u.id FROM CmsUser u'); + $ids = $query->getResult(); // array of CmsUser ids + +Retrieve the IDs of all users that have written an article: + +.. code-block:: php + + createQuery('SELECT DISTINCT u.id FROM CmsArticle a JOIN a.user u'); + $ids = $query->getResult(); // array of CmsUser ids + +Retrieve all articles and sort them by the name of the articles +users instance: + +.. code-block:: php + + createQuery('SELECT a FROM CmsArticle a JOIN a.user u ORDER BY u.name ASC'); + $articles = $query->getResult(); // array of CmsArticle objects + +Retrieve the Username and Name of a CmsUser: + +.. code-block:: php + + createQuery('SELECT u.username, u.name FROM CmsUser u'); + $users = $query->getResult(); // array of CmsUser username and name values + echo $users[0]['username']; + +Retrieve a ForumUser and his single associated entity: + +.. code-block:: php + + createQuery('SELECT u, a FROM ForumUser u JOIN u.avatar a'); + $users = $query->getResult(); // array of ForumUser objects with the avatar association loaded + echo get_class($users[0]->getAvatar()); + +Retrieve a CmsUser and fetch join all the phonenumbers he has: + +.. code-block:: php + + createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p'); + $users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded + $phonenumbers = $users[0]->getPhonenumbers(); + +Hydrate a result in Ascending: + +.. code-block:: php + + createQuery('SELECT u FROM ForumUser u ORDER BY u.id ASC'); + $users = $query->getResult(); // array of ForumUser objects + +Or in Descending Order: + +.. code-block:: php + + createQuery('SELECT u FROM ForumUser u ORDER BY u.id DESC'); + $users = $query->getResult(); // array of ForumUser objects + +Using Aggregate Functions: + +.. code-block:: php + + createQuery('SELECT COUNT(u.id) FROM Entities\User u'); + $count = $query->getSingleScalarResult(); + + $query = $em->createQuery('SELECT u, count(g.id) FROM Entities\User u JOIN u.groups g GROUP BY u.id'); + $result = $query->getResult(); + +With WHERE Clause and Positional Parameter: + +.. code-block:: php + + createQuery('SELECT u FROM ForumUser u WHERE u.id = ?1'); + $query->setParameter(1, 321); + $users = $query->getResult(); // array of ForumUser objects + +With WHERE Clause and Named Parameter: + +.. code-block:: php + + createQuery('SELECT u FROM ForumUser u WHERE u.username = :name'); + $query->setParameter('name', 'Bob'); + $users = $query->getResult(); // array of ForumUser objects + +With Nested Conditions in WHERE Clause: + +.. code-block:: php + + createQuery('SELECT u from ForumUser u WHERE (u.username = :name OR u.username = :name2) AND u.id = :id'); + $query->setParameters(array( + 'name' => 'Bob', + 'name2' => 'Alice', + 'id' => 321, + )); + $users = $query->getResult(); // array of ForumUser objects + +With COUNT DISTINCT: + +.. code-block:: php + + createQuery('SELECT COUNT(DISTINCT u.name) FROM CmsUser'); + $users = $query->getResult(); // array of ForumUser objects + +With Arithmetic Expression in WHERE clause: + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u WHERE ((u.id + 5000) * u.id + 3) < 10000000'); + $users = $query->getResult(); // array of ForumUser objects + +Using a LEFT JOIN to hydrate all user-ids and optionally associated +article-ids: + +.. code-block:: php + + createQuery('SELECT u.id, a.id as article_id FROM CmsUser u LEFT JOIN u.articles a'); + $results = $query->getResult(); // array of user ids and every article_id for each user + +Restricting a JOIN clause by additional conditions: + +.. code-block:: php + + createQuery("SELECT u FROM CmsUser u LEFT JOIN u.articles a WITH a.topic LIKE :foo"); + $query->setParameter('foo', '%foo%'); + $users = $query->getResult(); + +Using several Fetch JOINs: + +.. code-block:: php + + createQuery('SELECT u, a, p, c FROM CmsUser u JOIN u.articles a JOIN u.phonenumbers p JOIN a.comments c'); + $users = $query->getResult(); + +BETWEEN in WHERE clause: + +.. code-block:: php + + createQuery('SELECT u.name FROM CmsUser u WHERE u.id BETWEEN ?1 AND ?2'); + $query->setParameter(1, 123); + $query->setParameter(2, 321); + $usernames = $query->getResult(); + +DQL Functions in WHERE clause: + +.. code-block:: php + + createQuery("SELECT u.name FROM CmsUser u WHERE TRIM(u.name) = 'someone'"); + $usernames = $query->getResult(); + +IN() Expression: + +.. code-block:: php + + createQuery('SELECT u.name FROM CmsUser u WHERE u.id IN(46)'); + $usernames = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id IN (1, 2)'); + $users = $query->getResult(); + + $query = $em->createQuery('SELECT u FROM CmsUser u WHERE u.id NOT IN (1)'); + $users = $query->getResult(); + +CONCAT() DQL Function: + +.. code-block:: php + + createQuery("SELECT u.id FROM CmsUser u WHERE CONCAT(u.name, 's') = ?1"); + $query->setParameter(1, 'Jess'); + $ids = $query->getResult(); + + $query = $em->createQuery('SELECT CONCAT(u.id, u.name) FROM CmsUser u WHERE u.id = ?1'); + $query->setParameter(1, 321); + $idUsernames = $query->getResult(); + +EXISTS in WHERE clause with correlated Subquery + +.. code-block:: php + + createQuery('SELECT u.id FROM CmsUser u WHERE EXISTS (SELECT p.phonenumber FROM CmsPhonenumber p WHERE p.user = u.id)'); + $ids = $query->getResult(); + +Get all users who are members of $group. + +.. code-block:: php + + createQuery('SELECT u.id FROM CmsUser u WHERE :groupId MEMBER OF u.groups'); + $query->setParameter('groupId', $group); + $ids = $query->getResult(); + +Get all users that have more than 1 phonenumber + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u WHERE SIZE(u.phonenumbers) > 1'); + $users = $query->getResult(); + +Get all users that have no phonenumber + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u WHERE u.phonenumbers IS EMPTY'); + $users = $query->getResult(); + +Get all instances of a specific type, for use with inheritance +hierarchies: + +.. code-block:: php + + createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF Doctrine\Tests\Models\Company\CompanyEmployee'); + $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u INSTANCE OF ?1'); + $query = $em->createQuery('SELECT u FROM Doctrine\Tests\Models\Company\CompanyPerson u WHERE u NOT INSTANCE OF ?1'); + +Get all users visible on a given website that have chosen certain gender: + +.. code-block:: php + + createQuery('SELECT u FROM User u WHERE u.gender IN (SELECT IDENTITY(agl.gender) FROM Site s JOIN s.activeGenderList agl WHERE s.id = ?1)'); + +IDENTITY() DQL Function when the association has a composite primary key: + +.. code-block:: php + + createQuery('SELECT IDENTITY(c.location, 'latitude') AS latitude, IDENTITY(c.location, 'longitude') AS longitude FROM Checkpoint c WHERE c.user = ?1'); + +Partial Object Syntax +^^^^^^^^^^^^^^^^^^^^^ + +By default when you run a DQL query in Doctrine and select only a +subset of the fields for a given entity, you do not receive objects +back. Instead, you receive only arrays as a flat rectangular result +set, similar to how you would if you were just using SQL directly +and joining some data. + +If you want to select partial objects you can use the ``partial`` +DQL keyword: + +.. code-block:: php + + createQuery('SELECT partial u.{id, username} FROM CmsUser u'); + $users = $query->getResult(); // array of partially loaded CmsUser objects + +You use the partial syntax when joining as well: + +.. code-block:: php + + createQuery('SELECT partial u.{id, username}, partial a.{id, name} FROM CmsUser u JOIN u.articles a'); + $users = $query->getResult(); // array of partially loaded CmsUser objects + +"NEW" Operator Syntax +^^^^^^^^^^^^^^^^^^^^^ + +Using the ``NEW`` operator you can construct DTOs from queries. + +- When using ``SELECT NEW`` you don't need to specify a mapped entity. +- You can specify any PHP class, it's only require that you have a matching constructor in your class. +- This approach involves determining exactly which columns you really need, + and instantiating data-transfer object that containing a constructor with those arguments. + + +If you want to select data-transfer objects you should create a class: + +.. code-block:: php + + createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city) FROM Customer c JOIN c.email e JOIN c.address a'); + $users = $query->getResult(); // array of CustomerDTO + +.. code-block:: php + + createQuery('SELECT NEW CustomerDTO(c.name, e.email, a.city, SUM(o.value)) FROM Customer c JOIN c.email e JOIN c.address a JOIN c.orders o GROUP BY c'); + $users = $query->getResult(); // array of CustomerDTO + +Using INDEX BY +~~~~~~~~~~~~~~ + +The INDEX BY construct is nothing that directly translates into SQL +but that affects object and array hydration. After each FROM and +JOIN clause you specify by which field this class should be indexed +in the result. By default a result is incremented by numerical keys +starting with 0. However with INDEX BY you can specify any other +column to be the key of your result, it really only makes sense +with primary or unique fields though: + +.. code-block:: sql + + SELECT u.id, u.status, upper(u.name) nameUpper FROM User u INDEX BY u.id + JOIN u.phonenumbers p INDEX BY p.phonenumber + +Returns an array of the following kind, indexed by both user-id +then phonenumber-id: + +.. code-block:: php + + array + 0 => + array + 1 => + object(stdClass)[299] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 1 + .. + 'nameUpper' => string 'ROMANB' (length=6) + 1 => + array + 2 => + object(stdClass)[298] + public '__CLASS__' => string 'Doctrine\Tests\Models\CMS\CmsUser' (length=33) + public 'id' => int 2 + ... + 'nameUpper' => string 'JWAGE' (length=5) + +UPDATE queries +-------------- + +DQL not only allows to select your Entities using field names, you +can also execute bulk updates on a set of entities using an +DQL-UPDATE query. The Syntax of an UPDATE query works as expected, +as the following example shows: + +.. code-block:: sql + + UPDATE MyProject\Model\User u SET u.password = 'new' WHERE u.id IN (1, 2, 3) + +References to related entities are only possible in the WHERE +clause and using sub-selects. + +.. warning:: + + DQL UPDATE statements are ported directly into a + Database UPDATE statement and therefore bypass any locking scheme, events + and do not increment the version column. Entities that are already + loaded into the persistence context will *NOT* be synced with the + updated database state. It is recommended to call + ``EntityManager#clear()`` and retrieve new instances of any + affected entity. + + +DELETE queries +-------------- + +DELETE queries can also be specified using DQL and their syntax is +as simple as the UPDATE syntax: + +.. code-block:: sql + + DELETE MyProject\Model\User u WHERE u.id = 4 + +The same restrictions apply for the reference of related entities. + +.. warning:: + + DQL DELETE statements are ported directly into a + Database DELETE statement and therefore bypass any events and checks for the + version column if they are not explicitly added to the WHERE clause + of the query. Additionally Deletes of specifies entities are *NOT* + cascaded to related entities even if specified in the metadata. + + +Functions, Operators, Aggregates +-------------------------------- + +DQL Functions +~~~~~~~~~~~~~ + +The following functions are supported in SELECT, WHERE and HAVING +clauses: + + +- IDENTITY(single\_association\_path\_expression [, fieldMapping]) - Retrieve the foreign key column of association of the owning side +- ABS(arithmetic\_expression) +- CONCAT(str1, str2) +- CURRENT\_DATE() - Return the current date +- CURRENT\_TIME() - Returns the current time +- CURRENT\_TIMESTAMP() - Returns a timestamp of the current date + and time. +- LENGTH(str) - Returns the length of the given string +- LOCATE(needle, haystack [, offset]) - Locate the first + occurrence of the substring in the string. +- LOWER(str) - returns the string lowercased. +- MOD(a, b) - Return a MOD b. +- SIZE(collection) - Return the number of elements in the + specified collection +- SQRT(q) - Return the square-root of q. +- SUBSTRING(str, start [, length]) - Return substring of given + string. +- TRIM([LEADING \| TRAILING \| BOTH] ['trchar' FROM] str) - Trim + the string by the given trim char, defaults to whitespaces. +- UPPER(str) - Return the upper-case of the given string. +- DATE_ADD(date, days, unit) - Add the number of days to a given date. (Supported units are DAY, MONTH) +- DATE_SUB(date, days, unit) - Substract the number of days from a given date. (Supported units are DAY, MONTH) +- DATE_DIFF(date1, date2) - Calculate the difference in days between date1-date2. + +Arithmetic operators +~~~~~~~~~~~~~~~~~~~~ + +You can do math in DQL using numeric values, for example: + +.. code-block:: sql + + SELECT person.salary * 1.5 FROM CompanyPerson person WHERE person.salary < 100000 + +Aggregate Functions +~~~~~~~~~~~~~~~~~~~ + +The following aggregate functions are allowed in SELECT and GROUP +BY clauses: AVG, COUNT, MIN, MAX, SUM + +Other Expressions +~~~~~~~~~~~~~~~~~ + +DQL offers a wide-range of additional expressions that are known +from SQL, here is a list of all the supported constructs: + + +- ``ALL/ANY/SOME`` - Used in a WHERE clause followed by a + sub-select this works like the equivalent constructs in SQL. +- ``BETWEEN a AND b`` and ``NOT BETWEEN a AND b`` can be used to + match ranges of arithmetic values. +- ``IN (x1, x2, ...)`` and ``NOT IN (x1, x2, ..)`` can be used to + match a set of given values. +- ``LIKE ..`` and ``NOT LIKE ..`` match parts of a string or text + using % as a wildcard. +- ``IS NULL`` and ``IS NOT NULL`` to check for null values +- ``EXISTS`` and ``NOT EXISTS`` in combination with a sub-select + +Adding your own functions to the DQL language +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +By default DQL comes with functions that are part of a large basis +of underlying databases. However you will most likely choose a +database platform at the beginning of your project and most likely +never change it. For this cases you can easily extend the DQL +parser with own specialized platform functions. + +You can register custom DQL functions in your ORM Configuration: + +.. code-block:: php + + addCustomStringFunction($name, $class); + $config->addCustomNumericFunction($name, $class); + $config->addCustomDatetimeFunction($name, $class); + + $em = EntityManager::create($dbParams, $config); + +The functions have to return either a string, numeric or datetime +value depending on the registered function type. As an example we +will add a MySQL specific FLOOR() functionality. All the given +classes have to implement the base class : + +.. code-block:: php + + walkSimpleArithmeticExpression( + $this->simpleArithmeticExpression + ) . ')'; + } + + public function parse(\Doctrine\ORM\Query\Parser $parser) + { + $lexer = $parser->getLexer(); + + $parser->match(Lexer::T_IDENTIFIER); + $parser->match(Lexer::T_OPEN_PARENTHESIS); + + $this->simpleArithmeticExpression = $parser->SimpleArithmeticExpression(); + + $parser->match(Lexer::T_CLOSE_PARENTHESIS); + } + } + +We will register the function by calling and can then use it: + +.. code-block:: php + + getConfiguration(); + $config->registerNumericFunction('FLOOR', 'MyProject\Query\MysqlFloor'); + + $dql = "SELECT FLOOR(person.salary * 1.75) FROM CompanyPerson person"; + +Querying Inherited Classes +-------------------------- + +This section demonstrates how you can query inherited classes and +what type of results to expect. + +Single Table +~~~~~~~~~~~~ + +`Single Table Inheritance `_ +is an inheritance mapping strategy where all classes of a hierarchy +are mapped to a single database table. In order to distinguish +which row represents which type in the hierarchy a so-called +discriminator column is used. + +First we need to setup an example set of entities to use. In this +scenario it is a generic Person and Employee example: + +.. code-block:: php + + setName('test'); + $employee->setDepartment('testing'); + $em->persist($employee); + $em->flush(); + +Now lets run a simple query to retrieve the ``Employee`` we just +created: + +.. code-block:: sql + + SELECT e FROM Entities\Employee e WHERE e.name = 'test' + +If we check the generated SQL you will notice it has some special +conditions added to ensure that we will only get back ``Employee`` +entities: + +.. code-block:: sql + + SELECT p0_.id AS id0, p0_.name AS name1, p0_.department AS department2, + p0_.discr AS discr3 FROM Person p0_ + WHERE (p0_.name = ?) AND p0_.discr IN ('employee') + +Class Table Inheritance +~~~~~~~~~~~~~~~~~~~~~~~ + +`Class Table Inheritance `_ +is an inheritance mapping strategy where each class in a hierarchy +is mapped to several tables: its own table and the tables of all +parent classes. The table of a child class is linked to the table +of a parent class through a foreign key constraint. Doctrine 2 +implements this strategy through the use of a discriminator column +in the topmost table of the hierarchy because this is the easiest +way to achieve polymorphic queries with Class Table Inheritance. + +The example for class table inheritance is the same as single +table, you just need to change the inheritance type from +``SINGLE_TABLE`` to ``JOINED``: + +.. code-block:: php + + createQuery('select u from MyProject\Model\User u'); + + // example2: using setDql + $q = $em->createQuery(); + $q->setDql('select u from MyProject\Model\User u'); + +Query Result Formats +~~~~~~~~~~~~~~~~~~~~ + +The format in which the result of a DQL SELECT query is returned +can be influenced by a so-called ``hydration mode``. A hydration +mode specifies a particular way in which an SQL result set is +transformed. Each hydration mode has its own dedicated method on +the Query class. Here they are: + + +- ``Query#getResult()``: Retrieves a collection of objects. The + result is either a plain collection of objects (pure) or an array + where the objects are nested in the result rows (mixed). +- ``Query#getSingleResult()``: Retrieves a single object. If the + result contains more than one or no object, an exception is thrown. The + pure/mixed distinction does not apply. +- ``Query#getOneOrNullResult()``: Retrieve a single object. If no + object is found null will be returned. +- ``Query#getArrayResult()``: Retrieves an array graph (a nested + array) that is largely interchangeable with the object graph + generated by ``Query#getResult()`` for read-only purposes. + + .. note:: + + An array graph can differ from the corresponding object + graph in certain scenarios due to the difference of the identity + semantics between arrays and objects. + + + +- ``Query#getScalarResult()``: Retrieves a flat/rectangular result + set of scalar values that can contain duplicate data. The + pure/mixed distinction does not apply. +- ``Query#getSingleScalarResult()``: Retrieves a single scalar + value from the result returned by the dbms. If the result contains + more than a single scalar value, an exception is thrown. The + pure/mixed distinction does not apply. + +Instead of using these methods, you can alternatively use the +general-purpose method +``Query#execute(array $params = array(), $hydrationMode = Query::HYDRATE_OBJECT)``. +Using this method you can directly supply the hydration mode as the +second parameter via one of the Query constants. In fact, the +methods mentioned earlier are just convenient shortcuts for the +execute method. For example, the method ``Query#getResult()`` +internally invokes execute, passing in ``Query::HYDRATE_OBJECT`` as +the hydration mode. + +The use of the methods mentioned earlier is generally preferred as +it leads to more concise code. + +Pure and Mixed Results +~~~~~~~~~~~~~~~~~~~~~~ + +The nature of a result returned by a DQL SELECT query retrieved +through ``Query#getResult()`` or ``Query#getArrayResult()`` can be +of 2 forms: **pure** and **mixed**. In the previous simple +examples, you already saw a "pure" query result, with only objects. +By default, the result type is **pure** but +**as soon as scalar values, such as aggregate values or other scalar values that do not belong to an entity, appear in the SELECT part of the DQL query, the result becomes mixed**. +A mixed result has a different structure than a pure result in +order to accommodate for the scalar values. + +A pure result usually looks like this: + +.. code-block:: php + + $dql = "SELECT u FROM User u"; + + array + [0] => Object + [1] => Object + [2] => Object + ... + +A mixed result on the other hand has the following general +structure: + +.. code-block:: php + + $dql = "SELECT u, 'some scalar string', count(u.groups) AS num FROM User u JOIN u.groups g GROUP BY u.id"; + + array + [0] + [0] => Object + [1] => "some scalar string" + ['num'] => 42 + // ... more scalar values, either indexed numerically or with a name + [1] + [0] => Object + [1] => "some scalar string" + ['num'] => 42 + // ... more scalar values, either indexed numerically or with a name + +To better understand mixed results, consider the following DQL +query: + +.. code-block:: sql + + SELECT u, UPPER(u.name) nameUpper FROM MyProject\Model\User u + +This query makes use of the ``UPPER`` DQL function that returns a +scalar value and because there is now a scalar value in the SELECT +clause, we get a mixed result. + +Conventions for mixed results are as follows: + + +- The object fetched in the FROM clause is always positioned with the key '0'. +- Every scalar without a name is numbered in the order given in the query, starting with 1. +- Every aliased scalar is given with its alias-name as the key. The case of the name is kept. +- If several objects are fetched from the FROM clause they alternate every row. + + +Here is how the result could look like: + +.. code-block:: php + + array + array + [0] => User (Object) + ['nameUpper'] => "ROMAN" + array + [0] => User (Object) + ['nameUpper'] => "JONATHAN" + ... + +And here is how you would access it in PHP code: + +.. code-block:: php + + getName(); + echo "Name UPPER: " . $row['nameUpper']; + } + +Fetching Multiple FROM Entities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you fetch multiple entities that are listed in the FROM clause then the hydration +will return the rows iterating the different top-level entities. + +.. code-block:: php + + $dql = "SELECT u, g FROM User u, Group g"; + + array + [0] => Object (User) + [1] => Object (Group) + [2] => Object (User) + [3] => Object (Group) + + +Hydration Modes +~~~~~~~~~~~~~~~ + +Each of the Hydration Modes makes assumptions about how the result +is returned to user land. You should know about all the details to +make best use of the different result formats: + +The constants for the different hydration modes are: + + +- Query::HYDRATE\_OBJECT +- Query::HYDRATE\_ARRAY +- Query::HYDRATE\_SCALAR +- Query::HYDRATE\_SINGLE\_SCALAR + +Object Hydration +^^^^^^^^^^^^^^^^ + +Object hydration hydrates the result set into the object graph: + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_OBJECT); + +Array Hydration +^^^^^^^^^^^^^^^ + +You can run the same query with array hydration and the result set +is hydrated into an array that represents the object graph: + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_ARRAY); + +You can use the ``getArrayResult()`` shortcut as well: + +.. code-block:: php + + getArrayResult(); + +Scalar Hydration +^^^^^^^^^^^^^^^^ + +If you want to return a flat rectangular result set instead of an +object graph you can use scalar hydration: + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u'); + $users = $query->getResult(Query::HYDRATE_SCALAR); + echo $users[0]['u_id']; + +The following assumptions are made about selected fields using +Scalar Hydration: + + +1. Fields from classes are prefixed by the DQL alias in the result. + A query of the kind 'SELECT u.name ..' returns a key 'u\_name' in + the result rows. + +Single Scalar Hydration +^^^^^^^^^^^^^^^^^^^^^^^ + +If you a query which returns just a single scalar value you can use +single scalar hydration: + +.. code-block:: php + + createQuery('SELECT COUNT(a.id) FROM CmsUser u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id'); + $query->setParameter(1, 'jwage'); + $numArticles = $query->getResult(Query::HYDRATE_SINGLE_SCALAR); + +You can use the ``getSingleScalarResult()`` shortcut as well: + +.. code-block:: php + + getSingleScalarResult(); + +Custom Hydration Modes +^^^^^^^^^^^^^^^^^^^^^^ + +You can easily add your own custom hydration modes by first +creating a class which extends ``AbstractHydrator``: + +.. code-block:: php + + _stmt->fetchAll(PDO::FETCH_ASSOC); + } + } + +Next you just need to add the class to the ORM configuration: + +.. code-block:: php + + getConfiguration()->addCustomHydrationMode('CustomHydrator', 'MyProject\Hydrators\CustomHydrator'); + +Now the hydrator is ready to be used in your queries: + +.. code-block:: php + + createQuery('SELECT u FROM CmsUser u'); + $results = $query->getResult('CustomHydrator'); + +Iterating Large Result Sets +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are situations when a query you want to execute returns a +very large result-set that needs to be processed. All the +previously described hydration modes completely load a result-set +into memory which might not be feasible with large result sets. See +the `Batch Processing `_ section on details how +to iterate large result sets. + +Functions +~~~~~~~~~ + +The following methods exist on the ``AbstractQuery`` which both +``Query`` and ``NativeQuery`` extend from. + +Parameters +^^^^^^^^^^ + +Prepared Statements that use numerical or named wildcards require +additional parameters to be executable against the database. To +pass parameters to the query the following methods can be used: + + +- ``AbstractQuery::setParameter($param, $value)`` - Set the + numerical or named wildcard to the given value. +- ``AbstractQuery::setParameters(array $params)`` - Set an array + of parameter key-value pairs. +- ``AbstractQuery::getParameter($param)`` +- ``AbstractQuery::getParameters()`` + +Both named and positional parameters are passed to these methods without their ? or : prefix. + +Cache related API +^^^^^^^^^^^^^^^^^ + +You can cache query results based either on all variables that +define the result (SQL, Hydration Mode, Parameters and Hints) or on +user-defined cache keys. However by default query results are not +cached at all. You have to enable the result cache on a per query +basis. The following example shows a complete workflow using the +Result Cache API: + +.. code-block:: php + + createQuery('SELECT u FROM MyProject\Model\User u WHERE u.id = ?1'); + $query->setParameter(1, 12); + + $query->setResultCacheDriver(new ApcCache()); + + $query->useResultCache(true) + ->setResultCacheLifeTime($seconds = 3600); + + $result = $query->getResult(); // cache miss + + $query->expireResultCache(true); + $result = $query->getResult(); // forced expire, cache miss + + $query->setResultCacheId('my_query_result'); + $result = $query->getResult(); // saved in given result cache id. + + // or call useResultCache() with all parameters: + $query->useResultCache(true, $seconds = 3600, 'my_query_result'); + $result = $query->getResult(); // cache hit! + + // Introspection + $queryCacheProfile = $query->getQueryCacheProfile(); + $cacheDriver = $query->getResultCacheDriver(); + $lifetime = $query->getLifetime(); + $key = $query->getCacheKey(); + +.. note:: + + You can set the Result Cache Driver globally on the + ``Doctrine\ORM\Configuration`` instance so that it is passed to + every ``Query`` and ``NativeQuery`` instance. + + +Query Hints +^^^^^^^^^^^ + +You can pass hints to the query parser and hydrators by using the +``AbstractQuery::setHint($name, $value)`` method. Currently there +exist mostly internal query hints that are not be consumed in +userland. However the following few hints are to be used in +userland: + + +- Query::HINT\_FORCE\_PARTIAL\_LOAD - Allows to hydrate objects + although not all their columns are fetched. This query hint can be + used to handle memory consumption problems with large result-sets + that contain char or binary data. Doctrine has no way of implicitly + reloading this data. Partially loaded objects have to be passed to + ``EntityManager::refresh()`` if they are to be reloaded fully from + the database. +- Query::HINT\_REFRESH - This query is used internally by + ``EntityManager::refresh()`` and can be used in userland as well. + If you specify this hint and a query returns the data for an entity + that is already managed by the UnitOfWork, the fields of the + existing entity will be refreshed. In normal operation a result-set + that loads data of an already existing entity is discarded in favor + of the already existing entity. +- Query::HINT\_CUSTOM\_TREE\_WALKERS - An array of additional + ``Doctrine\ORM\Query\TreeWalker`` instances that are attached to + the DQL query parsing process. + +Query Cache (DQL Query Only) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Parsing a DQL query and converting it into an SQL query against the +underlying database platform obviously has some overhead in +contrast to directly executing Native SQL queries. That is why +there is a dedicated Query Cache for caching the DQL parser +results. In combination with the use of wildcards you can reduce +the number of parsed queries in production to zero. + +The Query Cache Driver is passed from the +``Doctrine\ORM\Configuration`` instance to each +``Doctrine\ORM\Query`` instance by default and is also enabled by +default. This also means you don't regularly need to fiddle with +the parameters of the Query Cache, however if you do there are +several methods to interact with it: + + +- ``Query::setQueryCacheDriver($driver)`` - Allows to set a Cache + instance +- ``Query::setQueryCacheLifeTime($seconds = 3600)`` - Set lifetime + of the query caching. +- ``Query::expireQueryCache($bool)`` - Enforce the expiring of the + query cache if set to true. +- ``Query::getExpireQueryCache()`` +- ``Query::getQueryCacheDriver()`` +- ``Query::getQueryCacheLifeTime()`` + +First and Max Result Items (DQL Query Only) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can limit the number of results returned from a DQL query as +well as specify the starting offset, Doctrine then uses a strategy +of manipulating the select query to return only the requested +number of results: + + +- ``Query::setMaxResults($maxResults)`` +- ``Query::setFirstResult($offset)`` + +.. note:: + + If your query contains a fetch-joined collection + specifying the result limit methods are not working as you would + expect. Set Max Results restricts the number of database result + rows, however in the case of fetch-joined collections one root + entity might appear in many rows, effectively hydrating less than + the specified number of results. + +.. _dql-temporarily-change-fetch-mode: + +Temporarily change fetch mode in DQL +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +While normally all your associations are marked as lazy or extra lazy you will have cases where you are using DQL and don't want to +fetch join a second, third or fourth level of entities into your result, because of the increased cost of the SQL JOIN. You +can mark a many-to-one or one-to-one association as fetched temporarily to batch fetch these entities using a WHERE .. IN query. + +.. code-block:: php + + createQuery("SELECT u FROM MyProject\User u"); + $query->setFetchMode("MyProject\User", "address", "EAGER"); + $query->execute(); + +Given that there are 10 users and corresponding addresses in the database the executed queries will look something like: + +.. code-block:: sql + + SELECT * FROM users; + SELECT * FROM address WHERE id IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10); + + +EBNF +---- + +The following context-free grammar, written in an EBNF variant, +describes the Doctrine Query Language. You can consult this grammar +whenever you are unsure about what is possible with DQL or what the +correct syntax for a particular query should be. + +Document syntax: +~~~~~~~~~~~~~~~~ + + +- non-terminals begin with an upper case character +- terminals begin with a lower case character +- parentheses (...) are used for grouping +- square brackets [...] are used for defining an optional part, + e.g. zero or one time +- curly brackets {...} are used for repetition, e.g. zero or more + times +- double quotation marks "..." define a terminal string a vertical + bar \| represents an alternative + +Terminals +~~~~~~~~~ + + +- identifier (name, email, ...) +- string ('foo', 'bar''s house', '%ninja%', ...) +- char ('/', '\\', ' ', ...) +- integer (-1, 0, 1, 34, ...) +- float (-0.23, 0.007, 1.245342E+8, ...) +- boolean (false, true) + +Query Language +~~~~~~~~~~~~~~ + +.. code-block:: php + + QueryLanguage ::= SelectStatement | UpdateStatement | DeleteStatement + +Statements +~~~~~~~~~~ + +.. code-block:: php + + SelectStatement ::= SelectClause FromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + UpdateStatement ::= UpdateClause [WhereClause] + DeleteStatement ::= DeleteClause [WhereClause] + +Identifiers +~~~~~~~~~~~ + +.. code-block:: php + + /* Alias Identification usage (the "u" of "u.name") */ + IdentificationVariable ::= identifier + + /* Alias Identification declaration (the "u" of "FROM User u") */ + AliasIdentificationVariable :: = identifier + + /* identifier that must be a class name (the "User" of "FROM User u") */ + AbstractSchemaName ::= identifier + + /* identifier that must be a field (the "name" of "u.name") */ + /* This is responsible to know if the field exists in Object, no matter if it's a relation or a simple field */ + FieldIdentificationVariable ::= identifier + + /* identifier that must be a collection-valued association field (to-many) (the "Phonenumbers" of "u.Phonenumbers") */ + CollectionValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be a single-valued association field (to-one) (the "Group" of "u.Group") */ + SingleValuedAssociationField ::= FieldIdentificationVariable + + /* identifier that must be an embedded class state field (for the future) */ + EmbeddedClassStateField ::= FieldIdentificationVariable + + /* identifier that must be a simple state field (name, email, ...) (the "name" of "u.name") */ + /* The difference between this and FieldIdentificationVariable is only semantical, because it points to a single field (not mapping to a relation) */ + SimpleStateField ::= FieldIdentificationVariable + + /* Alias ResultVariable declaration (the "total" of "COUNT(*) AS total") */ + AliasResultVariable = identifier + + /* ResultVariable identifier usage of mapped field aliases (the "total" of "COUNT(*) AS total") */ + ResultVariable = identifier + +Path Expressions +~~~~~~~~~~~~~~~~ + +.. code-block:: php + + /* "u.Group" or "u.Phonenumbers" declarations */ + JoinAssociationPathExpression ::= IdentificationVariable "." (CollectionValuedAssociationField | SingleValuedAssociationField) + + /* "u.Group" or "u.Phonenumbers" usages */ + AssociationPathExpression ::= CollectionValuedPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group" */ + SingleValuedPathExpression ::= StateFieldPathExpression | SingleValuedAssociationPathExpression + + /* "u.name" or "u.Group.name" */ + StateFieldPathExpression ::= IdentificationVariable "." StateField + + /* "u.Group" */ + SingleValuedAssociationPathExpression ::= IdentificationVariable "." SingleValuedAssociationField + + /* "u.Group.Permissions" */ + CollectionValuedPathExpression ::= IdentificationVariable "." CollectionValuedAssociationField + + /* "name" */ + StateField ::= {EmbeddedClassStateField "."}* SimpleStateField + +Clauses +~~~~~~~ + +.. code-block:: php + + SelectClause ::= "SELECT" ["DISTINCT"] SelectExpression {"," SelectExpression}* + SimpleSelectClause ::= "SELECT" ["DISTINCT"] SimpleSelectExpression + UpdateClause ::= "UPDATE" AbstractSchemaName ["AS"] AliasIdentificationVariable "SET" UpdateItem {"," UpdateItem}* + DeleteClause ::= "DELETE" ["FROM"] AbstractSchemaName ["AS"] AliasIdentificationVariable + FromClause ::= "FROM" IdentificationVariableDeclaration {"," IdentificationVariableDeclaration}* + SubselectFromClause ::= "FROM" SubselectIdentificationVariableDeclaration {"," SubselectIdentificationVariableDeclaration}* + WhereClause ::= "WHERE" ConditionalExpression + HavingClause ::= "HAVING" ConditionalExpression + GroupByClause ::= "GROUP" "BY" GroupByItem {"," GroupByItem}* + OrderByClause ::= "ORDER" "BY" OrderByItem {"," OrderByItem}* + Subselect ::= SimpleSelectClause SubselectFromClause [WhereClause] [GroupByClause] [HavingClause] [OrderByClause] + +Items +~~~~~ + +.. code-block:: php + + UpdateItem ::= SingleValuedPathExpression "=" NewValue + OrderByItem ::= (SimpleArithmeticExpression | SingleValuedPathExpression | ScalarExpression | ResultVariable) ["ASC" | "DESC"] + GroupByItem ::= IdentificationVariable | ResultVariable | SingleValuedPathExpression + NewValue ::= SimpleArithmeticExpression | "NULL" + +From, Join and Index by +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + IdentificationVariableDeclaration ::= RangeVariableDeclaration [IndexBy] {JoinVariableDeclaration}* + SubselectIdentificationVariableDeclaration ::= IdentificationVariableDeclaration | (AssociationPathExpression ["AS"] AliasIdentificationVariable) + JoinVariableDeclaration ::= Join [IndexBy] + RangeVariableDeclaration ::= AbstractSchemaName ["AS"] AliasIdentificationVariable + Join ::= ["LEFT" ["OUTER"] | "INNER"] "JOIN" JoinAssociationPathExpression ["AS"] AliasIdentificationVariable ["WITH" ConditionalExpression] + IndexBy ::= "INDEX" "BY" StateFieldPathExpression + +Select Expressions +~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + SelectExpression ::= (IdentificationVariable | ScalarExpression | AggregateExpression | FunctionDeclaration | PartialObjectExpression | "(" Subselect ")" | CaseExpression) [["AS"] ["HIDDEN"] AliasResultVariable] + SimpleSelectExpression ::= (StateFieldPathExpression | IdentificationVariable | FunctionDeclaration | AggregateExpression | "(" Subselect ")" | ScalarExpression) [["AS"] AliasResultVariable] + PartialObjectExpression ::= "PARTIAL" IdentificationVariable "." PartialFieldSet + PartialFieldSet ::= "{" SimpleStateField {"," SimpleStateField}* "}" + +Conditional Expressions +~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + ConditionalExpression ::= ConditionalTerm {"OR" ConditionalTerm}* + ConditionalTerm ::= ConditionalFactor {"AND" ConditionalFactor}* + ConditionalFactor ::= ["NOT"] ConditionalPrimary + ConditionalPrimary ::= SimpleConditionalExpression | "(" ConditionalExpression ")" + SimpleConditionalExpression ::= ComparisonExpression | BetweenExpression | LikeExpression | + InExpression | NullComparisonExpression | ExistsExpression | + EmptyCollectionComparisonExpression | CollectionMemberExpression | + InstanceOfExpression + + +Collection Expressions +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + EmptyCollectionComparisonExpression ::= CollectionValuedPathExpression "IS" ["NOT"] "EMPTY" + CollectionMemberExpression ::= EntityExpression ["NOT"] "MEMBER" ["OF"] CollectionValuedPathExpression + +Literal Values +~~~~~~~~~~~~~~ + +.. code-block:: php + + Literal ::= string | char | integer | float | boolean + InParameter ::= Literal | InputParameter + +Input Parameter +~~~~~~~~~~~~~~~ + +.. code-block:: php + + InputParameter ::= PositionalParameter | NamedParameter + PositionalParameter ::= "?" integer + NamedParameter ::= ":" string + +Arithmetic Expressions +~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + ArithmeticExpression ::= SimpleArithmeticExpression | "(" Subselect ")" + SimpleArithmeticExpression ::= ArithmeticTerm {("+" | "-") ArithmeticTerm}* + ArithmeticTerm ::= ArithmeticFactor {("*" | "/") ArithmeticFactor}* + ArithmeticFactor ::= [("+" | "-")] ArithmeticPrimary + ArithmeticPrimary ::= SingleValuedPathExpression | Literal | "(" SimpleArithmeticExpression ")" + | FunctionsReturningNumerics | AggregateExpression | FunctionsReturningStrings + | FunctionsReturningDatetime | IdentificationVariable | ResultVariable + | InputParameter | CaseExpression + +Scalar and Type Expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + ScalarExpression ::= SimpleArithmeticExpression | StringPrimary | DateTimePrimary | StateFieldPathExpression | BooleanPrimary | CaseExpression | InstanceOfExpression + StringExpression ::= StringPrimary | "(" Subselect ")" + StringPrimary ::= StateFieldPathExpression | string | InputParameter | FunctionsReturningStrings | AggregateExpression | CaseExpression + BooleanExpression ::= BooleanPrimary | "(" Subselect ")" + BooleanPrimary ::= StateFieldPathExpression | boolean | InputParameter + EntityExpression ::= SingleValuedAssociationPathExpression | SimpleEntityExpression + SimpleEntityExpression ::= IdentificationVariable | InputParameter + DatetimeExpression ::= DatetimePrimary | "(" Subselect ")" + DatetimePrimary ::= StateFieldPathExpression | InputParameter | FunctionsReturningDatetime | AggregateExpression + +.. note:: + + Parts of CASE expressions are not yet implemented. + +Aggregate Expressions +~~~~~~~~~~~~~~~~~~~~~ + +.. code-block:: php + + AggregateExpression ::= ("AVG" | "MAX" | "MIN" | "SUM") "(" ["DISTINCT"] StateFieldPathExpression ")" | + "COUNT" "(" ["DISTINCT"] (IdentificationVariable | SingleValuedPathExpression) ")" + +Case Expressions +~~~~~~~~~~~~~~~~ + +.. code-block:: php + + CaseExpression ::= GeneralCaseExpression | SimpleCaseExpression | CoalesceExpression | NullifExpression + GeneralCaseExpression ::= "CASE" WhenClause {WhenClause}* "ELSE" ScalarExpression "END" + WhenClause ::= "WHEN" ConditionalExpression "THEN" ScalarExpression + SimpleCaseExpression ::= "CASE" CaseOperand SimpleWhenClause {SimpleWhenClause}* "ELSE" ScalarExpression "END" + CaseOperand ::= StateFieldPathExpression | TypeDiscriminator + SimpleWhenClause ::= "WHEN" ScalarExpression "THEN" ScalarExpression + CoalesceExpression ::= "COALESCE" "(" ScalarExpression {"," ScalarExpression}* ")" + NullifExpression ::= "NULLIF" "(" ScalarExpression "," ScalarExpression ")" + +Other Expressions +~~~~~~~~~~~~~~~~~ + +QUANTIFIED/BETWEEN/COMPARISON/LIKE/NULL/EXISTS + +.. code-block:: php + + QuantifiedExpression ::= ("ALL" | "ANY" | "SOME") "(" Subselect ")" + BetweenExpression ::= ArithmeticExpression ["NOT"] "BETWEEN" ArithmeticExpression "AND" ArithmeticExpression + ComparisonExpression ::= ArithmeticExpression ComparisonOperator ( QuantifiedExpression | ArithmeticExpression ) + InExpression ::= SingleValuedPathExpression ["NOT"] "IN" "(" (InParameter {"," InParameter}* | Subselect) ")" + InstanceOfExpression ::= IdentificationVariable ["NOT"] "INSTANCE" ["OF"] (InstanceOfParameter | "(" InstanceOfParameter {"," InstanceOfParameter}* ")") + InstanceOfParameter ::= AbstractSchemaName | InputParameter + LikeExpression ::= StringExpression ["NOT"] "LIKE" StringPrimary ["ESCAPE" char] + NullComparisonExpression ::= (SingleValuedPathExpression | InputParameter) "IS" ["NOT"] "NULL" + ExistsExpression ::= ["NOT"] "EXISTS" "(" Subselect ")" + ComparisonOperator ::= "=" | "<" | "<=" | "<>" | ">" | ">=" | "!=" + +Functions +~~~~~~~~~ + +.. code-block:: php + + FunctionDeclaration ::= FunctionsReturningStrings | FunctionsReturningNumerics | FunctionsReturningDateTime + + FunctionsReturningNumerics ::= + "LENGTH" "(" StringPrimary ")" | + "LOCATE" "(" StringPrimary "," StringPrimary ["," SimpleArithmeticExpression]")" | + "ABS" "(" SimpleArithmeticExpression ")" | + "SQRT" "(" SimpleArithmeticExpression ")" | + "MOD" "(" SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "SIZE" "(" CollectionValuedPathExpression ")" | + "DATE_DIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | + "BIT_AND" "(" ArithmeticPrimary "," ArithmeticPrimary ")" | + "BIT_OR" "(" ArithmeticPrimary "," ArithmeticPrimary ")" + + FunctionsReturningDateTime ::= + "CURRENT_DATE" | + "CURRENT_TIME" | + "CURRENT_TIMESTAMP" | + "DATE_ADD" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" | + "DATE_SUB" "(" ArithmeticPrimary "," ArithmeticPrimary "," StringPrimary ")" + + FunctionsReturningStrings ::= + "CONCAT" "(" StringPrimary "," StringPrimary ")" | + "SUBSTRING" "(" StringPrimary "," SimpleArithmeticExpression "," SimpleArithmeticExpression ")" | + "TRIM" "(" [["LEADING" | "TRAILING" | "BOTH"] [char] "FROM"] StringPrimary ")" | + "LOWER" "(" StringPrimary ")" | + "UPPER" "(" StringPrimary ")" | + "IDENTITY" "(" SingleValuedAssociationPathExpression ")" + + diff --git a/docs/en/reference/events.rst b/docs/en/reference/events.rst new file mode 100644 index 000000000..a6b75ad12 --- /dev/null +++ b/docs/en/reference/events.rst @@ -0,0 +1,658 @@ +Events +====== + +Doctrine 2 features a lightweight event system that is part of the +Common package. + +The Event System +---------------- + +The event system is controlled by the ``EventManager``. It is the +central point of Doctrine's event listener system. Listeners are +registered on the manager and events are dispatched through the +manager. + +.. code-block:: php + + addEventListener(array(self::preFoo, self::postFoo), $this); + } + + public function preFoo(EventArgs $e) + { + $this->preFooInvoked = true; + } + + public function postFoo(EventArgs $e) + { + $this->postFooInvoked = true; + } + } + + // Create a new instance + $test = new EventTest($evm); + +Events can be dispatched by using the ``dispatchEvent()`` method. + +.. code-block:: php + + dispatchEvent(EventTest::preFoo); + $evm->dispatchEvent(EventTest::postFoo); + +You can easily remove a listener with the ``removeEventListener()`` +method. + +.. code-block:: php + + removeEventListener(array(self::preFoo, self::postFoo), $this); + +The Doctrine 2 event system also has a simple concept of event +subscribers. We can define a simple ``TestEventSubscriber`` class +which implements the ``\Doctrine\Common\EventSubscriber`` interface +and implements a ``getSubscribedEvents()`` method which returns an +array of events it should be subscribed to. + +.. code-block:: php + + preFooInvoked = true; + } + + public function getSubscribedEvents() + { + return array(TestEvent::preFoo); + } + } + + $eventSubscriber = new TestEventSubscriber(); + $evm->addEventSubscriber($eventSubscriber); + +Now when you dispatch an event any event subscribers will be +notified for that event. + +.. code-block:: php + + dispatchEvent(TestEvent::preFoo); + +Now you can test the ``$eventSubscriber`` instance to see if the +``preFoo()`` method was invoked. + +.. code-block:: php + + preFooInvoked) { + echo 'pre foo invoked!'; + } + +Naming convention +~~~~~~~~~~~~~~~~~ + +Events being used with the Doctrine 2 EventManager are best named +with camelcase and the value of the corresponding constant should +be the name of the constant itself, even with spelling. This has +several reasons: + + +- It is easy to read. +- Simplicity. +- Each method within an EventSubscriber is named after the + corresponding constant. If constant name and constant value differ, + you MUST use the new value and thus, your code might be subject to + codechanges when the value changes. This contradicts the intention + of a constant. + +An example for a correct notation can be found in the example +``EventTest`` above. + +Lifecycle Events +---------------- + +The EntityManager and UnitOfWork trigger a bunch of events during +the life-time of their registered entities. + + +- preRemove - The preRemove event occurs for a given entity before + the respective EntityManager remove operation for that entity is + executed. It is not called for a DQL DELETE statement. +- postRemove - The postRemove event occurs for an entity after the + entity has been deleted. It will be invoked after the database + delete operations. It is not called for a DQL DELETE statement. +- prePersist - The prePersist event occurs for a given entity + before the respective EntityManager persist operation for that + entity is executed. +- postPersist - The postPersist event occurs for an entity after + the entity has been made persistent. It will be invoked after the + database insert operations. Generated primary key values are + available in the postPersist event. +- preUpdate - The preUpdate event occurs before the database + update operations to entity data. It is not called for a DQL UPDATE statement. +- postUpdate - The postUpdate event occurs after the database + update operations to entity data. It is not called for a DQL UPDATE statement. +- postLoad - The postLoad event occurs for an entity after the + entity has been loaded into the current EntityManager from the + database or after the refresh operation has been applied to it. +- loadClassMetadata - The loadClassMetadata event occurs after the + mapping metadata for a class has been loaded from a mapping source + (annotations/xml/yaml). +- preFlush - The preFlush event occurs at the very beginning of a flush + operation. This event is not a lifecycle callback. +- onFlush - The onFlush event occurs after the change-sets of all + managed entities are computed. This event is not a lifecycle + callback. +- postFlush - The postFlush event occurs at the end of a flush operation. This + event is not a lifecycle callback. +- onClear - The onClear event occurs when the EntityManager#clear() operation is + invoked, after all references to entities have been removed from the unit of + work. + +.. warning:: + + Note that the postLoad event occurs for an entity + before any associations have been initialized. Therefore it is not + safe to access associations in a postLoad callback or event + handler. + + +You can access the Event constants from the ``Events`` class in the +ORM package. + +.. code-block:: php + + createdAt = date('Y-m-d H:i:s'); + } + + /** @PrePersist */ + public function doOtherStuffOnPrePersist() + { + $this->value = 'changed from prePersist callback!'; + } + + /** @PostPersist */ + public function doStuffOnPostPersist() + { + $this->value = 'changed from postPersist callback!'; + } + + /** @PostLoad */ + public function doStuffOnPostLoad() + { + $this->value = 'changed from postLoad callback!'; + } + + /** @PreUpdate */ + public function doStuffOnPreUpdate() + { + $this->value = 'changed from preUpdate callback!'; + } + } + +Note that when using annotations you have to apply the +@HasLifecycleCallbacks marker annotation on the entity class. + +If you want to register lifecycle callbacks from YAML or XML you +can do it with the following. + +.. code-block:: yaml + + User: + type: entity + fields: + # ... + name: + type: string(50) + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] + +XML would look something like this: + +.. code-block:: xml + + + + + + + + + + + + + + + + +You just need to make sure a public ``doStuffOnPrePersist()`` and +``doStuffOnPostPersist()`` method is defined on your ``User`` +model. + +.. code-block:: php + + addEventListener(array(Events::preUpdate), new MyEventListener()); + $eventManager->addEventSubscriber(new MyEventSubscriber()); + + $entityManager = EntityManager::create($dbOpts, $config, $eventManager); + +You can also retrieve the event manager instance after the +EntityManager was created: + +.. code-block:: php + + getEventManager()->addEventListener(array(Events::preUpdate), new MyEventListener()); + $entityManager->getEventManager()->addEventSubscriber(new MyEventSubscriber()); + +.. _reference-events-implementing-listeners: + +Implementing Event Listeners +---------------------------- + +This section explains what is and what is not allowed during +specific lifecycle events of the UnitOfWork. Although you get +passed the EntityManager in all of these events, you have to follow +these restrictions very carefully since operations in the wrong +event may produce lots of different errors, such as inconsistent +data and lost updates/persists/removes. + +For the described events that are also lifecycle callback events +the restrictions apply as well, with the additional restriction +that you do not have access to the EntityManager or UnitOfWork APIs +inside these events. + +prePersist +~~~~~~~~~~ + +There are two ways for the ``prePersist`` event to be triggered. +One is obviously when you call ``EntityManager#persist()``. The +event is also called for all cascaded associations. + +There is another way for ``prePersist`` to be called, inside the +``flush()`` method when changes to associations are computed and +this association is marked as cascade persist. Any new entity found +during this operation is also persisted and ``prePersist`` called +on it. This is called "persistence by reachability". + +In both cases you get passed a ``LifecycleEventArgs`` instance +which has access to the entity and the entity manager. + +The following restrictions apply to ``prePersist``: + + +- If you are using a PrePersist Identity Generator such as + sequences the ID value will *NOT* be available within any + PrePersist events. +- Doctrine will not recognize changes made to relations in a pre + persist event called by "reachability" through a cascade persist + unless you use the internal ``UnitOfWork`` API. We do not recommend + such operations in the persistence by reachability context, so do + this at your own risk and possibly supported by unit-tests. + +preRemove +~~~~~~~~~ + +The ``preRemove`` event is called on every entity when its passed +to the ``EntityManager#remove()`` method. It is cascaded for all +associations that are marked as cascade delete. + +There are no restrictions to what methods can be called inside the +``preRemove`` event, except when the remove method itself was +called during a flush operation. + +preFlush +~~~~~~~~ + +``preFlush`` is called at ``EntityManager#flush()`` before +anything else. ``EntityManager#flush()`` can be called safely +inside its listeners. + +.. code-block:: php + + getEntityManager(); + $uow = $em->getUnitOfWork(); + + foreach ($uow->getScheduledEntityInsertions() AS $entity) { + + } + + foreach ($uow->getScheduledEntityUpdates() AS $entity) { + + } + + foreach ($uow->getScheduledEntityDeletions() AS $entity) { + + } + + foreach ($uow->getScheduledCollectionDeletions() AS $col) { + + } + + foreach ($uow->getScheduledCollectionUpdates() AS $col) { + + } + } + } + +The following restrictions apply to the onFlush event: + + +- If you create and persist a new entity in "onFlush", then + calling ``EntityManager#persist()`` is not enough. + You have to execute an additional call to + ``$unitOfWork->computeChangeSet($classMetadata, $entity)``. +- Changing primitive fields or associations requires you to + explicitly trigger a re-computation of the changeset of the + affected entity. This can be done by either calling + ``$unitOfWork->recomputeSingleEntityChangeSet($classMetadata, $entity)``. + +postFlush +~~~~~~~~~ + +``postFlush`` is called at the end of ``EntityManager#flush()``. ``EntityManager#flush()`` can be +called safely inside its listeners. + +.. code-block:: php + + getEntity() instanceof User) { + if ($eventArgs->hasChangedField('name') && $eventArgs->getNewValue('name') == 'Alice') { + $eventArgs->setNewValue('name', 'Bob'); + } + } + } + } + +You could also use this listener to implement validation of all the +fields that have changed. This is more efficient than using a +lifecycle callback when there are expensive validations to call: + +.. code-block:: php + + getEntity() instanceof Account) { + if ($eventArgs->hasChangedField('creditCard')) { + $this->validateCreditCard($eventArgs->getNewValue('creditCard')); + } + } + } + + private function validateCreditCard($no) + { + // throw an exception to interrupt flush event. Transaction will be rolled back. + } + } + +Restrictions for this event: + + +- Changes to associations of the passed entities are not + recognized by the flush operation anymore. +- Changes to fields of the passed entities are not recognized by + the flush operation anymore, use the computed change-set passed to + the event to modify primitive field values. +- Any calls to ``EntityManager#persist()`` or + ``EntityManager#remove()``, even in combination with the UnitOfWork + API are strongly discouraged and don't work as expected outside the + flush operation. + +postUpdate, postRemove, postPersist +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The three post events are called inside ``EntityManager#flush()``. +Changes in here are not relevant to the persistence in the +database, but you can use these events to alter non-persistable items, +like non-mapped fields, logging or even associated classes that are +directly mapped by Doctrine. + +postLoad +~~~~~~~~ + +This event is called after an entity is constructed by the +EntityManager. + +Load ClassMetadata Event +------------------------ + +When the mapping information for an entity is read, it is populated +in to a ``ClassMetadataInfo`` instance. You can hook in to this +process and manipulate the instance. + +.. code-block:: php + + getMetadataFactory(); + $evm = $em->getEventManager(); + $evm->addEventListener(Events::loadClassMetadata, $test); + + class EventTest + { + public function loadClassMetadata(\Doctrine\ORM\Event\LoadClassMetadataEventArgs $eventArgs) + { + $classMetadata = $eventArgs->getClassMetadata(); + $fieldMapping = array( + 'fieldName' => 'about', + 'type' => 'string', + 'length' => 255 + ); + $classMetadata->mapField($fieldMapping); + } + } + + diff --git a/docs/en/reference/faq.rst b/docs/en/reference/faq.rst new file mode 100644 index 000000000..5d57c24f7 --- /dev/null +++ b/docs/en/reference/faq.rst @@ -0,0 +1,224 @@ +Frequently Asked Questions +========================== + +.. note:: + + This FAQ is a work in progress. We will add lots of questions and not answer them right away just to remember + what is often asked. If you stumble across an unanswered question please write a mail to the mailing-list or + join the #doctrine channel on Freenode IRC. + +Database Schema +--------------- + +How do I set the charset and collation for MySQL tables? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can't set these values inside the annotations, yml or xml mapping files. To make a database +work with the default charset and collation you should configure MySQL to use it as default charset, +or create the database with charset and collation details. This way they get inherited to all newly +created database tables and columns. + +Entity Classes +-------------- + +I access a variable and its null, what is wrong? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If this variable is a public variable then you are violating one of the criteria for entities. +All properties have to be protected or private for the proxy object pattern to work. + +How can I add default values to a column? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Doctrine does not support to set the default values in columns through the "DEFAULT" keyword in SQL. +This is not necessary however, you can just use your class properties as default values. These are then used +upon insert: + +.. code-block:: php + + class User + { + const STATUS_DISABLED = 0; + const STATUS_ENABLED = 1; + + private $algorithm = "sha1"; + private $status = self:STATUS_DISABLED; + } + +. + +Mapping +------- + +Why do I get exceptions about unique constraint failures during ``$em->flush()``? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Doctrine does not check if you are re-adding entities with a primary key that already exists +or adding entities to a collection twice. You have to check for both conditions yourself +in the code before calling ``$em->flush()`` if you know that unique constraint failures +can occur. + +In `Symfony2 `_ for example there is a Unique Entity Validator +to achieve this task. + +For collections you can check with ``$collection->contains($entity)`` if an entity is already +part of this collection. For a FETCH=LAZY collection this will initialize the collection, +however for FETCH=EXTRA_LAZY this method will use SQL to determine if this entity is already +part of the collection. + +Associations +------------ + +What is wrong when I get an InvalidArgumentException "A new entity was found through the relationship.."? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This exception is thrown during ``EntityManager#flush()`` when there exists an object in the identity map +that contains a reference to an object that Doctrine does not know about. Say for example you grab +a "User"-entity from the database with a specific id and set a completely new object into one of the associations +of the User object. If you then call ``EntityManager#flush()`` without letting Doctrine know about +this new object using ``EntityManager#persist($newObject)`` you will see this exception. + +You can solve this exception by: + +* Calling ``EntityManager#persist($newObject)`` on the new object +* Using cascade=persist on the association that contains the new object + +How can I filter an association? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Natively you can't filter associations in 2.0 and 2.1. You should use DQL queries to query for the filtered set of entities. + +I call clear() on a One-To-Many collection but the entities are not deleted +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This is an expected behavior that has to do with the inverse/owning side handling of Doctrine. +By definition a One-To-Many association is on the inverse side, that means changes to it +will not be recognized by Doctrine. + +If you want to perform the equivalent of the clear operation you have to iterate the +collection and set the owning side many-to-one reference to NULL as well to detach all entities +from the collection. This will trigger the appropriate UPDATE statements on the database. + +How can I add columns to a many-to-many table? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The many-to-many association is only supporting foreign keys in the table definition +To work with many-to-many tables containing extra columns you have to use the +foreign keys as primary keys feature of Doctrine introduced in version 2.1. + +See :doc:`the tutorial on composite primary keys for more information<../tutorials/composite-primary-keys>`. + + +How can i paginate fetch-joined collections? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are issuing a DQL statement that fetches a collection as well you cannot easily iterate +over this collection using a LIMIT statement (or vendor equivalent). + +Doctrine does not offer a solution for this out of the box but there are several extensions +that do: + +* `DoctrineExtensions `_ +* `Pagerfanta `_ + +Why does pagination not work correctly with fetch joins? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Pagination in Doctrine uses a LIMIT clause (or vendor equivalent) to restrict the results. +However when fetch-joining this is not returning the correct number of results since joining +with a one-to-many or many-to-many association multiplies the number of rows by the number +of associated entities. + +See the previous question for a solution to this task. + +Inheritance +----------- + +Can I use Inheritance with Doctrine 2? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Yes, you can use Single- or Joined-Table Inheritance in Doctrine 2. + +See the documentation chapter on :doc:`inheritance mapping ` for +the details. + +Why does Doctrine not create proxy objects for my inheritance hierarchy? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you set a many-to-one or one-to-one association target-entity to any parent class of +an inheritance hierarchy Doctrine does not know what PHP class the foreign is actually of. +To find this out it has to execute an SQL query to look this information up in the database. + +EntityGenerator +--------------- + +Why does the EntityGenerator not do X? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The EntityGenerator is not a full fledged code-generator that solves all tasks. Code-Generation +is not a first-class priority in Doctrine 2 anymore (compared to Doctrine 1). The EntityGenerator +is supposed to kick-start you, but not towards 100%. + +Why does the EntityGenerator not generate inheritance correctly? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Just from the details of the discriminator map the EntityGenerator cannot guess the inheritance hierachy. +This is why the generation of inherited entities does not fully work. You have to adjust some additional +code to get this one working correctly. + +Performance +----------- + +Why is an extra SQL query executed every time I fetch an entity with a one-to-one relation? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If Doctrine detects that you are fetching an inverse side one-to-one association +it has to execute an additional query to load this object, because it cannot know +if there is no such object (setting null) or if it should set a proxy and which id this proxy has. + +To solve this problem currently a query has to be executed to find out this information. + +Doctrine Query Language +----------------------- + +What is DQL? +~~~~~~~~~~~~ + +DQL stands for Doctrine Query Language, a query language that very much looks like SQL +but has some important benefits when using Doctrine: + +- It uses class names and fields instead of tables and columns, separating concerns between backend and your object model. +- It utilizes the metadata defined to offer a range of shortcuts when writing. For example you do not have to specify the ON clause of joins, since Doctrine already knows about them. +- It adds some functionality that is related to object management and transforms them into SQL. + +It also has some drawbacks of course: + +- The syntax is slightly different to SQL so you have to learn and remember the differences. +- To be vendor independent it can only implement a subset of all the existing SQL dialects. Vendor specific functionality and optimizations cannot be used through DQL unless implemented by you explicitly. +- For some DQL constructs subselects are used which are known to be slow in MySQL. + +Can I sort by a function (for example ORDER BY RAND()) in DQL? +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +No, it is not supported to sort by function in DQL. If you need this functionality you should either +use a native-query or come up with another solution. As a side note: Sorting with ORDER BY RAND() is painfully slow +starting with 1000 rows. + +A Query fails, how can I debug it? +---------------------------------- + +First, if you are using the QueryBuilder you can use +``$queryBuilder->getDQL()`` to get the DQL string of this query. The +corresponding SQL you can get from the Query instance by calling +``$query->getSQL()``. + +.. code-block:: php + + createQuery($dql); + var_dump($query->getSQL()); + + $qb = $entityManager->createQueryBuilder(); + $qb->select('u')->from('User', 'u'); + var_dump($qb->getDQL()); diff --git a/docs/en/reference/filters.rst b/docs/en/reference/filters.rst new file mode 100644 index 000000000..34485b087 --- /dev/null +++ b/docs/en/reference/filters.rst @@ -0,0 +1,91 @@ +Filters +======= + +Doctrine 2.2 features a filter system that allows the developer to add SQL to +the conditional clauses of queries, regardless the place where the SQL is +generated (e.g. from a DQL query, or by loading associated entities). + +The filter functionality works on SQL level. Whether an SQL query is generated +in a Persister, during lazy loading, in extra lazy collections or from DQL. +Each time the system iterates over all the enabled filters, adding a new SQL +part as a filter returns. + +By adding SQL to the conditional clauses of queries, the filter system filters +out rows belonging to the entities at the level of the SQL result set. This +means that the filtered entities are never hydrated (which can be expensive). + + +Example filter class +-------------------- +Throughout this document the example ``MyLocaleFilter`` class will be used to +illustrate how the filter feature works. A filter class must extend the base +``Doctrine\ORM\Query\Filter\SQLFilter`` class and implement the ``addFilterConstraint`` +method. The method receives the ``ClassMetadata`` of the filtered entity and the +table alias of the SQL table of the entity. + +.. note:: + + In the case of joined or single table inheritance, you always get passed the ClassMetadata of the + inheritance root. This is necessary to avoid edge cases that would break the SQL when applying the filters. + +Parameters for the query should be set on the filter object by +``SQLFilter#setParameter()``. Only parameters set via this function can be used +in filters. The ``SQLFilter#getParameter()`` function takes care of the +proper quoting of parameters. + +.. code-block:: php + + reflClass->implementsInterface('LocaleAware')) { + return ""; + } + + return $targetTableAlias.'.locale = ' . $this->getParameter('locale'); // getParameter applies quoting automatically + } + } + + +Configuration +------------- +Filter classes are added to the configuration as following: + +.. code-block:: php + + addFilter("locale", "\Doctrine\Tests\ORM\Functional\MyLocaleFilter"); + + +The ``Configuration#addFilter()`` method takes a name for the filter and the name of the +class responsible for the actual filtering. + + +Disabling/Enabling Filters and Setting Parameters +--------------------------------------------------- +Filters can be disabled and enabled via the ``FilterCollection`` which is +stored in the ``EntityManager``. The ``FilterCollection#enable($name)`` method +will retrieve the filter object. You can set the filter parameters on that +object. + +.. code-block:: php + + getFilters()->enable("locale"); + $filter->setParameter('locale', 'en'); + + // Disable it + $filter = $em->getFilters()->disable("locale"); + +.. warning:: + Disabling and enabling filters has no effect on managed entities. If you + want to refresh or reload an object after having modified a filter or the + FilterCollection, then you should clear the EntityManager and re-fetch your + entities, having the new rules for filtering applied. diff --git a/docs/en/reference/improving-performance.rst b/docs/en/reference/improving-performance.rst new file mode 100644 index 000000000..9bc4c8632 --- /dev/null +++ b/docs/en/reference/improving-performance.rst @@ -0,0 +1,64 @@ +Improving Performance +===================== + +Bytecode Cache +-------------- + +It is highly recommended to make use of a bytecode cache like APC. +A bytecode cache removes the need for parsing PHP code on every +request and can greatly improve performance. + + "If you care about performance and don't use a bytecode + cache then you don't really care about performance. Please get one + and start using it." + + *Stas Malyshev, Core Contributor to PHP and Zend Employee* + + +Metadata and Query caches +------------------------- + +As already mentioned earlier in the chapter about configuring +Doctrine, it is strongly discouraged to use Doctrine without a +Metadata and Query cache (preferably with APC or Memcache as the +cache driver). Operating Doctrine without these caches means +Doctrine will need to load your mapping information on every single +request and has to parse each DQL query on every single request. +This is a waste of resources. + +Alternative Query Result Formats +-------------------------------- + +Make effective use of the available alternative query result +formats like nested array graphs or pure scalar results, especially +in scenarios where data is loaded for read-only purposes. + +Read-Only Entities +------------------ + +Starting with Doctrine 2.1 you can mark entities as read only (See metadata mapping +references for details). This means that the entity marked as read only is never considered +for updates, which means when you call flush on the EntityManager these entities are skipped +even if properties changed. Read-Only allows to persist new entities of a kind and remove existing +ones, they are just not considered for updates. + +Extra-Lazy Collections +---------------------- + +If entities hold references to large collections you will get performance and memory problems initializing them. +To solve this issue you can use the EXTRA_LAZY fetch-mode feature for collections. See the :doc:`tutorial <../tutorials/extra-lazy-associations>` +for more information on how this fetch mode works. + +Temporarily change fetch mode in DQL +------------------------------------ + +See :ref:`Doctrine Query Language chapter ` + + +Apply Best Practices +-------------------- + +A lot of the points mentioned in the Best Practices chapter will +also positively affect the performance of Doctrine. + + diff --git a/docs/en/reference/inheritance-mapping.rst b/docs/en/reference/inheritance-mapping.rst new file mode 100644 index 000000000..1bc92f3f3 --- /dev/null +++ b/docs/en/reference/inheritance-mapping.rst @@ -0,0 +1,559 @@ +Inheritance Mapping +=================== + +Mapped Superclasses +------------------- + +A mapped superclass is an abstract or concrete class that provides +persistent entity state and mapping information for its subclasses, +but which is not itself an entity. Typically, the purpose of such a +mapped superclass is to define state and mapping information that +is common to multiple entity classes. + +Mapped superclasses, just as regular, non-mapped classes, can +appear in the middle of an otherwise mapped inheritance hierarchy +(through Single Table Inheritance or Class Table Inheritance). + +.. note:: + + A mapped superclass cannot be an entity, it is not query-able and + persistent relationships defined by a mapped superclass must be + unidirectional (with an owning side only). This means that One-To-Many + associations are not possible on a mapped superclass at all. + Furthermore Many-To-Many associations are only possible if the + mapped superclass is only used in exactly one entity at the moment. + For further support of inheritance, the single or + joined table inheritance features have to be used. + + +Example: + +.. code-block:: php + + `_ +is an inheritance mapping strategy where all classes of a hierarchy +are mapped to a single database table. In order to distinguish +which row represents which type in the hierarchy a so-called +discriminator column is used. + +Example: + +.. code-block:: php + + `_ +is an inheritance mapping strategy where each class in a hierarchy +is mapped to several tables: its own table and the tables of all +parent classes. The table of a child class is linked to the table +of a parent class through a foreign key constraint. Doctrine 2 +implements this strategy through the use of a discriminator column +in the topmost table of the hierarchy because this is the easiest +way to achieve polymorphic queries with Class Table Inheritance. + +Example: + +.. code-block:: php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + # user mapping + MyProject\Model\User: + type: mappedSuperclass + # other fields mapping + manyToOne: + address: + targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id + cascade: [ persist, merge ] + manyToMany: + groups: + targetEntity: Group + joinTable: + name: users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + cascade: [ persist, merge, detach ] + + # admin mapping + MyProject\Model\Admin: + type: entity + associationOverride: + address: + joinColumn: + adminaddress_id: + name: adminaddress_id + referencedColumnName: id + groups: + joinTable: + name: users_admingroups + joinColumns: + adminuser_id: + referencedColumnName: id + inverseJoinColumns: + admingroup_id: + referencedColumnName: id + + +Things to note: + +- The "association override" specifies the overrides base on the property name. +- This feature is available for all kind of associations. (OneToOne, OneToMany, ManyToOne, ManyToMany) +- The association type *CANNOT* be changed. +- The override could redefine the joinTables or joinColumns depending on the association type. + +Attribute Override +~~~~~~~~~~~~~~~~~~~~ +Override the mapping of a field. + +Could be used by an entity that extends a mapped superclass to override a field mapping defined by the mapped superclass. + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + # user mapping + MyProject\Model\User: + type: mappedSuperclass + id: + id: + type: integer + column: user_id + length: 150 + generator: + strategy: AUTO + fields: + name: + type: string + column: user_name + length: 250 + nullable: true + unique: false + #other fields mapping + + + # guest mapping + MyProject\Model\Guest: + type: entity + attributeOverride: + id: + column: guest_id + type: integer + length: 140 + name: + column: guest_name + type: string + length: 240 + nullable: false + unique: true + +Things to note: + +- The "attribute override" specifies the overrides base on the property name. +- The column type *CANNOT* be changed. if the column type is not equals you got a ``MappingException`` +- The override can redefine all the column except the type. diff --git a/docs/en/reference/installation.rst b/docs/en/reference/installation.rst new file mode 100644 index 000000000..48cdd1350 --- /dev/null +++ b/docs/en/reference/installation.rst @@ -0,0 +1,93 @@ +Installation +============ + +Doctrine can be installed many different ways. We will describe all the different ways and you can choose which one suits you best. + +Composer +-------- + +`Composer `_ is the suggested installation method for Doctrine. +Define the following requirement in your ``composer.json`` file: + +:: + + { + "require": { + "doctrine/orm": "*" + } + } + +Then run the composer command and you are done. Continue with the +:doc:`Configuration `. + +PEAR +---- + +You can easily install any of the three Doctrine packages from the +PEAR command line installation utility. + +To install just the ``Common`` package you can run the following +command: + +.. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineCommon- + +If you want to use the Doctrine Database Abstraction Layer you can +install it with the following command. + +.. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineDBAL- + +Or, if you want to get the works and go for the ORM you can install +it with the following command. + +.. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineORM- + + +.. note:: + + The ```` tag above represents the version you + want to install. For example if the current version at the time of + writing this is ``2.2.2`` for the ORM, so you could install it + like the following: + + .. code-block:: bash + + $ sudo pear install pear.doctrine-project.org/DoctrineORM-2.2.2 + +GitHub +------ + +Alternatively you can clone the latest version of Doctrine 2 via +GitHub.com: + +.. code-block:: php + + $ git clone git://github.com/doctrine/doctrine2.git doctrine + +This downloads all the sources of the ORM package. You need to +initialize the Github submodules for the Common and DBAL package +dependencies: + +.. code-block:: php + + $ git submodule init + $ git submodule update + +This updates your Git checkout to use the Doctrine and Doctrine +package versions that are recommended for the cloned Master version +of Doctrine 2. + +See the configuration chapter on how to configure a Github +installation of Doctrine with regards to autoloading. + +.. note:: + + You should not combine the Doctrine-Common, Doctrine-DBAL and + Doctrine-ORM master commits with each other in combination. The ORM + may not work with the current Common or DBAL master versions. + Instead the ORM ships with the Git Submodules that are required. diff --git a/docs/en/reference/limitations-and-known-issues.rst b/docs/en/reference/limitations-and-known-issues.rst new file mode 100644 index 000000000..e3eb498a0 --- /dev/null +++ b/docs/en/reference/limitations-and-known-issues.rst @@ -0,0 +1,189 @@ +Limitations and Known Issues +============================ + +We try to make using Doctrine2 a very pleasant experience. +Therefore we think it is very important to be honest about the +current limitations to our users. Much like every other piece of +software Doctrine2 is not perfect and far from feature complete. +This section should give you an overview of current limitations of +Doctrine 2 as well as critical known issues that you should know +about. + +Current Limitations +------------------- + +There is a set of limitations that exist currently which might be +solved in the future. Any of this limitations now stated has at +least one ticket in the Tracker and is discussed for future +releases. + +Join-Columns with non-primary keys +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is not possible to use join columns pointing to non-primary keys. Doctrine will think these are the primary +keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance +reasons not validate the correctness of this settings at runtime but only through the Validate Schema command. + +Mapping Arrays to a Join Table +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Related to the previous limitation with "Foreign Keys as +Identifier" you might be interested in mapping the same table +structure as given above to an array. However this is not yet +possible either. See the following example: + +.. code-block:: sql + + CREATE TABLE product ( + id INTEGER, + name VARCHAR, + PRIMARY KEY(id) + ); + + CREATE TABLE product_attributes ( + product_id INTEGER, + attribute_name VARCHAR, + attribute_value VARCHAR, + PRIMARY KEY (product_id, attribute_name) + ); + +This schema should be mapped to a Product Entity as follows: + +.. code-block:: php + + class Product + { + private $id; + private $name; + private $attributes = array(); + } + +Where the ``attribute_name`` column contains the key and +``attribute_value`` contains the value of each array element in +``$attributes``. + +The feature request for persistence of primitive value arrays +`is described in the DDC-298 ticket `_. + +Value Objects +~~~~~~~~~~~~~ + +There is currently no native support value objects in Doctrine +other than for ``DateTime`` instances or if you serialize the +objects using ``serialize()/deserialize()`` which the DBAL Type +"object" supports. + +The feature request for full value-object support +`is described in the DDC-93 ticket `_. + + +Cascade Merge with Bi-directional Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are two bugs now that concern the use of cascade merge in combination with bi-directional associations. +Make sure to study the behavior of cascade merge if you are using it: + +- `DDC-875 `_ Merge can sometimes add the same entity twice into a collection +- `DDC-763 `_ Cascade merge on associated entities can insert too many rows through "Persistence by Reachability" + +Custom Persisters +~~~~~~~~~~~~~~~~~ + +A Persister in Doctrine is an object that is responsible for the +hydration and write operations of an entity against the database. +Currently there is no way to overwrite the persister implementation +for a given entity, however there are several use-cases that can +benefit from custom persister implementations: + + +- `Add Upsert Support `_ +- `Evaluate possible ways in which stored-procedures can be used `_ +- The previous Filter Rules Feature Request + +Persist Keys of Collections +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +PHP Arrays are ordered hash-maps and so should be the +``Doctrine\Common\Collections\Collection`` interface. We plan to +evaluate a feature that optionally persists and hydrates the keys +of a Collection instance. + +`Ticket DDC-213 `_ + +Mapping many tables to one entity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +It is not possible to map several equally looking tables onto one +entity. For example if you have a production and an archive table +of a certain business concept then you cannot have both tables map +to the same entity. + +Behaviors +~~~~~~~~~ + +Doctrine 2 will **never** include a behavior system like Doctrine 1 +in the core library. We don't think behaviors add more value than +they cost pain and debugging hell. Please see the many different +blog posts we have written on this topics: + +- `Doctrine2 "Behaviors" in a Nutshell `_ +- `A re-usable Versionable behavior for Doctrine2 `_ +- `Write your own ORM on top of Doctrine2 `_ +- `Doctrine 2 Behavioral Extensions `_ +- `Doctrator _ + +Doctrine 2 has enough hooks and extension points so that **you** can +add whatever you want on top of it. None of this will ever become +core functionality of Doctrine2 however, you will have to rely on +third party extensions for magical behaviors. + +Nested Set +~~~~~~~~~~ + +NestedSet was offered as a behavior in Doctrine 1 and will not be +included in the core of Doctrine 2. However there are already two +extensions out there that offer support for Nested Set with +Doctrine 2: + + +- `Doctrine2 Hierarchical-Structural Behavior `_ +- `Doctrine2 NestedSet `_ + +Known Issues +------------ + +The Known Issues section describes critical/blocker bugs and other +issues that are either complicated to fix, not fixable due to +backwards compatibility issues or where no simple fix exists (yet). +We don't plan to add every bug in the tracker there, just those +issues that can potentially cause nightmares or pain of any sort. + +See the Open Bugs on Jira for more details on `bugs, improvement and feature +requests +`_. + +Identifier Quoting and Legacy Databases +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For compatibility reasons between all the supported vendors and +edge case problems Doctrine 2 does **NOT** do automatic identifier +quoting. This can lead to problems when trying to get +legacy-databases to work with Doctrine 2. + + +- You can quote column-names as described in the + :doc:`Basic-Mapping ` section. +- You cannot quote join column names. +- You cannot use non [a-zA-Z0-9\_]+ characters, they will break + several SQL statements. + +Having problems with these kind of column names? Many databases +support all CRUD operations on views that semantically map to +certain tables. You can create views for all your problematic +tables and column names to avoid the legacy quoting nightmare. + +Microsoft SQL Server and Doctrine "datetime" +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Doctrine assumes that you use ``DateTime2`` data-types. If your legacy database contains DateTime +datatypes then you have to add your own data-type (see Basic Mapping for an example). diff --git a/docs/en/reference/metadata-drivers.rst b/docs/en/reference/metadata-drivers.rst new file mode 100644 index 000000000..6b9cb31e4 --- /dev/null +++ b/docs/en/reference/metadata-drivers.rst @@ -0,0 +1,194 @@ +Metadata Drivers +================ + +The heart of an object relational mapper is the mapping information +that glues everything together. It instructs the EntityManager how +it should behave when dealing with the different entities. + +Core Metadata Drivers +--------------------- + +Doctrine provides a few different ways for you to specify your +metadata: + + +- **XML files** (XmlDriver) +- **Class DocBlock Annotations** (AnnotationDriver) +- **YAML files** (YamlDriver) +- **PHP Code in files or static functions** (PhpDriver) + +Something important to note about the above drivers is they are all +an intermediate step to the same end result. The mapping +information is populated to ``Doctrine\ORM\Mapping\ClassMetadata`` +instances. So in the end, Doctrine only ever has to work with the +API of the ``ClassMetadata`` class to get mapping information for +an entity. + +.. note:: + + The populated ``ClassMetadata`` instances are also cached + so in a production environment the parsing and populating only ever + happens once. You can configure the metadata cache implementation + using the ``setMetadataCacheImpl()`` method on the + ``Doctrine\ORM\Configuration`` class: + + .. code-block:: php + + getConfiguration()->setMetadataCacheImpl(new ApcCache()); + + +If you want to use one of the included core metadata drivers you +just need to configure it. All the drivers are in the +``Doctrine\ORM\Mapping\Driver`` namespace: + +.. code-block:: php + + getConfiguration()->setMetadataDriverImpl($driver); + +Implementing Metadata Drivers +----------------------------- + +In addition to the included metadata drivers you can very easily +implement your own. All you need to do is define a class which +implements the ``Driver`` interface: + +.. code-block:: php + + _loadMappingFile($file); + + // populate ClassMetadataInfo instance from $data + } + + /** + * {@inheritdoc} + */ + protected function _loadMappingFile($file) + { + // parse contents of $file and return php data structure + } + } + +.. note:: + + When using the ``AbstractFileDriver`` it requires that you + only have one entity defined per file and the file named after the + class described inside where namespace separators are replaced by + periods. So if you have an entity named ``Entities\User`` and you + wanted to write a mapping file for your driver above you would need + to name the file ``Entities.User.dcm.ext`` for it to be + recognized. + + +Now you can use your ``MyMetadataDriver`` implementation by setting +it with the ``setMetadataDriverImpl()`` method: + +.. code-block:: php + + getConfiguration()->setMetadataDriverImpl($driver); + +ClassMetadata +------------- + +The last piece you need to know and understand about metadata in +Doctrine 2 is the API of the ``ClassMetadata`` classes. You need to +be familiar with them in order to implement your own drivers but +more importantly to retrieve mapping information for a certain +entity when needed. + +You have all the methods you need to manually specify the mapping +information instead of using some mapping file to populate it from. +The base ``ClassMetadataInfo`` class is responsible for only data +storage and is not meant for runtime use. It does not require that +the class actually exists yet so it is useful for describing some +entity before it exists and using that information to generate for +example the entities themselves. The class ``ClassMetadata`` +extends ``ClassMetadataInfo`` and adds some functionality required +for runtime usage and requires that the PHP class is present and +can be autoloaded. + +You can read more about the API of the ``ClassMetadata`` classes in +the PHP Mapping chapter. + +Getting ClassMetadata Instances +------------------------------- + +If you want to get the ``ClassMetadata`` instance for an entity in +your project to programmatically use some mapping information to +generate some HTML or something similar you can retrieve it through +the ``ClassMetadataFactory``: + +.. code-block:: php + + getMetadataFactory(); + $class = $cmf->getMetadataFor('MyEntityName'); + +Now you can learn about the entity and use the data stored in the +``ClassMetadata`` instance to get all mapped fields for example and +iterate over them: + +.. code-block:: php + + fieldMappings as $fieldMapping) { + echo $fieldMapping['fieldName'] . "\n"; + } + + diff --git a/docs/en/reference/namingstrategy.rst b/docs/en/reference/namingstrategy.rst new file mode 100644 index 000000000..f1e856b58 --- /dev/null +++ b/docs/en/reference/namingstrategy.rst @@ -0,0 +1,148 @@ +Implementing a NamingStrategy +============================== + +Using a naming strategy you can provide rules for automatically generating +database identifiers, columns and tables names +when the table/column name is not given. +This feature helps reduce the verbosity of the mapping document, +eliminating repetitive noise (eg: ``TABLE_``). + + +Configuring a naming strategy +----------------------------- +The default strategy used by Doctrine is quite minimal. + +By default the ``Doctrine\ORM\Mapping\DefaultNamingStrategy`` +uses the simple class name and the attributes names to generate tables and columns + +You can specify a different strategy by calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` : + +.. code-block:: php + + setNamingStrategy($namingStrategy); + +Underscore naming strategy +--------------------------- + +``\Doctrine\ORM\Mapping\UnderscoreNamingStrategy`` is a built-in strategy +that might be a useful if you want to use a underlying convention. + +.. code-block:: php + + setNamingStrategy($namingStrategy); + +Then SomeEntityName will generate the table SOME_ENTITY_NAME when CASE_UPPER +or some_entity_name using CASE_LOWER is given. + + +Naming strategy interface +------------------------- +The interface ``Doctrine\ORM\Mapping\NamingStrategy`` allows you to specify +a "naming standard" for database tables and columns. + +.. code-block:: php + + referenceColumnName(); + } + public function joinTableName($sourceEntity, $targetEntity, $propertyName = null) + { + return strtolower($this->classToTableName($sourceEntity) . '_' . + $this->classToTableName($targetEntity)); + } + public function joinKeyColumnName($entityName, $referencedColumnName = null) + { + return strtolower($this->classToTableName($entityName) . '_' . + ($referencedColumnName ?: $this->referenceColumnName())); + } + } + +Configuring the namingstrategy is easy if. +Just set your naming strategy calling ``Doctrine\ORM\Configuration#setNamingStrategy()`` :. + +.. code-block:: php + + setNamingStrategy($namingStrategy); \ No newline at end of file diff --git a/docs/en/reference/native-sql.rst b/docs/en/reference/native-sql.rst new file mode 100644 index 000000000..cf822f2bb --- /dev/null +++ b/docs/en/reference/native-sql.rst @@ -0,0 +1,870 @@ +Native SQL +========== + +A ``NativeQuery`` lets you execute native SELECT SQL statements, mapping the results +according to your specifications. Such a specification that +describes how an SQL result set is mapped to a Doctrine result is +represented by a ``ResultSetMapping``. It describes how each column +of the database result should be mapped by Doctrine in terms of the +object graph. This allows you to map arbitrary SQL code to objects, +such as highly vendor-optimized SQL or stored-procedures. + +Because writing ``ResultSetMapping`` is not so simple, there is a convenience +wrapper around it called a ``ResultSetMappingBuilder``. The last section +of this chapter describes its usage. + +.. note:: + + If you want to execute DELETE, UPDATE or INSERT statements + the Native SQL API cannot be used and will probably throw errors. + Use ``EntityManager#getConnection()`` to access the native database + connection and call the ``executeUpdate()`` method for these + queries. + +The NativeQuery class +--------------------- + +To create a ``NativeQuery`` you use the method +``EntityManager#createNativeQuery($sql, $resultSetMapping)``. As +you can see in the signature of this method, it expects 2 +ingredients: The SQL you want to execute and the +``ResultSetMapping`` that describes how the results will be +mapped. + +Once you obtained an instance of a ``NativeQuery``, you can bind +parameters to it and finally execute it. + +The ResultSetMapping +-------------------- + +Understanding the ``ResultSetMapping`` is the key to using a +``NativeQuery``. A Doctrine result can contain the following +components: + + +- Entity results. These represent root result elements. +- Joined entity results. These represent joined entities in + associations of root entity results. +- Field results. These represent a column in the result set that + maps to a field of an entity. A field result always belongs to an + entity result or joined entity result. +- Scalar results. These represent scalar values in the result set + that will appear in each result row. Adding scalar results to a + ResultSetMapping can also cause the overall result to become + **mixed** (see DQL - Doctrine Query Language) if the same + ResultSetMapping also contains entity results. +- Meta results. These represent columns that contain + meta-information, such as foreign keys and discriminator columns. + When querying for objects (``getResult()``), all meta columns of + root entities or joined entities must be present in the SQL query + and mapped accordingly using ``ResultSetMapping#addMetaResult``. + +.. note:: + + It might not surprise you that Doctrine uses + ``ResultSetMapping`` internally when you create DQL queries. As + the query gets parsed and transformed to SQL, Doctrine fills a + ``ResultSetMapping`` that describes how the results should be + processed by the hydration routines. + + +We will now look at each of the result types that can appear in a +ResultSetMapping in detail. + +Entity results +~~~~~~~~~~~~~~ + +An entity result describes an entity type that appears as a root +element in the transformed result. You add an entity result through +``ResultSetMapping#addEntityResult()``. Let's take a look at the +method signature in detail: + +.. code-block:: php + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + + $query = $this->_em->createNativeQuery('SELECT id, name FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +The result would look like this: + +.. code-block:: php + + array( + [0] => User (Object) + ) + +Note that this would be a partial object if the entity has more +fields than just id and name. In the example above the column and +field names are identical but that is not necessary, of course. +Also note that the query string passed to createNativeQuery is +**real native SQL**. Doctrine does not touch this SQL in any way. + +In the previous basic example, a User had no relations and the +table the class is mapped to owns no foreign keys. The next example +assumes User has a unidirectional or bidirectional one-to-one +association to a CmsAddress, where the User is the owning side and +thus owns the foreign key. + +.. code-block:: php + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'address_id', 'address_id'); + + $query = $this->_em->createNativeQuery('SELECT id, name, address_id FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Foreign keys are used by Doctrine for lazy-loading purposes when +querying for objects. In the previous example, each user object in +the result will have a proxy (a "ghost") in place of the address +that contains the address\_id. When the ghost proxy is accessed, it +loads itself based on this key. + +Consequently, associations that are *fetch-joined* do not require +the foreign keys to be present in the SQL result set, only +associations that are lazy. + +.. code-block:: php + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addJoinedEntityResult('Address' , 'a', 'u', 'address'); + $rsm->addFieldResult('a', 'address_id', 'id'); + $rsm->addFieldResult('a', 'street', 'street'); + $rsm->addFieldResult('a', 'city', 'city'); + + $sql = 'SELECT u.id, u.name, a.id AS address_id, a.street, a.city FROM users u ' . + 'INNER JOIN address a ON u.address_id = a.id WHERE u.name = ?'; + $query = $this->_em->createNativeQuery($sql, $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +In this case the nested entity ``Address`` is registered with the +``ResultSetMapping#addJoinedEntityResult`` method, which notifies +Doctrine that this entity is not hydrated at the root level, but as +a joined entity somewhere inside the object graph. In this case we +specify the alias 'u' as third parameter and ``address`` as fourth +parameter, which means the ``Address`` is hydrated into the +``User::$address`` property. + +If a fetched entity is part of a mapped hierarchy that requires a +discriminator column, this column must be present in the result set +as a meta column so that Doctrine can create the appropriate +concrete type. This is shown in the following example where we +assume that there are one or more subclasses that extend User and +either Class Table Inheritance or Single Table Inheritance is used +to map the hierarchy (both use a discriminator column). + +.. code-block:: php + + addEntityResult('User', 'u'); + $rsm->addFieldResult('u', 'id', 'id'); + $rsm->addFieldResult('u', 'name', 'name'); + $rsm->addMetaResult('u', 'discr', 'discr'); // discriminator column + $rsm->setDiscriminatorColumn('u', 'discr'); + + $query = $this->_em->createNativeQuery('SELECT id, name, discr FROM users WHERE name = ?', $rsm); + $query->setParameter(1, 'romanb'); + + $users = $query->getResult(); + +Note that in the case of Class Table Inheritance, an example as +above would result in partial objects if any objects in the result +are actually a subtype of User. When using DQL, Doctrine +automatically includes the necessary joins for this mapping +strategy but with native SQL it is your responsibility. + +ResultSetMappingBuilder +----------------------- + +There are some downsides with Native SQL queries. The primary one is that you have to adjust all result set mapping +definitions if names of columns change. In DQL this is detected dynamically when the Query is regenerated with +the current metadata. + +To avoid this hassle you can use the ``ResultSetMappingBuilder`` class. It allows to add all columns of an entity +to a result set mapping. To avoid clashes you can optionally rename specific columns when you are doing the same +in your sQL statement: + +.. code-block:: php + + addRootEntityFromClassMetadata('MyProject\User', 'u'); + $rsm->addJoinedEntityFromClassMetadata('MyProject\Address', 'a', 'u', 'address', array('id' => 'address_id')); + +For entities with more columns the builder is very convenient to use. It extends the ``ResultSetMapping`` class +and as such has all the functionality of it as well. Currently the ``ResultSetMappingBuilder`` does not support +entities with inheritance. + + +Named Native Query +------------------ + +You can also map a native query using a named native query mapping. + +To achieve that, you must describe the SQL resultset structure +using named native query (and sql resultset mappings if is a several resultset mappings). + +Like named query, a named native query can be defined at class level or in a XML or YAML file. + + +A resultSetMapping parameter is defined in @NamedNativeQuery, +it represents the name of a defined @SqlResultSetMapping. + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + MyProject\Model\User: + type: entity + namedNativeQueries: + fetchMultipleJoinsEntityResults: + name: fetchMultipleJoinsEntityResults + resultSetMapping: mappingMultipleJoinsEntityResults + query: SELECT u.id AS u_id, u.name AS u_name, u.status AS u_status, a.id AS a_id, a.zip AS a_zip, a.country AS a_country, COUNT(p.phonenumber) AS numphones FROM users u INNER JOIN addresses a ON u.id = a.user_id INNER JOIN phonenumbers p ON u.id = p.user_id GROUP BY u.id, u.name, u.status, u.username, a.id, a.zip, a.country ORDER BY u.username + sqlResultSetMappings: + mappingMultipleJoinsEntityResults: + name: mappingMultipleJoinsEntityResults + columnResult: + 0: + name: numphones + entityResult: + 0: + entityClass: __CLASS__ + fieldResult: + 0: + name: id + column: u_id + 1: + name: name + column: u_name + 2: + name: status + column: u_status + 1: + entityClass: Address + fieldResult: + 0: + name: id + column: a_id + 1: + name: zip + column: a_zip + 2: + name: country + column: a_country + + +Things to note: + - The resultset mapping declares the entities retrieved by this native query. + - Each field of the entity is bound to an SQL alias (or column name). + - All fields of the entity including the ones of subclasses + and the foreign key columns of related entities have to be present in the SQL query. + - Field definitions are optional provided that they map to the same + column name as the one declared on the class property. + - ``__CLASS__`` is a alias for the mapped class + + +In the above example, +the ``fetchJoinedAddress`` named query use the joinMapping result set mapping. +This mapping returns 2 entities, User and Address, each property is declared and associated to a column name, +actually the column name retrieved by the query. + +Let's now see an implicit declaration of the property / column. + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT * FROM addresses + + + + + + + + + + .. code-block:: yaml + + MyProject\Model\Address: + type: entity + namedNativeQueries: + findAll: + resultSetMapping: mappingFindAll + query: SELECT * FROM addresses + sqlResultSetMappings: + mappingFindAll: + name: mappingFindAll + entityResult: + address: + entityClass: Address + + +In this example, we only describe the entity member of the result set mapping. +The property / column mappings is done using the entity mapping values. +In this case the model property is bound to the model_txt column. +If the association to a related entity involve a composite primary key, +a @FieldResult element should be used for each foreign key column. +The @FieldResult name is composed of the property name for the relationship, +followed by a dot ("."), followed by the name or the field or property of the primary key. + + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ? + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + MyProject\Model\User: + type: entity + namedNativeQueries: + fetchJoinedAddress: + name: fetchJoinedAddress + resultSetMapping: mappingJoinedAddress + query: SELECT u.id, u.name, u.status, a.id AS a_id, a.country AS a_country, a.zip AS a_zip, a.city AS a_city FROM users u INNER JOIN addresses a ON u.id = a.user_id WHERE u.username = ? + sqlResultSetMappings: + mappingJoinedAddress: + entityResult: + 0: + entityClass: __CLASS__ + fieldResult: + 0: + name: id + 1: + name: name + 2: + name: status + 3: + name: address.id + column: a_id + 4: + name: address.zip + column: a_zip + 5: + name: address.city + column: a_city + 6: + name: address.country + column: a_country + + + +If you retrieve a single entity and if you use the default mapping, +you can use the resultClass attribute instead of resultSetMapping: + +.. configuration-block:: + + .. code-block:: php + + + + + + SELECT * FROM addresses WHERE id = ? + + + + + .. code-block:: yaml + + MyProject\Model\Address: + type: entity + namedNativeQueries: + findAll: + name: findAll + resultClass: Address + query: SELECT * FROM addresses + + +In some of your native queries, you'll have to return scalar values, +for example when building report queries. +You can map them in the @SqlResultsetMapping through @ColumnResult. +You actually can even mix, entities and scalar returns in the same native query (this is probably not that common though). + +.. configuration-block:: + + .. code-block:: php + + + + + SELECT COUNT(*) AS count FROM addresses + + + + + + + + + .. code-block:: yaml + + MyProject\Model\Address: + type: entity + namedNativeQueries: + count: + name: count + resultSetMapping: mappingCount + query: SELECT COUNT(*) AS count FROM addresses + sqlResultSetMappings: + mappingCount: + name: mappingCount + columnResult: + count: + name: count \ No newline at end of file diff --git a/docs/en/reference/partial-objects.rst b/docs/en/reference/partial-objects.rst new file mode 100644 index 000000000..396eecce3 --- /dev/null +++ b/docs/en/reference/partial-objects.rst @@ -0,0 +1,90 @@ +Partial Objects +=============== + +A partial object is an object whose state is not fully initialized +after being reconstituted from the database and that is +disconnected from the rest of its data. The following section will +describe why partial objects are problematic and what the approach +of Doctrine2 to this problem is. + +.. note:: + + The partial object problem in general does not apply to + methods or queries where you do not retrieve the query result as + objects. Examples are: ``Query#getArrayResult()``, + ``Query#getScalarResult()``, ``Query#getSingleScalarResult()``, + etc. + +.. warning:: + + Use of partial objects is tricky. Fields that are not retrieved + from the database will not be updated by the UnitOfWork even if they + get changed in your objects. You can only promote a partial object + to a fully-loaded object by calling ``EntityManager#refresh()`` + or a DQL query with the refresh flag. + + +What is the problem? +-------------------- + +In short, partial objects are problematic because they are usually +objects with broken invariants. As such, code that uses these +partial objects tends to be very fragile and either needs to "know" +which fields or methods can be safely accessed or add checks around +every field access or method invocation. The same holds true for +the internals, i.e. the method implementations, of such objects. +You usually simply assume the state you need in the method is +available, after all you properly constructed this object before +you pushed it into the database, right? These blind assumptions can +quickly lead to null reference errors when working with such +partial objects. + +It gets worse with the scenario of an optional association (0..1 to +1). When the associated field is NULL, you don't know whether this +object does not have an associated object or whether it was simply +not loaded when the owning object was loaded from the database. + +These are reasons why many ORMs do not allow partial objects at all +and instead you always have to load an object with all its fields +(associations being proxied). One secure way to allow partial +objects is if the programming language/platform allows the ORM tool +to hook deeply into the object and instrument it in such a way that +individual fields (not only associations) can be loaded lazily on +first access. This is possible in Java, for example, through +bytecode instrumentation. In PHP though this is not possible, so +there is no way to have "secure" partial objects in an ORM with +transparent persistence. + +Doctrine, by default, does not allow partial objects. That means, +any query that only selects partial object data and wants to +retrieve the result as objects (i.e. ``Query#getResult()``) will +raise an exception telling you that partial objects are dangerous. +If you want to force a query to return you partial objects, +possibly as a performance tweak, you can use the ``partial`` +keyword as follows: + +.. code-block:: php + + createQuery("select partial u.{id,name} from MyApp\Domain\User u"); + +You can also get a partial reference instead of a proxy reference by +calling: + +.. code-block:: php + + getPartialReference('MyApp\Domain\User', 1); + +Partial references are objects with only the identifiers set as they +are passed to the second argument of the ``getPartialReference()`` method. +All other fields are null. + +When should I force partial objects? +------------------------------------ + +Mainly for optimization purposes, but be careful of premature +optimization as partial objects lead to potentially more fragile +code. + + diff --git a/docs/en/reference/php-mapping.rst b/docs/en/reference/php-mapping.rst new file mode 100644 index 000000000..90f00d9bc --- /dev/null +++ b/docs/en/reference/php-mapping.rst @@ -0,0 +1,310 @@ +PHP Mapping +=========== + +Doctrine 2 also allows you to provide the ORM metadata in the form +of plain PHP code using the ``ClassMetadata`` API. You can write +the code in PHP files or inside of a static function named +``loadMetadata($class)`` on the entity class itself. + +PHP Files +--------- + +If you wish to write your mapping information inside PHP files that +are named after the entity and included to populate the metadata +for an entity you can do so by using the ``PHPDriver``: + +.. code-block:: php + + getConfiguration()->setMetadataDriverImpl($driver); + +Now imagine we had an entity named ``Entities\User`` and we wanted +to write a mapping file for it using the above configured +``PHPDriver`` instance: + +.. code-block:: php + + mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer' + )); + + $metadata->mapField(array( + 'fieldName' => 'username', + 'type' => 'string' + )); + +Now we can easily retrieve the populated ``ClassMetadata`` instance +where the ``PHPDriver`` includes the file and the +``ClassMetadataFactory`` caches it for later retrieval: + +.. code-block:: php + + getClassMetadata('Entities\User'); + // or + $class = $em->getMetadataFactory()->getMetadataFor('Entities\User'); + +Static Function +--------------- + +In addition to the PHP files you can also specify your mapping +information inside of a static function defined on the entity class +itself. This is useful for cases where you want to keep your entity +and mapping information together but don't want to use annotations. +For this you just need to use the ``StaticPHPDriver``: + +.. code-block:: php + + getConfiguration()->setMetadataDriverImpl($driver); + +Now you just need to define a static function named +``loadMetadata($metadata)`` on your entity: + +.. code-block:: php + + mapField(array( + 'id' => true, + 'fieldName' => 'id', + 'type' => 'integer' + )); + + $metadata->mapField(array( + 'fieldName' => 'username', + 'type' => 'string' + )); + } + } + +ClassMetadataBuilder +-------------------- + +To ease the use of the ClassMetadata API (which is very raw) there is a ``ClassMetadataBuilder`` that you can use. + +.. code-block:: php + + createField('id', 'integer')->isPrimaryKey()->generatedValue()->build(); + $builder->addField('username', 'string'); + } + } + +The API of the ClassMetadataBuilder has the following methods with a fluent interface: + +- ``addField($name, $type, array $mapping)`` +- ``setMappedSuperclass()`` +- ``setReadOnly()`` +- ``setCustomRepositoryClass($className)`` +- ``setTable($name)`` +- ``addIndex(array $columns, $indexName)`` +- ``addUniqueConstraint(array $columns, $constraintName)`` +- ``addNamedQuery($name, $dqlQuery)`` +- ``setJoinedTableInheritance()`` +- ``setSingleTableInheritance()`` +- ``setDiscriminatorColumn($name, $type = 'string', $length = 255)`` +- ``addDiscriminatorMapClass($name, $class)`` +- ``setChangeTrackingPolicyDeferredExplicit()`` +- ``setChangeTrackingPolicyNotify()`` +- ``addLifecycleEvent($methodName, $event)`` +- ``addManyToOne($name, $targetEntity, $inversedBy = null)`` +- ``addInverseOneToOne($name, $targetEntity, $mappedBy)`` +- ``addOwningOneToOne($name, $targetEntity, $inversedBy = null)`` +- ``addOwningManyToMany($name, $targetEntity, $inversedBy = null)`` +- ``addInverseManyToMany($name, $targetEntity, $mappedBy)`` +- ``addOneToMany($name, $targetEntity, $mappedBy)`` + +It also has several methods that create builders (which are necessary for advanced mappings): + +- ``createField($name, $type)`` returns a ``FieldBuilder`` instance +- ``createManyToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance +- ``createOneToOne($name, $targetEntity)`` returns an ``AssociationBuilder`` instance +- ``createManyToMany($name, $targetEntity)`` returns an ``ManyToManyAssociationBuilder`` instance +- ``createOneToMany($name, $targetEntity)`` returns an ``OneToManyAssociationBuilder`` instance + +ClassMetadataInfo API +--------------------- + +The ``ClassMetadataInfo`` class is the base data object for storing +the mapping metadata for a single entity. It contains all the +getters and setters you need populate and retrieve information for +an entity. + +General Setters +~~~~~~~~~~~~~~~ + + +- ``setTableName($tableName)`` +- ``setPrimaryTable(array $primaryTableDefinition)`` +- ``setCustomRepositoryClass($repositoryClassName)`` +- ``setIdGeneratorType($generatorType)`` +- ``setIdGenerator($generator)`` +- ``setSequenceGeneratorDefinition(array $definition)`` +- ``setChangeTrackingPolicy($policy)`` +- ``setIdentifier(array $identifier)`` + +Inheritance Setters +~~~~~~~~~~~~~~~~~~~ + + +- ``setInheritanceType($type)`` +- ``setSubclasses(array $subclasses)`` +- ``setParentClasses(array $classNames)`` +- ``setDiscriminatorColumn($columnDef)`` +- ``setDiscriminatorMap(array $map)`` + +Field Mapping Setters +~~~~~~~~~~~~~~~~~~~~~ + + +- ``mapField(array $mapping)`` +- ``mapOneToOne(array $mapping)`` +- ``mapOneToMany(array $mapping)`` +- ``mapManyToOne(array $mapping)`` +- ``mapManyToMany(array $mapping)`` + +Lifecycle Callback Setters +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``addLifecycleCallback($callback, $event)`` +- ``setLifecycleCallbacks(array $callbacks)`` + +Versioning Setters +~~~~~~~~~~~~~~~~~~ + + +- ``setVersionMapping(array &$mapping)`` +- ``setVersioned($bool)`` +- ``setVersionField()`` + +General Getters +~~~~~~~~~~~~~~~ + + +- ``getTableName()`` +- ``getTemporaryIdTableName()`` + +Identifier Getters +~~~~~~~~~~~~~~~~~~ + + +- ``getIdentifierColumnNames()`` +- ``usesIdGenerator()`` +- ``isIdentifier($fieldName)`` +- ``isIdGeneratorIdentity()`` +- ``isIdGeneratorSequence()`` +- ``isIdGeneratorTable()`` +- ``isIdentifierNatural()`` +- ``getIdentifierFieldNames()`` +- ``getSingleIdentifierFieldName()`` +- ``getSingleIdentifierColumnName()`` + +Inheritance Getters +~~~~~~~~~~~~~~~~~~~ + + +- ``isInheritanceTypeNone()`` +- ``isInheritanceTypeJoined()`` +- ``isInheritanceTypeSingleTable()`` +- ``isInheritanceTypeTablePerClass()`` +- ``isInheritedField($fieldName)`` +- ``isInheritedAssociation($fieldName)`` + +Change Tracking Getters +~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``isChangeTrackingDeferredExplicit()`` +- ``isChangeTrackingDeferredImplicit()`` +- ``isChangeTrackingNotify()`` + +Field & Association Getters +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``isUniqueField($fieldName)`` +- ``isNullable($fieldName)`` +- ``getColumnName($fieldName)`` +- ``getFieldMapping($fieldName)`` +- ``getAssociationMapping($fieldName)`` +- ``getAssociationMappings()`` +- ``getFieldName($columnName)`` +- ``hasField($fieldName)`` +- ``getColumnNames(array $fieldNames = null)`` +- ``getTypeOfField($fieldName)`` +- ``getTypeOfColumn($columnName)`` +- ``hasAssociation($fieldName)`` +- ``isSingleValuedAssociation($fieldName)`` +- ``isCollectionValuedAssociation($fieldName)`` + +Lifecycle Callback Getters +~~~~~~~~~~~~~~~~~~~~~~~~~~ + + +- ``hasLifecycleCallbacks($lifecycleEvent)`` +- ``getLifecycleCallbacks($event)`` + +ClassMetadata API +----------------- + +The ``ClassMetadata`` class extends ``ClassMetadataInfo`` and adds +the runtime functionality required by Doctrine. It adds a few extra +methods related to runtime reflection for working with the entities +themselves. + + +- ``getReflectionClass()`` +- ``getReflectionProperties()`` +- ``getReflectionProperty($name)`` +- ``getSingleIdReflectionProperty()`` +- ``getIdentifierValues($entity)`` +- ``setIdentifierValues($entity, $id)`` +- ``setFieldValue($entity, $field, $value)`` +- ``getFieldValue($entity, $field)`` + + diff --git a/docs/en/reference/query-builder.rst b/docs/en/reference/query-builder.rst new file mode 100644 index 000000000..7d9ee9a8a --- /dev/null +++ b/docs/en/reference/query-builder.rst @@ -0,0 +1,541 @@ +The QueryBuilder +================ + +A ``QueryBuilder`` provides an API that is designed for +conditionally constructing a DQL query in several steps. + +It provides a set of classes and methods that is able to +programmatically build queries, and also provides a fluent API. +This means that you can change between one methodology to the other +as you want, and also pick one if you prefer. + +Constructing a new QueryBuilder object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The same way you build a normal Query, you build a ``QueryBuilder`` +object, just providing the correct method name. Here is an example +how to build a ``QueryBuilder`` object: + +.. code-block:: php + + createQueryBuilder(); + +Once you have created an instance of QueryBuilder, it provides a +set of useful informative functions that you can use. One good +example is to inspect what type of object the ``QueryBuilder`` is. + +.. code-block:: php + + getType(); // Prints: 0 + +There're currently 3 possible return values for ``getType()``: + + +- ``QueryBuilder::SELECT``, which returns value 0 +- ``QueryBuilder::DELETE``, returning value 1 +- ``QueryBuilder::UPDATE``, which returns value 2 + +It is possible to retrieve the associated ``EntityManager`` of the +current ``QueryBuilder``, its DQL and also a ``Query`` object when +you finish building your DQL. + +.. code-block:: php + + getEntityManager(); + + // example4: retrieve the DQL string of what was defined in QueryBuilder + $dql = $qb->getDql(); + + // example5: retrieve the associated Query object with the processed DQL + $q = $qb->getQuery(); + +Internally, ``QueryBuilder`` works with a DQL cache to increase +performance. Any changes that may affect the generated DQL actually +modifies the state of ``QueryBuilder`` to a stage we call +STATE\_DIRTY. One ``QueryBuilder`` can be in two different states: + + +- ``QueryBuilder::STATE_CLEAN``, which means DQL haven't been + altered since last retrieval or nothing were added since its + instantiation +- ``QueryBuilder::STATE_DIRTY``, means DQL query must (and will) + be processed on next retrieval + +Working with QueryBuilder +~~~~~~~~~~~~~~~~~~~~~~~~~ + + +High level API methods +^^^^^^^^^^^^^^^^^^^^^^ + +To simplify even more the way you build a query in Doctrine, we can take +advantage of what we call Helper methods. For all base code, there +is a set of useful methods to simplify a programmer's life. To +illustrate how to work with them, here is the same example 6 +re-written using ``QueryBuilder`` helper methods: + +.. code-block:: php + + select('u') + ->from('User', 'u') + ->where('u.id = ?1') + ->orderBy('u.name', 'ASC'); + +``QueryBuilder`` helper methods are considered the standard way to +build DQL queries. Although it is supported, it should be avoided +to use string based queries and greatly encouraged to use +``$qb->expr()->*`` methods. Here is a converted example 8 to +suggested standard way to build queries: + +.. code-block:: php + + select(array('u')) // string 'u' is converted to array internally + ->from('User', 'u') + ->where($qb->expr()->orX( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->orderBy('u.surname', 'ASC')); + +Here is a complete list of helper methods available in ``QueryBuilder``: + +.. code-block:: php + + select('u') + // Example - $qb->select(array('u', 'p')) + // Example - $qb->select($qb->expr()->select('u', 'p')) + public function select($select = null); + + // Example - $qb->delete('User', 'u') + public function delete($delete = null, $alias = null); + + // Example - $qb->update('Group', 'g') + public function update($update = null, $alias = null); + + // Example - $qb->set('u.firstName', $qb->expr()->literal('Arnold')) + // Example - $qb->set('u.numChilds', 'u.numChilds + ?1') + // Example - $qb->set('u.numChilds', $qb->expr()->sum('u.numChilds', '?1')) + public function set($key, $value); + + // Example - $qb->from('Phonenumber', 'p') + public function from($from, $alias = null); + + // Example - $qb->innerJoin('u.Group', 'g', Expr\Join::WITH, $qb->expr()->eq('u.status_id', '?1')) + // Example - $qb->innerJoin('u.Group', 'g', 'WITH', 'u.status = ?1') + public function innerJoin($join, $alias = null, $conditionType = null, $condition = null); + + // Example - $qb->leftJoin('u.Phonenumbers', 'p', Expr\Join::WITH, $qb->expr()->eq('p.area_code', 55)) + // Example - $qb->leftJoin('u.Phonenumbers', 'p', 'WITH', 'p.area_code = 55') + public function leftJoin($join, $alias = null, $conditionType = null, $condition = null); + + // NOTE: ->where() overrides all previously set conditions + // + // Example - $qb->where('u.firstName = ?1', $qb->expr()->eq('u.surname', '?2')) + // Example - $qb->where($qb->expr()->andX($qb->expr()->eq('u.firstName', '?1'), $qb->expr()->eq('u.surname', '?2'))) + // Example - $qb->where('u.firstName = ?1 AND u.surname = ?2') + public function where($where); + + // Example - $qb->andWhere($qb->expr()->orX($qb->expr()->lte('u.age', 40), 'u.numChild = 0')) + public function andWhere($where); + + // Example - $qb->orWhere($qb->expr()->between('u.id', 1, 10)); + public function orWhere($where); + + // NOTE: -> groupBy() overrides all previously set grouping conditions + // + // Example - $qb->groupBy('u.id') + public function groupBy($groupBy); + + // Example - $qb->addGroupBy('g.name') + public function addGroupBy($groupBy); + + // NOTE: -> having() overrides all previously set having conditions + // + // Example - $qb->having('u.salary >= ?1') + // Example - $qb->having($qb->expr()->gte('u.salary', '?1')) + public function having($having); + + // Example - $qb->andHaving($qb->expr()->gt($qb->expr()->count('u.numChild'), 0)) + public function andHaving($having); + + // Example - $qb->orHaving($qb->expr()->lte('g.managerLevel', '100')) + public function orHaving($having); + + // NOTE: -> orderBy() overrides all previously set ordering conditions + // + // Example - $qb->orderBy('u.surname', 'DESC') + public function orderBy($sort, $order = null); + + // Example - $qb->addOrderBy('u.firstName') + public function addOrderBy($sort, $order = null); // Default $order = 'ASC' + } + +Binding parameters to your query +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Doctrine supports dynamic binding of parameters to your query, +similar to preparing queries. You can use both strings and numbers +as placeholders, although both have a slightly different syntax. +Additionally, you must make your choice: Mixing both styles is not +allowed. Binding parameters can simply be achieved as follows: + +.. code-block:: php + + select('u') + ->from('User u') + ->where('u.id = ?1') + ->orderBy('u.name', 'ASC'); + ->setParameter(1, 100); // Sets ?1 to 100, and thus we will fetch a user with u.id = 100 + +You are not forced to enumerate your placeholders as the +alternative syntax is available: + +.. code-block:: php + + select('u') + ->from('User u') + ->where('u.id = :identifier') + ->orderBy('u.name ASC'); + ->setParameter('identifier', 100); // Sets :identifier to 100, and thus we will fetch a user with u.id = 100 + +Note that numeric placeholders start with a ? followed by a number +while the named placeholders start with a : followed by a string. + +Calling ``setParameter()`` automatically infers which type you are setting as +value. This works for integers, arrays of strings/integers, DateTime instances +and for managed entities. If you want to set a type explicitly you can call +the third argument to ``setParameter()`` explicitly. It accepts either a PDO +type or a DBAL Type name for conversion. + +If you've got several parameters to bind to your query, you can +also use setParameters() instead of setParameter() with the +following syntax: + +.. code-block:: php + + setParameters(array(1 => 'value for ?1', 2 => 'value for ?2')); + +Getting already bound parameters is easy - simply use the above +mentioned syntax with "getParameter()" or "getParameters()": + +.. code-block:: php + + getParameters(); + // $params instanceof \Doctrine\Common\Collections\ArrayCollection + + // Equivalent to + $param = $qb->getParameter(1); + // $param instanceof \Doctrine\ORM\Query\Parameter + +Note: If you try to get a parameter that was not bound yet, +getParameter() simply returns NULL. + +The API of a Query Parameter is: + +.. code-block:: php + + namespace Doctrine\ORM\Query; + + class Parameter + { + public function getName(); + public function getValue(); + public function getType(); + public function setValue($value, $type = null); + } + +Limiting the Result +^^^^^^^^^^^^^^^^^^^ + +To limit a result the query builder has some methods in common with +the Query object which can be retrieved from ``EntityManager#createQuery()``. + +.. code-block:: php + + add('select', 'u') + ->add('from', 'User u') + ->add('orderBy', 'u.name ASC') + ->setFirstResult( $offset ) + ->setMaxResults( $limit ); + +Executing a Query +^^^^^^^^^^^^^^^^^ + +The QueryBuilder is a builder object only, it has no means of actually +executing the Query. Additionally a set of parameters such as query hints +cannot be set on the QueryBuilder itself. This is why you always have to convert +a querybuilder instance into a Query object: + +.. code-block:: php + + getQuery(); + + // Set additional Query options + $query->setQueryHint('foo', 'bar'); + $query->useResultCache('my_cache_id'); + + // Execute Query + $result = $query->getResult(); + $single = $query->getSingleResult(); + $array = $query->getArrayResult(); + $scalar = $query->getScalarResult(); + $singleScalar = $query->getSingleScalarResult(); + +The Expr class +^^^^^^^^^^^^^^ + +To workaround some of the issues that ``add()`` method may cause, +Doctrine created a class that can be considered as a helper for +building expressions. This class is called ``Expr``, which provides a +set of useful methods to help build expressions: + +.. code-block:: php + + add('select', new Expr\Select(array('u'))) + ->add('from', new Expr\From('User', 'u')) + ->add('where', $qb->expr()->orX( + $qb->expr()->eq('u.id', '?1'), + $qb->expr()->like('u.nickname', '?2') + )) + ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); + +Although it still sounds complex, the ability to programmatically +create conditions are the main feature of ``Expr``. Here it is a +complete list of supported helper methods available: + +.. code-block:: php + + expr()->andX($cond1 [, $condN])->add(...)->... + public function andX($x = null); // Returns Expr\AndX instance + + // Example - $qb->expr()->orX($cond1 [, $condN])->add(...)->... + public function orX($x = null); // Returns Expr\OrX instance + + + /** Comparison objects **/ + + // Example - $qb->expr()->eq('u.id', '?1') => u.id = ?1 + public function eq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->neq('u.id', '?1') => u.id <> ?1 + public function neq($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lt('u.id', '?1') => u.id < ?1 + public function lt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->lte('u.id', '?1') => u.id <= ?1 + public function lte($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gt('u.id', '?1') => u.id > ?1 + public function gt($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->gte('u.id', '?1') => u.id >= ?1 + public function gte($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->isNull('u.id') => u.id IS NULL + public function isNull($x); // Returns string + + // Example - $qb->expr()->isNotNull('u.id') => u.id IS NOT NULL + public function isNotNull($x); // Returns string + + + /** Arithmetic objects **/ + + // Example - $qb->expr()->prod('u.id', '2') => u.id * 2 + public function prod($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->diff('u.id', '2') => u.id - 2 + public function diff($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->sum('u.id', '2') => u.id + 2 + public function sum($x, $y); // Returns Expr\Math instance + + // Example - $qb->expr()->quot('u.id', '2') => u.id / 2 + public function quot($x, $y); // Returns Expr\Math instance + + + /** Pseudo-function objects **/ + + // Example - $qb->expr()->exists($qb2->getDql()) + public function exists($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->all($qb2->getDql()) + public function all($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->some($qb2->getDql()) + public function some($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->any($qb2->getDql()) + public function any($subquery); // Returns Expr\Func instance + + // Example - $qb->expr()->not($qb->expr()->eq('u.id', '?1')) + public function not($restriction); // Returns Expr\Func instance + + // Example - $qb->expr()->in('u.id', array(1, 2, 3)) + // Make sure that you do NOT use something similar to $qb->expr()->in('value', array('stringvalue')) as this will cause Doctrine to throw an Exception. + // Instead, use $qb->expr()->in('value', array('?1')) and bind your parameter to ?1 (see section above) + public function in($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->notIn('u.id', '2') + public function notIn($x, $y); // Returns Expr\Func instance + + // Example - $qb->expr()->like('u.firstname', $qb->expr()->literal('Gui%')) + public function like($x, $y); // Returns Expr\Comparison instance + + // Example - $qb->expr()->between('u.id', '1', '10') + public function between($val, $x, $y); // Returns Expr\Func + + + /** Function objects **/ + + // Example - $qb->expr()->trim('u.firstname') + public function trim($x); // Returns Expr\Func + + // Example - $qb->expr()->concat('u.firstname', $qb->expr()->concat($qb->expr()->literal(' '), 'u.lastname')) + public function concat($x, $y); // Returns Expr\Func + + // Example - $qb->expr()->substr('u.firstname', 0, 1) + public function substr($x, $from, $len); // Returns Expr\Func + + // Example - $qb->expr()->lower('u.firstname') + public function lower($x); // Returns Expr\Func + + // Example - $qb->expr()->upper('u.firstname') + public function upper($x); // Returns Expr\Func + + // Example - $qb->expr()->length('u.firstname') + public function length($x); // Returns Expr\Func + + // Example - $qb->expr()->avg('u.age') + public function avg($x); // Returns Expr\Func + + // Example - $qb->expr()->max('u.age') + public function max($x); // Returns Expr\Func + + // Example - $qb->expr()->min('u.age') + public function min($x); // Returns Expr\Func + + // Example - $qb->expr()->abs('u.currentBalance') + public function abs($x); // Returns Expr\Func + + // Example - $qb->expr()->sqrt('u.currentBalance') + public function sqrt($x); // Returns Expr\Func + + // Example - $qb->expr()->count('u.firstname') + public function count($x); // Returns Expr\Func + + // Example - $qb->expr()->countDistinct('u.surname') + public function countDistinct($x); // Returns Expr\Func + } + + +Low Level API +^^^^^^^^^^^^^ + +Now we have describe the low level (thought of as the +hardcore method) of creating queries. It may be useful to work at +this level for optimization purposes, but most of the time it is +preferred to work at a higher level of abstraction. + +All helper methods in ``QueryBuilder`` actually rely on a single +one: ``add()``. This method is responsible of building every piece +of DQL. It takes 3 parameters: ``$dqlPartName``, ``$dqlPart`` and +``$append`` (default=false) + + +- ``$dqlPartName``: Where the ``$dqlPart`` should be placed. + Possible values: select, from, where, groupBy, having, orderBy +- ``$dqlPart``: What should be placed in ``$dqlPartName``. Accepts + a string or any instance of ``Doctrine\ORM\Query\Expr\*`` +- ``$append``: Optional flag (default=false) if the ``$dqlPart`` + should override all previously defined items in ``$dqlPartName`` or + not (no effect on the ``where`` and ``having`` DQL query parts, + which always override all previously defined items) + +- + +.. code-block:: php + + add('select', 'u') + ->add('from', 'User u') + ->add('where', 'u.id = ?1') + ->add('orderBy', 'u.name ASC'); + +Expr\* classes +^^^^^^^^^^^^^^ + +When you call ``add()`` with string, it internally evaluates to an +instance of ``Doctrine\ORM\Query\Expr\Expr\*`` class. Here is the +same query of example 6 written using +``Doctrine\ORM\Query\Expr\Expr\*`` classes: + +.. code-block:: php + + add('select', new Expr\Select(array('u'))) + ->add('from', new Expr\From('User', 'u')) + ->add('where', new Expr\Comparison('u.id', '=', '?1')) + ->add('orderBy', new Expr\OrderBy('u.name', 'ASC')); + +Of course this is the hardest way to build a DQL query in Doctrine. +To simplify some of these efforts, we introduce what we call as +``Expr`` helper class. + diff --git a/docs/en/reference/tools.rst b/docs/en/reference/tools.rst new file mode 100644 index 000000000..99afd35b0 --- /dev/null +++ b/docs/en/reference/tools.rst @@ -0,0 +1,526 @@ +Tools +===== + +Doctrine Console +---------------- + +The Doctrine Console is a Command Line Interface tool for +simplifying common tasks during the development of a project that +uses Doctrine 2. + +Take a look at the :doc:`Configuration ` for more +information how to setup the console command. + +Getting Help +~~~~~~~~~~~~ + +Type ``doctrine`` on the command line and you should see an +overview of the available commands or use the --help flag to get +information on the available commands. If you want to know more +about the use of generate entities for example, you can call: + +.. code-block:: php + + doctrine orm:generate-entities --help + + +Configuration (PEAR) +~~~~~~~~~~~~~~~~~~~~ + +Whenever the ``doctrine`` command line tool is invoked, it can +access all Commands that were registered by developer. There is no +auto-detection mechanism at work. The Doctrine binary +already registers all the commands that currently ship with +Doctrine DBAL and ORM. If you want to use additional commands you +have to register them yourself. + +All the commands of the Doctrine Console require access to the EntityManager +or DBAL Connection. You have to inject them into the console application +using so called Helper-Sets. This requires either the ``db`` +or the ``em`` helpers to be defined in order to work correctly. + +Whenever you invoke the Doctrine binary the current folder is searched for a +``cli-config.php`` file. This file contains the project specific configuration: + +.. code-block:: php + + new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn) + )); + $cli->setHelperSet($helperSet); + +When dealing with the ORM package, the EntityManagerHelper is +required: + +.. code-block:: php + + new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) + )); + $cli->setHelperSet($helperSet); + +The HelperSet instance has to be generated in a separate file (i.e. +``cli-config.php``) that contains typical Doctrine bootstrap code +and predefines the needed HelperSet attributes mentioned above. A +sample ``cli-config.php`` file looks as follows: + +.. code-block:: php + + new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($em->getConnection()), + 'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em) + )); + +It is important to define a correct HelperSet that Doctrine binary +script will ultimately use. The Doctrine Binary will automatically +find the first instance of HelperSet in the global variable +namespace and use this. + +.. note:: + + You have to adjust this snippet for your specific application or framework + and use their facilities to access the Doctrine EntityManager and + Connection Resources. + +Configuration (Non-PEAR) +~~~~~~~~~~~~~~~~~~~~~~~~ + +If you do not use a PEAR installation of Doctrine you have to define your own +Doctrine binary. Put this file into the application root and invoke it from +there whenever you want to access the Doctrine console. + +.. code-block:: php + + new ConnectionHelper($em->getConnection()), + 'em' => new EntityManagerHelper($em) + )); + + ConsoleRunner::run($helperSet); + + +Command Overview +~~~~~~~~~~~~~~~~ + +The following Commands are currently available: + + +- ``help`` Displays help for a command (?) +- ``list`` Lists commands +- ``dbal:import`` Import SQL file(s) directly to Database. +- ``dbal:run-sql`` Executes arbitrary SQL directly from the + command line. +- ``orm:clear-cache:metadata`` Clear all metadata cache of the + various cache drivers. +- ``orm:clear-cache:query`` Clear all query cache of the various + cache drivers. +- ``orm:clear-cache:result`` Clear result cache of the various + cache drivers. +- ``orm:convert-d1-schema`` Converts Doctrine 1.X schema into a + Doctrine 2.X schema. +- ``orm:convert-mapping`` Convert mapping information between + supported formats. +- ``orm:ensure-production-settings`` Verify that Doctrine is + properly configured for a production environment. +- ``orm:generate-entities`` Generate entity classes and method + stubs from your mapping information. +- ``orm:generate-proxies`` Generates proxy classes for entity + classes. +- ``orm:generate-repositories`` Generate repository classes from + your mapping information. +- ``orm:run-dql`` Executes arbitrary DQL directly from the command + line. +- ``orm:schema-tool:create`` Processes the schema and either + create it directly on EntityManager Storage Connection or generate + the SQL output. +- ``orm:schema-tool:drop`` Processes the schema and either drop + the database schema of EntityManager Storage Connection or generate + the SQL output. +- ``orm:schema-tool:update`` Processes the schema and either + update the database schema of EntityManager Storage Connection or + generate the SQL output. + +Database Schema Generation +-------------------------- + +.. note:: + + SchemaTool can do harm to your database. It will drop or alter + tables, indexes, sequences and such. Please use this tool with + caution in development and not on a production server. It is meant + for helping you develop your Database Schema, but NOT with + migrating schema from A to B in production. A safe approach would + be generating the SQL on development server and saving it into SQL + Migration files that are executed manually on the production + server. + + SchemaTool assumes your Doctrine Project uses the given database on + its own. Update and Drop commands will mess with other tables if + they are not related to the current project that is using Doctrine. + Please be careful! + + +To generate your database schema from your Doctrine mapping files +you can use the ``SchemaTool`` class or the ``schema-tool`` Console +Command. + +When using the SchemaTool class directly, create your schema using +the ``createSchema()`` method. First create an instance of the +``SchemaTool`` and pass it an instance of the ``EntityManager`` +that you want to use to create the schema. This method receives an +array of ``ClassMetadataInfo`` instances. + +.. code-block:: php + + getClassMetadata('Entities\User'), + $em->getClassMetadata('Entities\Profile') + ); + $tool->createSchema($classes); + +To drop the schema you can use the ``dropSchema()`` method. + +.. code-block:: php + + dropSchema($classes); + +This drops all the tables that are currently used by your metadata +model. When you are changing your metadata a lot during development +you might want to drop the complete database instead of only the +tables of the current model to clean up with orphaned tables. + +.. code-block:: php + + dropSchema($classes, \Doctrine\ORM\Tools\SchemaTool::DROP_DATABASE); + +You can also use database introspection to update your schema +easily with the ``updateSchema()`` method. It will compare your +existing database schema to the passed array of +``ClassMetdataInfo`` instances. + +.. code-block:: php + + updateSchema($classes); + +If you want to use this functionality from the command line you can +use the ``schema-tool`` command. + +To create the schema use the ``create`` command: + +.. code-block:: php + + $ php doctrine orm:schema-tool:create + +To drop the schema use the ``drop`` command: + +.. code-block:: php + + $ php doctrine orm:schema-tool:drop + +If you want to drop and then recreate the schema then use both +options: + +.. code-block:: php + + $ php doctrine orm:schema-tool:drop + $ php doctrine orm:schema-tool:create + +As you would think, if you want to update your schema use the +``update`` command: + +.. code-block:: php + + $ php doctrine orm:schema-tool:update + +All of the above commands also accept a ``--dump-sql`` option that +will output the SQL for the ran operation. + +.. code-block:: php + + $ php doctrine orm:schema-tool:create --dump-sql + +Before using the orm:schema-tool commands, remember to configure +your cli-config.php properly. + +.. note:: + + When using the Annotation Mapping Driver you have to either setup + your autoloader in the cli-config.php correctly to find all the + entities, or you can use the second argument of the + ``EntityManagerHelper`` to specify all the paths of your entities + (or mapping files), i.e. + ``new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($em, $mappingPaths);`` + +Entity Generation +----------------- + +Generate entity classes and method stubs from your mapping information. + +.. code-block:: php + + $ php doctrine orm:generate-entities + $ php doctrine orm:generate-entities --update-entities + $ php doctrine orm:generate-entities --regenerate-entities + +This command is not suited for constant usage. It is a little helper and does +not support all the mapping edge cases very well. You still have to put work +in your entities after using this command. + +It is possible to use the EntityGenerator on code that you have already written. It will +not be lost. The EntityGenerator will only append new code to your +file and will not delete the old code. However this approach may still be prone +to error and we suggest you use code repositories such as GIT or SVN to make +backups of your code. + +It makes sense to generate the entity code if you are using entities as Data +Access Objects only and don't put much additional logic on them. If you are +however putting much more logic on the entities you should refrain from using +the entity-generator and code your entities manually. + +.. note:: + + Even if you specified Inheritance options in your + XML or YAML Mapping files the generator cannot generate the base and + child classes for you correctly, because it doesn't know which + class is supposed to extend which. You have to adjust the entity + code manually for inheritance to work! + + +Convert Mapping Information +--------------------------- + +Convert mapping information between supported formats. + +This is an **execute one-time** command. It should not be necessary for +you to call this method multiple times, especially when using the ``--from-database`` +flag. + +Converting an existing database schema into mapping files only solves about 70-80% +of the necessary mapping information. Additionally the detection from an existing +database cannot detect inverse associations, inheritance types, +entities with foreign keys as primary keys and many of the +semantical operations on associations such as cascade. + +.. note:: + + There is no need to convert YAML or XML mapping files to annotations + every time you make changes. All mapping drivers are first class citizens + in Doctrine 2 and can be used as runtime mapping for the ORM. See the + docs on XML and YAML Mapping for an example how to register this metadata + drivers as primary mapping source. + +To convert some mapping information between the various supported +formats you can use the ``ClassMetadataExporter`` to get exporter +instances for the different formats: + +.. code-block:: php + + getExporter('yml', '/path/to/export/yml'); + +Now you can export some ``ClassMetadata`` instances: + +.. code-block:: php + + getClassMetadata('Entities\User'), + $em->getClassMetadata('Entities\Profile') + ); + $exporter->setMetadata($classes); + $exporter->export(); + +This functionality is also available from the command line to +convert your loaded mapping information to another format. The +``orm:convert-mapping`` command accepts two arguments, the type to +convert to and the path to generate it: + +.. code-block:: php + + $ php doctrine orm:convert-mapping xml /path/to/mapping-path-converted-to-xml + +Reverse Engineering +------------------- + +You can use the ``DatabaseDriver`` to reverse engineer a database +to an array of ``ClassMetadataInfo`` instances and generate YAML, +XML, etc. from them. + +.. note:: + + Reverse Engineering is a **one-time** process that can get you started with a project. + Converting an existing database schema into mapping files only detects about 70-80% + of the necessary mapping information. Additionally the detection from an existing + database cannot detect inverse associations, inheritance types, + entities with foreign keys as primary keys and many of the + semantical operations on associations such as cascade. + +First you need to retrieve the metadata instances with the +``DatabaseDriver``: + +.. code-block:: php + + getConfiguration()->setMetadataDriverImpl( + new \Doctrine\ORM\Mapping\Driver\DatabaseDriver( + $em->getConnection()->getSchemaManager() + ) + ); + + $cmf = new DisconnectedClassMetadataFactory(); + $cmf->setEntityManager($em); + $metadata = $cmf->getAllMetadata(); + +Now you can get an exporter instance and export the loaded metadata +to yml: + +.. code-block:: php + + getExporter('yml', '/path/to/export/yml'); + $exporter->setMetadata($metadata); + $exporter->export(); + +You can also reverse engineer a database using the +``orm:convert-mapping`` command: + +.. code-block:: php + + $ php doctrine orm:convert-mapping --from-database yml /path/to/mapping-path-converted-to-yml + +.. note:: + + Reverse Engineering is not always working perfectly + depending on special cases. It will only detect Many-To-One + relations (even if they are One-To-One) and will try to create + entities from Many-To-Many tables. It also has problems with naming + of foreign keys that have multiple column names. Any Reverse + Engineered Database-Schema needs considerable manual work to become + a useful domain model. + + +Runtime vs Development Mapping Validation +----------------------------------------- + +For performance reasons Doctrine 2 has to skip some of the +necessary validation of metadata mappings. You have to execute +this validation in your development workflow to verify the +associations are correctly defined. + +You can either use the Doctrine Command Line Tool: + +.. code-block:: php + + doctrine orm:validate-schema + +Or you can trigger the validation manually: + +.. code-block:: php + + validateMapping(); + + if (count($errors) > 0) { + // Lots of errors! + echo implode("\n\n", $errors); + } + +If the mapping is invalid the errors array contains a positive +number of elements with error messages. + +.. warning:: + + One mapping option that is not validated is the use of the referenced column name. + It has to point to the equivalent primary key otherwise Doctrine will not work. + +.. note:: + + One common error is to use a backlash in front of the + fully-qualified class-name. Whenever a FQCN is represented inside a + string (such as in your mapping definitions) you have to drop the + prefix backslash. PHP does this with ``get_class()`` or Reflection + methods for backwards compatibility reasons. + + +Adding own commands +------------------- + +You can also add your own commands on-top of the Doctrine supported +tools if you are using a manually built (Non-PEAR) binary. + +To include a new command on Doctrine Console, you need to do modify the +``doctrine.php`` file a little: + +.. code-block:: php + + setCatchExceptions(true); + $cli->setHelperSet($helperSet); + + // Register All Doctrine Commands + ConsoleRunner::addCommands($cli); + + // Register your own command + $cli->addCommand(new \MyProject\Tools\Console\Commands\MyCustomCommand); + + // Runs console application + $cli->run(); + +Additionally, include multiple commands (and overriding previously +defined ones) is possible through the command: + +.. code-block:: php + + addCommands(array( + new \MyProject\Tools\Console\Commands\MyCustomCommand(), + new \MyProject\Tools\Console\Commands\SomethingCommand(), + new \MyProject\Tools\Console\Commands\AnotherCommand(), + new \MyProject\Tools\Console\Commands\OneMoreCommand(), + )); diff --git a/docs/en/reference/transactions-and-concurrency.rst b/docs/en/reference/transactions-and-concurrency.rst new file mode 100644 index 000000000..0a752ad5b --- /dev/null +++ b/docs/en/reference/transactions-and-concurrency.rst @@ -0,0 +1,353 @@ +Transactions and Concurrency +============================ + +Transaction Demarcation +----------------------- + +Transaction demarcation is the task of defining your transaction +boundaries. Proper transaction demarcation is very important +because if not done properly it can negatively affect the +performance of your application. Many databases and database +abstraction layers like PDO by default operate in auto-commit mode, +which means that every single SQL statement is wrapped in a small +transaction. Without any explicit transaction demarcation from your +side, this quickly results in poor performance because transactions +are not cheap. + +For the most part, Doctrine 2 already takes care of proper +transaction demarcation for you: All the write operations +(INSERT/UPDATE/DELETE) are queued until ``EntityManager#flush()`` +is invoked which wraps all of these changes in a single +transaction. + +However, Doctrine 2 also allows (and encourages) you to take over +and control transaction demarcation yourself. + +These are two ways to deal with transactions when using the +Doctrine ORM and are now described in more detail. + +Approach 1: Implicitly +~~~~~~~~~~~~~~~~~~~~~~ + +The first approach is to use the implicit transaction handling +provided by the Doctrine ORM EntityManager. Given the following +code snippet, without any explicit transaction demarcation: + +.. code-block:: php + + setName('George'); + $em->persist($user); + $em->flush(); + +Since we do not do any custom transaction demarcation in the above +code, ``EntityManager#flush()`` will begin and commit/rollback a +transaction. This behavior is made possible by the aggregation of +the DML operations by the Doctrine ORM and is sufficient if all the +data manipulation that is part of a unit of work happens through +the domain model and thus the ORM. + +Approach 2: Explicitly +~~~~~~~~~~~~~~~~~~~~~~ + +The explicit alternative is to use the ``Doctrine\DBAL\Connection`` +API directly to control the transaction boundaries. The code then +looks like this: + +.. code-block:: php + + getConnection()->beginTransaction(); // suspend auto-commit + try { + //... do some work + $user = new User; + $user->setName('George'); + $em->persist($user); + $em->flush(); + $em->getConnection()->commit(); + } catch (Exception $e) { + $em->getConnection()->rollback(); + $em->close(); + throw $e; + } + +Explicit transaction demarcation is required when you want to +include custom DBAL operations in a unit of work or when you want +to make use of some methods of the ``EntityManager`` API that +require an active transaction. Such methods will throw a +``TransactionRequiredException`` to inform you of that +requirement. + +A more convenient alternative for explicit transaction demarcation +is the use of provided control abstractions in the form of +``Connection#transactional($func)`` and +``EntityManager#transactional($func)``. When used, these control +abstractions ensure that you never forget to rollback the +transaction or close the ``EntityManager``, apart from the obvious +code reduction. An example that is functionally equivalent to the +previously shown code looks as follows: + +.. code-block:: php + + transactional(function($em) { + //... do some work + $user = new User; + $user->setName('George'); + $em->persist($user); + }); + +The difference between ``Connection#transactional($func)`` and +``EntityManager#transactional($func)`` is that the latter +abstraction flushes the ``EntityManager`` prior to transaction +commit and also closes the ``EntityManager`` properly when an +exception occurs (in addition to rolling back the transaction). + +Exception Handling +~~~~~~~~~~~~~~~~~~ + +When using implicit transaction demarcation and an exception occurs +during ``EntityManager#flush()``, the transaction is automatically +rolled back and the ``EntityManager`` closed. + +When using explicit transaction demarcation and an exception +occurs, the transaction should be rolled back immediately and the +``EntityManager`` closed by invoking ``EntityManager#close()`` and +subsequently discarded, as demonstrated in the example above. This +can be handled elegantly by the control abstractions shown earlier. +Note that when catching ``Exception`` you should generally re-throw +the exception. If you intend to recover from some exceptions, catch +them explicitly in earlier catch blocks (but do not forget to +rollback the transaction and close the ``EntityManager`` there as +well). All other best practices of exception handling apply +similarly (i.e. either log or re-throw, not both, etc.). + +As a result of this procedure, all previously managed or removed +instances of the ``EntityManager`` become detached. The state of +the detached objects will be the state at the point at which the +transaction was rolled back. The state of the objects is in no way +rolled back and thus the objects are now out of synch with the +database. The application can continue to use the detached objects, +knowing that their state is potentially no longer accurate. + +If you intend to start another unit of work after an exception has +occurred you should do that with a new ``EntityManager``. + +Locking Support +--------------- + +Doctrine 2 offers support for Pessimistic- and Optimistic-locking +strategies natively. This allows to take very fine-grained control +over what kind of locking is required for your Entities in your +application. + +Optimistic Locking +~~~~~~~~~~~~~~~~~~ + +Database transactions are fine for concurrency control during a +single request. However, a database transaction should not span +across requests, the so-called "user think time". Therefore a +long-running "business transaction" that spans multiple requests +needs to involve several database transactions. Thus, database +transactions alone can no longer control concurrency during such a +long-running business transaction. Concurrency control becomes the +partial responsibility of the application itself. + +Doctrine has integrated support for automatic optimistic locking +via a version field. In this approach any entity that should be +protected against concurrent modifications during long-running +business transactions gets a version field that is either a simple +number (mapping type: integer) or a timestamp (mapping type: +datetime). When changes to such an entity are persisted at the end +of a long-running conversation the version of the entity is +compared to the version in the database and if they don't match, an +``OptimisticLockException`` is thrown, indicating that the entity +has been modified by someone else already. + +You designate a version field in an entity as follows. In this +example we'll use an integer. + +.. code-block:: php + + find('User', $theEntityId, LockMode::OPTIMISTIC, $expectedVersion); + + // do the work + + $em->flush(); + } catch(OptimisticLockException $e) { + echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; + } + +Or you can use ``EntityManager#lock()`` to find out: + +.. code-block:: php + + find('User', $theEntityId); + + try { + // assert version + $em->lock($entity, LockMode::OPTIMISTIC, $expectedVersion); + + } catch(OptimisticLockException $e) { + echo "Sorry, but someone else has already changed this entity. Please apply the changes again!"; + } + +Important Implementation Notes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can easily get the optimistic locking workflow wrong if you +compare the wrong versions. Say you have Alice and Bob editing a +hypothetical blog post: + +- Alice reads the headline of the blog post being "Foo", at + optimistic lock version 1 (GET Request) +- Bob reads the headline of the blog post being "Foo", at + optimistic lock version 1 (GET Request) +- Bob updates the headline to "Bar", upgrading the optimistic lock + version to 2 (POST Request of a Form) +- Alice updates the headline to "Baz", ... (POST Request of a + Form) + +Now at the last stage of this scenario the blog post has to be read +again from the database before Alice's headline can be applied. At +this point you will want to check if the blog post is still at +version 1 (which it is not in this scenario). + +Using optimistic locking correctly, you *have* to add the version +as an additional hidden field (or into the SESSION for more +safety). Otherwise you cannot verify the version is still the one +being originally read from the database when Alice performed her +GET request for the blog post. If this happens you might see lost +updates you wanted to prevent with Optimistic Locking. + +See the example code, The form (GET Request): + +.. code-block:: php + + find('BlogPost', 123456); + + echo ''; + echo ''; + +And the change headline action (POST Request): + +.. code-block:: php + + find('BlogPost', $postId, \Doctrine\DBAL\LockMode::OPTIMISTIC, $postVersion); + +Pessimistic Locking +~~~~~~~~~~~~~~~~~~~ + +Doctrine 2 supports Pessimistic Locking at the database level. No +attempt is being made to implement pessimistic locking inside +Doctrine, rather vendor-specific and ANSI-SQL commands are used to +acquire row-level locks. Every Entity can be part of a pessimistic +lock, there is no special metadata required to use this feature. + +However for Pessimistic Locking to work you have to disable the +Auto-Commit Mode of your Database and start a transaction around +your pessimistic lock use-case using the "Approach 2: Explicit +Transaction Demarcation" described above. Doctrine 2 will throw an +Exception if you attempt to acquire an pessimistic lock and no +transaction is running. + +Doctrine 2 currently supports two pessimistic lock modes: + + +- Pessimistic Write + (``Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE``), locks the + underlying database rows for concurrent Read and Write Operations. +- Pessimistic Read (``Doctrine\DBAL\LockMode::PESSIMISTIC_READ``), + locks other concurrent requests that attempt to update or lock rows + in write mode. + +You can use pessimistic locks in three different scenarios: + + +1. Using + ``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` + or + ``EntityManager#find($className, $id, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` +2. Using + ``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` + or + ``EntityManager#lock($entity, \Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` +3. Using + ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_WRITE)`` + or + ``Query#setLockMode(\Doctrine\DBAL\LockMode::PESSIMISTIC_READ)`` + + diff --git a/docs/en/reference/unitofwork-associations.rst b/docs/en/reference/unitofwork-associations.rst new file mode 100644 index 000000000..da3cc9dc8 --- /dev/null +++ b/docs/en/reference/unitofwork-associations.rst @@ -0,0 +1,60 @@ +Association Updates: Owning Side and Inverse Side +================================================= + +When mapping bidirectional associations it is important to +understand the concept of the owning and inverse sides. The +following general rules apply: + +- Relationships may be bidirectional or unidirectional. +- A bidirectional relationship has both an owning side and an inverse side +- A unidirectional relationship only has an owning side. +- Doctrine will **only** check the owning side of an association for changes. + +Bidirectional Associations +-------------------------- + +The following rules apply to **bidirectional** associations: + +- The inverse side has to use the ``mappedBy`` attribute of the OneToOne, + OneToMany, or ManyToMany mapping declaration. The mappedBy + attribute contains the name of the association-field on the owning side. +- The owning side has to use the ``inversedBy`` attribute of the + OneToOne, ManyToOne, or ManyToMany mapping declaration. + The inversedBy attribute contains the name of the association-field + on the inverse-side. +- ManyToOne is always the owning side of a bidirectional association. +- OneToMany is always the inverse side of a bidirectional association. +- The owning side of a OneToOne association is the entity with the table + containing the foreign key. +- You can pick the owning side of a many-to-many association yourself. + +Important concepts +------------------ + +**Doctrine will only check the owning side of an association for changes.** + +To fully understand this, remember how bidirectional associations +are maintained in the object world. There are 2 references on each +side of the association and these 2 references both represent the +same association but can change independently of one another. Of +course, in a correct application the semantics of the bidirectional +association are properly maintained by the application developer +(that's his responsibility). Doctrine needs to know which of these +two in-memory references is the one that should be persisted and +which not. This is what the owning/inverse concept is mainly used +for. + +**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)** + +The owning side of a bidirectional association is the side Doctrine +"looks at" when determining the state of the association, and +consequently whether there is anything to do to update the +association in the database. + +.. note:: + + "Owning side" and "inverse side" are technical concepts of + the ORM technology, not concepts of your domain model. What you + consider as the owning side in your domain model can be different + from what the owning side is for Doctrine. These are unrelated. + diff --git a/docs/en/reference/unitofwork.rst b/docs/en/reference/unitofwork.rst new file mode 100644 index 000000000..48f1029c6 --- /dev/null +++ b/docs/en/reference/unitofwork.rst @@ -0,0 +1,201 @@ +Doctrine Internals explained +============================ + +Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power. + +How Doctrine keeps track of Objects +----------------------------------- + +Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an +object from the database, Doctrine will keep a reference to this object inside +its UnitOfWork. The array holding all the entity references is two-levels deep +and has the keys "root entity name" and "id". Since Doctrine allows composite +keys the id is a sorted, serialized version of all the key columns. + +This allows Doctrine room for optimizations. If you call the EntityManager and +ask for an entity with a specific ID twice, it will return the same instance: + +.. code-block:: php + + public function testIdentityMap() + { + $objectA = $this->entityManager->find('EntityName', 1); + $objectB = $this->entityManager->find('EntityName', 1); + + $this->assertSame($objectA, $objectB) + } + +Only one SELECT query will be fired against the database here. In the second +``EntityManager#find()`` call Doctrine will check the identity map first and +doesn't need to make that database roundtrip. + +Even if you get a proxy object first then fetch the object by the same id you +will still end up with the same reference: + +.. code-block:: php + + public function testIdentityMapReference() + { + $objectA = $this->entityManager->getReference('EntityName', 1); + // check for proxyinterface + $this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA); + + $objectB = $this->entityManager->find('EntityName', 1); + + $this->assertSame($objectA, $objectB) + } + +The identity map being indexed by primary keys only allows shortcuts when you +ask for objects by primary key. Assume you have the following ``persons`` +table: + +:: + + id | name + ------------- + 1 | Benjamin + 2 | Bud + +Take the following example where two +consecutive calls are made against a repository to fetch an entity by a set of +criteria: + +.. code-block:: php + + public function testIdentityMapRepositoryFindBy() + { + $repository = $this->entityManager->getRepository('Person'); + $objectA = $repository->findOneBy(array('name' => 'Benjamin')); + $objectB = $repository->findOneBy(array('name' => 'Benjamin')); + + $this->assertSame($objectA, $objectB); + } + +This query will still return the same references and `$objectA` and `$objectB` +are indeed referencing the same object. However when checking your SQL logs you +will realize that two queries have been executed against the database. Doctrine +only knows objects by id, so a query for different criteria has to go to the +database, even if it was executed just before. + +But instead of creating a second Person object Doctrine first gets the primary +key from the row and check if it already has an object inside the UnitOfWork +with that primary key. In our example it finds an object and decides to return +this instead of creating a new one. + +The identity map has a second use-case. When you call ``EntityManager#flush`` +Doctrine will ask the identity map for all objects that are currently managed. +This means you don't have to call ``EntityManager#persist`` over and over again +to pass known objects to the EntityManager. This is a NO-OP for known entities, +but leads to much code written that is confusing to other developers. + +The following code WILL update your database with the changes made to the +``Person`` object, even if you did not call ``EntityManager#persist``: + +.. code-block:: php + + find("Person", 1); + $user->setName("Guilherme"); + $entityManager->flush(); + +How Doctrine Detects Changes +---------------------------- + +Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI). +This means you map php objects into a relational database that don't +necessarily know about the database at all. A natural question would now be, +"how does Doctrine even detect objects have changed?". + +For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch +an object from the database Doctrine will keep a copy of all the properties and +associations inside the UnitOfWork. Because variables in the PHP language are +subject to "copy-on-write" the memory usage of a PHP request that only reads +objects from the database is the same as if Doctrine did not keep this variable +copy. Only if you start changing variables PHP will create new variables internally +that consume new memory. + +Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the +Identity Map and for each object compares the original property and association +values with the values that are currently set on the object. If changes are +detected then the object is queued for an SQL UPDATE operation. Only the fields +that actually changed are updated. + +This process has an obvious performance impact. The larger the size of the +UnitOfWork is, the longer this computation takes. There are several ways to +optimize the performance of the Flush Operation: + +- Mark entities as read only. These entities can only be inserted or removed, + but are never updated. They are omitted in the changeset calculation. +- Temporarily mark entities as read only. If you have a very large UnitOfWork + but know that a large set of entities has not changed, just mark them as read + only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``. +- Flush only a single entity with ``$entityManager->flush($entity)``. +- Use :doc:`Change Tracking Policies ` to use more + explicit strategies of notifying the UnitOfWork what objects/properties + changed. + + +Query Internals +--------------- + +The different ORM Layers +------------------------ + +Doctrine ships with a set of layers with different responsibilities. This +section gives a short explanation of each layer. + +Hydration +~~~~~~~~~ + +Responsible for creating a final result from a raw database statement and a +result-set mapping object. The developer can choose which kind of result he +wishes to be hydrated. Default result-types include: + +- SQL to Entities +- SQL to structured Arrays +- SQL to simple scalar result arrays +- SQL to a single result variable + +Hydration to entities and arrays is one of most complex parts of Doctrine +algorithm-wise. It can built results with for example: + +- Single table selects +- Joins with n:1 or 1:n cardinality, grouping belonging to the same parent. +- Mixed results of objects and scalar values +- Hydration of results by a given scalar value as key. + +Persisters +~~~~~~~~~~ + +tbr + +UnitOfWork +~~~~~~~~~~ + +tbr + +ResultSetMapping +~~~~~~~~~~~~~~~~ + +tbr + +DQL Parser +~~~~~~~~~~ + +tbr + +SQLWalker +~~~~~~~~~ + +tbr + +EntityManager +~~~~~~~~~~~~~ + +tbr + +ClassMetadataFactory +~~~~~~~~~~~~~~~~~~~~ + +tbr + diff --git a/docs/en/reference/working-with-associations.rst b/docs/en/reference/working-with-associations.rst new file mode 100644 index 000000000..253edad7e --- /dev/null +++ b/docs/en/reference/working-with-associations.rst @@ -0,0 +1,712 @@ +Working with Associations +========================= + +Associations between entities are represented just like in regular +object-oriented PHP, with references to other objects or +collections of objects. When it comes to persistence, it is +important to understand three main things: + + +- The :doc:`concept of owning and inverse sides ` + in bidirectional associations. +- If an entity is removed from a collection, the association is + removed, not the entity itself. A collection of entities always + only represents the association to the containing entities, not the + entity itself. +- Collection-valued :ref:`persistent fields ` have to be instances of the + ``Doctrine\Common\Collections\Collection`` interface. + +Changes to associations in your code are not synchronized to the +database directly, but upon calling ``EntityManager#flush()``. + +To describe all the concepts of working with associations we +introduce a specific set of example entities that show all the +different flavors of association management in Doctrine. + +Association Example Entities +---------------------------- + +We will use a simple comment system with Users and Comments as +entities to show examples of association management. See the PHP +docblocks of each association in the following example for +information about its type and if it's the owning or inverse side. + +.. code-block:: php + + commentsRead; + } + + public function setFirstComment(Comment $c) { + $this->firstComment = $c; + } + } + +The interaction code would then look like in the following snippet +(``$em`` here is an instance of the EntityManager): + +.. code-block:: php + + find('User', $userId); + + // unidirectional many to many + $comment = $em->find('Comment', $readCommentId); + $user->getReadComments()->add($comment); + + $em->flush(); + + // unidirectional many to one + $myFirstComment = new Comment(); + $user->setFirstComment($myFirstComment); + + $em->persist($myFirstComment); + $em->flush(); + +In the case of bi-directional associations you have to update the +fields on both sides: + +.. code-block:: php + + commentsAuthored; + } + + public function getFavoriteComments() { + return $this->favorites; + } + } + + class Comment + { + // ... + + public function getUserFavorites() { + return $this->userFavorites; + } + + public function setAuthor(User $author = null) { + $this->author = $author; + } + } + + // Many-to-Many + $user->getFavorites()->add($favoriteComment); + $favoriteComment->getUserFavorites()->add($user); + + $em->flush(); + + // Many-To-One / One-To-Many Bidirectional + $newComment = new Comment(); + $user->getAuthoredComments()->add($newComment); + $newComment->setAuthor($user); + + $em->persist($newComment); + $em->flush(); + +Notice how always both sides of the bidirectional association are +updated. The previous unidirectional associations were simpler to +handle. + +Removing Associations +--------------------- + +Removing an association between two entities is similarly +straight-forward. There are two strategies to do so, by key and by +element. Here are some examples: + +.. code-block:: php + + getComments()->removeElement($comment); + $comment->setAuthor(null); + + $user->getFavorites()->removeElement($comment); + $comment->getUserFavorites()->removeElement($user); + + // Remove by Key + $user->getComments()->remove($ithComment); + $comment->setAuthor(null); + +You need to call ``$em->flush()`` to make persist these changes in +the database permanently. + +Notice how both sides of the bidirectional association are always +updated. Unidirectional associations are consequently simpler to +handle. Also note that if you use type-hinting in your methods, i.e. +``setAddress(Address $address)``, PHP will only allow null +values if ``null`` is set as default value. Otherwise +setAddress(null) will fail for removing the association. If you +insist on type-hinting a typical way to deal with this is to +provide a special method, like ``removeAddress()``. This can also +provide better encapsulation as it hides the internal meaning of +not having an address. + +When working with collections, keep in mind that a Collection is +essentially an ordered map (just like a PHP array). That is why the +``remove`` operation accepts an index/key. ``removeElement`` is a +separate method that has O(n) complexity using ``array_search``, +where n is the size of the map. + +.. note:: + + Since Doctrine always only looks at the owning side of a + bidirectional association for updates, it is not necessary for + write operations that an inverse collection of a bidirectional + one-to-many or many-to-many association is updated. This knowledge + can often be used to improve performance by avoiding the loading of + the inverse collection. + + +You can also clear the contents of a whole collection using the +``Collections::clear()`` method. You should be aware that using +this method can lead to a straight and optimized database delete or +update call during the flush operation that is not aware of +entities that have been re-added to the collection. + +Say you clear a collection of tags by calling +``$post->getTags()->clear();`` and then call +``$post->getTags()->add($tag)``. This will not recognize the tag having +already been added previously and will consequently issue two separate database +calls. + +Association Management Methods +------------------------------ + +It is generally a good idea to encapsulate proper association +management inside the entity classes. This makes it easier to use +the class correctly and can encapsulate details about how the +association is maintained. + +The following code shows updates to the previous User and Comment +example that encapsulate much of the association management code: + +.. code-block:: php + + commentsRead[] = $comment; + } + + public function addComment(Comment $comment) { + if (count($this->commentsAuthored) == 0) { + $this->setFirstComment($comment); + } + $this->comments[] = $comment; + $comment->setAuthor($this); + } + + private function setFirstComment(Comment $c) { + $this->firstComment = $c; + } + + public function addFavorite(Comment $comment) { + $this->favorites->add($comment); + $comment->addUserFavorite($this); + } + + public function removeFavorite(Comment $comment) { + $this->favorites->removeElement($comment); + $comment->removeUserFavorite($this); + } + } + + class Comment + { + // .. + + public function addUserFavorite(User $user) { + $this->userFavorites[] = $user; + } + + public function removeUserFavorite(User $user) { + $this->userFavorites->removeElement($user); + } + } + +You will notice that ``addUserFavorite`` and ``removeUserFavorite`` +do not call ``addFavorite`` and ``removeFavorite``, thus the +bidirectional association is strictly-speaking still incomplete. +However if you would naively add the ``addFavorite`` in +``addUserFavorite``, you end up with an infinite loop, so more work +is needed. As you can see, proper bidirectional association +management in plain OOP is a non-trivial task and encapsulating all +the details inside the classes can be challenging. + +.. note:: + + If you want to make sure that your collections are perfectly + encapsulated you should not return them from a + ``getCollectionName()`` method directly, but call + ``$collection->toArray()``. This way a client programmer for the + entity cannot circumvent the logic you implement on your entity for + association management. For example: + + +.. code-block:: php + + commentsRead->toArray(); + } + } + +This will however always initialize the collection, with all the +performance penalties given the size. In some scenarios of large +collections it might even be a good idea to completely hide the +read access behind methods on the EntityRepository. + +There is no single, best way for association management. It greatly +depends on the requirements of your concrete domain model as well +as your preferences. + +Synchronizing Bidirectional Collections +--------------------------------------- + +In the case of Many-To-Many associations you as the developer have the +responsibility of keeping the collections on the owning and inverse side +in sync when you apply changes to them. Doctrine can only +guarantee a consistent state for the hydration, not for your client +code. + +Using the User-Comment entities from above, a very simple example +can show the possible caveats you can encounter: + +.. code-block:: php + + getFavorites()->add($favoriteComment); + // not calling $favoriteComment->getUserFavorites()->add($user); + + $user->getFavorites()->contains($favoriteComment); // TRUE + $favoriteComment->getUserFavorites()->contains($user); // FALSE + +There are two approaches to handle this problem in your code: + + +1. Ignore updating the inverse side of bidirectional collections, + BUT never read from them in requests that changed their state. In + the next Request Doctrine hydrates the consistent collection state + again. +2. Always keep the bidirectional collections in sync through + association management methods. Reads of the Collections directly + after changes are consistent then. + +Transitive persistence / Cascade Operations +------------------------------------------- + +Persisting, removing, detaching and merging individual entities can +become pretty cumbersome, especially when a highly interweaved object graph +is involved. Therefore Doctrine 2 provides a +mechanism for transitive persistence through cascading of these +operations. Each association to another entity or a collection of +entities can be configured to automatically cascade certain +operations. By default, no operations are cascaded. + +The following cascade options exist: + + +- persist : Cascades persist operations to the associated + entities. +- remove : Cascades remove operations to the associated entities. +- merge : Cascades merge operations to the associated entities. +- detach : Cascades detach operations to the associated entities. +- all : Cascades persist, remove, merge and detach operations to + associated entities. + +.. note:: + + Cascade operations are performed in memory. That means collections and related entities + are fetched into memory, even if they are still marked as lazy when + the cascade operation is about to be performed. However this approach allows + entity lifecycle events to be performed for each of these operations. + + However, pulling objects graph into memory on cascade can cause considerable performance + overhead, especially when cascading collections are large. Makes sure + to weigh the benefits and downsides of each cascade operation that you define. + + To rely on the database level cascade operations for the delete operation instead, you can + configure each join column with the **onDelete** option. See the respective + mapping driver chapters for more information. + +The following example is an extension to the User-Comment example +of this chapter. Suppose in our application a user is created +whenever he writes his first comment. In this case we would use the +following code: + +.. code-block:: php + + addComment($myFirstComment); + + $em->persist($user); + $em->persist($myFirstComment); + $em->flush(); + +Even if you *persist* a new User that contains our new Comment this +code would fail if you removed the call to +``EntityManager#persist($myFirstComment)``. Doctrine 2 does not +cascade the persist operation to all nested entities that are new +as well. + +More complicated is the deletion of all of a user's comments when he is +removed from the system: + +.. code-block:: php + + $user = $em->find('User', $deleteUserId); + + foreach ($user->getAuthoredComments() AS $comment) { + $em->remove($comment); + } + $em->remove($user); + $em->flush(); + +Without the loop over all the authored comments Doctrine would use +an UPDATE statement only to set the foreign key to NULL and only +the User would be deleted from the database during the +flush()-Operation. + +To have Doctrine handle both cases automatically we can change the +``User#commentsAuthored`` property to cascade both the "persist" +and the "remove" operation. + +.. code-block:: php + + addresses = new ArrayCollection(); + } + + public function newStandingData(StandingData $sd) + { + $this->standingData = $sd; + } + + public function removeAddress($pos) + { + unset($this->addresses[$pos]); + } + } + +Now two examples of what happens when you remove the references: + +.. code-block:: php + + find("Addressbook\Contact", $contactId); + $contact->newStandingData(new StandingData("Firstname", "Lastname", "Street")); + $contact->removeAddress(1); + + $em->flush(); + +In this case you have not only changed the ``Contact`` entity itself but +you have also removed the references for standing data and as well as one +address reference. When flush is called not only are the references removed +but both the old standing data and the one address entity are also deleted +from the database. + +Filtering Collections +--------------------- + +.. filtering-collections: + +Collections have a filtering API that allows to slice parts of data from +a collection. If the collection has not been loaded from the database yet, +the filtering API can work on the SQL level to make optimized access to +large collections. + +.. code-block:: php + + find('Group', $groupId); + $userCollection = $group->getUsers(); + + $criteria = Criteria::create() + ->where(Criteria::expr()->eq("birthday", "1982-02-17")) + ->orderBy(array("username" => "ASC")) + ->setFirstResult(0) + ->setMaxResults(20) + ; + + $birthdayUsers = $userCollection->matching($criteria); + +.. tip:: + + You can move the access of slices of collections into dedicated methods of + an entity. For example ``Group#getTodaysBirthdayUsers()``. + +The Criteria has a limited matching language that works both on the +SQL and on the PHP collection level. This means you can use collection matching +interchangeably, independent of in-memory or sql-backed collections. + +.. code-block:: php + + find('CMS\Article', 1234); + $article->setHeadline('Hello World dude!'); + + $article2 = $entityManager->find('CMS\Article', 1234); + echo $article2->getHeadline(); + +In this case the Article is accessed from the entity manager twice, +but modified in between. Doctrine 2 realizes this and will only +ever give you access to one instance of the Article with ID 1234, +no matter how often do you retrieve it from the EntityManager and +even no matter what kind of Query method you are using (find, +Repository Finder or DQL). This is called "Identity Map" pattern, +which means Doctrine keeps a map of each entity and ids that have +been retrieved per PHP request and keeps returning you the same +instances. + +In the previous example the echo prints "Hello World dude!" to the +screen. You can even verify that ``$article`` and ``$article2`` are +indeed pointing to the same instance by running the following +code: + +.. code-block:: php + + comments = new ArrayCollection(); + } + + public function getAuthor() { return $this->author; } + public function getComments() { return $this->comments; } + } + + $article = $em->find('Article', 1); + +This code only retrieves the ``Article`` instance with id 1 executing +a single SELECT statement against the user table in the database. +You can still access the associated properties author and comments +and the associated objects they contain. + +This works by utilizing the lazy loading pattern. Instead of +passing you back a real Author instance and a collection of +comments Doctrine will create proxy instances for you. Only if you +access these proxies for the first time they will go through the +EntityManager and load their state from the database. + +This lazy-loading process happens behind the scenes, hidden from +your code. See the following code: + +.. code-block:: php + + find('Article', 1); + + // accessing a method of the user instance triggers the lazy-load + echo "Author: " . $article->getAuthor()->getName() . "\n"; + + // Lazy Loading Proxies pass instanceof tests: + if ($article->getAuthor() instanceof User) { + // a User Proxy is a generated "UserProxy" class + } + + // accessing the comments as an iterator triggers the lazy-load + // retrieving ALL the comments of this article from the database + // using a single SELECT statement + foreach ($article->getComments() AS $comment) { + echo $comment->getText() . "\n\n"; + } + + // Article::$comments passes instanceof tests for the Collection interface + // But it will NOT pass for the ArrayCollection interface + if ($article->getComments() instanceof \Doctrine\Common\Collections\Collection) { + echo "This will always be true!"; + } + +A slice of the generated proxy classes code looks like the +following piece of code. A real proxy class override ALL public +methods along the lines of the ``getName()`` method shown below: + +.. code-block:: php + + _load(); + return parent::getName(); + } + // .. other public methods of User + } + +.. warning:: + + Traversing the object graph for parts that are lazy-loaded will + easily trigger lots of SQL queries and will perform badly if used + to heavily. Make sure to use DQL to fetch-join all the parts of the + object-graph that you need as efficiently as possible. + + +Persisting entities +------------------- + +An entity can be made persistent by passing it to the +``EntityManager#persist($entity)`` method. By applying the persist +operation on some entity, that entity becomes MANAGED, which means +that its persistence is from now on managed by an EntityManager. As +a result the persistent state of such an entity will subsequently +be properly synchronized with the database when +``EntityManager#flush()`` is invoked. + +.. note:: + + Invoking the ``persist`` method on an entity does NOT + cause an immediate SQL INSERT to be issued on the database. + Doctrine applies a strategy called "transactional write-behind", + which means that it will delay most SQL commands until + ``EntityManager#flush()`` is invoked which will then issue all + necessary SQL statements to synchronize your objects with the + database in the most efficient way and a single, short transaction, + taking care of maintaining referential integrity. + + +Example: + +.. code-block:: php + + setName('Mr.Right'); + $em->persist($user); + $em->flush(); + +.. note:: + + Generated entity identifiers / primary keys are + guaranteed to be available after the next successful flush + operation that involves the entity in question. You can not rely on + a generated identifier to be available directly after invoking + ``persist``. The inverse is also true. You can not rely on a + generated identifier being not available after a failed flush + operation. + + +The semantics of the persist operation, applied on an entity X, are +as follows: + + +- If X is a new entity, it becomes managed. The entity X will be + entered into the database as a result of the flush operation. +- If X is a preexisting managed entity, it is ignored by the + persist operation. However, the persist operation is cascaded to + entities referenced by X, if the relationships from X to these + other entities are mapped with cascade=PERSIST or cascade=ALL (see + "Transitive Persistence"). +- If X is a removed entity, it becomes managed. +- If X is a detached entity, an exception will be thrown on + flush. + +Removing entities +----------------- + +An entity can be removed from persistent storage by passing it to +the ``EntityManager#remove($entity)`` method. By applying the +``remove`` operation on some entity, that entity becomes REMOVED, +which means that its persistent state will be deleted once +``EntityManager#flush()`` is invoked. + +.. note:: + + Just like ``persist``, invoking ``remove`` on an entity + does NOT cause an immediate SQL DELETE to be issued on the + database. The entity will be deleted on the next invocation of + ``EntityManager#flush()`` that involves that entity. This + means that entities scheduled for removal can still be queried + for and appear in query and collection results. See + the section on :ref:`Database and UnitOfWork Out-Of-Sync ` + for more information. + + +Example: + +.. code-block:: php + + remove($user); + $em->flush(); + +The semantics of the remove operation, applied to an entity X are +as follows: + + +- If X is a new entity, it is ignored by the remove operation. + However, the remove operation is cascaded to entities referenced by + X, if the relationship from X to these other entities is mapped + with cascade=REMOVE or cascade=ALL (see "Transitive Persistence"). +- If X is a managed entity, the remove operation causes it to + become removed. The remove operation is cascaded to entities + referenced by X, if the relationships from X to these other + entities is mapped with cascade=REMOVE or cascade=ALL (see + "Transitive Persistence"). +- If X is a detached entity, an InvalidArgumentException will be + thrown. +- If X is a removed entity, it is ignored by the remove operation. +- A removed entity X will be removed from the database as a result + of the flush operation. + +After an entity has been removed its in-memory state is the same as +before the removal, except for generated identifiers. + +Removing an entity will also automatically delete any existing +records in many-to-many join tables that link this entity. The +action taken depends on the value of the ``@joinColumn`` mapping +attribute "onDelete". Either Doctrine issues a dedicated ``DELETE`` +statement for records of each join table or it depends on the +foreign key semantics of onDelete="CASCADE". + +Deleting an object with all its associated objects can be achieved +in multiple ways with very different performance impacts. + + +1. If an association is marked as ``CASCADE=REMOVE`` Doctrine 2 + will fetch this association. If its a Single association it will + pass this entity to + ´EntityManager#remove()``. If the association is a collection, Doctrine will loop over all its elements and pass them to``EntityManager#remove()\`. + In both cases the cascade remove semantics are applied recursively. + For large object graphs this removal strategy can be very costly. +2. Using a DQL ``DELETE`` statement allows you to delete multiple + entities of a type with a single command and without hydrating + these entities. This can be very efficient to delete large object + graphs from the database. +3. Using foreign key semantics ``onDelete="CASCADE"`` can force the + database to remove all associated objects internally. This strategy + is a bit tricky to get right but can be very powerful and fast. You + should be aware however that using strategy 1 (``CASCADE=REMOVE``) + completely by-passes any foreign key ``onDelete=CASCADE`` option, + because Doctrine will fetch and remove all associated entities + explicitly nevertheless. + +Detaching entities +------------------ + +An entity is detached from an EntityManager and thus no longer +managed by invoking the ``EntityManager#detach($entity)`` method on +it or by cascading the detach operation to it. Changes made to the +detached entity, if any (including removal of the entity), will not +be synchronized to the database after the entity has been +detached. + +Doctrine will not hold on to any references to a detached entity. + +Example: + +.. code-block:: php + + detach($entity); + +The semantics of the detach operation, applied to an entity X are +as follows: + + +- If X is a managed entity, the detach operation causes it to + become detached. The detach operation is cascaded to entities + referenced by X, if the relationships from X to these other + entities is mapped with cascade=DETACH or cascade=ALL (see + "Transitive Persistence"). Entities which previously referenced X + will continue to reference X. +- If X is a new or detached entity, it is ignored by the detach + operation. +- If X is a removed entity, the detach operation is cascaded to + entities referenced by X, if the relationships from X to these + other entities is mapped with cascade=DETACH or cascade=ALL (see + "Transitive Persistence"). Entities which previously referenced X + will continue to reference X. + +There are several situations in which an entity is detached +automatically without invoking the ``detach`` method: + + +- When ``EntityManager#clear()`` is invoked, all entities that are + currently managed by the EntityManager instance become detached. +- When serializing an entity. The entity retrieved upon subsequent + unserialization will be detached (This is the case for all entities + that are serialized and stored in some cache, i.e. when using the + Query Result Cache). + +The ``detach`` operation is usually not as frequently needed and +used as ``persist`` and ``remove``. + +Merging entities +---------------- + +Merging entities refers to the merging of (usually detached) +entities into the context of an EntityManager so that they become +managed again. To merge the state of an entity into an +EntityManager use the ``EntityManager#merge($entity)`` method. The +state of the passed entity will be merged into a managed copy of +this entity and this copy will subsequently be returned. + +Example: + +.. code-block:: php + + merge($detachedEntity); + // $entity now refers to the fully managed copy returned by the merge operation. + // The EntityManager $em now manages the persistence of $entity as usual. + +.. note:: + + When you want to serialize/unserialize entities you + have to make all entity properties protected, never private. The + reason for this is, if you serialize a class that was a proxy + instance before, the private variables won't be serialized and a + PHP Notice is thrown. + + +The semantics of the merge operation, applied to an entity X, are +as follows: + + +- If X is a detached entity, the state of X is copied onto a + pre-existing managed entity instance X' of the same identity. +- If X is a new entity instance, a new managed copy X' will be + created and the state of X is copied onto this managed instance. +- If X is a removed entity instance, an InvalidArgumentException + will be thrown. +- If X is a managed entity, it is ignored by the merge operation, + however, the merge operation is cascaded to entities referenced by + relationships from X if these relationships have been mapped with + the cascade element value MERGE or ALL (see "Transitive + Persistence"). +- For all entities Y referenced by relationships from X having the + cascade element value MERGE or ALL, Y is merged recursively as Y'. + For all such Y referenced by X, X' is set to reference Y'. (Note + that if X is managed then X is the same object as X'.) +- If X is an entity merged to X', with a reference to another + entity Y, where cascade=MERGE or cascade=ALL is not specified, then + navigation of the same association from X' yields a reference to a + managed object Y' with the same persistent identity as Y. + +The ``merge`` operation will throw an ``OptimisticLockException`` +if the entity being merged uses optimistic locking through a +version field and the versions of the entity being merged and the +managed copy don't match. This usually means that the entity has +been modified while being detached. + +The ``merge`` operation is usually not as frequently needed and +used as ``persist`` and ``remove``. The most common scenario for +the ``merge`` operation is to reattach entities to an EntityManager +that come from some cache (and are therefore detached) and you want +to modify and persist such an entity. + +.. warning:: + + If you need to perform multiple merges of entities that share certain subparts + of their object-graphs and cascade merge, then you have to call ``EntityManager#clear()`` between the + successive calls to ``EntityManager#merge()``. Otherwise you might end up with + multiple copies of the "same" object in the database, however with different ids. + +.. note:: + + If you load some detached entities from a cache and you do + not need to persist or delete them or otherwise make use of them + without the need for persistence services there is no need to use + ``merge``. I.e. you can simply pass detached objects from a cache + directly to the view. + + +Synchronization with the Database +--------------------------------- + +The state of persistent entities is synchronized with the database +on flush of an ``EntityManager`` which commits the underlying +``UnitOfWork``. The synchronization involves writing any updates to +persistent entities and their relationships to the database. +Thereby bidirectional relationships are persisted based on the +references held by the owning side of the relationship as explained +in the Association Mapping chapter. + +When ``EntityManager#flush()`` is called, Doctrine inspects all +managed, new and removed entities and will perform the following +operations. + +.. _workingobjects_database_uow_outofsync: + +Effects of Database and UnitOfWork being Out-Of-Sync +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As soon as you begin to change the state of entities, call persist or remove the +contents of the UnitOfWork and the database will drive out of sync. They can +only be synchronized by calling ``EntityManager#flush()``. This section +describes the effects of database and UnitOfWork being out of sync. + +- Entities that are scheduled for removal can still be queried from the database. + They are returned from DQL and Repository queries and are visible in collections. +- Entities that are passed to ``EntityManager#persist`` do not turn up in query + results. +- Entities that have changed will not be overwritten with the state from the database. + This is because the identity map will detect the construction of an already existing + entity and assumes its the most up to date version. + +``EntityManager#flush()`` is never called implicitly by Doctrine. You always have to trigger it manually. + +Synchronizing New and Managed Entities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The flush operation applies to a managed entity with the following +semantics: + + +- The entity itself is synchronized to the database using a SQL + UPDATE statement, only if at least one persistent field has + changed. +- No SQL updates are executed if the entity did not change. + +The flush operation applies to a new entity with the following +semantics: + + +- The entity itself is synchronized to the database using a SQL + INSERT statement. + +For all (initialized) relationships of the new or managed entity +the following semantics apply to each associated entity X: + + +- If X is new and persist operations are configured to cascade on + the relationship, X will be persisted. +- If X is new and no persist operations are configured to cascade + on the relationship, an exception will be thrown as this indicates + a programming error. +- If X is removed and persist operations are configured to cascade + on the relationship, an exception will be thrown as this indicates + a programming error (X would be re-persisted by the cascade). +- If X is detached and persist operations are configured to + cascade on the relationship, an exception will be thrown (This is + semantically the same as passing X to persist()). + +Synchronizing Removed Entities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The flush operation applies to a removed entity by deleting its +persistent state from the database. No cascade options are relevant +for removed entities on flush, the cascade remove option is already +executed during ``EntityManager#remove($entity)``. + +The size of a Unit of Work +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The size of a Unit of Work mainly refers to the number of managed +entities at a particular point in time. + +The cost of flushing +~~~~~~~~~~~~~~~~~~~~ + +How costly a flush operation is, mainly depends on two factors: + + +- The size of the EntityManager's current UnitOfWork. +- The configured change tracking policies + +You can get the size of a UnitOfWork as follows: + +.. code-block:: php + + getUnitOfWork()->size(); + +The size represents the number of managed entities in the Unit of +Work. This size affects the performance of flush() operations due +to change tracking (see "Change Tracking Policies") and, of course, +memory consumption, so you may want to check it from time to time +during development. + +.. note:: + + Do not invoke ``flush`` after every change to an entity + or every single invocation of persist/remove/merge/... This is an + anti-pattern and unnecessarily reduces the performance of your + application. Instead, form units of work that operate on your + objects and call ``flush`` when you are done. While serving a + single HTTP request there should be usually no need for invoking + ``flush`` more than 0-2 times. + + +Direct access to a Unit of Work +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can get direct access to the Unit of Work by calling +``EntityManager#getUnitOfWork()``. This will return the UnitOfWork +instance the EntityManager is currently using. + +.. code-block:: php + + getUnitOfWork(); + +.. note:: + + Directly manipulating a UnitOfWork is not recommended. + When working directly with the UnitOfWork API, respect methods + marked as INTERNAL by not using them and carefully read the API + documentation. + + +Entity State +~~~~~~~~~~~~ + +As outlined in the architecture overview an entity can be in one of +four possible states: NEW, MANAGED, REMOVED, DETACHED. If you +explicitly need to find out what the current state of an entity is +in the context of a certain ``EntityManager`` you can ask the +underlying ``UnitOfWork``: + +.. code-block:: php + + getUnitOfWork()->getEntityState($entity)) { + case UnitOfWork::STATE_MANAGED: + ... + case UnitOfWork::STATE_REMOVED: + ... + case UnitOfWork::STATE_DETACHED: + ... + case UnitOfWork::STATE_NEW: + ... + } + +An entity is in MANAGED state if it is associated with an +``EntityManager`` and it is not REMOVED. + +An entity is in REMOVED state after it has been passed to +``EntityManager#remove()`` until the next flush operation of the +same EntityManager. A REMOVED entity is still associated with an +``EntityManager`` until the next flush operation. + +An entity is in DETACHED state if it has persistent state and +identity but is currently not associated with an +``EntityManager``. + +An entity is in NEW state if has no persistent state and identity +and is not associated with an ``EntityManager`` (for example those +just created via the "new" operator). + +Querying +-------- + +Doctrine 2 provides the following ways, in increasing level of +power and flexibility, to query for persistent objects. You should +always start with the simplest one that suits your needs. + +By Primary Key +~~~~~~~~~~~~~~ + +The most basic way to query for a persistent object is by its +identifier / primary key using the +``EntityManager#find($entityName, $id)`` method. Here is an +example: + +.. code-block:: php + + find('MyProject\Domain\User', $id); + +The return value is either the found entity instance or null if no +instance could be found with the given identifier. + +Essentially, ``EntityManager#find()`` is just a shortcut for the +following: + +.. code-block:: php + + getRepository('MyProject\Domain\User')->find($id); + +``EntityManager#getRepository($entityName)`` returns a repository +object which provides many ways to retrieve entities of the +specified type. By default, the repository instance is of type +``Doctrine\ORM\EntityRepository``. You can also use custom +repository classes as shown later. + +By Simple Conditions +~~~~~~~~~~~~~~~~~~~~ + +To query for one or more entities based on several conditions that +form a logical conjunction, use the ``findBy`` and ``findOneBy`` +methods on a repository as follows: + +.. code-block:: php + + getRepository('MyProject\Domain\User')->findBy(array('age' => 20)); + + // All users that are 20 years old and have a surname of 'Miller' + $users = $em->getRepository('MyProject\Domain\User')->findBy(array('age' => 20, 'surname' => 'Miller')); + + // A single user by its nickname + $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + +You can also load by owning side associations through the repository: + +.. code-block:: php + + find('MyProject\Domain\Phonenumber', 1234); + $user = $em->getRepository('MyProject\Domain\User')->findOneBy(array('phone' => $number->getId())); + +Be careful that this only works by passing the ID of the associated entity, not yet by passing the associated entity itself. + +The ``EntityRepository#findBy()`` method additionally accepts orderings, limit and offset as second to fourth parameters: + +.. code-block:: php + + getRepository('MyProject\Domain\User')->findBy(array('age' => 20), array('name' => 'ASC'), 10, 0); + +If you pass an array of values Doctrine will convert the query into a WHERE field IN (..) query automatically: + +.. code-block:: php + + getRepository('MyProject\Domain\User')->findBy(array('age' => array(20, 30, 40))); + // translates roughly to: SELECT * FROM users WHERE age IN (20, 30, 40) + +An EntityRepository also provides a mechanism for more concise +calls through its use of ``__call``. Thus, the following two +examples are equivalent: + +.. code-block:: php + + getRepository('MyProject\Domain\User')->findOneBy(array('nickname' => 'romanb')); + + // A single user by its nickname (__call magic) + $user = $em->getRepository('MyProject\Domain\User')->findOneByNickname('romanb'); + +By Criteria +~~~~~~~~~~~ + +The Repository implement the ``Doctrine\Common\Collections\Selectable`` +interface. That means you can build ``Doctrine\Common\Collections\Criteria`` +and pass them to the ``matching($criteria)`` method. + +See the :ref:`Working with Associations: Filtering collections +`. + +By Eager Loading +~~~~~~~~~~~~~~~~ + +Whenever you query for an entity that has persistent associations +and these associations are mapped as EAGER, they will automatically +be loaded together with the entity being queried and is thus +immediately available to your application. + +By Lazy Loading +~~~~~~~~~~~~~~~ + +Whenever you have a managed entity instance at hand, you can +traverse and use any associations of that entity that are +configured LAZY as if they were in-memory already. Doctrine will +automatically load the associated objects on demand through the +concept of lazy-loading. + +By DQL +~~~~~~ + +The most powerful and flexible method to query for persistent +objects is the Doctrine Query Language, an object query language. +DQL enables you to query for persistent objects in the language of +objects. DQL understands classes, fields, inheritance and +associations. DQL is syntactically very similar to the familiar SQL +but *it is not SQL*. + +A DQL query is represented by an instance of the +``Doctrine\ORM\Query`` class. You create a query using +``EntityManager#createQuery($dql)``. Here is a simple example: + +.. code-block:: php + + createQuery("select u from MyDomain\Model\User u where u.age >= 20 and u.age <= 30"); + $users = $q->getResult(); + +Note that this query contains no knowledge about the relational +schema, only about the object model. DQL supports positional as +well as named parameters, many functions, (fetch) joins, +aggregates, subqueries and much more. Detailed information about +DQL and its syntax as well as the Doctrine class can be found in +:doc:`the dedicated chapter `. +For programmatically building up queries based on conditions that +are only known at runtime, Doctrine provides the special +``Doctrine\ORM\QueryBuilder`` class. More information on +constructing queries with a QueryBuilder can be found +:doc:`in Query Builder chapter `. + +By Native Queries +~~~~~~~~~~~~~~~~~ + +As an alternative to DQL or as a fallback for special SQL +statements native queries can be used. Native queries are built by +using a hand-crafted SQL query and a ResultSetMapping that +describes how the SQL result set should be transformed by Doctrine. +More information about native queries can be found in +:doc:`the dedicated chapter `. + +Custom Repositories +~~~~~~~~~~~~~~~~~~~ + +By default the EntityManager returns a default implementation of +``Doctrine\ORM\EntityRepository`` when you call +``EntityManager#getRepository($entityClass)``. You can overwrite +this behaviour by specifying the class name of your own Entity +Repository in the Annotation, XML or YAML metadata. In large +applications that require lots of specialized DQL queries using a +custom repository is one recommended way of grouping these queries +in a central location. + +.. code-block:: php + + _em->createQuery('SELECT u FROM MyDomain\Model\User u WHERE u.status = "admin"') + ->getResult(); + } + } + +You can access your repository now by calling: + +.. code-block:: php + + getRepository('MyDomain\Model\User')->getAllAdminUsers(); + + diff --git a/docs/en/reference/xml-mapping.rst b/docs/en/reference/xml-mapping.rst new file mode 100644 index 000000000..d82c82fab --- /dev/null +++ b/docs/en/reference/xml-mapping.rst @@ -0,0 +1,747 @@ +XML Mapping +=========== + +The XML mapping driver enables you to provide the ORM metadata in +form of XML documents. + +The XML driver is backed by an XML Schema document that describes +the structure of a mapping document. The most recent version of the +XML Schema document is available online at +`http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd `_. +In order to point to the latest version of the document of a +particular stable release branch, just append the release number, +i.e.: doctrine-mapping-2.0.xsd The most convenient way to work with +XML mapping files is to use an IDE/editor that can provide +code-completion based on such an XML Schema document. The following +is an outline of a XML mapping document with the proper xmlns/xsi +setup for the latest code in trunk. + +.. code-block:: xml + + + + ... + + + +The XML mapping document of a class is loaded on-demand the first +time it is requested and subsequently stored in the metadata cache. +In order to work, this requires certain conventions: + + +- Each entity/mapped superclass must get its own dedicated XML + mapping document. +- The name of the mapping document must consist of the fully + qualified name of the class, where namespace separators are + replaced by dots (.). For example an Entity with the fully + qualified class-name "MyProject" would require a mapping file + "MyProject.Entities.User.dcm.xml" unless the extension is changed. +- All mapping documents should get the extension ".dcm.xml" to + identify it as a Doctrine mapping file. This is more of a + convention and you are not forced to do this. You can change the + file extension easily enough. + +- + +.. code-block:: php + + setFileExtension('.xml'); + +It is recommended to put all XML mapping documents in a single +folder but you can spread the documents over several folders if you +want to. In order to tell the XmlDriver where to look for your +mapping documents, supply an array of paths as the first argument +of the constructor, like this: + +.. code-block:: php + + setMetadataDriverImpl($driver); + +Simplified XML Driver +~~~~~~~~~~~~~~~~~~~~~ + +The Symfony project sponsored a driver that simplifies usage of the XML Driver. +The changes between the original driver are: + +1. File Extension is .orm.xml +2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.xml +3. You can add a global file and add multiple entities in this file. + +Configuration of this client works a little bit different: + +.. code-block:: php + + '/path/to/files1', + 'OtherProject\Entities' => '/path/to/files2' + ); + $driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedXmlDriver($namespaces); + $driver->setGlobalBasename('global'); // global.orm.xml + +Example +------- + +As a quick start, here is a small example document that makes use +of several common elements: + +.. code-block:: xml + + // Doctrine.Tests.ORM.Mapping.User.dcm.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Be aware that class-names specified in the XML files should be +fully qualified. + +XML-Element Reference +--------------------- + +The XML-Element reference explains all the tags and attributes that +the Doctrine Mapping XSD Schema defines. You should read the +Basic-, Association- and Inheritance Mapping chapters to understand +what each of this definitions means in detail. + +Defining an Entity +~~~~~~~~~~~~~~~~~~ + +Each XML Mapping File contains the definition of one entity, +specified as the ```` element as a direct child of the +```` element: + +.. code-block:: xml + + + + + + + +Required attributes: + + +- name - The fully qualified class-name of the entity. + +Optional attributes: + + +- **table** - The Table-Name to be used for this entity. Otherwise the + Unqualified Class-Name is used by default. +- **repository-class** - The fully qualified class-name of an + alternative ``Doctrine\ORM\EntityRepository`` implementation to be + used with this entity. +- **inheritance-type** - The type of inheritance, defaults to none. A + more detailed description follows in the + *Defining Inheritance Mappings* section. +- **read-only** - (>= 2.1) Specifies that this entity is marked as read only and not + considered for change-tracking. Entities of this type can be persisted + and removed though. + +Defining Fields +~~~~~~~~~~~~~~~ + +Each entity class can contain zero to infinite fields that are +managed by Doctrine. You can define them using the ```` +element as a children to the ```` element. The field +element is only used for primitive types that are not the ID of the +entity. For the ID mapping you have to use the ```` element. + +.. code-block:: xml + + + + + + + + + + +Required attributes: + + +- name - The name of the Property/Field on the given Entity PHP + class. + +Optional attributes: + + +- type - The ``Doctrine\DBAL\Types\Type`` name, defaults to + "string" +- column - Name of the column in the database, defaults to the + field name. +- length - The length of the given type, for use with strings + only. +- unique - Should this field contain a unique value across the + table? Defaults to false. +- nullable - Should this field allow NULL as a value? Defaults to + false. +- version - Should this field be used for optimistic locking? Only + works on fields with type integer or datetime. +- scale - Scale of a decimal type. +- precision - Precision of a decimal type. +- column-definition - Optional alternative SQL representation for + this column. This definition begin after the field-name and has to + specify the complete column definition. Using this feature will + turn this field dirty for Schema-Tool update commands at all + times. + +Defining Identity and Generator Strategies +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +An entity has to have at least one ```` element. For +composite keys you can specify more than one id-element, however +surrogate keys are recommended for use with Doctrine 2. The Id +field allows to define properties of the identifier and allows a +subset of the ```` element attributes: + +.. code-block:: xml + + + + + +Required attributes: + + +- name - The name of the Property/Field on the given Entity PHP + class. +- type - The ``Doctrine\DBAL\Types\Type`` name, preferably + "string" or "integer". + +Optional attributes: + + +- column - Name of the column in the database, defaults to the + field name. + +Using the simplified definition above Doctrine will use no +identifier strategy for this entity. That means you have to +manually set the identifier before calling +``EntityManager#persist($entity)``. This is the so called +``ASSIGNED`` strategy. + +If you want to switch the identifier generation strategy you have +to nest a ```` element inside the id-element. This of +course only works for surrogate keys. For composite keys you always +have to use the ``ASSIGNED`` strategy. + +.. code-block:: xml + + + + + + + +The following values are allowed for the ```` strategy +attribute: + + +- AUTO - Automatic detection of the identifier strategy based on + the preferred solution of the database vendor. +- IDENTITY - Use of a IDENTIFY strategy such as Auto-Increment IDs + available to Doctrine AFTER the INSERT statement has been executed. +- SEQUENCE - Use of a database sequence to retrieve the + entity-ids. This is possible before the INSERT statement is + executed. + +If you are using the SEQUENCE strategy you can define an additional +element to describe the sequence: + +.. code-block:: xml + + + + + + + + +Required attributes for ````: + + +- sequence-name - The name of the sequence + +Optional attributes for ````: + + +- allocation-size - By how much steps should the sequence be + incremented when a value is retrieved. Defaults to 1 +- initial-value - What should the initial value of the sequence + be. + + **NOTE** + + If you want to implement a cross-vendor compatible application you + have to specify and additionally define the + element, if Doctrine chooses the sequence strategy for a + platform. + + +Defining a Mapped Superclass +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you want to define a class that multiple entities inherit +from, which itself is not an entity however. The chapter on +*Inheritance Mapping* describes a Mapped Superclass in detail. You +can define it in XML using the ```` tag. + +.. code-block:: xml + + + + + + + + +Required attributes: + + +- name - Class name of the mapped superclass. + +You can nest any number of ```` and unidirectional +```` or ```` associations inside a +mapped superclass. + +Defining Inheritance Mappings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are currently two inheritance persistence strategies that you +can choose from when defining entities that inherit from each +other. Single Table inheritance saves the fields of the complete +inheritance hierarchy in a single table, joined table inheritance +creates a table for each entity combining the fields using join +conditions. + +You can specify the inheritance type in the ```` element +and then use the ```` and +```` attributes. + +.. code-block:: xml + + + + + + + + + + +The allowed values for inheritance-type attribute are ``JOINED`` or +``SINGLE_TABLE``. + +.. note:: + + All inheritance related definitions have to be defined on the root + entity of the hierarchy. + + +Defining Lifecycle Callbacks +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can define the lifecycle callback methods on your entities +using the ```` element: + +.. code-block:: xml + + + + + + + + +Defining One-To-One Relations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can define One-To-One Relations/Associations using the +```` element. The required and optional attributes +depend on the associations being on the inverse or owning side. + +For the inverse side the mapping is as simple as: + +.. code-block:: xml + + + + + +Required attributes for inverse One-To-One: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! +- mapped-by - Name of the field on the owning side (here Address + entity) that contains the owning side association. + +For the owning side this mapping would look like: + +.. code-block:: xml + + + + + +Required attributes for owning One-to-One: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! + +Optional attributes for owning One-to-One: + + +- inversed-by - If the association is bidirectional the + inversed-by attribute has to be specified with the name of the + field on the inverse entity that contains the back-reference. +- orphan-removal - If true, the inverse side entity is always + deleted when the owning side entity is. Defaults to false. +- fetch - Either LAZY or EAGER, defaults to LAZY. This attribute + makes only sense on the owning side, the inverse side *ALWAYS* has + to use the ``FETCH`` strategy. + +The definition for the owning side relies on a bunch of mapping +defaults for the join column names. Without the nested +```` element Doctrine assumes to foreign key to be +called ``user_id`` on the Address Entities table. This is because +the ``MyProject\Address`` entity is the owning side of this +association, which means it contains the foreign key. + +The completed explicitly defined mapping is: + +.. code-block:: xml + + + + + + + +Defining Many-To-One Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The many-to-one association is *ALWAYS* the owning side of any +bidirectional association. This simplifies the mapping compared to +the one-to-one case. The minimal mapping for this association looks +like: + +.. code-block:: xml + + + + + +Required attributes: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- inversed-by - If the association is bidirectional the + inversed-by attribute has to be specified with the name of the + field on the inverse entity that contains the back-reference. +- orphan-removal - If true the entity on the inverse side is + always deleted when the owning side entity is and it is not + connected to any other owning side entity anymore. Defaults to + false. +- fetch - Either LAZY or EAGER, defaults to LAZY. + +This definition relies on a bunch of mapping defaults with regards +to the naming of the join-column/foreign key. The explicitly +defined mapping includes a ```` tag nested inside +the many-to-one association tag: + +.. code-block:: xml + + + + + + + +The join-column attribute ``name`` specifies the column name of the +foreign key and the ``referenced-column-name`` attribute specifies +the name of the primary key column on the User entity. + +Defining One-To-Many Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The one-to-many association is *ALWAYS* the inverse side of any +association. There exists no such thing as a uni-directional +one-to-many association, which means this association only ever +exists for bi-directional associations. + +.. code-block:: xml + + + + + +Required attributes: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! +- mapped-by - Name of the field on the owning side (here + Phonenumber entity) that contains the owning side association. + +Optional attributes: + + +- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY. +- index-by: Index the collection by a field on the target entity. + +Defining Many-To-Many Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +From all the associations the many-to-many has the most complex +definition. When you rely on the mapping defaults you can omit many +definitions and rely on their implicit values. + +.. code-block:: xml + + + + + +Required attributes: + + +- field - Name of the property/field on the entity's PHP class. +- target-entity - Name of the entity associated entity class. If + this is not qualified the namespace of the current class is + prepended. *IMPORTANT:* No leading backslash! + +Optional attributes: + + +- mapped-by - Name of the field on the owning side that contains + the owning side association if the defined many-to-many association + is on the inverse side. +- inversed-by - If the association is bidirectional the + inversed-by attribute has to be specified with the name of the + field on the inverse entity that contains the back-reference. +- fetch - Either LAZY, EXTRA_LAZY or EAGER, defaults to LAZY. +- index-by: Index the collection by a field on the target entity. + +The mapping defaults would lead to a join-table with the name +"User\_Group" being created that contains two columns "user\_id" +and "group\_id". The explicit definition of this mapping would be: + +.. code-block:: xml + + + + + + + + + + + + + + +Here both the ```` and ```` +tags are necessary to tell Doctrine for which side the specified +join-columns apply. These are nested inside a ```` +attribute which allows to specify the table name of the +many-to-many join-table. + +Cascade Element +~~~~~~~~~~~~~~~ + +Doctrine allows cascading of several UnitOfWork operations to +related entities. You can specify the cascade operations in the +```` element inside any of the association mapping +tags. + +.. code-block:: xml + + + + + + + + + +Besides ```` the following operations can be +specified by their respective tags: + + +- ```` +- ```` +- ```` +- ```` + +Join Column Element +~~~~~~~~~~~~~~~~~~~ + +In any explicitly defined association mapping you will need the +```` tag. It defines how the foreign key and primary +key names are called that are used for joining two entities. + +Required attributes: + + +- name - The column name of the foreign key. +- referenced-column-name - The column name of the associated + entities primary key + +Optional attributes: + + +- unique - If the join column should contain a UNIQUE constraint. + This makes sense for Many-To-Many join-columns only to simulate a + one-to-many unidirectional using a join-table. +- nullable - should the join column be nullable, defaults to true. +- on-delete - Foreign Key Cascade action to perform when entity is + deleted, defaults to NO ACTION/RESTRICT but can be set to + "CASCADE". + +Defining Order of To-Many Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can require one-to-many or many-to-many associations to be +retrieved using an additional ``ORDER BY``. + +.. code-block:: xml + + + + + + + + + +Defining Indexes or Unique Constraints +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To define additional indexes or unique constraints on the entities +table you can use the ```` and +```` elements: + +.. code-block:: xml + + + + + + + + + + + + + +You have to specify the column and not the entity-class field names +in the index and unique-constraint definitions. + +Derived Entities ID syntax +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If the primary key of an entity contains a foreign key to another entity we speak of a derived +entity relationship. You can define this in XML with the "association-key" attribute in the ```` tag. + +.. code-block:: xml + + + + + + + + + + + + + diff --git a/docs/en/reference/yaml-mapping.rst b/docs/en/reference/yaml-mapping.rst new file mode 100644 index 000000000..9a00d08b3 --- /dev/null +++ b/docs/en/reference/yaml-mapping.rst @@ -0,0 +1,119 @@ +YAML Mapping +============ + +The YAML mapping driver enables you to provide the ORM metadata in +form of YAML documents. + +The YAML mapping document of a class is loaded on-demand the first +time it is requested and subsequently stored in the metadata cache. +In order to work, this requires certain conventions: + + +- Each entity/mapped superclass must get its own dedicated YAML + mapping document. +- The name of the mapping document must consist of the fully + qualified name of the class, where namespace separators are + replaced by dots (.). +- All mapping documents should get the extension ".dcm.yml" to + identify it as a Doctrine mapping file. This is more of a + convention and you are not forced to do this. You can change the + file extension easily enough. + +- + +.. code-block:: php + + setFileExtension('.yml'); + +It is recommended to put all YAML mapping documents in a single +folder but you can spread the documents over several folders if you +want to. In order to tell the YamlDriver where to look for your +mapping documents, supply an array of paths as the first argument +of the constructor, like this: + +.. code-block:: php + + setMetadataDriverImpl($driver); + +Simplified YAML Driver +~~~~~~~~~~~~~~~~~~~~~~ + +The Symfony project sponsored a driver that simplifies usage of the YAML Driver. +The changes between the original driver are: + +1. File Extension is .orm.yml +2. Filenames are shortened, "MyProject\Entities\User" will become User.orm.yml +3. You can add a global file and add multiple entities in this file. + +Configuration of this client works a little bit different: + +.. code-block:: php + + 'MyProject\Entities', + '/path/to/files2' => 'OtherProject\Entities' + ); + $driver = new \Doctrine\ORM\Mapping\Driver\SimplifiedYamlDriver($namespaces); + $driver->setGlobalBasename('global'); // global.orm.yml + +Example +------- + +As a quick start, here is a small example document that makes use +of several common elements: + +.. code-block:: yaml + + # Doctrine.Tests.ORM.Mapping.User.dcm.yml + Doctrine\Tests\ORM\Mapping\User: + type: entity + table: cms_users + indexes: + name_index: + columns: [ name ] + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + length: 50 + oneToOne: + address: + targetEntity: Address + joinColumn: + name: address_id + referencedColumnName: id + oneToMany: + phonenumbers: + targetEntity: Phonenumber + mappedBy: user + cascade: ["persist", "merge"] + manyToMany: + groups: + targetEntity: Group + joinTable: + name: cms_users_groups + joinColumns: + user_id: + referencedColumnName: id + inverseJoinColumns: + group_id: + referencedColumnName: id + lifecycleCallbacks: + prePersist: [ doStuffOnPrePersist, doOtherStuffOnPrePersistToo ] + postPersist: [ doStuffOnPostPersist ] + +Be aware that class-names specified in the YAML files should be +fully qualified. + + diff --git a/docs/en/toc.rst b/docs/en/toc.rst new file mode 100644 index 000000000..73f1f25b9 --- /dev/null +++ b/docs/en/toc.rst @@ -0,0 +1,80 @@ +Welcome to Doctrine 2 ORM's documentation! +========================================== + +Tutorials +--------- + +.. toctree:: + :maxdepth: 1 + + tutorials/getting-started + tutorials/working-with-indexed-associations + tutorials/extra-lazy-associations + tutorials/composite-primary-keys + tutorials/ordered-associations + tutorials/in-ten-quick-steps + tutorials/override-field-association-mappings-in-subclasses + +Reference Guide +--------------- + +.. toctree:: + :maxdepth: 1 + :numbered: + + reference/introduction + reference/architecture + reference/configuration + reference/faq + reference/basic-mapping + reference/association-mapping + reference/inheritance-mapping + reference/working-with-objects + reference/working-with-associations + reference/events + reference/unitofwork + reference/unitofwork-associations + reference/transactions-and-concurrency + reference/batch-processing + reference/dql-doctrine-query-language + reference/query-builder + reference/native-sql + reference/change-tracking-policies + reference/partial-objects + reference/xml-mapping + reference/yaml-mapping + reference/annotations-reference + reference/php-mapping + reference/caching + reference/improving-performance + reference/tools + reference/metadata-drivers + reference/best-practices + reference/limitations-and-known-issues + tutorials/pagination.rst + reference/filters.rst + reference/namingstrategy.rst + + +Cookbook +-------- + +.. toctree:: + :maxdepth: 1 + + cookbook/aggregate-fields + cookbook/decorator-pattern + cookbook/dql-custom-walkers + cookbook/dql-user-defined-functions + cookbook/implementing-arrayaccess-for-domain-objects + cookbook/implementing-the-notify-changetracking-policy + cookbook/implementing-wakeup-or-clone + cookbook/integrating-with-codeigniter + cookbook/sql-table-prefixes + cookbook/strategy-cookbook-introduction + cookbook/validation-of-entities + cookbook/working-with-datetime + cookbook/mysql-enums + cookbook/advanced-field-value-conversion-using-custom-mapping-types + cookbook/entities-in-session + diff --git a/docs/en/tutorials/composite-primary-keys.rst b/docs/en/tutorials/composite-primary-keys.rst new file mode 100644 index 000000000..dcf8bc687 --- /dev/null +++ b/docs/en/tutorials/composite-primary-keys.rst @@ -0,0 +1,351 @@ +Composite and Foreign Keys as Primary Key +========================================= + +Doctrine 2 supports composite primary keys natively. Composite keys are a very powerful relational database concept +and we took good care to make sure Doctrine 2 supports as many of the composite primary key use-cases. +For Doctrine 2.0 composite keys of primitive data-types are supported, for Doctrine 2.1 even foreign keys as +primary keys are supported. + +This tutorial shows how the semantics of composite primary keys work and how they map to the database. + +General Considerations +~~~~~~~~~~~~~~~~~~~~~~ + +Every entity with a composite key cannot use an id generator other than "ASSIGNED". That means +the ID fields have to have their values set before you call ``EntityManager#persist($entity)``. + +Primitive Types only +~~~~~~~~~~~~~~~~~~~~ + +Even in version 2.0 you can have composite keys as long as they only consist of the primative types +``integer`` and ``string``. Suppose you want to create a database of cars and use the model-name +and year of production as primary keys: + +.. configuration-block:: + + .. code-block:: php + + name = $name; + $this->year = $year; + } + + public function getModelName() + { + return $this->name; + } + + public function getYearOfProduction() + { + return $this->year; + } + } + + .. code-block:: xml + + + + + + + + + + + .. code-block:: yaml + + VehicleCatalogue\Model\Car: + type: entity + id: + name: + type: string + year: + type: integer + +Now you can use this entity: + +.. code-block:: php + + persist($car); + $em->flush(); + +And for querying you can use arrays to both DQL and EntityRepositories: + +.. code-block:: php + + find("VehicleCatalogue\Model\Car", array("name" => "Audi A8", "year" => 2010)); + + $dql = "SELECT c FROM VehicleCatalogue\Model\Car c WHERE c.id = ?1"; + $audi = $em->createQuery($dql) + ->setParameter(1, array("name" => "Audi A8", "year" => 2010)) + ->getSingleResult(); + +You can also use this entity in associations. Doctrine will then generate two foreign keys one for ``name`` +and to ``year`` to the related entities. + +.. note:: + + This example shows how you can nicely solve the requirement for existing + values before ``EntityManager#persist()``: By adding them as mandatory values for the constructor. + +Identity through foreign Entities +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. note:: + + Identity through foreign entities is only supported with Doctrine 2.1 + +There are tons of use-cases where the identity of an Entity should be determined by the entity +of one or many parent entities. + +- Dynamic Attributes of an Entity (for example Article). Each Article has many + attributes with primary key "article_id" and "attribute_name". +- Address object of a Person, the primary key of the adress is "user_id". This is not a case of a composite primary + key, but the identity is derived through a foreign entity and a foreign key. +- Join Tables with metadata can be modelled as Entity, for example connections between two articles + with a little description and a score. + +The semantics of mapping identity through foreign entities are easy: + +- Only allowed on Many-To-One or One-To-One associations. +- Plug an ``@Id`` annotation onto every association. +- Set an attribute ``association-key`` with the field name of the association in XML. +- Set a key ``associationKey:`` with the field name of the association in YAML. + +Use-Case 1: Dynamic Attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +We keep up the example of an Article with arbitrary attributes, the mapping looks like this: + +.. configuration-block:: + + .. code-block:: php + + attributes[$name] = new ArticleAttribute($name, $value, $this); + } + } + + /** + * @Entity + */ + class ArticleAttribute + { + /** @Id @ManyToOne(targetEntity="Article", inversedBy="attributes") */ + private $article; + + /** @Id @Column(type="string") */ + private $attribute; + + /** @Column(type="string") */ + private $value; + + public function __construct($name, $value, $article) + { + $this->attribute = $name; + $this->value = $value; + $this->article = $article; + } + } + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: yaml + + Application\Model\ArticleAttribute: + type: entity + id: + article: + associationKey: true + attribute: + type: string + fields: + value: + type: string + manyToOne: + article: + targetEntity: Article + inversedBy: attributes + + +Use-Case 2: Simple Derived Identity +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sometimes you have the requirement that two objects are related by a One-To-One association +and that the dependent class should re-use the primary key of the class it depends on. +One good example for this is a user-address relationship: + +.. code-block:: php + + customer = $customer; + $this->items = new ArrayCollection(); + $this->created = new \DateTime("now"); + } + } + + /** @Entity */ + class Product + { + /** @Id @Column(type="integer") @GeneratedValue */ + private $id; + + /** @Column(type="string") */ + private $name; + + /** @Column(type="decimal") */ + private $currentPrice; + + public function getCurrentPrice() + { + return $this->currentPrice; + } + } + + /** @Entity */ + class OrderItem + { + /** @Id @ManyToOne(targetEntity="Order") */ + private $order; + + /** @Id @ManyToOne(targetEntity="Product") */ + private $product; + + /** @Column(type="integer") */ + private $amount = 1; + + /** @Column(type="decimal") */ + private $offeredPrice; + + public function __construct(Order $order, Product $product, $amount = 1) + { + $this->order = $order; + $this->product = $product; + $this->offeredPrice = $product->getCurrentPrice(); + } + } + + +Performance Considerations +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Using composite keys always comes with a performance hit compared to using entities with +a simple surrogate key. This performance impact is mostly due to additional PHP code that is +necessary to handle this kind of keys, most notably when using derived identifiers. + +On the SQL side there is not much overhead as no additional or unexpected queries have to be +executed to manage entities with derived foreign keys. diff --git a/docs/en/tutorials/extra-lazy-associations.rst b/docs/en/tutorials/extra-lazy-associations.rst new file mode 100644 index 000000000..59fc440a0 --- /dev/null +++ b/docs/en/tutorials/extra-lazy-associations.rst @@ -0,0 +1,82 @@ +Extra Lazy Associations +======================= + +In many cases associations between entities can get pretty large. Even in a simple scenario like a blog. +where posts can be commented, you always have to assume that a post draws hundreds of comments. +In Doctrine 2.0 if you accessed an association it would always get loaded completely into memory. This +can lead to pretty serious performance problems, if your associations contain several hundreds or thousands +of entities. + +With Doctrine 2.1 a feature called **Extra Lazy** is introduced for associations. Associations +are marked as **Lazy** by default, which means the whole collection object for an association is populated +the first time its accessed. If you mark an association as extra lazy the following methods on collections +can be called without triggering a full load of the collection: + +- ``Collection#contains($entity)`` +- ``Collection#count()`` +- ``Collection#slice($offset, $length = null)`` + +For each of this three methods the following semantics apply: + +- For each call, if the Collection is not yet loaded, issue a straight SELECT statement against the database. +- For each call, if the collection is already loaded, fallback to the default functionality for lazy collections. No additional SELECT statements are executed. + +Additionally even with Doctrine 2.0 the following methods do not trigger the collection load: + +- ``Collection#add($entity)`` +- ``Collection#offsetSet($key, $entity)`` - ArrayAccess with no specific key ``$coll[] = $entity``, it does + not work when setting specific keys like ``$coll[0] = $entity``. + +With extra lazy collections you can now not only add entities to large collections but also paginate them +easily using a combination of ``count`` and ``slice``. + + +Enabling Extra-Lazy Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The mapping configuration is simple. Instead of using the default value of ``fetch="LAZY"`` you have to +switch to extra lazy as shown in these examples: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + .. code-block:: yaml + + Doctrine\Tests\Models\CMS\CmsGroup: + type: entity + # ... + manyToMany: + users: + targetEntity: CmsUser + mappedBy: groups + fetch: EXTRA_LAZY + diff --git a/docs/en/tutorials/getting-started-database.rst b/docs/en/tutorials/getting-started-database.rst new file mode 100644 index 000000000..c625193ae --- /dev/null +++ b/docs/en/tutorials/getting-started-database.rst @@ -0,0 +1,27 @@ +Getting Started: Database First +=============================== + +.. note:: *Development Workflows* + + When you :doc:`Code First `, you + start with developing Objects and then map them onto your database. When + you :doc:`Model First `, you are modelling your application using tools (for + example UML) and generate database schema and PHP code from this model. + When you have a :doc:`Database First `, you already have a database schema + and generate the corresponding PHP code from it. + +.. note:: + + This getting started guide is in development. + +Development of new applications often starts with an existing database schema. +When the database schema is the starting point for your application, then +development is said to use the *Database First* approach to Doctrine. + +In this workflow you would modify the database schema first and then +regenerate the PHP code to use with this schema. You need a flexible +code-generator for this task and up to Doctrine 2.2, the code generator hasn't +been flexible enough to achieve this. + +We spinned off a subproject, Doctrine CodeGenerator, that will fill this gap and +allow you to do *Database First* development. diff --git a/docs/en/tutorials/getting-started-models.rst b/docs/en/tutorials/getting-started-models.rst new file mode 100644 index 000000000..e844b4d68 --- /dev/null +++ b/docs/en/tutorials/getting-started-models.rst @@ -0,0 +1,24 @@ +Getting Started: Model First +============================ + +.. note:: *Development Workflows* + + When you :doc:`Code First `, you + start with developing Objects and then map them onto your database. When + you :doc:`Model First `, you are modelling your application using tools (for + example UML) and generate database schema and PHP code from this model. + When you have a :doc:`Database First `, then you already have a database schema + and generate the corresponding PHP code from it. + +.. note:: + + This getting started guide is in development. + +There are applications when you start with a high-level description of the +model using modelling tools such as UML. Modelling tools could also be Excel, +XML or CSV files that describe the model in some structured way. If your +application is using a modelling tool, then the development workflow is said to +be a *Model First* approach to Doctrine2. + +In this workflow you always change the model description and then regenerate +both PHP code and database schema from this model. diff --git a/docs/en/tutorials/getting-started.rst b/docs/en/tutorials/getting-started.rst new file mode 100644 index 000000000..d031e5d29 --- /dev/null +++ b/docs/en/tutorials/getting-started.rst @@ -0,0 +1,1516 @@ +Getting Started: Code First +=========================== + +.. note:: *Development Workflows* + + When you :doc:`Code First `, you + start with developing Objects and then map them onto your database. When + you :doc:`Model First `, you are modelling your application using tools (for + example UML) and generate database schema and PHP code from this model. + When you have a :doc:`Database First `, then you already have a database schema + and generate the corresponding PHP code from it. + +Doctrine 2 is an object-relational mapper (ORM) for PHP 5.3.0+ that provides +transparent persistence for PHP objects. It uses the Data Mapper pattern at +the heart of this project, aiming for a complete separation of the +domain/business logic from the persistence in a relational database management +system. The benefit of Doctrine for the programmer is the ability to focus +solely on the object-oriented business logic and worry about persistence only +as a secondary task. This doesn't mean persistence is not important to Doctrine +2, however it is our belief that there are considerable benefits for +object-oriented programming if persistence and entities are kept perfectly +separated. + +Starting with the object-oriented model is called the *Code First* approach to +Doctrine. + +.. note:: + + The code of this tutorial is `available on Github `_. + +What are Entities? +------------------ + +Entities are lightweight PHP Objects that don't need to extend any +abstract base class or interface. An entity class must not be final +or contain final methods. Additionally it must not implement +**clone** nor **wakeup** or :doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`. + +See the :doc:`architecture chapter <../reference/architecture>` for a full list of the restrictions +that your entities need to comply with. + +An entity contains persistable properties. A persistable property +is an instance variable of the entity that is saved into and retrieved from the database +by Doctrine's data mapping capabilities. + +An Example Model: Bug Tracker +----------------------------- + +For this Getting Started Guide for Doctrine we will implement the +Bug Tracker domain model from the +`Zend\_Db\_Table `_ +documentation. Reading their documentation we can extract the +requirements to be: + + +- A Bugs has a description, creation date, status, reporter and + engineer +- A bug can occur on different products (platforms) +- Products have a name. +- Bug Reporter and Engineers are both Users of the System. +- A user can create new bugs. +- The assigned engineer can close a bug. +- A user can see all his reported or assigned bugs. +- Bugs can be paginated through a list-view. + +.. warning:: + + This tutorial is incrementally building up your Doctrine 2 + knowledge and even lets you make some mistakes, to show some common + pitfalls in mapping Entities to a database. Don't blindly + copy-paste the examples here, it is not production ready without + the additional comments and knowledge this tutorial teaches. + +Setup Project +------------- + +Create a new empty folder for this tutorial project, for example +``doctrine2-tutorial`` and create a new file ``composer.json`` with +the following contents: + +:: + + { + "require": { + "doctrine/orm": "2.*", + "symfony/console": "2.*", + "symfony/yaml": "2.*" + }, + "autoload": { + "psr-0": {"": "entities"} + } + } + +Install Doctrine using the Composer Dependency Management tool, by calling: + +:: + + $ composer install + +This ill install the packages Doctrine Common, Doctrine DBAL, Doctrine ORM, +Symfony YAML and Symfony Console. Both Symfony dependencies are optional +but will be used in this tutorial. + +You can prepare the directory structure: + +:: + + project + |-- composer.json + |-- config + | |-- xml + | `-- yaml + `-- entities + +A first prototype +----------------- + +We start with a simplified design for the bug tracker domain, by creating three +classes ``Bug``, ``Product`` and ``User`` and putting them into +`entities/Bug.php`, `entities/Product.php` and `entities/User.php` +respectively. + +.. code-block:: php + + id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + } + +.. code-block:: php + + id; + } + + public function getName() + { + return $this->name; + } + + public function setName($name) + { + $this->name = $name; + } + } + +.. warning:: + + Properties should never be public when using Doctrine. + This will lead to bugs with the way lazy loading works in Doctrine. + +You see that all properties have getters and setters except `$id`. +Doctrine 2 uses Reflection to access the values in all your entities properties, so it +is possible to set the `$id` value for Doctrine, however not from +your application code. The use of reflection by Doctrine allows you +to completely encapsulate state and state changes in your entities. + +Many of the fields are single scalar values, for example the 3 ID +fields of the entities, their names, description, status and change +dates. Doctrine 2 can easily handle these single values as can any +other ORM. From a point of our domain model they are ready to be +used right now and we will see at a later stage how they are mapped +to the database. + +We will soon add references between objects in this domain +model. The semantics are discussed case by case to +explain how Doctrine handles them. In general each OneToOne or +ManyToOne Relation in the Database is replaced by an instance of +the related object in the domain model. Each OneToMany or +ManyToMany Relation is replaced by a collection of instances in the +domain model. You never have to work with the foreign keys, only +with objects that represent the foreign key through their own identity. + +If you think this through carefully you realize Doctrine 2 will +load up the complete database in memory if you access one object. +However by default Doctrine generates Lazy Load proxies of entities +or collections of all the relations that haven't been explicitly +retrieved from the database yet. + +To be able to use lazyload with collections, simple PHP arrays have +to be replaced by a generic collection interface for Doctrine which +tries to act as as much like an array as possible by using ArrayAccess, +IteratorAggregate and Countable interfaces. The class is the most +simple implementation of this interface. + +Now that we know this, we have to clear up our domain model to cope +with the assumptions about related collections: + +.. code-block:: php + + products = new ArrayCollection(); + } + } + +.. code-block:: php + + reportedBugs = new ArrayCollection(); + $this->assignedBugs = new ArrayCollection(); + } + } + +Whenever an entity is recreated from the database, an Collection +implementation of the type Doctrine is injected into your entity +instead of an array. Compared to the ArrayCollection this +implementation helps the Doctrine ORM understand the changes that +have happened to the collection which are noteworthy for +persistence. + +.. warning:: + + Lazy load proxies always contain an instance of + Doctrine's EntityManager and all its dependencies. Therefore a + var\_dump() will possibly dump a very large recursive structure + which is impossible to render and read. You have to use + ``Doctrine\Common\Util\Debug::dump()`` to restrict the dumping to a + human readable level. Additionally you should be aware that dumping + the EntityManager to a Browser may take several minutes, and the + Debug::dump() method just ignores any occurrences of it in Proxy + instances. + + +Because we only work with collections for the references we must be +careful to implement a bidirectional reference in the domain model. +The concept of owning or inverse side of a relation is central to +this notion and should always be kept in mind. The following +assumptions are made about relations and have to be followed to be +able to work with Doctrine 2. These assumptions are not unique to +Doctrine 2 but are best practices in handling database relations +and Object-Relational Mapping. + + +- Changes to Collections are saved or updated, when the entity on + the *owning* side of the collection is saved or updated. +- Saving an Entity at the inverse side of a relation never + triggers a persist operation to changes to the collection. +- In a one-to-one relation the entity holding the foreign key of + the related entity on its own database table is *always* the owning + side of the relation. +- In a many-to-many relation, both sides can be the owning side of + the relation. However in a bi-directional many-to-many relation + only one is allowed to be. +- In a many-to-one relation the Many-side is the owning side by + default, because it holds the foreign key. +- The OneToMany side of a relation is inverse by default, since + the foreign key is saved on the Many side. A OneToMany relation can + only be the owning side, if its implemented using a ManyToMany + relation with join table and restricting the one side to allow only + UNIQUE values per database constraint. + +.. note:: + + Consistency of bi-directional references on the inverse side of a + relation have to be managed in userland application code. Doctrine + cannot magically update your collections to be consistent. + + +In the case of Users and Bugs we have references back and forth to +the assigned and reported bugs from a user, making this relation +bi-directional. We have to change the code to ensure consistency of +the bi-directional reference: + +.. code-block:: php + + assignedToBug($this); + $this->engineer = $engineer; + } + + public function setReporter($reporter) + { + $reporter->addReportedBug($this); + $this->reporter = $reporter; + } + + public function getEngineer() + { + return $this->engineer; + } + + public function getReporter() + { + return $this->reporter; + } + } + +.. code-block:: php + + reportedBugs[] = $bug; + } + + public function assignedToBug($bug) + { + $this->assignedBugs[] = $bug; + } + } + +I chose to name the inverse methods in past-tense, which should +indicate that the actual assigning has already taken place and the +methods are only used for ensuring consistency of the references. +This approach is my personal preference, you can choose whatever +method to make this work. + +You can see from ``User::addReportedBug()`` and +``User::assignedToBug()`` that using this method in userland alone +would not add the Bug to the collection of the owning side in +``Bug::$reporter`` or ``Bug::$engineer``. Using these methods and +calling Doctrine for persistence would not update the collections +representation in the database. + +Only using ``Bug::setEngineer()`` or ``Bug::setReporter()`` +correctly saves the relation information. We also set both +collection instance variables to protected, however with PHP 5.3's +new features Doctrine is still able to use Reflection to set and +get values from protected and private properties. + +The ``Bug::$reporter`` and ``Bug::$engineer`` properties are +Many-To-One relations, which point to a User. In a normalized +relational model the foreign key is saved on the Bug's table, hence +in our object-relation model the Bug is at the owning side of the +relation. You should always make sure that the use-cases of your +domain model should drive which side is an inverse or owning one in +your Doctrine mapping. In our example, whenever a new bug is saved +or an engineer is assigned to the bug, we don't want to update the +User to persist the reference, but the Bug. This is the case with +the Bug being at the owning side of the relation. + +Bugs reference Products by an uni-directional ManyToMany relation in +the database that points from Bugs to Products. + +.. code-block:: php + + products[] = $product; + } + + public function getProducts() + { + return $this->products; + } + } + +We are now finished with the domain model given the requirements. +From the simple model with public properties only we had to do +quite some work to get to a model where we encapsulated the +references between the objects to make sure we don't break its +consistent state when using Doctrine. + +However up to now the assumptions Doctrine imposed on our business +objects have not restricting us much in our domain modelling +capabilities. Actually we would have encapsulated access to all the +properties anyways by using object-oriented best-practices. + +Metadata Mappings for our Entities +---------------------------------- + +Up to now we have only implemented our Entities as Data-Structures +without actually telling Doctrine how to persist them in the +database. If perfect in-memory databases would exist, we could now +finish the application using these entities by implementing code to +fulfil all the requirements. However the world isn't perfect and we +have to persist our entities in some storage to make sure we don't +loose their state. Doctrine currently serves Relational Database +Management Systems. In the future we are thinking to support NoSQL +vendors like CouchDb or MongoDb, however this is still far in the +future. + +The next step for persistence with Doctrine is to describe the +structure of our domain model entities to Doctrine using a metadata +language. The metadata language describes how entities, their +properties and references should be persisted and what constraints +should be applied to them. + +Metadata for entities are loaded using a +``Doctrine\ORM\Mapping\Driver\Driver`` implementation and Doctrine +2 already comes with XML, YAML and Annotations Drivers. This +Getting Started Guide will show the mappings for all Mapping Drivers. +References in the text will be made to the XML mapping. + +Since we haven't namespaced our three entities, we have to +implement three mapping files called Bug.dcm.xml, Product.dcm.xml +and User.dcm.xml (or .yml) and put them into a distinct folder for mapping +configurations. For the annotations driver we need to use +doc-block comments on the entity classes themselves. + +The first discussed definition will be for the Product, since it is +the most simple one: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + .. code-block:: yaml + + # config/yaml/Product.dcm.yml + Product: + type: entity + table: products + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + +The top-level ``entity`` definition tag specifies information about +the class and table-name. The primitive type ``Product::$name`` is +defined as ``field`` attributes. The Id property is defined with +the ``id`` tag. The id has a ``generator`` tag nested inside which +defines that the primary key generation mechanism automatically +uses the database platforms native id generation strategy, for +example AUTO INCREMENT in the case of MySql or Sequences in the +case of PostgreSql and Oracle. + +We then go on specifying the definition of a Bug: + +.. configuration-block:: + .. code-block:: php + + + + + + + + + + + + + + + + + + + + + .. code-block:: yaml + + # config/yaml/Bug.dcm.yml + Bug: + type: entity + table: bugs + id: + id: + type: integer + generator: + strategy: AUTO + fields: + description: + type: text + created: + type: datetime + status: + type: string + manyToOne: + reporter: + targetEntity: User + inversedBy: reportedBugs + engineer: + targetEntity: User + inversedBy: assignedBugs + manyToMany: + products: + targetEntity: Product + + +Here again we have the entity, id and primitive type definitions. +The column names are used from the Zend\_Db\_Table examples and +have different names than the properties on the Bug class. +Additionally for the "created" field it is specified that it is of +the Type "DATETIME", which translates the YYYY-mm-dd HH:mm:ss +Database format into a PHP DateTime instance and back. + +After the field definitions the two qualified references to the +user entity are defined. They are created by the ``many-to-one`` +tag. The class name of the related entity has to be specified with +the ``target-entity`` attribute, which is enough information for +the database mapper to access the foreign-table. Since +``reporter`` and ``engineer`` are on the owning side of a +bi-directional relation we also have to specify the ``inversed-by`` +attribute. They have to point to the field names on the inverse +side of the relationship. We will see in the next example that the ``inversed-by`` +attribute has a counterpart ``mapped-by`` which makes that +the inverse side. + +The last missing property is the ``Bug::$products`` collection. It +holds all products where the specific bug is occurring in. Again +you have to define the ``target-entity`` and ``field`` attributes +on the ``many-to-many`` tag. Furthermore you have to specify the +details of the many-to-many join-table and its foreign key columns. +The definition is rather complex, however relying on the XML +auto-completion I got it working easily, although I forget the +schema details all the time. + +The last missing definition is that of the User entity: + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + + + + + .. code-block:: yaml + + # config/xml/User.dcm.yml + User: + type: entity + table: users + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type: string + oneToMany: + reportedBugs: + targetEntity: Bug + mappedBy: reporter + assignedBugs: + targetEntity: Bug + mappedBy: engineer + +Here are some new things to mention about the ``one-to-many`` tags. +Remember that we discussed about the inverse and owning side. Now +both reportedBugs and assignedBugs are inverse relations, which +means the join details have already been defined on the owning +side. Therefore we only have to specify the property on the Bug +class that holds the owning sides. + +This example has a fair overview of the most basic features of the +metadata definition language. + +Obtaining the EntityManager +--------------------------- + +Doctrine's public interface is the EntityManager, it provides the +access point to the complete lifecycle management of your entities +and transforms entities from and back to persistence. You have to +configure and create it to use your entities with Doctrine 2. I +will show the configuration steps and then discuss them step by +step: + +.. code-block:: php + + ` for up to date autoloading details. + use Doctrine\ORM\Tools\Setup; + + require_once "Doctrine/ORM/Tools/Setup.php"; + Setup::registerAutoloadPEAR(); + + // Create a simple "default" Doctrine ORM configuration for XML Mapping + $isDevMode = true; + $config = Setup::createXMLMetadataConfiguration(array(__DIR__."/config/xml"), $isDevMode); + // or if you prefer yaml or annotations + //$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__."/entities"), $isDevMode); + //$config = Setup::createYAMLMetadataConfiguration(array(__DIR__."/config/yaml"), $isDevMode); + + // database configuration parameters + $conn = array( + 'driver' => 'pdo_sqlite', + 'path' => __DIR__ . '/db.sqlite', + ); + + // obtaining the entity manager + $entityManager = \Doctrine\ORM\EntityManager::create($conn, $config); + +The first block sets up the autoloading capabilities of Doctrine. +We assume here that you have installed Doctrine using PEAR. +See :doc:`Configuration <../reference/configuration>` for more details +on other installation procedures. + +The second block consists of the instantiation of the ORM +Configuration object using the Setup helper. It assumes a bunch +of defaults that you don't have to bother about for now. You can +read up on the configuration details in the +:doc:`reference chapter on configuration <../reference/configuration>`. + +The third block shows the configuration options required to connect +to a database, in my case a file-based sqlite database. All the +configuration options for all the shipped drivers are given in the +`DBAL Configuration section of the manual `_. + +You should make sure to make it configurable if Doctrine should run +in dev or production mode using the `$devMode` variable. You can +use an environment variable for example, hook into your frameworks configuration +or check for the HTTP_HOST of your devsystem (localhost for example) + +.. code-block:: php + + new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($entityManager) + )); + +You can then change into your project directory and call the +Doctrine command-line tool: + +:: + + $ cd project/ + $ doctrine orm:schema-tool:create + +.. note:: + + The ``doctrine`` command will only be present if you installed + Doctrine from PEAR. Otherwise you will have to dig into the + ``bin/doctrine.php`` code of your Doctrine 2 directory to setup + your doctrine command-line client. + + See the + :doc:`Tools section of the manual <../reference/tools>` + on how to setup the Doctrine console correctly. + + +During the development you probably need to re-create the database +several times when changing the Entity metadata. You can then +either re-create the database: + +:: + + $ doctrine orm:schema-tool:drop --force + $ doctrine orm:schema-tool:create + +Or use the update functionality: + +:: + + $ doctrine orm:schema-tool:update --force + +The updating of databases uses a Diff Algorithm for a given +Database Schema, a cornerstone of the ``Doctrine\DBAL`` package, +which can even be used without the Doctrine ORM package. However +its not available in SQLite since it does not support ALTER TABLE. + +Writing Entities into the Database +---------------------------------- + +.. note:: + + This tutorial assumes you call all the example scripts from the CLI. + +Having created the schema we can now start and save entities in the +database. For starters we need a create user use-case: + +.. code-block:: php + + setName($newUsername); + + $entityManager->persist($user); + $entityManager->flush(); + + echo "Created User with ID " . $user->getId() . "\n"; + +Products can also be created: + +.. code-block:: php + + setName($newProductName); + + $entityManager->persist($product); + $entityManager->flush(); + + echo "Created Product with ID " . $product->getId() . "\n"; + +Now call: + +:: + + $ php create_user.php beberlei + $ php create_product.php MyProduct + +So what is happening in those two snippets? In both examples the +code that works on User and Product is pretty standard OOP. The interesting bits are the +communication with the ``EntityManager``. To notify the +EntityManager that a new entity should be inserted into the +database you have to call ``persist()``. However the EntityManager +does not act on this command, its merely notified. You have to explicitly +call ``flush()`` to have the EntityManager write those two entities +to the database. + +You might wonder why does this distinction between persist +notification and flush exist: Doctrine 2 uses the UnitOfWork +pattern to aggregate all writes (INSERT, UPDATE, DELETE) into one +single transaction, which is executed when flush is called. +Using this approach the write-performance is significantly better +than in a scenario where updates are done for each entity in +isolation. In more complex scenarios than the previous two, you are +free to request updates on many different entities and all flush +them at once. + +Doctrine's UnitOfWork detects entities that have changed after +retrieval from the database automatically when the flush operation +is called, so that you only have to keep track of those entities +that are new or to be removed and pass them to +``EntityManager#persist()`` and ``EntityManager#remove()`` +respectively. + +We are now getting to the "Create a New Bug" requirement and the +code for this scenario may look like this: + +.. code-block:: php + + find("User", $theReporterId); + $engineer = $entityManager->find("User", $theDefaultEngineerId); + if (!$reporter || !$engineer) { + echo "No reporter and/or engineer found for the input.\n"; + exit(1); + } + + $bug = new Bug(); + $bug->setDescription("Something does not work!"); + $bug->setCreated(new DateTime("now")); + $bug->setStatus("OPEN"); + + foreach ($productIds AS $productId) { + $product = $entityManager->find("Product", $productId); + $bug->assignToProduct($product); + } + + $bug->setReporter($reporter); + $bug->setEngineer($engineer); + + $entityManager->persist($bug); + $entityManager->flush(); + + echo "Your new Bug Id: ".$bug->getId()."\n"; + +Since we only have one user and product, probably with the ID of 1, we can call this script with: + +:: + + php create_bug.php 1 1 1 + +This is the first contact with the read API of the EntityManager, +showing that a call to ``EntityManager#find($name, $id)`` returns a +single instance of an entity queried by primary key. Besides this +we see the persist + flush pattern again to save the Bug into the +database. + +See how simple relating Bug, Reporter, Engineer and Products is +done by using the discussed methods in the "A first prototype" +section. The UnitOfWork will detect this relations when flush is +called and relate them in the database appropriately. + +Queries for Application Use-Cases +--------------------------------- + +List of Bugs +~~~~~~~~~~~~ + +Using the previous examples we can fill up the database quite a +bit, however we now need to discuss how to query the underlying +mapper for the required view representations. When opening the +application, bugs can be paginated through a list-view, which is +the first read-only use-case: + +.. code-block:: php + + createQuery($dql); + $query->setMaxResults(30); + $bugs = $query->getResult(); + + foreach($bugs AS $bug) { + echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n"; + echo " Reported by: ".$bug->getReporter()->name."\n"; + echo " Assigned to: ".$bug->getEngineer()->name."\n"; + foreach($bug->getProducts() AS $product) { + echo " Platform: ".$product->name."\n"; + } + echo "\n"; + } + +The DQL Query in this example fetches the 30 most recent bugs with +their respective engineer and reporter in one single SQL statement. +The console output of this script is then: + +:: + + Something does not work! - 02.04.2010 + Reported by: beberlei + Assigned to: beberlei + Platform: My Product + +.. note:: + + **Dql is not Sql** + + You may wonder why we start writing SQL at the beginning of this + use-case. Don't we use an ORM to get rid of all the endless + hand-writing of SQL? Doctrine introduces DQL which is best + described as **object-query-language** and is a dialect of + `OQL `_ and + similar to `HQL `_ or + `JPQL `_. + It does not know the concept of columns and tables, but only those + of Entity-Class and property. Using the Metadata we defined before + it allows for very short distinctive and powerful queries. + + An important reason why DQL is favourable to the Query API of most + ORMs is its similarity to SQL. The DQL language allows query + constructs that most ORMs don't, GROUP BY even with HAVING, + Sub-selects, Fetch-Joins of nested classes, mixed results with + entities and scalar data such as COUNT() results and much more. + Using DQL you should seldom come to the point where you want to + throw your ORM into the dumpster, because it doesn't support some + the more powerful SQL concepts. + + Besides handwriting DQL you can however also use the + ``QueryBuilder`` retrieved by calling + ``$entityManager->createQueryBuilder()`` which is a Query Object + around the DQL language. + + As a last resort you can however also use Native SQL and a + description of the result set to retrieve entities from the + database. DQL boils down to a Native SQL statement and a + ``ResultSetMapping`` instance itself. Using Native SQL you could + even use stored procedures for data retrieval, or make use of + advanced non-portable database queries like PostgreSql's recursive + queries. + + +Array Hydration of the Bug List +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In the previous use-case we retrieved the result as their +respective object instances. We are not limited to retrieving +objects only from Doctrine however. For a simple list view like the +previous one we only need read access to our entities and can +switch the hydration from objects to simple PHP arrays instead. +This can obviously yield considerable performance benefits for +read-only requests. + +Implementing the same list view as before using array hydration we +can rewrite our code: + +.. code-block:: php + + createQuery($dql); + $bugs = $query->getArrayResult(); + + foreach ($bugs AS $bug) { + echo $bug['description'] . " - " . $bug['created']->format('d.m.Y')."\n"; + echo " Reported by: ".$bug['reporter']['name']."\n"; + echo " Assigned to: ".$bug['engineer']['name']."\n"; + foreach($bug['products'] AS $product) { + echo " Platform: ".$product['name']."\n"; + } + echo "\n"; + } + +There is one significant difference in the DQL query however, we +have to add an additional fetch-join for the products connected to +a bug. The resulting SQL query for this single select statement is +pretty large, however still more efficient to retrieve compared to +hydrating objects. + +Find by Primary Key +~~~~~~~~~~~~~~~~~~~ + +The next Use-Case is displaying a Bug by primary key. This could be +done using DQL as in the previous example with a where clause, +however there is a convenience method on the Entity Manager that +handles loading by primary key, which we have already seen in the +write scenarios: + +However we will soon see another problem with our entities using +this approach. Try displaying the engineer's name: + +.. code-block:: php + + find("Bug", (int)$theBugId); + + echo "Bug: ".$bug->getDescription()."\n"; + // Accessing our special public $name property here on purpose: + echo "Engineer: ".$bug->getEngineer()->name."\n"; + +The output of the engineers name is null! What is happening? +It worked in the previous example, so it can't be a problem with the persistence code of +Doctrine. What is it then? You walked in the public property trap. + +Since we only retrieved the bug by primary key both the engineer +and reporter are not immediately loaded from the database but are +replaced by LazyLoading proxies. Sample code of this proxy +generated code can be found in the specified Proxy Directory, it +looks like: + +.. code-block:: php + + _load(); + return parent::addReportedBug($bug); + } + + public function assignedToBug($bug) + { + $this->_load(); + return parent::assignedToBug($bug); + } + } + +See how upon each method call the proxy is lazily loaded from the +database? Using public properties however we never call a method +and Doctrine has no way to hook into the PHP Engine to detect a +direct access to a public property and trigger the lazy load. We +need to rewrite our entities, make all the properties private or +protected and add getters and setters to get a working example: + +.. code-block:: php + + find("Bug", (int)$theBugId); + + echo "Bug: ".$bug->getDescription()."\n"; + echo "Engineer: ".$bug->getEngineer()->getName()."\n"; + +Now prints: + +:: + + $ php show_bug.php 1 + Bug: Something does not work! + Engineer: beberlei + +Being required to use private or protected properties Doctrine 2 +actually enforces you to encapsulate your objects according to +object-oriented best-practices. + +Dashboard of the User +--------------------- + +For the next use-case we want to retrieve the dashboard view, a +list of all open bugs the user reported or was assigned to. This +will be achieved using DQL again, this time with some WHERE clauses +and usage of bound parameters: + +.. code-block:: php + + createQuery($dql) + ->setParameter(1, $theUserId) + ->setMaxResults(15) + ->getResult(); + + echo "You have created or assigned to " . count($myBugs) . " open bugs:\n\n"; + + foreach ($myBugs AS $bug) { + echo $bug->getId() . " - " . $bug->getDescription()."\n"; + } + +Number of Bugs +-------------- + +Until now we only retrieved entities or their array representation. +Doctrine also supports the retrieval of non-entities through DQL. +These values are called "scalar result values" and may even be +aggregate values using COUNT, SUM, MIN, MAX or AVG functions. + +We will need this knowledge to retrieve the number of open bugs +grouped by product: + +.. code-block:: php + + createQuery($dql)->getScalarResult(); + + foreach($productBugs as $productBug) { + echo $productBug['name']." has " . $productBug['openBugs'] . " open bugs!\n"; + } + +Updating Entities +----------------- + +There is a single use-case missing from the requirements, Engineers +should be able to close a bug. This looks like: + +.. code-block:: php + + status = "CLOSE"; + } + } + +.. code-block:: php + + find("Bug", (int)$theBugId); + $bug->close(); + + $entityManager->flush(); + +When retrieving the Bug from the database it is inserted into the +IdentityMap inside the UnitOfWork of Doctrine. This means your Bug +with exactly this id can only exist once during the whole request +no matter how often you call ``EntityManager#find()``. It even +detects entities that are hydrated using DQL and are already +present in the Identity Map. + +When flush is called the EntityManager loops over all the entities +in the identity map and performs a comparison between the values +originally retrieved from the database and those values the entity +currently has. If at least one of these properties is different the +entity is scheduled for an UPDATE against the database. Only the +changed columns are updated, which offers a pretty good performance +improvement compared to updating all the properties. + +Entity Repositories +------------------- + +For now we have not discussed how to separate the Doctrine query logic from your model. +In Doctrine 1 there was the concept of ``Doctrine_Table`` instances for this +separation. The similar concept in Doctrine2 is called Entity Repositories, integrating +the `repository pattern `_ at the heart of Doctrine. + +Every Entity uses a default repository by default and offers a bunch of convenience +methods that you can use to query for instances of that Entity. Take for example +our Product entity. If we wanted to Query by name, we can use: + +.. code-block:: php + + getRepository('Product') + ->findOneBy(array('name' => $productName)); + +The method ``findOneBy()`` takes an array of fields or association keys and the values to match against. + +If you want to find all entities matching a condition you can use ``findBy()``, for +example querying for all closed bugs: + +.. code-block:: php + + getRepository('Bug') + ->findBy(array('status' => 'CLOSED')); + + foreach ($bugs AS $bug) { + // do stuff + } + +Compared to DQL these query methods are falling short of functionality very fast. +Doctrine offers you a convenient way to extend the functionalities of the default ``EntityRepository`` +and put all the specialized DQL query logic on it. For this you have to create a subclass +of ``Doctrine\ORM\EntityRepository``, in our case a ``BugRepository`` and group all +the previously discussed query functionality in it: + +.. code-block:: php + + getEntityManager()->createQuery($dql); + $query->setMaxResults($number); + return $query->getResult(); + } + + public function getRecentBugsArray($number = 30) + { + $dql = "SELECT b, e, r, p FROM Bug b JOIN b.engineer e ". + "JOIN b.reporter r JOIN b.products p ORDER BY b.created DESC"; + $query = $this->getEntityManager()->createQuery($dql); + $query->setMaxResults($number); + return $query->getArrayResult(); + } + + public function getUsersBugs($userId, $number = 15) + { + $dql = "SELECT b, e, r FROM Bug b JOIN b.engineer e JOIN b.reporter r ". + "WHERE b.status = 'OPEN' AND e.id = ?1 OR r.id = ?1 ORDER BY b.created DESC"; + + return $this->getEntityManager()->createQuery($dql) + ->setParameter(1, $userId) + ->setMaxResults($number) + ->getResult(); + } + + public function getOpenBugsByProduct() + { + $dql = "SELECT p.id, p.name, count(b.id) AS openBugs FROM Bug b ". + "JOIN b.products p WHERE b.status = 'OPEN' GROUP BY p.id"; + return $this->getEntityManager()->createQuery($dql)->getScalarResult(); + } + } + +Don't forget to add a `require_once` call for this class to the bootstrap.php + +To be able to use this query logic through ``$this->getEntityManager()->getRepository('Bug')`` +we have to adjust the metadata slightly. + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + .. code-block:: yaml + + Bug: + type: entity + repositoryClass: BugRepository + +Now we can remove our query logic in all the places and instead use them through the EntityRepository. +As an example here is the code of the first use case "List of Bugs": + +.. code-block:: php + + getRepository('Bug')->getRecentBugs(); + + foreach($bugs AS $bug) { + echo $bug->getDescription()." - ".$bug->getCreated()->format('d.m.Y')."\n"; + echo " Reported by: ".$bug->getReporter()->getName()."\n"; + echo " Assigned to: ".$bug->getEngineer()->getName()."\n"; + foreach($bug->getProducts() AS $product) { + echo " Platform: ".$product->getName()."\n"; + } + echo "\n"; + } + +Using EntityRepositories you can avoid coupling your model with specific query logic. +You can also re-use query logic easily throughout your application. + +Conclusion +---------- + +This tutorial is over here, I hope you had fun. Additional content +will be added to this tutorial incrementally, topics will include: + +- More on Association Mappings +- Lifecycle Events triggered in the UnitOfWork +- Ordering of Collections + +Additional details on all the topics discussed here can be found in +the respective manual chapters. + + diff --git a/docs/en/tutorials/in-ten-quick-steps.rst b/docs/en/tutorials/in-ten-quick-steps.rst new file mode 100644 index 000000000..8ae25a067 --- /dev/null +++ b/docs/en/tutorials/in-ten-quick-steps.rst @@ -0,0 +1,300 @@ +Doctrine explained in 10 quick steps +==================================== + +You can follow this tutorial step by step yourself and end up with a simple +Doctrine application. It assumed that you installed Doctrine via PEAR. To work +with another setup just take a look into the :doc:`Installation help +<../reference/introduction>`. + +1. Allows you to map PHP Objects to database tables +--------------------------------------------------- + +.. code-block:: php + + CREATE TABLE Post (id INT AUTO_INCREMENT PRIMARY KEY, title + VARCHAR(255), body TEXT); + + mysql> DESCRIBE Post; + +-------+--------------+------+-----+---------+----------------+ + | Field | Type | Null | Key | Default | Extra | + +-------+--------------+------+-----+---------+----------------+ + | id | int(11) | NO | PRI | NULL | auto_increment | + | title | varchar(255) | YES | | NULL | | + | body | text | YES | | NULL | | + +-------+--------------+------+-----+---------+----------------+ + +.. tip:: + + Objects mapped with Doctrine are called Entities. They don't need to extend + a base class and even allow constructors with required parameters. + + You are responsible for implementing getters, setters and constructors of + your entities yourself. This gives you full freedom to design your business + objects as you wish. + +2. Using Annotations, XML or YAML for Metadata Mapping +------------------------------------------------------ + +.. configuration-block:: + + .. code-block:: php + + + + + + + + + + + + + +3. Object References map to Foreign keys +---------------------------------------- + +.. code-block:: php + + author = $user; + } + } + + /** @Entity **/ + class User + { + /** @Id @GeneratedValue @Column(type="integer") **/ + protected $id; + /** @Column(type="string") **/ + protected $name; + } + + $user = new User(); + $post = new Post($user); + + +:: + + mysql> CREATE TABLE Post (id INT AUTO_INCREMENT PRIMARY KEY, title + VARCHAR(255), body TEXT, author_id INT); + + mysql> CREATE TABLE User (id INT AUTO_INCREMENT PRIMARY KEY, name + VARCHAR(255)); + + mysql> ALTER TABLE Post ADD FOREIGN KEY (author_id) REFERENCES User (id); + + mysql> DESCRIBE Post; + +-----------+--------------+------+-----+---------+----------------+ + | Field | Type | Null | Key | Default | Extra | + +-----------+--------------+------+-----+---------+----------------+ + | id | int(11) | NO | PRI | NULL | auto_increment | + | title | varchar(255) | YES | | NULL | | + | body | text | YES | | NULL | | + | author_id | int(11) | YES | MUL | NULL | | + +-----------+--------------+------+-----+---------+----------------+ + +.. tip:: + + This means you don't have to mess with foreign keys yourself, just use + references to connect objects with each other and let Doctrine handle the + rest. + +4. Collections handle sets of objects references +------------------------------------------------ + +.. code-block:: php + + author = $author; + $this->posts = new ArrayCollection(); + } + + public function addComment($text) + { + $this->comments[] = new Comment($this, $text); + } + } + + /** @Entity **/ + class Comment + { + /** @Id @GeneratedValue @Column(type="integer") **/ + protected $id; + /** @Column(type="text") **/ + protected $comment; + /** + * @ManyToOne(targetEntity="Post", inversedBy="comments") + **/ + protected $post; + + public function __construct(Post $post, $text) + { + $this->post = $post; + $this->comment = $text; + } + } + + $post->addComment("First.."); + $post->addComment("Second!"); + +5. Easy to setup for the default configuration case +--------------------------------------------------- + +.. code-block:: php + + register(); + + $dbParams = array( + 'driver' => 'pdo_mysql', + 'user' => 'root', + 'password' => '', + 'dbname' => 'tests' + ); + $path = 'path/to/entities'; + $config = Setup::createAnnotationMetadataConfiguration($path, true); + $entityManager = EntityManager::create($dbParams, $config); + + +6. The EntityManager needs to know about your new objects +--------------------------------------------------------- + +.. code-block:: php + + persist($user); + $entityManager->persist($post); + +.. warning:: + + This does not lead to INSERT/UPDATE statements yet. You need to call + EntityManager#flush() + + +7. EntityManager#flush() batches SQL INSERT/UPDATE/DELETE statements +-------------------------------------------------------------------- + +.. code-block:: php + + flush(); + +.. tip:: + + Batching all write-operations against the database allows Doctrine to wrap all + statements into a single transaction and benefit from other performance + optimizations such as prepared statement re-use. + +8. You can fetch objects from the database through the EntityManager +-------------------------------------------------------------------- + +.. code-block:: php + + find("Post", $id); + +9. ..or through a Repository +---------------------------- + +.. code-block:: php + + getRepository("Author"); + $author = $authorRepository->find($authorId); + + $postRepository = $entityManager->getRepository("Post"); + $post = $postRepository->findOneBy(array("title" => "Hello World!")); + + $posts = $repository->findBy( + array("author" => $author), + array("title" => "ASC") + ); + + +10. Or complex finder scenarios with the Doctrine Query Language +---------------------------------------------------------------- + +.. code-block:: php + + createQuery($dql)->getResult(); diff --git a/docs/en/tutorials/ordered-associations.rst b/docs/en/tutorials/ordered-associations.rst new file mode 100644 index 000000000..e2a48ffcb --- /dev/null +++ b/docs/en/tutorials/ordered-associations.rst @@ -0,0 +1,91 @@ +Ordering To-Many Associations +----------------------------- + +There are use-cases when you'll want to sort collections when they are +retrieved from the database. In userland you do this as long as you +haven't initially saved an entity with its associations into the +database. To retrieve a sorted collection from the database you can +use the ``@OrderBy`` annotation with an collection that specifies +an DQL snippet that is appended to all queries with this +collection. + +Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you +can specify the ``@OrderBy`` in the following way: + +.. code-block:: php + + + + + + + + + + + +The DQL Snippet in OrderBy is only allowed to consist of +unqualified, unquoted field names and of an optional ASC/DESC +positional statement. Multiple Fields are separated by a comma (,). +The referenced field names have to exist on the ``targetEntity`` +class of the ``@ManyToMany`` or ``@OneToMany`` annotation. + +The semantics of this feature can be described as follows. + + +- ``@OrderBy`` acts as an implicit ORDER BY clause for the given + fields, that is appended to all the explicitly given ORDER BY + items. +- All collections of the ordered type are always retrieved in an + ordered fashion. +- To keep the database impact low, these implicit ORDER BY items + are only added to an DQL Query if the collection is fetch joined in + the DQL query. + +Given our previously defined example, the following would not add +ORDER BY, since g is not fetch joined: + +.. code-block:: sql + + SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10 + +However the following: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 + +...would internally be rewritten to: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC + +You can reverse the order with an explicit DQL ORDER BY: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC + +...is internally rewritten to: + +.. code-block:: sql + + SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC + + diff --git a/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst b/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst new file mode 100644 index 000000000..792d97887 --- /dev/null +++ b/docs/en/tutorials/override-field-association-mappings-in-subclasses.rst @@ -0,0 +1,90 @@ +Override Field Association Mappings In Subclasses +------------------------------------------------- + +Sometimes there is a need to persist entities but override all or part of the +mapping metadata. Sometimes also the mapping to override comes from entities +using traits where the traits have mapping metadata. +This tutorial explains how to override mapping metadata, +i.e. attributes and associations metadata in particular. The example here shows +the overriding of a class that uses a trait but is similar when extending a base +class as shown at the end of this tutorial. + +Suppose we have a class ExampleEntityWithOverride. This class uses trait ExampleTrait: + +.. code-block:: php + + createQuery($dql) + ->setFirstResult(0) + ->setMaxResults(100); + + $paginator = new Paginator($query, $fetchJoinCollection = true); + + $c = count($paginator); + foreach ($paginator as $post) { + echo $post->getHeadline() . "\n"; + } + +Paginating Doctrine queries is not as simple as you might think in the +beginning. If you have complex fetch-join scenarios with one-to-many or +many-to-many associations using the "default" LIMIT functionality of database +vendors is not sufficient to get the correct results. + +By default the pagination extension does the following steps to compute the +correct result: + +- Perform a Count query using `DISTINCT` keyword. +- Perform a Limit Subquery with `DISTINCT` to find all ids of the entity in from on the current page. +- Perform a WHERE IN query to get all results for the current page. + +This behavior is only necessary if you actually fetch join a to-many +collection. You can disable this behavior by setting the +``$fetchJoinCollection`` flag to ``false``; in that case only 2 instead of the 3 queries +described are executed. We hope to automate the detection for this in +the future. diff --git a/docs/en/tutorials/working-with-indexed-associations.rst b/docs/en/tutorials/working-with-indexed-associations.rst new file mode 100644 index 000000000..3536a34f8 --- /dev/null +++ b/docs/en/tutorials/working-with-indexed-associations.rst @@ -0,0 +1,298 @@ +Working with Indexed Associations +================================= + +.. note:: + + This feature is scheduled for version 2.1 of Doctrine and not included in the 2.0.x series. + +Doctrine 2 collections are modelled after PHPs native arrays. PHP arrays are an ordered hashmap, but in +the first version of Doctrine keys retrieved from the database were always numerical unless ``INDEX BY`` +was used. Starting with Doctrine 2.1 you can index your collections by a value in the related entity. +This is a first step towards full ordered hashmap support through the Doctrine ORM. +The feature works like an implicit ``INDEX BY`` for the selected association but has several +downsides also: + +- You have to manage both the key and field if you want to change the index by field value. +- On each request the keys are regenerated from the field value not from the previous collection key. +- Values of the Index-By keys are never considered during persistence, it only exists for accessing purposes. +- Fields that are used for the index by feature **HAVE** to be unique in the database. The behavior for multiple entities + with the same index-by field value is undefined. + +As an example we will design a simple stock exchange list view. The domain consists of the entity ``Stock`` +and ``Market`` where each Stock has a symbol and is traded on a single market. Instead of having a numerical +list of stocks traded on a market they will be indexed by their symbol, which is unique across all markets. + +Mapping Indexed Associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can map indexed associations by adding: + + * ``indexBy`` attribute to any ``@OneToMany`` or ``@ManyToMany`` annotation. + * ``index-by`` attribute to any ```` or ```` xml element. + * ``indexBy:`` key-value pair to any association defined in ``manyToMany:`` or ``oneToMany:`` YAML mapping files. + +The code and mappings for the Market entity looks like this: + +.. configuration-block:: + .. code-block:: php + + name = $name; + $this->stocks = new ArrayCollection(); + } + + public function getId() + { + return $this->id; + } + + public function getName() + { + return $this->name; + } + + public function addStock(Stock $stock) + { + $this->stocks[$stock->getSymbol()] = $stock; + } + + public function getStock($symbol) + { + if (!isset($this->stocks[$symbol])) { + throw new \InvalidArgumentException("Symbol is not traded on this market."); + } + + return $this->stocks[$symbol]; + } + + public function getStocks() + { + return $this->stocks->toArray(); + } + } + + .. code-block:: xml + + + + + + + + + + + + + + + + .. code-block:: yaml + + Doctrine\Tests\Models\StockExchange\Market: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + name: + type:string + oneToMany: + stocks: + targetEntity: Stock + mappedBy: market + indexBy: symbol + +Inside the ``addStock()`` method you can see how we directly set the key of the association to the symbol, +so that we can work with the indexed association directly after invoking ``addStock()``. Inside ``getStock($symbol)`` +we pick a stock traded on the particular market by symbol. If this stock doesn't exist an exception is thrown. + +The ``Stock`` entity doesn't contain any special instructions that are new, but for completeness +here are the code and mappings for it: + +.. configuration-block:: + .. code-block:: php + + symbol = $symbol; + $this->market = $market; + $market->addStock($this); + } + + public function getSymbol() + { + return $this->symbol; + } + } + + .. code-block:: xml + + + + + + + + + + + + + + + .. code-block:: yaml + + Doctrine\Tests\Models\StockExchange\Stock: + type: entity + id: + id: + type: integer + generator: + strategy: AUTO + fields: + symbol: + type: string + manyToOne: + market: + targetEntity: Market + inversedBy: stocks + +Querying indexed associations +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Now that we defined the stocks collection to be indexed by symbol we can take a look at some code, +that makes use of the indexing. + +First we will populate our database with two example stocks traded on a single market: + +.. code-block:: php + + persist($market); + $em->persist($stock1); + $em->persist($stock2); + $em->flush(); + +This code is not particular interesting since the indexing feature is not yet used. In a new request we could +now query for the market: + +.. code-block:: php + + find("Doctrine\Tests\Models\StockExchange\Market", $marketId); + + // Access the stocks by symbol now: + $stock = $market->getStock($symbol); + + echo $stock->getSymbol(); // will print "AAPL" + +The implementation ``Market::addStock()`` in combination with ``indexBy`` allows to access the collection +consistently by the Stock symbol. It does not matter if Stock is managed by Doctrine or not. + +The same applies to DQL queries: The ``indexBy`` configuration acts as implicit "INDEX BY" to a join association. + +.. code-block:: php + + createQuery($dql) + ->setParameter(1, $marketId) + ->getSingleResult(); + + // Access the stocks by symbol now: + $stock = $market->getStock($symbol); + + echo $stock->getSymbol(); // will print "AAPL" + +If you want to use ``INDEX BY`` explicitly on an indexed association you are free to do so. Additionally +indexed associations also work with the ``Collection::slice()`` functionality, no matter if marked as +LAZY or EXTRA_LAZY. + +Outlook into the Future +~~~~~~~~~~~~~~~~~~~~~~~ + +For the inverse side of a many-to-many associations there will be a way to persist the keys and the order +as a third and fourth parameter into the join table. This feature is discussed in `DDC-213 `_ +This feature cannot be implemented for One-To-Many associations, because they are never the owning side. +