├── 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 | 57 | Idiorm Demo 58 | 59 | 60 | 61 | 62 |

Idiorm Demo

63 | 64 |

Contact List ( contacts)

65 | 73 | 74 |
75 |

Add Contact

76 |

77 |

78 | 79 |
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 | --------------------------------------------------------------------------------