Commit d67a4e7b authored by Benjamin Eberlei's avatar Benjamin Eberlei

Add 'docs/' from commit 'b605286d'

git-subtree-dir: docs
git-subtree-mainline: 30b5c31a
git-subtree-split: b605286d
parents 30b5c31a b605286d
en/_exts/configurationblock.pyc
build
en/_build
[submodule "en/_theme"]
path = en/_theme
url = https://github.com/doctrine/doctrine-sphinx-theme.git
# Doctrine DBAL Documentation
## How to Generate
1. Run ./bin/install-dependencies.sh
2. Run ./bin/generate-docs.sh
It will generate the documentation into the build directory of the checkout.
\ No newline at end of file
#!/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/DoctrineDBAL.tex
\ No newline at end of file
#!/bin/bash
sudo apt-get install python25 python25-dev texlive-full rubber
sudo easy_install pygments
sudo easy_install sphinx
\ No newline at end of file
# 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/DoctrineDBAL.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DoctrineDBAL.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."
#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)
_theme @ 5aa024c9
Subproject commit 5aa024c9284228ed76c7a9aa785229cf19f396e3
# -*- coding: utf-8 -*-
#
# Doctrine DBAL documentation build configuration file, created by
# sphinx-quickstart on Mon Nov 1 19:50:57 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('.'))
# -- 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 = []
# 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 DBAL'
copyright = u'2010, Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.1'
# The full version, including alpha/beta/rc tags.
release = '2.1.0'
# 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 = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
highlight_language = 'php'
# 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 = 'DoctrineDBALdoc'
# -- 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', 'DoctrineDBAL.tex', u'Doctrine DBAL Documentation',
u'Roman Borschel, Guilherme Blanco, Benjamin Eberlei, Jonathan Wage', '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
.. Doctrine DBAL documentation master file, created by
sphinx-quickstart on Mon Nov 1 19:16:59 2010.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to Doctrine DBAL's documentation!
=========================================
Contents:
.. toctree::
:maxdepth: 1
:numbered:
reference/introduction
reference/architecture
reference/configuration
reference/data-retrieval-and-manipulation
reference/query-builder
reference/transactions
reference/platforms
reference/types
reference/schema-manager
reference/schema-representation
reference/events
reference/security
reference/sharding
reference/sharding_azure_tutorial
reference/supporting-other-databases
reference/portability
reference/caching
reference/known-vendor-issues
Indices and tables
==================
* :ref:`search`
@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\DoctrineDBAL.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\DoctrineDBAL.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
Architecture
============
As already said, the DBAL is a thin layer on top of PDO. PDO itself
is mainly defined in terms of 2 classes: ``PDO`` and
``PDOStatement``. The equivalent classes in the DBAL are
``Doctrine\DBAL\Connection`` and ``Doctrine\DBAL\Statement``. A
``Doctrine\DBAL\Connection`` wraps a
``Doctrine\DBAL\Driver\Connection`` and a
``Doctrine\DBAL\Statement`` wraps a
``Doctrine\DBAL\Driver\Statement``.
``Doctrine\DBAL\Driver\Connection`` and
``Doctrine\DBAL\Driver\Statement`` are just interfaces. These
interfaces are implemented by concrete drivers. For all PDO based
drivers, ``PDO`` and ``PDOStatement`` are the implementations of
these interfaces. Thus, for PDO-based drivers, a
``Doctrine\DBAL\Connection`` wraps a ``PDO`` instance and a
``Doctrine\DBAL\Statement`` wraps a ``PDOStatement`` instance. Even
more, a ``Doctrine\DBAL\Connection`` *is a*
``Doctrine\DBAL\Driver\Connection`` and a
``Doctrine\DBAL\Statement`` *is a*
``Doctrine\DBAL\Driver\Statement``.
What does a ``Doctrine\DBAL\Connection`` or a
``Doctrine\DBAL\Statement`` add to the underlying driver
implementations? The enhancements include SQL logging, events and
control over the transaction isolation level in a portable manner,
among others.
A DBAL driver is defined to the outside in terms of 3 interfaces:
``Doctrine\DBAL\Driver``, ``Doctrine\DBAL\Driver\Connection`` and
``Doctrine\DBAL\Driver\Statement``. The latter two resemble (a
subset of) the corresponding PDO API.
A concrete driver implementation must provide implementation
classes for these 3 interfaces.
The DBAL is separated into several different packages that
perfectly separate responsibilities of the different RDBMS layers.
Drivers
-------
The drivers abstract a PHP specific database API by enforcing two
interfaces:
- ``\Doctrine\DBAL\Driver\Driver``
- ``\Doctrine\DBAL\Driver\Statement``
The above two interfaces require exactly the same methods as PDO.
Platforms
---------
The platforms abstract the generation of queries and which database
features a platform supports. The
``\Doctrine\DBAL\Platforms\AbstractPlatform`` defines the common
denominator of what a database platform has to publish to the
userland, to be fully supportable by Doctrine. This includes the
SchemaTool, Transaction Isolation and many other features. The
Database platform for MySQL for example can be used by all 3 MySQL
extensions, PDO, Mysqli and ext/mysql.
Logging
-------
The logging holds the interface and some implementations for
debugging of Doctrine SQL query execution during a request.
Schema
------
The schema offers an API for each database platform to execute DDL
statements against your platform or retrieve metadata about it. It
also holds the Schema Abstraction Layer which is used by the
different Schema Management facilities of Doctrine DBAL and ORM.
Types
-----
The types offer an abstraction layer for the converting and
generation of types between Databases and PHP. Doctrine comes
bundled with some common types but offers the ability for
developers to define custom types or extend existing ones easily.
Caching
=======
A ``Doctrine\DBAL\Statement`` can automatically cache result sets.
For this to work an instance of ``Doctrine\Common\Cache\Cache`` must be provided.
This can be set on the configuration object (optionally it can also be passed at query time):
::
<?php
$cache = new \Doctrine\Common\Cache\ArrayCache();
$config = $conn->getConfiguration();
$config->setResultCacheImpl($cache);
To get the result set of a query cached it is necessary to pass a
``Doctrine\DBAL\Cache\QueryCacheProfile`` instance to the ``executeQuery`` or ``executeCacheQuery``
instance. The difference between these two methods is that the former does not
require this instance, while the later has this instance as a required parameter:
::
<?php
$stmt = $conn->executeQuery($query, $params, $types, new QueryCacheProfile(0, "some key"));
$stmt = $conn->executeCachedQuery($query, $params, $types, new QueryCacheProfile(0, "some key"));
It is also possible to pass in a the ``Doctrine\Common\Cache\Cache`` instance into the
constructor of ``Doctrine\DBAL\Cache\QueryCacheProfile`` in which case it overrides
the default cache instance:
::
<?php
$cache = new \Doctrine\Common\Cache\FilesystemCache(__DIR__);
new QueryCacheProfile(0, "some key", $cache);
In order for the data to actually be cached its necessary to ensure that the entire
result set is read (easiest way to ensure this is to use ``fetchAll``) and the statement
object is closed:
::
<?php
$stmt = $conn->executeCachedQuery($query, $params, $types, new QueryCacheProfile(0, "some key"));
$data = $stmt->fetchAll();
$stmt->close() // at this point the result is cached
.. warning::
When using the cache layer not all fetch modes are supported. See the code of the `ResultCacheStatement <https://github.com/doctrine/dbal/blob/master/lib/Doctrine/DBAL/Cache/ResultCacheStatement.php#L156>`_ for details.
\ No newline at end of file
Configuration
=============
Getting a Connection
--------------------
You can get a DBAL Connection through the
``Doctrine\DBAL\DriverManager`` class.
.. code-block:: php
<?php
$config = new \Doctrine\DBAL\Configuration();
//..
$connectionParams = array(
'dbname' => 'mydb',
'user' => 'user',
'password' => 'secret',
'host' => 'localhost',
'driver' => 'pdo_mysql',
);
$conn = \Doctrine\DBAL\DriverManager::getConnection($connectionParams, $config);
The ``DriverManager`` returns an instance of
``Doctrine\DBAL\Connection`` which is a wrapper around the
underlying driver connection (which is often a PDO instance).
The following sections describe the available connection parameters
in detail.
Driver
~~~~~~
The driver specifies the actual implementations of the DBAL
interfaces to use. It can be configured in one of three ways:
- ``driver``: The built-in driver implementation to use. The
following drivers are currently available:
- ``pdo_mysql``: A MySQL driver that uses the pdo\_mysql PDO
extension.
- ``pdo_sqlite``: An SQLite driver that uses the pdo\_sqlite PDO
extension.
- ``pdo_pgsql``: A PostgreSQL driver that uses the pdo\_pgsql PDO
extension.
- ``pdo_oci``: An Oracle driver that uses the pdo\_oci PDO
extension.
**Note that this driver caused problems in our tests. Prefer the oci8 driver if possible.**
- ``pdo_sqlsrv``: An MSSQL driver that uses pdo\_sqlsrv PDO
- ``oci8``: An Oracle driver that uses the oci8 PHP extension.
- ``driverClass``: Specifies a custom driver implementation if no
'driver' is specified. This allows the use of custom drivers that
are not part of the Doctrine DBAL itself.
- ``pdo``: Specifies an existing PDO instance to use.
Wrapper Class
~~~~~~~~~~~~~
By default a ``Doctrine\DBAL\Connection`` is wrapped around a
driver ``Connection``. The ``wrapperClass`` option allows to
specify a custom wrapper implementation to use, however, a custom
wrapper class must be a subclass of ``Doctrine\DBAL\Connection``.
Connection Details
~~~~~~~~~~~~~~~~~~
The connection details identify the database to connect to as well
as the credentials to use. The connection details can differ
depending on the used driver. The following sections describe the
options recognized by each built-in driver.
.. note::
When using an existing PDO instance through the ``pdo``
option, specifying connection details is obviously not necessary.
pdo\_sqlite
^^^^^^^^^^^
- ``user`` (string): Username to use when connecting to the
database.
- ``password`` (string): Password to use when connecting to the
database.
- ``path`` (string): The filesystem path to the database file.
Mutually exclusive with ``memory``. ``path`` takes precedence.
- ``memory`` (boolean): True if the SQLite database should be
in-memory (non-persistent). Mutually exclusive with ``path``.
``path`` takes precedence.
pdo\_mysql
^^^^^^^^^^
- ``user`` (string): Username to use when connecting to the
database.
- ``password`` (string): Password to use when connecting to the
database.
- ``host`` (string): Hostname of the database to connect to.
- ``port`` (integer): Port of the database to connect to.
- ``dbname`` (string): Name of the database/schema to connect to.
- ``unix_socket`` (string): Name of the socket used to connect to
the database.
- ``charset`` (string): The charset used when connecting to the
database.
pdo\_pgsql
^^^^^^^^^^
- ``user`` (string): Username to use when connecting to the
database.
- ``password`` (string): Password to use when connecting to the
database.
- ``host`` (string): Hostname of the database to connect to.
- ``port`` (integer): Port of the database to connect to.
- ``dbname`` (string): Name of the database/schema to connect to.
pdo\_oci / oci8
^^^^^^^^^^^^^^^
- ``user`` (string): Username to use when connecting to the
database.
- ``password`` (string): Password to use when connecting to the
database.
- ``host`` (string): Hostname of the database to connect to.
- ``port`` (integer): Port of the database to connect to.
- ``dbname`` (string): Name of the database/schema to connect to.
- ``charset`` (string): The charset used when connecting to the
database.
pdo\_sqlsrv
^^^^^^^^^^
- ``user`` (string): Username to use when connecting to the
database.
- ``password`` (string): Password to use when connecting to the
database.
- ``host`` (string): Hostname of the database to connect to.
- ``port`` (integer): Port of the database to connect to.
- ``dbname`` (string): Name of the database/schema to connect to.
Custom Platform
~~~~~~~~~~~~~~~
Each built-in driver uses a default implementation of
``Doctrine\DBAL\Platforms\AbstractPlatform``. If you wish to use a
customized or custom implementation, you can pass a precreated
instance in the ``platform`` option.
Custom Driver Options
~~~~~~~~~~~~~~~~~~~~~
The ``driverOptions`` option allows to pass arbitrary options
through to the driver. This is equivalent to the fourth argument of
the `PDO constructor <http://php.net/manual/en/pdo.construct.php>`_.
This diff is collapsed.
Events
======
Both ``Doctrine\DBAL\DriverManager`` and
``Doctrine\DBAL\Connection`` accept an instance of
``Doctrine\Common\EventManager``. The EventManager has a couple of
events inside the DBAL layer that are triggered for the user to
listen to.
PostConnect Event
-----------------
``Doctrine\DBAL\Events::postConnect`` is triggered right after the
connection to the database is established. It allows to specify any
relevant connection specific options and gives access to the
``Doctrine\DBAL\Connection`` instance that is responsible for the
connection management via an instance of
``Doctrine\DBAL\Event\ConnectionEventArgs`` event arguments
instance.
Doctrine ships with one implementation for the "PostConnect" event:
- ``Doctrine\DBAL\Event\Listeners\OracleSessionInit`` allows to
specify any number of Oracle Session related enviroment variables
that are set right after the connection is established.
You can register events by subscribing them to the ``EventManager``
instance passed to the Connection factory:
.. code-block:: php
<?php
$evm = new EventManager();
$evm->addEventSubscriber(new OracleSessionInit(array(
'NLS_TIME_FORMAT' => 'HH24:MI:SS',
)));
$conn = DriverManager::getConnection($connectionParams, null, $evm);
Introduction
============
The Doctrine database abstraction & access layer (DBAL) offers a
lightweight and thin runtime layer around a PDO-like API and a lot
of additional, horizontal features like database schema
introspection and manipulation through an OO API.
The fact that the Doctrine DBAL abstracts the concrete PDO API away
through the use of interfaces that closely resemble the existing
PDO API makes it possible to implement custom drivers that may use
existing native or self-made APIs. For example, the DBAL ships with
a driver for Oracle databases that uses the oci8 extension under
the hood.
The Doctrine 2 database layer can be used independently of the
object-relational mapper. In order to use the DBAL all you need is
the ``Doctrine\Common`` and ``Doctrine\DBAL`` namespaces. Once you
have the Common and DBAL namespaces you must setup a class loader
to be able to autoload the classes:
.. code-block:: php
<?php
use Doctrine\Common\ClassLoader;
require '/path/to/doctrine/lib/Doctrine/Common/ClassLoader.php';
$classLoader = new ClassLoader('Doctrine', '/path/to/doctrine');
$classLoader->register();
Now you are able to load classes that are in the
``/path/to/doctrine`` directory like
``/path/to/doctrine/Doctrine/DBAL/DriverManager.php`` which we will
use later in this documentation to configure our first Doctrine
DBAL connection.
Known Vendor Issues
===================
This section describes known compatability issues with all the
supported database vendors:
PostgreSQL
----------
DateTime, DateTimeTz and Time Types
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Postgres has a variable return format for the datatype TIMESTAMP(n)
and TIME(n) if microseconds are allowed (n > 0). Whenever you save
a value with microseconds = 0. PostgreSQL will return this value in
the format:
::
2010-10-10 10:10:10 (Y-m-d H:i:s)
However if you save a value with microseconds it will return the
full representation:
::
2010-10-10 10:10:10.123456 (Y-m-d H:i:s.u)
Using the DateTime, DateTimeTz or Time type with microseconds
enabled columns can lead to errors because internally types expect
the exact format 'Y-m-d H:i:s' in combination with
``DateTime::createFromFormat()``. This method is twice a fast as
passing the date to the constructor of ``DateTime``.
This is why Doctrine always wants to create the time related types
without microseconds:
- DateTime to ``TIMESTAMP(0) WITHOUT TIME ZONE``
- DateTimeTz to ``TIMESTAMP(0) WITH TIME ZONE``
- Time to ``TIME(0) WITHOUT TIME ZONE``
If you do not let Doctrine create the date column types and rather
use types with microseconds you have replace the "DateTime",
"DateTimeTz" and "Time" types with a more liberal DateTime parser
that detects the format automatically:
::
use Doctrine\DBAL\Types\Type;
Type::overrideType('datetime', 'Doctrine\DBAL\Types\VarDateTime');
Type::overrideType('datetimetz', 'Doctrine\DBAL\Types\VarDateTime');
Type::overrideType('time', 'Doctrine\DBAL\Types\VarDateTime');
Timezones and DateTimeTz
~~~~~~~~~~~~~~~~~~~~~~~~
Postgres does not save the actual Timezone Name but UTC-Offsets.
The difference is subtle but can be potentially very nasty. Derick
Rethans explains it very well
`in a blog post of his <http://derickrethans.nl/storing-date-time-in-database.html>`_.
MySQL
-----
DateTimeTz
~~~~~~~~~~
MySQL does not support saving timezones or offsets. The DateTimeTz
type therefore behave like the DateTime type.
Sqlite
------
DateTimeTz
~~~~~~~~~~
Sqlite does not support saving timezones or offsets. The DateTimeTz
type therefore behave like the DateTime type.
IBM DB2
-------
DateTimeTz
~~~~~~~~~~
DB2 does not save the actual Timezone Name but UTC-Offsets. The
difference is subtle but can be potentially very nasty. Derick
Rethans explains it very well
`in a blog post of his <http://derickrethans.nl/storing-date-time-in-database.html>`_.
Oracle
------
DateTimeTz
~~~~~~~~~~
Oracle does not save the actual Timezone Name but UTC-Offsets. The
difference is subtle but can be potentially very nasty. Derick
Rethans explains it very well
`in a blog post of his <http://derickrethans.nl/storing-date-time-in-database.html>`_.
OCI8: SQL Queries with Question Marks
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We had to implement a question mark to named parameter translation
inside the OCI8 DBAL Driver. It works as a very simple parser with two states: Inside Literal, Outside Literal.
From our perspective it should be working in all cases, but you have to be careful with certain
queries:
.. code-block:: sql
SELECT * FROM users WHERE name = 'bar?'
Could in case of a bug with the parser be rewritten into:
.. code-block:: sql
SELECT * FROM users WHERE name = 'bar:oci1'
For this reason you should always use prepared statements with
Oracle OCI8, never use string literals inside the queries. A query
for the user 'bar?' should look like:
.. code-block:: php
$sql = 'SELECT * FROM users WHERE name = ?'
$stmt = $conn->prepare($sql);
$stmt->bindValue(1, 'bar?');
$stmt->execute();
OCI-LOB instances
~~~~~~~~~~~~~~~~~
Doctrine 2 always requests CLOB columns as strings, so that you as
a developer never get access to the ``OCI-LOB`` instance. Since we
are using prepared statements for all write operations inside the
ORM, using strings instead of the ``OCI-LOB`` does not cause any
problems.
Microsoft SQL Server
--------------------
Unique and NULL
~~~~~~~~~~~~~~~
Microsoft SQL Server takes Unique very seriously. There is only
ever one NULL allowed contrary to the standard where you can have
multiple NULLs in a unique column.
Platforms
=========
Platforms abstract query generation and the subtle differences of
the supported database vendors. In most cases you don't need to
interact with the ``Doctrine\DBAL\Platforms`` package a lot, but
there might be certain cases when you are programming database
independent where you want to access the platform to generate
queries for you.
The platform can be accessed from any ``Doctrine\DBAL\Connection``
instance by calling the ``getDatabasePlatform()`` method.
::
<?php
$platform = $conn->getDatabasePlatform();
Each database driver has a platform associated with it by default.
Several drivers also share the same platform, for example PDO\_OCI
and OCI8 share the ``OraclePlatform``.
If you want to overwrite parts of your platform you can do so when
creating a connection. There is a ``platform`` option you can pass
an instance of the platform you want the connection to use:
::
<?php
$myPlatform = new MyPlatform();
$options = array(
'driver' => 'pdo_sqlite',
'path' => 'database.sqlite',
'platform' => $myPlatform
);
$conn = DriverManager::getConnection($options);
This way you can optimize your schema or generated SQL code with
features that might not be portable for instance, however are
required for your special needs. This can include using triggers or
views to simulate features or adding behaviour to existing SQL
functions.
Platforms are also responsible to know which database type
translates to which PHP Type. This is a very tricky issue across
all the different database vendors, for example MySQL BIGINT and
Oracle NUMBER should be handled as integer. Doctrine 2 offers a
powerful way to abstract the database to php and back conversion,
which is described in the next section.
Portability
===========
There are often cases when you need to write an application or library that is portable
across multiple different database vendors. The Doctrine ORM is one example of such
a library. It is an abstraction layer over all the currently supported vendors (MySQL, Oracle,
PostgreSQL, SQLite and MSSQL). If you want to use the DBAL to write a portable application
or library you have to follow lots of rules to make all the different vendors work the
same.
There are many different layers that you need to take care of, here is a quick list:
1. Returning of data is handled differently across vendors.
Oracle converts empty strings to NULL, which means a portable application
needs to convert all empty strings to null.
2. Additionally some vendors pad CHAR columns to their length, whereas others don't.
This means all strings returned from a database have to be passed through ``rtrim()``.
3. Case-sensitivity of column keys is handled differently in all databases, even depending
on identifier quoting or not. You either need to know all the rules or fix the cases
to lower/upper-case only.
4. ANSI-SQL is not implemented fully by the different vendors. You have to make
sure that the SQL you write is supported by all the vendors you are targeting.
5. Some vendors use sequences for identity generation, some auto-increment approaches.
Both are completely different (pre- and post-insert access) and therefore need
special handling.
6. Every vendor has a list of keywords that are not allowed inside SQL. Some even
allow a subset of their keywords, but not at every position.
7. Database types like dates, long text fields, booleans and many others are handled
very differently between the vendors.
8. There are differences with the regard to support of positional, named or both styles of parameters
in prepared statements between all vendors.
For each point in this list there are different abstraction layers in Doctrine DBAL that you
can use to write a portable application.
Connection Wrapper
------------------
This functionality is only implemented with Doctrine 2.1 upwards.
To handle all the points 1-3 you have to use a special wrapper around the database
connection. The handling and differences to tackle are all taken from the great
`PEAR MDB2 library <http://pear.php.net/package/MDB2/redirected>`_.
Using the following code block in your initialization will:
* ``rtrim()`` all strings if necessary
* Convert all empty strings to null
* Return all associative keys in lower-case, using PDO native functionality or implemented in PHP userland (OCI8).
.. code-block:: php
<?php
$params = array(
// vendor specific configuration
//...
'wrapperClass' => 'Doctrine\DBAL\Portability\Connection',
'portability' => \Doctrine\DBAL\Portability\Connection::PORTABILITY_ALL,
'fetch_case' => \PDO::CASE_LOWER,
);
This sort of portability handling is pretty expensive because all the result
rows and columns have to be looped inside PHP before being returned to you.
This is why by default Doctrine ORM does not use this compability wrapper but
implements another approach to handle assoc-key casing and ignores the other
two issues.
Database Platform
-----------------
Using the database platform you can generate bits of SQL for you, specifically
in the area of SQL functions to achieve portability. You should have a look
at all the different methods that the platforms allow you to access.
Keyword Lists
-------------
This functionality is only implemented with Doctrine 2.1 upwards.
Doctrine ships with lists of keywords for every supported vendor. You
can access a keyword list through the schema manager of the vendor you
are currently using or just instantiating it from the ``Doctrine\DBAL\Platforms\Keywords``
namespace.
\ No newline at end of file
SQL Query Builder
=================
Doctrine 2.1 ships with a powerful query builder for the SQL language. This QueryBuilder object has methods
to add parts to an SQL statement. If you built the complete state you can execute it using the connection
it was generated from. The API is roughly the same as that of the DQL Query Builder.
You can access the QueryBuilder by calling ``Doctrine\DBAL\Connection#createQueryBuilder``:
.. code-block:: php
<?php
$conn = DriverManager::getConnection(array(/*..*/));
$queryBuilder = $conn->createQueryBuilder();
Schema-Manager
==============
A Schema Manager instance helps you with the abstraction of the
generation of SQL assets such as Tables, Sequences, Foreign Keys
and Indexes.
To retrieve the ``SchemaManager`` for your connection you can use
the ``getSchemaManager()`` method:
.. code-block:: php
<?php
$sm = $conn->getSchemaManager();
Now with the ``SchemaManager`` instance in ``$em`` you can use the
available methods to learn about your database schema:
.. note::
Parameters containing identifiers passed to the SchemaManager
methods are *NOT* quoted automatically! Identifier quoting is
really difficult to do manually in a consistent way across
different databases. You have to manually quote the identifiers
when you accept data from user- or other sources not under your
control.
listDatabases()
---------------
Retrieve an array of databases on the configured connection:
.. code-block:: php
<?php
$databases = $sm->listDatabases();
listSequences()
-------------------------------
Retrieve an array of ``Doctrine\DBAL\Schema\Sequence`` instances
that exist for a database:
.. code-block:: php
<?php
$sequences = $sm->listSequences();
Or if you want to manually specify a database name:
.. code-block:: php
<?php
$sequences = $sm->listSequences('dbname');
Now you can loop over the array inspecting each sequence object:
.. code-block:: php
<?php
foreach ($sequences as $sequence) {
echo $sequence->getName() . "\n";
}
listTableColumns()
----------------------------
Retrieve an array of ``Doctrine\DBAL\Schema\Column`` instances that
exist for the given table:
.. code-block:: php
<?php
$columns = $sm->listTableColumns('user');
Now you can loop over the array inspecting each column object:
.. code-block:: php
<?php
foreach ($columns as $column) {
echo $column->getName() . ': ' . $column->getType() . "\n";
}
listTableDetails()
----------------------------
Retrieve a single ``Doctrine\DBAL\Schema\Table`` instance that
encapsulates all the details of the given table:
.. code-block:: php
<?php
$table = $sm->listTableDetails('user');
Now you can call methods on the table to manipulate the in memory
schema for that table. For example we can add a new column:
.. code-block:: php
<?php
$table->addColumn('email_address', 'string');
listTableForeignKeys()
--------------------------------
Retrieve an array of ``Doctrine\DBAL\Schema\ForeignKeyConstraint``
instances that exist for the given table:
.. code-block:: php
<?php
$foreignKeys = $sm->listTableForeignKeys('user');
Now you can loop over the array inspecting each foreign key
object:
.. code-block:: php
<?php
foreach ($foreignKeys as $foreignKey) {
echo $foreignKey->getName() . ': ' . $foreignKey->getLocalTableName() ."\n";
}
listTableIndexes()
----------------------------
Retrieve an array of ``Doctrine\DBAL\Schema\Index`` instances that
exist for the given table:
.. code-block:: php
<?php
$indexes = $sm->listTableIndexes('user');
Now you can loop over the array inspecting each index object:
.. code-block:: php
<?php
foreach ($indexes as $index) {
echo $index->getName() . ': ' . ($index->isUnique() ? 'unique' : 'not unique') . "\n";
}
listTables()
------------
Retrieve an array of ``Doctrine\DBAL\Schema\Table`` instances that
exist in the connections database:
.. code-block:: php
<?php
$tables = $sm->listTables();
Each ``Doctrine\DBAl\Schema\Table`` instance is populated with
information provided by all the above methods. So it encapsulates
an array of ``Doctrine\DBAL\Schema\Column`` instances that can be
retrieved with the ``getColumns()`` method:
.. code-block:: php
<?php
foreach ($tables as $table) {
echo $table->getName() . " columns:\n\n";
foreach ($table->getColumns() as $column) {
echo ' - ' . $column->getName() . "\n";
}
}
listViews()
-----------
Retrieve an array of ``Doctrine\DBAL\Schema\View`` instances that
exist in the connections database:
.. code-block:: php
<?php
$views = $sm->listViews();
Now you can loop over the array inspecting each view object:
.. code-block:: php
<?php
foreach ($views as $view) {
echo $view->getName() . ': ' . $view->getSql() . "\n";
}
createSchema()
--------------
For a complete representation of the current database you can use
the ``createSchema()`` method which returns an instance of
``Doctrine\DBAL\Schema\Schema``, which you can use in conjunction
with the SchemaTool or Schema Comparator.
.. code-block:: php
<?php
$fromSchema = $sm->createSchema();
Now we can clone the ``$fromSchema`` to ``$toSchema`` and drop a
table:
.. code-block:: php
<?php
$toSchema = clone $fromSchema;
$toSchema->dropTable('user');
Now we can compare the two schema instances in order to calculate
the differences between them and return the SQL required to make
the changes on the database:
.. code-block:: php
<?php
$sql = $fromSchema->getMigrateToSql($toSchema, $conn->getDatabasePlatform());
The ``$sql`` array should give you a SQL query to drop the user
table:
.. code-block:: php
<?php
print_r($sql);
/*
array(
0 => 'DROP TABLE user'
)
*/
Schema-Representation
=====================
Doctrine has a very powerful abstraction of database schemas. It
offers an object-oriented representation of a database schema with
support for all the details of Tables, Sequences, Indexes and
Foreign Keys. These Schema instances generate a representation that
is equal for all the supported platforms. Internally this
functionality is used by the ORM Schema Tool to offer you create,
drop and update database schema methods from your Doctrine ORM
Metadata model. Up to very specific functionality of your database
system this allows you to generate SQL code that makes your Domain
model work.
You will be pleased to hear, that Schema representation is
completly decoupled from the Doctrine ORM though, that is you can
also use it in any other project to implement database migrations
or for SQL schema generation for any metadata model that your
application has. You can easily generate a Schema, as a simple
example shows:
.. code-block:: php
<?php
$schema = new \Doctrine\DBAL\Schema\Schema();
$myTable = $schema->createTable("my_table");
$myTable->addColumn("id", "integer", array("unsigned" => true));
$myTable->addColumn("username", "string", array("length" => 32));
$myTable->setPrimaryKey(array("id"));
$myTable->addUniqueIndex(array("username"));
$schema->createSequence("my_table_seq");
$myForeign = $schema->createTable("my_foreign");
$myForeign->addColumn("id", "integer");
$myForeign->addColumn("user_id", "integer");
$myForeign->addForeignKeyConstraint($myTable, array("user_id"), array("id"), array("onUpdate" => "CASCADE"));
$queries = $schema->toSql($myPlatform); // get queries to create this schema.
$dropSchema = $schema->toDropSql($myPlatform); // get queries to safely delete this schema.
Now if you want to compare this schema with another schema, you can
use the ``Comparator`` class to get instances of ``SchemaDiff``,
``TableDiff`` and ``ColumnDiff``, as well as information about other
foreign key, sequence and index changes.
.. code-block:: php
<?php
$comparator = new \Doctrine\DBAL\Schema\Comparator();
$schemaDiff = $comparator->compare($fromSchema, $toSchema);
$queries = $schemaDiff->toSql($myPlatform); // queries to get from one to another schema.
$saveQueries = $schemaDiff->toSaveSql($myPlatform);
The Save Diff mode is a specific mode that prevents the deletion of
tables and sequences that might occour when making a diff of your
schema. This is often necessary when your target schema is not
complete but only describes a subset of your application.
All methods that generate SQL queries for you make much effort to
get the order of generation correct, so that no problems will ever
occour with missing links of foreign keys.
Security
========
Allowing users of your website to communicate with a database can possibly have security implications
that you should be aware of. Databases allow very powerful commands that not every user of your website
should be able to execute. Additionally the data in your database probably contains information that
should not be visible to everyone with access to the website.
The most dangerous security problem with regard to databases is the possibility of SQL injections.
An SQL injection security hole allows an attacker to execute new or modify existing SQL statements to
access information that he is not allowed to access.
Neither Doctrine DBAL nor ORM can prevent such attacks if you are careless as a developer. This section
explains to you the problems of SQL injection and how to prevent them.
User input in your queries
--------------------------
A database application necessarily requires user-input to passed to your queries.
There are wrong and right ways to do this and is very important to be very strict about this:
Wrong: String Concatenation
~~~~~~~~~~~~~~~~~~~~~~~~~~~
You should never ever build your queries dynamically and concatenate user-input into your
SQL or DQL query. For Example:
.. code-block:: php
<?php
// Very wrong!
$sql = "SELECT * FROM users WHERE name = '" . $_GET['username']. "'";
An attacker could inject any value into the GET variable "username" to modify the query to his needs.
Although DQL is a wrapper around SQL that can prevent you from some security implications, the previous
example is also a thread to DQL queries.
<?php
// DQL is not safe against arbitrary user-input as well:
$dql = "SELECT u FROM User u WHERE u.username = '" . $_GET['username'] . "'";
In this scenario an attacker could still pass a username set to "' OR 1 = 1" and create a valid DQL query.
Although DQL will make use of quoting functions when literals are used in a DQL statement, allowing
the attacker to modify the DQL statement with valid literals cannot be detected by the DQL parser, it
is your responsibility.
Right: Prepared Statements
~~~~~~~~~~~~~~~~~~~~~~~~~~
You should always use prepared statements to execute your queries. Prepared statements is a two-step
procedure, separating SQL query from the parameters. They are supported (and encouraged) for both
DBAL SQL queries and for ORM DQL queries.
Instead of using string concatenation to insert user-input into your SQL/DQL statements you just specify
either placeholders instead and then explain to the database driver which variable should be bound to
which placeholder. Each database vendor supports different placeholder styles:
- All PDO Drivers support positional (using question marks) and named placeholders (:param1, :foo, :bar).
- OCI8 only supports named parameters, but Doctrine DBAL has a thin layer around OCI8 and
also allows positional placeholders.
- Doctrine ORM DQL allows both named and positional parameters. The positional parameters however are not
just question marks, but suffixed with a number (?1, ?2, ?3, ...).
Following are examples of using prepared statements with SQL and DQL:
.. code-block:: php
<?php
// SQL Prepared Statements: Positional
$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $connection->prepare($sql);
$stmt->bindValue(1, $_GET['username']);
$stmt->execute();
// SQL Prepared Statements: Named
$sql = "SELECT * FROM users WHERE username = :user";
$stmt = $connection->prepare($sql);
$stmt->bindValue("user", $_GET['username']);
$stmt->execute();
// DQL Prepared Statements: Positional
$dql = "SELECT u FROM User u WHERE u.username = ?1";
$query = $em->createQuery($dql);
$query->setParameter(1, $_GET['username']);
$data = $query->getResult();
// DQL Prepared Statements: Named
$dql = "SELECT u FROM User u WHERE u.username = :name";
$query = $em->createQuery($dql);
$query->setParameter("name", $_GET['username']);
$data = $query->getResult();
You can see this is a bit more tedious to write, but this is the only way to write secure queries. If you
are using just the DBAL there are also helper methods which simplify the usage quite alot:
.. code-block:: php
<?php
// bind parameters and execute query at once.
$sql = "SELECT * FROM users WHERE username = ?";
$stmt = $connection->executeQuery($sql, array($_GET['username']));
There is also ``executeUpdate`` which does not return a statement but the number of affected rows.
Besides binding parameters you can also pass the type of the variable. This allows Doctrine or the underyling
vendor to not only escape but also cast the value to the correct type. See the docs on querying and DQL in the
respective chapters for more information.
Right: Quoting/Escaping values
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Although previously we said string concatenation is wrong, there is a way to do it correctly using
the ``Connection#quote`` method:
.. code-block:: php
<?php
// Parameter quoting
$sql = "SELECT * FROM users WHERE name = " . $connection->quote($_GET['username'], \PDO::PARAM_STR);
This method is only available for SQL, not for DQL. For DQL it is always encouraged to use prepared
statements not only for security, but also for caching reasons.
Non-ASCII compatible Charsets in MySQL
--------------------------------------
Up until PHP 5.3.6 PDO has a security problem when using non ascii compatible charsets. Even if specifying
the charset using "SET NAMES", emulated prepared statements and ``PDO#quote`` could not reliably escape
values, opening up to potential SQL injections. If you are running PHP 5.3.6 you can solve this issue
by passing the driver option "charset" to Doctrine PDO MySQL driver. Using SET NAMES does not suffice!
.. code-block::
<?php
$conn = DriverManager::getConnection(array(
'driver' => 'pdo_mysql',
'charset' => 'UTF8',
));
This diff is collapsed.
This diff is collapsed.
Supporting Other Databases
==========================
To support a database which is not currently shipped with Doctrine
you have to implement the following interfaces and abstract
classes:
- ``\Doctrine\DBAL\Driver\Driver``
- ``\Doctrine\DBAL\Driver\Statement``
- ``\Doctrine\DBAL\Platforms\AbstractPlatform``
- ``\Doctrine\DBAL\Schema\AbstractSchemaManager``
For an already supported platform but unsupported driver you only
need to implement the first two interfaces, since the SQL
Generation and Schema Management is already supported by the
respective platform and schema instances. You can also make use of
several Abstract Unittests in the ``\Doctrine\Tests\DBAL`` package
to check if your platform behaves like all the others which is
necessary for SchemaTool support, namely:
- ``\Doctrine\Tests\DBAL\Platforms\AbstractPlatformTestCase``
- ``\Doctrine\Tests\DBAL\Functional\Schema\AbstractSchemaManagerTestCase``
We would be very happy if any support for new databases would be
contributed back to Doctrine to make it an even better product.
Implementation Steps in Detail
------------------------------
1. Add your driver shortcut to class-name `Doctrine\DBAL\DriverManager`.
2. Make a copy of tests/dbproperties.xml.dev and adjust the values to your driver shortcut and testdatabase.
3. Create three new classes implementing ``\Doctrine\DBAL\Driver\Driver``, ``\Doctrine\DBAL\Driver\Statement``
and ``Doctrine\DBAL\Driver``. You can take a look at the ``Doctrine\DBAL\Driver\OCI8`` driver.
4. You can run the testsuite of your new database driver by calling "cd tests/ && phpunit -c myconfig.xml Doctrine/Tess/AllTests.php"
5. Start implementing AbstractPlatform and AbstractSchemaManager. Other implementations should serve as good example.
\ No newline at end of file
Transactions
============
A ``Doctrine\DBAL\Connection`` provides a PDO-like API for
transaction management, with the methods
``Connection#beginTransaction()``, ``Connection#commit()`` and
``Connection#rollback()``.
Transaction demarcation with the Doctrine DBAL looks as follows:
::
<?php
$conn->beginTransaction();
try{
// do stuff
$conn->commit();
} catch(Exception $e) {
$conn->rollback();
throw $e;
}
Alternatively, the control abstraction
``Connection#transactional($func)`` can be used to make the code
more concise and to make sure you never forget to rollback the
transaction in the case of an exception. The following code snippet
is functionally equivalent to the previous one:
::
<?php
$conn->transactional(function($conn) {
// do stuff
});
The ``Doctrine\DBAL\Connection`` also has methods to control the
transaction isolation level as supported by the underlying
database. ``Connection#setTransactionIsolation($level)`` and
``Connection#getTransactionIsolation()`` can be used for that purpose.
The possible isolation levels are represented by the following
constants:
::
<?php
Connection::TRANSACTION_READ_UNCOMMITTED
Connection::TRANSACTION_READ_COMMITTED
Connection::TRANSACTION_REPEATABLE_READ
Connection::TRANSACTION_SERIALIZABLE
The default transaction isolation level of a
``Doctrine\DBAL\Connection`` is chosen by the underlying platform
but it is always at least READ\_COMMITTED.
Transaction Nesting
-------------------
A ``Doctrine\DBAL\Connection`` also adds support for nesting
transactions, or rather propagating transaction control up the call
stack. For that purpose, the ``Connection`` class keeps an internal
counter that represents the nesting level and is
increased/decreased as ``beginTransaction()``, ``commit()`` and
``rollback()`` are invoked. ``beginTransaction()`` increases the
nesting level whilst
``commit()`` and ``rollback()`` decrease the nesting level. The nesting level starts at 0. Whenever the nesting level transitions from 0 to 1, ``beginTransaction()`` is invoked on the underlying driver connection and whenever the nesting level transitions from 1 to 0, ``commit()`` or ``rollback()`` is invoked on the underlying driver, depending on whether the transition was caused by ``Connection#commit()`` or ``Connection#rollback()``.
What this means is that transaction control is basically passed to
code higher up in the call stack and the inner transaction block is
ignored, with one important exception that is described further
below. Do not confuse this with "real" nested transactions or
savepoints. These are not supported by Doctrine. There is always
only a single, real database transaction.
To visualize what this means in practice, consider the following
example:
::
<?php
// $conn instanceof Doctrine\DBAL\Connection
$conn->beginTransaction(); // 0 => 1, "real" transaction started
try {
...
// nested transaction block, this might be in some other API/library code that is
// unaware of the outer transaction.
$conn->beginTransaction(); // 1 => 2
try {
...
$conn->commit(); // 2 => 1
} catch (Exception $e) {
$conn->rollback(); // 2 => 1, transaction marked for rollback only
throw $e;
}
...
$conn->commit(); // 1 => 0, "real" transaction committed
} catch (Exception $e) {
$conn->rollback(); // 1 => 0, "real" transaction rollback
throw $e;
}
However,
**a rollback in a nested transaction block will always mark the current transaction so that the only possible outcome of the transaction is to be rolled back**.
That means in the above example, the rollback in the inner
transaction block marks the whole transaction for rollback only.
Even if the nested transaction block would not rethrow the
exception, the transaction is marked for rollback only and the
commit of the outer transaction would trigger an exception, leading
to the final rollback. This also means that you can not
successfully commit some changes in an outer transaction if an
inner transaction block fails and issues a rollback, even if this
would be the desired behavior (i.e. because the nested operation is
"optional" for the purpose of the outer transaction block). To
achieve that, you need to restructure your application logic so as
to avoid nesting transaction blocks. If this is not possible
because the nested transaction blocks are in a third-party API
you're out of luck.
All that is guaruanteed to the inner transaction is that it still
happens atomically, all or nothing, the transaction just gets a
wider scope and the control is handed to the outer scope.
.. note::
The transaction nesting described here is a debated
feature that has its critics. Form your own opinion. We recommend
avoiding nesting transaction blocks when possible, and most of the
time, it is possible. Transaction control should mostly be left to
a service layer and not be handled in data access objects or
similar.
.. warning::
Directly invoking ``PDO#beginTransaction()``,
``PDO#commit()`` or ``PDO#rollback()`` or the corresponding methods
on the particular ``Doctrine\DBAL\Driver\Connection`` instance in
use bypasses the transparent transaction nesting that is provided
by ``Doctrine\DBAL\Connection`` and can therefore corrupt the
nesting level, causing errors with broken transaction boundaries
that may be hard to debug.
Types
=====
Besides abstraction of SQL one needs a translation between database
and PHP data-types to implement database independent applications.
Doctrine 2 has a type translation system baked in that supports the
conversion from and to PHP values from any database platform,
as well as platform independent SQL generation for any Doctrine
Type.
Using the ORM you generally don't need to know about the Type
system. This is unless you want to make use of database vendor
specific database types not included in Doctrine 2. The following
PHP Types are abstracted across all the supported database
vendors:
- Integer
- SmallInt
- BigInt
- String (string with maximum length, for example 255)
- Text (strings without maximum length)
- Decimal (restricted floats, *NOTE* Only works with a setlocale()
configuration that uses decimal points!)
- Boolean
- DateTime
- Date (DateTime instance where only Y-m-d get persisted)
- Time (DateTime instance where only H:i:s get persisted)
- Array (serialized into a text field for all vendors by default)
- Object (serialized into a text field for all vendors by default)
- Float (*NOTE* Only works with a setlocale() configuration that
uses decimal points!)
Types are flyweights. This means there is only ever one instance of
a type and it is not allowed to contain any state. Creation of type
instances is abstracted through a static get method
``Doctrine\DBAL\Types\Type::getType()``.
.. note::
See the `Known Vendor Issue <./../known-vendor-issues>`_ section
for details about the different handling of microseconds and
timezones across all the different vendors.
.. 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``.
If you need specific timezone handling you have to handle this
in your domain, converting all the values back and forth from UTC.
Detection of Database Types
---------------------------
When calling table inspection methods on your connections
``SchemaManager`` instance the retrieved database column types are
translated into Doctrine mapping types. Translation is necessary to
allow database abstraction and metadata comparisons for example for
Migrations or the ORM SchemaTool.
Each database platform has a default mapping of database types to
Doctrine types. You can inspect this mapping for platform of your
choice looking at the
``AbstractPlatform::initializeDoctrineTypeMappings()``
implementation.
If you want to change how Doctrine maps a database type to a
``Doctrine\DBAL\Types\Type`` instance you can use the
``AbstractPlatform::registerDoctrineTypeMapping($dbType, $doctrineType)``
method to add new database types or overwrite existing ones.
.. note::
You can only map a database type to exactly one Doctrine type.
Database vendors that allow to define custom types like PostgreSql
can help to overcome this issue.
Custom Mapping Types
--------------------
Just redefining how database types are mapped to all the existing
Doctrine types is not at all that useful. You can define your own
Doctrine Mapping Types by extending ``Doctrine\DBAL\Types\Type``.
You are required to implement 4 different methods to get this
working.
See this example of how to implement a Money object in PostgreSQL.
For this we create the type in PostgreSQL as:
.. code-block:: sql
CREATE DOMAIN MyMoney AS DECIMAL(18,3);
Now we implement our ``Doctrine\DBAL\Types\Type`` instance:
::
<?php
namespace My\Project\Types;
use Doctrine\DBAL\Types\Type;
use Doctrine\DBAL\Platforms\AbstractPlatform;
/**
* My custom datatype.
*/
class MoneyType extends Type
{
const MONEY = 'money'; // modify to match your type name
public function getSqlDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return 'MyMoney';
}
public function convertToPHPValue($value, AbstractPlatform $platform)
{
return new Money($value);
}
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
return $value->toDecimal();
}
public function getName()
{
return self::MONEY;
}
}
The job of Doctrine-DBAL is to transform your type into SQL declaration. You can modify the SQL declaration Doctrine will produce. At first, you must to enable this feature by overriding the canRequireSQLConversion method:
::
<?php
public function canRequireSQLConversion()
{
return true;
}
Then you override the methods convertToPhpValueSQL and convertToDatabaseValueSQL :
::
<?php
public function convertToPHPValueSQL($sqlExpr, $platform)
{
return 'MyMoneyFunction(\''.$sqlExpr.'\') ';
}
public function convertToDatabaseValueSQL($sqlExpr, AbstractPlatform $platform)
{
return 'MyFunction('.$sqlExpr.')';
}
Now we have to register this type with the Doctrine Type system and
hook it into the database platform:
::
<?php
Type::addType('money', 'My\Project\Types\MoneyType');
$conn->getDatabasePlatform()->registerDoctrineTypeMapping('MyMoney', 'money');
This would allow to use a money type in the ORM for example and
have Doctrine automatically convert it back and forth to the
database.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment