├── docs
├── installation.rst
├── transactions.rst
├── index.rst
├── philosophy.rst
├── connections.rst
├── models.rst
├── make.bat
├── Makefile
├── conf.py
├── configuration.rst
└── querying.rst
├── CONTRIBUTING.md
├── composer.json
├── demo.php
├── README.markdown
└── idiorm.php
/docs/installation.rst:
--------------------------------------------------------------------------------
1 | Installation
2 | ============
3 |
4 | Packagist
5 | ~~~~~~~~~
6 |
7 | This library is available through Packagist with the vendor and package
8 | identifier of ``j4mie/idiorm``
9 |
10 | Please see the `Packagist documentation`_ for further information.
11 |
12 | Download
13 | ~~~~~~~~
14 |
15 | You can clone the git repository, download idiorm.php or a release tag
16 | and then drop the idiorm.php file in the vendors/3rd party/libs
17 | directory of your project.
18 |
19 | .. _Packagist documentation: http://packagist.org/
--------------------------------------------------------------------------------
/docs/transactions.rst:
--------------------------------------------------------------------------------
1 | Transactions
2 | ============
3 |
4 | Idiorm doesn’t supply any extra methods to deal with transactions, but
5 | it’s very easy to use PDO’s built-in methods:
6 |
7 | .. code-block:: php
8 |
9 | beginTransaction();
12 |
13 | // Commit a transaction
14 | ORM::get_db()->commit();
15 |
16 | // Roll back a transaction
17 | ORM::get_db()->rollBack();
18 |
19 | For more details, see `the PDO documentation on Transactions`_.
20 |
21 | .. _the PDO documentation on Transactions: https://secure.php.net/manual/en/pdo.transactions.php
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. Idiorm documentation master file, created by
2 | sphinx-quickstart on Wed Nov 28 15:39:16 2012.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | Welcome to Idiorm's documentation!
7 | ==================================
8 |
9 | Contents:
10 |
11 | .. toctree::
12 | :maxdepth: 2
13 |
14 | philosophy
15 | installation
16 | configuration
17 | querying
18 | models
19 | transactions
20 | connections
21 |
22 |
23 | Indices and tables
24 | ==================
25 |
26 | * :ref:`genindex`
27 | * :ref:`modindex`
28 | * :ref:`search`
29 |
30 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | ---
2 | ### Feature complete
3 |
4 | Idiorm is now considered to be feature complete as of version 1.5.0. Whilst it will continue to be maintained with bug fixes there will be no further new features added.
5 |
6 | **Please do not submit feature requests or pull requests adding new features as they will be closed without ceremony.**
7 |
8 | ---
9 |
10 | When making a pull request please include the following aspects:
11 |
12 | - Update the changelog in the README.markdown file to include details of the pull request
13 | - If the documentation in the README or Sphinx docs needs to be amended please do so in the pull request
14 | - Include unit tests for any changes - if it is a bug include at least one regression test
15 |
16 |
17 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "j4mie/idiorm",
3 | "type": "library",
4 | "description": "A lightweight nearly-zero-configuration object-relational mapper and fluent query builder for PHP5",
5 | "keywords": ["idiorm", "orm", "query builder"],
6 | "homepage": "http://j4mie.github.com/idiormandparis",
7 | "support": {
8 | "issues": "https://github.com/j4mie/idiorm/issues",
9 | "source": "https://github.com/j4mie/idiorm"
10 | },
11 | "authors": [
12 | {
13 | "name": "Jamie Matthews",
14 | "email": "jamie.matthews@gmail.com",
15 | "homepage": "http://j4mie.org",
16 | "role": "Developer"
17 | },
18 | {
19 | "name": "Simon Holywell",
20 | "email": "treffynnon@php.net",
21 | "homepage": "http://simonholywell.com",
22 | "role": "Maintainer"
23 | },
24 | {
25 | "name": "Durham Hale",
26 | "email": "me@durhamhale.com",
27 | "homepage": "http://durhamhale.com",
28 | "role": "Maintainer"
29 | }
30 | ],
31 | "scripts": {
32 | "test": "phpunit -c ./phpunit.xml"
33 | },
34 | "require-dev": {
35 | "phpunit/phpunit": "^4.8",
36 | "ext-pdo_sqlite": "*"
37 | },
38 | "license": [
39 | "BSD-2-Clause",
40 | "BSD-3-Clause",
41 | "BSD-4-Clause"
42 | ],
43 | "require": {
44 | "php": ">=5.2.0"
45 | },
46 | "autoload": {
47 | "classmap": ["idiorm.php"]
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/docs/philosophy.rst:
--------------------------------------------------------------------------------
1 | Philosophy
2 | ==========
3 |
4 | The `Pareto Principle`_ states that *roughly 80% of the effects come
5 | from 20% of the causes.* In software development terms, this could be
6 | translated into something along the lines of *80% of the results come
7 | from 20% of the complexity*. In other words, you can get pretty far by
8 | being pretty stupid.
9 |
10 | **Idiorm is deliberately simple**. Where other ORMs consist of dozens of
11 | classes with complex inheritance hierarchies, Idiorm has only one class,
12 | ``ORM``, which functions as both a fluent ``SELECT`` query API and a
13 | simple CRUD model class. If my hunch is correct, this should be quite
14 | enough for many real-world applications. Let’s face it: most of us
15 | aren’t building Facebook. We’re working on small-to-medium-sized
16 | projects, where the emphasis is on simplicity and rapid development
17 | rather than infinite flexibility and features.
18 |
19 | You might think of **Idiorm** as a *micro-ORM*. It could, perhaps, be
20 | “the tie to go along with `Slim`_\ ’s tux” (to borrow a turn of phrase
21 | from `DocumentCloud`_). Or it could be an effective bit of spring
22 | cleaning for one of those horrendous SQL-littered legacy PHP apps you
23 | have to support.
24 |
25 | **Idiorm** might also provide a good base upon which to build
26 | higher-level, more complex database abstractions. For example, `Paris`_
27 | is an implementation of the `Active Record pattern`_ built on top of
28 | Idiorm.
29 |
30 | .. _Pareto Principle: http://en.wikipedia.org/wiki/Pareto_principle
31 | .. _Slim: http://github.com/codeguy/slim/
32 | .. _DocumentCloud: http://github.com/documentcloud/underscore
33 | .. _Paris: http://github.com/j4mie/paris
34 | .. _Active Record pattern: http://martinfowler.com/eaaCatalog/activeRecord.html
--------------------------------------------------------------------------------
/demo.php:
--------------------------------------------------------------------------------
1 | exec("
23 | CREATE TABLE IF NOT EXISTS contact (
24 | id INTEGER PRIMARY KEY,
25 | name TEXT,
26 | email TEXT
27 | );"
28 | );
29 |
30 | // Handle POST submission
31 | if (!empty($_POST)) {
32 |
33 | // Create a new contact object
34 | $contact = ORM::for_table('contact')->create();
35 |
36 | // SHOULD BE MORE ERROR CHECKING HERE!
37 |
38 | // Set the properties of the object
39 | $contact->name = $_POST['name'];
40 | $contact->email = $_POST['email'];
41 |
42 | // Save the object to the database
43 | $contact->save();
44 |
45 | // Redirect to self.
46 | header('Location: ' . basename(__FILE__));
47 | exit;
48 | }
49 |
50 | // Get a list of all contacts from the database
51 | $count = ORM::for_table('contact')->count();
52 | $contact_list = ORM::for_table('contact')->find_many();
53 | ?>
54 |
55 |
56 |
73 |
74 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/docs/connections.rst:
--------------------------------------------------------------------------------
1 | Multiple Connections
2 | ====================
3 | Idiorm now works with multiple conections. Most of the static functions
4 | work with an optional connection name as an extra parameter. For the
5 | ``ORM::configure`` method, this means that when passing connection
6 | strings for a new connection, the second parameter, which is typically
7 | omitted, should be ``null``. In all cases, if a connection name is not
8 | provided, it defaults to ``ORM::DEFAULT_CONNECTION``.
9 |
10 | When chaining, once ``for_table()`` has been used in the chain, remaining
11 | calls in the chain use the correct connection.
12 |
13 | .. code-block:: php
14 |
15 | find_one(5);
26 |
27 | // Using default connection, explicitly
28 | $person = ORM::for_table('person', ORM::DEFAULT_CONNECTION)->find_one(5);
29 |
30 | // Using named connection
31 | $person = ORM::for_table('different_person', 'remote')->find_one(5);
32 |
33 |
34 |
35 | Supported Methods
36 | ^^^^^^^^^^^^^^^^^
37 | In each of these cases, the ``$connection_name`` parameter is optional, and is
38 | an arbitrary key identifying the named connection.
39 |
40 | * ``ORM::configure($key, $value, $connection_name)``
41 | * ``ORM::for_table($table_name, $connection_name)``
42 | * ``ORM::set_db($pdo, $connection_name)``
43 | * ``ORM::get_db($connection_name)``
44 | * ``ORM::raw_execute($query, $parameters, $connection_name)``
45 | * ``ORM::get_last_query($connection_name)``
46 | * ``ORM::get_query_log($connection_name)``
47 |
48 | Of these methods, only ``ORM::get_last_query($connection_name)`` does *not*
49 | fallback to the default connection when no connection name is passed.
50 | Instead, passing no connection name (or ``null``) returns the most recent
51 | query on *any* connection.
52 |
53 | .. code-block:: php
54 |
55 | find_one(5);
58 |
59 | // Using named connection
60 | $person = ORM::for_table('different_person', 'remote')->find_one(5);
61 |
62 | // Last query on *any* connection
63 | ORM::get_last_query(); // returns query on 'different_person' using 'remote'
64 |
65 | // returns query on 'person' using default by passing in the connection name
66 | ORM::get_last_query(ORM::DEFAULT_CONNECTION);
67 |
68 | Notes
69 | ~~~~~
70 | * **There is no support for joins across connections**
71 | * Multiple connections do not share configuration settings. This means if
72 | one connection has logging set to ``true`` and the other does not, only
73 | queries from the logged connection will be available via
74 | ``ORM::get_last_query()`` and ``ORM::get_query_log()``.
75 | * A new method has been added, ``ORM::get_connection_names()``, which returns
76 | an array of connection names.
77 | * Caching *should* work with multiple connections (remember to turn caching
78 | on for each connection), but the unit tests are not robust. Please report
79 | any errors.
80 |
81 |
--------------------------------------------------------------------------------
/docs/models.rst:
--------------------------------------------------------------------------------
1 | Models
2 | ======
3 |
4 | Getting data from objects
5 | ~~~~~~~~~~~~~~~~~~~~~~~~~
6 |
7 | Once you've got a set of records (objects) back from a query, you can
8 | access properties on those objects (the values stored in the columns in
9 | its corresponding table) in two ways: by using the ``get`` method, or
10 | simply by accessing the property on the object directly:
11 |
12 | .. code-block:: php
13 |
14 | find_one(5);
16 |
17 | // The following two forms are equivalent
18 | $name = $person->get('name');
19 | $name = $person->name;
20 |
21 | You can also get the all the data wrapped by an ORM instance using the
22 | ``as_array`` method. This will return an associative array mapping
23 | column names (keys) to their values.
24 |
25 | The ``as_array`` method takes column names as optional arguments. If one
26 | or more of these arguments is supplied, only matching column names will
27 | be returned.
28 |
29 | .. code-block:: php
30 |
31 | create();
33 |
34 | $person->first_name = 'Fred';
35 | $person->surname = 'Bloggs';
36 | $person->age = 50;
37 |
38 | // Returns array('first_name' => 'Fred', 'surname' => 'Bloggs', 'age' => 50)
39 | $data = $person->as_array();
40 |
41 | // Returns array('first_name' => 'Fred', 'age' => 50)
42 | $data = $person->as_array('first_name', 'age');
43 |
44 | Updating records
45 | ~~~~~~~~~~~~~~~~
46 |
47 | To update the database, change one or more of the properties of the
48 | object, then call the ``save`` method to commit the changes to the
49 | database. Again, you can change the values of the object's properties
50 | either by using the ``set`` method or by setting the value of the
51 | property directly. By using the ``set`` method it is also possible to
52 | update multiple properties at once, by passing in an associative array:
53 |
54 | .. code-block:: php
55 |
56 | find_one(5);
58 |
59 | // The following two forms are equivalent
60 | $person->set('name', 'Bob Smith');
61 | $person->age = 20;
62 |
63 | // This is equivalent to the above two assignments
64 | $person->set(array(
65 | 'name' => 'Bob Smith',
66 | 'age' => 20
67 | ));
68 |
69 | // Syncronise the object with the database
70 | $person->save();
71 |
72 | Properties containing expressions
73 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
74 |
75 | It is possible to set properties on the model that contain database
76 | expressions using the ``set_expr`` method.
77 |
78 | .. code-block:: php
79 |
80 | find_one(5);
82 | $person->set('name', 'Bob Smith');
83 | $person->age = 20;
84 | $person->set_expr('updated', 'NOW()');
85 | $person->save();
86 |
87 | The ``updated`` column's value will be inserted into query in its raw
88 | form therefore allowing the database to execute any functions referenced
89 | - such as ``NOW()`` in this case.
90 |
91 | Creating new records
92 | ~~~~~~~~~~~~~~~~~~~~
93 |
94 | To add a new record, you need to first create an "empty" object
95 | instance. You then set values on the object as normal, and save it.
96 |
97 | .. code-block:: php
98 |
99 | create();
101 |
102 | $person->name = 'Joe Bloggs';
103 | $person->age = 40;
104 |
105 | $person->save();
106 |
107 | After the object has been saved, you can call its ``id()`` method to
108 | find the autogenerated primary key value that the database assigned to
109 | it.
110 |
111 | Properties containing expressions
112 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
113 |
114 | It is possible to set properties on the model that contain database
115 | expressions using the ``set_expr`` method.
116 |
117 | .. code-block:: php
118 |
119 | create();
121 | $person->set('name', 'Bob Smith');
122 | $person->age = 20;
123 | $person->set_expr('added', 'NOW()');
124 | $person->save();
125 |
126 | The ``added`` column's value will be inserted into query in its raw form
127 | therefore allowing the database to execute any functions referenced -
128 | such as ``NOW()`` in this case.
129 |
130 | Checking whether a property has been modified
131 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
132 |
133 | To check whether a property has been changed since the object was
134 | created (or last saved), call the ``is_dirty`` method:
135 |
136 | .. code-block:: php
137 |
138 | is_dirty('name'); // Returns true or false
140 |
141 | Deleting records
142 | ~~~~~~~~~~~~~~~~
143 |
144 | To delete an object from the database, simply call its ``delete``
145 | method.
146 |
147 | .. code-block:: php
148 |
149 | find_one(5);
151 | $person->delete();
152 |
153 | To delete more than one object from the database, build a query:
154 |
155 | .. code-block:: php
156 |
157 | where_equal('zipcode', 55555)
160 | ->delete_many();
161 |
162 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. linkcheck to check all external links for integrity
37 | echo. doctest to run all doctests embedded in the documentation if enabled
38 | goto end
39 | )
40 |
41 | if "%1" == "clean" (
42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
43 | del /q /s %BUILDDIR%\*
44 | goto end
45 | )
46 |
47 | if "%1" == "html" (
48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
49 | if errorlevel 1 exit /b 1
50 | echo.
51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
52 | goto end
53 | )
54 |
55 | if "%1" == "dirhtml" (
56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
57 | if errorlevel 1 exit /b 1
58 | echo.
59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
60 | goto end
61 | )
62 |
63 | if "%1" == "singlehtml" (
64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
68 | goto end
69 | )
70 |
71 | if "%1" == "pickle" (
72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished; now you can process the pickle files.
76 | goto end
77 | )
78 |
79 | if "%1" == "json" (
80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished; now you can process the JSON files.
84 | goto end
85 | )
86 |
87 | if "%1" == "htmlhelp" (
88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can run HTML Help Workshop with the ^
92 | .hhp project file in %BUILDDIR%/htmlhelp.
93 | goto end
94 | )
95 |
96 | if "%1" == "qthelp" (
97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
98 | if errorlevel 1 exit /b 1
99 | echo.
100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
101 | .qhcp project file in %BUILDDIR%/qthelp, like this:
102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Idiorm.qhcp
103 | echo.To view the help file:
104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Idiorm.ghc
105 | goto end
106 | )
107 |
108 | if "%1" == "devhelp" (
109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
110 | if errorlevel 1 exit /b 1
111 | echo.
112 | echo.Build finished.
113 | goto end
114 | )
115 |
116 | if "%1" == "epub" (
117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
118 | if errorlevel 1 exit /b 1
119 | echo.
120 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
121 | goto end
122 | )
123 |
124 | if "%1" == "latex" (
125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
129 | goto end
130 | )
131 |
132 | if "%1" == "text" (
133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The text files are in %BUILDDIR%/text.
137 | goto end
138 | )
139 |
140 | if "%1" == "man" (
141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
145 | goto end
146 | )
147 |
148 | if "%1" == "texinfo" (
149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
150 | if errorlevel 1 exit /b 1
151 | echo.
152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
153 | goto end
154 | )
155 |
156 | if "%1" == "gettext" (
157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
158 | if errorlevel 1 exit /b 1
159 | echo.
160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
161 | goto end
162 | )
163 |
164 | if "%1" == "changes" (
165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
166 | if errorlevel 1 exit /b 1
167 | echo.
168 | echo.The overview file is in %BUILDDIR%/changes.
169 | goto end
170 | )
171 |
172 | if "%1" == "linkcheck" (
173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
174 | if errorlevel 1 exit /b 1
175 | echo.
176 | echo.Link check complete; look for any errors in the above output ^
177 | or in %BUILDDIR%/linkcheck/output.txt.
178 | goto end
179 | )
180 |
181 | if "%1" == "doctest" (
182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
183 | if errorlevel 1 exit /b 1
184 | echo.
185 | echo.Testing of doctests in the sources finished, look at the ^
186 | results in %BUILDDIR%/doctest/output.txt.
187 | goto end
188 | )
189 |
190 | :end
191 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Idiorm.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Idiorm.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/Idiorm"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Idiorm"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # Idiorm documentation build configuration file, created by
4 | # sphinx-quickstart on Wed Nov 28 15:39:16 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | #sys.path.insert(0, os.path.abspath('.'))
20 |
21 | # -- General configuration -----------------------------------------------------
22 |
23 | # If your documentation needs a minimal Sphinx version, state it here.
24 | #needs_sphinx = '1.0'
25 |
26 | # Add any Sphinx extension module names here, as strings. They can be extensions
27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28 | extensions = []
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['_templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8-sig'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = u'Idiorm'
44 | copyright = u'2014, Jamie Matthews and Simon Holywell'
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 | # The short X.Y version.
51 | version = ''
52 | # The full version, including alpha/beta/rc tags.
53 | release = ''
54 |
55 | # The language for content autogenerated by Sphinx. Refer to documentation
56 | # for a list of supported languages.
57 | #language = None
58 |
59 | # There are two options for replacing |today|: either, you set today to some
60 | # non-false value, then it is used:
61 | #today = ''
62 | # Else, today_fmt is used as the format for a strftime call.
63 | #today_fmt = '%B %d, %Y'
64 |
65 | # List of patterns, relative to source directory, that match files and
66 | # directories to ignore when looking for source files.
67 | exclude_patterns = ['_build']
68 |
69 | # The reST default role (used for this markup: `text`) to use for all documents.
70 | #default_role = None
71 |
72 | # If true, '()' will be appended to :func: etc. cross-reference text.
73 | #add_function_parentheses = True
74 |
75 | # If true, the current module name will be prepended to all description
76 | # unit titles (such as .. function::).
77 | #add_module_names = True
78 |
79 | # If true, sectionauthor and moduleauthor directives will be shown in the
80 | # output. They are ignored by default.
81 | #show_authors = False
82 |
83 | # The name of the Pygments (syntax highlighting) style to use.
84 | pygments_style = 'sphinx'
85 |
86 | # A list of ignored prefixes for module index sorting.
87 | #modindex_common_prefix = []
88 |
89 |
90 | # -- Options for HTML output ---------------------------------------------------
91 |
92 | # The theme to use for HTML and HTML Help pages. See the documentation for
93 | # a list of builtin themes.
94 | html_theme = 'default'
95 |
96 | # Theme options are theme-specific and customize the look and feel of a theme
97 | # further. For a list of options available for each theme, see the
98 | # documentation.
99 | #html_theme_options = {}
100 |
101 | # Add any paths that contain custom themes here, relative to this directory.
102 | #html_theme_path = []
103 |
104 | # The name for this set of Sphinx documents. If None, it defaults to
105 | # " v documentation".
106 | #html_title = None
107 |
108 | # A shorter title for the navigation bar. Default is the same as html_title.
109 | #html_short_title = None
110 |
111 | # The name of an image file (relative to this directory) to place at the top
112 | # of the sidebar.
113 | #html_logo = None
114 |
115 | # The name of an image file (within the static path) to use as favicon of the
116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
117 | # pixels large.
118 | #html_favicon = None
119 |
120 | # Add any paths that contain custom static files (such as style sheets) here,
121 | # relative to this directory. They are copied after the builtin static files,
122 | # so a file named "default.css" will overwrite the builtin "default.css".
123 | html_static_path = ['_static']
124 |
125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
126 | # using the given strftime format.
127 | #html_last_updated_fmt = '%b %d, %Y'
128 |
129 | # If true, SmartyPants will be used to convert quotes and dashes to
130 | # typographically correct entities.
131 | #html_use_smartypants = True
132 |
133 | # Custom sidebar templates, maps document names to template names.
134 | #html_sidebars = {}
135 |
136 | # Additional templates that should be rendered to pages, maps page names to
137 | # template names.
138 | #html_additional_pages = {}
139 |
140 | # If false, no module index is generated.
141 | #html_domain_indices = True
142 |
143 | # If false, no index is generated.
144 | #html_use_index = True
145 |
146 | # If true, the index is split into individual pages for each letter.
147 | #html_split_index = False
148 |
149 | # If true, links to the reST sources are added to the pages.
150 | #html_show_sourcelink = True
151 |
152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
153 | #html_show_sphinx = True
154 |
155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
156 | #html_show_copyright = True
157 |
158 | # If true, an OpenSearch description file will be output, and all pages will
159 | # contain a tag referring to it. The value of this option must be the
160 | # base URL from which the finished HTML is served.
161 | #html_use_opensearch = ''
162 |
163 | # This is the file name suffix for HTML files (e.g. ".xhtml").
164 | #html_file_suffix = None
165 |
166 | # Output file base name for HTML help builder.
167 | htmlhelp_basename = 'Idiormdoc'
168 |
169 |
170 | # -- Options for LaTeX output --------------------------------------------------
171 |
172 | latex_elements = {
173 | # The paper size ('letterpaper' or 'a4paper').
174 | #'papersize': 'letterpaper',
175 |
176 | # The font size ('10pt', '11pt' or '12pt').
177 | #'pointsize': '10pt',
178 |
179 | # Additional stuff for the LaTeX preamble.
180 | #'preamble': '',
181 | }
182 |
183 | # Grouping the document tree into LaTeX files. List of tuples
184 | # (source start file, target name, title, author, documentclass [howto/manual]).
185 | latex_documents = [
186 | ('index', 'Idiorm.tex', u'Idiorm Documentation',
187 | u'Jamie Matthews and Simon Holywell', 'manual'),
188 | ]
189 |
190 | # The name of an image file (relative to this directory) to place at the top of
191 | # the title page.
192 | #latex_logo = None
193 |
194 | # For "manual" documents, if this is true, then toplevel headings are parts,
195 | # not chapters.
196 | #latex_use_parts = False
197 |
198 | # If true, show page references after internal links.
199 | #latex_show_pagerefs = False
200 |
201 | # If true, show URL addresses after external links.
202 | #latex_show_urls = False
203 |
204 | # Documents to append as an appendix to all manuals.
205 | #latex_appendices = []
206 |
207 | # If false, no module index is generated.
208 | #latex_domain_indices = True
209 |
210 |
211 | # -- Options for manual page output --------------------------------------------
212 |
213 | # One entry per manual page. List of tuples
214 | # (source start file, name, description, authors, manual section).
215 | man_pages = [
216 | ('index', 'idiorm', u'Idiorm Documentation',
217 | [u'Jamie Matthews and Simon Holywell'], 1)
218 | ]
219 |
220 | # If true, show URL addresses after external links.
221 | #man_show_urls = False
222 |
223 |
224 | # -- Options for Texinfo output ------------------------------------------------
225 |
226 | # Grouping the document tree into Texinfo files. List of tuples
227 | # (source start file, target name, title, author,
228 | # dir menu entry, description, category)
229 | texinfo_documents = [
230 | ('index', 'Idiorm', u'Idiorm Documentation',
231 | u'Jamie Matthews and Simon Holywell', 'Idiorm', 'One line description of project.',
232 | 'Miscellaneous'),
233 | ]
234 |
235 | # Documents to append as an appendix to all manuals.
236 | #texinfo_appendices = []
237 |
238 | # If false, no module index is generated.
239 | #texinfo_domain_indices = True
240 |
241 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
242 | #texinfo_show_urls = 'footnote'
243 |
--------------------------------------------------------------------------------
/docs/configuration.rst:
--------------------------------------------------------------------------------
1 | Configuration
2 | =============
3 |
4 | The first thing you need to know about Idiorm is that *you don’t need to
5 | define any model classes to use it*. With almost every other ORM, the
6 | first thing to do is set up your models and map them to database tables
7 | (through configuration variables, XML files or similar). With Idiorm,
8 | you can start using the ORM straight away.
9 |
10 | Setup
11 | ~~~~~
12 |
13 | First, ``require`` the Idiorm source file:
14 |
15 | .. code-block:: php
16 |
17 | 'value_for_setting_1',
64 | 'setting_name_2' => 'value_for_setting_2',
65 | 'etc' => 'etc'
66 | ));
67 |
68 | Use the ``get_config`` method to read current settings.
69 |
70 | .. code-block:: php
71 |
72 | 'mysql:host=localhost;dbname=my_database',
103 | 'username' => 'database_user',
104 | 'password' => 'top_secret'
105 | ));
106 |
107 | Result sets
108 | ^^^^^^^^^^^
109 |
110 | Setting: ``return_result_sets``
111 |
112 | Collections of results can be returned as an array (default) or as a result set.
113 | See the `find_result_set()` documentation for more information.
114 |
115 | .. code-block:: php
116 |
117 | 'SET NAMES utf8'));
141 |
142 | PDO Error Mode
143 | ^^^^^^^^^^^^^^
144 |
145 | Setting: ``error_mode``
146 |
147 | This can be used to set the ``PDO::ATTR_ERRMODE`` setting on the
148 | database connection class used by Idiorm. It should be passed one of the
149 | class constants defined by PDO. For example:
150 |
151 | .. code-block:: php
152 |
153 | 'person_id',
222 | 'role' => 'role_id',
223 | ));
224 |
225 | As with ``id_column`` setting, you can specify a compound primary key
226 | using an array.
227 |
228 | Limit clause style
229 | ^^^^^^^^^^^^^^^^^^
230 |
231 | Setting: ``limit_clause_style``
232 |
233 | You can specify the limit clause style in the configuration. This is to facilitate
234 | a MS SQL style limit clause that uses the ``TOP`` syntax.
235 |
236 | Acceptable values are ``ORM::LIMIT_STYLE_TOP_N`` and ``ORM::LIMIT_STYLE_LIMIT``.
237 |
238 | .. note::
239 |
240 | If the PDO driver you are using is one of sqlsrv, dblib or mssql then Idiorm
241 | will automatically select the ``ORM::LIMIT_STYLE_TOP_N`` for you unless you
242 | override the setting.
243 |
244 | Query logging
245 | ^^^^^^^^^^^^^
246 |
247 | Setting: ``logging``
248 |
249 | Idiorm can log all queries it executes. To enable query logging, set the
250 | ``logging`` option to ``true`` (it is ``false`` by default).
251 |
252 | When query logging is enabled, you can use two static methods to access
253 | the log. ``ORM::get_last_query()`` returns the most recent query
254 | executed. ``ORM::get_query_log()`` returns an array of all queries
255 | executed.
256 |
257 | .. note::
258 |
259 | The code that does the query log is an approximation of that provided by PDO/the
260 | database (see the Idiorm source code for detail). The actual query isn't even available
261 | to idiorm to log as the database/PDO handles the binding outside of idiorm's reach and
262 | doesn't pass it back.
263 |
264 | This means that you might come across some inconsistencies between what is logged and
265 | what is actually run. In these case you'll need to look at the query log provided by
266 | your database vendor (eg. MySQL).
267 |
268 | Query logger
269 | ^^^^^^^^^^^^
270 |
271 | Setting: ``logger``
272 |
273 | .. note::
274 |
275 | You must enable ``logging`` for this setting to have any effect.
276 |
277 | It is possible to supply a ``callable`` to this configuration setting, which will
278 | be executed for every query that idiorm executes. In PHP a ``callable`` is anything
279 | that can be executed as if it were a function. Most commonly this will take the
280 | form of a anonymous function.
281 |
282 | This setting is useful if you wish to log queries with an external library as it
283 | allows you too whatever you would like from inside the callback function.
284 |
285 | .. code-block:: php
286 |
287 | where_equal('username', 'j4mie')
58 | ->find_one();
59 |
60 | $user->first_name = 'Jamie';
61 | $user->save();
62 |
63 | $tweets = ORM::for_table('tweet')
64 | ->select('tweet.*')
65 | ->join('user', array(
66 | 'user.id', '=', 'tweet.user_id'
67 | ))
68 | ->where_equal('user.username', 'j4mie')
69 | ->find_many();
70 |
71 | foreach ($tweets as $tweet) {
72 | echo $tweet->text;
73 | }
74 | ```
75 |
76 | Tests
77 | -----
78 |
79 | Tests are written with PHPUnit and be run through composer
80 |
81 | composer test
82 |
83 | To make testing on PHP 5.2 (Idiorm maintains support back to this version of PHP) there
84 | is a Docker setup in `./test/docker_for_php52` - check the readme in there for more.
85 |
86 | Changelog
87 | ---------
88 |
89 | #### 1.5.7 - released 2020-04-29
90 |
91 | * Fix argument order in call to join() [[CatalinFrancu](https://github.com/CatalinFrancu)] - [issue #357](https://github.com/j4mie/idiorm/pull/357)
92 |
93 | #### 1.5.6 - released 2018-05-31
94 |
95 | * Assign `null` to `self::$_db` on `reset_db()` to ensure PDO closes the connections [[bleakgadfly](https://github.com/bleakgadfly)] - [issue #338](https://github.com/j4mie/idiorm/issues/338)
96 |
97 | #### 1.5.5 - released 2018-01-05
98 |
99 | * Add a docker setup for testing with PHP 5.2 (uses PHPUnit 3.6.12, which is the last version released compatible with PHP 5.2) [[Treffynnon](https://github.com/treffynnon)]
100 |
101 | #### 1.5.4 - released 2018-01-04
102 |
103 | * Reset Idiorm state when a cached result is returned [[fayland](https://github.com/fayland) (and [Treffynnon](https://github.com/treffynnon))] - [issue #319](https://github.com/j4mie/idiorm/issues/319)
104 | * Fix travis builds for PHP 5.2+ (adding 7.0 and 7.1) and document support for newer PHP versions [[Treffynnon](https://github.com/treffynnon)]
105 | * Correct PHPDoc comments for `selectMany()` [[kawausokun](https://github.com/kawausokun)] - [issue #325](github.com/j4mie/idiorm/issues/325)
106 | * Add pdo_sqlite to the composer require-dev dependencies [[qyanu](https://github.com/qyanu)] - [issue #328](github.com/j4mie/idiorm/issues/328)
107 |
108 | #### 1.5.3 - released 2017-03-21
109 |
110 | * Document the `raw_execute()` method and add a note for `get_db()` in the querying documentation - [[Treffynnon](https://github.com/treffynnon)]
111 |
112 | #### 1.5.2 - released 2016-12-14
113 |
114 | * Fix autoincremented compound keys inserts [[lrlopez](https://github.com/lrlopez)] - [issue #233](https://github.com/j4mie/idiorm/issues/233) and [pull #235](https://github.com/j4mie/idiorm/pull/235)
115 | * Add @method tags for magic methods [[stellis](https://github.com/stellis)] - [issue #237](https://github.com/j4mie/idiorm/issues/237)
116 | * Ensure `is_dirty()` returns correctly when fed null or an empty string [[tentwofour](https://github.com/tentwofour)] - [issue #268](https://github.com/j4mie/idiorm/issues/268)
117 | * Adding Code Climate badge to the readme file [[e3betht](https://github.com/e3betht)] - [issue #260](https://github.com/j4mie/idiorm/issues/260)
118 | * Typo in navigation [[leongersen](https://github.com/leongersen)] - [issue #257](https://github.com/j4mie/idiorm/issues/257)
119 | * Support named placeholders logging and test [[m92o](https://github.com/m92o)] - [issue #223](https://github.com/j4mie/idiorm/issues/223)
120 | * `having_id_is()` reference undefined variable `$value` [[Treffynnon](https://github.com/treffynnon)] - [issue #224](https://github.com/j4mie/idiorm/issues/224)
121 | * Documentation fix - ORM query output for `where_any_is()` [[uovidiu](https://github.com/uovidiu)] - [issue #306](https://github.com/j4mie/idiorm/issues/306)
122 | * Code style fix preventing nested loops from using the same variable names [[mkkeck](https://github.com/mkkeck)] - [issue #301](https://github.com/j4mie/idiorm/issues/301)
123 | * Document shortcomings of the built in query logger [[Treffynnon](https://github.com/treffynnon)] - [issue #307](https://github.com/j4mie/idiorm/issues/307)
124 | * Add phpunit to dev dependencies, add `composer test` script shortcut and fix PDO mock in test bootstrap [[Treffynnon](https://github.com/treffynnon)]
125 | * New test for multiple raw where clauses [[Treffynnon](https://github.com/treffynnon)] - [issue #236](https://github.com/j4mie/idiorm/issues/236)
126 | * Remove PHP 5.2 from travis-ci containers to test against (**note** Idiorm still supports PHP 5.2 despite this) [[Treffynnon](https://github.com/treffynnon)]
127 |
128 | #### 1.5.1 - released 2014-06-23
129 |
130 | * Binding of named parameters was broken [[cainmi](https://github.com/cainmi)] - [issue #221](https://github.com/j4mie/idiorm/pull/221)
131 |
132 | #### 1.5.0 - released 2014-06-22
133 |
134 | * Multiple OR'ed conditions support [[lrlopez](https://github.com/lrlopez)] - [issue #201](https://github.com/j4mie/idiorm/issues/201)
135 | * `where_id_in()` for selecting multiple records by primary key [[lrlopez](https://github.com/lrlopez)] - [issue #202](https://github.com/j4mie/idiorm/issues/202)
136 | * Add compound primary key support [[lrlopez](https://github.com/lrlopez)] - [issue #171](https://github.com/j4mie/idiorm/issues/171)
137 | * Add a RAW JOIN source to the query [[moiseevigor](https://github.com/moiseevigor)] - [issue #163](https://github.com/j4mie/idiorm/issues/163)
138 | * offsetExists() should return true for null values, resolves [#181](https://github.com/j4mie/idiorm/issues/181) [[cainmi](https://github.com/cainmi)] - [issue #214](https://github.com/j4mie/idiorm/pull/214)
139 | * Custom cache callback functions [[peter-mw](https://github.com/peter-mw)] - [issue #216](https://github.com/j4mie/idiorm/pull/216)
140 | * Restrict null primary keys on update/delete, resolves [#203](https://github.com/j4mie/idiorm/issues/203) [[cainmi](https://github.com/cainmi)] - [issue #205](https://github.com/j4mie/idiorm/issues/205)
141 | * Ensure parameters treated by type correctly [[charsleysa](https://github.com/charsleysa)] & [[SneakyBobito](https://github.com/SneakyBobito)] - [issue #206](https://github.com/j4mie/idiorm/issues/206) & [issue #208](https://github.com/j4mie/idiorm/issues/208)
142 | * Reduce the type casting on aggregate functions to allow characters [[herroffizier](https://github.com/herroffizier)] - [issue #150](https://github.com/j4mie/idiorm/issues/150)
143 | * Prevent invalid method calls from triggering infinite recursion [[michaelward82](https://github.com/michaelward82)] - [issue #152](https://github.com/j4mie/idiorm/issues/152)
144 | * Add time to query logging - adds query time parameter to external logger callback function [[AgelxNash](https://github.com/AgelxNash)] - [issue #180](https://github.com/j4mie/idiorm/issues/180)
145 | * Changed database array access to ensure it's always properly setup [[falmp](https://github.com/falmp)] - [issue #159](https://github.com/j4mie/idiorm/issues/159)
146 | * Allow unsetting the db (`ORM::set_db(null)`) to make the test work again [[borrel](https://github.com/borrel)] - [issue #160](https://github.com/j4mie/idiorm/issues/160)
147 | * Correct [issue #176](https://github.com/j4mie/idiorm/issues/176): Ensure database setup before building select [[kendru](https://github.com/kendru)] - [issue #197](https://github.com/j4mie/idiorm/issues/197)
148 | * Add HHVM to travis-ci build matrix [[ptarjan](https://github.com/ptarjan)] - [issue #168](https://github.com/j4mie/idiorm/issues/168)
149 | * Improve where statement precendence documentation [[thomasahle](https://github.com/thomasahle)] - [issue #190](https://github.com/j4mie/idiorm/issues/190)
150 | * Improve testing checks [[charsleysa](https://github.com/charsleysa)] - [issue #173](https://github.com/j4mie/idiorm/issues/173)
151 |
152 | #### 1.4.1 - released 2013-12-12
153 |
154 | **Patch update to remove a broken pull request** - may have consequences for users of 1.4.0 that exploited the "`find_many()` now returns an associative array with the databases primary ID as the array keys" change that was merged in 1.4.0.
155 |
156 | * Back out pull request/issue [#133](https://github.com/j4mie/idiorm/pull/133) as it breaks backwards compatibility in previously unexpected ways (see [#162](https://github.com/j4mie/idiorm/pull/162), [#156](https://github.com/j4mie/idiorm/issues/156) and [#133](https://github.com/j4mie/idiorm/pull/133#issuecomment-29063108)) - sorry for merging this change into Idiorm - closes [issue 156](https://github.com/j4mie/idiorm/issues/156)
157 |
158 | #### 1.4.0 - released 2013-09-05
159 |
160 | * `find_many()` now returns an associative array with the databases primary ID as the array keys [[Surt](https://github.com/Surt)] - [issue #133](https://github.com/j4mie/idiorm/issues/133)
161 | * Calls to `set()` and `set_expr()` return `$this` allowing them to be chained [[Surt](https://github.com/Surt)]
162 | * Add PSR-1 compliant camelCase method calls to Idiorm (PHP 5.3+ required) [[crhayes](https://github.com/crhayes)] - [issue #108](https://github.com/j4mie/idiorm/issues/108)
163 | * Add static method `get_config()` to access current configuration [[javierd](https://github.com/mikejestes)] - [issue #141](https://github.com/j4mie/idiorm/issues/141)
164 | * Add logging callback functionality [[lalop](https://github.com/lalop)] - [issue #130](https://github.com/j4mie/idiorm/issues/130)
165 | * Add support for MS SQL ``TOP`` limit style (automatically used for PDO drivers: sqlsrv, dblib and mssql) [[numkem](https://github.com/numkem)] - [issue #116](https://github.com/j4mie/idiorm/issues/116)
166 | * Uses table aliases in `WHERE` clauses [[vicvicvic](https://github.com/vicvicvic)] - [issue #140](https://github.com/j4mie/idiorm/issues/140)
167 | * Ignore result columns when calling an aggregate function [[tassoevan](https://github.com/tassoevan)] - [issue #120](https://github.com/j4mie/idiorm/issues/120)
168 | * Improve documentation [[bruston](https://github.com/bruston)] - [issue #111](https://github.com/j4mie/idiorm/issues/111)
169 | * Improve PHPDoc on `get_db()` [[mailopl](https://github.com/mailopl)] - [issue #106](https://github.com/j4mie/idiorm/issues/106)
170 | * Improve documentation [[sjparsons](https://github.com/sjparsons)] - [issue #103](https://github.com/j4mie/idiorm/issues/103)
171 | * Make tests/bootstrap.php HHVM compatible [[JoelMarcey](https://github.com/JoelMarcey)] - [issue #143](https://github.com/j4mie/idiorm/issues/143)
172 | * Fix docblock [[ulrikjohansson](https://github.com/ulrikjohansson)] - [issue #147](https://github.com/j4mie/idiorm/issues/147)
173 | * Fix incorrect variable name in querying documentation [[fridde](https://github.com/fridde)] - [issue #146](https://github.com/j4mie/idiorm/issues/146)
174 |
175 | #### 1.3.0 - released 2013-01-31
176 |
177 | * Documentation moved to [idiorm.rtfd.org](http://idiorm.rtfd.org) and now built using [Sphinx](http://sphinx-doc.org/)
178 | * Add support for multiple database connections - closes [issue #15](https://github.com/j4mie/idiorm/issues/15) [[tag](https://github.com/tag)]
179 | * Add in raw_execute - closes [issue #40](https://github.com/j4mie/idiorm/issues/40) [[tag](https://github.com/tag)]
180 | * Add `get_last_statement()` - closes [issue #84](https://github.com/j4mie/idiorm/issues/84) [[tag](https://github.com/tag)]
181 | * Add HAVING clause functionality - closes [issue #50](https://github.com/j4mie/idiorm/issues/50)
182 | * Add `is_new` method - closes [issue #85](https://github.com/j4mie/idiorm/issues/85)
183 | * Add `ArrayAccess` support to the model instances allowing property access via `$model['field']` as well as `$model->field` - [issue #51](https://github.com/j4mie/idiorm/issues/51)
184 | * Add a result set object for collections of models that can support method chains to filter or apply actions to multiple results at once - issue [#51](https://github.com/j4mie/idiorm/issues/51) and [#22](https://github.com/j4mie/idiorm/issues/22)
185 | * Add support for [Firebird](http://www.firebirdsql.org) with `ROWS` and `TO` result set limiting and identifier quoting [[mapner](https://github.com/mapner)] - [issue #98](https://github.com/j4mie/idiorm/issues/98)
186 | * Fix last insert ID for PostgreSQL using RETURNING - closes issues [#62](https://github.com/j4mie/idiorm/issues/62) and [#89](https://github.com/j4mie/idiorm/issues/89) [[laacz](https://github.com/laacz)]
187 | * Reset Idiorm after performing a query to allow for calling `count()` and then `find_many()` [[fayland](https://github.com/fayland)] - [issue #97](https://github.com/j4mie/idiorm/issues/97)
188 | * Change Composer to use a classmap so that autoloading is better supported [[javierd](https://github.com/javiervd)] - [issue #96](https://github.com/j4mie/idiorm/issues/96)
189 | * Add query logging to `delete_many` [[tag](https://github.com/tag)]
190 | * Fix when using `set_expr` alone it doesn't trigger query creation - closes [issue #90](https://github.com/j4mie/idiorm/issues/90)
191 | * Escape quote symbols in "_quote_identifier_part" - close [issue #74](https://github.com/j4mie/idiorm/issues/74)
192 | * Fix issue with aggregate functions always returning `int` when is `float` sometimes required - closes [issue #92](https://github.com/j4mie/idiorm/issues/92)
193 | * Move testing into PHPUnit to unify method testing and query generation testing
194 |
195 | #### 1.2.3 - released 2012-11-28
196 |
197 | * Fix [issue #78](https://github.com/j4mie/idiorm/issues/78) - remove use of PHP 5.3 static call
198 |
199 | #### 1.2.2 - released 2012-11-15
200 |
201 | * Fix bug where input parameters were sent as part-indexed, part associative
202 |
203 | #### 1.2.1 - released 2012-11-15
204 |
205 | * Fix minor bug caused by IdiormStringException not extending Exception
206 |
207 | #### 1.2.0 - released 2012-11-14
208 |
209 | * Setup composer for installation via packagist (j4mie/idiorm)
210 | * Add `order_by_expr` method [[sandermarechal](http://github.com/sandermarechal)]
211 | * Add support for raw queries without parameters argument [[sandermarechal](http://github.com/sandermarechal)]
212 | * Add support to set multiple properties at once by passing an associative array to `set` method [[sandermarechal](http://github.com/sandermarechal)]
213 | * Allow an associative array to be passed to `configure` method [[jordanlev](http://github.com/jordanlev)]
214 | * Patch to allow empty Paris models to be saved ([[j4mie/paris](http://github.com/j4mie/paris)]) - [issue #58](https://github.com/j4mie/idiorm/issues/58)
215 | * Add `select_many` and `select_many_expr` - closing issues [#49](https://github.com/j4mie/idiorm/issues/49) and [#69](https://github.com/j4mie/idiorm/issues/69)
216 | * Add support for `MIN`, `AVG`, `MAX` and `SUM` - closes [issue #16](https://github.com/j4mie/idiorm/issues/16)
217 | * Add `group_by_expr` - closes [issue #24](https://github.com/j4mie/idiorm/issues/24)
218 | * Add `set_expr` to allow database expressions to be set as ORM properties - closes issues [#59](https://github.com/j4mie/idiorm/issues/59) and [#43](https://github.com/j4mie/idiorm/issues/43) [[brianherbert](https://github.com/brianherbert)]
219 | * Prevent ambiguous column names when joining tables - [issue #66](https://github.com/j4mie/idiorm/issues/66) [[hellogerard](https://github.com/hellogerard)]
220 | * Add `delete_many` method [[CBeerta](https://github.com/CBeerta)]
221 | * Allow unsetting of ORM parameters [[CBeerta](https://github.com/CBeerta)]
222 | * Add `find_array` to get the records as associative arrays [[Surt](https://github.com/Surt)] - closes [issue #17](https://github.com/j4mie/idiorm/issues/17)
223 | * Fix bug in `_log_query` with `?` and `%` supplied in raw where statements etc. - closes [issue #57](https://github.com/j4mie/idiorm/issues/57) [[ridgerunner](https://github.com/ridgerunner)]
224 |
225 | #### 1.1.1 - released 2011-01-30
226 |
227 | * Fix bug in quoting column wildcard. j4mie/paris#12
228 | * Small documentation improvements
229 |
230 | #### 1.1.0 - released 2011-01-24
231 |
232 | * Add `is_dirty` method
233 | * Add basic query caching
234 | * Add `distinct` method
235 | * Add `group_by` method
236 |
237 | #### 1.0.0 - released 2010-12-01
238 |
239 | * Initial release
240 |
--------------------------------------------------------------------------------
/docs/querying.rst:
--------------------------------------------------------------------------------
1 | Querying
2 | ========
3 |
4 | Idiorm provides a `*fluent
5 | interface* `_ to enable
6 | simple queries to be built without writing a single character of SQL. If
7 | you've used `jQuery `_ at all, you'll be familiar
8 | with the concept of a fluent interface. It just means that you can
9 | *chain* method calls together, one after another. This can make your
10 | code more readable, as the method calls strung together in order can
11 | start to look a bit like a sentence.
12 |
13 | All Idiorm queries start with a call to the ``for_table`` static method
14 | on the ORM class. This tells the ORM which table to use when making the
15 | query.
16 |
17 | *Note that this method **does not** escape its query parameter and so
18 | the table name should **not** be passed directly from user input.*
19 |
20 | Method calls which add filters and constraints to your query are then
21 | strung together. Finally, the chain is finished by calling either
22 | ``find_one()`` or ``find_many()``, which executes the query and returns
23 | the result.
24 |
25 | Let's start with a simple example. Say we have a table called ``person``
26 | which contains the columns ``id`` (the primary key of the record -
27 | Idiorm assumes the primary key column is called ``id`` but this is
28 | configurable, see below), ``name``, ``age`` and ``gender``.
29 |
30 | A note on PSR-1 and camelCase
31 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
32 |
33 | All the methods detailed in the documentation can also be called in a PSR-1 way:
34 | underscores (_) become camelCase. Here follows an example of one query chain
35 | being converted to a PSR-1 compliant style.
36 |
37 | .. code-block:: php
38 |
39 | where('name', 'Fred Bloggs')->find_one();
42 |
43 | // PSR-1 compliant style
44 | $person = ORM::forTable('person')->where('name', 'Fred Bloggs')->findOne();
45 |
46 | As you can see any method can be changed from the documented underscore (_) format
47 | to that of a camelCase method name.
48 |
49 | .. note::
50 |
51 | In the background the PSR-1 compliant style uses the `__call()` and
52 | `__callStatic()` magic methods to map the camelCase method name you supply
53 | to the original underscore method name. It then uses `call_user_func_array()`
54 | to apply the arguments to the method. If this minimal overhead is too great
55 | then you can simply revert to using the underscore methods to avoid it. In
56 | general this will not be a bottle neck in any application however and should
57 | be considered a micro-optimisation.
58 |
59 | As `__callStatic()` was added in PHP 5.3.0 you will need at least that version
60 | of PHP to use this feature in any meaningful way.
61 |
62 | Single records
63 | ^^^^^^^^^^^^^^
64 |
65 | Any method chain that ends in ``find_one()`` will return either a
66 | *single* instance of the ORM class representing the database row you
67 | requested, or ``false`` if no matching record was found.
68 |
69 | To find a single record where the ``name`` column has the value "Fred
70 | Bloggs":
71 |
72 | .. code-block:: php
73 |
74 | where('name', 'Fred Bloggs')->find_one();
76 |
77 | This roughly translates into the following SQL:
78 | ``SELECT * FROM person WHERE name = "Fred Bloggs"``
79 |
80 | To find a single record by ID, you can pass the ID directly to the
81 | ``find_one`` method:
82 |
83 | .. code-block:: php
84 |
85 | find_one(5);
87 |
88 | If you are using a compound primary key, you can find the records
89 | using an array as the parameter:
90 |
91 | .. code-block:: php
92 |
93 | find_one(array(
95 | 'user_id' => 34,
96 | 'role_id' => 10
97 | ));
98 |
99 |
100 | Multiple records
101 | ^^^^^^^^^^^^^^^^
102 |
103 | .. note::
104 |
105 | It is recommended that you use results sets over arrays - see `As a result set`
106 | below.
107 |
108 | Any method chain that ends in ``find_many()`` will return an *array* of
109 | ORM class instances, one for each row matched by your query. If no rows
110 | were found, an empty array will be returned.
111 |
112 | To find all records in the table:
113 |
114 | .. code-block:: php
115 |
116 | find_many();
118 |
119 | To find all records where the ``gender`` is ``female``:
120 |
121 | .. code-block:: php
122 |
123 | where('gender', 'female')->find_many();
125 |
126 | As a result set
127 | '''''''''''''''
128 |
129 | .. note::
130 |
131 | There is a configuration setting ``return_result_sets`` that will cause
132 | ``find_many()`` to return result sets by default. It is recommended that you
133 | turn this setting on:
134 |
135 | ::
136 |
137 | ORM::configure('return_result_sets', true);
138 |
139 | You can also find many records as a result set instead of an array of Idiorm
140 | instances. This gives you the advantage that you can run batch operations on a
141 | set of results.
142 |
143 | So for example instead of running this:
144 |
145 | .. code-block:: php
146 |
147 | find_many();
149 | foreach ($people as $person) {
150 | $person->age = 50;
151 | $person->save();
152 | }
153 |
154 | You can simply do this instead:
155 |
156 | .. code-block:: php
157 |
158 | find_result_set()
160 | ->set('age', 50)
161 | ->save();
162 |
163 | To do this substitute any call to ``find_many()`` with
164 | ``find_result_set()``.
165 |
166 | A result set will also behave like an array so you can `count()` it and `foreach`
167 | over it just like an array.
168 |
169 | .. code-block:: php
170 |
171 | find_result_set() as $record) {
173 | echo $record->name;
174 | }
175 |
176 | .. code-block:: php
177 |
178 | find_result_set());
180 |
181 | .. note::
182 |
183 | For deleting many records it is recommended that you use `delete_many()` as it
184 | is more efficient than calling `delete()` on a result set.
185 |
186 | As an associative array
187 | '''''''''''''''''''''''
188 |
189 | You can also find many records as an associative array instead of Idiorm
190 | instances. To do this substitute any call to ``find_many()`` with
191 | ``find_array()``.
192 |
193 | .. code-block:: php
194 |
195 | where('gender', 'female')->find_array();
197 |
198 | This is useful if you need to serialise the the query output into a
199 | format like JSON and you do not need the ability to update the returned
200 | records.
201 |
202 | Counting results
203 | ^^^^^^^^^^^^^^^^
204 |
205 | To return a count of the number of rows that would be returned by a
206 | query, call the ``count()`` method.
207 |
208 | .. code-block:: php
209 |
210 | count();
212 |
213 | Filtering results
214 | ^^^^^^^^^^^^^^^^^
215 |
216 | Idiorm provides a family of methods to extract only records which
217 | satisfy some condition or conditions. These methods may be called
218 | multiple times to build up your query, and Idiorm's fluent interface
219 | allows method calls to be *chained* to create readable and
220 | simple-to-understand queries.
221 |
222 | *Caveats*
223 | '''''''''
224 |
225 | Only a subset of the available conditions supported by SQL are available
226 | when using Idiorm. Additionally, all the ``WHERE`` clauses will be
227 | ``AND``\ ed together when the query is run. Support for ``OR``\ ing
228 | ``WHERE`` clauses is not currently present.
229 |
230 | These limits are deliberate: these are by far the most commonly used
231 | criteria, and by avoiding support for very complex queries, the Idiorm
232 | codebase can remain small and simple.
233 |
234 | Some support for more complex conditions and queries is provided by the
235 | ``where_raw`` and ``raw_query`` methods (see below). If you find
236 | yourself regularly requiring more functionality than Idiorm can provide,
237 | it may be time to consider using a more full-featured ORM.
238 |
239 | Equality: ``where``, ``where_equal``, ``where_not_equal``
240 | '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
241 |
242 | By default, calling ``where`` with two parameters (the column name and
243 | the value) will combine them using an equals operator (``=``). For
244 | example, calling ``where('name', 'Fred')`` will result in the clause
245 | ``WHERE name = "Fred"``.
246 |
247 | If your coding style favours clarity over brevity, you may prefer to use
248 | the ``where_equal`` method: this is identical to ``where``.
249 |
250 | The ``where_not_equal`` method adds a ``WHERE column != "value"`` clause
251 | to your query.
252 |
253 | You can specify multiple columns and their values in the same call. In this
254 | case you should pass an associative array as the first parameter. The array
255 | notation uses keys as column names.
256 |
257 | .. code-block:: php
258 |
259 | where(array(
262 | 'name' => 'Fred',
263 | 'age' => 20
264 | ))
265 | ->find_many();
266 |
267 | // Creates SQL:
268 | SELECT * FROM `person` WHERE `name` = "Fred" AND `age` = "20";
269 |
270 | Shortcut: ``where_id_is``
271 | '''''''''''''''''''''''''
272 |
273 | This is a simple helper method to query the table by primary key.
274 | Respects the ID column specified in the config. If you are using a compound
275 | primary key, you must pass an array where the key is the column name. Columns
276 | that don't belong to the key will be ignored.
277 |
278 | Shortcut: ``where_id_in``
279 | '''''''''''''''''''''''''
280 |
281 | This helper method is similar to ``where_id_is`, but it expects an array of
282 | primary keys to be selected. It is compound primary keys aware.
283 |
284 | Less than / greater than: ``where_lt``, ``where_gt``, ``where_lte``, ``where_gte``
285 | ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
286 |
287 | There are four methods available for inequalities:
288 |
289 | - Less than:
290 | ``$people = ORM::for_table('person')->where_lt('age', 10)->find_many();``
291 | - Greater than:
292 | ``$people = ORM::for_table('person')->where_gt('age', 5)->find_many();``
293 | - Less than or equal:
294 | ``$people = ORM::for_table('person')->where_lte('age', 10)->find_many();``
295 | - Greater than or equal:
296 | ``$people = ORM::for_table('person')->where_gte('age', 5)->find_many();``
297 |
298 | String comparision: ``where_like`` and ``where_not_like``
299 | '''''''''''''''''''''''''''''''''''''''''''''''''''''''''
300 |
301 | To add a ``WHERE ... LIKE`` clause, use:
302 |
303 | .. code-block:: php
304 |
305 | where_like('name', '%fred%')->find_many();
307 |
308 | Similarly, to add a ``WHERE ... NOT LIKE`` clause, use:
309 |
310 | .. code-block:: php
311 |
312 | where_not_like('name', '%bob%')->find_many();
314 |
315 | Multiple OR'ed conditions
316 | '''''''''''''''''''''''''
317 |
318 | You can add simple OR'ed conditions to the same WHERE clause using ``where_any_is``. You
319 | should specify multiple conditions using an array of items. Each item will be an
320 | associative array that contains a multiple conditions.
321 |
322 | .. code-block:: php
323 |
324 | where_any_is(array(
327 | array('name' => 'Joe', 'age' => 10),
328 | array('name' => 'Fred', 'age' => 20)))
329 | ->find_many();
330 |
331 | // Creates SQL:
332 | SELECT * FROM `widget` WHERE (( `name` = 'Joe' AND `age` = '10' ) OR ( `name` = 'Fred' AND `age` = '20' ));
333 |
334 | By default, it uses the equal operator for every column, but it can be overriden for any
335 | column using a second parameter:
336 |
337 | .. code-block:: php
338 |
339 | where_any_is(array(
342 | array('name' => 'Joe', 'age' => 10),
343 | array('name' => 'Fred', 'age' => 20)), array('age' => '>'))
344 | ->find_many();
345 |
346 | // Creates SQL:
347 | SELECT * FROM `widget` WHERE (( `name` = 'Joe' AND `age` = '10' ) OR ( `name` = 'Fred' AND `age` > '20' ));
348 |
349 | If you want to set the default operator for all the columns, just pass it as the second parameter:
350 |
351 | .. code-block:: php
352 |
353 | where_any_is(array(
356 | array('score' => '5', 'age' => 10),
357 | array('score' => '15', 'age' => 20)), '>')
358 | ->find_many();
359 |
360 | // Creates SQL:
361 | SELECT * FROM `widget` WHERE (( `score` > '5' AND `age` > '10' ) OR ( `score` > '15' AND `age` > '20' ));
362 |
363 | Set membership: ``where_in`` and ``where_not_in``
364 | '''''''''''''''''''''''''''''''''''''''''''''''''
365 |
366 | To add a ``WHERE ... IN ()`` or ``WHERE ... NOT IN ()`` clause, use the
367 | ``where_in`` and ``where_not_in`` methods respectively.
368 |
369 | Both methods accept two arguments. The first is the column name to
370 | compare against. The second is an *array* of possible values. As all the
371 | ``where_`` methods, you can specify multiple columns using an associative
372 | *array* as the only parameter.
373 |
374 | .. code-block:: php
375 |
376 | where_in('name', array('Fred', 'Joe', 'John'))->find_many();
378 |
379 | Working with ``NULL`` values: ``where_null`` and ``where_not_null``
380 | '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
381 |
382 | To add a ``WHERE column IS NULL`` or ``WHERE column IS NOT NULL``
383 | clause, use the ``where_null`` and ``where_not_null`` methods
384 | respectively. Both methods accept a single parameter: the column name to
385 | test.
386 |
387 | Raw WHERE clauses
388 | '''''''''''''''''
389 |
390 | If you require a more complex query, you can use the ``where_raw``
391 | method to specify the SQL fragment for the WHERE clause exactly. This
392 | method takes two arguments: the string to add to the query, and an
393 | (optional) array of parameters which will be bound to the string. If
394 | parameters are supplied, the string should contain question mark
395 | characters (``?``) to represent the values to be bound, and the
396 | parameter array should contain the values to be substituted into the
397 | string in the correct order.
398 |
399 | This method may be used in a method chain alongside other ``where_*``
400 | methods as well as methods such as ``offset``, ``limit`` and
401 | ``order_by_*``. The contents of the string you supply will be connected
402 | with preceding and following WHERE clauses with AND.
403 |
404 | .. code-block:: php
405 |
406 | where('name', 'Fred')
409 | ->where_raw('(`age` = ? OR `age` = ?)', array(20, 25))
410 | ->order_by_asc('name')
411 | ->find_many();
412 |
413 | // Creates SQL:
414 | SELECT * FROM `person` WHERE `name` = "Fred" AND (`age` = 20 OR `age` = 25) ORDER BY `name` ASC;
415 |
416 | .. note::
417 |
418 | You must wrap your expression in parentheses when using any of ``ALL``,
419 | ``ANY``, ``BETWEEN``, ``IN``, ``LIKE``, ``OR`` and ``SOME``. Otherwise
420 | the precedence of ``AND`` will bind stronger and in the above example
421 | you would effectively get ``WHERE (`name` = "Fred" AND `age` = 20) OR `age` = 25``
422 |
423 | Note that this method only supports "question mark placeholder" syntax,
424 | and NOT "named placeholder" syntax. This is because PDO does not allow
425 | queries that contain a mixture of placeholder types. Also, you should
426 | ensure that the number of question mark placeholders in the string
427 | exactly matches the number of elements in the array.
428 |
429 | If you require yet more flexibility, you can manually specify the entire
430 | query. See *Raw queries* below.
431 |
432 | Limits and offsets
433 | ''''''''''''''''''
434 |
435 | *Note that these methods **do not** escape their query parameters and so
436 | these should **not** be passed directly from user input.*
437 |
438 | The ``limit`` and ``offset`` methods map pretty closely to their SQL
439 | equivalents.
440 |
441 | .. code-block:: php
442 |
443 | where('gender', 'female')->limit(5)->offset(10)->find_many();
445 |
446 | Ordering
447 | ''''''''
448 |
449 | *Note that these methods **do not** escape their query parameters and so
450 | these should **not** be passed directly from user input.*
451 |
452 | Two methods are provided to add ``ORDER BY`` clauses to your query.
453 | These are ``order_by_desc`` and ``order_by_asc``, each of which takes a
454 | column name to sort by. The column names will be quoted.
455 |
456 | .. code-block:: php
457 |
458 | order_by_asc('gender')->order_by_desc('name')->find_many();
460 |
461 | If you want to order by something other than a column name, then use the
462 | ``order_by_expr`` method to add an unquoted SQL expression as an
463 | ``ORDER BY`` clause.
464 |
465 | .. code-block:: php
466 |
467 | order_by_expr('SOUNDEX(`name`)')->find_many();
469 |
470 | Grouping
471 | ^^^^^^^^
472 |
473 | *Note that this method **does not** escape it query parameter and so
474 | this should **not** by passed directly from user input.*
475 |
476 | To add a ``GROUP BY`` clause to your query, call the ``group_by``
477 | method, passing in the column name. You can call this method multiple
478 | times to add further columns.
479 |
480 | .. code-block:: php
481 |
482 | where('gender', 'female')->group_by('name')->find_many();
484 |
485 | It is also possible to ``GROUP BY`` a database expression:
486 |
487 | .. code-block:: php
488 |
489 | where('gender', 'female')->group_by_expr("FROM_UNIXTIME(`time`, '%Y-%m')")->find_many();
491 |
492 | Having
493 | ^^^^^^
494 |
495 | When using aggregate functions in combination with a ``GROUP BY`` you can use
496 | ``HAVING`` to filter based on those values.
497 |
498 | ``HAVING`` works in exactly the same way as all of the ``where*`` functions in Idiorm.
499 | Substitute ``where_`` for ``having_`` to make use of these functions.
500 |
501 | For example:
502 |
503 | .. code-block:: php
504 |
505 | group_by('name')->having_not_like('name', '%bob%')->find_many();
507 |
508 | Result columns
509 | ^^^^^^^^^^^^^^
510 |
511 | By default, all columns in the ``SELECT`` statement are returned from
512 | your query. That is, calling:
513 |
514 | .. code-block:: php
515 |
516 | find_many();
518 |
519 | Will result in the query:
520 |
521 | .. code-block:: php
522 |
523 | `_ to specify
529 | many columns at once.
530 |
531 | .. code-block:: php
532 |
533 | select('name')->select('age')->find_many();
535 |
536 | Will result in the query:
537 |
538 | .. code-block:: php
539 |
540 | select('name', 'person_name')->find_many();
550 |
551 | Will result in the query:
552 |
553 | .. code-block:: php
554 |
555 | select('person.name', 'person_name')->find_many();
565 |
566 | Will result in the query:
567 |
568 | .. code-block:: php
569 |
570 | `_
578 | to specify many expressions at once.
579 |
580 | .. code-block:: php
581 |
582 | select_expr('COUNT(*)', 'count')->find_many();
585 |
586 | Will result in the query:
587 |
588 | .. code-block:: php
589 |
590 | select_many('name', 'age')->find_many();
603 |
604 | Will result in the query:
605 |
606 | .. code-block:: php
607 |
608 | select_many(array('first_name' => 'name'), 'age', 'height')->find_many();
618 |
619 | Will result in the query:
620 |
621 | .. code-block:: php
622 |
623 | 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')
633 | select_many('column', 'column2', 'column3')
634 | select_many(array('column', 'column2', 'column3'), 'column4', 'column5')
635 |
636 | All the select methods can also be chained with each other so you could
637 | do the following to get a neat select query including an expression:
638 |
639 | .. code-block:: php
640 |
641 | select_many('name', 'age', 'height')->select_expr('NOW()', 'timestamp')->find_many();
643 |
644 | Will result in the query:
645 |
646 | .. code-block:: php
647 |
648 | distinct()->select('name')->find_many();
661 |
662 | This will result in the query:
663 |
664 | .. code-block:: php
665 |
666 | join('person_profile', array('person.id', '=', 'person_profile.person_id'))->find_many();
692 |
693 | It is also possible to specify the condition as a string, which will be
694 | inserted as-is into the query. However, in this case the column names
695 | will **not** be escaped, and so this method should be used with caution.
696 |
697 | .. code-block:: php
698 |
699 | join('person_profile', 'person.id = person_profile.person_id')->find_many();
702 |
703 | The ``join`` methods also take an optional third parameter, which is an
704 | ``alias`` for the table in the query. This is useful if you wish to join
705 | the table to *itself* to create a hierarchical structure. In this case,
706 | it is best combined with the ``table_alias`` method, which will add an
707 | alias to the *main* table associated with the ORM, and the ``select``
708 | method to control which columns get returned.
709 |
710 | .. code-block:: php
711 |
712 | table_alias('p1')
715 | ->select('p1.*')
716 | ->select('p2.name', 'parent_name')
717 | ->join('person', array('p1.parent', '=', 'p2.id'), 'p2')
718 | ->find_many();
719 |
720 | Raw JOIN clauses
721 | '''''''''''''''''
722 |
723 | If you need to construct a more complex query, you can use the ``raw_join``
724 | method to specify the SQL fragment for the JOIN clause exactly. This
725 | method takes four required arguments: the string to add to the query,
726 | the conditions is as an *array* containing three components:
727 | the first column, the operator, and the second column, the table alias and
728 | (optional) the parameters array. If parameters are supplied,
729 | the string should contain question mark characters (``?``) to represent
730 | the values to be bound, and the parameter array should contain the values
731 | to be substituted into the string in the correct order.
732 |
733 | This method may be used in a method chain alongside other ``*_join``
734 | methods as well as methods such as ``offset``, ``limit`` and
735 | ``order_by_*``. The contents of the string you supply will be connected
736 | with preceding and following JOIN clauses.
737 |
738 | .. code-block:: php
739 |
740 | raw_join(
743 | 'JOIN (SELECT * FROM role WHERE role.name = ?)',
744 | array('person.role_id', '=', 'role.id'),
745 | 'role',
746 | array('role' => 'janitor'))
747 | ->order_by_asc('person.name')
748 | ->find_many();
749 |
750 | // Creates SQL:
751 | SELECT * FROM `person` JOIN (SELECT * FROM role WHERE role.name = 'janitor') `role` ON `person`.`role_id` = `role`.`id` ORDER BY `person`.`name` ASC
752 |
753 | Note that this method only supports "question mark placeholder" syntax,
754 | and NOT "named placeholder" syntax. This is because PDO does not allow
755 | queries that contain a mixture of placeholder types. Also, you should
756 | ensure that the number of question mark placeholders in the string
757 | exactly matches the number of elements in the array.
758 |
759 | If you require yet more flexibility, you can manually specify the entire
760 | query. See *Raw queries* below.
761 |
762 |
763 | Aggregate functions
764 | ^^^^^^^^^^^^^^^^^^^
765 |
766 | There is support for ``MIN``, ``AVG``, ``MAX`` and ``SUM`` in addition
767 | to ``COUNT`` (documented earlier).
768 |
769 | To return a minimum value of column, call the ``min()`` method.
770 |
771 | .. code-block:: php
772 |
773 | min('height');
775 |
776 | The other functions (``AVG``, ``MAX`` and ``SUM``) work in exactly the
777 | same manner. Supply a column name to perform the aggregate function on
778 | and it will return an integer.
779 |
780 | Raw queries
781 | ^^^^^^^^^^^
782 |
783 | If you need to perform more complex queries, you can completely specify
784 | the query to execute by using the ``raw_query`` method. This method
785 | takes a string and optionally an array of parameters. The string can
786 | contain placeholders, either in question mark or named placeholder
787 | syntax, which will be used to bind the parameters to the query.
788 |
789 | .. code-block:: php
790 |
791 | raw_query('SELECT p.* FROM person p JOIN role r ON p.role_id = r.id WHERE r.name = :role', array('role' => 'janitor'))->find_many();
793 |
794 | The ORM class instance(s) returned will contain data for all the columns
795 | returned by the query. Note that you still must call ``for_table`` to
796 | bind the instances to a particular table, even though there is nothing
797 | to stop you from specifying a completely different table in the query.
798 | This is because if you wish to later called ``save``, the ORM will need
799 | to know which table to update.
800 |
801 | .. note::
802 |
803 | Using ``raw_query`` is advanced and possibly dangerous, and
804 | Idiorm does not make any attempt to protect you from making errors when
805 | using this method. If you find yourself calling ``raw_query`` often, you
806 | may have misunderstood the purpose of using an ORM, or your application
807 | may be too complex for Idiorm. Consider using a more full-featured
808 | database abstraction system.
809 |
810 | Raw SQL execution using PDO
811 | '''''''''''''''''''''''''''
812 |
813 | .. warning::
814 |
815 | By using this function you're dropping down to PHPs PDO directly. Idiorm
816 | does not make any attempt to protect you from making errors when using this
817 | method.
818 |
819 | You're essentially just using Idiorm to manage the connection and configuration
820 | when you implement ``raw_execute()``.
821 |
822 | It can be handy, in some instances, to make use of the PDO instance underneath
823 | Idiorm to make advanced queries. These can be things like dropping a table from
824 | the database that Idiorm doesn't support and will not support in the future. These
825 | are operations that fall outside the 80/20 philosophy of Idiorm. That said there is
826 | a lot of interest in this function and quite a lot of support requests related to
827 | it.
828 |
829 | This method directly maps to `PDOStatement::execute()`_ underneath so please
830 | familiarise yourself with it's documentation.
831 |
832 | Dropping tables
833 | ~~~~~~~~~~~~~~~
834 |
835 | This can be done very simply using ``raw_execute()``.
836 |
837 | .. code-block:: php
838 |
839 | fetch(PDO::FETCH_ASSOC)) {
865 | var_dump($row);
866 | }
867 |
868 | It is also worth noting that ``$statement`` is a ``PDOStatement`` instance so calling
869 | its ``fetch()`` method is the same as if you had called against PDO without Idiorm.
870 |
871 | Getting the PDO instance
872 | ''''''''''''''''''''''''
873 |
874 | .. warning::
875 |
876 | By using this function you're dropping down to PHPs PDO directly. Idiorm
877 | does not make any attempt to protect you from making errors when using this
878 | method.
879 |
880 | You're essentially just using Idiorm to manage the connection and configuration
881 | when you implement against ``get_db()``.
882 |
883 | If none of the preceeding methods suit your purposes then you can also get direct
884 | access to the PDO instance underneath Idiorm using ``ORM::get_db()``. This will
885 | return a configured instance of `PDO`_.
886 |
887 | .. code-block:: php
888 |
889 | query('SHOW TABLES') as $row) {
892 | var_dump($row);
893 | }
894 |
895 | .. _PDOStatement::execute(): https://secure.php.net/manual/en/pdostatement.execute.php
896 | .. _PDO: https://secure.php.net/manual/en/class.pdo.php
897 |
--------------------------------------------------------------------------------
/idiorm.php:
--------------------------------------------------------------------------------
1 | 'sqlite::memory:',
138 | 'id_column' => 'id',
139 | 'id_column_overrides' => array(),
140 | 'error_mode' => PDO::ERRMODE_EXCEPTION,
141 | 'username' => null,
142 | 'password' => null,
143 | 'driver_options' => null,
144 | 'identifier_quote_character' => null, // if this is null, will be autodetected
145 | 'limit_clause_style' => null, // if this is null, will be autodetected
146 | 'logging' => false,
147 | 'logger' => null,
148 | 'caching' => false,
149 | 'caching_auto_clear' => false,
150 | 'return_result_sets' => false,
151 | );
152 |
153 | // Map of configuration settings
154 | protected static $_config = array();
155 |
156 | // Map of database connections, instances of the PDO class
157 | protected static $_db = array();
158 |
159 | // Last query run, only populated if logging is enabled
160 | protected static $_last_query;
161 |
162 | // Log of all queries run, mapped by connection key, only populated if logging is enabled
163 | protected static $_query_log = array();
164 |
165 | // Query cache, only used if query caching is enabled
166 | protected static $_query_cache = array();
167 |
168 | // Reference to previously used PDOStatement object to enable low-level access, if needed
169 | protected static $_last_statement = null;
170 |
171 | // --------------------------- //
172 | // --- INSTANCE PROPERTIES --- //
173 | // --------------------------- //
174 |
175 | // Key name of the connections in self::$_db used by this instance
176 | protected $_connection_name;
177 |
178 | // The name of the table the current ORM instance is associated with
179 | protected $_table_name;
180 |
181 | // Alias for the table to be used in SELECT queries
182 | protected $_table_alias = null;
183 |
184 | // Values to be bound to the query
185 | protected $_values = array();
186 |
187 | // Columns to select in the result
188 | protected $_result_columns = array('*');
189 |
190 | // Are we using the default result column or have these been manually changed?
191 | protected $_using_default_result_columns = true;
192 |
193 | // Join sources
194 | protected $_join_sources = array();
195 |
196 | // Should the query include a DISTINCT keyword?
197 | protected $_distinct = false;
198 |
199 | // Is this a raw query?
200 | protected $_is_raw_query = false;
201 |
202 | // The raw query
203 | protected $_raw_query = '';
204 |
205 | // The raw query parameters
206 | protected $_raw_parameters = array();
207 |
208 | // Array of WHERE clauses
209 | protected $_where_conditions = array();
210 |
211 | // LIMIT
212 | protected $_limit = null;
213 |
214 | // OFFSET
215 | protected $_offset = null;
216 |
217 | // ORDER BY
218 | protected $_order_by = array();
219 |
220 | // GROUP BY
221 | protected $_group_by = array();
222 |
223 | // HAVING
224 | protected $_having_conditions = array();
225 |
226 | // The data for a hydrated instance of the class
227 | protected $_data = array();
228 |
229 | // Fields that have been modified during the
230 | // lifetime of the object
231 | protected $_dirty_fields = array();
232 |
233 | // Fields that are to be inserted in the DB raw
234 | protected $_expr_fields = array();
235 |
236 | // Is this a new object (has create() been called)?
237 | protected $_is_new = false;
238 |
239 | // Name of the column to use as the primary key for
240 | // this instance only. Overrides the config settings.
241 | protected $_instance_id_column = null;
242 |
243 | // ---------------------- //
244 | // --- STATIC METHODS --- //
245 | // ---------------------- //
246 |
247 | /**
248 | * Pass configuration settings to the class in the form of
249 | * key/value pairs. As a shortcut, if the second argument
250 | * is omitted and the key is a string, the setting is
251 | * assumed to be the DSN string used by PDO to connect
252 | * to the database (often, this will be the only configuration
253 | * required to use Idiorm). If you have more than one setting
254 | * you wish to configure, another shortcut is to pass an array
255 | * of settings (and omit the second argument).
256 | * @param string|array $key
257 | * @param mixed $value
258 | * @param string $connection_name Which connection to use
259 | */
260 | public static function configure($key, $value = null, $connection_name = self::DEFAULT_CONNECTION) {
261 | self::_setup_db_config($connection_name); //ensures at least default config is set
262 |
263 | if (is_array($key)) {
264 | // Shortcut: If only one array argument is passed,
265 | // assume it's an array of configuration settings
266 | foreach ($key as $conf_key => $conf_value) {
267 | self::configure($conf_key, $conf_value, $connection_name);
268 | }
269 | } else {
270 | if (is_null($value)) {
271 | // Shortcut: If only one string argument is passed,
272 | // assume it's a connection string
273 | $value = $key;
274 | $key = 'connection_string';
275 | }
276 | self::$_config[$connection_name][$key] = $value;
277 | }
278 | }
279 |
280 | /**
281 | * Retrieve configuration options by key, or as whole array.
282 | * @param string $key
283 | * @param string $connection_name Which connection to use
284 | */
285 | public static function get_config($key = null, $connection_name = self::DEFAULT_CONNECTION) {
286 | if ($key) {
287 | return self::$_config[$connection_name][$key];
288 | } else {
289 | return self::$_config[$connection_name];
290 | }
291 | }
292 |
293 | /**
294 | * Delete all configs in _config array.
295 | */
296 | public static function reset_config() {
297 | self::$_config = array();
298 | }
299 |
300 | /**
301 | * Despite its slightly odd name, this is actually the factory
302 | * method used to acquire instances of the class. It is named
303 | * this way for the sake of a readable interface, ie
304 | * ORM::for_table('table_name')->find_one()-> etc. As such,
305 | * this will normally be the first method called in a chain.
306 | * @param string $table_name
307 | * @param string $connection_name Which connection to use
308 | * @return ORM
309 | */
310 | public static function for_table($table_name, $connection_name = self::DEFAULT_CONNECTION) {
311 | self::_setup_db($connection_name);
312 | return new self($table_name, array(), $connection_name);
313 | }
314 |
315 | /**
316 | * Set up the database connection used by the class
317 | * @param string $connection_name Which connection to use
318 | */
319 | protected static function _setup_db($connection_name = self::DEFAULT_CONNECTION) {
320 | if (!array_key_exists($connection_name, self::$_db) ||
321 | !is_object(self::$_db[$connection_name])) {
322 | self::_setup_db_config($connection_name);
323 |
324 | $db = new PDO(
325 | self::$_config[$connection_name]['connection_string'],
326 | self::$_config[$connection_name]['username'],
327 | self::$_config[$connection_name]['password'],
328 | self::$_config[$connection_name]['driver_options']
329 | );
330 |
331 | $db->setAttribute(PDO::ATTR_ERRMODE, self::$_config[$connection_name]['error_mode']);
332 | self::set_db($db, $connection_name);
333 | }
334 | }
335 |
336 | /**
337 | * Ensures configuration (multiple connections) is at least set to default.
338 | * @param string $connection_name Which connection to use
339 | */
340 | protected static function _setup_db_config($connection_name) {
341 | if (!array_key_exists($connection_name, self::$_config)) {
342 | self::$_config[$connection_name] = self::$_default_config;
343 | }
344 | }
345 |
346 | /**
347 | * Set the PDO object used by Idiorm to communicate with the database.
348 | * This is public in case the ORM should use a ready-instantiated
349 | * PDO object as its database connection. Accepts an optional string key
350 | * to identify the connection if multiple connections are used.
351 | * @param PDO $db
352 | * @param string $connection_name Which connection to use
353 | */
354 | public static function set_db($db, $connection_name = self::DEFAULT_CONNECTION) {
355 | self::_setup_db_config($connection_name);
356 | self::$_db[$connection_name] = $db;
357 | if(!is_null(self::$_db[$connection_name])) {
358 | self::_setup_identifier_quote_character($connection_name);
359 | self::_setup_limit_clause_style($connection_name);
360 | }
361 | }
362 |
363 | /**
364 | * Close and delete all registered PDO objects in _db array.
365 | */
366 | public static function reset_db() {
367 | self::$_db = null;
368 |
369 | self::$_db = array();
370 | }
371 |
372 | /**
373 | * Detect and initialise the character used to quote identifiers
374 | * (table names, column names etc). If this has been specified
375 | * manually using ORM::configure('identifier_quote_character', 'some-char'),
376 | * this will do nothing.
377 | * @param string $connection_name Which connection to use
378 | */
379 | protected static function _setup_identifier_quote_character($connection_name) {
380 | if (is_null(self::$_config[$connection_name]['identifier_quote_character'])) {
381 | self::$_config[$connection_name]['identifier_quote_character'] =
382 | self::_detect_identifier_quote_character($connection_name);
383 | }
384 | }
385 |
386 | /**
387 | * Detect and initialise the limit clause style ("SELECT TOP 5" /
388 | * "... LIMIT 5"). If this has been specified manually using
389 | * ORM::configure('limit_clause_style', 'top'), this will do nothing.
390 | * @param string $connection_name Which connection to use
391 | */
392 | public static function _setup_limit_clause_style($connection_name) {
393 | if (is_null(self::$_config[$connection_name]['limit_clause_style'])) {
394 | self::$_config[$connection_name]['limit_clause_style'] =
395 | self::_detect_limit_clause_style($connection_name);
396 | }
397 | }
398 |
399 | /**
400 | * Return the correct character used to quote identifiers (table
401 | * names, column names etc) by looking at the driver being used by PDO.
402 | * @param string $connection_name Which connection to use
403 | * @return string
404 | */
405 | protected static function _detect_identifier_quote_character($connection_name) {
406 | switch(self::get_db($connection_name)->getAttribute(PDO::ATTR_DRIVER_NAME)) {
407 | case 'pgsql':
408 | case 'sqlsrv':
409 | case 'dblib':
410 | case 'mssql':
411 | case 'sybase':
412 | case 'firebird':
413 | return '"';
414 | case 'mysql':
415 | case 'sqlite':
416 | case 'sqlite2':
417 | default:
418 | return '`';
419 | }
420 | }
421 |
422 | /**
423 | * Returns a constant after determining the appropriate limit clause
424 | * style
425 | * @param string $connection_name Which connection to use
426 | * @return string Limit clause style keyword/constant
427 | */
428 | protected static function _detect_limit_clause_style($connection_name) {
429 | switch(self::get_db($connection_name)->getAttribute(PDO::ATTR_DRIVER_NAME)) {
430 | case 'sqlsrv':
431 | case 'dblib':
432 | case 'mssql':
433 | return ORM::LIMIT_STYLE_TOP_N;
434 | default:
435 | return ORM::LIMIT_STYLE_LIMIT;
436 | }
437 | }
438 |
439 | /**
440 | * Returns the PDO instance used by the the ORM to communicate with
441 | * the database. This can be called if any low-level DB access is
442 | * required outside the class. If multiple connections are used,
443 | * accepts an optional key name for the connection.
444 | * @param string $connection_name Which connection to use
445 | * @return PDO
446 | */
447 | public static function get_db($connection_name = self::DEFAULT_CONNECTION) {
448 | self::_setup_db($connection_name); // required in case this is called before Idiorm is instantiated
449 | return self::$_db[$connection_name];
450 | }
451 |
452 | /**
453 | * Executes a raw query as a wrapper for PDOStatement::execute.
454 | * Useful for queries that can't be accomplished through Idiorm,
455 | * particularly those using engine-specific features.
456 | * @example raw_execute('SELECT `name`, AVG(`order`) FROM `customer` GROUP BY `name` HAVING AVG(`order`) > 10')
457 | * @example raw_execute('INSERT OR REPLACE INTO `widget` (`id`, `name`) SELECT `id`, `name` FROM `other_table`')
458 | * @param string $query The raw SQL query
459 | * @param array $parameters Optional bound parameters
460 | * @param string $connection_name Which connection to use
461 | * @return bool Success
462 | */
463 | public static function raw_execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION) {
464 | self::_setup_db($connection_name);
465 | return self::_execute($query, $parameters, $connection_name);
466 | }
467 |
468 | /**
469 | * Returns the PDOStatement instance last used by any connection wrapped by the ORM.
470 | * Useful for access to PDOStatement::rowCount() or error information
471 | * @return PDOStatement
472 | */
473 | public static function get_last_statement() {
474 | return self::$_last_statement;
475 | }
476 |
477 | /**
478 | * Internal helper method for executing statments. Logs queries, and
479 | * stores statement object in ::_last_statment, accessible publicly
480 | * through ::get_last_statement()
481 | * @param string $query
482 | * @param array $parameters An array of parameters to be bound in to the query
483 | * @param string $connection_name Which connection to use
484 | * @return bool Response of PDOStatement::execute()
485 | */
486 | protected static function _execute($query, $parameters = array(), $connection_name = self::DEFAULT_CONNECTION) {
487 | $statement = self::get_db($connection_name)->prepare($query);
488 | self::$_last_statement = $statement;
489 | $time = microtime(true);
490 |
491 | foreach ($parameters as $key => &$param) {
492 | if (is_null($param)) {
493 | $type = PDO::PARAM_NULL;
494 | } else if (is_bool($param)) {
495 | $type = PDO::PARAM_BOOL;
496 | } else if (is_int($param)) {
497 | $type = PDO::PARAM_INT;
498 | } else {
499 | $type = PDO::PARAM_STR;
500 | }
501 |
502 | $statement->bindParam(is_int($key) ? ++$key : $key, $param, $type);
503 | }
504 |
505 | $q = $statement->execute();
506 | self::_log_query($query, $parameters, $connection_name, (microtime(true)-$time));
507 |
508 | return $q;
509 | }
510 |
511 | /**
512 | * Add a query to the internal query log. Only works if the
513 | * 'logging' config option is set to true.
514 | *
515 | * This works by manually binding the parameters to the query - the
516 | * query isn't executed like this (PDO normally passes the query and
517 | * parameters to the database which takes care of the binding) but
518 | * doing it this way makes the logged queries more readable.
519 | * @param string $query
520 | * @param array $parameters An array of parameters to be bound in to the query
521 | * @param string $connection_name Which connection to use
522 | * @param float $query_time Query time
523 | * @return bool
524 | */
525 | protected static function _log_query($query, $parameters, $connection_name, $query_time) {
526 | // If logging is not enabled, do nothing
527 | if (!self::$_config[$connection_name]['logging']) {
528 | return false;
529 | }
530 |
531 | if (!isset(self::$_query_log[$connection_name])) {
532 | self::$_query_log[$connection_name] = array();
533 | }
534 |
535 | if (empty($parameters)) {
536 | $bound_query = $query;
537 | } else {
538 | // Escape the parameters
539 | $parameters = array_map(array(self::get_db($connection_name), 'quote'), $parameters);
540 |
541 | if (array_values($parameters) === $parameters) {
542 | // ? placeholders
543 | // Avoid %format collision for vsprintf
544 | $query = str_replace("%", "%%", $query);
545 |
546 | // Replace placeholders in the query for vsprintf
547 | if(false !== strpos($query, "'") || false !== strpos($query, '"')) {
548 | $query = IdiormString::str_replace_outside_quotes("?", "%s", $query);
549 | } else {
550 | $query = str_replace("?", "%s", $query);
551 | }
552 |
553 | // Replace the question marks in the query with the parameters
554 | $bound_query = vsprintf($query, $parameters);
555 | } else {
556 | // named placeholders
557 | foreach ($parameters as $key => $val) {
558 | $query = str_replace($key, $val, $query);
559 | }
560 | $bound_query = $query;
561 | }
562 | }
563 |
564 | self::$_last_query = $bound_query;
565 | self::$_query_log[$connection_name][] = $bound_query;
566 |
567 |
568 | if(is_callable(self::$_config[$connection_name]['logger'])){
569 | $logger = self::$_config[$connection_name]['logger'];
570 | $logger($bound_query, $query_time);
571 | }
572 |
573 | return true;
574 | }
575 |
576 | /**
577 | * Get the last query executed. Only works if the
578 | * 'logging' config option is set to true. Otherwise
579 | * this will return null. Returns last query from all connections if
580 | * no connection_name is specified
581 | * @param null|string $connection_name Which connection to use
582 | * @return string
583 | */
584 | public static function get_last_query($connection_name = null) {
585 | if ($connection_name === null) {
586 | return self::$_last_query;
587 | }
588 | if (!isset(self::$_query_log[$connection_name])) {
589 | return '';
590 | }
591 |
592 | return end(self::$_query_log[$connection_name]);
593 | }
594 |
595 | /**
596 | * Get an array containing all the queries run on a
597 | * specified connection up to now.
598 | * Only works if the 'logging' config option is
599 | * set to true. Otherwise, returned array will be empty.
600 | * @param string $connection_name Which connection to use
601 | */
602 | public static function get_query_log($connection_name = self::DEFAULT_CONNECTION) {
603 | if (isset(self::$_query_log[$connection_name])) {
604 | return self::$_query_log[$connection_name];
605 | }
606 | return array();
607 | }
608 |
609 | /**
610 | * Get a list of the available connection names
611 | * @return array
612 | */
613 | public static function get_connection_names() {
614 | return array_keys(self::$_db);
615 | }
616 |
617 | // ------------------------ //
618 | // --- INSTANCE METHODS --- //
619 | // ------------------------ //
620 |
621 | /**
622 | * "Private" constructor; shouldn't be called directly.
623 | * Use the ORM::for_table factory method instead.
624 | */
625 | protected function __construct($table_name, $data = array(), $connection_name = self::DEFAULT_CONNECTION) {
626 | $this->_table_name = $table_name;
627 | $this->_data = $data;
628 |
629 | $this->_connection_name = $connection_name;
630 | self::_setup_db_config($connection_name);
631 | }
632 |
633 | /**
634 | * Create a new, empty instance of the class. Used
635 | * to add a new row to your database. May optionally
636 | * be passed an associative array of data to populate
637 | * the instance. If so, all fields will be flagged as
638 | * dirty so all will be saved to the database when
639 | * save() is called.
640 | */
641 | public function create($data=null) {
642 | $this->_is_new = true;
643 | if (!is_null($data)) {
644 | return $this->hydrate($data)->force_all_dirty();
645 | }
646 | return $this;
647 | }
648 |
649 | /**
650 | * Specify the ID column to use for this instance or array of instances only.
651 | * This overrides the id_column and id_column_overrides settings.
652 | *
653 | * This is mostly useful for libraries built on top of Idiorm, and will
654 | * not normally be used in manually built queries. If you don't know why
655 | * you would want to use this, you should probably just ignore it.
656 | */
657 | public function use_id_column($id_column) {
658 | $this->_instance_id_column = $id_column;
659 | return $this;
660 | }
661 |
662 | /**
663 | * Create an ORM instance from the given row (an associative
664 | * array of data fetched from the database)
665 | */
666 | protected function _create_instance_from_row($row) {
667 | $instance = self::for_table($this->_table_name, $this->_connection_name);
668 | $instance->use_id_column($this->_instance_id_column);
669 | $instance->hydrate($row);
670 | return $instance;
671 | }
672 |
673 | /**
674 | * Tell the ORM that you are expecting a single result
675 | * back from your query, and execute it. Will return
676 | * a single instance of the ORM class, or false if no
677 | * rows were returned.
678 | * As a shortcut, you may supply an ID as a parameter
679 | * to this method. This will perform a primary key
680 | * lookup on the table.
681 | */
682 | public function find_one($id=null) {
683 | if (!is_null($id)) {
684 | $this->where_id_is($id);
685 | }
686 | $this->limit(1);
687 | $rows = $this->_run();
688 |
689 | if (empty($rows)) {
690 | return false;
691 | }
692 |
693 | return $this->_create_instance_from_row($rows[0]);
694 | }
695 |
696 | /**
697 | * Tell the ORM that you are expecting multiple results
698 | * from your query, and execute it. Will return an array
699 | * of instances of the ORM class, or an empty array if
700 | * no rows were returned.
701 | * @return array|\IdiormResultSet
702 | */
703 | public function find_many() {
704 | if(self::$_config[$this->_connection_name]['return_result_sets']) {
705 | return $this->find_result_set();
706 | }
707 | return $this->_find_many();
708 | }
709 |
710 | /**
711 | * Tell the ORM that you are expecting multiple results
712 | * from your query, and execute it. Will return an array
713 | * of instances of the ORM class, or an empty array if
714 | * no rows were returned.
715 | * @return array
716 | */
717 | protected function _find_many() {
718 | $rows = $this->_run();
719 | return array_map(array($this, '_create_instance_from_row'), $rows);
720 | }
721 |
722 | /**
723 | * Tell the ORM that you are expecting multiple results
724 | * from your query, and execute it. Will return a result set object
725 | * containing instances of the ORM class.
726 | * @return \IdiormResultSet
727 | */
728 | public function find_result_set() {
729 | return new IdiormResultSet($this->_find_many());
730 | }
731 |
732 | /**
733 | * Tell the ORM that you are expecting multiple results
734 | * from your query, and execute it. Will return an array,
735 | * or an empty array if no rows were returned.
736 | * @return array
737 | */
738 | public function find_array() {
739 | return $this->_run();
740 | }
741 |
742 | /**
743 | * Tell the ORM that you wish to execute a COUNT query.
744 | * Will return an integer representing the number of
745 | * rows returned.
746 | */
747 | public function count($column = '*') {
748 | return $this->_call_aggregate_db_function(__FUNCTION__, $column);
749 | }
750 |
751 | /**
752 | * Tell the ORM that you wish to execute a MAX query.
753 | * Will return the max value of the choosen column.
754 | */
755 | public function max($column) {
756 | return $this->_call_aggregate_db_function(__FUNCTION__, $column);
757 | }
758 |
759 | /**
760 | * Tell the ORM that you wish to execute a MIN query.
761 | * Will return the min value of the choosen column.
762 | */
763 | public function min($column) {
764 | return $this->_call_aggregate_db_function(__FUNCTION__, $column);
765 | }
766 |
767 | /**
768 | * Tell the ORM that you wish to execute a AVG query.
769 | * Will return the average value of the choosen column.
770 | */
771 | public function avg($column) {
772 | return $this->_call_aggregate_db_function(__FUNCTION__, $column);
773 | }
774 |
775 | /**
776 | * Tell the ORM that you wish to execute a SUM query.
777 | * Will return the sum of the choosen column.
778 | */
779 | public function sum($column) {
780 | return $this->_call_aggregate_db_function(__FUNCTION__, $column);
781 | }
782 |
783 | /**
784 | * Execute an aggregate query on the current connection.
785 | * @param string $sql_function The aggregate function to call eg. MIN, COUNT, etc
786 | * @param string $column The column to execute the aggregate query against
787 | * @return int
788 | */
789 | protected function _call_aggregate_db_function($sql_function, $column) {
790 | $alias = strtolower($sql_function);
791 | $sql_function = strtoupper($sql_function);
792 | if('*' != $column) {
793 | $column = $this->_quote_identifier($column);
794 | }
795 | $result_columns = $this->_result_columns;
796 | $this->_result_columns = array();
797 | $this->select_expr("$sql_function($column)", $alias);
798 | $result = $this->find_one();
799 | $this->_result_columns = $result_columns;
800 |
801 | $return_value = 0;
802 | if($result !== false && isset($result->$alias)) {
803 | if (!is_numeric($result->$alias)) {
804 | $return_value = $result->$alias;
805 | }
806 | elseif((int) $result->$alias == (float) $result->$alias) {
807 | $return_value = (int) $result->$alias;
808 | } else {
809 | $return_value = (float) $result->$alias;
810 | }
811 | }
812 | return $return_value;
813 | }
814 |
815 | /**
816 | * This method can be called to hydrate (populate) this
817 | * instance of the class from an associative array of data.
818 | * This will usually be called only from inside the class,
819 | * but it's public in case you need to call it directly.
820 | */
821 | public function hydrate($data=array()) {
822 | $this->_data = $data;
823 | return $this;
824 | }
825 |
826 | /**
827 | * Force the ORM to flag all the fields in the $data array
828 | * as "dirty" and therefore update them when save() is called.
829 | */
830 | public function force_all_dirty() {
831 | $this->_dirty_fields = $this->_data;
832 | return $this;
833 | }
834 |
835 | /**
836 | * Perform a raw query. The query can contain placeholders in
837 | * either named or question mark style. If placeholders are
838 | * used, the parameters should be an array of values which will
839 | * be bound to the placeholders in the query. If this method
840 | * is called, all other query building methods will be ignored.
841 | */
842 | public function raw_query($query, $parameters = array()) {
843 | $this->_is_raw_query = true;
844 | $this->_raw_query = $query;
845 | $this->_raw_parameters = $parameters;
846 | return $this;
847 | }
848 |
849 | /**
850 | * Add an alias for the main table to be used in SELECT queries
851 | */
852 | public function table_alias($alias) {
853 | $this->_table_alias = $alias;
854 | return $this;
855 | }
856 |
857 | /**
858 | * Internal method to add an unquoted expression to the set
859 | * of columns returned by the SELECT query. The second optional
860 | * argument is the alias to return the expression as.
861 | */
862 | protected function _add_result_column($expr, $alias=null) {
863 | if (!is_null($alias)) {
864 | $expr .= " AS " . $this->_quote_identifier($alias);
865 | }
866 |
867 | if ($this->_using_default_result_columns) {
868 | $this->_result_columns = array($expr);
869 | $this->_using_default_result_columns = false;
870 | } else {
871 | $this->_result_columns[] = $expr;
872 | }
873 | return $this;
874 | }
875 |
876 | /**
877 | * Counts the number of columns that belong to the primary
878 | * key and their value is null.
879 | */
880 | public function count_null_id_columns() {
881 | if (is_array($this->_get_id_column_name())) {
882 | return count(array_filter($this->id(), 'is_null'));
883 | } else {
884 | return is_null($this->id()) ? 1 : 0;
885 | }
886 | }
887 |
888 | /**
889 | * Add a column to the list of columns returned by the SELECT
890 | * query. This defaults to '*'. The second optional argument is
891 | * the alias to return the column as.
892 | */
893 | public function select($column, $alias=null) {
894 | $column = $this->_quote_identifier($column);
895 | return $this->_add_result_column($column, $alias);
896 | }
897 |
898 | /**
899 | * Add an unquoted expression to the list of columns returned
900 | * by the SELECT query. The second optional argument is
901 | * the alias to return the column as.
902 | */
903 | public function select_expr($expr, $alias=null) {
904 | return $this->_add_result_column($expr, $alias);
905 | }
906 |
907 | /**
908 | * Add columns to the list of columns returned by the SELECT
909 | * query. This defaults to '*'. Many columns can be supplied
910 | * as either an array or as a list of parameters to the method.
911 | *
912 | * Note that the alias must not be numeric - if you want a
913 | * numeric alias then prepend it with some alpha chars. eg. a1
914 | *
915 | * @example select_many(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5');
916 | * @example select_many('column', 'column2', 'column3');
917 | * @example select_many(array('column', 'column2', 'column3'), 'column4', 'column5');
918 | *
919 | * @return \ORM
920 | */
921 | public function select_many() {
922 | $columns = func_get_args();
923 | if(!empty($columns)) {
924 | $columns = $this->_normalise_select_many_columns($columns);
925 | foreach($columns as $alias => $column) {
926 | if(is_numeric($alias)) {
927 | $alias = null;
928 | }
929 | $this->select($column, $alias);
930 | }
931 | }
932 | return $this;
933 | }
934 |
935 | /**
936 | * Add an unquoted expression to the list of columns returned
937 | * by the SELECT query. Many columns can be supplied as either
938 | * an array or as a list of parameters to the method.
939 | *
940 | * Note that the alias must not be numeric - if you want a
941 | * numeric alias then prepend it with some alpha chars. eg. a1
942 | *
943 | * @example select_many_expr(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5')
944 | * @example select_many_expr('column', 'column2', 'column3')
945 | * @example select_many_expr(array('column', 'column2', 'column3'), 'column4', 'column5')
946 | *
947 | * @return \ORM
948 | */
949 | public function select_many_expr() {
950 | $columns = func_get_args();
951 | if(!empty($columns)) {
952 | $columns = $this->_normalise_select_many_columns($columns);
953 | foreach($columns as $alias => $column) {
954 | if(is_numeric($alias)) {
955 | $alias = null;
956 | }
957 | $this->select_expr($column, $alias);
958 | }
959 | }
960 | return $this;
961 | }
962 |
963 | /**
964 | * Take a column specification for the select many methods and convert it
965 | * into a normalised array of columns and aliases.
966 | *
967 | * It is designed to turn the following styles into a normalised array:
968 | *
969 | * array(array('alias' => 'column', 'column2', 'alias2' => 'column3'), 'column4', 'column5'))
970 | *
971 | * @param array $columns
972 | * @return array
973 | */
974 | protected function _normalise_select_many_columns($columns) {
975 | $return = array();
976 | foreach($columns as $column) {
977 | if(is_array($column)) {
978 | foreach($column as $key => $value) {
979 | if(!is_numeric($key)) {
980 | $return[$key] = $value;
981 | } else {
982 | $return[] = $value;
983 | }
984 | }
985 | } else {
986 | $return[] = $column;
987 | }
988 | }
989 | return $return;
990 | }
991 |
992 | /**
993 | * Add a DISTINCT keyword before the list of columns in the SELECT query
994 | */
995 | public function distinct() {
996 | $this->_distinct = true;
997 | return $this;
998 | }
999 |
1000 | /**
1001 | * Internal method to add a JOIN source to the query.
1002 | *
1003 | * The join_operator should be one of INNER, LEFT OUTER, CROSS etc - this
1004 | * will be prepended to JOIN.
1005 | *
1006 | * The table should be the name of the table to join to.
1007 | *
1008 | * The constraint may be either a string or an array with three elements. If it
1009 | * is a string, it will be compiled into the query as-is, with no escaping. The
1010 | * recommended way to supply the constraint is as an array with three elements:
1011 | *
1012 | * first_column, operator, second_column
1013 | *
1014 | * Example: array('user.id', '=', 'profile.user_id')
1015 | *
1016 | * will compile to
1017 | *
1018 | * ON `user`.`id` = `profile`.`user_id`
1019 | *
1020 | * The final (optional) argument specifies an alias for the joined table.
1021 | */
1022 | protected function _add_join_source($join_operator, $table, $constraint, $table_alias=null) {
1023 |
1024 | $join_operator = trim("{$join_operator} JOIN");
1025 |
1026 | $table = $this->_quote_identifier($table);
1027 |
1028 | // Add table alias if present
1029 | if (!is_null($table_alias)) {
1030 | $table_alias = $this->_quote_identifier($table_alias);
1031 | $table .= " {$table_alias}";
1032 | }
1033 |
1034 | // Build the constraint
1035 | if (is_array($constraint)) {
1036 | list($first_column, $operator, $second_column) = $constraint;
1037 | $first_column = $this->_quote_identifier($first_column);
1038 | $second_column = $this->_quote_identifier($second_column);
1039 | $constraint = "{$first_column} {$operator} {$second_column}";
1040 | }
1041 |
1042 | $this->_join_sources[] = "{$join_operator} {$table} ON {$constraint}";
1043 | return $this;
1044 | }
1045 |
1046 | /**
1047 | * Add a RAW JOIN source to the query
1048 | */
1049 | public function raw_join($table, $constraint, $table_alias, $parameters = array()) {
1050 | // Add table alias if present
1051 | if (!is_null($table_alias)) {
1052 | $table_alias = $this->_quote_identifier($table_alias);
1053 | $table .= " {$table_alias}";
1054 | }
1055 |
1056 | $this->_values = array_merge($this->_values, $parameters);
1057 |
1058 | // Build the constraint
1059 | if (is_array($constraint)) {
1060 | list($first_column, $operator, $second_column) = $constraint;
1061 | $first_column = $this->_quote_identifier($first_column);
1062 | $second_column = $this->_quote_identifier($second_column);
1063 | $constraint = "{$first_column} {$operator} {$second_column}";
1064 | }
1065 |
1066 | $this->_join_sources[] = "{$table} ON {$constraint}";
1067 | return $this;
1068 | }
1069 |
1070 | /**
1071 | * Add a simple JOIN source to the query
1072 | */
1073 | public function join($table, $constraint, $table_alias=null) {
1074 | return $this->_add_join_source("", $table, $constraint, $table_alias);
1075 | }
1076 |
1077 | /**
1078 | * Add an INNER JOIN souce to the query
1079 | */
1080 | public function inner_join($table, $constraint, $table_alias=null) {
1081 | return $this->_add_join_source("INNER", $table, $constraint, $table_alias);
1082 | }
1083 |
1084 | /**
1085 | * Add a LEFT OUTER JOIN souce to the query
1086 | */
1087 | public function left_outer_join($table, $constraint, $table_alias=null) {
1088 | return $this->_add_join_source("LEFT OUTER", $table, $constraint, $table_alias);
1089 | }
1090 |
1091 | /**
1092 | * Add an RIGHT OUTER JOIN souce to the query
1093 | */
1094 | public function right_outer_join($table, $constraint, $table_alias=null) {
1095 | return $this->_add_join_source("RIGHT OUTER", $table, $constraint, $table_alias);
1096 | }
1097 |
1098 | /**
1099 | * Add an FULL OUTER JOIN souce to the query
1100 | */
1101 | public function full_outer_join($table, $constraint, $table_alias=null) {
1102 | return $this->_add_join_source("FULL OUTER", $table, $constraint, $table_alias);
1103 | }
1104 |
1105 | /**
1106 | * Internal method to add a HAVING condition to the query
1107 | */
1108 | protected function _add_having($fragment, $values=array()) {
1109 | return $this->_add_condition('having', $fragment, $values);
1110 | }
1111 |
1112 | /**
1113 | * Internal method to add a HAVING condition to the query
1114 | */
1115 | protected function _add_simple_having($column_name, $separator, $value) {
1116 | return $this->_add_simple_condition('having', $column_name, $separator, $value);
1117 | }
1118 |
1119 | /**
1120 | * Internal method to add a HAVING clause with multiple values (like IN and NOT IN)
1121 | */
1122 | public function _add_having_placeholder($column_name, $separator, $values) {
1123 | if (!is_array($column_name)) {
1124 | $data = array($column_name => $values);
1125 | } else {
1126 | $data = $column_name;
1127 | }
1128 | $result = $this;
1129 | foreach ($data as $key => $val) {
1130 | $column = $result->_quote_identifier($key);
1131 | $placeholders = $result->_create_placeholders($val);
1132 | $result = $result->_add_having("{$column} {$separator} ({$placeholders})", $val);
1133 | }
1134 | return $result;
1135 | }
1136 |
1137 | /**
1138 | * Internal method to add a HAVING clause with no parameters(like IS NULL and IS NOT NULL)
1139 | */
1140 | public function _add_having_no_value($column_name, $operator) {
1141 | $conditions = (is_array($column_name)) ? $column_name : array($column_name);
1142 | $result = $this;
1143 | foreach($conditions as $column) {
1144 | $column = $this->_quote_identifier($column);
1145 | $result = $result->_add_having("{$column} {$operator}");
1146 | }
1147 | return $result;
1148 | }
1149 |
1150 | /**
1151 | * Internal method to add a WHERE condition to the query
1152 | */
1153 | protected function _add_where($fragment, $values=array()) {
1154 | return $this->_add_condition('where', $fragment, $values);
1155 | }
1156 |
1157 | /**
1158 | * Internal method to add a WHERE condition to the query
1159 | */
1160 | protected function _add_simple_where($column_name, $separator, $value) {
1161 | return $this->_add_simple_condition('where', $column_name, $separator, $value);
1162 | }
1163 |
1164 | /**
1165 | * Add a WHERE clause with multiple values (like IN and NOT IN)
1166 | */
1167 | public function _add_where_placeholder($column_name, $separator, $values) {
1168 | if (!is_array($column_name)) {
1169 | $data = array($column_name => $values);
1170 | } else {
1171 | $data = $column_name;
1172 | }
1173 | $result = $this;
1174 | foreach ($data as $key => $val) {
1175 | $column = $result->_quote_identifier($key);
1176 | $placeholders = $result->_create_placeholders($val);
1177 | $result = $result->_add_where("{$column} {$separator} ({$placeholders})", $val);
1178 | }
1179 | return $result;
1180 | }
1181 |
1182 | /**
1183 | * Add a WHERE clause with no parameters(like IS NULL and IS NOT NULL)
1184 | */
1185 | public function _add_where_no_value($column_name, $operator) {
1186 | $conditions = (is_array($column_name)) ? $column_name : array($column_name);
1187 | $result = $this;
1188 | foreach($conditions as $column) {
1189 | $column = $this->_quote_identifier($column);
1190 | $result = $result->_add_where("{$column} {$operator}");
1191 | }
1192 | return $result;
1193 | }
1194 |
1195 | /**
1196 | * Internal method to add a HAVING or WHERE condition to the query
1197 | */
1198 | protected function _add_condition($type, $fragment, $values=array()) {
1199 | $conditions_class_property_name = "_{$type}_conditions";
1200 | if (!is_array($values)) {
1201 | $values = array($values);
1202 | }
1203 | array_push($this->$conditions_class_property_name, array(
1204 | self::CONDITION_FRAGMENT => $fragment,
1205 | self::CONDITION_VALUES => $values,
1206 | ));
1207 | return $this;
1208 | }
1209 |
1210 | /**
1211 | * Helper method to compile a simple COLUMN SEPARATOR VALUE
1212 | * style HAVING or WHERE condition into a string and value ready to
1213 | * be passed to the _add_condition method. Avoids duplication
1214 | * of the call to _quote_identifier
1215 | *
1216 | * If column_name is an associative array, it will add a condition for each column
1217 | */
1218 | protected function _add_simple_condition($type, $column_name, $separator, $value) {
1219 | $multiple = is_array($column_name) ? $column_name : array($column_name => $value);
1220 | $result = $this;
1221 |
1222 | foreach($multiple as $key => $val) {
1223 | // Add the table name in case of ambiguous columns
1224 | if (count($result->_join_sources) > 0 && strpos($key, '.') === false) {
1225 | $table = $result->_table_name;
1226 | if (!is_null($result->_table_alias)) {
1227 | $table = $result->_table_alias;
1228 | }
1229 |
1230 | $key = "{$table}.{$key}";
1231 | }
1232 | $key = $result->_quote_identifier($key);
1233 | $result = $result->_add_condition($type, "{$key} {$separator} ?", $val);
1234 | }
1235 | return $result;
1236 | }
1237 |
1238 | /**
1239 | * Return a string containing the given number of question marks,
1240 | * separated by commas. Eg "?, ?, ?"
1241 | */
1242 | protected function _create_placeholders($fields) {
1243 | if(!empty($fields)) {
1244 | $db_fields = array();
1245 | foreach($fields as $key => $value) {
1246 | // Process expression fields directly into the query
1247 | if(array_key_exists($key, $this->_expr_fields)) {
1248 | $db_fields[] = $value;
1249 | } else {
1250 | $db_fields[] = '?';
1251 | }
1252 | }
1253 | return implode(', ', $db_fields);
1254 | }
1255 | }
1256 |
1257 | /**
1258 | * Helper method that filters a column/value array returning only those
1259 | * columns that belong to a compound primary key.
1260 | *
1261 | * If the key contains a column that does not exist in the given array,
1262 | * a null value will be returned for it.
1263 | */
1264 | protected function _get_compound_id_column_values($value) {
1265 | $filtered = array();
1266 | foreach($this->_get_id_column_name() as $key) {
1267 | $filtered[$key] = isset($value[$key]) ? $value[$key] : null;
1268 | }
1269 | return $filtered;
1270 | }
1271 |
1272 | /**
1273 | * Helper method that filters an array containing compound column/value
1274 | * arrays.
1275 | */
1276 | protected function _get_compound_id_column_values_array($values) {
1277 | $filtered = array();
1278 | foreach($values as $value) {
1279 | $filtered[] = $this->_get_compound_id_column_values($value);
1280 | }
1281 | return $filtered;
1282 | }
1283 |
1284 | /**
1285 | * Add a WHERE column = value clause to your query. Each time
1286 | * this is called in the chain, an additional WHERE will be
1287 | * added, and these will be ANDed together when the final query
1288 | * is built.
1289 | *
1290 | * If you use an array in $column_name, a new clause will be
1291 | * added for each element. In this case, $value is ignored.
1292 | */
1293 | public function where($column_name, $value=null) {
1294 | return $this->where_equal($column_name, $value);
1295 | }
1296 |
1297 | /**
1298 | * More explicitly named version of for the where() method.
1299 | * Can be used if preferred.
1300 | */
1301 | public function where_equal($column_name, $value=null) {
1302 | return $this->_add_simple_where($column_name, '=', $value);
1303 | }
1304 |
1305 | /**
1306 | * Add a WHERE column != value clause to your query.
1307 | */
1308 | public function where_not_equal($column_name, $value=null) {
1309 | return $this->_add_simple_where($column_name, '!=', $value);
1310 | }
1311 |
1312 | /**
1313 | * Special method to query the table by its primary key
1314 | *
1315 | * If primary key is compound, only the columns that
1316 | * belong to they key will be used for the query
1317 | */
1318 | public function where_id_is($id) {
1319 | return (is_array($this->_get_id_column_name())) ?
1320 | $this->where($this->_get_compound_id_column_values($id), null) :
1321 | $this->where($this->_get_id_column_name(), $id);
1322 | }
1323 |
1324 | /**
1325 | * Allows adding a WHERE clause that matches any of the conditions
1326 | * specified in the array. Each element in the associative array will
1327 | * be a different condition, where the key will be the column name.
1328 | *
1329 | * By default, an equal operator will be used against all columns, but
1330 | * it can be overriden for any or every column using the second parameter.
1331 | *
1332 | * Each condition will be ORed together when added to the final query.
1333 | */
1334 | public function where_any_is($values, $operator='=') {
1335 | $data = array();
1336 | $query = array("((");
1337 | $first = true;
1338 | foreach ($values as $value) {
1339 | if ($first) {
1340 | $first = false;
1341 | } else {
1342 | $query[] = ") OR (";
1343 | }
1344 | $firstsub = true;
1345 | foreach($value as $key => $item) {
1346 | $op = is_string($operator) ? $operator : (isset($operator[$key]) ? $operator[$key] : '=');
1347 | if ($firstsub) {
1348 | $firstsub = false;
1349 | } else {
1350 | $query[] = "AND";
1351 | }
1352 | $query[] = $this->_quote_identifier($key);
1353 | $data[] = $item;
1354 | $query[] = $op . " ?";
1355 | }
1356 | }
1357 | $query[] = "))";
1358 | return $this->where_raw(join(' ', $query), $data);
1359 | }
1360 |
1361 | /**
1362 | * Similar to where_id_is() but allowing multiple primary keys.
1363 | *
1364 | * If primary key is compound, only the columns that
1365 | * belong to they key will be used for the query
1366 | */
1367 | public function where_id_in($ids) {
1368 | return (is_array($this->_get_id_column_name())) ?
1369 | $this->where_any_is($this->_get_compound_id_column_values_array($ids)) :
1370 | $this->where_in($this->_get_id_column_name(), $ids);
1371 | }
1372 |
1373 | /**
1374 | * Add a WHERE ... LIKE clause to your query.
1375 | */
1376 | public function where_like($column_name, $value=null) {
1377 | return $this->_add_simple_where($column_name, 'LIKE', $value);
1378 | }
1379 |
1380 | /**
1381 | * Add where WHERE ... NOT LIKE clause to your query.
1382 | */
1383 | public function where_not_like($column_name, $value=null) {
1384 | return $this->_add_simple_where($column_name, 'NOT LIKE', $value);
1385 | }
1386 |
1387 | /**
1388 | * Add a WHERE ... > clause to your query
1389 | */
1390 | public function where_gt($column_name, $value=null) {
1391 | return $this->_add_simple_where($column_name, '>', $value);
1392 | }
1393 |
1394 | /**
1395 | * Add a WHERE ... < clause to your query
1396 | */
1397 | public function where_lt($column_name, $value=null) {
1398 | return $this->_add_simple_where($column_name, '<', $value);
1399 | }
1400 |
1401 | /**
1402 | * Add a WHERE ... >= clause to your query
1403 | */
1404 | public function where_gte($column_name, $value=null) {
1405 | return $this->_add_simple_where($column_name, '>=', $value);
1406 | }
1407 |
1408 | /**
1409 | * Add a WHERE ... <= clause to your query
1410 | */
1411 | public function where_lte($column_name, $value=null) {
1412 | return $this->_add_simple_where($column_name, '<=', $value);
1413 | }
1414 |
1415 | /**
1416 | * Add a WHERE ... IN clause to your query
1417 | */
1418 | public function where_in($column_name, $values) {
1419 | return $this->_add_where_placeholder($column_name, 'IN', $values);
1420 | }
1421 |
1422 | /**
1423 | * Add a WHERE ... NOT IN clause to your query
1424 | */
1425 | public function where_not_in($column_name, $values) {
1426 | return $this->_add_where_placeholder($column_name, 'NOT IN', $values);
1427 | }
1428 |
1429 | /**
1430 | * Add a WHERE column IS NULL clause to your query
1431 | */
1432 | public function where_null($column_name) {
1433 | return $this->_add_where_no_value($column_name, "IS NULL");
1434 | }
1435 |
1436 | /**
1437 | * Add a WHERE column IS NOT NULL clause to your query
1438 | */
1439 | public function where_not_null($column_name) {
1440 | return $this->_add_where_no_value($column_name, "IS NOT NULL");
1441 | }
1442 |
1443 | /**
1444 | * Add a raw WHERE clause to the query. The clause should
1445 | * contain question mark placeholders, which will be bound
1446 | * to the parameters supplied in the second argument.
1447 | */
1448 | public function where_raw($clause, $parameters=array()) {
1449 | return $this->_add_where($clause, $parameters);
1450 | }
1451 |
1452 | /**
1453 | * Add a LIMIT to the query
1454 | */
1455 | public function limit($limit) {
1456 | $this->_limit = $limit;
1457 | return $this;
1458 | }
1459 |
1460 | /**
1461 | * Add an OFFSET to the query
1462 | */
1463 | public function offset($offset) {
1464 | $this->_offset = $offset;
1465 | return $this;
1466 | }
1467 |
1468 | /**
1469 | * Add an ORDER BY clause to the query
1470 | */
1471 | protected function _add_order_by($column_name, $ordering) {
1472 | $column_name = $this->_quote_identifier($column_name);
1473 | $this->_order_by[] = "{$column_name} {$ordering}";
1474 | return $this;
1475 | }
1476 |
1477 | /**
1478 | * Add an ORDER BY column DESC clause
1479 | */
1480 | public function order_by_desc($column_name) {
1481 | return $this->_add_order_by($column_name, 'DESC');
1482 | }
1483 |
1484 | /**
1485 | * Add an ORDER BY column ASC clause
1486 | */
1487 | public function order_by_asc($column_name) {
1488 | return $this->_add_order_by($column_name, 'ASC');
1489 | }
1490 |
1491 | /**
1492 | * Add an unquoted expression as an ORDER BY clause
1493 | */
1494 | public function order_by_expr($clause) {
1495 | $this->_order_by[] = $clause;
1496 | return $this;
1497 | }
1498 |
1499 | /**
1500 | * Add a column to the list of columns to GROUP BY
1501 | */
1502 | public function group_by($column_name) {
1503 | $column_name = $this->_quote_identifier($column_name);
1504 | $this->_group_by[] = $column_name;
1505 | return $this;
1506 | }
1507 |
1508 | /**
1509 | * Add an unquoted expression to the list of columns to GROUP BY
1510 | */
1511 | public function group_by_expr($expr) {
1512 | $this->_group_by[] = $expr;
1513 | return $this;
1514 | }
1515 |
1516 | /**
1517 | * Add a HAVING column = value clause to your query. Each time
1518 | * this is called in the chain, an additional HAVING will be
1519 | * added, and these will be ANDed together when the final query
1520 | * is built.
1521 | *
1522 | * If you use an array in $column_name, a new clause will be
1523 | * added for each element. In this case, $value is ignored.
1524 | */
1525 | public function having($column_name, $value=null) {
1526 | return $this->having_equal($column_name, $value);
1527 | }
1528 |
1529 | /**
1530 | * More explicitly named version of for the having() method.
1531 | * Can be used if preferred.
1532 | */
1533 | public function having_equal($column_name, $value=null) {
1534 | return $this->_add_simple_having($column_name, '=', $value);
1535 | }
1536 |
1537 | /**
1538 | * Add a HAVING column != value clause to your query.
1539 | */
1540 | public function having_not_equal($column_name, $value=null) {
1541 | return $this->_add_simple_having($column_name, '!=', $value);
1542 | }
1543 |
1544 | /**
1545 | * Special method to query the table by its primary key.
1546 | *
1547 | * If primary key is compound, only the columns that
1548 | * belong to they key will be used for the query
1549 | */
1550 | public function having_id_is($id) {
1551 | return (is_array($this->_get_id_column_name())) ?
1552 | $this->having($this->_get_compound_id_column_values($id), null) :
1553 | $this->having($this->_get_id_column_name(), $id);
1554 | }
1555 |
1556 | /**
1557 | * Add a HAVING ... LIKE clause to your query.
1558 | */
1559 | public function having_like($column_name, $value=null) {
1560 | return $this->_add_simple_having($column_name, 'LIKE', $value);
1561 | }
1562 |
1563 | /**
1564 | * Add where HAVING ... NOT LIKE clause to your query.
1565 | */
1566 | public function having_not_like($column_name, $value=null) {
1567 | return $this->_add_simple_having($column_name, 'NOT LIKE', $value);
1568 | }
1569 |
1570 | /**
1571 | * Add a HAVING ... > clause to your query
1572 | */
1573 | public function having_gt($column_name, $value=null) {
1574 | return $this->_add_simple_having($column_name, '>', $value);
1575 | }
1576 |
1577 | /**
1578 | * Add a HAVING ... < clause to your query
1579 | */
1580 | public function having_lt($column_name, $value=null) {
1581 | return $this->_add_simple_having($column_name, '<', $value);
1582 | }
1583 |
1584 | /**
1585 | * Add a HAVING ... >= clause to your query
1586 | */
1587 | public function having_gte($column_name, $value=null) {
1588 | return $this->_add_simple_having($column_name, '>=', $value);
1589 | }
1590 |
1591 | /**
1592 | * Add a HAVING ... <= clause to your query
1593 | */
1594 | public function having_lte($column_name, $value=null) {
1595 | return $this->_add_simple_having($column_name, '<=', $value);
1596 | }
1597 |
1598 | /**
1599 | * Add a HAVING ... IN clause to your query
1600 | */
1601 | public function having_in($column_name, $values=null) {
1602 | return $this->_add_having_placeholder($column_name, 'IN', $values);
1603 | }
1604 |
1605 | /**
1606 | * Add a HAVING ... NOT IN clause to your query
1607 | */
1608 | public function having_not_in($column_name, $values=null) {
1609 | return $this->_add_having_placeholder($column_name, 'NOT IN', $values);
1610 | }
1611 |
1612 | /**
1613 | * Add a HAVING column IS NULL clause to your query
1614 | */
1615 | public function having_null($column_name) {
1616 | return $this->_add_having_no_value($column_name, 'IS NULL');
1617 | }
1618 |
1619 | /**
1620 | * Add a HAVING column IS NOT NULL clause to your query
1621 | */
1622 | public function having_not_null($column_name) {
1623 | return $this->_add_having_no_value($column_name, 'IS NOT NULL');
1624 | }
1625 |
1626 | /**
1627 | * Add a raw HAVING clause to the query. The clause should
1628 | * contain question mark placeholders, which will be bound
1629 | * to the parameters supplied in the second argument.
1630 | */
1631 | public function having_raw($clause, $parameters=array()) {
1632 | return $this->_add_having($clause, $parameters);
1633 | }
1634 |
1635 | /**
1636 | * Build a SELECT statement based on the clauses that have
1637 | * been passed to this instance by chaining method calls.
1638 | */
1639 | protected function _build_select() {
1640 | // If the query is raw, just set the $this->_values to be
1641 | // the raw query parameters and return the raw query
1642 | if ($this->_is_raw_query) {
1643 | $this->_values = $this->_raw_parameters;
1644 | return $this->_raw_query;
1645 | }
1646 |
1647 | // Build and return the full SELECT statement by concatenating
1648 | // the results of calling each separate builder method.
1649 | return $this->_join_if_not_empty(" ", array(
1650 | $this->_build_select_start(),
1651 | $this->_build_join(),
1652 | $this->_build_where(),
1653 | $this->_build_group_by(),
1654 | $this->_build_having(),
1655 | $this->_build_order_by(),
1656 | $this->_build_limit(),
1657 | $this->_build_offset(),
1658 | ));
1659 | }
1660 |
1661 | /**
1662 | * Build the start of the SELECT statement
1663 | */
1664 | protected function _build_select_start() {
1665 | $fragment = 'SELECT ';
1666 | $result_columns = join(', ', $this->_result_columns);
1667 |
1668 | if (!is_null($this->_limit) &&
1669 | self::$_config[$this->_connection_name]['limit_clause_style'] === ORM::LIMIT_STYLE_TOP_N) {
1670 | $fragment .= "TOP {$this->_limit} ";
1671 | }
1672 |
1673 | if ($this->_distinct) {
1674 | $result_columns = 'DISTINCT ' . $result_columns;
1675 | }
1676 |
1677 | $fragment .= "{$result_columns} FROM " . $this->_quote_identifier($this->_table_name);
1678 |
1679 | if (!is_null($this->_table_alias)) {
1680 | $fragment .= " " . $this->_quote_identifier($this->_table_alias);
1681 | }
1682 | return $fragment;
1683 | }
1684 |
1685 | /**
1686 | * Build the JOIN sources
1687 | */
1688 | protected function _build_join() {
1689 | if (count($this->_join_sources) === 0) {
1690 | return '';
1691 | }
1692 |
1693 | return join(" ", $this->_join_sources);
1694 | }
1695 |
1696 | /**
1697 | * Build the WHERE clause(s)
1698 | */
1699 | protected function _build_where() {
1700 | return $this->_build_conditions('where');
1701 | }
1702 |
1703 | /**
1704 | * Build the HAVING clause(s)
1705 | */
1706 | protected function _build_having() {
1707 | return $this->_build_conditions('having');
1708 | }
1709 |
1710 | /**
1711 | * Build GROUP BY
1712 | */
1713 | protected function _build_group_by() {
1714 | if (count($this->_group_by) === 0) {
1715 | return '';
1716 | }
1717 | return "GROUP BY " . join(", ", $this->_group_by);
1718 | }
1719 |
1720 | /**
1721 | * Build a WHERE or HAVING clause
1722 | * @param string $type
1723 | * @return string
1724 | */
1725 | protected function _build_conditions($type) {
1726 | $conditions_class_property_name = "_{$type}_conditions";
1727 | // If there are no clauses, return empty string
1728 | if (count($this->$conditions_class_property_name) === 0) {
1729 | return '';
1730 | }
1731 |
1732 | $conditions = array();
1733 | foreach ($this->$conditions_class_property_name as $condition) {
1734 | $conditions[] = $condition[self::CONDITION_FRAGMENT];
1735 | $this->_values = array_merge($this->_values, $condition[self::CONDITION_VALUES]);
1736 | }
1737 |
1738 | return strtoupper($type) . " " . join(" AND ", $conditions);
1739 | }
1740 |
1741 | /**
1742 | * Build ORDER BY
1743 | */
1744 | protected function _build_order_by() {
1745 | if (count($this->_order_by) === 0) {
1746 | return '';
1747 | }
1748 | return "ORDER BY " . join(", ", $this->_order_by);
1749 | }
1750 |
1751 | /**
1752 | * Build LIMIT
1753 | */
1754 | protected function _build_limit() {
1755 | $fragment = '';
1756 | if (!is_null($this->_limit) &&
1757 | self::$_config[$this->_connection_name]['limit_clause_style'] == ORM::LIMIT_STYLE_LIMIT) {
1758 | if (self::get_db($this->_connection_name)->getAttribute(PDO::ATTR_DRIVER_NAME) == 'firebird') {
1759 | $fragment = 'ROWS';
1760 | } else {
1761 | $fragment = 'LIMIT';
1762 | }
1763 | $fragment .= " {$this->_limit}";
1764 | }
1765 | return $fragment;
1766 | }
1767 |
1768 | /**
1769 | * Build OFFSET
1770 | */
1771 | protected function _build_offset() {
1772 | if (!is_null($this->_offset)) {
1773 | $clause = 'OFFSET';
1774 | if (self::get_db($this->_connection_name)->getAttribute(PDO::ATTR_DRIVER_NAME) == 'firebird') {
1775 | $clause = 'TO';
1776 | }
1777 | return "$clause " . $this->_offset;
1778 | }
1779 | return '';
1780 | }
1781 |
1782 | /**
1783 | * Wrapper around PHP's join function which
1784 | * only adds the pieces if they are not empty.
1785 | */
1786 | protected function _join_if_not_empty($glue, $pieces) {
1787 | $filtered_pieces = array();
1788 | foreach ($pieces as $piece) {
1789 | if (is_string($piece)) {
1790 | $piece = trim($piece);
1791 | }
1792 | if (!empty($piece)) {
1793 | $filtered_pieces[] = $piece;
1794 | }
1795 | }
1796 | return join($glue, $filtered_pieces);
1797 | }
1798 |
1799 | /**
1800 | * Quote a string that is used as an identifier
1801 | * (table names, column names etc). This method can
1802 | * also deal with dot-separated identifiers eg table.column
1803 | */
1804 | protected function _quote_one_identifier($identifier) {
1805 | $parts = explode('.', $identifier);
1806 | $parts = array_map(array($this, '_quote_identifier_part'), $parts);
1807 | return join('.', $parts);
1808 | }
1809 |
1810 | /**
1811 | * Quote a string that is used as an identifier
1812 | * (table names, column names etc) or an array containing
1813 | * multiple identifiers. This method can also deal with
1814 | * dot-separated identifiers eg table.column
1815 | */
1816 | protected function _quote_identifier($identifier) {
1817 | if (is_array($identifier)) {
1818 | $result = array_map(array($this, '_quote_one_identifier'), $identifier);
1819 | return join(', ', $result);
1820 | } else {
1821 | return $this->_quote_one_identifier($identifier);
1822 | }
1823 | }
1824 |
1825 | /**
1826 | * This method performs the actual quoting of a single
1827 | * part of an identifier, using the identifier quote
1828 | * character specified in the config (or autodetected).
1829 | */
1830 | protected function _quote_identifier_part($part) {
1831 | if ($part === '*') {
1832 | return $part;
1833 | }
1834 |
1835 | $quote_character = self::$_config[$this->_connection_name]['identifier_quote_character'];
1836 | // double up any identifier quotes to escape them
1837 | return $quote_character .
1838 | str_replace($quote_character,
1839 | $quote_character . $quote_character,
1840 | $part
1841 | ) . $quote_character;
1842 | }
1843 |
1844 | /**
1845 | * Create a cache key for the given query and parameters.
1846 | */
1847 | protected static function _create_cache_key($query, $parameters, $table_name = null, $connection_name = self::DEFAULT_CONNECTION) {
1848 | if(isset(self::$_config[$connection_name]['create_cache_key']) and is_callable(self::$_config[$connection_name]['create_cache_key'])){
1849 | return call_user_func_array(self::$_config[$connection_name]['create_cache_key'], array($query, $parameters, $table_name, $connection_name));
1850 | }
1851 | $parameter_string = join(',', $parameters);
1852 | $key = $query . ':' . $parameter_string;
1853 | return sha1($key);
1854 | }
1855 |
1856 | /**
1857 | * Check the query cache for the given cache key. If a value
1858 | * is cached for the key, return the value. Otherwise, return false.
1859 | */
1860 | protected static function _check_query_cache($cache_key, $table_name = null, $connection_name = self::DEFAULT_CONNECTION) {
1861 | if(isset(self::$_config[$connection_name]['check_query_cache']) and is_callable(self::$_config[$connection_name]['check_query_cache'])){
1862 | return call_user_func_array(self::$_config[$connection_name]['check_query_cache'], array($cache_key, $table_name, $connection_name));
1863 | } elseif (isset(self::$_query_cache[$connection_name][$cache_key])) {
1864 | return self::$_query_cache[$connection_name][$cache_key];
1865 | }
1866 | return false;
1867 | }
1868 |
1869 | /**
1870 | * Clear the query cache
1871 | */
1872 | public static function clear_cache($table_name = null, $connection_name = self::DEFAULT_CONNECTION) {
1873 | self::$_query_cache = array();
1874 | if(isset(self::$_config[$connection_name]['clear_cache']) and is_callable(self::$_config[$connection_name]['clear_cache'])){
1875 | return call_user_func_array(self::$_config[$connection_name]['clear_cache'], array($table_name, $connection_name));
1876 | }
1877 | }
1878 |
1879 | /**
1880 | * Add the given value to the query cache.
1881 | */
1882 | protected static function _cache_query_result($cache_key, $value, $table_name = null, $connection_name = self::DEFAULT_CONNECTION) {
1883 | if(isset(self::$_config[$connection_name]['cache_query_result']) and is_callable(self::$_config[$connection_name]['cache_query_result'])){
1884 | return call_user_func_array(self::$_config[$connection_name]['cache_query_result'], array($cache_key, $value, $table_name, $connection_name));
1885 | } elseif (!isset(self::$_query_cache[$connection_name])) {
1886 | self::$_query_cache[$connection_name] = array();
1887 | }
1888 | self::$_query_cache[$connection_name][$cache_key] = $value;
1889 | }
1890 |
1891 | /**
1892 | * Execute the SELECT query that has been built up by chaining methods
1893 | * on this class. Return an array of rows as associative arrays.
1894 | */
1895 | protected function _run() {
1896 | $query = $this->_build_select();
1897 | $caching_enabled = self::$_config[$this->_connection_name]['caching'];
1898 |
1899 | if ($caching_enabled) {
1900 | $cache_key = self::_create_cache_key($query, $this->_values, $this->_table_name, $this->_connection_name);
1901 | $cached_result = self::_check_query_cache($cache_key, $this->_table_name, $this->_connection_name);
1902 |
1903 | if ($cached_result !== false) {
1904 | $this->_reset_idiorm_state();
1905 | return $cached_result;
1906 | }
1907 | }
1908 |
1909 | self::_execute($query, $this->_values, $this->_connection_name);
1910 | $statement = self::get_last_statement();
1911 |
1912 | $rows = array();
1913 | while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
1914 | $rows[] = $row;
1915 | }
1916 |
1917 | if ($caching_enabled) {
1918 | self::_cache_query_result($cache_key, $rows, $this->_table_name, $this->_connection_name);
1919 | }
1920 |
1921 | $this->_reset_idiorm_state();
1922 | return $rows;
1923 | }
1924 |
1925 | /**
1926 | * Reset the Idiorm instance state
1927 | */
1928 | private function _reset_idiorm_state() {
1929 | $this->_values = array();
1930 | $this->_result_columns = array('*');
1931 | $this->_using_default_result_columns = true;
1932 | }
1933 |
1934 | /**
1935 | * Return the raw data wrapped by this ORM
1936 | * instance as an associative array. Column
1937 | * names may optionally be supplied as arguments,
1938 | * if so, only those keys will be returned.
1939 | */
1940 | public function as_array() {
1941 | if (func_num_args() === 0) {
1942 | return $this->_data;
1943 | }
1944 | $args = func_get_args();
1945 | return array_intersect_key($this->_data, array_flip($args));
1946 | }
1947 |
1948 | /**
1949 | * Return the value of a property of this object (database row)
1950 | * or null if not present.
1951 | *
1952 | * If a column-names array is passed, it will return a associative array
1953 | * with the value of each column or null if it is not present.
1954 | */
1955 | public function get($key) {
1956 | if (is_array($key)) {
1957 | $result = array();
1958 | foreach($key as $column) {
1959 | $result[$column] = isset($this->_data[$column]) ? $this->_data[$column] : null;
1960 | }
1961 | return $result;
1962 | } else {
1963 | return isset($this->_data[$key]) ? $this->_data[$key] : null;
1964 | }
1965 | }
1966 |
1967 | /**
1968 | * Return the name of the column in the database table which contains
1969 | * the primary key ID of the row.
1970 | */
1971 | protected function _get_id_column_name() {
1972 | if (!is_null($this->_instance_id_column)) {
1973 | return $this->_instance_id_column;
1974 | }
1975 | if (isset(self::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name])) {
1976 | return self::$_config[$this->_connection_name]['id_column_overrides'][$this->_table_name];
1977 | }
1978 | return self::$_config[$this->_connection_name]['id_column'];
1979 | }
1980 |
1981 | /**
1982 | * Get the primary key ID of this object.
1983 | */
1984 | public function id($disallow_null = false) {
1985 | $id = $this->get($this->_get_id_column_name());
1986 |
1987 | if ($disallow_null) {
1988 | if (is_array($id)) {
1989 | foreach ($id as $id_part) {
1990 | if ($id_part === null) {
1991 | throw new Exception('Primary key ID contains null value(s)');
1992 | }
1993 | }
1994 | } else if ($id === null) {
1995 | throw new Exception('Primary key ID missing from row or is null');
1996 | }
1997 | }
1998 |
1999 | return $id;
2000 | }
2001 |
2002 | /**
2003 | * Set a property to a particular value on this object.
2004 | * To set multiple properties at once, pass an associative array
2005 | * as the first parameter and leave out the second parameter.
2006 | * Flags the properties as 'dirty' so they will be saved to the
2007 | * database when save() is called.
2008 | */
2009 | public function set($key, $value = null) {
2010 | return $this->_set_orm_property($key, $value);
2011 | }
2012 |
2013 | /**
2014 | * Set a property to a particular value on this object.
2015 | * To set multiple properties at once, pass an associative array
2016 | * as the first parameter and leave out the second parameter.
2017 | * Flags the properties as 'dirty' so they will be saved to the
2018 | * database when save() is called.
2019 | * @param string|array $key
2020 | * @param string|null $value
2021 | */
2022 | public function set_expr($key, $value = null) {
2023 | return $this->_set_orm_property($key, $value, true);
2024 | }
2025 |
2026 | /**
2027 | * Set a property on the ORM object.
2028 | * @param string|array $key
2029 | * @param string|null $value
2030 | * @param bool $raw Whether this value should be treated as raw or not
2031 | */
2032 | protected function _set_orm_property($key, $value = null, $expr = false) {
2033 | if (!is_array($key)) {
2034 | $key = array($key => $value);
2035 | }
2036 | foreach ($key as $field => $value) {
2037 | $this->_data[$field] = $value;
2038 | $this->_dirty_fields[$field] = $value;
2039 | if (false === $expr and isset($this->_expr_fields[$field])) {
2040 | unset($this->_expr_fields[$field]);
2041 | } else if (true === $expr) {
2042 | $this->_expr_fields[$field] = true;
2043 | }
2044 | }
2045 | return $this;
2046 | }
2047 |
2048 | /**
2049 | * Check whether the given field has been changed since this
2050 | * object was saved.
2051 | */
2052 | public function is_dirty($key) {
2053 | return array_key_exists($key, $this->_dirty_fields);
2054 | }
2055 |
2056 | /**
2057 | * Check whether the model was the result of a call to create() or not
2058 | * @return bool
2059 | */
2060 | public function is_new() {
2061 | return $this->_is_new;
2062 | }
2063 |
2064 | /**
2065 | * Save any fields which have been modified on this object
2066 | * to the database.
2067 | */
2068 | public function save() {
2069 | $query = array();
2070 |
2071 | // remove any expression fields as they are already baked into the query
2072 | $values = array_values(array_diff_key($this->_dirty_fields, $this->_expr_fields));
2073 |
2074 | if (!$this->_is_new) { // UPDATE
2075 | // If there are no dirty values, do nothing
2076 | if (empty($values) && empty($this->_expr_fields)) {
2077 | return true;
2078 | }
2079 | $query = $this->_build_update();
2080 | $id = $this->id(true);
2081 | if (is_array($id)) {
2082 | $values = array_merge($values, array_values($id));
2083 | } else {
2084 | $values[] = $id;
2085 | }
2086 | } else { // INSERT
2087 | $query = $this->_build_insert();
2088 | }
2089 |
2090 | $success = self::_execute($query, $values, $this->_connection_name);
2091 | $caching_auto_clear_enabled = self::$_config[$this->_connection_name]['caching_auto_clear'];
2092 | if($caching_auto_clear_enabled){
2093 | self::clear_cache($this->_table_name, $this->_connection_name);
2094 | }
2095 | // If we've just inserted a new record, set the ID of this object
2096 | if ($this->_is_new) {
2097 | $this->_is_new = false;
2098 | if ($this->count_null_id_columns() != 0) {
2099 | $db = self::get_db($this->_connection_name);
2100 | if($db->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') {
2101 | // it may return several columns if a compound primary
2102 | // key is used
2103 | $row = self::get_last_statement()->fetch(PDO::FETCH_ASSOC);
2104 | foreach($row as $key => $value) {
2105 | $this->_data[$key] = $value;
2106 | }
2107 | } else {
2108 | $column = $this->_get_id_column_name();
2109 | // if the primary key is compound, assign the last inserted id
2110 | // to the first column
2111 | if (is_array($column)) {
2112 | $column = reset($column);
2113 | }
2114 | $this->_data[$column] = $db->lastInsertId();
2115 | }
2116 | }
2117 | }
2118 |
2119 | $this->_dirty_fields = $this->_expr_fields = array();
2120 | return $success;
2121 | }
2122 |
2123 | /**
2124 | * Add a WHERE clause for every column that belongs to the primary key
2125 | */
2126 | public function _add_id_column_conditions(&$query) {
2127 | $query[] = "WHERE";
2128 | $keys = is_array($this->_get_id_column_name()) ? $this->_get_id_column_name() : array( $this->_get_id_column_name() );
2129 | $first = true;
2130 | foreach($keys as $key) {
2131 | if ($first) {
2132 | $first = false;
2133 | }
2134 | else {
2135 | $query[] = "AND";
2136 | }
2137 | $query[] = $this->_quote_identifier($key);
2138 | $query[] = "= ?";
2139 | }
2140 | }
2141 |
2142 | /**
2143 | * Build an UPDATE query
2144 | */
2145 | protected function _build_update() {
2146 | $query = array();
2147 | $query[] = "UPDATE {$this->_quote_identifier($this->_table_name)} SET";
2148 |
2149 | $field_list = array();
2150 | foreach ($this->_dirty_fields as $key => $value) {
2151 | if(!array_key_exists($key, $this->_expr_fields)) {
2152 | $value = '?';
2153 | }
2154 | $field_list[] = "{$this->_quote_identifier($key)} = $value";
2155 | }
2156 | $query[] = join(", ", $field_list);
2157 | $this->_add_id_column_conditions($query);
2158 | return join(" ", $query);
2159 | }
2160 |
2161 | /**
2162 | * Build an INSERT query
2163 | */
2164 | protected function _build_insert() {
2165 | $query[] = "INSERT INTO";
2166 | $query[] = $this->_quote_identifier($this->_table_name);
2167 | $field_list = array_map(array($this, '_quote_identifier'), array_keys($this->_dirty_fields));
2168 | $query[] = "(" . join(", ", $field_list) . ")";
2169 | $query[] = "VALUES";
2170 |
2171 | $placeholders = $this->_create_placeholders($this->_dirty_fields);
2172 | $query[] = "({$placeholders})";
2173 |
2174 | if (self::get_db($this->_connection_name)->getAttribute(PDO::ATTR_DRIVER_NAME) == 'pgsql') {
2175 | $query[] = 'RETURNING ' . $this->_quote_identifier($this->_get_id_column_name());
2176 | }
2177 |
2178 | return join(" ", $query);
2179 | }
2180 |
2181 | /**
2182 | * Delete this record from the database
2183 | */
2184 | public function delete() {
2185 | $query = array(
2186 | "DELETE FROM",
2187 | $this->_quote_identifier($this->_table_name)
2188 | );
2189 | $this->_add_id_column_conditions($query);
2190 | return self::_execute(join(" ", $query), is_array($this->id(true)) ? array_values($this->id(true)) : array($this->id(true)), $this->_connection_name);
2191 | }
2192 |
2193 | /**
2194 | * Delete many records from the database
2195 | */
2196 | public function delete_many() {
2197 | // Build and return the full DELETE statement by concatenating
2198 | // the results of calling each separate builder method.
2199 | $query = $this->_join_if_not_empty(" ", array(
2200 | "DELETE FROM",
2201 | $this->_quote_identifier($this->_table_name),
2202 | $this->_build_where(),
2203 | ));
2204 |
2205 | return self::_execute($query, $this->_values, $this->_connection_name);
2206 | }
2207 |
2208 | // --------------------- //
2209 | // --- ArrayAccess --- //
2210 | // --------------------- //
2211 |
2212 | #[\ReturnTypeWillChange]
2213 | public function offsetExists($key) {
2214 | return array_key_exists($key, $this->_data);
2215 | }
2216 |
2217 | #[\ReturnTypeWillChange]
2218 | public function offsetGet($key) {
2219 | return $this->get($key);
2220 | }
2221 |
2222 | #[\ReturnTypeWillChange]
2223 | public function offsetSet($key, $value) {
2224 | if(is_null($key)) {
2225 | throw new InvalidArgumentException('You must specify a key/array index.');
2226 | }
2227 | $this->set($key, $value);
2228 | }
2229 |
2230 | #[\ReturnTypeWillChange]
2231 | public function offsetUnset($key) {
2232 | unset($this->_data[$key]);
2233 | unset($this->_dirty_fields[$key]);
2234 | }
2235 |
2236 | // --------------------- //
2237 | // --- MAGIC METHODS --- //
2238 | // --------------------- //
2239 | public function __get($key) {
2240 | return $this->offsetGet($key);
2241 | }
2242 |
2243 | public function __set($key, $value) {
2244 | $this->offsetSet($key, $value);
2245 | }
2246 |
2247 | public function __unset($key) {
2248 | $this->offsetUnset($key);
2249 | }
2250 |
2251 |
2252 | public function __isset($key) {
2253 | return $this->offsetExists($key);
2254 | }
2255 |
2256 | /**
2257 | * Magic method to capture calls to undefined class methods.
2258 | * In this case we are attempting to convert camel case formatted
2259 | * methods into underscore formatted methods.
2260 | *
2261 | * This allows us to call ORM methods using camel case and remain
2262 | * backwards compatible.
2263 | *
2264 | * @param string $name
2265 | * @param array $arguments
2266 | * @return ORM
2267 | */
2268 | public function __call($name, $arguments)
2269 | {
2270 | $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
2271 |
2272 | if (method_exists($this, $method)) {
2273 | return call_user_func_array(array($this, $method), $arguments);
2274 | } else {
2275 | throw new IdiormMethodMissingException("Method $name() does not exist in class " . get_class($this));
2276 | }
2277 | }
2278 |
2279 | /**
2280 | * Magic method to capture calls to undefined static class methods.
2281 | * In this case we are attempting to convert camel case formatted
2282 | * methods into underscore formatted methods.
2283 | *
2284 | * This allows us to call ORM methods using camel case and remain
2285 | * backwards compatible.
2286 | *
2287 | * @param string $name
2288 | * @param array $arguments
2289 | * @return ORM
2290 | */
2291 | public static function __callStatic($name, $arguments)
2292 | {
2293 | $method = strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name));
2294 |
2295 | return call_user_func_array(array('ORM', $method), $arguments);
2296 | }
2297 | }
2298 |
2299 | /**
2300 | * A class to handle str_replace operations that involve quoted strings
2301 | * @example IdiormString::str_replace_outside_quotes('?', '%s', 'columnA = "Hello?" AND columnB = ?');
2302 | * @example IdiormString::value('columnA = "Hello?" AND columnB = ?')->replace_outside_quotes('?', '%s');
2303 | * @author Jeff Roberson
2304 | * @author Simon Holywell
2305 | * @link http://stackoverflow.com/a/13370709/461813 StackOverflow answer
2306 | */
2307 | class IdiormString {
2308 | protected $subject;
2309 | protected $search;
2310 | protected $replace;
2311 |
2312 | /**
2313 | * Get an easy to use instance of the class
2314 | * @param string $subject
2315 | * @return \self
2316 | */
2317 | public static function value($subject) {
2318 | return new self($subject);
2319 | }
2320 |
2321 | /**
2322 | * Shortcut method: Replace all occurrences of the search string with the replacement
2323 | * string where they appear outside quotes.
2324 | * @param string $search
2325 | * @param string $replace
2326 | * @param string $subject
2327 | * @return string
2328 | */
2329 | public static function str_replace_outside_quotes($search, $replace, $subject) {
2330 | return self::value($subject)->replace_outside_quotes($search, $replace);
2331 | }
2332 |
2333 | /**
2334 | * Set the base string object
2335 | * @param string $subject
2336 | */
2337 | public function __construct($subject) {
2338 | $this->subject = (string) $subject;
2339 | }
2340 |
2341 | /**
2342 | * Replace all occurrences of the search string with the replacement
2343 | * string where they appear outside quotes
2344 | * @param string $search
2345 | * @param string $replace
2346 | * @return string
2347 | */
2348 | public function replace_outside_quotes($search, $replace) {
2349 | $this->search = $search;
2350 | $this->replace = $replace;
2351 | return $this->_str_replace_outside_quotes();
2352 | }
2353 |
2354 | /**
2355 | * Validate an input string and perform a replace on all ocurrences
2356 | * of $this->search with $this->replace
2357 | * @author Jeff Roberson
2358 | * @link http://stackoverflow.com/a/13370709/461813 StackOverflow answer
2359 | * @return string
2360 | */
2361 | protected function _str_replace_outside_quotes(){
2362 | $re_valid = '/
2363 | # Validate string having embedded quoted substrings.
2364 | ^ # Anchor to start of string.
2365 | (?: # Zero or more string chunks.
2366 | "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" # Either a double quoted chunk,
2367 | | \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # or a single quoted chunk,
2368 | | [^\'"\\\\]+ # or an unquoted chunk (no escapes).
2369 | )* # Zero or more string chunks.
2370 | \z # Anchor to end of string.
2371 | /sx';
2372 | if (!preg_match($re_valid, $this->subject)) {
2373 | throw new IdiormStringException("Subject string is not valid in the replace_outside_quotes context.");
2374 | }
2375 | $re_parse = '/
2376 | # Match one chunk of a valid string having embedded quoted substrings.
2377 | ( # Either $1: Quoted chunk.
2378 | "[^"\\\\]*(?:\\\\.[^"\\\\]*)*" # Either a double quoted chunk,
2379 | | \'[^\'\\\\]*(?:\\\\.[^\'\\\\]*)*\' # or a single quoted chunk.
2380 | ) # End $1: Quoted chunk.
2381 | | ([^\'"\\\\]+) # or $2: an unquoted chunk (no escapes).
2382 | /sx';
2383 | return preg_replace_callback($re_parse, array($this, '_str_replace_outside_quotes_cb'), $this->subject);
2384 | }
2385 |
2386 | /**
2387 | * Process each matching chunk from preg_replace_callback replacing
2388 | * each occurrence of $this->search with $this->replace
2389 | * @author Jeff Roberson
2390 | * @link http://stackoverflow.com/a/13370709/461813 StackOverflow answer
2391 | * @param array $matches
2392 | * @return string
2393 | */
2394 | protected function _str_replace_outside_quotes_cb($matches) {
2395 | // Return quoted string chunks (in group $1) unaltered.
2396 | if ($matches[1]) return $matches[1];
2397 | // Process only unquoted chunks (in group $2).
2398 | return preg_replace('/'. preg_quote($this->search, '/') .'/',
2399 | $this->replace, $matches[2]);
2400 | }
2401 | }
2402 |
2403 | /**
2404 | * A result set class for working with collections of model instances
2405 | * @author Simon Holywell
2406 | * @method null setResults(array $results)
2407 | * @method array getResults()
2408 | */
2409 | class IdiormResultSet implements Countable, IteratorAggregate, ArrayAccess, Serializable {
2410 | /**
2411 | * The current result set as an array
2412 | * @var array
2413 | */
2414 | protected $_results = array();
2415 |
2416 | /**
2417 | * Optionally set the contents of the result set by passing in array
2418 | * @param array $results
2419 | */
2420 | public function __construct(array $results = array()) {
2421 | $this->set_results($results);
2422 | }
2423 |
2424 | /**
2425 | * Set the contents of the result set by passing in array
2426 | * @param array $results
2427 | */
2428 | public function set_results(array $results) {
2429 | $this->_results = $results;
2430 | }
2431 |
2432 | /**
2433 | * Get the current result set as an array
2434 | * @return array
2435 | */
2436 | public function get_results() {
2437 | return $this->_results;
2438 | }
2439 |
2440 | /**
2441 | * Get the current result set as an array
2442 | * @return array
2443 | */
2444 | public function as_array() {
2445 | return $this->get_results();
2446 | }
2447 |
2448 | /**
2449 | * Get the number of records in the result set
2450 | * @return int
2451 | */
2452 | #[\ReturnTypeWillChange]
2453 | public function count() {
2454 | return count($this->_results);
2455 | }
2456 |
2457 | /**
2458 | * Get an iterator for this object. In this case it supports foreaching
2459 | * over the result set.
2460 | * @return \ArrayIterator
2461 | */
2462 | #[\ReturnTypeWillChange]
2463 | public function getIterator() {
2464 | return new ArrayIterator($this->_results);
2465 | }
2466 |
2467 | /**
2468 | * ArrayAccess
2469 | * @param int|string $offset
2470 | * @return bool
2471 | */
2472 | #[\ReturnTypeWillChange]
2473 | public function offsetExists($offset) {
2474 | return isset($this->_results[$offset]);
2475 | }
2476 |
2477 | /**
2478 | * ArrayAccess
2479 | * @param int|string $offset
2480 | * @return mixed
2481 | */
2482 | #[\ReturnTypeWillChange]
2483 | public function offsetGet($offset) {
2484 | return $this->_results[$offset];
2485 | }
2486 |
2487 | /**
2488 | * ArrayAccess
2489 | * @param int|string $offset
2490 | * @param mixed $value
2491 | */
2492 | #[\ReturnTypeWillChange]
2493 | public function offsetSet($offset, $value) {
2494 | $this->_results[$offset] = $value;
2495 | }
2496 |
2497 | /**
2498 | * ArrayAccess
2499 | * @param int|string $offset
2500 | */
2501 | #[\ReturnTypeWillChange]
2502 | public function offsetUnset($offset) {
2503 | unset($this->_results[$offset]);
2504 | }
2505 |
2506 | public function __serialize() {
2507 | return $this->serialize();
2508 | }
2509 |
2510 | public function __unserialize($data) {
2511 | $this->unserialize($data);
2512 | }
2513 |
2514 | /**
2515 | * Serializable
2516 | * @return string
2517 | */
2518 | public function serialize() {
2519 | return serialize($this->_results);
2520 | }
2521 |
2522 | /**
2523 | * Serializable
2524 | * @param string $serialized
2525 | * @return array
2526 | */
2527 | public function unserialize($serialized) {
2528 | return unserialize($serialized);
2529 | }
2530 |
2531 | /**
2532 | * Call a method on all models in a result set. This allows for method
2533 | * chaining such as setting a property on all models in a result set or
2534 | * any other batch operation across models.
2535 | * @example ORM::for_table('Widget')->find_many()->set('field', 'value')->save();
2536 | * @param string $method
2537 | * @param array $params
2538 | * @return \IdiormResultSet
2539 | */
2540 | public function __call($method, $params = array()) {
2541 | foreach($this->_results as $model) {
2542 | if (method_exists($model, $method)) {
2543 | call_user_func_array(array($model, $method), $params);
2544 | } else {
2545 | throw new IdiormMethodMissingException("Method $method() does not exist in class " . get_class($this));
2546 | }
2547 | }
2548 | return $this;
2549 | }
2550 | }
2551 |
2552 | /**
2553 | * A placeholder for exceptions eminating from the IdiormString class
2554 | */
2555 | class IdiormStringException extends Exception {}
2556 |
2557 | class IdiormMethodMissingException extends Exception {}
2558 |
--------------------------------------------------------------------------------