Add 'docs/' from commit '8fcf2d45019bf38a1df728353a1e417343c69cfb'
git-subtree-dir: docs git-subtree-mainline:271bd37ad3
git-subtree-split:8fcf2d4501
This commit is contained in:
commit
cbcc693e36
4
docs/.gitignore
vendored
Normal file
4
docs/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
en/_exts/configurationblock.pyc
|
||||||
|
build
|
||||||
|
en/_build
|
||||||
|
.idea
|
3
docs/.gitmodules
vendored
Normal file
3
docs/.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "en/_theme"]
|
||||||
|
path = en/_theme
|
||||||
|
url = https://github.com/doctrine/doctrine-sphinx-theme.git
|
8
docs/README.md
Normal file
8
docs/README.md
Normal file
@ -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.
|
10
docs/bin/generate-docs.sh
Executable file
10
docs/bin/generate-docs.sh
Executable file
@ -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
|
4
docs/bin/install-dependencies.sh
Normal file
4
docs/bin/install-dependencies.sh
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
sudo apt-get install python25 python25-dev texlive-full rubber
|
||||||
|
sudo easy_install pygments
|
||||||
|
sudo easy_install sphinx
|
89
docs/en/Makefile
Normal file
89
docs/en/Makefile
Normal file
@ -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 <target>' where <target> 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."
|
93
docs/en/_exts/configurationblock.py
Normal file
93
docs/en/_exts/configurationblock.py
Normal file
@ -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('</div>\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)
|
1
docs/en/_theme
Submodule
1
docs/en/_theme
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 5aa024c9284228ed76c7a9aa785229cf19f396e3
|
194
docs/en/conf.py
Normal file
194
docs/en/conf.py
Normal file
@ -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
|
||||||
|
# "<project> v<release> 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 <link> 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
|
@ -0,0 +1,256 @@
|
|||||||
|
Advanced field value conversion using custom mapping types
|
||||||
|
==========================================================
|
||||||
|
|
||||||
|
.. sectionauthor:: Jan Sorgalla <jsorgalla@googlemail.com>
|
||||||
|
|
||||||
|
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 <http://dev.mysql.com/doc/refman/5.5/en/gis-class-point.html>`_.
|
||||||
|
|
||||||
|
The ``Point`` type is part of the `Spatial extension <http://dev.mysql.com/doc/refman/5.5/en/spatial-extensions.html>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Geo\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Location
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Column(type="point")
|
||||||
|
*
|
||||||
|
* @var \Geo\ValueObject\Point
|
||||||
|
*/
|
||||||
|
private $point;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Column(type="string")
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $address;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param \Geo\ValueObject\Point $point
|
||||||
|
*/
|
||||||
|
public function setPoint(\Geo\ValueObject\Point $point)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Geo\ValueObject;
|
||||||
|
|
||||||
|
class Point
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param float $latitude
|
||||||
|
* @param float $longitude
|
||||||
|
*/
|
||||||
|
public function __construct($latitude, $longitude)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Geo\Types;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||||
|
|
||||||
|
use Geo\ValueObject\Point;
|
||||||
|
|
||||||
|
class PointType extends Type
|
||||||
|
{
|
||||||
|
const POINT = 'point';
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return self::POINT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
return 'POINT';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
list($longitude, $latitude) = sscanf($value, 'POINT(%f %f)');
|
||||||
|
|
||||||
|
return new Point($latitude, $longitude);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
if ($value instanceof Point) {
|
||||||
|
$value = sprintf('POINT(%F %F)', $value->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)
|
||||||
|
<http://en.wikipedia.org/wiki/Well-known_text>`_. 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 <http://dev.mysql.com/doc/refman/5.5/en/creating-spatial-values.html#function_pointfromtext>`_
|
||||||
|
and `AsText <http://dev.mysql.com/doc/refman/5.5/en/functions-to-convert-geometries-between-formats.html#function_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 <dql-user-defined-functions>` for
|
||||||
|
``PointFromText``.
|
||||||
|
|
||||||
|
Example usage
|
||||||
|
-------------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// Bootstrapping stuff...
|
||||||
|
// $em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config);
|
||||||
|
|
||||||
|
// Setup custom mapping type
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
|
||||||
|
Type::addType('point', 'Geo\Types\Point');
|
||||||
|
$em->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();
|
376
docs/en/cookbook/aggregate-fields.rst
Normal file
376
docs/en/cookbook/aggregate-fields.rst
Normal file
@ -0,0 +1,376 @@
|
|||||||
|
Aggregate Fields
|
||||||
|
================
|
||||||
|
|
||||||
|
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Bank\Entities;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Account
|
||||||
|
{
|
||||||
|
/** @Id @GeneratedValue @Column(type="integer") */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @Column(type="string", unique=true) */
|
||||||
|
private $no;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToMany(targetEntity="Entry", mappedBy="account", cascade={"persist"})
|
||||||
|
*/
|
||||||
|
private $entries;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $maxCredit = 0;
|
||||||
|
|
||||||
|
public function __construct($no, $maxCredit = 0)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$dql = "SELECT SUM(e.amount) AS balance FROM Bank\Entities\Entry e " .
|
||||||
|
"WHERE e.account = ?1";
|
||||||
|
$balance = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Account
|
||||||
|
{
|
||||||
|
// .. previous code
|
||||||
|
public function getBalance()
|
||||||
|
{
|
||||||
|
$balance = 0;
|
||||||
|
foreach ($this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Account
|
||||||
|
{
|
||||||
|
public function addEntry($amount)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class AccountTest extends \PHPUnit_Framework_TestCase
|
||||||
|
{
|
||||||
|
public function testAddEntry()
|
||||||
|
{
|
||||||
|
$account = new Account("123456", $maxCredit = 200);
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Account
|
||||||
|
{
|
||||||
|
private function assertAcceptEntryAllowed($amount)
|
||||||
|
{
|
||||||
|
$futureBalance = $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$account = new Account("123456", 200);
|
||||||
|
$account->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Account
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Column(type="integer")
|
||||||
|
*/
|
||||||
|
private $balance = 0;
|
||||||
|
|
||||||
|
public function getBalance()
|
||||||
|
{
|
||||||
|
return $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// The Account $accId has a balance of 0 and a max credit limit of 200:
|
||||||
|
// request 1 account
|
||||||
|
$account1 = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Amount
|
||||||
|
{
|
||||||
|
/** @Column(type="integer") @Version */
|
||||||
|
private $version;
|
||||||
|
}
|
||||||
|
|
||||||
|
The previous example would then throw an exception in the face of
|
||||||
|
whatever request saves the entity last (and would create the
|
||||||
|
inconsistent state).
|
||||||
|
|
||||||
|
Pessimistic locking requires an additional flag set on the
|
||||||
|
``EntityManager::find()`` call, enabling write locking directly in
|
||||||
|
the database using a FOR UPDATE.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\DBAL\LockMode;
|
||||||
|
|
||||||
|
$account = $em->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.
|
||||||
|
|
||||||
|
|
273
docs/en/cookbook/decorator-pattern.rst
Normal file
273
docs/en/cookbook/decorator-pattern.rst
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
Persisting the Decorator Pattern
|
||||||
|
================================
|
||||||
|
|
||||||
|
.. sectionauthor:: Chris Woodford <chris.woodford@gmail.com>
|
||||||
|
|
||||||
|
This recipe will show you a simple example of how you can use
|
||||||
|
Doctrine 2 to persist an implementation of the
|
||||||
|
`Decorator Pattern <http://en.wikipedia.org/wiki/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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Test;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @InheritanceType("SINGLE_TABLE")
|
||||||
|
* @DiscriminatorColumn(name="discr", type="string")
|
||||||
|
* @DiscriminatorMap({"cc" = "Test\Component\ConcreteComponent",
|
||||||
|
"cd" = "Test\Decorator\ConcreteDecorator"})
|
||||||
|
*/
|
||||||
|
abstract class Component
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Id @Column(type="integer")
|
||||||
|
* @GeneratedValue(strategy="AUTO")
|
||||||
|
*/
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
/** @Column(type="string", nullable=true) */
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get id
|
||||||
|
* @return integer $id
|
||||||
|
*/
|
||||||
|
public function getId()
|
||||||
|
{
|
||||||
|
return $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Test\Component;
|
||||||
|
|
||||||
|
use Test\Component;
|
||||||
|
|
||||||
|
/** @Entity */
|
||||||
|
class ConcreteComponent extends Component
|
||||||
|
{}
|
||||||
|
|
||||||
|
Decorator
|
||||||
|
---------
|
||||||
|
|
||||||
|
The ``Decorator`` class doesn't need to be persisted, but it does
|
||||||
|
need to define an association with a persisted ``Entity``. We can
|
||||||
|
use a ``MappedSuperclass`` for this.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Test;
|
||||||
|
|
||||||
|
/** @MappedSuperclass */
|
||||||
|
abstract class Decorator extends Component
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToOne(targetEntity="Test\Component", cascade={"all"})
|
||||||
|
* @JoinColumn(name="decorates", referencedColumnName="id")
|
||||||
|
*/
|
||||||
|
protected $decorates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* initialize the decorator
|
||||||
|
* @param Component $c
|
||||||
|
*/
|
||||||
|
public function __construct(Component $c)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Test\Decorator;
|
||||||
|
|
||||||
|
use Test\Decorator;
|
||||||
|
|
||||||
|
/** @Entity */
|
||||||
|
class ConcreteDecorator extends Decorator
|
||||||
|
{
|
||||||
|
|
||||||
|
/** @Column(type="string", nullable=true) */
|
||||||
|
protected $special;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set special
|
||||||
|
* @param string $special
|
||||||
|
*/
|
||||||
|
public function setSpecial($special)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Test\Component\ConcreteComponent,
|
||||||
|
Test\Decorator\ConcreteDecorator;
|
||||||
|
|
||||||
|
// assumes Doctrine 2 is configured and an instance of
|
||||||
|
// an EntityManager is available as $em
|
||||||
|
|
||||||
|
// create a new concrete component
|
||||||
|
$c = new ConcreteComponent();
|
||||||
|
$c->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
|
||||||
|
|
217
docs/en/cookbook/dql-custom-walkers.rst
Normal file
217
docs/en/cookbook/dql-custom-walkers.rst
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
Extending DQL in Doctrine 2: Custom AST Walkers
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
|
||||||
|
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 <http://en.wikipedia.org/wiki/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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$pageNum = 1;
|
||||||
|
$query = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Paginate
|
||||||
|
{
|
||||||
|
static public function count(Query $query)
|
||||||
|
{
|
||||||
|
/* @var $countQuery Query */
|
||||||
|
$countQuery = clone $query;
|
||||||
|
|
||||||
|
$countQuery->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class CountSqlWalker extends TreeWalkerAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Walks down a SelectStatement AST node, thereby generating the appropriate SQL.
|
||||||
|
*
|
||||||
|
* @return string The SQL.
|
||||||
|
*/
|
||||||
|
public function walkSelectStatement(SelectStatement $AST)
|
||||||
|
{
|
||||||
|
$parent = null;
|
||||||
|
$parentName = null;
|
||||||
|
foreach ($this->_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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$dql = "SELECT p, c, a FROM BlogPost p JOIN p.category c JOIN p.author a WHERE ...";
|
||||||
|
$query = $m->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MysqlWalker extends SqlWalker
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Walks down a SelectClause AST node, thereby generating the appropriate SQL.
|
||||||
|
*
|
||||||
|
* @param $selectClause
|
||||||
|
* @return string The SQL.
|
||||||
|
*/
|
||||||
|
public function walkSelectClause($selectClause)
|
||||||
|
{
|
||||||
|
$sql = parent::walkSelectClause($selectClause);
|
||||||
|
|
||||||
|
if ($this->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.
|
||||||
|
|
240
docs/en/cookbook/dql-user-defined-functions.rst
Normal file
240
docs/en/cookbook/dql-user-defined-functions.rst
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
DQL User Defined Functions
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config = new \Doctrine\ORM\Configuration();
|
||||||
|
$config->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 <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_datediff>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* DateDiffFunction ::= "DATEDIFF" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
|
||||||
|
*/
|
||||||
|
class DateDiff extends FunctionNode
|
||||||
|
{
|
||||||
|
// (1)
|
||||||
|
public $firstDateExpression = null;
|
||||||
|
public $secondDateExpression = null;
|
||||||
|
|
||||||
|
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||||
|
{
|
||||||
|
$parser->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 <http://www.doctrine-project.org/documentation/manual/2_0/en/dql-doctrine-query-language#ebnf>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config = new \Doctrine\ORM\Configuration();
|
||||||
|
$config->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 <http://dev.mysql.com/doc/refman/5.1/en/date-and-time-functions.html#function_date-add>`_.
|
||||||
|
|
||||||
|
I'll skip the blah and show the code for this function:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* DateAddFunction ::=
|
||||||
|
* "DATE_ADD" "(" ArithmeticPrimary ", INTERVAL" ArithmeticPrimary Identifier ")"
|
||||||
|
*/
|
||||||
|
class DateAdd extends FunctionNode
|
||||||
|
{
|
||||||
|
public $firstDateExpression = null;
|
||||||
|
public $intervalExpression = null;
|
||||||
|
public $unit = null;
|
||||||
|
|
||||||
|
public function parse(\Doctrine\ORM\Query\Parser $parser)
|
||||||
|
{
|
||||||
|
$parser->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 <http://github.com/beberlei/DoctrineExtensions>`_.
|
||||||
|
|
||||||
|
|
68
docs/en/cookbook/entities-in-session.rst
Normal file
68
docs/en/cookbook/entities-in-session.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'bootstrap.php';
|
||||||
|
$em = GetEntityManager(); // creates an EntityManager
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
if (isset($_SESSION['user']) && $_SESSION['user'] instanceof User) {
|
||||||
|
$user = $_SESSION['user'];
|
||||||
|
$user = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
require_once 'bootstrap.php';
|
||||||
|
$em = GetEntityManager(); // creates an EntityManager
|
||||||
|
|
||||||
|
$user = $em->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.
|
||||||
|
|
112
docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst
Normal file
112
docs/en/cookbook/implementing-arrayaccess-for-domain-objects.rst
Normal file
@ -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 <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
abstract class DomainObject implements ArrayAccess
|
||||||
|
{
|
||||||
|
public function offsetExists($offset) {
|
||||||
|
return isset($this->$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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
abstract class DomainObject implements ArrayAccess
|
||||||
|
{
|
||||||
|
public function offsetExists($offset) {
|
||||||
|
// In this example we say that exists means it is not null
|
||||||
|
$value = $this->{"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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
abstract class DomainObject implements ArrayAccess
|
||||||
|
{
|
||||||
|
public function offsetExists($offset) {
|
||||||
|
// option 1 or option 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetSet($offset, $value) {
|
||||||
|
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetGet($offset) {
|
||||||
|
// option 1 or option 2
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offsetUnset($offset) {
|
||||||
|
throw new BadMethodCallException("Array access of class " . get_class($this) . " is read-only!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,72 @@
|
|||||||
|
Implementing the Notify ChangeTracking Policy
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
.. sectionauthor:: Roman Borschel (roman@code-factory.org)
|
||||||
|
|
||||||
|
The NOTIFY change-tracking policy is the most effective
|
||||||
|
change-tracking policy provided by Doctrine but it requires some
|
||||||
|
boilerplate code. This recipe will show you how this boilerplate
|
||||||
|
code should look like. We will implement it on a
|
||||||
|
`Layer Supertype <http://martinfowler.com/eaaCatalog/layerSupertype.html>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\Common\NotifyPropertyChanged,
|
||||||
|
Doctrine\Common\PropertyChangedListener;
|
||||||
|
|
||||||
|
abstract class DomainObject implements NotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private $_listeners = array();
|
||||||
|
|
||||||
|
public function addPropertyChangedListener(PropertyChangedListener $listener) {
|
||||||
|
$this->_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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Mapping not shown, either in annotations, xml or yaml as usual
|
||||||
|
class MyEntity extends DomainObject
|
||||||
|
{
|
||||||
|
private $data;
|
||||||
|
// ... other fields as usual
|
||||||
|
|
||||||
|
public function setData($data) {
|
||||||
|
if ($data != $this->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.
|
||||||
|
|
||||||
|
|
78
docs/en/cookbook/implementing-wakeup-or-clone.rst
Normal file
78
docs/en/cookbook/implementing-wakeup-or-clone.rst
Normal file
@ -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 <http://www.doctrine-project.org/documentation/manual/2_0/en/architecture#entities>`_,
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MyEntity
|
||||||
|
{
|
||||||
|
private $id; // This is the identifier of the entity.
|
||||||
|
//...
|
||||||
|
|
||||||
|
public function __wakeup()
|
||||||
|
{
|
||||||
|
// If the entity has an identity, proceed as normal.
|
||||||
|
if ($this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MyEntity
|
||||||
|
{
|
||||||
|
private $id; // This is the identifier of the entity.
|
||||||
|
//...
|
||||||
|
|
||||||
|
public function __clone()
|
||||||
|
{
|
||||||
|
// If the entity has an identity, proceed as normal.
|
||||||
|
if ($this->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.
|
||||||
|
|
||||||
|
|
140
docs/en/cookbook/integrating-with-codeigniter.rst
Normal file
140
docs/en/cookbook/integrating-with-codeigniter.rst
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
Integrating with CodeIgniter
|
||||||
|
============================
|
||||||
|
|
||||||
|
This is recipe for using Doctrine 2 in your
|
||||||
|
`CodeIgniter <http://www.codeigniter.com>`_ 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.
|
||||||
|
|
||||||
|
<?php $autoload['libraries'] = array('doctrine');
|
||||||
|
|
||||||
|
|
||||||
|
Creating your Doctrine CodeIgniter library
|
||||||
|
------------------------------------------
|
||||||
|
|
||||||
|
Now, here is what your Doctrine.php file should look like.
|
||||||
|
Customize it to your needs.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\Common\ClassLoader,
|
||||||
|
Doctrine\ORM\Configuration,
|
||||||
|
Doctrine\ORM\EntityManager,
|
||||||
|
Doctrine\Common\Cache\ArrayCache,
|
||||||
|
Doctrine\DBAL\Logging\EchoSQLLogger;
|
||||||
|
|
||||||
|
class Doctrine {
|
||||||
|
|
||||||
|
public $em = null;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// load database configuration from CodeIgniter
|
||||||
|
require_once APPPATH.'config/database.php';
|
||||||
|
|
||||||
|
// Set up class loading. You could use different autoloaders, provided by your favorite framework,
|
||||||
|
// if you want to.
|
||||||
|
require_once APPPATH.'libraries/Doctrine/Common/ClassLoader.php';
|
||||||
|
|
||||||
|
$doctrineClassLoader = new ClassLoader('Doctrine', APPPATH.'libraries');
|
||||||
|
$doctrineClassLoader->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 <http://www.doctrine-project.org/documentation/manual/2_0/en/configuration#configuration-options>`_.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$em = $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$this->load->library('doctrine');
|
||||||
|
|
||||||
|
Good luck!
|
||||||
|
|
||||||
|
|
189
docs/en/cookbook/mysql-enums.rst
Normal file
189
docs/en/cookbook/mysql-enums.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$conn = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
const STATUS_VISIBLE = 'visible';
|
||||||
|
const STATUS_INVISIBLE = 'invisible';
|
||||||
|
|
||||||
|
/** @Column(type="string") */
|
||||||
|
private $status;
|
||||||
|
|
||||||
|
public function setStatus($status)
|
||||||
|
{
|
||||||
|
if (!in_array($status, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||||
|
throw new \InvalidArgumentException("Invalid status");
|
||||||
|
}
|
||||||
|
$this->status = $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
If you want to actively create enums through the Doctrine Schema-Tool by using
|
||||||
|
the **columnDefinition** attribute.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
/** @Column(type="string", columnDefinition="ENUM('visible', 'invisible')") */
|
||||||
|
private $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
In this case however Schema-Tool update will have a hard time not to request changes for this column on each call.
|
||||||
|
|
||||||
|
Solution 2: Defining a Type
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
You can make a stateless ENUM type by creating a type class for each unique set of ENUM values.
|
||||||
|
For example for the previous enum type:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\DBAL;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||||
|
|
||||||
|
class EnumVisibilityType extends Type
|
||||||
|
{
|
||||||
|
const ENUM_VISIBILITY = 'enumvisibility';
|
||||||
|
const STATUS_VISIBLE = 'visible';
|
||||||
|
const STATUS_INVISIBLE = 'invisible';
|
||||||
|
|
||||||
|
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
return "ENUM('visible', 'invisible') COMMENT '(DC2Type:enumvisibility)'";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
if (!in_array($value, array(self::STATUS_VISIBLE, self::STATUS_INVISIBLE))) {
|
||||||
|
throw new \InvalidArgumentException("Invalid status");
|
||||||
|
}
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return self::ENUM_VISIBILITY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
You can register this type with ``Type::addType('enumvisibility', 'MyProject\DBAL\EnumVisibilityType');``.
|
||||||
|
Then in your entity you can just use this type:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
/** @Column(type="enumvisibility") */
|
||||||
|
private $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
You can generalize this approach easily to create a base class for enums:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\DBAL;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||||
|
|
||||||
|
abstract class EnumType extends Type
|
||||||
|
{
|
||||||
|
protected $name;
|
||||||
|
protected $values = array();
|
||||||
|
|
||||||
|
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
$values = array_map(function($val) { return "'".$val."'"; }, $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\DBAL;
|
||||||
|
|
||||||
|
class EnumVisibilityType extends EnumType
|
||||||
|
{
|
||||||
|
protected $name = 'enumvisibility';
|
||||||
|
protected $values = array('visible', 'invisible');
|
||||||
|
}
|
||||||
|
|
136
docs/en/cookbook/resolve-target-entity-listener.rst
Normal file
136
docs/en/cookbook/resolve-target-entity-listener.rst
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
Keeping your Modules independent
|
||||||
|
=================================
|
||||||
|
|
||||||
|
One of the goals of using modules is to create discreet units of functionality
|
||||||
|
that do not have many (if any) dependencies, allowing you to use that
|
||||||
|
functionality in other applications without including unnecessary items.
|
||||||
|
|
||||||
|
Doctrine 2.2 includes a new utility called the ``ResolveTargetEntityListener``,
|
||||||
|
that functions by intercepting certain calls inside Doctrine and rewrite
|
||||||
|
targetEntity parameters in your metadata mapping at runtime. It means that
|
||||||
|
in your bundle you are able to use an interface or abstract class in your
|
||||||
|
mappings and expect correct mapping to a concrete entity at runtime.
|
||||||
|
|
||||||
|
This functionality allows you to define relationships between different entities
|
||||||
|
but not making them hard dependencies.
|
||||||
|
|
||||||
|
Background
|
||||||
|
----------
|
||||||
|
|
||||||
|
In the following example, the situation is we have an `InvoiceModule`
|
||||||
|
which provides invoicing functionality, and a `CustomerModule` that
|
||||||
|
contains customer management tools. We want to keep these separated,
|
||||||
|
because they can be used in other systems without each other, but for
|
||||||
|
our application we want to use them together.
|
||||||
|
|
||||||
|
In this case, we have an ``Invoice`` entity with a relationship to a
|
||||||
|
non-existent object, an ``InvoiceSubjectInterface``. The goal is to get
|
||||||
|
the ``ResolveTargetEntityListener`` to replace any mention of the interface
|
||||||
|
with a real object that implements that interface.
|
||||||
|
|
||||||
|
Set up
|
||||||
|
------
|
||||||
|
|
||||||
|
We're going to use the following basic entities (which are incomplete
|
||||||
|
for brevity) to explain how to set up and use the RTEL.
|
||||||
|
|
||||||
|
A Customer entity
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
// src/Acme/AppModule/Entity/Customer.php
|
||||||
|
|
||||||
|
namespace Acme\AppModule\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Acme\CustomerModule\Entity\Customer as BaseCustomer;
|
||||||
|
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(name="customer")
|
||||||
|
*/
|
||||||
|
class Customer extends BaseCustomer implements InvoiceSubjectInterface
|
||||||
|
{
|
||||||
|
// In our example, any methods defined in the InvoiceSubjectInterface
|
||||||
|
// are already implemented in the BaseCustomer
|
||||||
|
}
|
||||||
|
|
||||||
|
An Invoice entity
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
// src/Acme/InvoiceModule/Entity/Invoice.php
|
||||||
|
|
||||||
|
namespace Acme\InvoiceModule\Entity;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping AS ORM;
|
||||||
|
use Acme\InvoiceModule\Model\InvoiceSubjectInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an Invoice.
|
||||||
|
*
|
||||||
|
* @ORM\Entity
|
||||||
|
* @ORM\Table(name="invoice")
|
||||||
|
*/
|
||||||
|
class Invoice
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ORM\ManyToOne(targetEntity="Acme\InvoiceModule\Model\InvoiceSubjectInterface")
|
||||||
|
* @var InvoiceSubjectInterface
|
||||||
|
*/
|
||||||
|
protected $subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
An InvoiceSubjectInterface
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
// src/Acme/InvoiceModule/Model/InvoiceSubjectInterface.php
|
||||||
|
|
||||||
|
namespace Acme\InvoiceModule\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface that the invoice Subject object should implement.
|
||||||
|
* In most circumstances, only a single object should implement
|
||||||
|
* this interface as the ResolveTargetEntityListener can only
|
||||||
|
* change the target to a single object.
|
||||||
|
*/
|
||||||
|
interface InvoiceSubjectInterface
|
||||||
|
{
|
||||||
|
// List any additional methods that your InvoiceModule
|
||||||
|
// will need to access on the subject so that you can
|
||||||
|
// be sure that you have access to those methods.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
Next, we need to configure the listener. Add this to the area you set up Doctrine. You
|
||||||
|
must set this up in the way outlined below, otherwise you can not be guaranteed that
|
||||||
|
the targetEntity resolution will occur reliably::
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
$evm = new \Doctrine\Common\EventManager;
|
||||||
|
|
||||||
|
$rtel = new \Doctrine\ORM\Tools\ResolveTargetEntityListener;
|
||||||
|
$rtel->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.
|
||||||
|
|
||||||
|
|
80
docs/en/cookbook/sql-table-prefixes.rst
Normal file
80
docs/en/cookbook/sql-table-prefixes.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DoctrineExtensions;
|
||||||
|
use \Doctrine\ORM\Event\LoadClassMetadataEventArgs;
|
||||||
|
|
||||||
|
class TablePrefix
|
||||||
|
{
|
||||||
|
protected $prefix = '';
|
||||||
|
|
||||||
|
public function __construct($prefix)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
// $connectionOptions and $config set earlier
|
||||||
|
|
||||||
|
$evm = new \Doctrine\Common\EventManager;
|
||||||
|
|
||||||
|
// Table Prefix
|
||||||
|
$tablePrefix = new \DoctrineExtensions\TablePrefix('prefix_');
|
||||||
|
$evm->addEventListener(\Doctrine\ORM\Events::loadClassMetadata, $tablePrefix);
|
||||||
|
|
||||||
|
$em = \Doctrine\ORM\EntityManager::create($connectionOptions, $config, $evm);
|
||||||
|
|
||||||
|
|
254
docs/en/cookbook/strategy-cookbook-introduction.rst
Normal file
254
docs/en/cookbook/strategy-cookbook-introduction.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This interface defines the basic actions that a block / panel needs to support.
|
||||||
|
*
|
||||||
|
* Every blockstrategy is *only* responsible for rendering a block and declaring some basic
|
||||||
|
* support, but *not* for updating its configuration etc. For this purpose, use controllers
|
||||||
|
* and models.
|
||||||
|
*/
|
||||||
|
interface BlockStrategyInterface {
|
||||||
|
/**
|
||||||
|
* This could configure your entity
|
||||||
|
*/
|
||||||
|
public function setConfig(Config\EntityConfig $config);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the config this strategy is configured with.
|
||||||
|
* @return Core\Model\Config\EntityConfig
|
||||||
|
*/
|
||||||
|
public function getConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the view object.
|
||||||
|
* @param \Zend_View_Interface $view
|
||||||
|
* @return \Zend_View_Helper_Interface
|
||||||
|
*/
|
||||||
|
public function setView(\Zend_View_Interface $view);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return \Zend_View_Interface
|
||||||
|
*/
|
||||||
|
public function getView();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders this strategy. This method will be called when the user
|
||||||
|
* displays the site.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function renderFrontend();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the backend of this block. This method will be called when
|
||||||
|
* a user tries to reconfigure this block instance.
|
||||||
|
*
|
||||||
|
* Most of the time, this method will return / output a simple form which in turn
|
||||||
|
* calls some controllers.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function renderBackend();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all possible types of panels this block can be stacked onto
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function getRequiredPanelTypes();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether a Block is able to use a given type or not
|
||||||
|
* @param string $typeName The typename
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public function canUsePanelType($typeName);
|
||||||
|
|
||||||
|
public function setBlockEntity(AbstractBlock $block);
|
||||||
|
|
||||||
|
public function getBlockEntity();
|
||||||
|
}
|
||||||
|
|
||||||
|
As you can see, we have a method "setBlockEntity" which ties a potential strategy to an object of type AbstractBlock. This type will simply define the basic behaviour of our blocks and could potentially look something like this:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This is the base class for both Panels and Blocks.
|
||||||
|
* It shouldn't be extended by your own blocks - simply write a strategy!
|
||||||
|
*/
|
||||||
|
abstract class AbstractBlock {
|
||||||
|
/**
|
||||||
|
* The id of the block item instance
|
||||||
|
* This is a doctrine field, so you need to setup generation for it
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
// Add code for relation to the parent panel, configuration objects, ....
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This var contains the classname of the strategy
|
||||||
|
* that is used for this blockitem. (This string (!) value will be persisted by Doctrine 2)
|
||||||
|
*
|
||||||
|
* This is a doctrine field, so make sure that you use an @column annotation or setup your
|
||||||
|
* yaml or xml files correctly
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $strategyClassName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This var contains an instance of $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use \Doctrine\ORM,
|
||||||
|
\Doctrine\Common;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The BlockStrategyEventListener will initialize a strategy after the
|
||||||
|
* block itself was loaded.
|
||||||
|
*/
|
||||||
|
class BlockStrategyEventListener implements Common\EventSubscriber {
|
||||||
|
|
||||||
|
protected $view;
|
||||||
|
|
||||||
|
public function __construct(\Zend_View_Interface $view) {
|
||||||
|
$this->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.
|
||||||
|
|
||||||
|
|
137
docs/en/cookbook/validation-of-entities.rst
Normal file
137
docs/en/cookbook/validation-of-entities.rst
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
Validation of Entities
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. sectionauthor:: Benjamin Eberlei <kontakt@beberlei.de>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Order
|
||||||
|
{
|
||||||
|
public function assertCustomerAllowedBuying()
|
||||||
|
{
|
||||||
|
$orderLimit = $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @HasLifecycleCallbacks
|
||||||
|
*/
|
||||||
|
class Order
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @PrePersist @PreUpdate
|
||||||
|
*/
|
||||||
|
public function assertCustomerAllowedBuying() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
In XML Mappings:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="Order">
|
||||||
|
<lifecycle-callbacks>
|
||||||
|
<lifecycle-callback type="prePersist" method="assertCustomerallowedBuying" />
|
||||||
|
<lifecycle-callback type="preUpdate" method="assertCustomerallowedBuying" />
|
||||||
|
</lifecycle-callbacks>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Order
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @PrePersist @PreUpdate
|
||||||
|
*/
|
||||||
|
public function validate()
|
||||||
|
{
|
||||||
|
if (!($this->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>`
|
168
docs/en/cookbook/working-with-datetime.rst
Normal file
168
docs/en/cookbook/working-with-datetime.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
/** @Column(type="datetime") */
|
||||||
|
private $updated;
|
||||||
|
|
||||||
|
public function setUpdated()
|
||||||
|
{
|
||||||
|
// will NOT be saved in the database
|
||||||
|
$this->updated->modify("now");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The way to go would be:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
public function setUpdated()
|
||||||
|
{
|
||||||
|
// WILL be saved in the database
|
||||||
|
$this->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 <http://derickrethans.nl/storing-date-time-in-database.html>`_.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace DoctrineExtensions\DBAL\Types;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||||
|
use Doctrine\DBAL\Types\ConversionException;
|
||||||
|
|
||||||
|
class UTCDateTimeType extends DateTimeType
|
||||||
|
{
|
||||||
|
static private $utc = null;
|
||||||
|
|
||||||
|
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return $value->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Shipping;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Event
|
||||||
|
{
|
||||||
|
/** @Column(type="datetime") */
|
||||||
|
private $created;
|
||||||
|
|
||||||
|
/** @Column(type="string") */
|
||||||
|
private $timezone;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private $localized = false;
|
||||||
|
|
||||||
|
public function __construct(\DateTime $createDate)
|
||||||
|
{
|
||||||
|
$this->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.
|
127
docs/en/index.rst
Normal file
127
docs/en/index.rst
Normal file
@ -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 <reference/faq>` with answers to frequent questions.
|
||||||
|
- The `Doctrine Mailing List <http://groups.google.com/group/doctrine-user>`_
|
||||||
|
- Internet Relay Chat (IRC) in `#doctrine on Freenode <irc://irc.freenode.net/doctrine>`_
|
||||||
|
- Report a bug on `JIRA <http://www.doctrine-project.org/jira>`_.
|
||||||
|
- On `Twitter <https://twitter.com/search/%23doctrine2>`_ with ``#doctrine2``
|
||||||
|
- On `StackOverflow <http://stackoverflow.com/questions/tagged/doctrine2>`_
|
||||||
|
|
||||||
|
If you need more structure over the different topics you can browse the :doc:`table
|
||||||
|
of contents <toc>`.
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* **Tutorial**:
|
||||||
|
:doc:`Code First <tutorials/getting-started>` |
|
||||||
|
:doc:`Model First <tutorials/getting-started-models>` |
|
||||||
|
:doc:`Database First <tutorials/getting-started-database>`
|
||||||
|
|
||||||
|
* **Introduction**:
|
||||||
|
:doc:`In 10 quick steps <tutorials/in-ten-quick-steps>` |
|
||||||
|
:doc:`Architecture <reference/architecture>`
|
||||||
|
|
||||||
|
* **Setup**:
|
||||||
|
:doc:`Installation <reference/installation>` |
|
||||||
|
:doc:`Configuration <reference/configuration>` |
|
||||||
|
:doc:`Tools <reference/tools>`
|
||||||
|
|
||||||
|
* :doc:`Limitations and knowns issues <reference/limitations-and-known-issues>`
|
||||||
|
|
||||||
|
Mapping Objects onto a Database
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
* **Mapping**:
|
||||||
|
:doc:`Objects <reference/basic-mapping>` |
|
||||||
|
:doc:`Associations <reference/association-mapping>` |
|
||||||
|
:doc:`Inheritance <reference/inheritance-mapping>`
|
||||||
|
|
||||||
|
* **Drivers**:
|
||||||
|
:doc:`Docblock Annotations <reference/annotations-reference>` |
|
||||||
|
:doc:`XML <reference/xml-mapping>` |
|
||||||
|
:doc:`YAML <reference/yaml-mapping>` |
|
||||||
|
:doc:`PHP <reference/php-mapping>`
|
||||||
|
|
||||||
|
Working with Objects
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* **Basic Reference**:
|
||||||
|
:doc:`Entities <reference/working-with-objects>` |
|
||||||
|
:doc:`Associations <reference/working-with-associations>` |
|
||||||
|
:doc:`Events <reference/events>`
|
||||||
|
|
||||||
|
* **Query Reference**:
|
||||||
|
:doc:`DQL <reference/dql-doctrine-query-language>` |
|
||||||
|
:doc:`QueryBuilder <reference/query-builder>` |
|
||||||
|
:doc:`Native SQL <reference/native-sql>`
|
||||||
|
|
||||||
|
* **Internals**:
|
||||||
|
:doc:`Internals explained <reference/unitofwork>` |
|
||||||
|
:doc:`Associations <reference/unitofwork-associations>`
|
||||||
|
|
||||||
|
Advanced Topics
|
||||||
|
---------------
|
||||||
|
|
||||||
|
* :doc:`Transactions and Concurrency <reference/transactions-and-concurrency>`
|
||||||
|
* :doc:`Filters <reference/filters>`
|
||||||
|
* :doc:`NamingStrategy <reference/namingstrategy>`
|
||||||
|
* :doc:`Improving Performance <reference/improving-performance>`
|
||||||
|
* :doc:`Caching <reference/caching>`
|
||||||
|
* :doc:`Partial Objects <reference/partial-objects>`
|
||||||
|
* :doc:`Change Tracking Policies <reference/change-tracking-policies>`
|
||||||
|
* :doc:`Best Practices <reference/best-practices>`
|
||||||
|
* :doc:`Metadata Drivers <reference/metadata-drivers>`
|
||||||
|
|
||||||
|
Tutorials
|
||||||
|
---------
|
||||||
|
|
||||||
|
* :doc:`Indexed associations <tutorials/working-with-indexed-associations>`
|
||||||
|
* :doc:`Extra Lazy Associations <tutorials/extra-lazy-associations>`
|
||||||
|
* :doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
|
||||||
|
* :doc:`Ordered associations <tutorials/ordered-associations>`
|
||||||
|
* :doc:`Pagination <tutorials/pagination>`
|
||||||
|
* :doc:`Override Field/Association Mappings In Subclasses <tutorials/override-field-association-mappings-in-subclasses>`
|
||||||
|
|
||||||
|
Cookbook
|
||||||
|
--------
|
||||||
|
|
||||||
|
* **Patterns**:
|
||||||
|
:doc:`Aggregate Fields <cookbook/aggregate-fields>` |
|
||||||
|
:doc:`Decorator Pattern <cookbook/decorator-pattern>` |
|
||||||
|
:doc:`Strategy Pattern <cookbook/strategy-cookbook-introduction>`
|
||||||
|
|
||||||
|
* **DQL Extension Points**:
|
||||||
|
:doc:`DQL Custom Walkers <cookbook/dql-custom-walkers>` |
|
||||||
|
:doc:`DQL User-Defined-Functions <cookbook/dql-user-defined-functions>`
|
||||||
|
|
||||||
|
* **Implementation**:
|
||||||
|
:doc:`Array Access <cookbook/implementing-arrayaccess-for-domain-objects>` |
|
||||||
|
:doc:`Notify ChangeTracking Example <cookbook/implementing-the-notify-changetracking-policy>` |
|
||||||
|
:doc:`Using Wakeup Or Clone <cookbook/implementing-wakeup-or-clone>` |
|
||||||
|
:doc:`Working with DateTime <cookbook/working-with-datetime>` |
|
||||||
|
:doc:`Validation <cookbook/validation-of-entities>` |
|
||||||
|
:doc:`Entities in the Session <cookbook/entities-in-session>`
|
||||||
|
|
||||||
|
* **Integration into Frameworks/Libraries**
|
||||||
|
:doc:`CodeIgniter <cookbook/integrating-with-codeigniter>`
|
||||||
|
|
||||||
|
* **Hidden Gems**
|
||||||
|
:doc:`Prefixing Table Name <cookbook/sql-table-prefixes>`
|
||||||
|
|
||||||
|
* **Custom Datatypes**
|
||||||
|
:doc:`MySQL Enums <cookbook/mysql-enums>`
|
||||||
|
:doc:`Advanced Field Value Conversion <cookbook/advanced-field-value-conversion-using-custom-mapping-types>`
|
||||||
|
|
113
docs/en/make.bat
Normal file
113
docs/en/make.bat
Normal file
@ -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 ^<target^>` where ^<target^> 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
|
1113
docs/en/reference/annotations-reference.rst
Normal file
1113
docs/en/reference/annotations-reference.rst
Normal file
File diff suppressed because it is too large
Load Diff
197
docs/en/reference/architecture.rst
Normal file
197
docs/en/reference/architecture.rst
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
Architecture
|
||||||
|
============
|
||||||
|
|
||||||
|
This chapter gives an overview of the overall architecture,
|
||||||
|
terminology and constraints of Doctrine 2. It is recommended to
|
||||||
|
read this chapter carefully.
|
||||||
|
|
||||||
|
Using an Object-Relational Mapper
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
As the term ORM already hints at, Doctrine 2 aims to simplify the
|
||||||
|
translation between database rows and the PHP object model. The
|
||||||
|
primary use case for Doctrine are therefore applications that
|
||||||
|
utilize the Object-Oriented Programming Paradigm. For applications
|
||||||
|
that not primarily work with objects Doctrine 2 is not suited very
|
||||||
|
well.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
------------
|
||||||
|
|
||||||
|
Doctrine 2 requires a minimum of PHP 5.3.0. For greatly improved
|
||||||
|
performance it is also recommended that you use APC with PHP.
|
||||||
|
|
||||||
|
Doctrine 2 Packages
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Doctrine 2 is divided into three main packages.
|
||||||
|
|
||||||
|
- Common
|
||||||
|
- DBAL (includes Common)
|
||||||
|
- ORM (includes DBAL+Common)
|
||||||
|
|
||||||
|
This manual mainly covers the ORM package, sometimes touching parts
|
||||||
|
of the underlying DBAL and Common packages. The Doctrine code base
|
||||||
|
is split in to these packages for a few reasons and they are to...
|
||||||
|
|
||||||
|
|
||||||
|
- ...make things more maintainable and decoupled
|
||||||
|
- ...allow you to use the code in Doctrine Common without the ORM
|
||||||
|
or DBAL
|
||||||
|
- ...allow you to use the DBAL without the ORM
|
||||||
|
|
||||||
|
The Common Package
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The Common package contains highly reusable components that have no
|
||||||
|
dependencies beyond the package itself (and PHP, of course). The
|
||||||
|
root namespace of the Common package is ``Doctrine\Common``.
|
||||||
|
|
||||||
|
The DBAL Package
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The DBAL package contains an enhanced database abstraction layer on
|
||||||
|
top of PDO but is not strongly bound to PDO. The purpose of this
|
||||||
|
layer is to provide a single API that bridges most of the
|
||||||
|
differences between the different RDBMS vendors. The root namespace
|
||||||
|
of the DBAL package is ``Doctrine\DBAL``.
|
||||||
|
|
||||||
|
The ORM Package
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The ORM package contains the object-relational mapping toolkit that
|
||||||
|
provides transparent relational persistence for plain PHP objects.
|
||||||
|
The root namespace of the ORM package is ``Doctrine\ORM``.
|
||||||
|
|
||||||
|
Terminology
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Entities
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
An entity is a lightweight, persistent domain object. An entity can
|
||||||
|
be any regular PHP class observing the following restrictions:
|
||||||
|
|
||||||
|
|
||||||
|
- An entity class must not be final or contain final methods.
|
||||||
|
- All persistent properties/field of any entity class should
|
||||||
|
always be private or protected, otherwise lazy-loading might not
|
||||||
|
work as expected. In case you serialize entities (for example Session)
|
||||||
|
properties should be protected (See Serialize section below).
|
||||||
|
- An entity class must not implement ``__clone`` or
|
||||||
|
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||||
|
- An entity class must not implement ``__wakeup`` or
|
||||||
|
:doc:`do so safely <../cookbook/implementing-wakeup-or-clone>`.
|
||||||
|
Also consider implementing
|
||||||
|
`Serializable <http://de3.php.net/manual/en/class.serializable.php>`_
|
||||||
|
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 <http://martinfowler.com/eaaCatalog/unitOfWork.html>`_,
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
1144
docs/en/reference/association-mapping.rst
Normal file
1144
docs/en/reference/association-mapping.rst
Normal file
File diff suppressed because it is too large
Load Diff
675
docs/en/reference/basic-mapping.rst
Normal file
675
docs/en/reference/basic-mapping.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class MyPersistentClass
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyPersistentClass">
|
||||||
|
<!-- ... -->
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @Table(name="my_persistent_class")
|
||||||
|
*/
|
||||||
|
class MyPersistentClass
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyPersistentClass" table="my_persistent_class">
|
||||||
|
<!-- ... -->
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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() <http://docs.php.net/manual/en/function.date-default-timezone-set.php>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class MyPersistentClass
|
||||||
|
{
|
||||||
|
/** @Column(type="integer") */
|
||||||
|
private $id;
|
||||||
|
/** @Column(length=50) */
|
||||||
|
private $name; // type defaults to string
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyPersistentClass">
|
||||||
|
<field name="id" type="integer" />
|
||||||
|
<field name="name" length="50" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Column(name="db_name") */
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyPersistentClass">
|
||||||
|
<field name="name" column="db_name" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace My\Project\Types;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
use Doctrine\DBAL\Platforms\AbstractPlatform;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* My custom datatype.
|
||||||
|
*/
|
||||||
|
class MyType extends Type
|
||||||
|
{
|
||||||
|
const MYTYPE = 'mytype'; // modify to match your type name
|
||||||
|
|
||||||
|
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
// return the SQL used to create your column type. To create a portable column type, use the $platform.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertToPHPValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
// This is executed when the value is read from the database. Make your conversions here, optionally using the $platform.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function convertToDatabaseValue($value, AbstractPlatform $platform)
|
||||||
|
{
|
||||||
|
// This is executed when the value is written to the database. Make your conversions here, optionally using the $platform.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
return self::MYTYPE; // modify to match your constant name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Restrictions to keep in mind:
|
||||||
|
|
||||||
|
|
||||||
|
- If the value of the field is *NULL* the method
|
||||||
|
``convertToDatabaseValue()`` is not called.
|
||||||
|
- The ``UnitOfWork`` never passes values to the database convert
|
||||||
|
method that did not change in the request.
|
||||||
|
|
||||||
|
When you have implemented the type you still need to let Doctrine
|
||||||
|
know about it. This can be achieved through the
|
||||||
|
``Doctrine\DBAL\Types\Type#addType($name, $className)``
|
||||||
|
method. See the following example:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// in bootstrapping code
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Types\Type;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Register my type
|
||||||
|
Type::addType('mytype', 'My\Project\Types\MyType');
|
||||||
|
|
||||||
|
As can be seen above, when registering the custom types in the
|
||||||
|
configuration you specify a unique name for the mapping type and
|
||||||
|
map that to the corresponding fully qualified class name. Now you
|
||||||
|
can use your new type in your mapping like this:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MyPersistentClass
|
||||||
|
{
|
||||||
|
/** @Column(type="mytype") */
|
||||||
|
private $field;
|
||||||
|
}
|
||||||
|
|
||||||
|
To have Schema-Tool convert the underlying database type of your
|
||||||
|
new "mytype" directly into an instance of ``MyType`` you have to
|
||||||
|
additionally register this mapping with your database platform:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$conn = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MyPersistentClass
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") */
|
||||||
|
private $id;
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyPersistentClass">
|
||||||
|
<id name="id" type="integer" />
|
||||||
|
<field name="name" length="50" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MyPersistentClass
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id @Column(type="integer")
|
||||||
|
* @GeneratedValue
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyPersistentClass">
|
||||||
|
<id name="id" type="integer">
|
||||||
|
<generator strategy="AUTO" />
|
||||||
|
</id>
|
||||||
|
<field name="name" length="50" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id
|
||||||
|
* @GeneratedValue(strategy="SEQUENCE")
|
||||||
|
* @SequenceGenerator(sequenceName="tablename_seq", initialValue=1, allocationSize=100)
|
||||||
|
*/
|
||||||
|
protected $id = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="User">
|
||||||
|
<id name="id" type="integer">
|
||||||
|
<generator strategy="SEQUENCE" />
|
||||||
|
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
|
||||||
|
</id>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Column(name="`number`", type="integer") */
|
||||||
|
private $number;
|
||||||
|
|
||||||
|
Doctrine will then quote this column name in all SQL statements
|
||||||
|
according to the used database platform.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Identifier Quoting is not supported for join column
|
||||||
|
names or discriminator column names.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Identifier Quoting is a feature that is mainly intended
|
||||||
|
to support legacy database schemas. The use of reserved words and
|
||||||
|
identifier quoting is generally discouraged. Identifier quoting
|
||||||
|
should not be used to enable the use non-standard-characters such
|
||||||
|
as a dash in a hypothetical column ``test-name``. Also Schema-Tool
|
||||||
|
will likely have troubles when quoting is used for case-sensitivity
|
||||||
|
reasons (in Oracle for example).
|
||||||
|
|
||||||
|
|
||||||
|
|
177
docs/en/reference/batch-processing.rst
Normal file
177
docs/en/reference/batch-processing.rst
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
Batch Processing
|
||||||
|
================
|
||||||
|
|
||||||
|
This chapter shows you how to accomplish bulk inserts, updates and
|
||||||
|
deletes with Doctrine in an efficient way. The main problem with
|
||||||
|
bulk operations is usually not to run out of memory and this is
|
||||||
|
especially what the strategies presented here provide help with.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
An ORM tool is not primarily well-suited for mass
|
||||||
|
inserts, updates or deletions. Every RDBMS has its own, most
|
||||||
|
effective way of dealing with such operations and if the options
|
||||||
|
outlined below are not sufficient for your purposes we recommend
|
||||||
|
you use the tools for your particular RDBMS for these bulk
|
||||||
|
operations.
|
||||||
|
|
||||||
|
|
||||||
|
Bulk Inserts
|
||||||
|
------------
|
||||||
|
|
||||||
|
Bulk inserts in Doctrine are best performed in batches, taking
|
||||||
|
advantage of the transactional write-behind behavior of an
|
||||||
|
``EntityManager``. The following code shows an example for
|
||||||
|
inserting 10000 objects with a batch size of 20. You may need to
|
||||||
|
experiment with the batch size to find the size that works best for
|
||||||
|
you. Larger batch sizes mean more prepared statement reuse
|
||||||
|
internally but also mean more work during ``flush``.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$batchSize = 20;
|
||||||
|
for ($i = 1; $i <= 10000; ++$i) {
|
||||||
|
$user = new CmsUser;
|
||||||
|
$user->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$q = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$batchSize = 20;
|
||||||
|
$i = 0;
|
||||||
|
$q = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$q = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$batchSize = 20;
|
||||||
|
$i = 0;
|
||||||
|
$q = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$q = $this->_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.
|
||||||
|
|
||||||
|
|
||||||
|
|
127
docs/en/reference/best-practices.rst
Normal file
127
docs/en/reference/best-practices.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
|
class User {
|
||||||
|
private $addresses;
|
||||||
|
private $articles;
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
$this->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.
|
||||||
|
|
||||||
|
|
493
docs/en/reference/caching.rst
Normal file
493
docs/en/reference/caching.rst
Normal file
@ -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 <http://us2.php.net/apc>`_. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cacheDriver = new \Doctrine\Common\Cache\ApcCache();
|
||||||
|
$cacheDriver->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 <http://us2.php.net/memcache>`_. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$memcache = new Memcache();
|
||||||
|
$memcache->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 <http://xcache.lighttpd.net/>`_. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cacheDriver = new \Doctrine\Common\Cache\XcacheCache();
|
||||||
|
$cacheDriver->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 <http://redis.io/>`_. Also check
|
||||||
|
`here <https://github.com/nicolasff/phpredis/>`_ 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$redis = new Redis();
|
||||||
|
$redis->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cacheDriver = new \Doctrine\Common\Cache\ArrayCache();
|
||||||
|
|
||||||
|
Saving
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
To save some data to the cache driver it is as simple as using the
|
||||||
|
``save()`` method.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$array = array(
|
||||||
|
'key1' => '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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$array = $cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$deleted = $cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$deleted = $cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$deleted = $cacheDriver->deleteByPrefix('users_');
|
||||||
|
|
||||||
|
By Suffix
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
Just like we did above with the prefix you can do the same with a
|
||||||
|
suffix.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$deleted = $cacheDriver->deleteBySuffix('_my_account');
|
||||||
|
|
||||||
|
All
|
||||||
|
^^^
|
||||||
|
|
||||||
|
If you simply want to delete all cache entries you can do so with
|
||||||
|
the ``deleteAll()`` method.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$deleted = $cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
echo $cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cacheDriver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config = new \Doctrine\ORM\Configuration();
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$query = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$query->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()``.
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$query->useResultCache(false);
|
||||||
|
|
||||||
|
|
||||||
|
If you want to set the time the cache has to live you can use the
|
||||||
|
``setResultCacheLifetime()`` method.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$query->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$query->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$query->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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 <http://notmysock.org/blog/php/user-cache-timebomb.html>`_.
|
||||||
|
|
||||||
|
|
151
docs/en/reference/change-tracking-policies.rst
Normal file
151
docs/en/reference/change-tracking-policies.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @ChangeTrackingPolicy("DEFERRED_EXPLICIT")
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Notify
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
This 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
|
||||||
|
namespace. As a guideline, such an implementation can look as
|
||||||
|
follows:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\Common\NotifyPropertyChanged,
|
||||||
|
Doctrine\Common\PropertyChangedListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @ChangeTrackingPolicy("NOTIFY")
|
||||||
|
*/
|
||||||
|
class MyEntity implements NotifyPropertyChanged
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
private $_listeners = array();
|
||||||
|
|
||||||
|
public function addPropertyChangedListener(PropertyChangedListener $listener)
|
||||||
|
{
|
||||||
|
$this->_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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// ...
|
||||||
|
|
||||||
|
class MyEntity implements NotifyPropertyChanged
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
protected function _onPropertyChanged($propName, $oldValue, $newValue)
|
||||||
|
{
|
||||||
|
if ($this->_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.
|
||||||
|
|
||||||
|
|
590
docs/en/reference/configuration.rst
Normal file
590
docs/en/reference/configuration.rst
Normal file
@ -0,0 +1,590 @@
|
|||||||
|
Configuration
|
||||||
|
=============
|
||||||
|
|
||||||
|
Bootstrapping Doctrine is a relatively simple procedure that
|
||||||
|
roughly exists of four steps:
|
||||||
|
|
||||||
|
- `Installation <reference/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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Include Composer Autoload
|
||||||
|
require_once "vendor/autoload.php";
|
||||||
|
|
||||||
|
Skip the rest of this section unless you are using another installation method.
|
||||||
|
|
||||||
|
Lets start with the class loading setup. We need to set up some
|
||||||
|
class loaders (often called "autoloader") so that Doctrine class
|
||||||
|
files are loaded on demand. The Doctrine namespace contains a very
|
||||||
|
fast and minimalistic class loader that can be used for Doctrine
|
||||||
|
and any other libraries where the coding standards ensure that a
|
||||||
|
class's location in the directory tree is reflected by its name and
|
||||||
|
namespace and where there is a common root namespace.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
You are not forced to use the Doctrine class loader to
|
||||||
|
load Doctrine classes. Doctrine does not care how the classes are
|
||||||
|
loaded, if you want to use a different class loader or your own to
|
||||||
|
load Doctrine classes, just do that. Along the same lines, the
|
||||||
|
class loader in the Doctrine namespace is not meant to be only used
|
||||||
|
for Doctrine classes, too. It is a generic class loader that can be
|
||||||
|
used for any classes that follow some basic naming standards as
|
||||||
|
described above.
|
||||||
|
|
||||||
|
|
||||||
|
The following example shows the setup of a ``ClassLoader`` for the
|
||||||
|
different types of Doctrine Installations:
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This assumes you've created some kind of script to test
|
||||||
|
the following code in. Something like a ``test.php`` file.
|
||||||
|
|
||||||
|
PEAR
|
||||||
|
^^^^
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// test.php
|
||||||
|
|
||||||
|
require 'Doctrine/ORM/Tools/Setup.php';
|
||||||
|
|
||||||
|
Doctrine\ORM\Tools\Setup::registerAutoloadPEAR();
|
||||||
|
|
||||||
|
Tarball Download
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// test.php
|
||||||
|
require 'Doctrine/ORM/Tools/Setup.php';
|
||||||
|
|
||||||
|
$lib = "/path/to/doctrine2-orm/lib";
|
||||||
|
Doctrine\ORM\Tools\Setup::registerAutoloadDirectory($lib);
|
||||||
|
|
||||||
|
Git
|
||||||
|
^^^
|
||||||
|
|
||||||
|
The Git bootstrap assumes that you have fetched the related
|
||||||
|
packages through ``git submodule update --init``
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// test.php
|
||||||
|
require 'Doctrine/ORM/Tools/Setup.php';
|
||||||
|
|
||||||
|
$lib = '/path/to/doctrine2-orm-root';
|
||||||
|
Doctrine\ORM\Tools\Setup::registerAutoloadGit($lib);
|
||||||
|
|
||||||
|
|
||||||
|
Additional Symfony Components
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
All three autoloading setups described above also take care of
|
||||||
|
the autoloading of the Symfony Console and YAML component,
|
||||||
|
which are optional dependencies for Doctrine 2.
|
||||||
|
|
||||||
|
Obtaining an EntityManager
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Once you have prepared the class loading, you acquire an
|
||||||
|
*EntityManager* instance. The EntityManager class is the primary
|
||||||
|
access point to ORM functionality provided by Doctrine.
|
||||||
|
|
||||||
|
Quick Configuration Example
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The above example is a complete setup of the required options for Doctrine.
|
||||||
|
You can have this step of your code much simpler and use one of the predefined
|
||||||
|
setup methods:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\Tools\Setup;
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
|
||||||
|
$paths = array("/path/to/entities-or-mapping-files");
|
||||||
|
$isDevMode = false;
|
||||||
|
|
||||||
|
// the connection configuration
|
||||||
|
$dbParams = array(
|
||||||
|
'driver' => '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 <http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html>`_.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\EntityManager,
|
||||||
|
Doctrine\ORM\Configuration;
|
||||||
|
|
||||||
|
// ...
|
||||||
|
|
||||||
|
if ($applicationMode == "development") {
|
||||||
|
$cache = new \Doctrine\Common\Cache\ArrayCache;
|
||||||
|
} else {
|
||||||
|
$cache = new \Doctrine\Common\Cache\ApcCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = new Configuration;
|
||||||
|
$config->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 <http://www.php.net/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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$driverImpl = $config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager, $cart instanceof MyProject\Model\Cart
|
||||||
|
// $itemId comes from somewhere, probably a request parameter
|
||||||
|
$item = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\Proxy\Autoloader;
|
||||||
|
|
||||||
|
$proxyDir = "/path/to/proxies";
|
||||||
|
$proxyNamespace = "MyProxies";
|
||||||
|
|
||||||
|
Autoloader::register($proxyDir, $proxyNamespace);
|
||||||
|
|
||||||
|
If you want to execute additional logic to intercept the proxy file not found
|
||||||
|
state you can pass a closure as the third argument. It will be called with
|
||||||
|
the arguments proxydir, namespace and className when the proxy file could not
|
||||||
|
be found.
|
||||||
|
|
||||||
|
Multiple Metadata Sources
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
When using different components using Doctrine 2 you may end up
|
||||||
|
with them using two different metadata drivers, for example XML and
|
||||||
|
YAML. You can use the DriverChain Metadata implementations to
|
||||||
|
aggregate these drivers based on namespaces:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\Mapping\Driver\DriverChain;
|
||||||
|
|
||||||
|
$chain = new DriverChain();
|
||||||
|
$chain->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
|
||||||
|
$cli->setCatchExceptions(true);
|
||||||
|
$cli->setHelperSet($helperSet);
|
||||||
|
Doctrine\ORM\Tools\Console\ConsoleRunner::addCommands($cli);
|
||||||
|
$cli->run();
|
||||||
|
|
1667
docs/en/reference/dql-doctrine-query-language.rst
Normal file
1667
docs/en/reference/dql-doctrine-query-language.rst
Normal file
File diff suppressed because it is too large
Load Diff
658
docs/en/reference/events.rst
Normal file
658
docs/en/reference/events.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$evm = new EventManager();
|
||||||
|
|
||||||
|
Now we can add some event listeners to the ``$evm``. Let's create a
|
||||||
|
``EventTest`` class to play around with.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class EventTest
|
||||||
|
{
|
||||||
|
const preFoo = 'preFoo';
|
||||||
|
const postFoo = 'postFoo';
|
||||||
|
|
||||||
|
private $_evm;
|
||||||
|
|
||||||
|
public $preFooInvoked = false;
|
||||||
|
public $postFooInvoked = false;
|
||||||
|
|
||||||
|
public function __construct($evm)
|
||||||
|
{
|
||||||
|
$evm->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$evm->dispatchEvent(EventTest::preFoo);
|
||||||
|
$evm->dispatchEvent(EventTest::postFoo);
|
||||||
|
|
||||||
|
You can easily remove a listener with the ``removeEventListener()``
|
||||||
|
method.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$evm->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class TestEventSubscriber implements \Doctrine\Common\EventSubscriber
|
||||||
|
{
|
||||||
|
public $preFooInvoked = false;
|
||||||
|
|
||||||
|
public function preFoo()
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$evm->dispatchEvent(TestEvent::preFoo);
|
||||||
|
|
||||||
|
Now you can test the ``$eventSubscriber`` instance to see if the
|
||||||
|
``preFoo()`` method was invoked.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($eventSubscriber->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\Events;
|
||||||
|
echo Events::preUpdate;
|
||||||
|
|
||||||
|
These can be hooked into by two different types of event
|
||||||
|
listeners:
|
||||||
|
|
||||||
|
|
||||||
|
- Lifecycle Callbacks are methods on the entity classes that are
|
||||||
|
called when the event is triggered. They receive absolutely no
|
||||||
|
arguments and are specifically designed to allow changes inside the
|
||||||
|
entity classes state.
|
||||||
|
- Lifecycle Event Listeners are classes with specific callback
|
||||||
|
methods that receives some kind of ``EventArgs`` instance which
|
||||||
|
give access to the entity, EntityManager or other relevant data.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
All Lifecycle events that happen during the ``flush()`` of
|
||||||
|
an EntityManager have very specific constraints on the allowed
|
||||||
|
operations that can be executed. Please read the
|
||||||
|
:ref:`reference-events-implementing-listeners` section very carefully
|
||||||
|
to understand which operations are allowed in which lifecycle event.
|
||||||
|
|
||||||
|
|
||||||
|
Lifecycle Callbacks
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
A lifecycle event is a regular event with the additional feature of
|
||||||
|
providing a mechanism to register direct callbacks inside the
|
||||||
|
corresponding entity classes that are executed when the lifecycle
|
||||||
|
event occurs.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/** @Entity @HasLifecycleCallbacks */
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Column(type="string", length=255)
|
||||||
|
*/
|
||||||
|
public $value;
|
||||||
|
|
||||||
|
/** @Column(name="created_at", type="string", length=255) */
|
||||||
|
private $createdAt;
|
||||||
|
|
||||||
|
/** @PrePersist */
|
||||||
|
public function doStuffOnPrePersist()
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
/Users/robo/dev/php/Doctrine/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="User">
|
||||||
|
|
||||||
|
<lifecycle-callbacks>
|
||||||
|
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
|
||||||
|
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
|
||||||
|
</lifecycle-callbacks>
|
||||||
|
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
You just need to make sure a public ``doStuffOnPrePersist()`` and
|
||||||
|
``doStuffOnPostPersist()`` method is defined on your ``User``
|
||||||
|
model.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// ...
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
public function doStuffOnPrePersist()
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
public function doStuffOnPostPersist()
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The ``key`` of the lifecycleCallbacks is the name of the method and
|
||||||
|
the value is the event type. The allowed event types are the ones
|
||||||
|
listed in the previous Lifecycle Events section.
|
||||||
|
|
||||||
|
Listening to Lifecycle Events
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Lifecycle event listeners are much more powerful than the simple
|
||||||
|
lifecycle callbacks that are defined on the entity classes. They
|
||||||
|
allow to implement re-usable behaviors between different entity
|
||||||
|
classes, yet require much more detailed knowledge about the inner
|
||||||
|
workings of the EntityManager and UnitOfWork. Please read the
|
||||||
|
*Implementing Event Listeners* section carefully if you are trying
|
||||||
|
to write your own listener.
|
||||||
|
|
||||||
|
To register an event listener you have to hook it into the
|
||||||
|
EventManager that is passed to the EntityManager factory:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$eventManager = new EventManager();
|
||||||
|
$eventManager->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$entityManager->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\ORM\Event\PreFlushEventArgs;
|
||||||
|
|
||||||
|
class PreFlushExampleListener
|
||||||
|
{
|
||||||
|
public function preFlush(PreFlushEventArgs $args)
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onFlush
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
OnFlush is a very powerful event. It is called inside
|
||||||
|
``EntityManager#flush()`` after the changes to all the managed
|
||||||
|
entities and their associations have been computed. This means, the
|
||||||
|
``onFlush`` event has access to the sets of:
|
||||||
|
|
||||||
|
|
||||||
|
- Entities scheduled for insert
|
||||||
|
- Entities scheduled for update
|
||||||
|
- Entities scheduled for removal
|
||||||
|
- Collections scheduled for update
|
||||||
|
- Collections scheduled for removal
|
||||||
|
|
||||||
|
To make use of the onFlush event you have to be familiar with the
|
||||||
|
internal UnitOfWork API, which grants you access to the previously
|
||||||
|
mentioned sets. See this example:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class FlushExampleListener
|
||||||
|
{
|
||||||
|
public function onFlush(OnFlushEventArgs $eventArgs)
|
||||||
|
{
|
||||||
|
$em = $eventArgs->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\ORM\Event\PostFlushEventArgs;
|
||||||
|
|
||||||
|
class PostFlushExampleListener
|
||||||
|
{
|
||||||
|
public function postFlush(PostFlushEventArgs $args)
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
preUpdate
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
PreUpdate is the most restrictive to use event, since it is called
|
||||||
|
right before an update statement is called for an entity inside the
|
||||||
|
``EntityManager#flush()`` method.
|
||||||
|
|
||||||
|
Changes to associations of the updated entity are never allowed in
|
||||||
|
this event, since Doctrine cannot guarantee to correctly handle
|
||||||
|
referential integrity at this point of the flush operation. This
|
||||||
|
event has a powerful feature however, it is executed with a
|
||||||
|
``PreUpdateEventArgs`` instance, which contains a reference to the
|
||||||
|
computed change-set of this entity.
|
||||||
|
|
||||||
|
This means you have access to all the fields that have changed for
|
||||||
|
this entity with their old and new value. The following methods are
|
||||||
|
available on the ``PreUpdateEventArgs``:
|
||||||
|
|
||||||
|
|
||||||
|
- ``getEntity()`` to get access to the actual entity.
|
||||||
|
- ``getEntityChangeSet()`` to get a copy of the changeset array.
|
||||||
|
Changes to this returned array do not affect updating.
|
||||||
|
- ``hasChangedField($fieldName)`` to check if the given field name
|
||||||
|
of the current entity changed.
|
||||||
|
- ``getOldValue($fieldName)`` and ``getNewValue($fieldName)`` to
|
||||||
|
access the values of a field.
|
||||||
|
- ``setNewValue($fieldName, $value)`` to change the value of a
|
||||||
|
field to be updated.
|
||||||
|
|
||||||
|
A simple example for this event looks like:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class NeverAliceOnlyBobListener
|
||||||
|
{
|
||||||
|
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||||
|
{
|
||||||
|
if ($eventArgs->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class ValidCreditCardListener
|
||||||
|
{
|
||||||
|
public function preUpdate(PreUpdateEventArgs $eventArgs)
|
||||||
|
{
|
||||||
|
if ($eventArgs->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$test = new EventTest();
|
||||||
|
$metadataFactory = $em->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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
224
docs/en/reference/faq.rst
Normal file
224
docs/en/reference/faq.rst
Normal file
@ -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 <http://www.symfony.com>`_ 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 <http://github.com/beberlei/DoctrineExtensions>`_
|
||||||
|
* `Pagerfanta <http://github.com/whiteoctober/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 <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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$dql = "SELECT u FROM User u";
|
||||||
|
$query = $entityManager->createQuery($dql);
|
||||||
|
var_dump($query->getSQL());
|
||||||
|
|
||||||
|
$qb = $entityManager->createQueryBuilder();
|
||||||
|
$qb->select('u')->from('User', 'u');
|
||||||
|
var_dump($qb->getDQL());
|
91
docs/en/reference/filters.rst
Normal file
91
docs/en/reference/filters.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Example;
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetaData,
|
||||||
|
Doctrine\ORM\Query\Filter\SQLFilter;
|
||||||
|
|
||||||
|
class MyLocaleFilter extends SQLFilter
|
||||||
|
{
|
||||||
|
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
|
||||||
|
{
|
||||||
|
// Check if the entity implements the LocalAware interface
|
||||||
|
if (!$targetEntity->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$filter = $em->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.
|
64
docs/en/reference/improving-performance.rst
Normal file
64
docs/en/reference/improving-performance.rst
Normal file
@ -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 <dql-temporarily-change-fetch-mode>`
|
||||||
|
|
||||||
|
|
||||||
|
Apply Best Practices
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
A lot of the points mentioned in the Best Practices chapter will
|
||||||
|
also positively affect the performance of Doctrine.
|
||||||
|
|
||||||
|
|
559
docs/en/reference/inheritance-mapping.rst
Normal file
559
docs/en/reference/inheritance-mapping.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @MappedSuperclass */
|
||||||
|
class MappedSuperclassBase
|
||||||
|
{
|
||||||
|
/** @Column(type="integer") */
|
||||||
|
protected $mapped1;
|
||||||
|
/** @Column(type="string") */
|
||||||
|
protected $mapped2;
|
||||||
|
/**
|
||||||
|
* @OneToOne(targetEntity="MappedSuperclassRelated1")
|
||||||
|
* @JoinColumn(name="related1_id", referencedColumnName="id")
|
||||||
|
*/
|
||||||
|
protected $mappedRelated1;
|
||||||
|
|
||||||
|
// ... more fields and methods
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @Entity */
|
||||||
|
class EntitySubClass extends MappedSuperclassBase
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") */
|
||||||
|
private $id;
|
||||||
|
/** @Column(type="string") */
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
// ... more fields and methods
|
||||||
|
}
|
||||||
|
|
||||||
|
The DDL for the corresponding database schema would look something
|
||||||
|
like this (this is for SQLite):
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
CREATE TABLE EntitySubClass (mapped1 INTEGER NOT NULL, mapped2 TEXT NOT NULL, id INTEGER NOT NULL, name TEXT NOT NULL, related1_id INTEGER DEFAULT NULL, PRIMARY KEY(id))
|
||||||
|
|
||||||
|
As you can see from this DDL snippet, there is only a single table
|
||||||
|
for the entity subclass. All the mappings from the mapped
|
||||||
|
superclass were inherited to the subclass as if they had been
|
||||||
|
defined on that class directly.
|
||||||
|
|
||||||
|
Single Table Inheritance
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
`Single Table Inheritance <http://martinfowler.com/eaaCatalog/singleTableInheritance.html>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @InheritanceType("SINGLE_TABLE")
|
||||||
|
* @DiscriminatorColumn(name="discr", type="string")
|
||||||
|
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||||
|
*/
|
||||||
|
class Person
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Employee extends Person
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Things to note:
|
||||||
|
|
||||||
|
|
||||||
|
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
|
||||||
|
must be specified on the topmost class that is part of the mapped
|
||||||
|
entity hierarchy.
|
||||||
|
- The @DiscriminatorMap specifies which values of the
|
||||||
|
discriminator column identify a row as being of a certain type. In
|
||||||
|
the case above a value of "person" identifies a row as being of
|
||||||
|
type ``Person`` and "employee" identifies a row as being of type
|
||||||
|
``Employee``.
|
||||||
|
- The names of the classes in the discriminator map do not need to
|
||||||
|
be fully qualified if the classes are contained in the same
|
||||||
|
namespace as the entity class on which the discriminator map is
|
||||||
|
applied.
|
||||||
|
|
||||||
|
Design-time considerations
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This mapping approach works well when the type hierarchy is fairly
|
||||||
|
simple and stable. Adding a new type to the hierarchy and adding
|
||||||
|
fields to existing supertypes simply involves adding new columns to
|
||||||
|
the table, though in large deployments this may have an adverse
|
||||||
|
impact on the index and column layout inside the database.
|
||||||
|
|
||||||
|
Performance impact
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This strategy is very efficient for querying across all types in
|
||||||
|
the hierarchy or for specific types. No table joins are required,
|
||||||
|
only a WHERE clause listing the type identifiers. In particular,
|
||||||
|
relationships involving types that employ this mapping strategy are
|
||||||
|
very performant.
|
||||||
|
|
||||||
|
There is a general performance consideration with Single Table
|
||||||
|
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||||
|
association is an STI entity, it is preferable for performance reasons that it
|
||||||
|
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||||
|
Otherwise Doctrine *CANNOT* create proxy instances
|
||||||
|
of this entity and will *ALWAYS* load the entity eagerly.
|
||||||
|
|
||||||
|
SQL Schema considerations
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
For Single-Table-Inheritance to work in scenarios where you are
|
||||||
|
using either a legacy database schema or a self-written database
|
||||||
|
schema you have to make sure that all columns that are not in the
|
||||||
|
root entity but in any of the different sub-entities has to allows
|
||||||
|
null values. Columns that have NOT NULL constraints have to be on
|
||||||
|
the root entity of the single-table inheritance hierarchy.
|
||||||
|
|
||||||
|
Class Table Inheritance
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
`Class Table Inheritance <http://martinfowler.com/eaaCatalog/classTableInheritance.html>`_
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @InheritanceType("JOINED")
|
||||||
|
* @DiscriminatorColumn(name="discr", type="string")
|
||||||
|
* @DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
|
||||||
|
*/
|
||||||
|
class Person
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @Entity */
|
||||||
|
class Employee extends Person
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Things to note:
|
||||||
|
|
||||||
|
|
||||||
|
- The @InheritanceType, @DiscriminatorColumn and @DiscriminatorMap
|
||||||
|
must be specified on the topmost class that is part of the mapped
|
||||||
|
entity hierarchy.
|
||||||
|
- The @DiscriminatorMap specifies which values of the
|
||||||
|
discriminator column identify a row as being of which type. In the
|
||||||
|
case above a value of "person" identifies a row as being of type
|
||||||
|
``Person`` and "employee" identifies a row as being of type
|
||||||
|
``Employee``.
|
||||||
|
- The names of the classes in the discriminator map do not need to
|
||||||
|
be fully qualified if the classes are contained in the same
|
||||||
|
namespace as the entity class on which the discriminator map is
|
||||||
|
applied.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When you do not use the SchemaTool to generate the
|
||||||
|
required SQL you should know that deleting a class table
|
||||||
|
inheritance makes use of the foreign key property
|
||||||
|
``ON DELETE CASCADE`` in all database implementations. A failure to
|
||||||
|
implement this yourself will lead to dead rows in the database.
|
||||||
|
|
||||||
|
|
||||||
|
Design-time considerations
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Introducing a new type to the hierarchy, at any level, simply
|
||||||
|
involves interjecting a new table into the schema. Subtypes of that
|
||||||
|
type will automatically join with that new type at runtime.
|
||||||
|
Similarly, modifying any entity type in the hierarchy by adding,
|
||||||
|
modifying or removing fields affects only the immediate table
|
||||||
|
mapped to that type. This mapping strategy provides the greatest
|
||||||
|
flexibility at design time, since changes to any type are always
|
||||||
|
limited to that type's dedicated table.
|
||||||
|
|
||||||
|
Performance impact
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This strategy inherently requires multiple JOIN operations to
|
||||||
|
perform just about any query which can have a negative impact on
|
||||||
|
performance, especially with large tables and/or large hierarchies.
|
||||||
|
When partial objects are allowed, either globally or on the
|
||||||
|
specific query, then querying for any type will not cause the
|
||||||
|
tables of subtypes to be OUTER JOINed which can increase
|
||||||
|
performance but the resulting partial objects will not fully load
|
||||||
|
themselves on access of any subtype fields, so accessing fields of
|
||||||
|
subtypes after such a query is not safe.
|
||||||
|
|
||||||
|
There is a general performance consideration with Class Table
|
||||||
|
Inheritance: If the target-entity of a many-to-one or one-to-one
|
||||||
|
association is a CTI entity, it is preferable for performance reasons that it
|
||||||
|
be a leaf entity in the inheritance hierarchy, (ie. have no subclasses).
|
||||||
|
Otherwise Doctrine *CANNOT* create proxy instances
|
||||||
|
of this entity and will *ALWAYS* load the entity eagerly.
|
||||||
|
|
||||||
|
SQL Schema considerations
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
For each entity in the Class-Table Inheritance hierarchy all the
|
||||||
|
mapped fields have to be columns on the table of this entity.
|
||||||
|
Additionally each child table has to have an id column that matches
|
||||||
|
the id column definition on the root table (except for any sequence
|
||||||
|
or auto-increment details). Furthermore each child table has to
|
||||||
|
have a foreign key pointing from the id column to the root table id
|
||||||
|
column and cascading on delete.
|
||||||
|
|
||||||
|
|
||||||
|
Overrides
|
||||||
|
---------
|
||||||
|
Used to override a mapping for an entity field or relationship.
|
||||||
|
May be applied to an entity that extends a mapped superclass
|
||||||
|
to override a relationship or field mapping defined by the mapped superclass.
|
||||||
|
|
||||||
|
|
||||||
|
Association Override
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
Override a mapping for an entity relationship.
|
||||||
|
|
||||||
|
Could be used by an entity that extends a mapped superclass
|
||||||
|
to override a relationship mapping defined by the mapped superclass.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. configuration-block::
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// user mapping
|
||||||
|
namespace MyProject\Model;
|
||||||
|
/**
|
||||||
|
* @MappedSuperclass
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
//other fields mapping
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToMany(targetEntity="Group", inversedBy="users")
|
||||||
|
* @JoinTable(name="users_groups",
|
||||||
|
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||||
|
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
protected $groups;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToOne(targetEntity="Address")
|
||||||
|
* @JoinColumn(name="address_id", referencedColumnName="id")
|
||||||
|
*/
|
||||||
|
protected $address;
|
||||||
|
}
|
||||||
|
|
||||||
|
// admin mapping
|
||||||
|
namespace MyProject\Model;
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @AssociationOverrides({
|
||||||
|
* @AssociationOverride(name="groups",
|
||||||
|
* joinTable=@JoinTable(
|
||||||
|
* name="users_admingroups",
|
||||||
|
* joinColumns=@JoinColumn(name="adminuser_id"),
|
||||||
|
* inverseJoinColumns=@JoinColumn(name="admingroup_id")
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
* @AssociationOverride(name="address",
|
||||||
|
* joinColumns=@JoinColumn(
|
||||||
|
* name="adminaddress_id", referencedColumnName="id"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class Admin extends User
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<!-- user mapping -->
|
||||||
|
<doctrine-mapping>
|
||||||
|
<mapped-superclass name="MyProject\Model\User">
|
||||||
|
<!-- other fields mapping -->
|
||||||
|
<many-to-many field="groups" target-entity="Group" inversed-by="users">
|
||||||
|
<cascade>
|
||||||
|
<cascade-persist/>
|
||||||
|
<cascade-merge/>
|
||||||
|
<cascade-detach/>
|
||||||
|
</cascade>
|
||||||
|
<join-table name="users_groups">
|
||||||
|
<join-columns>
|
||||||
|
<join-column name="user_id" referenced-column-name="id" />
|
||||||
|
</join-columns>
|
||||||
|
<inverse-join-columns>
|
||||||
|
<join-column name="group_id" referenced-column-name="id" />
|
||||||
|
</inverse-join-columns>
|
||||||
|
</join-table>
|
||||||
|
</many-to-many>
|
||||||
|
</mapped-superclass>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
<!-- admin mapping -->
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\Model\Admin">
|
||||||
|
<association-overrides>
|
||||||
|
<association-override name="groups">
|
||||||
|
<join-table name="users_admingroups">
|
||||||
|
<join-columns>
|
||||||
|
<join-column name="adminuser_id"/>
|
||||||
|
</join-columns>
|
||||||
|
<inverse-join-columns>
|
||||||
|
<join-column name="admingroup_id"/>
|
||||||
|
</inverse-join-columns>
|
||||||
|
</join-table>
|
||||||
|
</association-override>
|
||||||
|
<association-override name="address">
|
||||||
|
<join-columns>
|
||||||
|
<join-column name="adminaddress_id" referenced-column-name="id"/>
|
||||||
|
</join-columns>
|
||||||
|
</association-override>
|
||||||
|
</association-overrides>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// user mapping
|
||||||
|
namespace MyProject\Model;
|
||||||
|
/**
|
||||||
|
* @MappedSuperclass
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/** @Id @GeneratedValue @Column(type="integer", name="user_id", length=150) */
|
||||||
|
protected $id;
|
||||||
|
|
||||||
|
/** @Column(name="user_name", nullable=true, unique=false, length=250) */
|
||||||
|
protected $name;
|
||||||
|
|
||||||
|
// other fields mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
// guest mapping
|
||||||
|
namespace MyProject\Model;
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @AttributeOverrides({
|
||||||
|
* @AttributeOverride(name="id",
|
||||||
|
* column=@Column(
|
||||||
|
* name = "guest_id",
|
||||||
|
* type = "integer",
|
||||||
|
length = 140
|
||||||
|
* )
|
||||||
|
* ),
|
||||||
|
* @AttributeOverride(name="name",
|
||||||
|
* column=@Column(
|
||||||
|
* name = "guest_name",
|
||||||
|
* nullable = false,
|
||||||
|
* unique = true,
|
||||||
|
length = 240
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class Guest extends User
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<!-- user mapping -->
|
||||||
|
<doctrine-mapping>
|
||||||
|
<mapped-superclass name="MyProject\Model\User">
|
||||||
|
<id name="id" type="integer" column="user_id" length="150">
|
||||||
|
<generator strategy="AUTO"/>
|
||||||
|
</id>
|
||||||
|
<field name="name" column="user_name" type="string" length="250" nullable="true" unique="false" />
|
||||||
|
<many-to-one field="address" target-entity="Address">
|
||||||
|
<cascade>
|
||||||
|
<cascade-persist/>
|
||||||
|
<cascade-merge/>
|
||||||
|
</cascade>
|
||||||
|
<join-column name="address_id" referenced-column-name="id"/>
|
||||||
|
</many-to-one>
|
||||||
|
<!-- other fields mapping -->
|
||||||
|
</mapped-superclass>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
<!-- admin mapping -->
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\Model\Guest">
|
||||||
|
<attribute-overrides>
|
||||||
|
<attribute-override name="id">
|
||||||
|
<field column="guest_id" length="140"/>
|
||||||
|
</attribute-override>
|
||||||
|
<attribute-override name="name">
|
||||||
|
<field column="guest_name" type="string" length="240" nullable="false" unique="true" />
|
||||||
|
</attribute-override>
|
||||||
|
</attribute-overrides>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
.. 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.
|
93
docs/en/reference/installation.rst
Normal file
93
docs/en/reference/installation.rst
Normal file
@ -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 <http://www.getcomposer.org>`_ 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 <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-<version>
|
||||||
|
|
||||||
|
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-<version>
|
||||||
|
|
||||||
|
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-<version>
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``<version>`` 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.
|
189
docs/en/reference/limitations-and-known-issues.rst
Normal file
189
docs/en/reference/limitations-and-known-issues.rst
Normal file
@ -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 <http://www.doctrine-project.org/jira/browse/DDC-298>`_.
|
||||||
|
|
||||||
|
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 <http://www.doctrine-project.org/jira/browse/DDC-93>`_.
|
||||||
|
|
||||||
|
|
||||||
|
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 <http://www.doctrine-project.org/jira/browse/DDC-875>`_ Merge can sometimes add the same entity twice into a collection
|
||||||
|
- `DDC-763 <http://www.doctrine-project.org/jira/browse/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 <http://www.doctrine-project.org/jira/browse/DDC-668>`_
|
||||||
|
- `Evaluate possible ways in which stored-procedures can be used <http://www.doctrine-project.org/jira/browse/DDC-445>`_
|
||||||
|
- 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 <http://www.doctrine-project.org/jira/browse/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 <http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell>`_
|
||||||
|
- `A re-usable Versionable behavior for Doctrine2 <http://www.doctrine-project.org/blog/doctrine2-versionable>`_
|
||||||
|
- `Write your own ORM on top of Doctrine2 <http://www.doctrine-project.org/blog/your-own-orm-doctrine2>`_
|
||||||
|
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
|
||||||
|
- `Doctrator <https://github.com/pablodip/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 <http://github.com/guilhermeblanco/Doctrine2-Hierarchical-Structural-Behavior>`_
|
||||||
|
- `Doctrine2 NestedSet <http://github.com/blt04/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
|
||||||
|
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
|
||||||
|
|
||||||
|
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 <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).
|
194
docs/en/reference/metadata-drivers.rst
Normal file
194
docs/en/reference/metadata-drivers.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver('/path/to/mapping/files');
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Doctrine\ORM\Mapping\Driver;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadataInfo;
|
||||||
|
|
||||||
|
interface Driver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Loads the metadata for the specified class into the provided container.
|
||||||
|
*
|
||||||
|
* @param string $className
|
||||||
|
* @param ClassMetadataInfo $metadata
|
||||||
|
*/
|
||||||
|
function loadMetadataForClass($className, ClassMetadataInfo $metadata);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the names of all mapped classes known to this driver.
|
||||||
|
*
|
||||||
|
* @return array The names of all mapped classes known to this driver.
|
||||||
|
*/
|
||||||
|
function getAllClassNames();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the class with the specified name should have its metadata loaded.
|
||||||
|
* This is only the case if it is either mapped as an Entity or a
|
||||||
|
* MappedSuperclass.
|
||||||
|
*
|
||||||
|
* @param string $className
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
function isTransient($className);
|
||||||
|
}
|
||||||
|
|
||||||
|
If you want to write a metadata driver to parse information from
|
||||||
|
some file format we've made your life a little easier by providing
|
||||||
|
the ``AbstractFileDriver`` implementation for you to extend from:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MyMetadataDriver extends AbstractFileDriver
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected $_fileExtension = '.dcm.ext';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function loadMetadataForClass($className, ClassMetadataInfo $metadata)
|
||||||
|
{
|
||||||
|
$data = $this->_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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$driver = new MyMetadataDriver('/path/to/mapping/files');
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cmf = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
foreach ($class->fieldMappings as $fieldMapping) {
|
||||||
|
echo $fieldMapping['fieldName'] . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
148
docs/en/reference/namingstrategy.rst
Normal file
148
docs/en/reference/namingstrategy.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$namingStrategy = new MyNamingStrategy();
|
||||||
|
$configuration()->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$namingStrategy = new \Doctrine\ORM\Mapping\UnderscoreNamingStrategy(CASE_UPPER);
|
||||||
|
$configuration()->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Return a table name for an entity class
|
||||||
|
*
|
||||||
|
* @param string $className The fully-qualified class name
|
||||||
|
* @return string A table name
|
||||||
|
*/
|
||||||
|
function classToTableName($className);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a column name for a property
|
||||||
|
*
|
||||||
|
* @param string $propertyName A property
|
||||||
|
* @return string A column name
|
||||||
|
*/
|
||||||
|
function propertyToColumnName($propertyName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the default reference column name
|
||||||
|
*
|
||||||
|
* @return string A column name
|
||||||
|
*/
|
||||||
|
function referenceColumnName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a join column name for a property
|
||||||
|
*
|
||||||
|
* @param string $propertyName A property
|
||||||
|
* @return string A join column name
|
||||||
|
*/
|
||||||
|
function joinColumnName($propertyName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a join table name
|
||||||
|
*
|
||||||
|
* @param string $sourceEntity The source entity
|
||||||
|
* @param string $targetEntity The target entity
|
||||||
|
* @param string $propertyName A property
|
||||||
|
* @return string A join table name
|
||||||
|
*/
|
||||||
|
function joinTableName($sourceEntity, $targetEntity, $propertyName = null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the foreign key column name for the given parameters
|
||||||
|
*
|
||||||
|
* @param string $entityName A entity
|
||||||
|
* @param string $referencedColumnName A property
|
||||||
|
* @return string A join column name
|
||||||
|
*/
|
||||||
|
function joinKeyColumnName($entityName, $referencedColumnName = null);
|
||||||
|
|
||||||
|
Implementing a naming strategy
|
||||||
|
-------------------------------
|
||||||
|
If you have database naming standards like all tables names should be prefixed
|
||||||
|
by the application prefix, all column names should be upper case,
|
||||||
|
you can easily achieve such standards by implementing a naming strategy.
|
||||||
|
You need to implements NamingStrategy first. Following is an example
|
||||||
|
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class MyAppNamingStrategy implements NamingStrategy
|
||||||
|
{
|
||||||
|
public function classToTableName($className)
|
||||||
|
{
|
||||||
|
return 'MyApp_' . substr($className, strrpos($className, '\\') + 1);
|
||||||
|
}
|
||||||
|
public function propertyToColumnName($propertyName)
|
||||||
|
{
|
||||||
|
return $propertyName;
|
||||||
|
}
|
||||||
|
public function referenceColumnName()
|
||||||
|
{
|
||||||
|
return 'id';
|
||||||
|
}
|
||||||
|
public function joinColumnName($propertyName)
|
||||||
|
{
|
||||||
|
return $propertyName . '_' . $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$namingStrategy = new MyAppNamingStrategy();
|
||||||
|
$configuration()->setNamingStrategy($namingStrategy);
|
870
docs/en/reference/native-sql.rst
Normal file
870
docs/en/reference/native-sql.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Adds an entity result to this ResultSetMapping.
|
||||||
|
*
|
||||||
|
* @param string $class The class name of the entity.
|
||||||
|
* @param string $alias The alias for the class. The alias must be unique among all entity
|
||||||
|
* results or joined entity results within this ResultSetMapping.
|
||||||
|
*/
|
||||||
|
public function addEntityResult($class, $alias)
|
||||||
|
|
||||||
|
The first parameter is the fully qualified name of the entity
|
||||||
|
class. The second parameter is some arbitrary alias for this entity
|
||||||
|
result that must be unique within a ``ResultSetMapping``. You use
|
||||||
|
this alias to attach field results to the entity result. It is very
|
||||||
|
similar to an identification variable that you use in DQL to alias
|
||||||
|
classes or relationships.
|
||||||
|
|
||||||
|
An entity result alone is not enough to form a valid
|
||||||
|
``ResultSetMapping``. An entity result or joined entity result
|
||||||
|
always needs a set of field results, which we will look at soon.
|
||||||
|
|
||||||
|
Joined entity results
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A joined entity result describes an entity type that appears as a
|
||||||
|
joined relationship element in the transformed result, attached to
|
||||||
|
a (root) entity result. You add a joined entity result through
|
||||||
|
``ResultSetMapping#addJoinedEntityResult()``. Let's take a look at
|
||||||
|
the method signature in detail:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Adds a joined entity result.
|
||||||
|
*
|
||||||
|
* @param string $class The class name of the joined entity.
|
||||||
|
* @param string $alias The unique alias to use for the joined entity.
|
||||||
|
* @param string $parentAlias The alias of the entity result that is the parent of this joined result.
|
||||||
|
* @param object $relation The association field that connects the parent entity result with the joined entity result.
|
||||||
|
*/
|
||||||
|
public function addJoinedEntityResult($class, $alias, $parentAlias, $relation)
|
||||||
|
|
||||||
|
The first parameter is the class name of the joined entity. The
|
||||||
|
second parameter is an arbitrary alias for the joined entity that
|
||||||
|
must be unique within the ``ResultSetMapping``. You use this alias
|
||||||
|
to attach field results to the entity result. The third parameter
|
||||||
|
is the alias of the entity result that is the parent type of the
|
||||||
|
joined relationship. The fourth and last parameter is the name of
|
||||||
|
the field on the parent entity result that should contain the
|
||||||
|
joined entity result.
|
||||||
|
|
||||||
|
Field results
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A field result describes the mapping of a single column in an SQL
|
||||||
|
result set to a field in an entity. As such, field results are
|
||||||
|
inherently bound to entity results. You add a field result through
|
||||||
|
``ResultSetMapping#addFieldResult()``. Again, let's examine the
|
||||||
|
method signature in detail:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Adds a field result that is part of an entity result or joined entity result.
|
||||||
|
*
|
||||||
|
* @param string $alias The alias of the entity result or joined entity result.
|
||||||
|
* @param string $columnName The name of the column in the SQL result set.
|
||||||
|
* @param string $fieldName The name of the field on the (joined) entity.
|
||||||
|
*/
|
||||||
|
public function addFieldResult($alias, $columnName, $fieldName)
|
||||||
|
|
||||||
|
The first parameter is the alias of the entity result to which the
|
||||||
|
field result will belong. The second parameter is the name of the
|
||||||
|
column in the SQL result set. Note that this name is case
|
||||||
|
sensitive, i.e. if you use a native query against Oracle it must be
|
||||||
|
all uppercase. The third parameter is the name of the field on the
|
||||||
|
entity result identified by ``$alias`` into which the value of the
|
||||||
|
column should be set.
|
||||||
|
|
||||||
|
Scalar results
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A scalar result describes the mapping of a single column in an SQL
|
||||||
|
result set to a scalar value in the Doctrine result. Scalar results
|
||||||
|
are typically used for aggregate values but any column in the SQL
|
||||||
|
result set can be mapped as a scalar value. To add a scalar result
|
||||||
|
use ``ResultSetMapping#addScalarResult()``. The method signature in
|
||||||
|
detail:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Adds a scalar result mapping.
|
||||||
|
*
|
||||||
|
* @param string $columnName The name of the column in the SQL result set.
|
||||||
|
* @param string $alias The result alias with which the scalar result should be placed in the result structure.
|
||||||
|
*/
|
||||||
|
public function addScalarResult($columnName, $alias)
|
||||||
|
|
||||||
|
The first parameter is the name of the column in the SQL result set
|
||||||
|
and the second parameter is the result alias under which the value
|
||||||
|
of the column will be placed in the transformed Doctrine result.
|
||||||
|
|
||||||
|
Meta results
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A meta result describes a single column in an SQL result set that
|
||||||
|
is either a foreign key or a discriminator column. These columns
|
||||||
|
are essential for Doctrine to properly construct objects out of SQL
|
||||||
|
result sets. To add a column as a meta result use
|
||||||
|
``ResultSetMapping#addMetaResult()``. The method signature in
|
||||||
|
detail:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Adds a meta column (foreign key or discriminator column) to the result set.
|
||||||
|
*
|
||||||
|
* @param string $alias
|
||||||
|
* @param string $columnAlias
|
||||||
|
* @param string $columnName
|
||||||
|
*/
|
||||||
|
public function addMetaResult($alias, $columnAlias, $columnName)
|
||||||
|
|
||||||
|
The first parameter is the alias of the entity result to which the
|
||||||
|
meta column belongs. A meta result column (foreign key or
|
||||||
|
discriminator column) always belongs to to an entity result. The
|
||||||
|
second parameter is the column alias/name of the column in the SQL
|
||||||
|
result set and the third parameter is the column name used in the
|
||||||
|
mapping.
|
||||||
|
|
||||||
|
Discriminator Column
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When joining an inheritance tree you have to give Doctrine a hint
|
||||||
|
which meta-column is the discriminator column of this tree.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Sets a discriminator column for an entity result or joined entity result.
|
||||||
|
* The discriminator column will be used to determine the concrete class name to
|
||||||
|
* instantiate.
|
||||||
|
*
|
||||||
|
* @param string $alias The alias of the entity result or joined entity result the discriminator
|
||||||
|
* column should be used for.
|
||||||
|
* @param string $discrColumn The name of the discriminator column in the SQL result set.
|
||||||
|
*/
|
||||||
|
public function setDiscriminatorColumn($alias, $discrColumn)
|
||||||
|
|
||||||
|
Examples
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
Understanding a ResultSetMapping is probably easiest through
|
||||||
|
looking at some examples.
|
||||||
|
|
||||||
|
First a basic example that describes the mapping of a single
|
||||||
|
entity.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Equivalent DQL query: "select u from User u where u.name=?1"
|
||||||
|
// User owns no associations.
|
||||||
|
$rsm = new ResultSetMapping;
|
||||||
|
$rsm->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Equivalent DQL query: "select u from User u where u.name=?1"
|
||||||
|
// User owns an association to an Address but the Address is not loaded in the query.
|
||||||
|
$rsm = new ResultSetMapping;
|
||||||
|
$rsm->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Equivalent DQL query: "select u from User u join u.address a WHERE u.name = ?1"
|
||||||
|
// User owns association to an Address and the Address is loaded in the query.
|
||||||
|
$rsm = new ResultSetMapping;
|
||||||
|
$rsm->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Equivalent DQL query: "select u from User u where u.name=?1"
|
||||||
|
// User is a mapped base class for other classes. User owns no associations.
|
||||||
|
$rsm = new ResultSetMapping;
|
||||||
|
$rsm->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\ORM\Query\ResultSetMappingBuilder;
|
||||||
|
|
||||||
|
$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";
|
||||||
|
|
||||||
|
$rsm = new ResultSetMappingBuilder($entityManager);
|
||||||
|
$rsm->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
/**
|
||||||
|
* @NamedNativeQueries({
|
||||||
|
* @NamedNativeQuery(
|
||||||
|
* 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({
|
||||||
|
* @SqlResultSetMapping(
|
||||||
|
* name = "mappingMultipleJoinsEntityResults",
|
||||||
|
* entities= {
|
||||||
|
* @EntityResult(
|
||||||
|
* entityClass = "__CLASS__",
|
||||||
|
* fields = {
|
||||||
|
* @FieldResult(name = "id", column="u_id"),
|
||||||
|
* @FieldResult(name = "name", column="u_name"),
|
||||||
|
* @FieldResult(name = "status", column="u_status"),
|
||||||
|
* }
|
||||||
|
* ),
|
||||||
|
* @EntityResult(
|
||||||
|
* entityClass = "Address",
|
||||||
|
* fields = {
|
||||||
|
* @FieldResult(name = "id", column="a_id"),
|
||||||
|
* @FieldResult(name = "zip", column="a_zip"),
|
||||||
|
* @FieldResult(name = "country", column="a_country"),
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* },
|
||||||
|
* columns = {
|
||||||
|
* @ColumnResult("numphones")
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
*})
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/** @Column(type="string", length=50, nullable=true) */
|
||||||
|
public $status;
|
||||||
|
|
||||||
|
/** @Column(type="string", length=255, unique=true) */
|
||||||
|
public $username;
|
||||||
|
|
||||||
|
/** @Column(type="string", length=255) */
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/** @OneToMany(targetEntity="Phonenumber") */
|
||||||
|
public $phonenumbers;
|
||||||
|
|
||||||
|
/** @OneToOne(targetEntity="Address") */
|
||||||
|
public $address;
|
||||||
|
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\Model\User">
|
||||||
|
<named-native-queries>
|
||||||
|
<named-native-query name="fetchMultipleJoinsEntityResults" result-set-mapping="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</query>
|
||||||
|
</named-native-query>
|
||||||
|
</named-native-queries>
|
||||||
|
<sql-result-set-mappings>
|
||||||
|
<sql-result-set-mapping name="mappingMultipleJoinsEntityResults">
|
||||||
|
<entity-result entity-class="__CLASS__">
|
||||||
|
<field-result name="id" column="u_id"/>
|
||||||
|
<field-result name="name" column="u_name"/>
|
||||||
|
<field-result name="status" column="u_status"/>
|
||||||
|
</entity-result>
|
||||||
|
<entity-result entity-class="Address">
|
||||||
|
<field-result name="id" column="a_id"/>
|
||||||
|
<field-result name="zip" column="a_zip"/>
|
||||||
|
<field-result name="country" column="a_country"/>
|
||||||
|
</entity-result>
|
||||||
|
<column-result name="numphones"/>
|
||||||
|
</sql-result-set-mapping>
|
||||||
|
</sql-result-set-mappings>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @NamedNativeQueries({
|
||||||
|
* @NamedNativeQuery(
|
||||||
|
* name = "findAll",
|
||||||
|
* resultSetMapping = "mappingFindAll",
|
||||||
|
* query = "SELECT * FROM addresses"
|
||||||
|
* ),
|
||||||
|
* })
|
||||||
|
* @SqlResultSetMappings({
|
||||||
|
* @SqlResultSetMapping(
|
||||||
|
* name = "mappingFindAll",
|
||||||
|
* entities= {
|
||||||
|
* @EntityResult(
|
||||||
|
* entityClass = "Address"
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class Address
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/** @Column() */
|
||||||
|
public $country;
|
||||||
|
|
||||||
|
/** @Column() */
|
||||||
|
public $zip;
|
||||||
|
|
||||||
|
/** @Column()*/
|
||||||
|
public $city;
|
||||||
|
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\Model\Address">
|
||||||
|
<named-native-queries>
|
||||||
|
<named-native-query name="findAll" result-set-mapping="mappingFindAll">
|
||||||
|
<query>SELECT * FROM addresses</query>
|
||||||
|
</named-native-query>
|
||||||
|
</named-native-queries>
|
||||||
|
<sql-result-set-mappings>
|
||||||
|
<sql-result-set-mapping name="mappingFindAll">
|
||||||
|
<entity-result entity-class="Address"/>
|
||||||
|
</sql-result-set-mapping>
|
||||||
|
</sql-result-set-mappings>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @NamedNativeQueries({
|
||||||
|
* @NamedNativeQuery(
|
||||||
|
* 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({
|
||||||
|
* @SqlResultSetMapping(
|
||||||
|
* name = "mappingJoinedAddress",
|
||||||
|
* entities= {
|
||||||
|
* @EntityResult(
|
||||||
|
* entityClass = "__CLASS__",
|
||||||
|
* fields = {
|
||||||
|
* @FieldResult(name = "id"),
|
||||||
|
* @FieldResult(name = "name"),
|
||||||
|
* @FieldResult(name = "status"),
|
||||||
|
* @FieldResult(name = "address.id", column = "a_id"),
|
||||||
|
* @FieldResult(name = "address.zip", column = "a_zip"),
|
||||||
|
* @FieldResult(name = "address.city", column = "a_city"),
|
||||||
|
* @FieldResult(name = "address.country", column = "a_country"),
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
public $id;
|
||||||
|
|
||||||
|
/** @Column(type="string", length=50, nullable=true) */
|
||||||
|
public $status;
|
||||||
|
|
||||||
|
/** @Column(type="string", length=255, unique=true) */
|
||||||
|
public $username;
|
||||||
|
|
||||||
|
/** @Column(type="string", length=255) */
|
||||||
|
public $name;
|
||||||
|
|
||||||
|
/** @OneToOne(targetEntity="Address") */
|
||||||
|
public $address;
|
||||||
|
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\Model\User">
|
||||||
|
<named-native-queries>
|
||||||
|
<named-native-query name="fetchJoinedAddress" result-set-mapping="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 = ?</query>
|
||||||
|
</named-native-query>
|
||||||
|
</named-native-queries>
|
||||||
|
<sql-result-set-mappings>
|
||||||
|
<sql-result-set-mapping name="mappingJoinedAddress">
|
||||||
|
<entity-result entity-class="__CLASS__">
|
||||||
|
<field-result name="id"/>
|
||||||
|
<field-result name="name"/>
|
||||||
|
<field-result name="status"/>
|
||||||
|
<field-result name="address.id" column="a_id"/>
|
||||||
|
<field-result name="address.zip" column="a_zip"/>
|
||||||
|
<field-result name="address.city" column="a_city"/>
|
||||||
|
<field-result name="address.country" column="a_country"/>
|
||||||
|
</entity-result>
|
||||||
|
</sql-result-set-mapping>
|
||||||
|
</sql-result-set-mappings>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @NamedNativeQueries({
|
||||||
|
* @NamedNativeQuery(
|
||||||
|
* name = "find-by-id",
|
||||||
|
* resultClass = "Address",
|
||||||
|
* query = "SELECT * FROM addresses"
|
||||||
|
* ),
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class Address
|
||||||
|
{
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\Model\Address">
|
||||||
|
<named-native-queries>
|
||||||
|
<named-native-query name="find-by-id" result-class="Address">
|
||||||
|
<query>SELECT * FROM addresses WHERE id = ?</query>
|
||||||
|
</named-native-query>
|
||||||
|
</named-native-queries>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyProject\Model;
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @NamedNativeQueries({
|
||||||
|
* @NamedNativeQuery(
|
||||||
|
* name = "count",
|
||||||
|
* resultSetMapping= "mappingCount",
|
||||||
|
* query = "SELECT COUNT(*) AS count FROM addresses"
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
* @SqlResultSetMappings({
|
||||||
|
* @SqlResultSetMapping(
|
||||||
|
* name = "mappingCount",
|
||||||
|
* columns = {
|
||||||
|
* @ColumnResult(
|
||||||
|
* name = "count"
|
||||||
|
* )
|
||||||
|
* }
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class Address
|
||||||
|
{
|
||||||
|
// ....
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\Model\Address">
|
||||||
|
<named-native-query name="count" result-set-mapping="mappingCount">
|
||||||
|
<query>SELECT COUNT(*) AS count FROM addresses</query>
|
||||||
|
</named-native-query>
|
||||||
|
<sql-result-set-mappings>
|
||||||
|
<sql-result-set-mapping name="mappingCount">
|
||||||
|
<column-result name="count"/>
|
||||||
|
</sql-result-set-mapping>
|
||||||
|
</sql-result-set-mappings>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
.. 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
|
90
docs/en/reference/partial-objects.rst
Normal file
90
docs/en/reference/partial-objects.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$q = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$reference = $em->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.
|
||||||
|
|
||||||
|
|
310
docs/en/reference/php-mapping.rst
Normal file
310
docs/en/reference/php-mapping.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$driver = new PHPDriver('/path/to/php/mapping/files');
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Entities;
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
private $id;
|
||||||
|
private $username;
|
||||||
|
}
|
||||||
|
|
||||||
|
To write the mapping information you just need to create a file
|
||||||
|
named ``Entities.User.php`` inside of the
|
||||||
|
``/path/to/php/mapping/files`` folder:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// /path/to/php/mapping/files/Entities.User.php
|
||||||
|
|
||||||
|
$metadata->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$class = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$driver = new StaticPHPDriver('/path/to/entities');
|
||||||
|
$em->getConfiguration()->setMetadataDriverImpl($driver);
|
||||||
|
|
||||||
|
Now you just need to define a static function named
|
||||||
|
``loadMetadata($metadata)`` on your entity:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Entities;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
public static function loadMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
$metadata->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Entities;
|
||||||
|
|
||||||
|
use Doctrine\ORM\Mapping\ClassMetadata;
|
||||||
|
use Doctrine\ORM\Mapping\Builder\ClassMetadataBuilder;
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
public static function loadMetadata(ClassMetadata $metadata)
|
||||||
|
{
|
||||||
|
$builder = new ClassMetadataBuilder($metadata);
|
||||||
|
$builder->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)``
|
||||||
|
|
||||||
|
|
541
docs/en/reference/query-builder.rst
Normal file
541
docs/en/reference/query-builder.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
|
||||||
|
// example1: creating a QueryBuilder instance
|
||||||
|
$qb = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
// example2: retrieving type of QueryBuilder
|
||||||
|
echo $qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
// example3: retrieve the associated EntityManager
|
||||||
|
$em = $qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class QueryBuilder
|
||||||
|
{
|
||||||
|
// Example - $qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
// Query here...
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
// See example above
|
||||||
|
$params = $qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
$offset = (int)$_GET['offset'];
|
||||||
|
$limit = (int)$_GET['limit'];
|
||||||
|
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
$query = $qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
// example8: QueryBuilder port of: "SELECT u FROM User u WHERE u.id = ? OR u.nickname LIKE ? ORDER BY u.surname DESC" using Expr class
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Expr
|
||||||
|
{
|
||||||
|
/** Conditional objects **/
|
||||||
|
|
||||||
|
// Example - $qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
// example6: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder string support
|
||||||
|
$qb->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $qb instanceof QueryBuilder
|
||||||
|
|
||||||
|
// example7: how to define: "SELECT u FROM User u WHERE u.id = ? ORDER BY u.name ASC" using QueryBuilder using Expr\* instances
|
||||||
|
$qb->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.
|
||||||
|
|
526
docs/en/reference/tools.rst
Normal file
526
docs/en/reference/tools.rst
Normal file
@ -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 <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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||||
|
'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($conn)
|
||||||
|
));
|
||||||
|
$cli->setHelperSet($helperSet);
|
||||||
|
|
||||||
|
When dealing with the ORM package, the EntityManagerHelper is
|
||||||
|
required:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||||
|
'em' => 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// cli-config.php
|
||||||
|
require_once 'my_bootstrap.php';
|
||||||
|
|
||||||
|
// Any way to access the EntityManager from your application
|
||||||
|
$em = GetMyEntityManager();
|
||||||
|
|
||||||
|
$helperSet = new \Symfony\Component\Console\Helper\HelperSet(array(
|
||||||
|
'db' => 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// doctrine.php - Put in your application root
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper;
|
||||||
|
use Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper;
|
||||||
|
use Doctrine\ORM\Tools\Console\ConsoleRunner;
|
||||||
|
use Symfony\Component\Console\Helper\HelperSet;
|
||||||
|
|
||||||
|
require_once 'my_bootstrap.php';
|
||||||
|
|
||||||
|
// Any way to access the EntityManager from your application
|
||||||
|
$em = GetMyEntityManager();
|
||||||
|
|
||||||
|
$helperSet = new HelperSet(array(
|
||||||
|
'db' => 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$tool = new \Doctrine\ORM\Tools\SchemaTool($em);
|
||||||
|
$classes = array(
|
||||||
|
$em->getClassMetadata('Entities\User'),
|
||||||
|
$em->getClassMetadata('Entities\Profile')
|
||||||
|
);
|
||||||
|
$tool->createSchema($classes);
|
||||||
|
|
||||||
|
To drop the schema you can use the ``dropSchema()`` method.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$tool->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$tool->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$tool->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$cme = new \Doctrine\ORM\Tools\Export\ClassMetadataExporter();
|
||||||
|
|
||||||
|
Once you have a instance you can use it to get an exporter. For
|
||||||
|
example, the yml exporter:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$exporter = $cme->getExporter('yml', '/path/to/export/yml');
|
||||||
|
|
||||||
|
Now you can export some ``ClassMetadata`` instances:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$classes = array(
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$exporter = $cme->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\Tools\SchemaValidator;
|
||||||
|
|
||||||
|
$validator = new SchemaValidator($entityManager);
|
||||||
|
$errors = $validator->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// doctrine.php
|
||||||
|
use Symfony\Component\Console\Helper\Application;
|
||||||
|
|
||||||
|
// as before ...
|
||||||
|
|
||||||
|
// replace the ConsoleRunner::run() statement with:
|
||||||
|
$cli = new Application('Doctrine Command Line Interface', \Doctrine\ORM\Version::VERSION);
|
||||||
|
$cli->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$cli->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(),
|
||||||
|
));
|
353
docs/en/reference/transactions-and-concurrency.rst
Normal file
353
docs/en/reference/transactions-and-concurrency.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
$user = new User;
|
||||||
|
$user->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
/** @Version @Column(type="integer") */
|
||||||
|
private $version;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Alternatively a datetime type can be used (which maps to an SQL
|
||||||
|
timestamp or datetime):
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
/** @Version @Column(type="datetime") */
|
||||||
|
private $version;
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Version numbers (not timestamps) should however be preferred as
|
||||||
|
they can not potentially conflict in a highly concurrent
|
||||||
|
environment, unlike timestamps where this is a possibility,
|
||||||
|
depending on the resolution of the timestamp on the particular
|
||||||
|
database platform.
|
||||||
|
|
||||||
|
When a version conflict is encountered during
|
||||||
|
``EntityManager#flush()``, an ``OptimisticLockException`` is thrown
|
||||||
|
and the active transaction rolled back (or marked for rollback).
|
||||||
|
This exception can be caught and handled. Potential responses to an
|
||||||
|
OptimisticLockException are to present the conflict to the user or
|
||||||
|
to refresh or reload objects in a new transaction and then retrying
|
||||||
|
the transaction.
|
||||||
|
|
||||||
|
With PHP promoting a share-nothing architecture, the time between
|
||||||
|
showing an update form and actually modifying the entity can in the
|
||||||
|
worst scenario be as long as your applications session timeout. If
|
||||||
|
changes happen to the entity in that time frame you want to know
|
||||||
|
directly when retrieving the entity that you will hit an optimistic
|
||||||
|
locking exception:
|
||||||
|
|
||||||
|
You can always verify the version of an entity during a request
|
||||||
|
either when calling ``EntityManager#find()``:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\DBAL\LockMode;
|
||||||
|
use Doctrine\ORM\OptimisticLockException;
|
||||||
|
|
||||||
|
$theEntityId = 1;
|
||||||
|
$expectedVersion = 184;
|
||||||
|
|
||||||
|
try {
|
||||||
|
$entity = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\DBAL\LockMode;
|
||||||
|
use Doctrine\ORM\OptimisticLockException;
|
||||||
|
|
||||||
|
$theEntityId = 1;
|
||||||
|
$expectedVersion = 184;
|
||||||
|
|
||||||
|
$entity = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$post = $em->find('BlogPost', 123456);
|
||||||
|
|
||||||
|
echo '<input type="hidden" name="id" value="' . $post->getId() . '" />';
|
||||||
|
echo '<input type="hidden" name="version" value="' . $post->getCurrentVersion() . '" />';
|
||||||
|
|
||||||
|
And the change headline action (POST Request):
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$postId = (int)$_GET['id'];
|
||||||
|
$postVersion = (int)$_GET['version'];
|
||||||
|
|
||||||
|
$post = $em->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)``
|
||||||
|
|
||||||
|
|
60
docs/en/reference/unitofwork-associations.rst
Normal file
60
docs/en/reference/unitofwork-associations.rst
Normal file
@ -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.
|
||||||
|
|
201
docs/en/reference/unitofwork.rst
Normal file
201
docs/en/reference/unitofwork.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$user = $entityManager->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 <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
|
||||||
|
|
712
docs/en/reference/working-with-associations.rst
Normal file
712
docs/en/reference/working-with-associations.rst
Normal file
@ -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 <unitofwork-associations>`
|
||||||
|
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 <architecture_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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/** @Id @GeneratedValue @Column(type="string") */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bidirectional - Many users have Many favorite comments (OWNING SIDE)
|
||||||
|
*
|
||||||
|
* @ManyToMany(targetEntity="Comment", inversedBy="userFavorites")
|
||||||
|
* @JoinTable(name="user_favorite_comments",
|
||||||
|
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||||
|
* inverseJoinColumns={@JoinColumn(name="favorite_comment_id", referencedColumnName="id")}
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
private $favorites;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unidirectional - Many users have marked many comments as read
|
||||||
|
*
|
||||||
|
* @ManyToMany(targetEntity="Comment")
|
||||||
|
* @JoinTable(name="user_read_comments",
|
||||||
|
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
|
||||||
|
* inverseJoinColumns={@JoinColumn(name="comment_id", referencedColumnName="id")}
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
private $commentsRead;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||||
|
*
|
||||||
|
* @OneToMany(targetEntity="Comment", mappedBy="author")
|
||||||
|
*/
|
||||||
|
private $commentsAuthored;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unidirectional - Many-To-One
|
||||||
|
*
|
||||||
|
* @ManyToOne(targetEntity="Comment")
|
||||||
|
*/
|
||||||
|
private $firstComment;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @Entity */
|
||||||
|
class Comment
|
||||||
|
{
|
||||||
|
/** @Id @GeneratedValue @Column(type="string") */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bidirectional - Many comments are favorited by many users (INVERSE SIDE)
|
||||||
|
*
|
||||||
|
* @ManyToMany(targetEntity="User", mappedBy="favorites")
|
||||||
|
*/
|
||||||
|
private $userFavorites;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bidirectional - Many Comments are authored by one user (OWNING SIDE)
|
||||||
|
*
|
||||||
|
* @ManyToOne(targetEntity="User", inversedBy="authoredComments")
|
||||||
|
*/
|
||||||
|
private $author;
|
||||||
|
}
|
||||||
|
|
||||||
|
This two entities generate the following MySQL Schema (Foreign Key
|
||||||
|
definitions omitted):
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
CREATE TABLE User (
|
||||||
|
id VARCHAR(255) NOT NULL,
|
||||||
|
firstComment_id VARCHAR(255) DEFAULT NULL,
|
||||||
|
PRIMARY KEY(id)
|
||||||
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
CREATE TABLE Comment (
|
||||||
|
id VARCHAR(255) NOT NULL,
|
||||||
|
author_id VARCHAR(255) DEFAULT NULL,
|
||||||
|
PRIMARY KEY(id)
|
||||||
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
CREATE TABLE user_favorite_comments (
|
||||||
|
user_id VARCHAR(255) NOT NULL,
|
||||||
|
favorite_comment_id VARCHAR(255) NOT NULL,
|
||||||
|
PRIMARY KEY(user_id, favorite_comment_id)
|
||||||
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
CREATE TABLE user_read_comments (
|
||||||
|
user_id VARCHAR(255) NOT NULL,
|
||||||
|
comment_id VARCHAR(255) NOT NULL,
|
||||||
|
PRIMARY KEY(user_id, comment_id)
|
||||||
|
) ENGINE = InnoDB;
|
||||||
|
|
||||||
|
Establishing Associations
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Establishing an association between two entities is
|
||||||
|
straight-forward. Here are some examples for the unidirectional
|
||||||
|
relations of the ``User``:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
public function getReadComments() {
|
||||||
|
return $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$user = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ..
|
||||||
|
|
||||||
|
public function getAuthoredComments() {
|
||||||
|
return $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// Remove by Elements
|
||||||
|
$user->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
public function markCommentRead(Comment $comment) {
|
||||||
|
// Collections implement ArrayAccess
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User {
|
||||||
|
public function getReadComments() {
|
||||||
|
return $this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$user->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$user = new User();
|
||||||
|
$myFirstComment = new Comment();
|
||||||
|
$user->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
//...
|
||||||
|
/**
|
||||||
|
* Bidirectional - One-To-Many (INVERSE SIDE)
|
||||||
|
*
|
||||||
|
* @OneToMany(targetEntity="Comment", mappedBy="author", cascade={"persist", "remove"})
|
||||||
|
*/
|
||||||
|
private $commentsAuthored;
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
Even though automatic cascading is convenient it should be used
|
||||||
|
with care. Do not blindly apply cascade=all to all associations as
|
||||||
|
it will unnecessarily degrade the performance of your application.
|
||||||
|
For each cascade operation that gets activated Doctrine also
|
||||||
|
applies that operation to the association, be it single or
|
||||||
|
collection valued.
|
||||||
|
|
||||||
|
Persistence by Reachability: Cascade Persist
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
There are additional semantics that apply to the Cascade Persist
|
||||||
|
operation. During each flush() operation Doctrine detects if there
|
||||||
|
are new entities in any collection and three possible cases can
|
||||||
|
happen:
|
||||||
|
|
||||||
|
|
||||||
|
1. New entities in a collection marked as cascade persist will be
|
||||||
|
directly persisted by Doctrine.
|
||||||
|
2. New entities in a collection not marked as cascade persist will
|
||||||
|
produce an Exception and rollback the flush() operation.
|
||||||
|
3. Collections without new entities are skipped.
|
||||||
|
|
||||||
|
This concept is called Persistence by Reachability: New entities
|
||||||
|
that are found on already managed entities are automatically
|
||||||
|
persisted as long as the association is defined as cascade
|
||||||
|
persist.
|
||||||
|
|
||||||
|
Orphan Removal
|
||||||
|
--------------
|
||||||
|
|
||||||
|
There is another concept of cascading that is relevant only when removing entities
|
||||||
|
from collections. If an Entity of type ``A`` contains references to privately
|
||||||
|
owned Entities ``B`` then if the reference from ``A`` to ``B`` is removed the
|
||||||
|
entity ``B`` should also be removed, because it is not used anymore.
|
||||||
|
|
||||||
|
OrphanRemoval works with one-to-one, one-to-many and many-to-many associations.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When using the ``orphanRemoval=true`` option Doctrine makes the assumption
|
||||||
|
that the entities are privately owned and will **NOT** be reused by other entities.
|
||||||
|
If you neglect this assumption your entities will get deleted by Doctrine even if
|
||||||
|
you assigned the orphaned entity to another one.
|
||||||
|
|
||||||
|
As a better example consider an Addressbook application where you have Contacts, Addresses
|
||||||
|
and StandingData:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Addressbook;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Contact
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @OneToOne(targetEntity="StandingData", orphanRemoval=true) */
|
||||||
|
private $standingData;
|
||||||
|
|
||||||
|
/** @OneToMany(targetEntity="Address", mappedBy="contact", orphanRemoval=true) */
|
||||||
|
private $addresses;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$contact = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\Criteria;
|
||||||
|
|
||||||
|
$group = $entityManager->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections;
|
||||||
|
|
||||||
|
class Criteria
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return Criteria
|
||||||
|
*/
|
||||||
|
static public function create();
|
||||||
|
/**
|
||||||
|
* @param Expression $where
|
||||||
|
* @return Criteria
|
||||||
|
*/
|
||||||
|
public function where(Expression $where);
|
||||||
|
/**
|
||||||
|
* @param Expression $where
|
||||||
|
* @return Criteria
|
||||||
|
*/
|
||||||
|
public function andWhere(Expression $where);
|
||||||
|
/**
|
||||||
|
* @param Expression $where
|
||||||
|
* @return Criteria
|
||||||
|
*/
|
||||||
|
public function orWhere(Expression $where);
|
||||||
|
/**
|
||||||
|
* @param array $orderings
|
||||||
|
* @return Criteria
|
||||||
|
*/
|
||||||
|
public function orderBy(array $orderings);
|
||||||
|
/**
|
||||||
|
* @param int $firstResult
|
||||||
|
* @return Criteria
|
||||||
|
*/
|
||||||
|
public function setFirstResult($firstResult);
|
||||||
|
/**
|
||||||
|
* @param int $maxResults
|
||||||
|
* @return Criteria
|
||||||
|
*/
|
||||||
|
public function setMaxResults($maxResults);
|
||||||
|
public function getOrderings();
|
||||||
|
public function getWhereExpresion();
|
||||||
|
public function getFirstResult();
|
||||||
|
public function getMaxResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
You can build expressions through the ExpressionBuilder. It has the following
|
||||||
|
methods:
|
||||||
|
|
||||||
|
* ``andX($arg1, $arg2, ...)``
|
||||||
|
* ``orX($arg1, $arg2, ...)``
|
||||||
|
* ``eq($field, $value)``
|
||||||
|
* ``gt($field, $value)``
|
||||||
|
* ``lt($field, $value)``
|
||||||
|
* ``lte($field, $value)``
|
||||||
|
* ``gte($field, $value)``
|
||||||
|
* ``neq($field, $value)``
|
||||||
|
* ``isNull($field)``
|
||||||
|
* ``in($field, array $values)``
|
||||||
|
* ``notIn($field, array $values)``
|
||||||
|
|
||||||
|
|
848
docs/en/reference/working-with-objects.rst
Normal file
848
docs/en/reference/working-with-objects.rst
Normal file
@ -0,0 +1,848 @@
|
|||||||
|
Working with Objects
|
||||||
|
====================
|
||||||
|
|
||||||
|
In this chapter we will help you understand the ``EntityManager``
|
||||||
|
and the ``UnitOfWork``. A Unit of Work is similar to an
|
||||||
|
object-level transaction. A new Unit of Work is implicitly started
|
||||||
|
when an EntityManager is initially created or after
|
||||||
|
``EntityManager#flush()`` has been invoked. A Unit of Work is
|
||||||
|
committed (and a new one started) by invoking
|
||||||
|
``EntityManager#flush()``.
|
||||||
|
|
||||||
|
A Unit of Work can be manually closed by calling
|
||||||
|
EntityManager#close(). Any changes to objects within this Unit of
|
||||||
|
Work that have not yet been persisted are lost.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
It is very important to understand that only
|
||||||
|
``EntityManager#flush()`` ever causes write operations against the
|
||||||
|
database to be executed. Any other methods such as
|
||||||
|
``EntityManager#persist($entity)`` or
|
||||||
|
``EntityManager#remove($entity)`` only notify the UnitOfWork to
|
||||||
|
perform these operations during flush.
|
||||||
|
|
||||||
|
Not calling ``EntityManager#flush()`` will lead to all changes
|
||||||
|
during that request being lost.
|
||||||
|
|
||||||
|
|
||||||
|
Entities and the Identity Map
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Entities are objects with identity. Their identity has a conceptual
|
||||||
|
meaning inside your domain. In a CMS application each article has a
|
||||||
|
unique id. You can uniquely identify each article by that id.
|
||||||
|
|
||||||
|
Take the following example, where you find an article with the
|
||||||
|
headline "Hello World" with the ID 1234:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$article = $entityManager->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
if ($article === $article2) {
|
||||||
|
echo "Yes we are the same!";
|
||||||
|
}
|
||||||
|
|
||||||
|
Sometimes you want to clear the identity map of an EntityManager to
|
||||||
|
start over. We use this regularly in our unit-tests to enforce
|
||||||
|
loading objects from the database again instead of serving them
|
||||||
|
from the identity map. You can call ``EntityManager#clear()`` to
|
||||||
|
achieve this result.
|
||||||
|
|
||||||
|
Entity Object Graph Traversal
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
Although Doctrine allows for a complete separation of your domain
|
||||||
|
model (Entity classes) there will never be a situation where
|
||||||
|
objects are "missing" when traversing associations. You can walk
|
||||||
|
all the associations inside your entity models as deep as you
|
||||||
|
want.
|
||||||
|
|
||||||
|
Take the following example of a single ``Article`` entity fetched
|
||||||
|
from newly opened EntityManager.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity */
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @Column(type="string") */
|
||||||
|
private $headline;
|
||||||
|
|
||||||
|
/** @ManyToOne(targetEntity="User") */
|
||||||
|
private $author;
|
||||||
|
|
||||||
|
/** @OneToMany(targetEntity="Comment", mappedBy="article") */
|
||||||
|
private $comments;
|
||||||
|
|
||||||
|
public function __construct {
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$article = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class UserProxy extends User implements Proxy
|
||||||
|
{
|
||||||
|
private function _load()
|
||||||
|
{
|
||||||
|
// lazy loading code
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getName()
|
||||||
|
{
|
||||||
|
$this->_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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$user = new User;
|
||||||
|
$user->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 <workingobjects_database_uow_outofsync>`
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$detachedEntity = unserialize($serializedEntity); // some detached entity
|
||||||
|
$entity = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$uowSize = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$uow = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
switch ($em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
$user = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
$user = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
|
||||||
|
// All users that are 20 years old
|
||||||
|
$users = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$number = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$tenUsers = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$users = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// A single user by its nickname
|
||||||
|
$user = $em->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
|
||||||
|
<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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
|
||||||
|
// All users with an age between 20 and 30 (inclusive).
|
||||||
|
$q = $em->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 <dql-doctrine-query-language>`.
|
||||||
|
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 <query-builder>`.
|
||||||
|
|
||||||
|
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 <native-sql>`.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace MyDomain\Model;
|
||||||
|
|
||||||
|
use Doctrine\ORM\EntityRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @entity(repositoryClass="MyDomain\Model\UserRepository")
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserRepository extends EntityRepository
|
||||||
|
{
|
||||||
|
public function getAllAdminUsers()
|
||||||
|
{
|
||||||
|
return $this->_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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em instanceof EntityManager
|
||||||
|
|
||||||
|
$admins = $em->getRepository('MyDomain\Model\User')->getAllAdminUsers();
|
||||||
|
|
||||||
|
|
747
docs/en/reference/xml-mapping.rst
Normal file
747
docs/en/reference/xml-mapping.rst
Normal file
@ -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 <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
|
||||||
|
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
https://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$driver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$config = new \Doctrine\ORM\Configuration();
|
||||||
|
$driver = new \Doctrine\ORM\Mapping\Driver\XmlDriver(array('/path/to/files1', '/path/to/files2'));
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$namespaces = array(
|
||||||
|
'MyProject\Entities' => '/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
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||||
|
|
||||||
|
<indexes>
|
||||||
|
<index name="name_idx" columns="name"/>
|
||||||
|
<index columns="user_email"/>
|
||||||
|
</indexes>
|
||||||
|
|
||||||
|
<unique-constraints>
|
||||||
|
<unique-constraint columns="name,user_email" name="search_idx" />
|
||||||
|
</unique-constraints>
|
||||||
|
|
||||||
|
<lifecycle-callbacks>
|
||||||
|
<lifecycle-callback type="prePersist" method="doStuffOnPrePersist"/>
|
||||||
|
<lifecycle-callback type="prePersist" method="doOtherStuffOnPrePersistToo"/>
|
||||||
|
<lifecycle-callback type="postPersist" method="doStuffOnPostPersist"/>
|
||||||
|
</lifecycle-callbacks>
|
||||||
|
|
||||||
|
<id name="id" type="integer" column="id">
|
||||||
|
<generator strategy="AUTO"/>
|
||||||
|
<sequence-generator sequence-name="tablename_seq" allocation-size="100" initial-value="1" />
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<field name="name" column="name" type="string" length="50" nullable="true" unique="true" />
|
||||||
|
<field name="email" column="user_email" type="string" column-definition="CHAR(32) NOT NULL" />
|
||||||
|
|
||||||
|
<one-to-one field="address" target-entity="Address" inversed-by="user">
|
||||||
|
<cascade><cascade-remove /></cascade>
|
||||||
|
<join-column name="address_id" referenced-column-name="id" on-delete="CASCADE" on-update="CASCADE"/>
|
||||||
|
</one-to-one>
|
||||||
|
|
||||||
|
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user">
|
||||||
|
<cascade>
|
||||||
|
<cascade-persist/>
|
||||||
|
</cascade>
|
||||||
|
<order-by>
|
||||||
|
<order-by-field name="number" direction="ASC" />
|
||||||
|
</order-by>
|
||||||
|
</one-to-many>
|
||||||
|
|
||||||
|
<many-to-many field="groups" target-entity="Group">
|
||||||
|
<cascade>
|
||||||
|
<cascade-all/>
|
||||||
|
</cascade>
|
||||||
|
<join-table name="cms_users_groups">
|
||||||
|
<join-columns>
|
||||||
|
<join-column name="user_id" referenced-column-name="id" nullable="false" unique="false" />
|
||||||
|
</join-columns>
|
||||||
|
<inverse-join-columns>
|
||||||
|
<join-column name="group_id" referenced-column-name="id" column-definition="INT NULL" />
|
||||||
|
</inverse-join-columns>
|
||||||
|
</join-table>
|
||||||
|
</many-to-many>
|
||||||
|
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
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 ``<entity />`` element as a direct child of the
|
||||||
|
``<doctrine-mapping />`` element:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="MyProject\User" table="cms_users" repository-class="MyProject\UserRepository">
|
||||||
|
<!-- definition here -->
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
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 ``<field />``
|
||||||
|
element as a children to the ``<entity />`` 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 ``<id />`` element.
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<entity name="MyProject\User">
|
||||||
|
|
||||||
|
<field name="name" type="string" length="50" />
|
||||||
|
<field name="username" type="string" unique="true" />
|
||||||
|
<field name="age" type="integer" nullable="true" />
|
||||||
|
<field name="isActive" column="is_active" type="boolean" />
|
||||||
|
<field name="weight" type="decimal" scale="5" precision="2" />
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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 ``<id />`` 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 ``<field />`` element attributes:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<entity name="MyProject\User">
|
||||||
|
<id name="id" type="integer" column="user_id" />
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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 ``<generator />`` 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
|
||||||
|
|
||||||
|
<entity name="MyProject\User">
|
||||||
|
<id name="id" type="integer" column="user_id">
|
||||||
|
<generator strategy="AUTO" />
|
||||||
|
</id>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
The following values are allowed for the ``<generator />`` 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
|
||||||
|
|
||||||
|
<entity name="MyProject\User">
|
||||||
|
<id name="id" type="integer" column="user_id">
|
||||||
|
<generator strategy="SEQUENCE" />
|
||||||
|
<sequence-generator sequence-name="user_seq" allocation-size="5" initial-value="1" />
|
||||||
|
</id>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
Required attributes for ``<sequence-generator />``:
|
||||||
|
|
||||||
|
|
||||||
|
- sequence-name - The name of the sequence
|
||||||
|
|
||||||
|
Optional attributes for ``<sequence-generator />``:
|
||||||
|
|
||||||
|
|
||||||
|
- 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 <sequence-generator />
|
||||||
|
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 ``<mapped-superclass />`` tag.
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<mapped-superclass name="MyProject\BaseClass">
|
||||||
|
<field name="created" type="datetime" />
|
||||||
|
<field name="updated" type="datetime" />
|
||||||
|
</mapped-superclass>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
Required attributes:
|
||||||
|
|
||||||
|
|
||||||
|
- name - Class name of the mapped superclass.
|
||||||
|
|
||||||
|
You can nest any number of ``<field />`` and unidirectional
|
||||||
|
``<many-to-one />`` or ``<one-to-one />`` 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 ``<entity />`` element
|
||||||
|
and then use the ``<discriminator-column />`` and
|
||||||
|
``<discriminator-mapping />`` attributes.
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<entity name="MyProject\Animal" inheritance-type="JOINED">
|
||||||
|
<discriminator-column name="discr" type="string" />
|
||||||
|
<discriminator-map>
|
||||||
|
<discriminator-mapping value="cat" class="MyProject\Cat" />
|
||||||
|
<discriminator-mapping value="dog" class="MyProject\Dog" />
|
||||||
|
<discriminator-mapping value="mouse" class="MyProject\Mouse" />
|
||||||
|
</discriminator-map>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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 ``<lifecycle-callbacks />`` element:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||||
|
|
||||||
|
<lifecycle-callbacks>
|
||||||
|
<lifecycle-callback type="prePersist" method="onPrePersist" />
|
||||||
|
</lifecycle-callbacks>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
Defining One-To-One Relations
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
You can define One-To-One Relations/Associations using the
|
||||||
|
``<one-to-one />`` 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
|
||||||
|
|
||||||
|
<entity class="MyProject\User">
|
||||||
|
<one-to-one field="address" target-entity="Address" mapped-by="user" />
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<entity class="MyProject\Address">
|
||||||
|
<one-to-one field="user" target-entity="User" inversed-by="address" />
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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
|
||||||
|
``<join-column />`` 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
|
||||||
|
|
||||||
|
<entity class="MyProject\Address">
|
||||||
|
<one-to-one field="user" target-entity="User" inversed-by="address">
|
||||||
|
<join-column name="user_id" referenced-column-name="id" />
|
||||||
|
</one-to-one>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<entity class="MyProject\Article">
|
||||||
|
<many-to-one field="author" target-entity="User" />
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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 ``<join-column />`` tag nested inside
|
||||||
|
the many-to-one association tag:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<entity class="MyProject\Article">
|
||||||
|
<many-to-one field="author" target-entity="User">
|
||||||
|
<join-column name="author_id" referenced-column-name="id" />
|
||||||
|
</many-to-one>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<entity class="MyProject\User">
|
||||||
|
<one-to-many field="phonenumbers" target-entity="Phonenumber" mapped-by="user" />
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<entity class="MyProject\User">
|
||||||
|
<many-to-many field="groups" target-entity="Group" />
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
<entity class="MyProject\User">
|
||||||
|
<many-to-many field="groups" target-entity="Group">
|
||||||
|
<join-table name="cms_users_groups">
|
||||||
|
<join-columns>
|
||||||
|
<join-column name="user_id" referenced-column-name="id"/>
|
||||||
|
</join-columns>
|
||||||
|
<inverse-join-columns>
|
||||||
|
<join-column name="group_id" referenced-column-name="id"/>
|
||||||
|
</inverse-join-columns>
|
||||||
|
</join-table>
|
||||||
|
</many-to-many>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
Here both the ``<join-columns>`` and ``<inverse-join-columns>``
|
||||||
|
tags are necessary to tell Doctrine for which side the specified
|
||||||
|
join-columns apply. These are nested inside a ``<join-table />``
|
||||||
|
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
|
||||||
|
``<cascade />`` element inside any of the association mapping
|
||||||
|
tags.
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<entity class="MyProject\User">
|
||||||
|
<many-to-many field="groups" target-entity="Group">
|
||||||
|
<cascade>
|
||||||
|
<cascade-all/>
|
||||||
|
</cascade>
|
||||||
|
</many-to-many>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
Besides ``<cascade-all />`` the following operations can be
|
||||||
|
specified by their respective tags:
|
||||||
|
|
||||||
|
|
||||||
|
- ``<cascade-persist />``
|
||||||
|
- ``<cascade-merge />``
|
||||||
|
- ``<cascade-remove />``
|
||||||
|
- ``<cascade-refresh />``
|
||||||
|
|
||||||
|
Join Column Element
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In any explicitly defined association mapping you will need the
|
||||||
|
``<join-column />`` 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
|
||||||
|
|
||||||
|
<entity class="MyProject\User">
|
||||||
|
<many-to-many field="groups" target-entity="Group">
|
||||||
|
<order-by>
|
||||||
|
<order-by-field name="name" direction="ASC" />
|
||||||
|
</order-by>
|
||||||
|
</many-to-many>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
Defining Indexes or Unique Constraints
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To define additional indexes or unique constraints on the entities
|
||||||
|
table you can use the ``<indexes />`` and
|
||||||
|
``<unique-constraints />`` elements:
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<entity name="Doctrine\Tests\ORM\Mapping\User" table="cms_users">
|
||||||
|
|
||||||
|
<indexes>
|
||||||
|
<index name="name_idx" columns="name"/>
|
||||||
|
<index columns="user_email"/>
|
||||||
|
</indexes>
|
||||||
|
|
||||||
|
<unique-constraints>
|
||||||
|
<unique-constraint columns="name,user_email" name="search_idx" />
|
||||||
|
</unique-constraints>
|
||||||
|
</entity>
|
||||||
|
|
||||||
|
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 ``<id>`` tag.
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://raw.github.com/doctrine/doctrine2/master/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="Application\Model\ArticleAttribute">
|
||||||
|
<id name="article" association-key="true" />
|
||||||
|
<id name="attribute" type="string" />
|
||||||
|
|
||||||
|
<field name="value" type="string" />
|
||||||
|
|
||||||
|
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
|
||||||
|
<entity>
|
||||||
|
|
||||||
|
</doctrine-mapping>
|
119
docs/en/reference/yaml-mapping.rst
Normal file
119
docs/en/reference/yaml-mapping.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$driver->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\Mapping\Driver\YamlDriver;
|
||||||
|
|
||||||
|
// $config instanceof Doctrine\ORM\Configuration
|
||||||
|
$driver = new YamlDriver(array('/path/to/files'));
|
||||||
|
$config->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$namespaces = array(
|
||||||
|
'/path/to/files1' => '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.
|
||||||
|
|
||||||
|
|
80
docs/en/toc.rst
Normal file
80
docs/en/toc.rst
Normal file
@ -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
|
||||||
|
|
351
docs/en/tutorials/composite-primary-keys.rst
Normal file
351
docs/en/tutorials/composite-primary-keys.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace VehicleCatalogue\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Car
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="string") */
|
||||||
|
private $name;
|
||||||
|
/** @Id @Column(type="integer") */
|
||||||
|
private $year
|
||||||
|
|
||||||
|
public function __construct($name, $year)
|
||||||
|
{
|
||||||
|
$this->name = $name;
|
||||||
|
$this->year = $year;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getModelName()
|
||||||
|
{
|
||||||
|
return $this->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getYearOfProduction()
|
||||||
|
{
|
||||||
|
return $this->year;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="VehicleCatalogue\Model\Car">
|
||||||
|
<id field="name" type="string" />
|
||||||
|
<id field="year" type="integer" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
VehicleCatalogue\Model\Car:
|
||||||
|
type: entity
|
||||||
|
id:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
year:
|
||||||
|
type: integer
|
||||||
|
|
||||||
|
Now you can use this entity:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace VehicleCatalogue\Model;
|
||||||
|
|
||||||
|
// $em is the EntityManager
|
||||||
|
|
||||||
|
$car = new Car("Audi A8", 2010);
|
||||||
|
$em->persist($car);
|
||||||
|
$em->flush();
|
||||||
|
|
||||||
|
And for querying you can use arrays to both DQL and EntityRepositories:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace VehicleCatalogue\Model;
|
||||||
|
|
||||||
|
// $em is the EntityManager
|
||||||
|
$audi = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Application\Model;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Article
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
private $id;
|
||||||
|
/** @Column(type="string") */
|
||||||
|
private $title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToMany(targetEntity="ArticleAttribute", mappedBy="article", cascade={"ALL"}, indexBy="attribute")
|
||||||
|
*/
|
||||||
|
private $attributes;
|
||||||
|
|
||||||
|
public function addAttribute($name, $value)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="Application\Model\ArticleAttribute">
|
||||||
|
<id name="article" association-key="true" />
|
||||||
|
<id name="attribute" type="string" />
|
||||||
|
|
||||||
|
<field name="value" type="string" />
|
||||||
|
|
||||||
|
<many-to-one field="article" target-entity="Article" inversed-by="attributes" />
|
||||||
|
<entity>
|
||||||
|
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
private $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Address
|
||||||
|
{
|
||||||
|
/** @Id @OneToOne(targetEntity="User") */
|
||||||
|
private $user;
|
||||||
|
}
|
||||||
|
|
||||||
|
Use-Case 3: Join-Table with Metadata
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In the classic order product shop example there is the concept of the order item
|
||||||
|
which contains references to order and product and additional data such as the amount
|
||||||
|
of products purchased and maybe even the current price.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
|
/** @Entity */
|
||||||
|
class Order
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="integer") @GeneratedValue */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/** @ManyToOne(targetEntity="Customer") */
|
||||||
|
private $customer;
|
||||||
|
/** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
|
||||||
|
private $items;
|
||||||
|
|
||||||
|
/** @Column(type="boolean") */
|
||||||
|
private $payed = false;
|
||||||
|
/** @Column(type="boolean") */
|
||||||
|
private $shipped = false;
|
||||||
|
/** @Column(type="datetime") */
|
||||||
|
private $created;
|
||||||
|
|
||||||
|
public function __construct(Customer $customer)
|
||||||
|
{
|
||||||
|
$this->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.
|
82
docs/en/tutorials/extra-lazy-associations.rst
Normal file
82
docs/en/tutorials/extra-lazy-associations.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Doctrine\Tests\Models\CMS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class CmsGroup
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @ManyToMany(targetEntity="CmsUser", mappedBy="groups", fetch="EXTRA_LAZY")
|
||||||
|
*/
|
||||||
|
public $users;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="Doctrine\Tests\Models\CMS\CmsGroup">
|
||||||
|
<!-- ... -->
|
||||||
|
<many-to-many field="users" target-entity="CmsUser" mapped-by="groups" fetch="EXTRA_LAZY" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
Doctrine\Tests\Models\CMS\CmsGroup:
|
||||||
|
type: entity
|
||||||
|
# ...
|
||||||
|
manyToMany:
|
||||||
|
users:
|
||||||
|
targetEntity: CmsUser
|
||||||
|
mappedBy: groups
|
||||||
|
fetch: EXTRA_LAZY
|
||||||
|
|
27
docs/en/tutorials/getting-started-database.rst
Normal file
27
docs/en/tutorials/getting-started-database.rst
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Getting Started: Database First
|
||||||
|
===============================
|
||||||
|
|
||||||
|
.. note:: *Development Workflows*
|
||||||
|
|
||||||
|
When you :doc:`Code First <getting-started>`, you
|
||||||
|
start with developing Objects and then map them onto your database. When
|
||||||
|
you :doc:`Model First <getting-started-models>`, 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 <getting-started-database>`, 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.
|
24
docs/en/tutorials/getting-started-models.rst
Normal file
24
docs/en/tutorials/getting-started-models.rst
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
Getting Started: Model First
|
||||||
|
============================
|
||||||
|
|
||||||
|
.. note:: *Development Workflows*
|
||||||
|
|
||||||
|
When you :doc:`Code First <getting-started>`, you
|
||||||
|
start with developing Objects and then map them onto your database. When
|
||||||
|
you :doc:`Model First <getting-started-models>`, 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 <getting-started-database>`, 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.
|
1516
docs/en/tutorials/getting-started.rst
Normal file
1516
docs/en/tutorials/getting-started.rst
Normal file
File diff suppressed because it is too large
Load Diff
300
docs/en/tutorials/in-ten-quick-steps.rst
Normal file
300
docs/en/tutorials/in-ten-quick-steps.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
class Post
|
||||||
|
{
|
||||||
|
protected $id;
|
||||||
|
protected $title;
|
||||||
|
protected $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
mysql> 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity **/
|
||||||
|
class Post
|
||||||
|
{
|
||||||
|
/** @Id @GeneratedValue @Column(type="integer") **/
|
||||||
|
protected $id;
|
||||||
|
/** @Column(type="string") **/
|
||||||
|
protected $title;
|
||||||
|
/** @Column(type="text") **/
|
||||||
|
protected $body;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
Post:
|
||||||
|
type: entity
|
||||||
|
id:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
generator:
|
||||||
|
strategy: AUTO
|
||||||
|
fields:
|
||||||
|
title:
|
||||||
|
type: string
|
||||||
|
body:
|
||||||
|
type: text
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<?xml version="1.0" ?>
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="Post">
|
||||||
|
<id name="id type="integer">
|
||||||
|
<generator strategy="AUTO" />
|
||||||
|
</id>
|
||||||
|
<field name="title" type="string" />
|
||||||
|
<field name="body" type="text" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
|
||||||
|
3. Object References map to Foreign keys
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity **/
|
||||||
|
class Post
|
||||||
|
{
|
||||||
|
// .. previous code
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToOne(targetEntity="User")
|
||||||
|
**/
|
||||||
|
protected $author;
|
||||||
|
|
||||||
|
public function __construct(User $user)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
|
class Post
|
||||||
|
{
|
||||||
|
// .. previous code
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToMany(targetEntity="Comment", mappedBy="post",
|
||||||
|
* cascade={"persist"})
|
||||||
|
**/
|
||||||
|
protected $comments;
|
||||||
|
|
||||||
|
public function __construct(User $author)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Doctrine\ORM\Tools\Setup;
|
||||||
|
use Doctrine\ORM\EntityManager;
|
||||||
|
|
||||||
|
require_once 'Doctrine/Common/ClassLoader.php';
|
||||||
|
|
||||||
|
$loader = new \Doctrine\Common\ClassLoader("Doctrine");
|
||||||
|
$loader->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$entityManager->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$entityManager->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$post = $entityManager->find("Post", $id);
|
||||||
|
|
||||||
|
9. ..or through a Repository
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$authorRepository = $entityManager->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// all posts and their comment count
|
||||||
|
$dql = "SELECT p, count(c.id) AS comments " .
|
||||||
|
"FROM Post p JOIN p.comments GROUP BY p";
|
||||||
|
$results = $entityManager->createQuery($dql)->getResult();
|
91
docs/en/tutorials/ordered-associations.rst
Normal file
91
docs/en/tutorials/ordered-associations.rst
Normal file
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity **/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToMany(targetEntity="Group")
|
||||||
|
* @OrderBy({"name" = "ASC"})
|
||||||
|
**/
|
||||||
|
private $groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<doctrine-mapping>
|
||||||
|
<entity name="User">
|
||||||
|
<many-to-many field="groups" target-entity="Group">
|
||||||
|
<order-by>
|
||||||
|
<order-by-field name="name" direction="ASC" />
|
||||||
|
</order-by>
|
||||||
|
</many-to-many>
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*
|
||||||
|
* @AttributeOverrides({
|
||||||
|
* @AttributeOverride(name="foo",
|
||||||
|
* column=@Column(
|
||||||
|
* name = "foo_overridden",
|
||||||
|
* type = "integer",
|
||||||
|
* length = 140,
|
||||||
|
* nullable = false,
|
||||||
|
* unique = false
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
*
|
||||||
|
* @AssociationOverrides({
|
||||||
|
* @AssociationOverride(name="bar",
|
||||||
|
* joinColumns=@JoinColumn(
|
||||||
|
* name="example_entity_overridden_bar_id", referencedColumnName="id"
|
||||||
|
* )
|
||||||
|
* )
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
class ExampleEntityWithOverride
|
||||||
|
{
|
||||||
|
use ExampleTrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
*/
|
||||||
|
class Bar
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="string") */
|
||||||
|
private $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
The docblock is showing metadata override of the attribute and association type. It
|
||||||
|
basically changes the names of the columns mapped for a property ``foo`` and for
|
||||||
|
the association ``bar`` which relates to Bar class shown above. Here is the trait
|
||||||
|
which has mapping metadata that is overridden by the annotation above:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait class
|
||||||
|
*/
|
||||||
|
trait ExampleTrait
|
||||||
|
{
|
||||||
|
/** @Id @Column(type="string") */
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Column(name="trait_foo", type="integer", length=100, nullable=true, unique=true)
|
||||||
|
*/
|
||||||
|
protected $foo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToOne(targetEntity="Bar", cascade={"persist", "merge"})
|
||||||
|
* @JoinColumn(name="example_trait_bar_id", referencedColumnName="id")
|
||||||
|
*/
|
||||||
|
protected $bar;
|
||||||
|
}
|
||||||
|
|
||||||
|
The case for just extending a class would be just the same but:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
class ExampleEntityWithOverride extends BaseEntityWithSomeMapping
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Overriding is also supported via XML and YAML.
|
41
docs/en/tutorials/pagination.rst
Normal file
41
docs/en/tutorials/pagination.rst
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
Pagination
|
||||||
|
==========
|
||||||
|
|
||||||
|
Starting with version 2.2 Doctrine ships with a Paginator for DQL queries. It
|
||||||
|
has a very simple API and implements the SPL interfaces ``Countable`` and
|
||||||
|
``IteratorAggregate``.
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||||
|
|
||||||
|
$dql = "SELECT p, c FROM BlogPost p JOIN p.comments c";
|
||||||
|
$query = $entityManager->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.
|
298
docs/en/tutorials/working-with-indexed-associations.rst
Normal file
298
docs/en/tutorials/working-with-indexed-associations.rst
Normal file
@ -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 ``<one-to-many />`` or ``<many-to-many />`` 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Doctrine\Tests\Models\StockExchange;
|
||||||
|
|
||||||
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @Table(name="exchange_markets")
|
||||||
|
*/
|
||||||
|
class Market
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id @Column(type="integer") @GeneratedValue
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Column(type="string")
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @OneToMany(targetEntity="Stock", mappedBy="market", indexBy="symbol")
|
||||||
|
* @var Stock[]
|
||||||
|
*/
|
||||||
|
private $stocks;
|
||||||
|
|
||||||
|
public function __construct($name)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="Doctrine\Tests\Models\StockExchange\Market">
|
||||||
|
<id name="id" type="integer">
|
||||||
|
<generator strategy="AUTO" />
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<field name="name" type="string"/>
|
||||||
|
|
||||||
|
<one-to-many target-entity="Stock" mapped-by="market" field="stocks" index-by="symbol" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
namespace Doctrine\Tests\Models\StockExchange;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Entity
|
||||||
|
* @Table(name="exchange_stocks")
|
||||||
|
*/
|
||||||
|
class Stock
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @Id @GeneratedValue @Column(type="integer")
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
private $id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For real this column would have to be unique=true. But I want to test behavior of non-unique overrides.
|
||||||
|
*
|
||||||
|
* @Column(type="string", unique=true)
|
||||||
|
*/
|
||||||
|
private $symbol;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToOne(targetEntity="Market", inversedBy="stocks")
|
||||||
|
* @var Market
|
||||||
|
*/
|
||||||
|
private $market;
|
||||||
|
|
||||||
|
public function __construct($symbol, Market $market)
|
||||||
|
{
|
||||||
|
$this->symbol = $symbol;
|
||||||
|
$this->market = $market;
|
||||||
|
$market->addStock($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSymbol()
|
||||||
|
{
|
||||||
|
return $this->symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. code-block:: xml
|
||||||
|
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
|
||||||
|
http://www.doctrine-project.org/schemas/orm/doctrine-mapping.xsd">
|
||||||
|
|
||||||
|
<entity name="Doctrine\Tests\Models\StockExchange\Stock">
|
||||||
|
<id name="id" type="integer">
|
||||||
|
<generator strategy="AUTO" />
|
||||||
|
</id>
|
||||||
|
|
||||||
|
<field name="symbol" type="string" unique="true" />
|
||||||
|
<many-to-one target-entity="Market" field="market" inversed-by="stocks" />
|
||||||
|
</entity>
|
||||||
|
</doctrine-mapping>
|
||||||
|
|
||||||
|
.. 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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em is the EntityManager
|
||||||
|
|
||||||
|
$market = new Market("Some Exchange");
|
||||||
|
$stock1 = new Stock("AAPL", $market);
|
||||||
|
$stock2 = new Stock("GOOG", $market);
|
||||||
|
|
||||||
|
$em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em is the EntityManager
|
||||||
|
$marketId = 1;
|
||||||
|
$symbol = "AAPL";
|
||||||
|
|
||||||
|
$market = $em->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
|
||||||
|
|
||||||
|
<?php
|
||||||
|
// $em is the EntityManager
|
||||||
|
$marketId = 1;
|
||||||
|
$symbol = "AAPL";
|
||||||
|
|
||||||
|
$dql = "SELECT m, s FROM Doctrine\Tests\Models\StockExchange\Market m JOIN m.stocks s WHERE m.id = ?1";
|
||||||
|
$market = $em->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 <http://www.doctrine-project.org/jira/browse/DDC-213>`_
|
||||||
|
This feature cannot be implemented for One-To-Many associations, because they are never the owning side.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user