diff --git a/tl/404.rst b/tl/404.rst new file mode 100644 index 0000000000000000000000000000000000000000..6c676eac39cd7e9903f9e320fdedf4b910504052 --- /dev/null +++ b/tl/404.rst @@ -0,0 +1,7 @@ +:orphan: True + +Not Found +######### + +The page you're looking for cannot be found. Try using the search bar to find +what you're looking for. diff --git a/tl/Makefile b/tl/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..9d2fee1833a1bd9b91ab721e022d48aa7a39a269 --- /dev/null +++ b/tl/Makefile @@ -0,0 +1,132 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = ../build +PYTHON = python +LANG = en + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees/$(LANG) $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html/$(LANG) + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html/$(LANG)." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp/$(LANG) + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp/$(LANG)." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/CakePHPCookbook.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/CakePHPCookbook.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/CakePHPCookbook" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/CakePHPCookbook" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub -D master_doc='epub-contents' $(ALLSPHINXOPTS) $(BUILDDIR)/epub/$(LANG) + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub/$(LANG)." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex/$(LANG) + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex/$(LANG)." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex/$(LANG) + @echo "Running LaTeX files through pdflatex..." + make -C $(BUILDDIR)/latex/$(LANG) all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex/$(LANG)." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/tl/_static/img/debug-kit/history-panel-use.gif b/tl/_static/img/debug-kit/history-panel-use.gif new file mode 100644 index 0000000000000000000000000000000000000000..54efaba67859b77f75275c705d4c2f11f59f5b53 Binary files /dev/null and b/tl/_static/img/debug-kit/history-panel-use.gif differ diff --git a/tl/_static/img/debug-kit/history-panel.png b/tl/_static/img/debug-kit/history-panel.png new file mode 100644 index 0000000000000000000000000000000000000000..0c6b27e20c8c6ac8896b6f97bdcc3732f6ffdd74 Binary files /dev/null and b/tl/_static/img/debug-kit/history-panel.png differ diff --git a/tl/_static/img/debug-kit/mail-panel.gif b/tl/_static/img/debug-kit/mail-panel.gif new file mode 100644 index 0000000000000000000000000000000000000000..2d0409d6bcb5c1a2ec9f683fb18a4212dd4b4ebc Binary files /dev/null and b/tl/_static/img/debug-kit/mail-panel.gif differ diff --git a/tl/_static/img/debug-kit/mail-previewer.gif b/tl/_static/img/debug-kit/mail-previewer.gif new file mode 100644 index 0000000000000000000000000000000000000000..1302654345718f6d441997c92ea1ab5862ca4127 Binary files /dev/null and b/tl/_static/img/debug-kit/mail-previewer.gif differ diff --git a/tl/_static/img/middleware-request.png b/tl/_static/img/middleware-request.png new file mode 100644 index 0000000000000000000000000000000000000000..7301b090339d4336c7fb804cb876438931d8638d Binary files /dev/null and b/tl/_static/img/middleware-request.png differ diff --git a/tl/_static/img/middleware-setup.png b/tl/_static/img/middleware-setup.png new file mode 100644 index 0000000000000000000000000000000000000000..5e65f5c4990fcd0518544edde4b88dc972c6cfb8 Binary files /dev/null and b/tl/_static/img/middleware-setup.png differ diff --git a/tl/_static/img/read-the-book.jpg b/tl/_static/img/read-the-book.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb3bd7cd7882387adf6b0b799b5aebb50a9c32e6 Binary files /dev/null and b/tl/_static/img/read-the-book.jpg differ diff --git a/tl/_static/img/save-cycle.png b/tl/_static/img/save-cycle.png new file mode 100644 index 0000000000000000000000000000000000000000..8c133e5f6a7f1db9c586005121c95a9e14b18773 Binary files /dev/null and b/tl/_static/img/save-cycle.png differ diff --git a/tl/_static/img/typical-cake-request.png b/tl/_static/img/typical-cake-request.png new file mode 100644 index 0000000000000000000000000000000000000000..15c2ed501c3324af460563344982397b6d5db691 Binary files /dev/null and b/tl/_static/img/typical-cake-request.png differ diff --git a/tl/_static/img/validation-cycle.png b/tl/_static/img/validation-cycle.png new file mode 100644 index 0000000000000000000000000000000000000000..85ee172442f702a9e1b821bcdaf5fdd7c3cb769a Binary files /dev/null and b/tl/_static/img/validation-cycle.png differ diff --git a/tl/appendices.rst b/tl/appendices.rst new file mode 100644 index 0000000000000000000000000000000000000000..436a311df4b7a9832e3db36343c8b63df1ca1ce2 --- /dev/null +++ b/tl/appendices.rst @@ -0,0 +1,26 @@ +Appendices +########## + +Appendices contain information regarding the new features +introduced in each version and the migration path between versions. + +3.x Migration Guide +=================== + +.. toctree:: + :maxdepth: 1 + + appendices/3-x-migration-guide + +General Information +=================== + +.. toctree:: + :maxdepth: 1 + + appendices/cakephp-development-process + appendices/glossary + +.. meta:: + :title lang=en: Appendices + :keywords lang=en: migration guide,migration path,new features,glossary diff --git a/tl/appendices/3-0-migration-guide.rst b/tl/appendices/3-0-migration-guide.rst new file mode 100644 index 0000000000000000000000000000000000000000..1c480e6d4ca3dedf4e04700d15f6cbb31704b585 --- /dev/null +++ b/tl/appendices/3-0-migration-guide.rst @@ -0,0 +1,1275 @@ +3.0 Migration Guide +################### + +This page summarizes the changes from CakePHP 2.x that will assist in migrating +a project to 3.0, as well as a reference to get up to date with the changes made +to the core since the CakePHP 2.x branch. Be sure to read the other pages in +this guide for all the new features and API changes. + +Requirements +============ + +- CakePHP 3.x supports PHP Version 5.4.16 and above. +- CakePHP 3.x requires the mbstring extension. +- CakePHP 3.x requires the intl extension. + +.. warning:: + + CakePHP 3.0 will not work if you do not meet the above requirements. + +Upgrade Tool +============ + +While this document covers all the breaking changes and improvements made in +CakePHP 3.0, we've also created a console application to help you complete some +of the time consuming mechanical changes. You can `get the upgrade tool from +github `_. + +Application Directory Layout +============================ + +The application directory layout has changed and now follows +`PSR-4 `_. You should use the +`app skeleton `_ project as a reference point +when updating your application. + +CakePHP should be installed with Composer +========================================= + +Since CakePHP can no longer be installed via PEAR, or in a shared +directory, those options are no longer supported. Instead you should use +`Composer `_ to install CakePHP into your application. + +Namespaces +========== + +All of CakePHP's core classes are now namespaced and follow PSR-4 autoloading +specifications. For example **src/Cache/Cache.php** is namespaced as +``Cake\Cache\Cache``. Global constants and helper methods like :php:meth:`__()` +and :php:meth:`debug()` are not namespaced for convenience sake. + +Removed Constants +================= + +The following deprecated constants have been removed: + +* ``IMAGES`` +* ``CSS`` +* ``JS`` +* ``IMAGES_URL`` +* ``JS_URL`` +* ``CSS_URL`` +* ``DEFAULT_LANGUAGE`` + +Configuration +============= + +Configuration in CakePHP 3.0 is significantly different than in previous +versions. You should read the :doc:`/development/configuration` documentation +for how configuration is done in 3.0. + +You can no longer use ``App::build()`` to configure additional class paths. +Instead you should map additional paths using your application's autoloader. See +the section on :ref:`additional-class-paths` for more information. + +Three new configure variables provide the path configuration for plugins, +views and locale files. You can add multiple paths to ``App.paths.templates``, +``App.paths.plugins``, ``App.paths.locales`` to configure multiple paths for +templates, plugins and locale files respectively. + +The config key ``www_root`` has been changed to ``wwwRoot`` for consistency. Please adjust +your **app.php** config file as well as any usage of ``Configure::read('App.wwwRoot')``. + +New ORM +======= + +CakePHP 3.0 features a new ORM that has been re-built from the ground up. The +new ORM is significantly different and incompatible with the previous one. +Upgrading to the new ORM will require extensive changes in any application that +is being upgraded. See the new :doc:`/orm` documentation for information on how +to use the new ORM. + +Basics +====== + +* ``LogError()`` was removed, it provided no benefit and is rarely/never used. +* The following global functions have been removed: ``config()``, ``cache()``, + ``clearCache()``, ``convertSlashes()``, ``am()``, ``fileExistsInPath()``, + ``sortByKey()``. + +Debugging +========= + +* ``Configure::write('debug', $bool)`` does not support 0/1/2 anymore. A simple boolean + is used instead to switch debug mode on or off. + +Object settings/configuration +============================= + +* Objects used in CakePHP now have a consistent instance-configuration storage/retrieval + system. Code which previously accessed for example: ``$object->settings`` should instead + be updated to use ``$object->config()``. + +Cache +===== + +* ``Memcache`` engine has been removed, use :php:class:`Cake\\Cache\\Cache\\Engine\\Memcached` instead. +* Cache engines are now lazy loaded upon first use. +* :php:meth:`Cake\\Cache\\Cache::engine()` has been added. +* :php:meth:`Cake\\Cache\\Cache::enabled()` has been added. This replaced the + ``Cache.disable`` configure option. +* :php:meth:`Cake\\Cache\\Cache::enable()` has been added. +* :php:meth:`Cake\\Cache\\Cache::disable()` has been added. +* Cache configurations are now immutable. If you need to change configuration + you must first drop the configuration and then re-create it. This prevents + synchronization issues with configuration options. +* ``Cache::set()`` has been removed. It is recommended that you create multiple + cache configurations to replace runtime configuration tweaks previously + possible with ``Cache::set()``. +* All ``CacheEngine`` subclasses now implement a ``config()`` method. +* :php:meth:`Cake\\Cache\\Cache::readMany()`, :php:meth:`Cake\\Cache\\Cache::deleteMany()`, + and :php:meth:`Cake\\Cache\\Cache::writeMany()` were added. + +All :php:class:`Cake\\Cache\\Cache\\CacheEngine` methods now honor/are responsible for handling the +configured key prefix. The :php:meth:`Cake\\Cache\\CacheEngine::write()` no longer permits setting +the duration on write - the duration is taken from the cache engine's runtime config. Calling a +cache method with an empty key will now throw an :php:class:`InvalidArgumentException`, instead +of returning ``false``. + +Core +==== + +App +--- + +- ``App::pluginPath()`` has been removed. Use ``CakePlugin::path()`` instead. +- ``App::build()`` has been removed. +- ``App::location()`` has been removed. +- ``App::paths()`` has been removed. +- ``App::load()`` has been removed. +- ``App::objects()`` has been removed. +- ``App::RESET`` has been removed. +- ``App::APPEND`` has been removed. +- ``App::PREPEND`` has been removed. +- ``App::REGISTER`` has been removed. + +Plugin +------ + +- :php:meth:`Cake\\Core\\Plugin::load()` does not setup an autoloader unless + you set the ``autoload`` option to ``true``. +- When loading plugins you can no longer provide a callable. +- When loading plugins you can no longer provide an array of config files to + load. + +Configure +--------- + +- ``Cake\Configure\PhpReader`` renamed to + :php:class:`Cake\\Core\\Configure\\Engine\PhpConfig` +- ``Cake\Configure\IniReader`` renamed to + :php:class:`Cake\\Core\\Configure\\Engine\IniConfig` +- ``Cake\Configure\ConfigReaderInterface`` renamed to + :php:class:`Cake\\Core\\Configure\\ConfigEngineInterface` +- :php:meth:`Cake\\Core\\Configure::consume()` was added. +- :php:meth:`Cake\\Core\\Configure::load()` now expects the file name without + extension suffix as this can be derived from the engine. E.g. using PhpConfig + use ``app`` to load **app.php**. +- Setting a ``$config`` variable in PHP config file is deprecated. + :php:class:`Cake\\Core\\Configure\\Engine\PhpConfig` now expects the config + file to return an array. +- A new config engine :php:class:`Cake\\Core\\Configure\\Engine\JsonConfig` has + been added. + +Object +------ + +The ``Object`` class has been removed. It formerly contained a grab bag of +methods that were used in various places across the framework. The most useful +of these methods have been extracted into traits. You can use the +:php:trait:`Cake\\Log\\LogTrait` to access the ``log()`` method. The +:php:trait:`Cake\\Routing\\RequestActionTrait` provides ``requestAction()``. + +Console +======= + +The ``cake`` executable has been moved from the **app/Console** directory to the +**bin** directory within the application skeleton. You can now invoke CakePHP's +console with ``bin/cake``. + +TaskCollection Replaced +----------------------- + +This class has been renamed to :php:class:`Cake\\Console\\TaskRegistry`. +See the section on :doc:`/core-libraries/registry-objects` for more information +on the features provided by the new class. You can use the ``cake upgrade +rename_collections`` to assist in upgrading your code. Tasks no longer have +access to callbacks, as there were never any callbacks to use. + +Shell +----- + +- ``Shell::__construct()`` has changed. It now takes an instance of + :php:class:`Cake\\Console\\ConsoleIo`. +- ``Shell::param()`` has been added as convenience access to the params. + +Additionally all shell methods will be transformed to camel case when invoked. +For example, if you had a ``hello_world()`` method inside a shell and invoked it +with ``bin/cake my_shell hello_world``, you will need to rename the method +to ``helloWorld``. There are no changes required in the way you invoke commands. + +ConsoleOptionParser +------------------- + +- ``ConsoleOptionParser::merge()`` has been added to merge parsers. + +ConsoleInputArgument +-------------------- + +- ``ConsoleInputArgument::isEqualTo()`` has been added to compare two arguments. + +Shell / Task +============ + +Shells and Tasks have been moved from ``Console/Command`` and +``Console/Command/Task`` to ``Shell`` and ``Shell/Task``. + +ApiShell Removed +---------------- + +The ApiShell was removed as it didn't provide any benefit over the file source +itself and the online documentation/`API `_. + +SchemaShell Removed +------------------- + +The SchemaShell was removed as it was never a complete database migration implementation +and better tools such as `Phinx `_ have emerged. It has been replaced by +the `CakePHP Migrations Plugin `_ which acts as a wrapper between +CakePHP and `Phinx `_. + +ExtractTask +----------- + +- ``bin/cake i18n extract`` no longer includes untranslated validation + messages. If you want translated validation messages you should wrap those + messages in `__()` calls like any other content. + +BakeShell / TemplateTask +------------------------ + +- Bake is no longer part of the core source and is superseded by + `CakePHP Bake Plugin `_ +- Bake templates have been moved under **src/Template/Bake**. +- The syntax of Bake templates now uses erb-style tags (``<% %>``) to denote + templating logic, allowing php code to be treated as plain text. +- The ``bake view`` command has been renamed ``bake template``. + +Event +===== + +The ``getEventManager()`` method, was removed on all objects that had it. An +``eventManager()`` method is now provided by the ``EventManagerTrait``. The +``EventManagerTrait`` contains the logic of instantiating and keeping +a reference to a local event manager. + +The Event subsystem has had a number of optional features removed. When +dispatching events you can no longer use the following options: + +* ``passParams`` This option is now enabled always implicitly. You + cannot turn it off. +* ``break`` This option has been removed. You must now stop events. +* ``breakOn`` This option has been removed. You must now stop events. + +Log +=== + +* Log configurations are now immutable. If you need to change configuration + you must first drop the configuration and then re-create it. This prevents + synchronization issues with configuration options. +* Log engines are now lazily loaded upon the first write to the logs. +* :php:meth:`Cake\\Log\\Log::engine()` has been added. +* The following methods have been removed from :php:class:`Cake\\Log\\Log` :: + ``defaultLevels()``, ``enabled()``, ``enable()``, ``disable()``. +* You can no longer create custom levels using ``Log::levels()``. +* When configuring loggers you should use ``'levels'`` instead of ``'types'``. +* You can no longer specify custom log levels. You must use the default set of + log levels. You should use logging scopes to create custom log files or + specific handling for different sections of your application. Using + a non-standard log level will now throw an exception. +* :php:trait:`Cake\\Log\\LogTrait` was added. You can use this trait in your + classes to add the ``log()`` method. +* The logging scope passed to :php:meth:`Cake\\Log\\Log::write()` is now + forwarded to the log engines' ``write()`` method in order to provide better + context to the engines. +* Log engines are now required to implement ``Psr\Log\LogInterface`` instead of + Cake's own ``LogInterface``. In general, if you extended :php:class:`Cake\\Log\\Engine\\BaseEngine` + you just need to rename the ``write()`` method to ``log()``. +* :php:meth:`Cake\\Log\\Engine\\FileLog` now writes files in ``ROOT/logs`` instead of ``ROOT/tmp/logs``. + +Routing +======= + +Named Parameters +---------------- + +Named parameters were removed in 3.0. Named parameters were added in 1.2.0 as +a 'pretty' version of query string parameters. While the visual benefit is +arguable, the problems named parameters created are not. + +Named parameters required special handling in CakePHP as well as any PHP or +JavaScript library that needed to interact with them, as named parameters are +not implemented or understood by any library *except* CakePHP. The additional +complexity and code required to support named parameters did not justify their +existence, and they have been removed. In their place you should use standard +query string parameters or passed arguments. By default ``Router`` will treat +any additional parameters to ``Router::url()`` as query string arguments. + +Since many applications will still need to parse incoming URLs containing named +parameters. :php:meth:`Cake\\Routing\\Router::parseNamedParams()` has +been added to allow backwards compatibility with existing URLs. + +RequestActionTrait +------------------ + +- :php:meth:`Cake\\Routing\\RequestActionTrait::requestAction()` has had + some of the extra options changed: + + - ``options[url]`` is now ``options[query]``. + - ``options[data]`` is now ``options[post]``. + - Named parameters are no longer supported. + +Router +------ + +* Named parameters have been removed, see above for more information. +* The ``full_base`` option has been replaced with the ``_full`` option. +* The ``ext`` option has been replaced with the ``_ext`` option. +* ``_scheme``, ``_port``, ``_host``, ``_base``, ``_full``, ``_ext`` options added. +* String URLs are no longer modified by adding the plugin/controller/prefix names. +* The default fallback route handling was removed. If no routes + match a parameter set ``/`` will be returned. +* Route classes are responsible for *all* URL generation including + query string parameters. This makes routes far more powerful and flexible. +* Persistent parameters were removed. They were replaced with + :php:meth:`Cake\\Routing\\Router::urlFilter()` which allows + a more flexible way to mutate URLs being reverse routed. +* ``Router::parseExtensions()`` has been removed. + Use :php:meth:`Cake\\Routing\\Router::extensions()` instead. This method + **must** be called before routes are connected. It won't modify existing + routes. +* ``Router::setExtensions()`` has been removed. + Use :php:meth:`Cake\\Routing\\Router::extensions()` instead. +* ``Router::resourceMap()`` has been removed. +* The ``[method]`` option has been renamed to ``_method``. +* The ability to match arbitrary headers with ``[]`` style parameters has been + removed. If you need to parse/match on arbitrary conditions consider using + custom route classes. +* ``Router::promote()`` has been removed. +* ``Router::parse()`` will now raise an exception when a URL cannot be handled + by any route. +* ``Router::url()`` will now raise an exception when no route matches a set of + parameters. +* Routing scopes have been introduced. Routing scopes allow you to keep your + routes file DRY and give Router hints on how to optimize parsing & reverse + routing URLs. + +Route +----- + +* ``CakeRoute`` was re-named to ``Route``. +* The signature of ``match()`` has changed to ``match($url, $context = [])`` + See :php:meth:`Cake\\Routing\\Route::match()` for information on the new signature. + +Dispatcher Filters Configuration Changed +---------------------------------------- + +Dispatcher filters are no longer added to your application using ``Configure``. +You now append them with :php:class:`Cake\\Routing\\DispatcherFactory`. This +means if your application used ``Dispatcher.filters``, you should now use +:php:meth:`Cake\\Routing\\DispatcherFactory::add()`. + +In addition to configuration changes, dispatcher filters have had some +conventions updated, and features added. See the +:doc:`/development/dispatch-filters` documentation for more information. + +Filter\AssetFilter +------------------ + +* Plugin & theme assets handled by the AssetFilter are no longer read via + ``include`` instead they are treated as plain text files. This fixes a number + of issues with JavaScript libraries like TinyMCE and environments with + short_tags enabled. +* Support for the ``Asset.filter`` configuration and hooks were removed. This + feature should be replaced with a plugin or dispatcher filter. + +Network +======= + +Request +------- + +* ``CakeRequest`` has been renamed to :php:class:`Cake\\Network\\Request`. +* :php:meth:`Cake\\Network\\Request::port()` was added. +* :php:meth:`Cake\\Network\\Request::scheme()` was added. +* :php:meth:`Cake\\Network\\Request::cookie()` was added. +* :php:attr:`Cake\\Network\\Request::$trustProxy` was added. This makes it easier to put + CakePHP applications behind load balancers. +* :php:attr:`Cake\\Network\\Request::$data` is no longer merged with the prefixed data + key, as that prefix has been removed. +* :php:meth:`Cake\\Network\\Request::env()` was added. +* :php:meth:`Cake\\Network\\Request::acceptLanguage()` was changed from static method + to non-static. +* Request detector for "mobile" has been removed from the core. Instead the app + template adds detectors for "mobile" and "tablet" using ``MobileDetect`` lib. +* The method ``onlyAllow()`` has been renamed to ``allowMethod()`` and no longer accepts "var args". + All method names need to be passed as first argument, either as string or array of strings. + +Response +-------- + +* The mapping of mimetype ``text/plain`` to extension ``csv`` has been removed. + As a consequence :php:class:`Cake\\Controller\\Component\\RequestHandlerComponent` + doesn't set extension to ``csv`` if ``Accept`` header contains mimetype ``text/plain`` + which was a common annoyance when receiving a jQuery XHR request. + +Sessions +======== + +The session class is no longer static, instead the session can be accessed +through the request object. See the :doc:`/development/sessions` documentation +for using the session object. + +* :php:class:`Cake\\Network\\Session` and related session classes have been + moved under the ``Cake\Network`` namespace. +* ``SessionHandlerInterface`` has been removed in favor of the one provided by + PHP itself. +* The property ``Session::$requestCountdown`` has been removed. +* The session checkAgent feature has been removed. It caused a number of bugs + when chrome frame, and flash player are involved. +* The conventional sessions database table name is now ``sessions`` instead of + ``cake_sessions``. +* The session cookie timeout is automatically updated in tandem with the timeout + in the session data. +* The path for session cookie now defaults to app's base path instead of "/". + A new configuration variable ``Session.cookiePath`` has been added to + customize the cookie path. +* A new convenience method :php:meth:`Cake\\Network\\Session::consume()` has been added + to allow reading and deleting session data in a single step. +* The default value of :php:meth:`Cake\\Network\\Session::clear()`'s argument ``$renew`` has been changed + from ``true`` to ``false``. + +Network\\Http +============= + +* ``HttpSocket`` is now :php:class:`Cake\\Network\\Http\\Client`. +* Http\Client has been re-written from the ground up. It has a simpler/easier to + use API, support for new authentication systems like OAuth, and file uploads. + It uses PHP's stream APIs so there is no requirement for cURL. See the + :doc:`/core-libraries/httpclient` documentation for more information. + +Network\\Email +============== + +* :php:meth:`Cake\\Network\\Email\\Email::config()` is now used to define + configuration profiles. This replaces the ``EmailConfig`` classes in previous + versions. +* :php:meth:`Cake\\Network\\Email\\Email::profile()` replaces ``config()`` as + the way to modify per instance configuration options. +* :php:meth:`Cake\\Network\\Email\\Email::drop()` has been added to allow the + removal of email configuration. +* :php:meth:`Cake\\Network\\Email\\Email::configTransport()` has been added to allow the + definition of transport configurations. This change removes transport options + from delivery profiles and allows you to re-use transports across email + profiles. +* :php:meth:`Cake\\Network\\Email\\Email::dropTransport()` has been added to allow the + removal of transport configuration. + +Controller +========== + +Controller +---------- + +- The ``$helpers``, ``$components`` properties are now merged + with **all** parent classes not just ``AppController`` and the plugin + AppController. The properties are merged differently now as well. Instead of + all settings in all classes being merged together, the configuration defined + in the child class will be used. This means that if you have some + configuration defined in your AppController, and some configuration defined in + a subclass, only the configuration in the subclass will be used. +- ``Controller::httpCodes()`` has been removed, use + :php:meth:`Cake\\Network\\Response::httpCodes()` instead. +- ``Controller::disableCache()`` has been removed, use + :php:meth:`Cake\\Network\\Response::disableCache()` instead. +- ``Controller::flash()`` has been removed. This method was rarely used in real + applications and served no purpose anymore. +- ``Controller::validate()`` and ``Controller::validationErrors()`` have been + removed. They were left over methods from the 1.x days where the concerns of + models + controllers were far more intertwined. +- ``Controller::loadModel()`` now loads table objects. +- The ``Controller::$scaffold`` property has been removed. Dynamic scaffolding + has been removed from CakePHP core. An improved scaffolding plugin, named CRUD, can be found here: https://github.com/FriendsOfCake/crud +- The ``Controller::$ext`` property has been removed. You now have to extend and + override the ``View::$_ext`` property if you want to use a non-default view file + extension. +- The ``Controller::$methods`` property has been removed. You should now use + ``Controller::isAction()`` to determine whether or not a method name is an + action. This change was made to allow easier customization of what is and is + not counted as an action. +- The ``Controller::$Components`` property has been removed and replaced with + ``_components``. If you need to load components at runtime you should use + ``$this->loadComponent()`` on your controller. +- The signature of :php:meth:`Cake\\Controller\\Controller::redirect()` has been + changed to ``Controller::redirect(string|array $url, int $status = null)``. + The 3rd argument ``$exit`` has been dropped. The method can no longer send + response and exit script, instead it returns a ``Response`` instance with + appropriate headers set. +- The ``base``, ``webroot``, ``here``, ``data``, ``action``, and ``params`` + magic properties have been removed. You should access all of these properties + on ``$this->request`` instead. +- Underscore prefixed controller methods like ``_someMethod()`` are no longer + treated as private methods. Use proper visibility keywords instead. Only + public methods can be used as controller actions. + +Scaffold Removed +---------------- + +The dynamic scaffolding in CakePHP has been removed from CakePHP core. It was +infrequently used, and never intended for production use. An improved +scaffolding plugin, named CRUD, can be found here: +https://github.com/FriendsOfCake/crud + +ComponentCollection Replaced +---------------------------- + +This class has been renamed to :php:class:`Cake\\Controller\\ComponentRegistry`. +See the section on :doc:`/core-libraries/registry-objects` for more information +on the features provided by the new class. You can use the ``cake upgrade +rename_collections`` to assist in upgrading your code. + +Component +--------- + +* The ``_Collection`` property is now ``_registry``. It contains an instance + of :php:class:`Cake\\Controller\\ComponentRegistry` now. +* All components should now use the ``config()`` method to get/set + configuration. +* Default configuration for components should be defined in the + ``$_defaultConfig`` property. This property is automatically merged with any + configuration provided to the constructor. +* Configuration options are no longer set as public properties. +* The ``Component::initialize()`` method is no longer an event listener. + Instead, it is a post-constructor hook like ``Table::initialize()`` and + ``Controller::initialize()``. The new ``Component::beforeFilter()`` method is + bound to the same event that ``Component::initialize()`` used to be. The + initialize method should have the following signature ``initialize(array + $config)``. + +Controller\\Components +====================== + +CookieComponent +--------------- + +- Uses :php:meth:`Cake\\Network\\Request::cookie()` to read cookie data, + this eases testing, and allows for ControllerTestCase to set cookies. +- Cookies encrypted in previous versions of CakePHP using the ``cipher()`` method + are now un-readable because ``Security::cipher()`` has been removed. You will + need to re-encrypt cookies with the ``rijndael()`` or ``aes()`` method before upgrading. +- ``CookieComponent::type()`` has been removed and replaced with configuration + data accessed through ``config()``. +- ``write()`` no longer takes ``encryption`` or ``expires`` parameters. Both of + these are now managed through config data. See + :doc:`/controllers/components/cookie` for more information. +- The path for cookies now defaults to app's base path instead of "/". + +AuthComponent +------------- + +- ``Default`` is now the default password hasher used by authentication classes. + It uses exclusively the bcrypt hashing algorithm. If you want to continue using + SHA1 hashing used in 2.x use ``'passwordHasher' => 'Weak'`` in your authenticator configuration. +- A new ``FallbackPasswordHasher`` was added to help users migrate old passwords + from one algorithm to another. Check AuthComponent's documentation for more + info. +- ``BlowfishAuthenticate`` class has been removed. Just use ``FormAuthenticate`` +- ``BlowfishPasswordHasher`` class has been removed. Use + ``DefaultPasswordHasher`` instead. +- The ``loggedIn()`` method has been removed. Use ``user()`` instead. +- Configuration options are no longer set as public properties. +- The methods ``allow()`` and ``deny()`` no longer accept "var args". All method names need + to be passed as first argument, either as string or array of strings. +- The method ``login()`` has been removed and replaced by ``setUser()`` instead. + To login a user you now have to call ``identify()`` which returns user info upon + successful identification and then use ``setUser()`` to save the info to + session for persistence across requests. + +- ``BaseAuthenticate::_password()`` has been removed. Use a ``PasswordHasher`` + class instead. +- ``BaseAuthenticate::logout()`` has been removed. +- ``AuthComponent`` now triggers two events ``Auth.afterIdentify`` and + ``Auth.logout`` after a user has been identified and before a user is + logged out respectively. You can set callback functions for these events by + returning a mapping array from ``implementedEvents()`` method of your + authenticate class. + +ACL related classes were moved to a separate plugin. Password hashers, Authentication and +Authorization providers where moved to the ``\Cake\Auth`` namespace. You are +required to move your providers and hashers to the ``App\Auth`` namespace as +well. + +RequestHandlerComponent +----------------------- + +- The following methods have been removed from RequestHandler component:: + ``isAjax()``, ``isFlash()``, ``isSSL()``, ``isPut()``, ``isPost()``, ``isGet()``, ``isDelete()``. + Use the :php:meth:`Cake\\Network\\Request::is()` method instead with relevant argument. +- ``RequestHandler::setContent()`` was removed, use :php:meth:`Cake\\Network\\Response::type()` instead. +- ``RequestHandler::getReferer()`` was removed, use :php:meth:`Cake\\Network\\Request::referer()` instead. +- ``RequestHandler::getClientIP()`` was removed, use :php:meth:`Cake\\Network\\Request::clientIp()` instead. +- ``RequestHandler::getAjaxVersion()`` was removed. +- ``RequestHandler::mapType()`` was removed, use :php:meth:`Cake\\Network\\Response::mapType()` instead. +- Configuration options are no longer set as public properties. + +SecurityComponent +----------------- + +- The following methods and their related properties have been removed from Security component: + ``requirePost()``, ``requireGet()``, ``requirePut()``, ``requireDelete()``. + Use the :php:meth:`Cake\\Network\\Request::allowMethod()` instead. +- ``SecurityComponent::$disabledFields()`` has been removed, use + ``SecurityComponent::$unlockedFields()``. +- The CSRF related features in SecurityComponent have been extracted and moved + into a separate CsrfComponent. This allows you to use CSRF protection + without having to use form tampering prevention. +- Configuration options are no longer set as public properties. +- The methods ``requireAuth()`` and ``requireSecure()`` no longer accept "var args". + All method names need to be passed as first argument, either as string or array of strings. + +SessionComponent +---------------- + +- ``SessionComponent::setFlash()`` is deprecated. You should use + :doc:`/controllers/components/flash` instead. + +Error +----- + +Custom ExceptionRenderers are now expected to either return +a :php:class:`Cake\\Network\\Response` object or string when rendering errors. This means +that any methods handling specific exceptions must return a response or string +value. + +Model +===== + +The Model layer in 2.x has been entirely re-written and replaced. You should +review the :doc:`/appendices/orm-migration` for information on how to use the +new ORM. + +- The ``Model`` class has been removed. +- The ``BehaviorCollection`` class has been removed. +- The ``DboSource`` class has been removed. +- The ``Datasource`` class has been removed. +- The various datasource classes have been removed. + +ConnectionManager +----------------- + +- ConnectionManager has been moved to the ``Cake\Datasource`` namespace. +- ConnectionManager has had the following methods removed: + + - ``sourceList`` + - ``getSourceName`` + - ``loadDataSource`` + - ``enumConnectionObjects`` + +- :php:meth:`~Cake\\Database\\ConnectionManager::config()` has been added and is + now the only way to configure connections. +- :php:meth:`~Cake\\Database\\ConnectionManager::get()` has been added. It + replaces ``getDataSource()``. +- :php:meth:`~Cake\\Database\\ConnectionManager::configured()` has been added. It + and ``config()`` replace ``sourceList()`` & ``enumConnectionObjects()`` with + a more standard and consistent API. +- ``ConnectionManager::create()`` has been removed. + It can be replaced by ``config($name, $config)`` and ``get($name)``. + +Behaviors +--------- +- Underscore prefixed behavior methods like ``_someMethod()`` are no longer + treated as private methods. Use proper visibility keywords instead. + +TreeBehavior +------------ + +The TreeBehavior was completely re-written to use the new ORM. Although it works +the same as in 2.x, a few methods were renamed or removed: + +- ``TreeBehavior::children()`` is now a custom finder ``find('children')``. +- ``TreeBehavior::generateTreeList()`` is now a custom finder ``find('treeList')``. +- ``TreeBehavior::getParentNode()`` was removed. +- ``TreeBehavior::getPath()`` is now a custom finder ``find('path')``. +- ``TreeBehavior::reorder()`` was removed. +- ``TreeBehavior::verify()`` was removed. + +TestSuite +========= + +TestCase +-------- + +- ``_normalizePath()`` has been added to allow path comparison tests to run across all + operation systems regarding their DS settings (``\`` in Windows vs ``/`` in UNIX, for example). + +The following assertion methods have been removed as they have long been deprecated and replaced by +their new PHPUnit counterpart: + +- ``assertEqual()`` in favor of ``assertEquals()`` +- ``assertNotEqual()`` in favor of ``assertNotEquals()`` +- ``assertIdentical()`` in favor of ``assertSame()`` +- ``assertNotIdentical()`` in favor of ``assertNotSame()`` +- ``assertPattern()`` in favor of ``assertRegExp()`` +- ``assertNoPattern()`` in favor of ``assertNotRegExp()`` +- ``assertReference()`` if favor of ``assertSame()`` +- ``assertIsA()`` in favor of ``assertInstanceOf()`` + +Note that some methods have switched the argument order, e.g. ``assertEqual($is, $expected)`` should now be +``assertEquals($expected, $is)``. + +The following assertion methods have been deprecated and will be removed in the future: + +- ``assertWithinMargin()`` in favor of ``assertWithinRange()`` +- ``assertTags()`` in favor of ``assertHtml()`` + +Both method replacements also switched the argument order for a consistent assert method API +with ``$expected`` as first argument. + +The following assertion methods have been added: + +- ``assertNotWithinRange()`` as counter part to ``assertWithinRange()`` + +View +==== + +Themes are now Basic Plugins +---------------------------- + +Having themes and plugins as ways to create modular application components has +proven to be limited, and confusing. In CakePHP 3.0, themes no longer reside +**inside** the application. Instead they are standalone plugins. This solves +a few problems with themes: + +- You could not put themes *in* plugins. +- Themes could not provide helpers, or custom view classes. + +Both these issues are solved by converting themes into plugins. + +View Folders Renamed +-------------------- + +The folders containing view files now go under **src/Template** instead of **src/View**. +This was done to separate the view files from files containing php classes (eg. Helpers, View classes). + +The following View folders have been renamed to avoid naming collisions with controller names: + +- ``Layouts`` is now ``Layout`` +- ``Elements`` is now ``Element`` +- ``Errors`` is now ``Error`` +- ``Emails`` is now ``Email`` (same for ``Email`` inside ``Layout``) + +HelperCollection Replaced +------------------------- + +This class has been renamed to :php:class:`Cake\\View\\HelperRegistry`. +See the section on :doc:`/core-libraries/registry-objects` for more information +on the features provided by the new class. You can use the ``cake upgrade +rename_collections`` to assist in upgrading your code. + +View Class +---------- + +- The ``plugin`` key has been removed from ``$options`` argument of :php:meth:`Cake\\View\\View::element()`. + Specify the element name as ``SomePlugin.element_name`` instead. +- ``View::getVar()`` has been removed, use :php:meth:`Cake\\View\\View::get()` instead. +- ``View::$ext`` has been removed and instead a protected property ``View::$_ext`` + has been added. +- ``View::addScript()`` has been removed. Use :ref:`view-blocks` instead. +- The ``base``, ``webroot``, ``here``, ``data``, ``action``, and ``params`` + magic properties have been removed. You should access all of these properties + on ``$this->request`` instead. +- ``View::start()`` no longer appends to an existing block. Instead it will + overwrite the block content when end is called. If you need to combine block + contents you should fetch the block content when calling start a second time, + or use the capturing mode of ``append()``. +- ``View::prepend()`` no longer has a capturing mode. +- ``View::startIfEmpty()`` has been removed. Now that start() always overwrites + startIfEmpty serves no purpose. +- The ``View::$Helpers`` property has been removed and replaced with + ``_helpers``. If you need to load helpers at runtime you should use + ``$this->addHelper()`` in your view files. +- ``View`` will now raise ``Cake\View\Exception\MissingTemplateException`` when + templates are missing instead of ``MissingViewException``. + +ViewBlock +--------- + +- ``ViewBlock::append()`` has been removed, use :php:meth:`Cake\\View\ViewBlock::concat()` instead. However, + ``View::append()`` still exists. + +JsonView +-------- + +- By default JSON data will have HTML entities encoded now. This prevents + possible XSS issues when JSON view content is embedded in HTML files. +- :php:class:`Cake\\View\\JsonView` now supports the ``_jsonOptions`` view + variable. This allows you to configure the bit-mask options used when generating + JSON. + +XmlView +------- + +- :php:class:`Cake\\View\\XmlView` now supports the ``_xmlOptions`` view + variable. This allows you to configure the options used when generating + XML. + +View\\Helper +============ + +- The ``$settings`` property is now called ``$_config`` and should be accessed + through the ``config()`` method. +- Configuration options are no longer set as public properties. +- ``Helper::clean()`` was removed. It was never robust enough + to fully prevent XSS. instead you should escape content with :php:func:`h` or + use a dedicated library like htmlPurifier. +- ``Helper::output()`` was removed. This method was + deprecated in 2.x. +- Methods ``Helper::webroot()``, ``Helper::url()``, ``Helper::assetUrl()``, + ``Helper::assetTimestamp()`` have been moved to new :php:class:`Cake\\View\\Helper\\UrlHelper` + helper. ``Helper::url()`` is now available as :php:meth:`Cake\\View\\Helper\\UrlHelper::build()`. +- Magic accessors to deprecated properties have been removed. The following + properties now need to be accessed from the request object: + + - base + - here + - webroot + - data + - action + - params + +Helper +------ + +Helper has had the following methods removed: + +* ``Helper::setEntity()`` +* ``Helper::entity()`` +* ``Helper::model()`` +* ``Helper::field()`` +* ``Helper::value()`` +* ``Helper::_name()`` +* ``Helper::_initInputField()`` +* ``Helper::_selectedArray()`` + +These methods were part used only by FormHelper, and part of the persistent +field features that have proven to be problematic over time. FormHelper no +longer relies on these methods and the complexity they provide is not necessary +anymore. + +The following methods have been removed: + +* ``Helper::_parseAttributes()`` +* ``Helper::_formatAttribute()`` + +These methods can now be found on the ``StringTemplate`` class that helpers +frequently use. See the ``StringTemplateTrait`` for an easy way to integrate +string templates into your own helpers. + +FormHelper +---------- + +FormHelper has been entirely rewritten for 3.0. It features a few large changes: + +* FormHelper works with the new ORM. But has an extensible system for + integrating with other ORMs or datasources. +* FormHelper features an extensible widget system that allows you to create new + custom input widgets and augment the built-in ones. +* String templates are the foundation of the helper. Instead of munging arrays + together everywhere, most of the HTML FormHelper generates can be customized + in one central place using template sets. + +In addition to these larger changes, some smaller breaking changes have been +made as well. These changes should help streamline the HTML FormHelper generates +and reduce the problems people had in the past: + +- The ``data[`` prefix was removed from all generated inputs. The prefix serves no real purpose anymore. +- The various standalone input methods like ``text()``, ``select()`` and others + no longer generate id attributes. +- The ``inputDefaults`` option has been removed from ``create()``. +- Options ``default`` and ``onsubmit`` of ``create()`` have been removed. Instead + one should use JavaScript event binding or set all required js code for ``onsubmit``. +- ``end()`` can no longer make buttons. You should create buttons with + ``button()`` or ``submit()``. +- ``FormHelper::tagIsInvalid()`` has been removed. Use ``isFieldError()`` + instead. +- ``FormHelper::inputDefaults()`` has been removed. You can use ``templates()`` + to define/augment the templates FormHelper uses. +- The ``wrap`` and ``class`` options have been removed from the ``error()`` + method. +- The ``showParents`` option has been removed from select(). +- The ``div``, ``before``, ``after``, ``between`` and ``errorMessage`` options + have been removed from ``input()``. You can use templates to update the + wrapping HTML. The ``templates`` option allows you to override the loaded + templates for one input. +- The ``separator``, ``between``, and ``legend`` options have been removed from + ``radio()``. You can use templates to change the wrapping HTML now. +- The ``format24Hours`` parameter has been removed from ``hour()``. + It has been replaced with the ``format`` option. +- The ``minYear``, and ``maxYear`` parameters have been removed from ``year()``. + Both of these parameters can now be provided as options. +- The ``dateFormat`` and ``timeFormat`` parameters have been removed from + ``datetime()``. You can use the template to define the order the inputs should + be displayed in. +- The ``submit()`` has had the ``div``, ``before`` and ``after`` options + removed. You can customize the ``submitContainer`` template to modify this + content. +- The ``inputs()`` method no longer accepts ``legend`` and ``fieldset`` in the + ``$fields`` parameter, you must use the ``$options`` parameter. + It now also requires ``$fields`` parameter to be an array. The ``$blacklist`` + parameter has been removed, the functionality has been replaced by specifying + ``'field' => false`` in the ``$fields`` parameter. +- The ``inline`` parameter has been removed from postLink() method. + You should use the ``block`` option instead. Setting ``block => true`` will + emulate the previous behavior. +- The ``timeFormat`` parameter for ``hour()``, ``time()`` and ``dateTime()`` now + defaults to 24, complying with ISO 8601. +- The ``$confirmMessage`` argument of :php:meth:`Cake\\View\\Helper\\FormHelper::postLink()` + has been removed. You should now use key ``confirm`` in ``$options`` to specify + the message. +- Checkbox and radio input types are now rendered *inside* of label elements + by default. This helps increase compatibility with popular CSS libraries like + `Bootstrap `_ and + `Foundation `_. +- Templates tags are now all camelBacked. Pre-3.0 tags ``formstart``, ``formend``, ``hiddenblock`` + and ``inputsubmit`` are now ``formStart``, ``formEnd``, ``hiddenBlock`` and ``inputSubmit``. + Make sure you change them if they are customized in your app. + +It is recommended that you review the :doc:`/views/helpers/form` +documentation for more details on how to use the FormHelper in 3.0. + +HtmlHelper +---------- + +- ``HtmlHelper::useTag()`` has been removed, use ``tag()`` instead. +- ``HtmlHelper::loadConfig()`` has been removed. Customizing the tags can now be + done using ``templates()`` or the ``templates`` setting. +- The second parameter ``$options`` for ``HtmlHelper::css()`` now always requires an array as documented. +- The first parameter ``$data`` for ``HtmlHelper::style()`` now always requires an array as documented. +- The ``inline`` parameter has been removed from meta(), css(), script(), scriptBlock() + methods. You should use the ``block`` option instead. Setting ``block => + true`` will emulate the previous behavior. +- ``HtmlHelper::meta()`` now requires ``$type`` to be a string. Additional options can + further on be passed as ``$options``. +- ``HtmlHelper::nestedList()`` now requires ``$options`` to be an array. The forth argument for the tag type + has been removed and included in the ``$options`` array. +- The ``$confirmMessage`` argument of :php:meth:`Cake\\View\\Helper\\HtmlHelper::link()` + has been removed. You should now use key ``confirm`` in ``$options`` to specify + the message. + +PaginatorHelper +--------------- + +- ``link()`` has been removed. It was no longer used by the helper internally. + It had low usage in user land code, and no longer fit the goals of the helper. +- ``next()`` no longer has 'class', or 'tag' options. It no longer has disabled + arguments. Instead templates are used. +- ``prev()`` no longer has 'class', or 'tag' options. It no longer has disabled + arguments. Instead templates are used. +- ``first()`` no longer has 'after', 'ellipsis', 'separator', 'class', or 'tag' options. +- ``last()`` no longer has 'after', 'ellipsis', 'separator', 'class', or 'tag' options. +- ``numbers()`` no longer has 'separator', 'tag', 'currentTag', 'currentClass', + 'class', 'tag', 'ellipsis' options. These options are now facilitated through + templates. It also requires the ``$options`` parameter to be an array now. +- The ``%page%`` style placeholders have been removed from :php:meth:`Cake\\View\\Helper\\PaginatorHelper::counter()`. + Use ``{{page}}`` style placeholders instead. +- ``url()`` has been renamed to ``generateUrl()`` to avoid method declaration clashes with ``Helper::url()``. + +By default all links and inactive texts are wrapped in ``
  • `` elements. This +helps make CSS easier to write, and improves compatibility with popular CSS +frameworks. + +Instead of the various options in each method, you should use the templates +feature. See the :ref:`paginator-templates` documentation for +information on how to use templates. + +TimeHelper +---------- + +- ``TimeHelper::__set()``, ``TimeHelper::__get()``, and ``TimeHelper::__isset()`` were + removed. These were magic methods for deprecated attributes. +- ``TimeHelper::serverOffset()`` has been removed. It promoted incorrect time math practices. +- ``TimeHelper::niceShort()`` has been removed. + +NumberHelper +------------ + +- :php:meth:`NumberHelper::format()` now requires ``$options`` to be an array. + +SessionHelper +------------- + +- The ``SessionHelper`` has been deprecated. You can use ``$this->request->session()`` directly, + and the flash message functionality has been moved into :doc:`/views/helpers/flash` instead. + +JsHelper +-------- + +- ``JsHelper`` and all associated engines have been removed. It could only + generate a very small subset of JavaScript code for selected library and + hence trying to generate all JavaScript code using just the helper often + became an impediment. It's now recommended to directly use JavaScript library + of your choice. + +CacheHelper Removed +------------------- + +CacheHelper has been removed. The caching functionality it provided was +non-standard, limited and incompatible with non-HTML layouts and data views. +These limitations meant a full rebuild would be necessary. Edge Side Includes +have become a standardized way to implement the functionality CacheHelper used +to provide. However, implementing `Edge Side Includes +`_ in PHP has a number of +limitations and edge cases. Instead of building a sub-par solution, we recommend +that developers needing full response caching use `Varnish +`_ or `Squid `_ instead. + +I18n +==== + +The I18n subsystem was completely rewritten. In general, you can expect the same +behavior as in previous versions, specifically if you are using the ``__()`` +family of functions. + +Internally, the ``I18n`` class uses ``Aura\Intl``, and appropriate methods are +exposed to access the specific features of this library. For this reason most +methods inside ``I18n`` were removed or renamed. + +Due to the use of ``ext/intl``, the L10n class was completely removed. It +provided outdated and incomplete data in comparison to the data available from +the ``Locale`` class in PHP. + +The default application language will no longer be changed automatically by the +browser accepted language nor by having the ``Config.language`` value set in the +browser session. You can, however, use a dispatcher filter to get automatic +language switching from the ``Accept-Language`` header sent by the browser:: + + // In config/bootstrap.php + DispatcherFactory::addFilter('LocaleSelector'); + +There is no built-in replacement for automatically selecting the language by +setting a value in the user session. + +The default formatting function for translated messages is no longer +``sprintf``, but the more advanced and feature rich ``MessageFormatter`` class. +In general you can rewrite placeholders in messages as follows:: + + // Before: + __('Today is a %s day in %s', 'Sunny', 'Spain'); + + // After: + __('Today is a {0} day in {1}', 'Sunny', 'Spain'); + +You can avoid rewriting your messages by using the old ``sprintf`` formatter:: + + I18n::defaultFormatter('sprintf'); + +Additionally, the ``Config.language`` value was removed and it can no longer be +used to control the current language of the application. Instead, you can use +the ``I18n`` class:: + + // Before + Configure::write('Config.language', 'fr_FR'); + + // Now + I18n::setLocale('en_US'); + +- The methods below have been moved: + + - From ``Cake\I18n\Multibyte::utf8()`` to ``Cake\Utility\Text::utf8()`` + - From ``Cake\I18n\Multibyte::ascii()`` to ``Cake\Utility\Text::ascii()`` + - From ``Cake\I18n\Multibyte::checkMultibyte()`` to ``Cake\Utility\Text::isMultibyte()`` + +- Since CakePHP now requires the mbstring extension, the + ``Multibyte`` class has been removed. +- Error messages throughout CakePHP are no longer passed through I18n + functions. This was done to simplify the internals of CakePHP and reduce + overhead. The developer facing messages are rarely, if ever, actually translated - + so the additional overhead reaps very little benefit. + +L10n +==== + +- :php:class:`Cake\\I18n\\L10n` 's constructor now takes a :php:class:`Cake\\Network\\Request` instance as argument. + +Testing +======= + +- The ``TestShell`` has been removed. CakePHP, the application skeleton and + newly baked plugins all use ``phpunit`` to run tests. +- The webrunner (webroot/test.php) has been removed. CLI adoption has greatly + increased since the initial release of 2.x. Additionaly, CLI runners offer + superior integration with IDE's and other automated tooling. + + If you find yourself in need of a way to run tests from a browser you should + checkout `VisualPHPUnit `_. It + offers many additional features over the old webrunner. +- ``ControllerTestCase`` is deprecated and will be removed for CakePHP 3.0.0. + You should use the new :ref:`integration-testing` features instead. +- Fixtures should now be referenced using their plural form:: + + // Instead of + $fixtures = ['app.article']; + + // You should use + $fixtures = ['app.articles']; + +Utility +======= + +Set Class Removed +----------------- + +The Set class has been removed, you should use the Hash class instead now. + +Folder & File +------------- + +The folder and file classes have been renamed: + +- ``Cake\Utility\File`` renamed to :php:class:`Cake\\Filesystem\\File` +- ``Cake\Utility\Folder`` renamed to :php:class:`Cake\\Filesystem\\Folder` + +Inflector +--------- + +- The default value for ``$replacement`` argument of :php:meth:`Cake\\Utility\\Inflector::slug()` + has been changed from underscore (``_``) to dash (``-``). Using dashes to + separate words in URLs is the popular choice and also recommended by Google. + +- Transliterations for :php:meth:`Cake\\Utility\\Inflector::slug()` have changed. + If you use custom transliterations you will need to update your code. Instead + of regular expressions, transliterations use simple string replacement. This + yielded significant performance improvements:: + + // Instead of + Inflector::rules('transliteration', [ + '/ä|æ/' => 'ae', + '/å/' => 'aa' + ]); + + // You should use + Inflector::rules('transliteration', [ + 'ä' => 'ae', + 'æ' => 'ae', + 'å' => 'aa' + ]); + +- Separate set of uninflected and irregular rules for pluralization and + singularization have been removed. Instead we now have a common list for each. + When using :php:meth:`Cake\\Utility\\Inflector::rules()` with type 'singular' + and 'plural' you can no longer use keys like 'uninflected', 'irregular' in + ``$rules`` argument array. + + You can add / overwrite the list of uninflected and irregular rules using + :php:meth:`Cake\\Utility\\Inflector::rules()` by using values 'uninflected' and + 'irregular' for ``$type`` argument. + +Sanitize +-------- + +- ``Sanitize`` class has been removed. + +Security +-------- + +- ``Security::cipher()`` has been removed. It is insecure and promoted bad + cryptographic practices. You should use :php:meth:`Security::encrypt()` + instead. +- The Configure value ``Security.cipherSeed`` is no longer required. With the + removal of ``Security::cipher()`` it serves no use. +- Backwards compatibility in :php:meth:`Cake\\Utility\\Security::rijndael()` for values encrypted prior + to CakePHP 2.3.1 has been removed. You should re-encrypt values using + ``Security::encrypt()`` and a recent version of CakePHP 2.x before migrating. +- The ability to generate a blowfish hash has been removed. You can no longer use type + "blowfish" for ``Security::hash()``. One should just use PHP's `password_hash()` + and `password_verify()` to generate and verify blowfish hashes. The compability + library `ircmaxell/password-compat `_ + which is installed along with CakePHP provides these functions for PHP < 5.5. +- OpenSSL is now used over mcrypt when encrypting/decrypting data. This change + provides better performance and future proofs CakePHP against distros dropping + support for mcrypt. +- ``Security::rijndael()`` is deprecated and only available when using mcrypt. + +.. warning:: + + Data encrypted with Security::encrypt() in previous versions is not + compatible with the openssl implementation. You should :ref:`set the + implementation to mcrypt ` when upgrading. + +Time +---- + +- ``CakeTime`` has been renamed to :php:class:`Cake\\I18n\\Time`. +- ``CakeTime::serverOffset()`` has been removed. It promoted incorrect time math practises. +- ``CakeTime::niceShort()`` has been removed. +- ``CakeTime::convert()`` has been removed. +- ``CakeTime::convertSpecifiers()`` has been removed. +- ``CakeTime::dayAsSql()`` has been removed. +- ``CakeTime::daysAsSql()`` has been removed. +- ``CakeTime::fromString()`` has been removed. +- ``CakeTime::gmt()`` has been removed. +- ``CakeTime::toATOM()`` has been renamed to ``toAtomString``. +- ``CakeTime::toRSS()`` has been renamed to ``toRssString``. +- ``CakeTime::toUnix()`` has been renamed to ``toUnixString``. +- ``CakeTime::wasYesterday()`` has been renamed to ``isYesterday`` to match the rest + of the method naming. +- ``CakeTime::format()`` Does not use ``sprintf`` format strings anymore, you can use + ``i18nFormat`` instead. +- :php:meth:`Time::timeAgoInWords()` now requires ``$options`` to be an array. + +Time is not a collection of static methods anymore, it extends ``DateTime`` to +inherit all its methods and adds location aware formatting functions with the +help of the ``intl`` extension. + +In general, expressions looking like this:: + + CakeTime::aMethod($date); + +Can be migrated by rewriting it to:: + + (new Time($date))->aMethod(); + +Number +------ + +The Number library was rewritten to internally use the ``NumberFormatter`` +class. + +- ``CakeNumber`` has been renamed to :php:class:`Cake\\I18n\\Number`. +- :php:meth:`Number::format()` now requires ``$options`` to be an array. +- :php:meth:`Number::addFormat()` was removed. +- ``Number::fromReadableSize()`` has been moved to :php:meth:`Cake\\Utility\\Text::parseFileSize()`. + +Validation +---------- + +- The range for :php:meth:`Validation::range()` now is inclusive if ``$lower`` and + ``$upper`` are provided. +- ``Validation::ssn()`` has been removed. + +Xml +--- + +- :php:meth:`Xml::build()` now requires ``$options`` to be an array. +- ``Xml::build()`` no longer accepts a URL. If you need to create an XML + document from a URL, use :ref:`Http\\Client `. diff --git a/tl/appendices/3-1-migration-guide.rst b/tl/appendices/3-1-migration-guide.rst new file mode 100644 index 0000000000000000000000000000000000000000..862def98169c81ea75276dd03698132420b58035 --- /dev/null +++ b/tl/appendices/3-1-migration-guide.rst @@ -0,0 +1,229 @@ +3.1 Migration Guide +################### + +CakePHP 3.1 is a fully API compatible upgrade from 3.0. This page outlines +the changes and improvements made in 3.1. + +Routing +======= + +- The default route class has been changed to ``DashedRoute`` in the + ``cakephp/app`` repo. Your current code base is not affected by this, but it is + recommended to use this route class from now on. +- Name prefix options were added to the various route builder methods. See the + :ref:`named-routes` section for more information. + +Console +======= + +- ``Shell::dispatchShell()`` no longer outputs the welcome message from the + dispatched shell. +- The ``breakpoint()`` helper function has been added. This function provides + a snippet of code that can be put into ``eval()`` to trigger an interactive + console. This is very helpful when debugging in test cases, or other CLI + scripts. +- The ``--verbose`` and ``--quiet`` console options now control stdout/stderr + logging output levels. + +Shell Helpers Added +------------------- + +- Console applications can now create helper classes that encapsulate re-usable + blocks of output logic. See the :doc:`/console-and-shells/helpers` section + for more information. + +RoutesShell +----------- + +- RoutesShell has been added and now provides you a simple to use CLI + interface for testing and debugging routes. See the + :doc:`/console-and-shells/routes-shell` section for more information. + +Controller +========== + +- The following Controller properties are now deprecated: + + * layout + * view - replaced with ``template`` + * theme + * autoLayout + * viewPath - replaced with ``templatePath`` + * viewClass - replaced with ``className`` + * layoutPath + + Instead of setting these properties on your controllers, you should set them + on the view using methods with matching names:: + + // In a controller, instead of + $this->layout = 'advanced'; + + // You should use + $this->viewBuilder()->layout('advanced'); + +These methods should be called after you've determined which view class will be +used by a controller/action. + +AuthComponent +------------- + +- New config option ``storage`` has been added. It contains the storage class name that + ``AuthComponent`` uses to store user record. By default ``SessionStorage`` is used. + If using a stateless authenticator you should configure ``AuthComponent`` to + use ``MemoryStorage`` instead. +- New config option ``checkAuthIn`` has been added. It contains the name of the + event for which auth checks should be done. By default ``Controller.startup`` + is used, but you can set it to ``Controller.initialize`` if you want + authentication to be checked before you controller's ``beforeFilter()`` method + is run. +- The options ``scope`` and ``contain`` for authenticator classes have been + deprecated. Instead, use the new ``finder`` option to configure a custom finder + method and modify the query used to find a user there. +- The logic for setting ``Auth.redirect`` session variable, which is used to get + the URL to be redirected to after login, has been changed. It is now set only when + trying to access a protected URL without authentication. So ``Auth::redirectUrl()`` + returns the protected URL after login. Under normal circumstances, when a user + directly accesses the login page, ``Auth::redirectUrl()`` returns the value set + for ``loginRedirect`` config. + +FlashComponent +-------------- + +- ``FlashComponent`` now stacks Flash messages when set with the ``set()`` + or ``__call()`` method. This means that the structure in the Session for + stored Flash messages has changed. + +CsrfComponent +------------- + +- CSRF cookie expiry time can now be set as a ``strtotime()`` compatible value. +- Invalid CSRF tokens will now throw + a ``Cake\Network\Exception\InvalidCsrfTokenException`` instead of the + ``Cake\Network\Exception\ForbiddenException``. + +RequestHandlerComponent +----------------------- + +- ``RequestHandlerComponent`` now switches the layout and template based on + the parsed extension or ``Accept`` header in the ``beforeRender()`` callback + instead of ``startup()``. +- ``addInputType()`` and ``viewClassMap()`` are deprecated. You should use + ``config()`` to modify this configuration data at runtime. +- When ``inputTypeMap`` or ``viewClassMap`` are defined in the component + settings, they will *overwrite* the default values. This change makes it + possible to remove the default configuration. + +Network +======= + +Http\Client +----------- + +- The default mime type used when sending requests has changed. Previously + ``multipart/form-data`` would always be used. In 3.1, ``multipart/form-data`` + is only used when file uploads are present. When there are no file uploads, + ``application/x-www-form-urlencoded`` is used instead. + +ORM +=== + +You can now :ref:`Lazily Eager Load Associations +`. This feature allows you to conditionally +load additional associations into a result set, entity or collection of +entities. + +The ``patchEntity()`` and ``newEntity()`` method now support the ``onlyIds`` +option. This option allows you to restrict hasMany/belongsToMany association +marshalling to only use the ``_ids`` list. This option defaults to ``false``. + +Query +----- + +- ``Query::notMatching()`` was added. +- ``Query::leftJoinWith()`` was added. +- ``Query::innerJoinWith()`` was added. +- ``Query::select()`` now supports ``Table`` and ``Association`` objects as + parameters. These parameter types will select all the columns on the provided + table or association instance's target table. +- ``Query::distinct()`` now accepts a string to distinct on a single column. +- ``Table::loadInto()`` was added. +- ``EXTRACT``, ``DATE_ADD`` and ``DAYOFWEEK`` raw SQL functions have been + abstracted to ``extract()``, ``dateAdd()`` and ``dayOfWeek()``. + +View +==== + +- You can now set ``_serialized`` to ``true`` for ``JsonView`` and ``XmlView`` + to serialize all view variables instead of explicitly specifying them. +- ``View::$viewPath`` is deprecated. You should use ``View::templatePath()`` + instead. +- ``View::$view`` is deprecated. You should use ``View::template()`` + instead. +- ``View::TYPE_VIEW`` is deprecated. You should use ``View::TYPE_TEMPLATE`` + instead. + +Helper +====== + +SessionHelper +------------- + +- The ``SessionHelper`` has been deprecated. You can use + ``$this->request->session()`` directly. + +FlashHelper +----------- + +- ``FlashHelper`` can render multiple messages if multiple messages where + set with the ``FlashComponent``. Each message will be rendered in its own + element. Messages will be rendered in the order they were set. + +FormHelper +---------- + +- New option ``templateVars`` has been added. ``templateVars`` allows you to + pass additional variables to your custom form control templates. + +Email +===== + +- ``Email`` and ``Transport`` classes have been moved under the ``Cake\Mailer`` + namespace. Their former namespaces are still usable as class aliases have + been set for them. +- The ``default`` email profile is now automatically set when an ``Email`` + instance is created. This behavior is similar to what is done in 2.x. + +Mailer +------ + +- The ``Mailer`` class was added. This class helps create reusable emails in an + application. + +I18n +==== + +Time +---- + +- ``Time::fromNow()`` has been added. This method makes it easier to calculate + differences from 'now'. +- ``Time::i18nFormat()`` now supports non-gregorian calendars when formatting + dates. + +Validation +========== + +- ``Validation::geoCoordinate()`` was added. +- ``Validation::latitude()`` was added. +- ``Validation::longitude()`` was added. +- ``Validation::isInteger()`` was added. +- ``Validation::ascii()`` was added. +- ``Validation::utf8()`` was added. + +Testing +======= + +TestFixture +----------- + +``model`` key is now supported to retrieve the table name for importing. diff --git a/tl/appendices/3-2-migration-guide.rst b/tl/appendices/3-2-migration-guide.rst new file mode 100644 index 0000000000000000000000000000000000000000..1ae2fd1cd6fe71d16c2102d6b889b65687c4d428 --- /dev/null +++ b/tl/appendices/3-2-migration-guide.rst @@ -0,0 +1,166 @@ +3.2 Migration Guide +################### + +CakePHP 3.2 is an API compatible upgrade from 3.1. This page outlines the +changes and improvements made in 3.2. + +Minimum PHP 5.5 Required +======================== + +CakePHP 3.2 requires at least PHP 5.5.9. By adopting PHP 5.5 we can provide +better Date and Time libraries and remove dependencies on password compatibility +libraries. + +Deprecations +============ + +As we continue to improve CakePHP, certain features are deprecated as they are +replaced with better solutions. Deprecated features will not be removed until +4.0: + +* ``Shell::error()`` is deprecated because its name does not clearly indicate + that it both outputs a message and stops execution. Use ``Shell::abort()`` + instead. +* ``Cake\Database\Expression\QueryExpression::type()`` is deprecated. Use + ``tieWith()`` instead. +* ``Cake\Database\Type\DateTimeType::$dateTimeClass`` is deprecated. Use + DateTimeType::useMutable() or DateTimeType::useImmutable() instead. +* ``Cake\Database\Type\DateType::$dateTimeClass`` is deprecated. Use + ``DateTimeType::useMutable()`` or ``DateType::useImmutable()`` instead. +* ``Cake\ORM\ResultSet::_calculateTypeMap()`` is now unused and deprecated. +* ``Cake\ORM\ResultSet::_castValues()`` is now unused and deprecated. +* The ``action`` key for ``FormHelper::create()`` has been deprecated. You + should use the ``url`` key directly. + +Disabling Deprecation Warnings +------------------------------ + +Upon upgrading you may encounter several deprecation warnings. These warnings +are emitted by methods, options and functionality that will be removed in +CakePHP 4.x, but will continue to exist throughout the lifetime of 3.x. While we +recommend addressing deprecation issues as they are encountered, that is not +always possible. If you'd like to defer fixing deprecation notices, you can +disable them in your **config/app.php**:: + + 'Error' => [ + 'errorLevel' => E_ALL & ~E_DEPRECATED & ~E_USER_DEPRECATED, + ] + +The above error level will suppress deprecation warnings from CakePHP. + +New Enhancements +================ + +Carbon Replaced with Chronos +---------------------------- + +The Carbon library has been replaced with :doc:`cakephp/chronos `. +This new library is a fork of Carbon with no additional dependencies. It also +offer a calendar date object, and immutable versions of both date and datetime +objects. + +New Date Object +--------------- + +The ``Date`` class allows you to cleanly map ``DATE`` columns into PHP objects. +Date instances will always fix their time to ``00:00:00 UTC``. By default the +ORM creates instances of ``Date`` when mapping ``DATE`` columns now. + +New Immutable Date and Time Objects +----------------------------------- + +The ``FrozenTime``, and ``FrozenDate`` classes were added. These classes offer +the same API as the ``Time`` object has. The frozen classes provide immutable +variants of ``Time`` and ``Date``. By using immutable objects, you can prevent +accidental mutations. Instead of in-place modifications, modifier methods return +*new* instances:: + + use Cake\I18n\FrozenTime; + + $time = new FrozenTime('2016-01-01 12:23:32'); + $newTime = $time->modify('+1 day'); + +In the above code ``$time`` and ``$newTime`` are different objects. The +``$time`` object retains its original value, while ``$newTime`` has the modified +value. See the :ref:`immutable-time` section for more information. As of 3.2, +the ORM can map date/datetime columns into immutable objects. See the +:ref:`immutable-datetime-mapping` section for more information. + +CorsBuilder Added +----------------- + +In order to make setting headers related to Cross Origin Requests (CORS) easier, +a new ``CorsBuilder`` has been added. This class lets you define CORS related +headers with a fluent interface. See :ref:`cors-headers` for more information. + +RedirectRoute raises an exception on redirect +--------------------------------------------- + +``Router::redirect()`` now raises ``Cake\Network\Routing\RedirectException`` +when a redirect condition is reached. This exception is caught by the routing +filter and converted into a response. This replaces calls to +``response->send()`` and allows dispatcher filters to interact with redirect +responses. + +ORM Improvements +---------------- + +* Containing the same association multiple times now works as expected, and the + query builder functions are now stacked. +* Function expressions now correctly cast their results. This means that + expressions like ``$query->func()->current_date()`` will return datetime + instances. +* Field data that fails validation can now be accessed in entities via the + ``invalid()`` method. +* Entity accessor method lookups are now cached and perform better. + +Improved Validator API +---------------------- + +The Validator object has a number of new methods that make building validators +less verbose. For example adding validation rules to a username field can now +look like:: + + $validator->email('username') + ->ascii('username') + ->lengthBetween('username', [4, 8]); + +Console Improvements +-------------------- + +* ``Shell::info()``, ``Shell::warn()`` and ``Shell::success()`` were added. + These helper methods make using commonly used styling simpler. +* ``Cake\Console\Exception\StopException`` was added. +* ``Shell::abort()`` was added to replace ``error()``. + +StopException Added +------------------- + +``Shell::_stop()`` and ``Shell::error()`` no longer call ``exit()``. Instead +they raise ``Cake\Console\Exception\StopException``. If your shells/tasks are +catching ``\Exception`` where these methods would have been called, those catch +blocks will need to be updated so they don't catch the ``StopException``. By not +calling ``exit()`` testing shells should be easier and require fewer mocks. + +Helper initialize() added +------------------------- + +Helpers can now implement an ``initialize(array $config)`` hook method like +other class types. + +Fatal Error Memory Limit Handling +--------------------------------- + +A new configuration option ``Error.extraFatalErrorMemory`` can be set to the +number of megabytes to increase the memory limit by when a fatal error is +encountered. This allows breathing room to complete logging or error handling. + +Migration Steps +=============== + +Updating setToStringFormat() +---------------------------- + +Before CakePHP 3.2 using Time::setToStringFormat() was working on Date Objects +as well. After upgrading you will need to add Date::setToStringFormat() in +addition to see the formatted Date again. diff --git a/tl/appendices/3-3-migration-guide.rst b/tl/appendices/3-3-migration-guide.rst new file mode 100644 index 0000000000000000000000000000000000000000..eef50a0177af102d5cf8aa00a7cc2712b6bef654 --- /dev/null +++ b/tl/appendices/3-3-migration-guide.rst @@ -0,0 +1,186 @@ +3.3 Migration Guide +################### + +CakePHP 3.3 is an API compatible upgrade from 3.2. This page outlines the +changes and improvements made in 3.3. + +Deprecations +============ + +* ``Router::mapResources()`` is deprecated. Use routing scopes and + ``$routes->resources()`` instead. +* ``Router::redirect()`` is deprecated. Use routing scopes and + ``$routes->redirect()`` instead. +* ``Router::parseNamedParams()`` is deprecated. Named parameter backwards + compatibility will be removed in 4.0.0 +* ``Cake\Http\Client\Response`` has had the following methods deprecated because they + overlap with PSR-7 interface methods: + + * ``statusCode()`` use ``getStatusCode()`` instead. + * ``encoding()`` use ``getEncoding()`` instead. + * ``header()`` use ``getHeaderLine()`` instead. + * ``cookie()`` use ``getCookie()`` instead. + * ``version()`` use ``getProtocolVersion()`` instead. +* Dispatcher Filters are now deprecated. Use :doc:`/controllers/middleware` + instead. +* ``RequestActionTrait`` has been deprecated. Refactor your code to use + :doc:`/views/cells` instead. +* ``Cake\\Utility\\Crypto\\Mcrypt`` engine has been deprecated as the ``mcrypt`` + extension is deprecated in PHP 7.1. Use the ``openssl`` and + :php:class:`Cake\\Utility\\Crypto\\Openssl` instead. + +Behavior Changes +================ + +While these changes are API compatible, they represent minor variances in +behavior that may effect your application: + +* The default JSON encode format for Date and DateTime instances is now + ISO-8601. This means that the timezone value contains a ``:``. + For example ``2015-11-06T00:00:00+03:00`` +* ``Controller::referer()`` now consistently omits the application base path + when generating application local URLs. Previously string URLs would have the + base path prepended to them, while array URLs would not. +* The default ``ErrorController`` no longer disables ``Auth`` and ``Security`` + components, as it does not extend ``AppController``. If you are enabling these + components through events, you will need to update your code. +* ``Entity::clean`` now cleans original values, clearing them on save. This + behavior was a bug as the entity's original state should not be retained after + a save, but instead reflect the new state of the entity. + +PSR-7 Middleware Support Added +============================== + +In tandem with the deprecation of Dispatcher Filters, support for PSR-7 +middleware has been added. Middleware is part of the new HTTP stack that is an +opt-in component of CakePHP 3.3.0. By using the new HTTP stack, you can take +advantage of features like: + +* Using middleware from plugins, and libraries outside of CakePHP. +* Leverage the same response object methods in both the responses you get from + ``Http\Client`` and the responses your application generates. +* Be able to augment the response objects emitted by error handling and asset + delivery. + +See the :doc:`/controllers/middleware` chapter and :ref:`adding-http-stack` +sections for more information and how to add the new HTTP stack to an existing +application. + +Http Client is now PSR-7 Compatible +=================================== + +``Cake\Network\Http\Client`` has been moved to ``Cake\Http\Client``. Its request +and response objects now implement the +`PSR-7 interfaces `__. Several methods on +``Cake\Http\Client\Response`` are now deprecated, see above for more +information. + +ORM Improvements +================ + +* Additional support has been added for mapping complex data types. This makes + it easier to work with geo-spatial types, and data that cannot be represented + by strings in SQL queries. See the + :ref:`mapping-custom-datatypes-to-sql-expressions` for more information. +* A new ``JsonType`` was added. This new type lets you use the native JSON types + available in MySQL and Postgres. In other database providers the ``json`` type + will map to ``TEXT`` columns. +* ``Association::unique()`` was added. This method proxies the target table's + ``unique()`` method, but ensures that association conditions are applied. +* ``isUnique`` rules now apply association conditions. +* When entities are converted into JSON, the associated objects are no longer + converted to an array first using ``toArray()``. Instead, the + ``jsonSerialize()`` method will be invoked on all associated entities. This + gives you more flexibility and control on which properties are exposed in JSON + representations of your entities. +* ``Table::newEntity()`` and ``Table::patchEntity()`` will now raise an + exception when an unknown association is in the 'associated' key. +* ``RulesChecker::validCount()`` was added. This new method allows to apply + rules to the number of associated records an entity has. +* The ``allowNullableNulls`` option was added to the ``existsIn`` rule. This + option allows rules to pass when some columns are null. +* Saving translated records is now simpler. See the + :ref:`saving-multiple-translations` for more information. + +Multiple Pagination Support Added +================================= + +You can now paginate multiple queries in a single controller action/view +template. See the :ref:`paginating-multiple-queries` section for more +details. + +Cache Shell Added +================= + +To help you better manage cached data from the CLI environment, a shell command +has been added that exposes methods for clearing cached data:: + + // Clear one cache config + bin/cake cache clear + + // Clear all cache configs + bin/cake cache clear_all + +FormHelper +========== + +* FormHelper will now automatically set the default value of fields to the + default value defined in your database columns. You can disable this behavior + by setting ``schemaDefault`` option to false. + +Validation +========== + +* ``Validator::requirePresence()``, ``Validator::allowEmpty()`` and + ``Validator::notEmpty()`` now accept a list of fields. This allows you + to more concisely define the fields that are required. + +StringTemplate +============== + +``StringTemplate::format()`` now throws an exception instead of returning +``null`` when requested template is not found. + +Other Enhancements +================== + +* ``Collection::transpose()`` was added. This method allows you to tranpose the + rows and columns of a matrix with equal length rows. +* The default ``ErrorController`` now loads ``RequestHandlerComponent`` to + enable ``Accept`` header based content-type negotiation for error pages. + +Routing +------- + +* ``Router::parse()``, ``RouteCollection::parse()`` and ``Route::parse()`` had + a ``$method`` argument added. It defaults to 'GET'. This new parameter reduces + reliance on global state, and necessary for the PSR-7 work integration to be + done. +* When building resource routes, you can now define a prefix. This is useful + when defining nested resources as you can create specialized controllers for + nested resources. +* Dispatcher Filters are now deprecated. Use :doc:`/controllers/middleware` + instead. + +Console +------- + +* Shell tasks that are invoked directly from the CLI no longer have their + ``_welcome`` method invoked. They will also have the ``requested`` parameter + set now. +* ``Shell::err()`` will now apply the 'error' style to text. The default + styling is red text. + +Request +------- + +* ``Request::is()`` and ``Request::addDetector()`` now supports additional + arguments in detectors. This allows detector callables to operate on + additional parameters. + +Debugging Functions +------------------- + +* The ``pr()``, ``debug()``, and ``pj()`` functions now return the value being + dumped. This makes them easier to use when values are being returned. +* :php:func:`dd()` has been added to completely halt execution. diff --git a/tl/appendices/3-4-migration-guide.rst b/tl/appendices/3-4-migration-guide.rst new file mode 100644 index 0000000000000000000000000000000000000000..7d389b4fadb8753980536092218524a242107bb6 --- /dev/null +++ b/tl/appendices/3-4-migration-guide.rst @@ -0,0 +1,471 @@ +3.4 Migration Guide +################### + +CakePHP 3.4 is an API compatible upgrade from 3.3. This page outlines the +changes and improvements made in 3.4. + +Minimum PHP 5.6 Required +======================== +CakePHP 3.4 requires at least PHP 5.6.0 as PHP 5.5 is no longer supported and +won't receive any security fixes anymore. + +Deprecations +============ + +The following is a list of deprecated methods, properties and behaviors. These +features will continue to function until 4.0.0 after which they will be removed. + +Request & Response Deprecations +------------------------------- + +The bulk of deprecations for 3.4 are in the ``Request`` and ``Response`` +objects. The existing methods that modify objects in-place are now deprecated, +and superseded by methods that follow the immutable object patterns described in +the PSR-7 standard. + +Several properties on ``Cake\Network\Request`` have been deprecated: + +* ``Request::$params`` is deprecated. Use ``Request::getAttribute('params')`` instead. +* ``Request::$data`` is deprecated. Use ``Request::getData()`` instead. +* ``Request::$query`` is deprecated. Use ``Request::getQueryParams()`` instead. +* ``Request::$cookies`` is deprecated. Use ``Request::getCookie()`` instead. +* ``Request::$base`` is deprecated. Use ``Request::getAttribute('base')`` instead. +* ``Request::$webroot`` is deprecated. Use ``Request::getAttribute('webroot')`` instead. +* ``Request::$here`` is deprecated. Use ``Request::getRequestTarget()`` instead. +* ``Request::$_session`` was renamed to ``Request::$session``. + +A number of methods on ``Cake\Network\Request`` have been deprecated: + +* ``__get()`` & ``__isset()`` methods are deprecated. Use ``getParam()`` instead. +* ``method()`` is deprecated. Use ``getMethod()`` instead. +* ``setInput()`` is deprecated. Use ``withBody()`` instead. +* The ``ArrayAccess`` methods have all been deprecated. +* ``Request::param()`` is deprecated. Use ``Request::getParam()`` instead. +* ``Request::data()`` is deprecated. Use ``Request::getData()`` instead. +* ``Request::query()`` is deprecated. Use ``Request::getQuery()`` instead. +* ``Request::cookie()`` is deprecated. Use ``Request::getCookie()`` instead. + +Several methods on ``Cake\Network\Response`` have been deprecated because they +either overlap the PSR-7 methods, or are made obsolete by the PSR-7 stack: + +* ``Response::header()`` is deprecated. Use ``getHeaderLine()``, ``hasHeader()`` or + ``Response::getHeader()`` instead. +* ``Response::body()`` is deprecated. Use ``Response::withBody()`` instead. +* ``Response::statusCode()`` is deprecated. Use ``Response::getStatusCode()`` instead. +* ``Response::httpCodes()`` This method should no longer be used. CakePHP now supports all + standards recommended status codes. +* ``Response::protocol()`` is deprecated. Use ``Response::getProtocolVersion()`` instead. +* ``send()``, ``sendHeaders()``, ``_sendHeader()``, ``_sendContent()``, + ``_setCookies()``, ``_setContentType()``, and ``stop()`` are deprecated and + made obsolete by the PSR-7 HTTP stack. + +With responses heading towards immutable object patterns as recommended by the +PSR-7 standards, a number of 'helper' methods in ``Response`` have been +deprecated and immutable variants are now recommended: + +* ``Response::location()`` would become ``Response::withLocation()`` +* ``Response::disableCache()`` would become ``Response::withDisabledCache()`` +* ``Response::type()`` would become ``Response::withType()`` +* ``Response::charset()`` would become ``Response::withCharset()`` +* ``Response::cache()`` would become ``Response::withCache()`` +* ``Response::modified()`` would become ``Response::withModified()`` +* ``Response::expires()`` would become ``Response::withExpires()`` +* ``Response::sharable()`` would become ``Response::withSharable()`` +* ``Response::maxAge()`` would become ``Response::withMaxAge()`` +* ``Response::vary()`` would become ``Response::withVary()`` +* ``Response::etag()`` would become ``Response::withEtag()`` +* ``Response::compress()`` would become ``Response::withCompression()`` +* ``Response::length()`` would become ``Response::withLength()`` +* ``Response::mustRevalidate()`` would become ``Response::withMustRevalidate()`` +* ``Response::notModified()`` would become ``Response::withNotModified()`` +* ``Response::cookie()`` would become ``Response::withCookie()`` +* ``Response::file()`` would become ``Response::withFile()`` +* ``Response::download()`` would become ``Response::withDownload()`` + +Please see the :ref:`adopting-immutable-responses` section for more information +before updating your code as using responses through the immutable methods will +require additional changes. + +Other Deprecations +------------------ + +* The public properties on ``Cake\Event\Event`` are deprecated, new methods have + been added to read/write the relevant properties. +* ``Event::name()`` is deprecated. Use ``Event::getName()`` instead. +* ``Event::subject()`` is deprecated. Use ``Event::getSubject()`` instead. +* ``Event::result()`` is deprecated. Use ``Event::getResult()`` instead. +* ``Event::data()`` is deprecated. Use ``Event::getData()`` instead. +* The ``Auth.redirect`` session variable is no longer used. Instead a query + string parameter is used to store the redirect URL. This has the additional + effect of removing the ability to store a redirect URL in the session outside + of login scenarios. +* ``AuthComponent`` no longer stores redirect URLs when the unauthorized URL is + not a ``GET`` action. +* The ``ajaxLogin`` option for ``AuthComponent`` is deprecated. You should use the + ``403`` status code to trigger the correct behavior in clientside code now. +* The ``beforeRedirect`` method of ``RequestHandlerComponent`` is now + deprecated. +* The ``306`` status code in ``Cake\Network\Response`` is now deprecated and has + its status phrase changed to 'Unused' as this status code is non-standard. +* ``Cake\Database\Schema\Table`` has been renamed to + ``Cake\Database\Schema\TableSchema``. The previous name was confusing to a number + of users. +* The ``fieldList`` option for ``Cake\ORM\Table::newEntity()`` and + ``patchEntity()`` has been renamed to ``fields`` to be more consistent with + other parts of the ORM. +* ``Router::parse()`` is deprecated. ``Router::parseRequest()`` should be used + instead as it accepts a request and gives more control/flexibility in handling + incoming requests. +* ``Route::parse()`` is deprecated. ``Route::parseRequest()`` should be used + instead as it accepts a request and gives more control/flexibility in handling + incoming requests. +* ``FormHelper::input()`` is deprecated. Use ``FormHelper::control()`` instead. +* ``FormHelper::inputs()`` is deprecated. Use ``FormHelper::controls()`` instead. +* ``FormHelper::allInputs()`` is deprecated. Use ``FormHelper::allControls()`` instead. +* ``Mailer::layout()`` is deprecated. Use ``Mailer::setLayout()`` provided by + ``Mailer::__call()`` instead. + +Deprecated Combined Get/Set Methods +----------------------------------- + +In the past CakePHP has leveraged 'modal' methods that provide both +a get/set mode. These methods complicate IDE autocompletion and our ability +to add stricter return types in the future. For these reasons, combined get/set +methods are being split into separate get and set methods. + +The following is a list of methods that are deprecated and replaced with +``getX()`` and ``setX()`` methods: + +``Cake\Core\InstanceConfigTrait`` + * ``config()`` +``Cake\Core\StaticConfigTrait`` + * ``config()`` + * ``dsnClassMap()`` +``Cake\Console\ConsoleOptionParser`` + * ``command()`` + * ``description()`` + * ``epilog()`` +``Cake\Database\Connection`` + * ``driver()`` + * ``schemaCollection()`` + * ``useSavePoints()`` (now ``enableSavePoints()``/``isSavePointsEnabled()``) +``Cake\Database\Driver`` + * ``autoQuoting`` (now ``enableAutoQuoting()``/``isAutoQuotingEnabled()``) +``Cake\Database\Expression\FunctionExpression`` + * ``name()`` +``Cake\Database\Expression\QueryExpression`` + * ``tieWith()`` (now ``setConjunction()``/``getConjunction()``) +``Cake\Database\Expression\ValuesExpression`` + * ``columns()`` + * ``values()`` + * ``query()`` +``Cake\Database\Query`` + * ``connection()`` + * ``selectTypeMap()`` + * ``bufferResults()`` (now ``enableBufferedResults()``/``isBufferedResultsEnabled()``) +``Cake\Database\Schema\CachedCollection`` + * ``cacheMetadata()`` +``Cake\Database\Schema\TableSchema`` + * ``options()`` + * ``temporary()`` (now ``setTemporary()``/``isTemporary()``) +``Cake\Database\TypeMap`` + * ``defaults()`` + * ``types()`` +``Cake\Database\TypeMapTrait`` + * ``typeMap()`` + * ``defaultTypes()`` +``Cake\ORM\Association`` + * ``name()`` + * ``cascadeCallbacks()`` + * ``source()`` + * ``target()`` + * ``conditions()`` + * ``bindingKey()`` + * ``foreignKey()`` + * ``dependent()`` + * ``joinType()`` + * ``property()`` + * ``strategy()`` + * ``finder()`` +``Cake\ORM\Association\BelongsToMany`` + * ``targetForeignKey()`` + * ``saveStrategy()`` + * ``conditions()`` +``Cake\ORM\Association\HasMany`` + * ``saveStrategy()`` + * ``foreignKey()`` + * ``sort()`` +``Cake\ORM\Association\HasOne`` + * ``foreignKey()`` +``Cake\ORM\EagerLoadable`` + * ``config()`` + * setter part of ``canBeJoined()`` (now ``setCanBeJoined()``) +``Cake\ORM\EagerLoader`` + * ``matching()`` (``getMatching()`` will have to be called after ``setMatching()`` + to keep the old behavior) + * ``autoFields()`` (now ``enableAutoFields()``/``isAutoFieldsEnabled()``) +``Cake\ORM\Locator\TableLocator`` + * ``config()`` +``Cake\ORM\Query`` + * ``eagerLoader()`` + * ``hydrate()`` (now ``enableHydration()``/``isHydrationEnabled()``) + * ``autoFields()`` (now ``enableAutoFields()``/``isAutoFieldsEnabled()``) +``Cake\ORM\Table`` + * ``table()`` + * ``alias()`` + * ``registryAlias()`` + * ``connection()`` + * ``schema()`` + * ``primaryKey()`` + * ``displayField()`` + * ``entityClass()`` +``Cake\Mailer\Email`` + * ``from()`` + * ``sender()`` + * ``replyTo()`` + * ``readReceipt()`` + * ``returnPath()`` + * ``to()`` + * ``cc()`` + * ``bcc()`` + * ``charset()`` + * ``headerCharset()`` + * ``emailPattern()`` + * ``subject()`` + * ``template()`` (now ``setTemplate()``/``getTemplate()`` and ``setLayout()``/``getLayout()``) + * ``viewRender()`` (now ``setViewRenderer()``/``getViewRenderer()``) + * ``viewVars()`` + * ``theme()`` + * ``helpers()`` + * ``emailFormat()`` + * ``transport()`` + * ``messageId()`` + * ``domain()`` + * ``attachments()`` + * ``configTransport()`` + * ``profile()`` +``Cake\Validation\Validator`` + * ``provider()`` +``Cake\View\StringTemplateTrait`` + * ``templates()`` +``Cake\View\ViewBuilder`` + * ``templatePath()`` + * ``layoutPath()`` + * ``plugin()`` + * ``helpers()`` + * ``theme()`` + * ``template()`` + * ``layout()`` + * ``options()`` + * ``name()`` + * ``className()`` + * ``autoLayout()`` (now ``enableAutoLayout()``/``isAutoLayoutEnabled()``) + +.. _adopting-immutable-responses: + +Adopting Immutable Responses +============================ + +Before you migrate your code to use the new response methods you should be aware +of the conceptual differences the new methods have. The immutable methods are +generally indicated using a ``with`` prefix. For example, ``withLocation()``. +Because these methods operate in an immutable context, they return *new* +instances which you need to assign to variables or properties. If you had +controller code that looked like:: + + $response = $this->response; + $response->location('/login') + $response->header('X-something', 'a value'); + +If you were to simply find & replace method names your code would break. Instead +you must now use code that looks like:: + + $this->response = $this->response + ->withLocation('/login') + ->withHeader('X-something', 'a value'); + +There are a few key differences: + +#. The result of your changes is re-assigned to ``$this->response``. This is + critical to preserving the intent of the above code. +#. The setter methods can all be chained together. This allows you to skip + storing all the intermediate objects. + +Component Migration Tips +------------------------ + +In previous versions of CakePHP, Components often held onto references to both +the request and response, in order to make changes later. Before you adopt the +immutable methods you should use the response attached to the Controller:: + + // In a component method (not a callback) + $this->response->header('X-Rate-Limit', $this->remaining); + + // Should become + $controller = $this->getController(); + $controller->response = $controller->response->withHeader('X-Rate-Limit', $this->remaining); + +In component callbacks you can use the event object to access the +response/controller:: + + public function beforeRender($event) + { + $controller = $event->getSubject(); + $controller->response = $controller->response->withHeader('X-Teapot', 1); + } + +.. tip:: + Instead of holding onto references of Responses, always get the current + response from the controller, and re-assign the response property when you + are done. + +Behavior Changes +================ + +While these changes are API compatible, they represent minor variances in +behavior that may affect your application: + +* ``ORM\Query`` results will not typecast aliased columns based on the original + column's type. For example if you alias ``created`` to ``created_time`` you + will now get a ``Time`` object back instead of a string. +* Internal ORM traits used to build Association classes have been removed and + replaced with new internal APIs. This shouldn't impact your applications, but + may if you have created custom association types. +* ``AuthComponent`` now uses a query string to store the redirect URL when an + unauthenticated user is redirected to the login page. Previously, this redirect + was stored in the session. Using the query string allows for better + multi-browser experience. +* Database Schema reflection now treats unknown column types as ``string`` and + not ``text``. A visible impact of this is that ``FormHelper`` will generate + text inputs instead of textarea elements for unknown column types. +* ``AuthComponent`` no longer stores the flash messages it creates under the + 'auth' key. They are now rendered with the 'error' template under the + 'default' flash message key. This simplifies using ``AuthComponent``. +* ``Mailer\Email`` will now autodetect attachment content types using + ``mime_content_type`` if a content-type is not provided. Previously + attachments would have defaulted to 'application/octet-stream'. +* CakePHP now uses the ``...`` operator in place of ``call_user_func_array()``. + If you are passing associative arrays, you + should update your code to pass a numerically indexed array using + ``array_values()`` for the following methods: + + * ``Cake\Mailer\Mailer::send()`` + * ``Cake\Controller\Controller::setAction()`` + * ``Cake\Http\ServerRequest::is()`` + +Visibility Changes +================== + +* ``MailerAwareTrait::getMailer()`` will now become protected. +* ``CellTrait::cell()`` will now become protected. + +If the above traits are used in controllers, their public methods could be +accessed by default routing as actions. These changes help protect your +controllers. If you need the methods to remain public you will need to update +your ``use`` statement to look like:: + + use CellTrait { + cell as public; + } + use MailerAwareTrait { + getMailer as public; + } + +Collection +========== + +* ``CollectionInterface::chunkWithKeys()`` was added. User land implementations + of the ``CollectionInterface`` will need to implement this method now. +* ``Collection::chunkWithKeys()`` was added. + +Error +===== + +* ``Debugger::setOutputMask()`` and ``Debugger::outputMask()`` were added. These + methods allow you to configure properties/array keys that should be masked + from output generated by Debugger (for instance, when calling ``debug()``). + +Event +===== + +* ``Event::getName()`` was added. +* ``Event::getSubject()`` was added. +* ``Event::getData()`` was added. +* ``Event::setData()`` was added. +* ``Event::getResult()`` was added. +* ``Event::setResult()`` was added. + +I18n +==== + +* You can now customize the behavior of the fallback message loader. See + :ref:`creating-generic-translators` for more information. + +Routing +======= + +* ``RouteBuilder::prefix()`` now accepts an array of defaults to add to each + connected route. +* Routes can now match only specific hosts through the ``_host`` option. + +Email +===== + +* ``Email::setPriority()``/``Email::getPriority()`` have been added. + +HtmlHelper +========== + +* ``HtmlHelper::scriptBlock()`` no longer wraps the JavaScript code in ``` on a per-engine basis. +* ``Cake\Database\Type\DateTimeType`` will now marshal ISO-8859-1 formatted + datetime strings (e.g. 2017-07-09T12:33:00+00:02) in addition to the + previously accepted format. If you have a subclass of DateTimeType you may + need to update your code. + +New Features +============ + +Scoped Middleware +----------------- + +Middleware can now be conditionally applied to routes in specific URL +scopes. This allows you to build specific stacks of middleware for different +parts of your application without having to write URL checking code in your +middleware. See the :ref:`connecting-scoped-middleware` section for more +information. + +New Console Runner +------------------ + +3.5.0 adds ``Cake\Console\CommandRunner``. This class alongside +``Cake\Console\CommandCollection`` integrate the CLI environment with the new +``Application`` class. Application classes can now implement a ``console()`` +hook that allows them to have full control over which CLI commands are exposed, +how they are named and how the shells get their dependencies. Adopting this new +class requires replacing the contents of your ``bin/cake.php`` file with the +`following file `_. + +Cache Engine Fallbacks +---------------------- + +Cache engines can now be configured with a ``fallback`` key that defines a +cache configuration to fall back to if the engine is misconfigured (or +unavailable). See :ref:`cache-configuration-fallback` for more information on +configuring fallbacks. + +dotenv Support added to Application Skeleton +-------------------------------------------- + +The application skeleton now features a 'dotenv' integration making it easier to +use environment variables to configure your application. See the +:ref:`environment-variables` section for more information. + +Console Integration Testing +--------------------------- + +The ``Cake\TestSuite\ConsoleIntegrationTestCase`` class was added to make +integration testing console applications easier. For more information, visit +the :ref:`console-integration-testing` section. This test class is fully +compatible with the current ``Cake\Console\ShellDispatcher`` as well as the new +``Cake\Console\CommandRunner``. + +Collection +---------- + +* ``Cake\Collection\Collection::avg()`` was added. +* ``Cake\Collection\Collection::median()`` was added. + +Core +---- + +* ``Cake\Core\Configure::read()`` now supports default values if the desired key + does not exist. +* ``Cake\Core\ObjectRegistry`` now implements the ``Countable`` and + ``IteratorAggregate`` interfaces. + +Console +------- + +* ``Cake\Console\ConsoleOptionParser::setHelpAlias()`` was added. This method + allows you to set the command name used when generating help output. Defaults + to ``cake``. +* ``Cake\Console\CommandRunnner`` was added replacing + ``Cake\Console\ShellDispatcher``. +* ``Cake\Console\CommandCollection`` was added to provide an interface for + applications to define the command line tools they offer. + +Database +-------- + +* SQLite driver had the ``mask`` option added. This option lets you set the + file permissions on the SQLite database file when it is created. + +Datasource +---------- + +* ``Cake\Datasource\SchemaInterface`` was added. +* New abstract types were added for ``smallinteger`` and ``tinyinteger``. + Existing ``SMALLINT`` and ``TINYINT`` columns will now be reflected as these + new abstract types. ``TINYINT(1)`` columns will continue to be treated as + boolean columns in MySQL. +* ``Cake\Datasource\PaginatorInterface`` was added. The ``PaginatorComponent`` + now uses this interface to interact with paginators. This allows other + ORM-like implementations to be paginated by the component. +* ``Cake\Datasource\Paginator`` was added to paginate ORM/Database Query + instances. + +Event +----- + +* ``Cake\Event\EventManager::on()`` and ``off()`` methods are now chainable + making it simpler to set multiple events at once. + +Http +---- + +* New ``Cookie`` & ``CookieCollection`` classes have been added. These classes allow you + to work with cookies in an object-orientated way, and are available on + ``Cake\Http\ServerRequest``, ``Cake\Http\Response``, and + ``Cake\Http\Client\Response``. See the :ref:`request-cookies` and + :ref:`response-cookies` for more information. +* New middleware has been added to make applying security headers easier. See + :ref:`security-header-middleware` for more information. +* New middleware has been added to transparently encrypt cookie data. See + :ref:`encrypted-cookie-middleware` for more information. +* New middleware has been added to make protecting against CSRF easier. See + :ref:`csrf-middleware` for more information. +* ``Cake\Http\Client::addCookie()`` was added to make it easy to add cookies to + a client instance. + +InstanceConfigTrait +------------------- + +* ``InstanceConfigTrait::getConfig()`` now takes a 2nd parameter ``$default``. + If no value is available for the specified ``$key``, the ``$default`` value + will be returned. + +ORM +--- + +* ``Cake\ORM\Query::contain()`` now allows you to call it without the wrapping + array when containing a single association. ``contain('Comments', function () + { ... });`` will now work. This makes ``contain()`` consistent with other + eagerloading related methods like ``leftJoinWith()`` and ``matching()``. + +Routing +------- + +* ``Cake\Routing\Router::reverseToArray()`` was added. This method allow you to + convert a request object into an array that can be used to generate URL + strings. +* ``Cake\Routing\RouteBuilder::resources()`` had the ``path`` option + added. This option lets you make the resource path and controller name not + match. +* ``Cake\Routing\RouteBuilder`` now has methods to create routes for + specific HTTP methods. e.g ``get()`` and ``post()``. +* ``Cake\Routing\RouteBuilder::loadPlugin()`` was added. +* ``Cake\Routing\Route`` now has fluent methods for defining options. + +TestSuite +--------- + +* ``TestCase::loadFixtures()`` will now load all fixtures when no arguments are + provided. +* ``IntegrationTestCase::head()`` was added. +* ``IntegrationTestCase::options()`` was added. +* ``IntegrationTestCase::disableErrorHandlerMiddleware()`` was added to make + debugging errors easier in integration tests. + +Validation +---------- + +* ``Cake\Validation\Validator::scalar()`` was added to ensure that fields do not + get non-scalar data. +* ``Cake\Validation\Validator::regex()`` was added for a more convenient way + to validate data against a regex pattern. +* ``Cake\Validation\Validator::addDefaultProvider()`` was added. This method + lets you inject validation providers into all the validators created in your + application. +* ``Cake\Validation\ValidatorAwareInterface`` was added to define the methods + implemented by ``Cake\Validation\ValidatorAwareTrait``. + +View +---- + +* ``Cake\View\Helper\PaginatorHelper::limitControl()`` was added. This method + lets you create a form with a select box for updating the limit value on + a paginated result set. diff --git a/tl/appendices/3-x-migration-guide.rst b/tl/appendices/3-x-migration-guide.rst new file mode 100644 index 0000000000000000000000000000000000000000..7338a487a52944452057547920ae3c78bbcbf4ab --- /dev/null +++ b/tl/appendices/3-x-migration-guide.rst @@ -0,0 +1,59 @@ +3.x Migration Guide +################### + +.. toctree:: + :hidden: + +Migration guides contain information regarding the new features introduced in +each version and the migration path between 2.x and 3.x. If you are currently +using 1.x you should first upgrade to 2.x. See the 2.x documentation for the +relevant upgrade guides. + +3.5 Migration Guide +=================== + +.. toctree:: + :maxdepth: 1 + + 3-5-migration-guide + +3.4 Migration Guide +=================== + +.. toctree:: + :maxdepth: 1 + + 3-4-migration-guide + +3.3 Migration Guide +=================== + +.. toctree:: + :maxdepth: 1 + + 3-3-migration-guide + +3.2 Migration Guide +=================== + +.. toctree:: + :maxdepth: 1 + + 3-2-migration-guide + +3.1 Migration Guide +=================== + +.. toctree:: + :maxdepth: 1 + + 3-1-migration-guide + +3.0 Migration Guide +=================== + +.. toctree:: + :maxdepth: 1 + + 3-0-migration-guide + orm-migration diff --git a/tl/appendices/cakephp-development-process.rst b/tl/appendices/cakephp-development-process.rst new file mode 100644 index 0000000000000000000000000000000000000000..a9e1297c8b10d2fd880e651945c87db3db5b14e7 --- /dev/null +++ b/tl/appendices/cakephp-development-process.rst @@ -0,0 +1,51 @@ +CakePHP Development Process +########################### + +Here we attempt to explain the process we use when developing the +CakePHP framework. We rely heavily on community interaction through +tickets and IRC chat. IRC is the best place to find members of the +`development team `_ and discuss +ideas, the latest code, and make general comments. If something more +formal needs to be proposed or there is a problem with a release, the +ticket system is the best place to share your thoughts. + +We currently maintain 4 versions of CakePHP. + +- **tagged release** : Tagged releases intended for production where stability + is more important than features. Issues filed against these releases + will be fixed in the related branch, and be part of the next release. +- **mainline branch** : These branches are where all bugfixes are merged into. + Stable releases are tagged from these branches. ``master`` is the mainline + branch for the current release series. ``2.x`` is the maintenance branch for + the 2.x release series. If you are using a stable release and need fixes that + haven't made their way into a tagged release check here. +- **development branches** : Development branches contain leading edge fixes and + features. They are named after the major version they are for. E.g *3.next*. + Once development branches have reached a stable release point they are merged + into the mainline branch. +- **feature branches** : Feature branches contain unfinished or possibly + unstable features and are recommended only for power users interested in the + most advanced feature set and willing to contribute back to the community. + Feature branches are named with the following convention *version-feature*. An + example would be *3.3-router* Which would contain new features for the Router + for 3.3. + +Hopefully this will help you understand what version is right for you. +Once you pick your version you may feel compelled to contribute a bug report or +make general comments on the code. + +- If you are using a stable version or maintenance branch, please submit tickets + or discuss with us on IRC. +- If you are using the development branch or feature branch, the first place to + go is IRC. If you have a comment and cannot reach us in IRC after a day or + two, please submit a ticket. + +If you find an issue, the best answer is to write a test. The best advice we can +offer in writing tests is to look at the ones included in the core. + +As always, if you have any questions or comments, visit us at #cakephp on +irc.freenode.net. + +.. meta:: + :title lang=en: CakePHP Development Process + :keywords lang=en: maintenance branch,community interaction,community feature,necessary feature,stable release,ticket system,advanced feature,power users,feature set,chat irc,leading edge,router,new features,members,attempt,development branches,branch development diff --git a/tl/appendices/glossary.rst b/tl/appendices/glossary.rst new file mode 100644 index 0000000000000000000000000000000000000000..ea00d0600d65e1e09160e08009d21c698ec6ff8b --- /dev/null +++ b/tl/appendices/glossary.rst @@ -0,0 +1,99 @@ +Glossary +######## + +.. glossary:: + + CDN + Content Delivery Network. A 3rd party vendor you can pay to help + distribute your content to data centers around the world. This helps + put your static assets closer to geographically distributed users. + + columns + Used in the ORM when referring to the table columns in an database + table. + + CSRF + Cross Site Request Forgery. Prevents replay attacks, double + submissions and forged requests from other domains. + + DSN + Data Source Name. A connection string format that is formed like a URI. + CakePHP supports DSN's for Cache, Database, Log and Email connections. + + dot notation + Dot notation defines an array path, by separating nested levels with ``.`` + For example:: + + Cache.default.engine + + Would point to the following value:: + + [ + 'Cache' => [ + 'default' => [ + 'engine' => 'File' + ] + ] + ] + + DRY + Don't repeat yourself. Is a principle of software development aimed at + reducing repetition of information of all kinds. In CakePHP DRY is used + to allow you to code things once and re-use them across your + application. + + fields + A generic term used to describe both entity properties, or database + columns. Often used in conjunction with the FormHelper. + + HTML attributes + An array of key => values that are composed into HTML attributes. For example:: + + // Given + ['class' => 'my-class', 'target' => '_blank'] + + // Would generate + class="my-class" target="_blank" + + If an option can be minimized or accepts its name as the value, then ``true`` + can be used:: + + // Given + ['checked' => true] + + // Would generate + checked="checked" + + PaaS + Platform as a Service. Platform as a Service providers will provide + cloud based hosting, database and caching resources. Some popular + providers include Heroku, EngineYard and PagodaBox + + properties + Used when referencing columns mapped onto an ORM entity. + + plugin syntax + Plugin syntax refers to the dot separated class name indicating classes + are part of a plugin:: + + // The plugin is "DebugKit", and the class name is "Toolbar". + 'DebugKit.Toolbar' + + // The plugin is "AcmeCorp/Tools", and the class name is "Toolbar". + 'AcmeCorp/Tools.Toolbar' + + routes.php + A file in ``config`` directory that contains routing configuration. + This file is included before each request is processed. + It should connect all the routes your application needs so + requests can be routed to the correct controller + action. + + routing array + An array of attributes that are passed to :php:meth:`Router::url()`. + They typically look like:: + + ['controller' => 'Posts', 'action' => 'view', 5] + +.. meta:: + :title lang=en: Glossary + :keywords lang=en: html attributes,array class,array controller,glossary glossary,target blank,fields,properties,columns,dot notation,routing configuration,forgery,replay,router,syntax,config,submissions diff --git a/tl/appendices/orm-migration.rst b/tl/appendices/orm-migration.rst new file mode 100644 index 0000000000000000000000000000000000000000..af41e5d24c9aefabd041f758800fa0f2fbf89f63 --- /dev/null +++ b/tl/appendices/orm-migration.rst @@ -0,0 +1,605 @@ +New ORM Upgrade Guide +##################### + +CakePHP 3.0 features a new ORM that has been re-written from the ground up. +While the ORM used in 1.x and 2.x has served us well for a long time it had +a few issues that we wanted to fix. + +* Frankenstein - Is it a record, or a table? Currently it's both. +* Inconsistent API - Model::read() for example. +* No query object - Queries are always defined as arrays, this has some + limitations and restrictions. For example it makes doing unions and + sub-queries much harder. +* Returns arrays - This is a common complaint about CakePHP, and has probably + reduced adoption at some levels. +* No record object - This makes attaching formatting methods + difficult/impossible. +* Containable - Should be part of the ORM, not a crazy hacky behavior. +* Recursive - This should be better controlled as defining which associations + are included, not a level of recursiveness. +* DboSource - It is a beast, and Model relies on it more than datasource. That + separation could be cleaner and simpler. +* Validation - Should be separate, it's a giant crazy function right now. Making + it a reusable bit would make the framework more extensible. + +The ORM in CakePHP 3.0 solves these and many more problems. The new ORM +focuses on relational data stores right now. In the future and through plugins +we will add non relational stores like ElasticSearch and others. + +Design of the New ORM +===================== + +The new ORM solves several problems by having more specialized and focused +classes. In the past you would use ``Model`` and a Datasource for all +operations. Now the ORM is split into more layers: + +* ``Cake\Database\Connection`` - Provides a platform independent way to create + and use connections. This class provides a way to use transactions, + execute queries and access schema data. +* ``Cake\Database\Dialect`` - The classes in this namespace provide platform + specific SQL and transform queries to work around platform specific + limitations. +* ``Cake\Database\Type`` - Is the gateway class to CakePHP database type + conversion system. It is a pluggable framework for adding abstract column + types and providing mappings between database, PHP representations and PDO + bindings for each data type. For example datetime columns are represented as + ``DateTime`` instances in your code now. +* ``Cake\ORM\Table`` - The main entry point into the new ORM. Provides access + to a single table. Handles the definition of association, use of behaviors and + creation of entities and query objects. +* ``Cake\ORM\Behavior`` - The base class for behaviors, which act very similar + to behaviors in previous versions of CakePHP. +* ``Cake\ORM\Query`` - A fluent object based query builder that replaces + the deeply nested arrays used in previous versions of CakePHP. +* ``Cake\ORM\ResultSet`` - A collection of results that gives powerful tools + for manipulating data in aggregate. +* ``Cake\ORM\Entity`` - Represents a single row result. Makes accessing data + and serializing to various formats a snap. + +Now that you are more familiar with some of the classes you'll interact with +most frequently in the new ORM it is good to look at the three most important +classes. The ``Table``, ``Query`` and ``Entity`` classes do much of the heavy +lifting in the new ORM, and each serves a different purpose. + +Table Objects +------------- + +Table objects are the gateway into your data. They handle many of the tasks that +``Model`` did in previous releases. Table classes handle tasks like: + +- Creating queries. +- Providing finders. +- Validating and saving entities. +- Deleting entities. +- Defining and accessing associations. +- Triggering callback events. +- Interacting with behaviors. + +The documentation chapter on :doc:`/orm/table-objects` provides far more detail +on how to use table objects than this guide can. Generally when moving existing +model code over it will end up in a table object. Table objects don't contain +any platform dependent SQL. Instead they collaborate with entities and the query +builder to do their work. Table objects also interact with behaviors and other +interested parties through published events. + +Query Objects +------------- + +While these are not classes you will build yourself, your application code will +make extensive use of the :doc:`/orm/query-builder` which is central to the new +ORM. The query builder makes it easy to build simple or complex queries +including those that were previously very difficult in CakePHP like ``HAVING``, +``UNION`` and sub-queries. + +The various find() calls your application has currently will need to be updated +to use the new query builder. The Query object is responsible for containing the +data to make a query without executing the query itself. It collaborates with +the connection/dialect to generate platform specific SQL which is executed +creating a ``ResultSet`` as the output. + +Entity Objects +-------------- + +In previous versions of CakePHP the ``Model`` class returned dumb arrays that +could not contain any logic or behavior. While the community made this +short-coming less painful with projects like CakeEntity, the array results were +often a short coming that caused many developers trouble. For CakePHP 3.0, the +ORM always returns object result sets unless you explicitly disable that +feature. The chapter on :doc:`/orm/entities` covers the various tasks you can +accomplish with entities. + +Entities are created in one of two ways. Either by loading data from the +database, or converting request data into entities. Once created, entities allow +you to manipulate the data they contain and persist their data by collaborating +with table objects. + +Key Differences +=============== + +The new ORM is a large departure from the existing ``Model`` layer. There are +many important differences that are important in understanding how the new ORM +operates and how to update your code. + +Inflection Rules Updated +------------------------ + +You may have noticed that table classes have a pluralized name. In addition to +tables having pluralized names, associations are also referred in the plural +form. This is in contrast to ``Model`` where class names and association aliases +were singular. There are a few reasons for this change: + +* Table classes represent **collections** of data, not single rows. +* Associations link tables together, describing the relations between many + things. + +While the conventions for table objects are to always use plural forms, your +entity association properties will be populated based on the association type. + +.. note:: + + BelongsTo and HasOne associations will use the singular form in entity + properties, while HasMany and BelongsToMany (HABTM) will use plural forms. + +The convention change for table objects is most apparent when building queries. +Instead of expressing queries like:: + + // Wrong + $query->where(['User.active' => 1]); + +You need to use the plural form:: + + // Correct + $query->where(['Users.active' => 1]); + +Find returns a Query Object +--------------------------- + +One important difference in the new ORM is that calling ``find`` on a table will +not return the results immediately, but will return a Query object; this serves +several purposes. + +It is possible to alter queries further, after calling ``find``:: + + $articles = TableRegistry::get('Articles'); + $query = $articles->find(); + $query->where(['author_id' => 1])->order(['title' => 'DESC']); + +It is possible to stack custom finders to append conditions, sorting, limit and +any other clause to the same query before it is executed:: + + $query = $articles->find('approved')->find('popular'); + $query->find('latest'); + +You can compose queries one into the other to create subqueries easier than +ever:: + + $query = $articles->find('approved'); + $favoritesQuery = $article->find('favorites', ['for' => $user]); + $query->where(['id' => $favoritesQuery->select(['id'])]); + +You can decorate queries with iterators and call methods without even touching +the database. This is great when you have parts of your view cached and having +the results taken from the database is not actually required:: + + // No queries made in this example! + $results = $articles->find() + ->order(['title' => 'DESC']) + ->formatResults(function (\Cake\Collection\CollectionInterface $results) { + return $results->extract('title'); + }); + +Queries can be seen as the result object, trying to iterate the query, calling +``toArray()`` or any method inherited from :doc:`collection `, +will result in the query being executed and results returned to you. + +The biggest difference you will find when coming from CakePHP 2.x is that +``find('first')`` does not exist anymore. There is a trivial replacement for it, +and it is the ``first()`` method:: + + // Before + $article = $this->Article->find('first'); + + // Now + $article = $this->Articles->find()->first(); + + // Before + $article = $this->Article->find('first', [ + 'conditions' => ['author_id' => 1] + ]); + + // Now + $article = $this->Articles->find('all', [ + 'conditions' => ['author_id' => 1] + ])->first(); + + // Can also be written + $article = $this->Articles->find() + ->where(['author_id' => 1]) + ->first(); + +If you are loading a single record by its primary key, it will be better to +just call ``get()``:: + + $article = $this->Articles->get(10); + +Finder Method Changes +--------------------- + +Returning a query object from a find method has several advantages, but comes at +a cost for people migrating from 2.x. If you had some custom find methods in +your models, they will need some modifications. This is how you create custom +finder methods in 3.0:: + + class ArticlesTable + { + + public function findPopular(Query $query, array $options) + { + return $query->where(['times_viewed' > 1000]); + } + + public function findFavorites(Query $query, array $options) + { + $for = $options['for']; + return $query->matching('Users.Favorites', function ($q) use ($for) { + return $q->where(['Favorites.user_id' => $for]); + }); + } + } + +As you can see, they are pretty straightforward, they get a Query object instead +of an array and must return a Query object back. For 2.x users that implemented +afterFind logic in custom finders, you should check out the :ref:`map-reduce` +section, or use the features found on the +:doc:`collection objects `. If in your +models you used to rely on having an afterFind for all find operations you can +migrate this code in one of a few ways: + +1. Override your entity constructor method and do additional formatting there. +2. Create accessor methods in your entity to create the virtual properties. +3. Redefine ``findAll()`` and use ``formatResults``. + +In the 3rd case above your code would look like:: + + public function findAll(Query $query, array $options) + { + return $query->formatResults(function (\Cake\Collection\CollectionInterface $results) { + return $results->map(function ($row) { + // Your afterfind logic + }); + }) + } + +You may have noticed that custom finders receive an options array. You can pass +any extra information to your finder using this parameter. This is great +news for people migrating from 2.x. Any of the query keys that were used in +previous versions will be converted automatically for you in 3.x to the correct +functions:: + + // This works in both CakePHP 2.x and 3.0 + $articles = $this->Articles->find('all', [ + 'fields' => ['id', 'title'], + 'conditions' => [ + 'OR' => ['title' => 'Cake', 'author_id' => 1], + 'published' => true + ], + 'contain' => ['Authors'], // The only change! (notice plural) + 'order' => ['title' => 'DESC'], + 'limit' => 10, + ]); + +If your application uses 'magic' or :ref:`dynamic-finders`, you will have to +adapt those calls. In 3.x the ``findAllBy*`` methods have been removed, instead +``findBy*`` always returns a query object. To get the first result, you need to +use the ``first()`` method:: + + $article = $this->Articles->findByTitle('A great post!')->first(); + +Hopefully, migrating from older versions is not as daunting as it first seems. +Many of the features we have added will help you remove code as you can better +express your requirements using the new ORM and at the same time the +compatibility wrappers will help you rewrite those tiny differences in a fast +and painless way. + +One of the other nice improvements in 3.x around finder methods is that +behaviors can implement finder methods with no fuss. By simply defining a method +with a matching name and signature on a Behavior the finder will automatically +be available on any tables the behavior is attached to. + +Recursive and ContainableBehavior Removed +----------------------------------------- + +In previous versions of CakePHP you needed to use ``recursive``, +``bindModel()``, ``unbindModel()`` and ``ContainableBehavior`` to reduce the +loaded data to the set of associations you were interested in. A common tactic +to manage associations was to set ``recursive`` to ``-1`` and use Containable to +manage all associations. In CakePHP 3.0 ContainableBehavior, recursive, +bindModel, and unbindModel have all been removed. Instead the ``contain()`` +method has been promoted to be a core feature of the query builder. Associations +are only loaded if they are explicitly turned on. For example:: + + $query = $this->Articles->find('all'); + +Will **only** load data from the ``articles`` table as no associations have been +included. To load articles and their related authors you would do:: + + $query = $this->Articles->find('all')->contain(['Authors']); + +By only loading associated data that has been specifically requested you spend +less time fighting the ORM trying to get only the data you want. + +No afterFind Event or Virtual Fields +------------------------------------ + +In previous versions of CakePHP you needed to make extensive use of the +``afterFind`` callback and virtual fields in order to create generated data +properties. These features have been removed in 3.0. Because of how ResultSets +iteratively generate entities, the ``afterFind`` callback was not possible. +Both afterFind and virtual fields can largely be replaced with virtual +properties on entities. For example if your User entity has both first and last +name columns you can add an accessor for `full_name` and generate the property +on the fly:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class User extends Entity + { + protected function _getFullName() + { + return $this->first_name . ' ' . $this->last_name; + } + } + +Once defined you can access your new property using ``$user->full_name``. +Using the :ref:`map-reduce` features of the ORM allow you to build aggregated +data from your results, which is another use case that the ``afterFind`` +callback was often used for. + +While virtual fields are no longer an explicit feature of the ORM, adding +calculated fields is easy to do in your finder methods. By using the query +builder and expression objects you can achieve the same results that virtual +fields gave:: + + namespace App\Model\Table; + + use Cake\ORM\Table; + use Cake\ORM\Query; + + class ReviewsTable extends Table + { + public function findAverage(Query $query, array $options = []) + { + $avg = $query->func()->avg('rating'); + $query->select(['average' => $avg]); + return $query; + } + } + +Associations No Longer Defined as Properties +-------------------------------------------- + +In previous versions of CakePHP the various associations your models had were +defined in properties like ``$belongsTo`` and ``$hasMany``. In CakePHP 3.0, +associations are created with methods. Using methods allows us to sidestep the +many limitations class definitions have, and provide only one way to define +associations. Your ``initialize()`` method and all other parts of your application +code, interact with the same API when manipulating associations:: + + namespace App\Model\Table; + + use Cake\ORM\Table; + use Cake\ORM\Query; + + class ReviewsTable extends Table + { + + public function initialize(array $config) + { + $this->belongsTo('Movies'); + $this->hasOne('Ratings'); + $this->hasMany('Comments') + $this->belongsToMany('Tags') + } + + } + +As you can see from the example above each of the association types uses +a method to create the association. One other difference is that +``hasAndBelongsToMany`` has been renamed to ``belongsToMany``. To find out more +about creating associations in 3.0 see the section on :doc:`/orm/associations`. + +Another welcome improvement to CakePHP is the ability to create your own +association classes. If you have association types that are not covered by the +built-in relation types you can create a custom ``Association`` sub-class and +define the association logic you need. + +Validation No Longer Defined as a Property +------------------------------------------ + +Like associations, validation rules were defined as a class property in previous +versions of CakePHP. This array would then be lazily transformed into +a ``ModelValidator`` object. This transformation step added a layer of +indirection, complicating rule changes at runtime. Furthermore, validation rules +being defined as a property made it difficult for a model to have multiple sets +of validation rules. In CakePHP 3.0, both these problems have been remedied. +Validation rules are always built with a ``Validator`` object, and it is trivial +to have multiple sets of rules:: + + namespace App\Model\Table; + + use Cake\ORM\Table; + use Cake\ORM\Query; + use Cake\Validation\Validator; + + class ReviewsTable extends Table + { + + public function validationDefault(Validator $validator) + { + $validator->requirePresence('body') + ->add('body', 'length', [ + 'rule' => ['minLength', 20], + 'message' => 'Reviews must be 20 characters or more', + ]) + ->add('user_id', 'numeric', [ + 'rule' => 'numeric' + ]); + return $validator; + } + + } + +You can define as many validation methods as you need. Each method should be +prefixed with ``validation`` and accept a ``$validator`` argument. + +In previous versions of CakePHP 'validation' and the related callbacks covered +a few related but different uses. In CakePHP 3.0, what was formerly called +validation is now split into two concepts: + +#. Data type and format validation. +#. Enforcing application, or business rules. + +Validation is now applied before ORM entities are created from request data. +This step lets you ensure data matches the data type, format, and basic shape +your application expects. You can use your validators when converting request +data into entities by using the ``validate`` option. See the documentation on +:ref:`converting-request-data` for more information. + +:ref:`Application rules ` allow you to define rules that +ensure your application's rules, state and workflows are enforced. Rules are +defined in your Table's ``buildRules()`` method. Behaviors can add rules using +the ``buildRules()`` hook method. An example ``buildRules()`` method for our +articles table could be:: + + // In src/Model/Table/ArticlesTable.php + namespace App\Model\Table; + + use Cake\ORM\Table; + use Cake\ORM\RulesChecker; + + class ArticlesTable extends Table + { + public function buildRules(RulesChecker $rules) + { + $rules->add($rules->existsIn('user_id', 'Users')); + $rules->add( + function ($article, $options) { + return ($article->published && empty($article->reviewer)); + }, + 'isReviewed', + [ + 'errorField' => 'published', + 'message' => 'Articles must be reviewed before publishing.' + ] + ); + return $rules; + } + } + +Identifier Quoting Disabled by Default +-------------------------------------- + +In the past CakePHP has always quoted identifiers. Parsing SQL snippets and +attempting to quote identifiers was both error prone and expensive. If you are +following the conventions CakePHP sets out, the cost of identifier quoting far +outweighs any benefit it provides. Because of this identifier quoting has been +disabled by default in 3.0. You should only need to enable identifier quoting if +you are using column names or table names that contain special characters or are +reserved words. If required, you can enable identifier quoting when configuring +a connection:: + + // In config/app.php + 'Datasources' => [ + 'default' => [ + 'className' => 'Cake\Database\Driver\Mysql', + 'username' => 'root', + 'password' => 'super_secret', + 'host' => 'localhost', + 'database' => 'cakephp', + 'quoteIdentifiers' => true, + ] + ], + +.. note:: + + Identifiers in ``QueryExpression`` objects will not be quoted, and you will + need to quote them manually or use IdentifierExpression objects. + +Updating Behaviors +================== + +Like most ORM related features, behaviors have changed in 3.0 as well. They now +attach to ``Table`` instances which are the conceptual descendant of the +``Model`` class in previous versions of CakePHP. There are a few key +differences from behaviors in CakePHP 2.x: + +- Behaviors are no longer shared across multiple tables. This means you no + longer have to 'namespace' settings stored in a behavior. Each table using + a behavior will get its own instance. +- The method signatures for mixin methods have changed. +- The method signatures for callback methods have changed. +- The base class for behaviors have changed. +- Behaviors can add finder methods. + +New Base Class +-------------- + +The base class for behaviors has changed. Behaviors should now extend +``Cake\ORM\Behavior``; if a behavior does not extend this class an exception +will be raised. In addition to the base class changing, the constructor for +behaviors has been modified, and the ``startup()`` method has been removed. +Behaviors that need access to the table they are attached to should define +a constructor:: + + namespace App\Model\Behavior; + + use Cake\ORM\Behavior; + + class SluggableBehavior extends Behavior + { + + protected $_table; + + public function __construct(Table $table, array $config) + { + parent::__construct($table, $config); + $this->_table = $table; + } + + } + +Mixin Methods Signature Changes +------------------------------- + +Behaviors continue to offer the ability to add 'mixin' methods to Table objects, +however the method signature for these methods has changed. In CakePHP 3.0, +behavior mixin methods can expect the **same** arguments provided to the table +'method'. For example:: + + // Assume table has a slug() method provided by a behavior. + $table->slug($someValue); + +The behavior providing the ``slug()`` method will receive only 1 argument, and its +method signature should look like:: + + public function slug($value) + { + // Code here. + } + +Callback Method Signature Changes +--------------------------------- + +Behavior callbacks have been unified with all other listener methods. Instead of +their previous arguments, they need to expect an event object as their first +argument:: + + public function beforeFind(Event $event, Query $query, array $options) + { + // Code. + } + +See :ref:`table-callbacks` for the signatures of all the callbacks a behavior +can subscribe to. diff --git a/tl/bake.rst b/tl/bake.rst new file mode 100644 index 0000000000000000000000000000000000000000..8a74a5fe542dd988bedcb83a57be660291b7a49e --- /dev/null +++ b/tl/bake.rst @@ -0,0 +1,31 @@ +Bake Console +############ + +CakePHP's bake console is another effort to get you up and running in CakePHP +– fast. The bake console can create any of CakePHP's basic ingredients: models, +behaviors, views, helpers, controllers, components, test cases, fixtures and plugins. +And we aren't just talking skeleton classes: Bake can create a fully functional +application in just a few minutes. In fact, Bake is a natural step to take once +an application has been scaffolded. + +Installation +============ + +Before trying to use or extend bake, make sure it is installed in your +application. Bake is provided as a plugin that you can install with Composer:: + + composer require --dev cakephp/bake:~1.0 + +The above will install bake as a development dependency. This means that it will +not be installed when you do production deployments. The following sections +cover bake in more detail: + +.. toctree:: + :maxdepth: 1 + + bake/usage + bake/development + +.. meta:: + :title lang=en: Bake Console + :keywords lang=en: command line interface,development,bake view, bake template syntax,erb tags,asp tags,percent tags diff --git a/tl/bake/development.rst b/tl/bake/development.rst new file mode 100644 index 0000000000000000000000000000000000000000..1be36fd2250fba7adb2580b466b66fd1ac3a3c30 --- /dev/null +++ b/tl/bake/development.rst @@ -0,0 +1,304 @@ +Extending Bake +############## + +Bake features an extensible architecture that allows your application or plugins +to modify or add-to the base functionality. Bake makes use of a dedicated +view class which uses the `Twig `_ template engine. + +Bake Events +=========== + +As a view class, ``BakeView`` emits the same events as any other view class, +plus one extra initialize event. However, whereas standard view classes use the +event prefix "View.", ``BakeView`` uses the event prefix "Bake.". + +The initialize event can be used to make changes which apply to all baked +output, for example to add another helper to the bake view class this event can +be used:: + + on('Bake.initialize', function (Event $event) { + $view = $event->getSubject(); + + // In my bake templates, allow the use of the MySpecial helper + $view->loadHelper('MySpecial', ['some' => 'config']); + + // And add an $author variable so it's always available + $view->set('author', 'Andy'); + + }); + +If you want to modify bake from within another plugin, putting your plugin's +bake events in the plugin ``config/bootstrap.php`` file is a good idea. + +Bake events can be handy for making small changes to existing templates. +For example, to change the variable names used when baking controller/template +files one can use a function listening for ``Bake.beforeRender`` to modify the +variables used in the bake templates:: + + on('Bake.beforeRender', function (Event $event) { + $view = $event->getSubject(); + + // Use $rows for the main data variable in indexes + if ($view->get('pluralName')) { + $view->set('pluralName', 'rows'); + } + if ($view->get('pluralVar')) { + $view->set('pluralVar', 'rows'); + } + + // Use $theOne for the main data variable in view/edit + if ($view->get('singularName')) { + $view->set('singularName', 'theOne'); + } + if ($view->get('singularVar')) { + $view->set('singularVar', 'theOne'); + } + + }); + +You may also scope the ``Bake.beforeRender`` and ``Bake.afterRender`` events to +a specific generated file. For instance, if you want to add specific actions to +your UsersController when generating from a **Controller/controller.twig** file, +you can use the following event:: + + on( + 'Bake.beforeRender.Controller.controller', + function (Event $event) { + $view = $event->getSubject(); + if ($view->viewVars['name'] == 'Users') { + // add the login and logout actions to the Users controller + $view->viewVars['actions'] = [ + 'login', + 'logout', + 'index', + 'view', + 'add', + 'edit', + 'delete' + ]; + } + } + ); + +By scoping event listeners to specific bake templates, you can simplify your +bake related event logic and provide callbacks that are easier to test. + +Bake Template Syntax +==================== + +Bake template files use the `Twig `__ template syntax. + +One way to see/understand how bake templates works, especially when attempting +to modify bake template files, is to bake a class and compare the template used +with the pre-processed template file which is left in the application's +**tmp/bake** folder. + +So, for example, when baking a shell like so: + +.. code-block:: bash + + bin/cake bake shell Foo + +The template used (**vendor/cakephp/bake/src/Template/Bake/Shell/shell.twig**) +looks like this:: + + `` A Bake template php close tag + * ``<%=`` A Bake template php short-echo tag + * ``<%-`` A Bake template php open tag, stripping any leading whitespace + before the tag + * ``-%>`` A Bake template php close tag, stripping trailing whitespace after + the tag + +.. _creating-a-bake-theme: + +Creating a Bake Theme +===================== + +If you wish to modify the output produced by the "bake" command, you can +create your own bake 'theme' which allows you to replace some or all of the +templates that bake uses. The best way to do this is: + +#. Bake a new plugin. The name of the plugin is the bake 'theme' name +#. Create a new directory **plugins/[name]/src/Template/Bake/Template/**. +#. Copy any templates you want to override from + **vendor/cakephp/bake/src/Template/Bake/Template** to matching files in your + plugin. +#. When running bake use the ``--theme`` option to specify the bake-theme you + want to use. To avoid having to specify this option in each call, you can also + set your custom theme to be used as default theme:: + + Test->classSuffixes[$this->name()])) { + $this->Test->classSuffixes[$this->name()] = 'Foo'; + } + + $name = ucfirst($this->name()); + if (!isset($this->Test->classTypes[$name])) { + $this->Test->classTypes[$name] = 'Foo'; + } + + return parent::bakeTest($className); + } + +* The **class suffix** will be appened to the name provided in your ``bake`` + call. In the previous example, it would create a ``ExampleFooTest.php`` file. +* The **class type** will be the sub-namespace used that will lead to your + file (relative to the app or the plugin you are baking into). In the previous + example, it would create your test with the namespace ``App\Test\TestCase\Foo`` + . + +.. meta:: + :title lang=en: Extending Bake + :keywords lang=en: command line interface,development,bake view, bake template syntax,twig,erb tags,percent tags + diff --git a/tl/bake/usage.rst b/tl/bake/usage.rst new file mode 100644 index 0000000000000000000000000000000000000000..cb806192a0867c16c760b0ab8ca5b22e675bac65 --- /dev/null +++ b/tl/bake/usage.rst @@ -0,0 +1,127 @@ +Code Generation with Bake +######################### + +The cake console is run using the PHP CLI (command line interface). +If you have problems running the script, ensure that: + +#. You have the PHP CLI installed and that it has the proper modules enabled (eg: MySQL, intl). +#. Users also might have issues if the database host is 'localhost' and should try '127.0.0.1' instead, as localhost can cause issues with PHP CLI. +#. Depending on how your computer is configured, you may have to set execute rights on the cake bash script to call it using ``bin/cake bake``. + +Before running bake you should make sure you have at least one database +connection configured. See the section on :ref:`database configuration +` for more information. + +When run with no arguments ``bin/cake bake`` will output a list of available +tasks. You should see something like:: + + $ bin/cake bake + + Welcome to CakePHP v3.4.6 Console + --------------------------------------------------------------- + App : src + Path: /var/www/cakephp.dev/src/ + PHP : 5.6.20 + --------------------------------------------------------------- + The following commands can be used to generate skeleton code for your application. + + Available bake commands: + + - all + - behavior + - cell + - component + - controller + - fixture + - form + - helper + - mailer + - migration + - migration_diff + - migration_snapshot + - model + - plugin + - seed + - shell + - shell_helper + - task + - template + - test + + By using `cake bake [name]` you can invoke a specific bake task. + +You can get more information on what each task does, and what options are +available using the ``--help`` option:: + + $ bin/cake bake --help + + Welcome to CakePHP v3.4.6 Console + --------------------------------------------------------------- + App : src + Path: /var/www/cakephp.dev/src/ + PHP : 5.6.20 + --------------------------------------------------------------- + The Bake script generates controllers, models and template files for + your application. If run with no command line arguments, Bake guides the + user through the class creation process. You can customize the + generation process by telling Bake where different parts of your + application are using command line arguments. + + Usage: + cake bake.bake [subcommand] [options] + + Subcommands: + + all Bake a complete MVC skeleton. + behavior Bake a behavior class file. + cell Bake a cell class file. + component Bake a component class file. + controller Bake a controller skeleton. + fixture Generate fixtures for use with the test suite. You + can use `bake fixture all` to bake all fixtures. + form Bake a form class file. + helper Bake a helper class file. + mailer Bake a mailer class file. + migration Bake migration class. + migration_diff Bake migration class. + migration_snapshot Bake migration snapshot class. + model Bake table and entity classes. + plugin Create the directory structure, AppController class + and testing setup for a new plugin. Can create + plugins in any of your bootstrapped plugin paths. + seed Bake seed class. + shell Bake a shell class file. + shell_helper Bake a shell_helper class file. + task Bake a task class file. + template Bake views for a controller, using built-in or + custom templates. + test Bake test case skeletons for classes. + + To see help on a subcommand use `cake bake.bake [subcommand] --help` + + Options: + + --connection, -c Database connection to use in conjunction with `bake + all`. (default: default) + --everything Bake a complete MVC skeleton, using all the available + tables. Usage: "bake all --everything" + --force, -f Force overwriting existing files without prompting. + --help, -h Display this help. + --plugin, -p Plugin to bake into. + --prefix Prefix to bake controllers and templates into. + --quiet, -q Enable quiet output. + --tablePrefix Table prefix to be used in models. + --theme, -t The theme to use when baking code. (choices: + Bake|Migrations) + --verbose, -v Enable verbose output. + +Bake Themes +=========== + +The theme option is common to all bake commands, and allows changing the bake +template files used when baking. To create your own templates, see the +:ref:`bake theme creation documentation `. + +.. meta:: + :title lang=en: Code Generation with Bake + :keywords lang=en: command line interface,functional application,database,database configuration,bash script,basic ingredients,project,model,path path,code generation,scaffolding,windows users,configuration file,few minutes,config,iew,shell,models,running,mysql diff --git a/tl/chronos.rst b/tl/chronos.rst new file mode 100644 index 0000000000000000000000000000000000000000..4b4fe167e25b2e05d2e998641f03a99f3004d146 --- /dev/null +++ b/tl/chronos.rst @@ -0,0 +1,313 @@ +Chronos +======= + +Chronos provides a zero-dependency collection of extensions to the ``DateTime`` +object. In addition to convenience methods, Chronos provides: + +* ``Date`` objects for representing calendar dates. +* Immutable date and datetime objects. +* A pluggable translation system. Only English translations are included in the + library. However, ``cakephp/i18n`` can be used for full language support. + +Installation +------------ + +To install Chronos, you should use ``composer``. From your +application's ROOT directory (where composer.json file is located) run the +following:: + + php composer.phar require cakephp/chronos "@stable" + +Overview +-------- + +Chronos provides a number of extensions to the DateTime objects provided by PHP. +Chronos provides 5 classes that cover mutable and immutable date/time variants +and extensions to ``DateInterval``. + +* ``Cake\Chronos\Chronos`` is an immutable *date and time* object. +* ``Cake\Chronos\Date`` is a immutable *date* object. +* ``Cake\Chronos\MutableDateTime`` is a mutable *date and time* object. +* ``Cake\Chronos\MutableDate`` is a mutable *date* object. +* ``Cake\Chronos\ChronosInterval`` is an extension to the ``DateInterval`` + object. + +Lastly, if you want to typehint against Chronos-provided date/time objects you +should use ``Cake\Chronos\ChronosInterface``. All of the date and time objects +implement this interface. + +Creating Instances +------------------ + +There are many ways to get an instance of Chronos or Date. There are a number of +factory methods that work with different argument sets:: + + use Cake\Chronos\Chronos; + + $now = Chronos::now(); + $today = Chronos::today(); + $yesterday = Chronos::yesterday(); + $tomorrow = Chronos::tomorrow(); + + // Parse relative expressions + $date = Chronos::parse('+2 days, +3 hours'); + + // Date and time integer values. + $date = Chronos::create(2015, 12, 25, 4, 32, 58); + + // Date or time integer values. + $date = Chronos::createFromDate(2015, 12, 25); + $date = Chronos::createFromTime(11, 45, 10); + + // Parse formatted values. + $date = Chronos::createFromFormat('m/d/Y', '06/15/2015'); + +Working with Immutable Objects +------------------------------ + +If you've used PHP's ``DateTime`` objects, you're comfortable with *mutable* +objects. Chronos offers mutable objects, but it also provides *immutable* +objects. Immutable objects create copies of objects each time an object is +modified. Because modifier methods around datetimes are not always transparent, +data can be modified accidentally or without the developer knowing. +Immutable objects prevent accidental changes to +data, and make code free of order-based dependency issues. Immutability +does mean that you will need to remember to replace variables when using +modifiers:: + + // This code doesn't work with immutable objects + $time->addDay(1); + doSomething($time); + return $time; + + // This works like you'd expect + $time = $time->addDay(1); + $time = doSomething($time); + return $time; + +By capturing the return value of each modification your code will work as +expected. If you ever have an immutable object, and want to create a mutable +one, you can use ``toMutable()``:: + + $inplace = $time->toMutable(); + +Date Objects +------------ + +PHP only provides a single DateTime object. Representing calendar dates can be +a bit awkward with this class as it includes timezones, and time components that +don't really belong in the concept of a 'day'. Chronos provides a ``Date`` +object that allows you to represent dates. The time and timezone for these +objects is always fixed to ``00:00:00 UTC`` and all formatting/difference +methods operate at the day resolution:: + + use Cake\Chronos\Date; + + $today = Date::today(); + + // Changes to the time/timezone are ignored. + $today->modify('+1 hours'); + + // Outputs '2015-12-20' + echo $today; + +Modifier Methods +---------------- + +Chronos objects provide modifier methods that let you modify the value in +a granular way:: + + // Set components of the datetime value. + $halloween = Date::create() + ->year(2015) + ->month(10) + ->day(31) + ->hour(20) + ->minute(30); + +You can also modify parts of a date relatively:: + + $future = Date::create() + ->addYear(1) + ->subMonth(2) + ->addDays(15) + ->addHours(20) + ->subMinutes(2); + +It is also possible to make big jumps to defined points in time:: + + $time = Chronos::create(); + $time->startOfDay(); + $time->endOfDay(); + $time->startOfMonth(); + $time->endOfMonth(); + $time->startOfYear(); + $time->endOfYear(); + $time->startOfWeek(); + $time->endOfWeek(); + +Or jump to specific days of the week:: + + $time->next(ChronosInterface::TUESDAY); + $time->previous(ChronosInterface::MONDAY); + +When modifying dates/times across :abbr:`DST (Daylight Savings Time)` transitions +your operations may gain/lose an additional hours resulting in hour values that +don't add up. You can avoid these issues by first changing your timezone to +``UTC``, modifying the time:: + + // Additional hour gained. + $time = new Chronos('2014-03-30 00:00:00', 'Europe/London'); + debug($time->modify('+24 hours')); // 2014-03-31 01:00:00 + + // First switch to UTC, and modify + $time = $time->setTimezone('UTC') + ->modify('+24 hours'); + +Once you are done modifying the time you can add the original timezone to get +the localized time. + +Comparison Methods +------------------ + +Once you have 2 instances of Chronos date/time objects you can compare them in +a variety of ways:: + + // Full suite of comparators exist + // ne, gt, lt, lte. + $first->eq($second); + $first->gte($second); + + // See if the current object is between two others. + $now->between($start, $end); + + // Find which argument is closest or farthest. + $now->closest($june, $november); + $now->farthest($june, $november); + +You can also inquire about where a given value falls on the calendar:: + + $now->isToday(); + $now->isYesterday(); + $now->isFuture(); + $now->isPast(); + + // Check the day of the week + $now->isWeekend(); + + // All other weekday methods exist too. + $now->isMonday(); + +You can also find out if a value was within a relative time period:: + + $time->wasWithinLast('3 days'); + $time->isWithinNext('3 hours'); + +Generating Differences +---------------------- + +In addition to comparing datetimes, calculating differences or deltas between +two values is a common task:: + + // Get a DateInterval representing the difference + $first->diff($second); + + // Get difference as a count of specific units. + $first->diffInHours($second); + $first->diffInDays($second); + $first->diffInWeeks($second); + $first->diffInYears($second); + +You can generate human readable differences suitable for use in a feed or +timeline:: + + // Difference from now. + echo $date->diffForHumans(); + + // Difference from another point in time. + echo $date->diffForHumans($other); // 1 hour ago; + +Formatting Strings +------------------ + +Chronos provides a number of methods for displaying our outputting datetime +objects:: + + // Uses the format controlled by setToStringFormat() + echo $date; + + // Different standard formats + echo $time->toAtomString(); // 1975-12-25T14:15:16-05:00 + echo $time->toCookieString(); // Thursday, 25-Dec-1975 14:15:16 EST + echo $time->toIso8601String(); // 1975-12-25T14:15:16-05:00 + echo $time->toRfc822String(); // Thu, 25 Dec 75 14:15:16 -0500 + echo $time->toRfc850String(); // Thursday, 25-Dec-75 14:15:16 EST + echo $time->toRfc1036String(); // Thu, 25 Dec 75 14:15:16 -0500 + echo $time->toRfc1123String(); // Thu, 25 Dec 1975 14:15:16 -0500 + echo $time->toRfc2822String(); // Thu, 25 Dec 1975 14:15:16 -0500 + echo $time->toRfc3339String(); // 1975-12-25T14:15:16-05:00 + echo $time->toRssString(); // Thu, 25 Dec 1975 14:15:16 -0500 + echo $time->toW3cString(); // 1975-12-25T14:15:16-05:00 + + // Get the quarter/week + echo $time->toQuarter(); // 4 + echo $time->toWeek(); // 52 + + // Generic formatting + echo $time->toTimeString(); // 14:15:16 + echo $time->toDateString(); // 1975-12-25 + echo $time->toDateTimeString(); // 1975-12-25 14:15:16 + echo $time->toFormattedDateString(); // Dec 25, 1975 + echo $time->toDayDateTimeString(); // Thu, Dec 25, 1975 2:15 PM + +Extracting Date Components +-------------------------- + +Getting parts of a date object can be done by directly accessing properties:: + + $time = new Chronos('2015-12-31 23:59:58'); + $time->year; // 2015 + $time->month; // 12 + $time->day; // 31 + $time->hour // 23 + $time->minute // 59 + $time->second // 58 + +Other properties that can be accessed are: + +- timezone +- timezoneName +- micro +- dayOfWeek +- dayOfMonth +- dayOfYear +- daysInMonth +- timestamp +- quarter + +Testing Aids +------------ + +When writing unit tests, it is helpful to fixate the current time. Chronos lets +you fix the current time for each class. As part of your test suite's bootstrap +process you can include the following:: + + Chronos::setTestNow(Chronos::now()); + MutableDateTime::setTestNow(MutableDateTime::now()); + Date::setTestNow(Date::now()); + MutableDate::setTestNow(MutableDate::now()); + +This will fix the current time of all objects to be the point at which the test +suite started. + +For example, if you fixate the ``Chronos`` to some moment in the past, any new +instance of ``Chronos`` created with ``now`` or a relative time string, will be +returned relative to the fixated time:: + + Chronos::setTestNow(new Chronos('1975-12-25 00:00:00')); + + $time = new Chronos(); // 1975-12-25 00:00:00 + $time = new Chronos('1 hour ago'); // 1975-12-24 23:00:00 + +To reset the fixation, simply call ``setTestNow()`` again with no parameter or +with ``null`` as a parameter. diff --git a/tl/conf.py b/tl/conf.py new file mode 100644 index 0000000000000000000000000000000000000000..ee933abfbeac0132fc21719e059d122dd8f0e20c --- /dev/null +++ b/tl/conf.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# CakePHP Cookbook documentation build configuration file, created by +# sphinx-quickstart on Tue Jan 18 12:54:14 2011. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# Append the top level directory of the docs, so we can import from the config dir. +sys.path.insert(0, os.path.abspath('..')) + +# Pull in all the configuration options defined in the global config file.. +from config.all import * + +language = 'en' diff --git a/tl/console-and-shells.rst b/tl/console-and-shells.rst new file mode 100644 index 0000000000000000000000000000000000000000..b0ff9890002bd7295b5637601e421b72385b7b9f --- /dev/null +++ b/tl/console-and-shells.rst @@ -0,0 +1,1296 @@ +Console Tools, Shells & Tasks +############################# + +.. php:namespace:: Cake\Console + +CakePHP features not only a web framework but also a console framework for +creating console applications. Console applications are ideal for handling a +variety of background tasks such as maintenance, and completing work outside of +the request-response cycle. CakePHP console applications allow you to reuse your +application classes from the command line. + +CakePHP comes with a number of console applications out of the box. Some of +these applications are used in concert with other CakePHP features (like i18n), +and others are for general use to get you working faster. + +The CakePHP Console +=================== + +This section provides an introduction into CakePHP at the command-line. Console +tools are ideal for use in cron jobs, or command line based utilities that don't +need to be accessible from a web browser. + +PHP provides a CLI client that makes interfacing with your file system and +applications much smoother. The CakePHP console provides a framework for +creating shell scripts. The Console uses a dispatcher-type setup to load a shell +or task, and provide its parameters. + +.. note:: + + A command-line (CLI) build of PHP must be available on the system + if you plan to use the Console. + +Before we get into specifics, let's make sure you can run the CakePHP console. +First, you'll need to bring up a system shell. The examples shown in this +section will be in bash, but the CakePHP Console is Windows-compatible as well. +This example assumes that the user is currently logged into a bash prompt and is +currently at the root of a CakePHP application. + +A CakePHP application contains **src/Shell** and **src/Shell/Task** directories +that contain all of its shells and tasks. It also comes with an executable in +the **bin** directory:: + + $ cd /path/to/app + $ bin/cake + +.. note:: + + For Windows, the command needs to be ``bin\cake`` (note the backslash). + +Running the Console with no arguments produces this help message:: + + Welcome to CakePHP v3.5.0 Console + --------------------------------------------------------------- + App : App + Path: /Users/markstory/Sites/cakephp-app/src/ + --------------------------------------------------------------- + Current Paths: + + -app: src + -root: /Users/markstory/Sites/cakephp-app + -core: /Users/markstory/Sites/cakephp-app/vendor/cakephp/cakephp + + Changing Paths: + + Your working path should be the same as your application path. To change your path use the '-app' param. + Example: -app relative/path/to/myapp or -app /absolute/path/to/myapp + + Available Shells: + + - version + - help + - cache + - completion + - i18n + - orm_cache + - plugin + - routes + - server + - bug + - console + - event + - orm + - bake + - bake.bake + - migrations + - migrations.migrations + + To run a command, type `cake shell_name [args|options]` + To get help on a specific command, type `cake shell_name --help` + +The first information printed relates to paths. This is helpful if you're +running the console from different parts of the filesystem. + +You could then run the any of the listed shells by using its name:: + + # run server shell + bin/cake server + + # run migrations shell + bin/cake migrations -h + + # run bake (with plugin prefix) + bin/cake bake.bake -h + +Plugin shells can be invoked without a plugin prefix if the shell's name does +not overlap with an application or framework shell. In the case that two plugins +provide a shell with the same name, the first loaded plugin will get the short +alias. You can always use the ``plugin.shell`` format to unambiguously reference +a shell. + +.. php:class:: Shell + +Creating a Shell +================ + +Let's create a shell for use in the Console. For this example, we'll create a +simple Hello world shell. In your application's **src/Shell** directory create +**HelloShell.php**. Put the following code inside it:: + + namespace App\Shell; + + use Cake\Console\Shell; + + class HelloShell extends Shell + { + public function main() + { + $this->out('Hello world.'); + } + } + +The conventions for shell classes are that the class name should match the file +name, with the suffix of Shell. In our shell we created a ``main()`` method. +This method is called when a shell is called with no additional commands. We'll +add some more commands in a bit, but for now let's just run our shell. From your +application directory, run:: + + bin/cake hello + +You should see the following output:: + + Hello world. + +As mentioned before, the ``main()`` method in shells is a special method called +whenever there are no other commands or arguments given to a shell. Since our +main method wasn't very interesting let's add another command that does +something:: + + namespace App\Shell; + + use Cake\Console\Shell; + + class HelloShell extends Shell + { + public function main() + { + $this->out('Hello world.'); + } + + public function heyThere($name = 'Anonymous') + { + $this->out('Hey there ' . $name); + } + } + +After saving this file, you should be able to run the following command and see +your name printed out:: + + bin/cake hello hey_there your-name + +Any public method not prefixed by an ``_`` is allowed to be called from the +command line. As you can see, methods invoked from the command line are +transformed from the underscored shell argument to the correct camel-cased +method name in the class. + +In our ``heyThere()`` method we can see that positional arguments are provided +to our ``heyThere()`` function. Positional arguments are also available in the +``args`` property. +You can access switches or options on shell applications, which are available at +``$this->params``, but we'll cover that in a bit. + +When using a ``main()`` method you won't be able to use the positional +arguments. This is because the first positional argument or option is +interpreted as the command name. If you want to use arguments, you should use +method names other than ``main``. + +Shell Tasks +=========== + +There will be times when building more advanced console applications, you'll +want to compose functionality into re-usable classes that can be shared across +many shells. Tasks allow you to extract commands into classes. For example the +``bake`` command is made almost entirely of tasks. You define a tasks for a +shell using the ``$tasks`` property:: + + class UserShell extends Shell + { + public $tasks = ['Template']; + } + +You can use tasks from plugins using the standard :term:`plugin syntax`. +Tasks are stored in ``Shell/Task/`` in files named after their classes. So if +we were to create a new 'FileGenerator' task, you would create +**src/Shell/Task/FileGeneratorTask.php**. + +Each task must at least implement a ``main()`` method. The ShellDispatcher, +will call this method when the task is invoked. A task class looks like:: + + namespace App\Shell\Task; + + use Cake\Console\Shell; + + class FileGeneratorTask extends Shell + { + public function main() + { + + } + } + +A shell can also access its tasks as properties, which makes tasks great for +making re-usable chunks of functionality similar to +:doc:`/controllers/components`:: + + // Found in src/Shell/SeaShell.php + class SeaShell extends Shell + { + // Found in src/Shell/Task/SoundTask.php + public $tasks = ['Sound']; + + public function main() + { + $this->Sound->main(); + } + } + +You can also access tasks directly from the command line:: + + $ cake sea sound + +.. note:: + + In order to access tasks directly from the command line, the task + **must** be included in the shell class' $tasks property. + +Also, the task name must be added as a sub-command to the Shell's OptionParser:: + + public function getOptionParser() + { + $parser = parent::getOptionParser(); + $parser->addSubcommand('sound', [ + // Provide help text for the command list + 'help' => 'Execute The Sound Task.', + // Link the option parsers together. + 'parser' => $this->Sound->getOptionParser(), + ]); + return $parser; + } + +Loading Tasks On The Fly with TaskRegistry +------------------------------------------ + +You can load tasks on the fly using the Task registry object. You can load tasks +that were not declared in $tasks this way:: + + $project = $this->Tasks->load('Project'); + +Would load and return a ProjectTask instance. You can load tasks from plugins +using:: + + $progressBar = $this->Tasks->load('ProgressBar.ProgressBar'); + +Using Models in Your Shells +=========================== + +You'll often need access to your application's business logic in shell +utilities; CakePHP makes that super easy. You can load models in shells, just as +you would in a controller using ``loadModel()``. The loaded models are set as +properties attached to your shell:: + + namespace App\Shell; + + use Cake\Console\Shell; + + class UserShell extends Shell + { + + public function initialize() + { + parent::initialize(); + $this->loadModel('Users'); + } + + public function show() + { + if (empty($this->args[0])) { + // Use error() before CakePHP 3.2 + return $this->abort('Please enter a username.'); + } + $user = $this->Users->findByUsername($this->args[0])->first(); + $this->out(print_r($user, true)); + } + } + +The above shell, will fetch a user by username and display the information +stored in the database. + +Shell Helpers +============= + +If you have complex output generation logic, you can use +:doc:`/console-and-shells/helpers` to encapsulate this logic in a re-usable way. + +.. _invoking-other-shells-from-your-shell: + +Invoking Other Shells from Your Shell +===================================== + +.. php:method:: dispatchShell($args) + +There are still many cases where you will want to invoke one shell from another though. +``Shell::dispatchShell()`` gives you the ability to call other shells by providing the +``argv`` for the sub shell. You can provide arguments and options either +as var args or as a string:: + + // As a string + $this->dispatchShell('schema create Blog --plugin Blog'); + + // As an array + $this->dispatchShell('schema', 'create', 'Blog', '--plugin', 'Blog'); + +The above shows how you can call the schema shell to create the schema for a plugin +from inside your plugin's shell. + +Passing extra parameters to the dispatched Shell +------------------------------------------------ + +.. versionadded:: 3.1 + +It can sometimes be useful to pass on extra parameters (that are not shell arguments) +to the dispatched Shell. In order to do this, you can now pass an array to +``dispatchShell()``. The array is expected to have a ``command`` key as well +as an ``extra`` key:: + + // Using a command string + $this->dispatchShell([ + 'command' => 'schema create Blog --plugin Blog', + 'extra' => [ + 'foo' => 'bar' + ] + ]); + + // Using a command array + $this->dispatchShell([ + 'command' => ['schema', 'create', 'Blog', '--plugin', 'Blog'], + 'extra' => [ + 'foo' => 'bar' + ] + ]); + +Parameters passed through ``extra`` will be merged in the ``Shell::$params`` +property and are accessible with the ``Shell::param()`` method. +By default, a ``requested`` extra param is automatically added when a Shell +is dispatched using ``dispatchShell()``. This ``requested`` parameter prevents +the CakePHP console welcome message from being displayed on dispatched shells. + +Getting User Input +================== + +.. php:method:: in($question, $choices = null, $default = null) + +When building interactive console applications you'll need to get user input. +CakePHP provides an easy way to do this:: + + // Get arbitrary text from the user. + $color = $this->in('What color do you like?'); + + // Get a choice from the user. + $selection = $this->in('Red or Green?', ['R', 'G'], 'R'); + +Selection validation is case-insensitive. + +Creating Files +============== + +.. php:method:: createFile($path, $contents) + +Many Shell applications help automate development or deployment tasks. Creating +files is often important in these use cases. CakePHP provides an easy way to +create a file at a given path:: + + $this->createFile('bower.json', $stuff); + +If the Shell is interactive, a warning will be generated, and the user asked if +they want to overwrite the file if it already exists. If the shell's +interactive property is ``false``, no question will be asked and the file will +simply be overwritten. + +Console Output +============== + +.. php:method:out($message, $newlines, $level) +.. php:method:err($message, $newlines) + +The ``Shell`` class provides a few methods for outputting content:: + + // Write to stdout + $this->out('Normal message'); + + // Write to stderr + $this->err('Error message'); + + // Write to stderr and raise a stop exception + $this->abort('Fatal error'); + + // Before CakePHP 3.2. Write to stderr and exit() + $this->error('Fatal error'); + +It also provides two convenience methods regarding the output level:: + + // Would only appear when verbose output is enabled (-v) + $this->verbose('Verbose message'); + + // Would appear at all levels. + $this->quiet('Quiet message'); + +Shell also includes methods for clearing output, creating blank lines, or +drawing a line of dashes:: + + // Output 2 newlines + $this->out($this->nl(2)); + + // Clear the user's screen + $this->clear(); + + // Draw a horizontal line + $this->hr(); + +Lastly, you can update the current line of text on the screen using +``_io->overwrite()``:: + + $this->out('Counting down'); + $this->out('10', 0); + for ($i = 9; $i > 0; $i--) { + sleep(1); + $this->_io->overwrite($i, 0, 2); + } + +It is important to remember, that you cannot overwrite text +once a new line has been output. + +.. _shell-output-level: + +Console Output Levels +--------------------- + +Shells often need different levels of verbosity. When running as cron jobs, +most output is un-necessary. And there are times when you are not interested in +everything that a shell has to say. You can use output levels to flag output +appropriately. The user of the shell, can then decide what level of detail +they are interested in by setting the correct flag when calling the shell. +:php:meth:`Cake\\Console\\Shell::out()` supports 3 types of output by default. + +* ``QUIET`` - Only absolutely important information should be marked for quiet + output. +* ``NORMAL`` - The default level, and normal usage. +* ``VERBOSE`` - Mark messages that may be too noisy for everyday use, but + helpful for debugging as ``VERBOSE``. + +You can mark output as follows:: + + // Would appear at all levels. + $this->out('Quiet message', 1, Shell::QUIET); + $this->quiet('Quiet message'); + + // Would not appear when quiet output is toggled. + $this->out('normal message', 1, Shell::NORMAL); + $this->out('loud message', 1, Shell::VERBOSE); + $this->verbose('Verbose output'); + + // Would only appear when verbose output is enabled. + $this->out('extra message', 1, Shell::VERBOSE); + $this->verbose('Verbose output'); + +You can control the output level of shells, by using the ``--quiet`` and +``--verbose`` options. These options are added by default, and allow you to +consistently control output levels inside your CakePHP shells. + +The ``--quiet`` and ``--verbose`` options also control how logging data is +output to stdout/stderr. Normally info and higher log messages are output to +stdout/stderr. When ``--verbose`` is used, debug logs will be output to stdout. +When ``--quiet`` is used, only warning and higher log messages will be output to +stderr. + +Styling Output +-------------- + +Styling output is done by including tags - just like HTML - in your output. +ConsoleOutput will replace these tags with the correct ansi code sequence, or +remove the tags if you are on a console that doesn't support ansi codes. There +are several built-in styles, and you can create more. The built-in ones are + +* ``success`` Success messages. Green text. +* ``error`` Error messages. Red text. +* ``warning`` Warning messages. Yellow text. +* ``info`` Informational messages. Cyan text. +* ``comment`` Additional text. Blue text. +* ``question`` Text that is a question, added automatically by shell. + +You can create additional styles using ``$this->stdout->styles()``. To declare a +new output style you could do:: + + $this->_io->styles('flashy', ['text' => 'magenta', 'blink' => true]); + +This would then allow you to use a ```` tag in your shell output, and if +ansi colours are enabled, the following would be rendered as blinking magenta +text ``$this->out('Whoooa Something went wrong');``. When +defining styles you can use the following colours for the ``text`` and +``background`` attributes: + +* black +* blue +* cyan +* green +* magenta +* red +* white +* yellow + +You can also use the following options as boolean switches, setting them to a +truthy value enables them. + +* blink +* bold +* reverse +* underline + +Adding a style makes it available on all instances of ConsoleOutput as well, +so you don't have to redeclare styles for both stdout and stderr objects. + +Turning Off Colouring +--------------------- + +Although colouring is pretty awesome, there may be times when you want to turn it off, +or force it on:: + + $this->_io->outputAs(ConsoleOutput::RAW); + +The above will put the output object into raw output mode. In raw output mode, +no styling is done at all. There are three modes you can use. + +* ``ConsoleOutput::COLOR`` - Output with color escape codes in place. +* ``ConsoleOutput::PLAIN`` - Plain text output, known style tags will be + stripped from the output. +* ``ConsoleOutput::RAW`` - Raw output, no styling or formatting will be done. + This is a good mode to use if you are outputting XML or, want to debug why + your styling isn't working. + +By default on \*nix systems ConsoleOutput objects default to colour output. +On Windows systems, plain output is the default unless the ``ANSICON`` +environment variable is present. + +Stopping Shell Execution +======================== + +When your shell commands have reached a condition where you want execution to +stop, you can use ``abort()`` to raise a ``StopException`` that will halt the +process:: + + $user = $this->Users->get($this->args[0]); + if (!$user) { + // Halt with an error message and error code. + $this->abort('User cannot be found', 128); + } + +.. versionadded:: 3.2 + The abort() method was added in 3.2. In prior versions you can use + ``error()`` to output a message and stop execution. + +Status and Error Codes +---------------------- + +Command-line tools should return 0 to indicate success, or a non-zero value to +indicate an error condition. Since PHP methods usually return ``true`` or +``false``, the Cake Shell ``dispatch`` function helps to bridge these semantics +by converting your ``null`` and ``true`` return values to 0, and all other +values to 1. + +The Cake Shell ``dispatch`` function also catches the ``StopException`` and +uses its exception code value as the shell's exit code. As described above, you +can use the ``abort()`` method to print a message and exit with a specific +code, or raise the ``StopException`` directly as shown in the example:: + + namespace App\Shell\Task; + + use Cake\Console\Shell; + + class ErroneousShell extends Shell + { + public function main() + { + return true; + } + + public function itFails() + { + return false; + } + + public function itFailsSpecifically() + { + throw new StopException("", 2); + } + } + +The example above will return the following exit codes when executed on a +command-line:: + + $ bin/cake erroneousshell ; echo $? + 0 + $ bin/cake erroneousshell itFails ; echo $? + 1 + $ bin/cake erroneousshell itFailsSpecifically ; echo $? + 2 + +.. tip:: + + Avoid exit codes 64 - 78, as they have specific meanings described by + ``sysexits.h``. + Avoid exit codes above 127, as these are used to indicate process exit + by signal, such as SIGKILL or SIGSEGV. + +.. note:: + + You can read more about conventional exit codes in the sysexit manual page + on most Unix systems (``man sysexits``), or the ``System Error Codes`` help + page in Windows. + +Hook Methods +============ + +.. php:method:: initialize() + + Initializes the Shell, acts as constructor for subclasses and allows + configuration of tasks prior to shell execution. + +.. php:method:: startup() + + Starts up the Shell and displays the welcome message. Allows for checking + and configuring prior to command or main execution. + +.. tip:: + + Override the ``startup()`` method if you want to remove the welcome + information, or otherwise modify the pre-command flow. + +Configuring Options and Generating Help +======================================= + +.. php:class:: ConsoleOptionParser + +``ConsoleOptionParser`` provides a command line option and +argument parser. + +OptionParsers allow you to accomplish two goals at the same time. First, they +allow you to define the options and arguments for your commands. This allows +you to separate basic input validation and your console commands. Secondly, it +allows you to provide documentation, that is used to generate a well formatted +help file. + +The console framework in CakePHP gets your shell's option parser by calling +``$this->getOptionParser()``. Overriding this method allows you to configure the +OptionParser to define the expected inputs of your shell. You can also configure +subcommand option parsers, which allow you to have different option parsers for +subcommands and tasks. The ConsoleOptionParser implements a fluent interface and +includes methods for setting multiple options/arguments at once:: + + public function getOptionParser() + { + $parser = parent::getOptionParser(); + // Configure parser + return $parser; + } + +Configuring an Option Parser with the Fluent Interface +------------------------------------------------------ + +All of the methods that configure an option parser can be chained, allowing you +to define an entire option parser in one series of method calls:: + + public function getOptionParser() + { + $parser = parent::getOptionParser(); + $parser->addArgument('type', [ + 'help' => 'Either a full path or type of class.' + ])->addArgument('className', [ + 'help' => 'A CakePHP core class name (e.g: Component, HtmlHelper).' + ])->addOption('method', [ + 'short' => 'm', + 'help' => __('The specific method you want help on.') + ])->setDescription(__('Lookup doc block comments for classes in CakePHP.')); + return $parser; + } + +The methods that allow chaining are: + +- addArgument() +- addArguments() +- addOption() +- addOptions() +- addSubcommand() +- addSubcommands() +- setCommand() +- setDescription() +- setEpilog() + +Set the Description +~~~~~~~~~~~~~~~~~~~ + +.. php:method:: setDescription($text) + +The description displays above the argument and option information. By passing +in either an array or a string, you can set the value of the description:: + + // Set multiple lines at once + $parser->setDescription(['line one', 'line two']); + // Prior to 3.4 + $parser->description(['line one', 'line two']); + + // Read the current value + $parser->getDescription(); + +The **src/Shell/ConsoleShell.php** is a good example of the ``description()`` +method in action:: + + /** + * Display help for this console. + * + * @return ConsoleOptionParser + */ + public function getOptionParser() + { + $parser = new ConsoleOptionParser('console'); + $parser->setDescription( + 'This shell provides a REPL that you can use to interact ' . + 'with your application in an interactive fashion. You can use ' . + 'it to run adhoc queries with your models, or experiment ' . + 'and explore the features of CakePHP and your application.' . + "\n\n" . + 'You will need to have psysh installed for this Shell to work.' + ); + return $parser; + } + +The console's ``description`` output can be seen by executing the following +command:: + + $ bin/cake console --help + + Welcome to CakePHP v3.0.13 Console + --------------------------------------------------------------- + App : src + Path: /home/user/cakeblog/src/ + --------------------------------------------------------------- + This shell provides a REPL that you can use to interact with your + application in an interactive fashion. You can use it to run adhoc + queries with your models, or experiment and explore the features of + CakePHP and your application. + + You will need to have psysh installed for this Shell to work. + + Usage: + cake console [-h] [-v] [-q] + + Options: + + --help, -h Display this help. + --verbose, -v Enable verbose output. + --quiet, -q Enable quiet output. + +Set a help alias +~~~~~~~~~~~~~~~~ + +.. php:method:: setHelpAlias($alias) + +If you want to change the command name, you can use the ``setHelpAlias()`` method:: + + $parser->setHelpAlias('my-shell'); + +This will change the usage output to ``my-shell`` instead of the default ``cake`` value:: + + Usage: + my-shell console [-h] [-v] [-q] + +.. versionadded:: 3.5.0 + The ``setHelpAlias`` method was added in 3.5.0 + +Set the Epilog +-------------- + +.. php:method:: setEpilog($text) + +Gets or sets the epilog for the option parser. The epilog is displayed after the +argument and option information. By passing in either an array or a string, you +can set the value of the epilog. Calling with no arguments will return the +current value:: + + // Set multiple lines at once + $parser->setEpilog(['line one', 'line two']); + // Prior to 3.4 + $parser->epilog(['line one', 'line two']); + + // Read the current value + $parser->getEpilog(); + +To illustrate the ``epilog()`` method in action lets add a call to the +``getOptionParser()`` method used above in the **src/Shell/ConsoleShell.php**:: + + /** + * Display help for this console. + * + * @return ConsoleOptionParser + */ + public function getOptionParser() + { + $parser = new ConsoleOptionParser('console'); + $parser->setDescription( + 'This shell provides a REPL that you can use to interact ' . + 'with your application in an interactive fashion. You can use ' . + 'it to run adhoc queries with your models, or experiment ' . + 'and explore the features of CakePHP and your application.' . + "\n\n" . + 'You will need to have psysh installed for this Shell to work.' + ); + $parser->setEpilog('Thank you for baking with CakePHP!'); + return $parser; + } + +The text added with the ``setEpilog()`` method can be seen in the output from +the following console command:: + + $ bin/cake console --help + + Welcome to CakePHP v3.0.13 Console + --------------------------------------------------------------- + App : src + Path: /home/user/cakeblog/src/ + --------------------------------------------------------------- + This shell provides a REPL that you can use to interact with your + application in an interactive fashion. You can use it to run adhoc + queries with your models, or experiment and explore the features of + CakePHP and your application. + + You will need to have psysh installed for this Shell to work. + + Usage: + cake console [-h] [-v] [-q] + + Options: + + --help, -h Display this help. + --verbose, -v Enable verbose output. + --quiet, -q Enable quiet output. + + Thank you for baking with CakePHP! + +Adding Arguments +---------------- + +.. php:method:: addArgument($name, $params = []) + +Positional arguments are frequently used in command line tools, +and ``ConsoleOptionParser`` allows you to define positional +arguments as well as make them required. You can add arguments +one at a time with ``$parser->addArgument();`` or multiple at once +with ``$parser->addArguments();``:: + + $parser->addArgument('model', ['help' => 'The model to bake']); + +You can use the following options when creating an argument: + +* ``help`` The help text to display for this argument. +* ``required`` Whether this parameter is required. +* ``index`` The index for the arg, if left undefined the argument will be put + onto the end of the arguments. If you define the same index twice the + first option will be overwritten. +* ``choices`` An array of valid choices for this argument. If left empty all + values are valid. An exception will be raised when parse() encounters an + invalid value. + +Arguments that have been marked as required will throw an exception when +parsing the command if they have been omitted. So you don't have to +handle that in your shell. + +.. php:method:: addArguments(array $args) + +If you have an array with multiple arguments you can use +``$parser->addArguments()`` to add multiple arguments at once. :: + + $parser->addArguments([ + 'node' => ['help' => 'The node to create', 'required' => true], + 'parent' => ['help' => 'The parent node', 'required' => true] + ]); + +As with all the builder methods on ConsoleOptionParser, addArguments +can be used as part of a fluent method chain. + +Validating Arguments +-------------------- + +When creating positional arguments, you can use the ``required`` flag, to +indicate that an argument must be present when a shell is called. +Additionally you can use ``choices`` to force an argument to be from a list of +valid choices:: + + $parser->addArgument('type', [ + 'help' => 'The type of node to interact with.', + 'required' => true, + 'choices' => ['aro', 'aco'] + ]); + +The above will create an argument that is required and has validation on the +input. If the argument is either missing, or has an incorrect value an exception +will be raised and the shell will be stopped. + +Adding Options +-------------- + +.. php:method:: addOption($name, $options = []) + +Options or flags are also frequently used in command line tools. +``ConsoleOptionParser`` supports creating options with both verbose and short +aliases, supplying defaults and creating boolean switches. Options are created +with either ``$parser->addOption()`` or ``$parser->addOptions()``. :: + + $parser->addOption('connection', [ + 'short' => 'c', + 'help' => 'connection', + 'default' => 'default', + ]); + +The above would allow you to use either ``cake myshell --connection=other``, +``cake myshell --connection other``, or ``cake myshell -c other`` +when invoking the shell. You can also create boolean switches, these switches do +not consume values, and their presence just enables them in the parsed +parameters. :: + + $parser->addOption('no-commit', ['boolean' => true]); + +With this option, when calling a shell like +``cake myshell --no-commit something`` the no-commit param would have a value of +``true``, and 'something' would be a treated as a positional argument. +The built-in ``--help``, ``--verbose``, and ``--quiet`` options use this +feature. + +When creating options you can use the following options to define the behavior +of the option: + +* ``short`` - The single letter variant for this option, leave undefined for + none. +* ``help`` - Help text for this option. Used when generating help for the + option. +* ``default`` - The default value for this option. If not defined the default + will be ``true``. +* ``boolean`` - The option uses no value, it's just a boolean switch. + Defaults to ``false``. +* ``choices`` - An array of valid choices for this option. If left empty all + values are valid. An exception will be raised when parse() encounters an + invalid value. + +.. php:method:: addOptions(array $options) + +If you have an array with multiple options you can use ``$parser->addOptions()`` +to add multiple options at once. :: + + $parser->addOptions([ + 'node' => ['short' => 'n', 'help' => 'The node to create'], + 'parent' => ['short' => 'p', 'help' => 'The parent node'] + ]); + +As with all the builder methods on ConsoleOptionParser, addOptions can be used +as part of a fluent method chain. + +Option values are stored in the ``$this->params`` array. You can also use the +convenience method ``$this->param()`` to avoid errors when trying to access +non-present options. + +Validating Options +------------------ + +Options can be provided with a set of choices much like positional arguments +can be. When an option has defined choices, those are the only valid choices +for an option. All other values will raise an ``InvalidArgumentException``:: + + $parser->addOption('accept', [ + 'help' => 'What version to accept.', + 'choices' => ['working', 'theirs', 'mine'] + ]); + +Using Boolean Options +--------------------- + +Options can be defined as boolean options, which are useful when you need to +create some flag options. Like options with defaults, boolean options always +include themselves into the parsed parameters. When the flags are present they +are set to ``true``, when they are absent they are set to ``false``:: + + $parser->addOption('verbose', [ + 'help' => 'Enable verbose output.', + 'boolean' => true + ]); + +The following option would result in ``$this->params['verbose']`` always being +available. This lets you omit ``empty()`` or ``isset()`` checks for boolean +flags:: + + if ($this->params['verbose']) { + // Do something. + } + +Since the boolean options are always defined as ``true`` or ``false`` you can +omit additional check methods when using the array access. The +``$this->param()`` method makes these checks unnecessary for all cases. + +Adding Subcommands +------------------ + +.. php:method:: addSubcommand($name, $options = []) + +Console applications are often made of subcommands, and these subcommands may +require special option parsing and have their own help. A perfect example of +this is ``bake``. Bake is made of many separate tasks that all have their own +help and options. ``ConsoleOptionParser`` allows you to define subcommands and +provide command specific option parsers so the shell knows how to parse commands +for its tasks:: + + $parser->addSubcommand('model', [ + 'help' => 'Bake a model', + 'parser' => $this->Model->getOptionParser() + ]); + +The above is an example of how you could provide help and a specialized option +parser for a shell's task. By calling the Task's ``getOptionParser()`` we don't +have to duplicate the option parser generation, or mix concerns in our shell. +Adding subcommands in this way has two advantages. First, it lets your shell +document its subcommands in the generated help. It also gives easy access to the +subcommand help. With the above subcommand created you could call +``cake myshell --help`` and see the list of subcommands, and also run +``cake myshell model --help`` to view the help for just the model task. + +.. note:: + + Once your Shell defines subcommands, all subcommands must be explicitly + defined. + +When defining a subcommand you can use the following options: + +* ``help`` - Help text for the subcommand. +* ``parser`` - A ConsoleOptionParser for the subcommand. This allows you to + create method specific option parsers. When help is generated for a + subcommand, if a parser is present it will be used. You can also supply the + parser as an array that is compatible with + :php:meth:`Cake\\Console\\ConsoleOptionParser::buildFromArray()` + +Adding subcommands can be done as part of a fluent method chain. + +.. versionchanged:: 3.5.0 + When adding multi-word subcommands you can now invoke those commands using + ``snake_case`` in addition to the camelBacked form. + +Building a ConsoleOptionParser from an Array +-------------------------------------------- + +.. php:method:: buildFromArray($spec) + +As previously mentioned, when creating subcommand option parsers, you can define +the parser spec as an array for that method. This can help make building +subcommand parsers easier, as everything is an array:: + + $parser->addSubcommand('check', [ + 'help' => __('Check the permissions between an ACO and ARO.'), + 'parser' => [ + 'description' => [ + __("Use this command to grant ACL permissions. Once executed, the "), + __("ARO specified (and its children, if any) will have ALLOW access "), + __("to the specified ACO action (and the ACO's children, if any).") + ], + 'arguments' => [ + 'aro' => ['help' => __('ARO to check.'), 'required' => true], + 'aco' => ['help' => __('ACO to check.'), 'required' => true], + 'action' => ['help' => __('Action to check')] + ] + ] + ]); + +Inside the parser spec, you can define keys for ``arguments``, ``options``, +``description`` and ``epilog``. You cannot define ``subcommands`` inside an +array style builder. The values for arguments, and options, should follow the +format that :php:func:`Cake\\Console\\ConsoleOptionParser::addArguments()` and +:php:func:`Cake\\Console\\ConsoleOptionParser::addOptions()` use. You can also +use buildFromArray on its own, to build an option parser:: + + public function getOptionParser() + { + return ConsoleOptionParser::buildFromArray([ + 'description' => [ + __("Use this command to grant ACL permissions. Once executed, the "), + __("ARO specified (and its children, if any) will have ALLOW access "), + __("to the specified ACO action (and the ACO's children, if any).") + ], + 'arguments' => [ + 'aro' => ['help' => __('ARO to check.'), 'required' => true], + 'aco' => ['help' => __('ACO to check.'), 'required' => true], + 'action' => ['help' => __('Action to check')] + ] + ]); + } + +Merging ConsoleOptionParsers +---------------------------- + +.. php:method:: merge($spec) + +When building a group command, you maybe want to combine several parsers for +this:: + + $parser->merge($anotherParser); + +Note that the order of arguments for each parser must be the same, and that +options must also be compatible for it work. So do not use keys for different +things. + +Getting Help from Shells +------------------------ + +With the addition of ConsoleOptionParser getting help from shells is done in a +consistent and uniform way. By using the ``--help`` or -``h`` option you +can view the help for any core shell, and any shell that implements a +ConsoleOptionParser:: + + cake bake --help + cake bake -h + +Would both generate the help for bake. If the shell supports subcommands you can +get help for those in a similar fashion:: + + cake bake model --help + cake bake model -h + +This would get you the help specific to bake's model task. + +Getting Help as XML +------------------- + +When building automated tools or development tools that need to interact with +CakePHP shells, it's nice to have help available in a machine parse-able format. +The ConsoleOptionParser can provide help in xml by setting an additional +argument:: + + cake bake --help xml + cake bake -h xml + +The above would return an XML document with the generated help, options, +arguments and subcommands for the selected shell. A sample XML document would +look like: + +.. code-block:: xml + + + + bake fixture + Generate fixtures for use with the test suite. You can use + `bake fixture all` to bake all fixtures. + + Omitting all arguments and options will enter into an interactive + mode. + + + + + + + + + + + + + + + + + + +Renaming Commands +================= + +By default CakePHP will automatically discover all the commands in your +application and its plugins. You may want to reduce the number of exposed +commands, when building standalone console applications. You can use your +Application's ``console()`` hook to limit which commands are exposed and rename +the commands that are exposed:: + + namespace App; + + use App\Shell\UserShell; + use App\Shell\VersionShell; + use Cake\Http\BaseApplication; + + class Application extends BaseApplication + { + public function console($commands) + { + // Add by classname + $commands->add('user', UserShell::class); + + // Add instance + $commands->add('version', new VersionShell()); + + return $commands; + } + } + +In the above example, the only commands available would be ``help``, ``version`` +and ``user``. + +.. versionadded:: 3.5.0 + The ``console`` hook was added. + +Routing in Shells / CLI +======================= + +In command-line interface (CLI), specifically your shells and tasks, +``env('HTTP_HOST')`` and other webbrowser specific environment variables are not +set. + +If you generate reports or send emails that make use of ``Router::url()`` those +will contain the default host ``http://localhost/`` and thus resulting in +invalid URLs. In this case you need to specify the domain manually. +You can do that using the Configure value ``App.fullBaseUrl`` from your +bootstrap or config, for example. + +For sending emails, you should provide Email class with the host you want to +send the email with:: + + use Cake\Mailer\Email; + + $email = new Email(); + // Prior to 3.4 use domain() + $email->setDomain('www.example.org'); + +This asserts that the generated message IDs are valid and fit to the domain the +emails are sent from. + +More Topics +=========== + +.. toctree:: + :maxdepth: 1 + + console-and-shells/helpers + console-and-shells/repl + console-and-shells/cron-jobs + console-and-shells/i18n-shell + console-and-shells/completion-shell + console-and-shells/plugin-shell + console-and-shells/routes-shell + console-and-shells/upgrade-shell + console-and-shells/server-shell + console-and-shells/cache + +.. meta:: + :title lang=en: Shells, Tasks & Console Tools + :keywords lang=en: shell scripts,system shell,application classes,background tasks,line script,cron job,request response,system path,acl,new projects,shells,specifics,parameters,i18n,cakephp,directory,maintenance,ideal,applications,mvc diff --git a/tl/console-and-shells/cache.rst b/tl/console-and-shells/cache.rst new file mode 100644 index 0000000000000000000000000000000000000000..a37015f944cd6f4403ccfee354dc583085a86e16 --- /dev/null +++ b/tl/console-and-shells/cache.rst @@ -0,0 +1,14 @@ +Cache Shell +=========== + +To help you better manage cached data from a CLI environment, a shell command +is available for clearing cached data your application has:: + + // Clear one cache config + bin/cake cache clear + + // Clear all cache configs + bin/cake cache clear_all + +.. versionadded:: 3.3.0 + The cache shell was added in 3.3.0 diff --git a/tl/console-and-shells/completion-shell.rst b/tl/console-and-shells/completion-shell.rst new file mode 100644 index 0000000000000000000000000000000000000000..6cd73ed1c48f32d663b6a72cd83743ce83d48f55 --- /dev/null +++ b/tl/console-and-shells/completion-shell.rst @@ -0,0 +1,179 @@ +Completion Shell +################ + +Working with the console gives the developer a lot of possibilities but having +to completely know and write those commands can be tedious. Especially when +developing new shells where the commands differ per minute iteration. The +Completion Shells aids in this matter by providing an API to write completion +scripts for shells like bash, zsh, fish etc. + +Sub Commands +============ + +The Completion Shell consists of a number of sub commands to assist the +developer creating its completion script. Each for a different step in the +autocompletion process. + +Commands +-------- + +For the first step commands outputs the available Shell Commands, including +plugin name when applicable. (All returned possibilities, for this and the other +sub commands, are separated by a space.) For example:: + + bin/cake Completion commands + +Returns:: + + acl api bake command_list completion console i18n schema server test testsuite upgrade + +Your completion script can select the relevant commands from that list to +continue with. (For this and the following sub commands.) + +subCommands +----------- + +Once the preferred command has been chosen subCommands comes in as the second +step and outputs the possible sub command for the given shell command. For +example:: + + bin/cake Completion subcommands bake + +Returns:: + + controller db_config fixture model plugin project test view + +options +------- + +As the third and final options outputs options for the given (sub) command as +set in getOptionParser. (Including the default options inherited from Shell.) +For example:: + + bin/cake Completion options bake + +Returns:: + + --help -h --verbose -v --quiet -q --everything --connection -c --force -f --plugin -p --prefix --theme -t + +You can also pass an additional argument being the shell sub-command : it will +output the specific options of this sub-command. + +How to enable Bash autocompletion for the CakePHP Console +========================================================= + +First, make sure the **bash-completion** library is installed. If not, you do it +with the following command:: + + apt-get install bash-completion + +Create a file named **cake** in **/etc/bash_completion.d/** and put the +:ref:`bash-completion-file-content` inside it. + +Save the file, then restart your console. + +.. note:: + + If you are using MacOS X, you can install the **bash-completion** library + using **homebrew** with the command ``brew install bash-completion``. + The target directory for the **cake** file will be + **/usr/local/etc/bash_completion.d/**. + +.. _bash-completion-file-content: + +Bash Completion file content +---------------------------- + +This is the code you need to put inside the **cake** file in the correct location +in order to get autocompletion when using the CakePHP console:: + + # + # Bash completion file for CakePHP console + # + + _cake() + { + local cur prev opts cake + COMPREPLY=() + cake="${COMP_WORDS[0]}" + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ "$cur" == -* ]] ; then + if [[ ${COMP_CWORD} = 1 ]] ; then + opts=$(${cake} Completion options) + elif [[ ${COMP_CWORD} = 2 ]] ; then + opts=$(${cake} Completion options "${COMP_WORDS[1]}") + else + opts=$(${cake} Completion options "${COMP_WORDS[1]}" "${COMP_WORDS[2]}") + fi + + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + if [[ ${COMP_CWORD} = 1 ]] ; then + opts=$(${cake} Completion commands) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 + fi + + if [[ ${COMP_CWORD} = 2 ]] ; then + opts=$(${cake} Completion subcommands $prev) + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + if [[ $COMPREPLY = "" ]] ; then + _filedir + return 0 + fi + return 0 + fi + + opts=$(${cake} Completion fuzzy "${COMP_WORDS[@]:1}") + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + if [[ $COMPREPLY = "" ]] ; then + _filedir + return 0 + fi + return 0; + } + + complete -F _cake cake bin/cake + +Using autocompletion +==================== + +Once enabled, the autocompletion can be used the same way than for other +built-in commands, using the **TAB** key. +Three type of autocompletion are provided. The following output are from a fresh CakePHP install. + +Commands +-------- + +Sample output for commands autocompletion:: + + $ bin/cake + bake i18n orm_cache routes + console migrations plugin server + +Subcommands +----------- + +Sample output for subcommands autocompletion:: + + $ bin/cake bake + behavior helper shell + cell mailer shell_helper + component migration template + controller migration_snapshot test + fixture model + form plugin + +Options +------- + +Sample output for subcommands options autocompletion:: + + $ bin/cake bake - + -c --everything --force --help --plugin -q -t -v + --connection -f -h -p --prefix --quiet --theme --verbose + diff --git a/tl/console-and-shells/cron-jobs.rst b/tl/console-and-shells/cron-jobs.rst new file mode 100644 index 0000000000000000000000000000000000000000..605a0183aa36ead1bfdd2e1affafdeada21b2bf6 --- /dev/null +++ b/tl/console-and-shells/cron-jobs.rst @@ -0,0 +1,43 @@ +Running Shells as Cron Jobs +########################### + +A common thing to do with a shell is making it run as a cronjob to +clean up the database once in a while or send newsletters. This is +trivial to setup, for example:: + + */5 * * * * cd /full/path/to/root && bin/cake myshell myparam + # * * * * * command to execute + # │ │ │ │ │ + # │ │ │ │ │ + # │ │ │ │ \───── day of week (0 - 6) (0 to 6 are Sunday to Saturday, + # | | | | or use names) + # │ │ │ \────────── month (1 - 12) + # │ │ \─────────────── day of month (1 - 31) + # │ \──────────────────── hour (0 - 23) + # \───────────────────────── min (0 - 59) + +You can see more info here: http://en.wikipedia.org/wiki/Cron + +.. tip:: + + Use ``-q`` (or `--quiet`) to silence any output for cronjobs. + +Cron Jobs on Shared Hosting +--------------------------- + +On some shared hostings ``cd /full/path/to/root && bin/cake myshell myparam`` +might not work. Instead you can use +``php /full/path/to/root/bin/cake.php myshell myparam``. + +.. note:: + + register_argc_argv has to be turned on by including ``register_argc_argv + = 1`` in your php.ini. If you cannot change register_argc_argv globally, + you can tell the cron job to use your own configuration by + specifying it with ``-d register_argc_argv=1`` parameter. Example: ``php + -d register_argc_argv=1 /full/path/to/root/bin/cake.php myshell + myparam`` + +.. meta:: + :title lang=en: Running Shells as cronjobs + :keywords lang=en: cronjob,bash script,crontab diff --git a/tl/console-and-shells/helpers.rst b/tl/console-and-shells/helpers.rst new file mode 100644 index 0000000000000000000000000000000000000000..731656a84b1170be6445cc4b5bd30815d8391c61 --- /dev/null +++ b/tl/console-and-shells/helpers.rst @@ -0,0 +1,124 @@ +Shell Helpers +############# + +.. versionadded:: 3.1 + Shell Helpers were added in 3.1.0 + +Shell Helpers let you package up complex output generation code. Shell +Helpers can be accessed and used from any shell or task:: + + // Output some data as a table. + $this->helper('Table')->output($data); + + // Get a helper from a plugin. + $this->helper('Plugin.HelperName')->output($data); + +You can also get instances of helpers and call any public methods on them:: + + // Get and use the Progress Helper. + $progress = $this->helper('Progress'); + $progress->increment(10); + $progress->draw(); + +Creating Helpers +================ + +While CakePHP comes with a few shell helpers you can create more in your +application or plugins. As an example, we'll create a simple helper to generate +fancy headings. First create the **src/Shell/Helper/HeadingHelper.php** and put +the following in it:: + + _io->out($marker . ' ' . $args[0] . ' ' . $marker); + } + } + +We can then use this new helper in one of our shell commands by calling it:: + + // With ### on either side + $this->helper('Heading')->output(['It works!']); + + // With ~~~~ on either side + $this->helper('Heading')->output(['It works!', '~', 4]); + +Helpers generally implement the ``output()`` method which takes an array of +parameters. However, because Console Helpers are vanilla classes they can +implement additional methods that take any form of arguments. + +Built-In Helpers +================ + +Table Helper +------------ + +The TableHelper assists in making well formatted ASCII art tables. Using it is +pretty simple:: + + $data = [ + ['Header 1', 'Header', 'Long Header'], + ['short', 'Longish thing', 'short'], + ['Longer thing', 'short', 'Longest Value'], + ]; + $this->helper('Table')->output($data); + + // Outputs + +--------------+---------------+---------------+ + | Header 1 | Header | Long Header | + +--------------+---------------+---------------+ + | short | Longish thing | short | + | Longer thing | short | Longest Value | + +--------------+---------------+---------------+ + +Progress Helper +--------------- + +The ProgressHelper can be used in two different ways. The simple mode lets you +provide a callback that is invoked until the progress is complete:: + + $this->helper('Progress')->output(['callback' => function ($progress) { + // Do work here. + $progress->increment(20); + $progress->draw(); + }]); + +You can control the progress bar more by providing additional options: + +- ``total`` The total number of items in the progress bar. Defaults + to 100. +- ``width`` The width of the progress bar. Defaults to 80. +- ``callback`` The callback that will be called in a loop to advance the + progress bar. + +An example of all the options in use would be:: + + $this->helper('Progress')->output([ + 'total' => 10, + 'width' => 20, + 'callback' => function ($progress) { + $progress->increment(2); + $progress->draw(); + } + ]); + +The progress helper can also be used manually to increment and re-render the +progress bar as necessary:: + + $progress = $this->helper('Progress'); + $progress->init([ + 'total' => 10, + 'width' => 20, + ]); + + $progress->increment(4); + $progress->draw(); + diff --git a/tl/console-and-shells/i18n-shell.rst b/tl/console-and-shells/i18n-shell.rst new file mode 100644 index 0000000000000000000000000000000000000000..09998e3b94aef78419b815cb583e90e0d4f210dc --- /dev/null +++ b/tl/console-and-shells/i18n-shell.rst @@ -0,0 +1,80 @@ +I18N Shell +########## + +The i18n features of CakePHP use `po files `_ +as their translation source. PO files integrate with commonly used translation tools +like `Poedit `_. + +The i18n shell provides a quick and easy way to generate po template files. +These templates files can then be given to translators so they can translate the +strings in your application. Once you have translations done, pot files can be +merged with existing translations to help update your translations. + +Generating POT Files +==================== + +POT files can be generated for an existing application using the ``extract`` +command. This command will scan your entire application for ``__()`` style +function calls, and extract the message string. Each unique string in your +application will be combined into a single POT file:: + + bin/cake i18n extract + +The above will run the extraction shell. The result of this command will be the +file **src/Locale/default.pot**. You use the pot file as a template for creating +po files. If you are manually creating po files from the pot file, be sure to +correctly set the ``Plural-Forms`` header line. + +Generating POT Files for Plugins +-------------------------------- + +You can generate a POT file for a specific plugin using:: + + bin/cake i18n extract --plugin + +This will generate the required POT files used in the plugins. + +Extracting from multiple folders at once +---------------------------------------- + +Sometimes, you might need to extract strings from more than one directory of +your application. For instance, if you are defining some strings in the +``config`` directory of your application, you probably want to extract strings +from this directory as well as from the ``src`` directory. You can do it by +using the ``--paths`` option. It takes a comma-separated list of absolute paths +to extract:: + + bin/cake i18n extract --paths /var/www/app/config,/var/www/app/src + +Excluding Folders +----------------- + +You can pass a comma separated list of folders that you wish to be excluded. +Any path containing a path segment with the provided values will be ignored:: + + bin/cake i18n extract --exclude Test,Vendor + +Skipping Overwrite Warnings for Existing POT Files +-------------------------------------------------- + +By adding ``--overwrite``, the shell script will no longer warn you if a POT +file already exists and will overwrite by default:: + + bin/cake i18n extract --overwrite + +Extracting Messages from the CakePHP Core Libraries +--------------------------------------------------- + +By default, the extract shell script will ask you if you like to extract +the messages used in the CakePHP core libraries. Set ``--extract-core`` to yes +or no to set the default behavior:: + + bin/cake i18n extract --extract-core yes + + // or + + bin/cake i18n extract --extract-core no + +.. meta:: + :title lang=en: I18N shell + :keywords lang=en: pot files,locale default,translation tools,message string,app locale,php class,validation,i18n,translations,shell,models diff --git a/tl/console-and-shells/orm-cache.rst b/tl/console-and-shells/orm-cache.rst new file mode 100644 index 0000000000000000000000000000000000000000..a295d03dc5cb53fddb4bee9cf0d341a02c5a119c --- /dev/null +++ b/tl/console-and-shells/orm-cache.rst @@ -0,0 +1,25 @@ +ORM Cache Shell +############### + +The OrmCacheShell provides a simple CLI tool for managing your application's +metadata caches. In deployment situations it is helpful to rebuild the metadata +cache in-place without clearing the existing cache data. You can do this by +running:: + + bin/cake orm_cache build --connection default + +This will rebuild the metadata cache for all tables on the ``default`` +connection. If you only need to rebuild a single table you can do that by +providing its name:: + + bin/cake orm_cache build --connection default articles + +In addition to building cached data, you can use the OrmCacheShell to remove +cached metadata as well:: + + # Clear all metadata + bin/cake orm_cache clear + + # Clear a single table + bin/cake orm_cache clear articles + diff --git a/tl/console-and-shells/plugin-shell.rst b/tl/console-and-shells/plugin-shell.rst new file mode 100644 index 0000000000000000000000000000000000000000..e502c624228d6bb9cc608bb06d0e0cc4821800ca --- /dev/null +++ b/tl/console-and-shells/plugin-shell.rst @@ -0,0 +1,75 @@ +.. _plugin-shell: + +Plugin Shell +############ + +The plugin shell allows you to load and unload plugins via the command prompt. +If you need help, run:: + + bin/cake plugin --help + +Loading Plugins +--------------- + +Via the ``Load`` task you are able to load plugins in your +**config/bootstrap.php**. You can do this by running:: + + bin/cake plugin load MyPlugin + +This will add the following to your **config/bootstrap.php**:: + + Plugin::load('MyPlugin'); + +Adding the ``-b`` or ``-r`` switch to the load task will enable loading of the plugin's +``bootstrap`` and ``routes`` values:: + + bin/cake plugin load -b MyPlugin + + // Load the bootstrap.php from the plugin + Plugin::load('MyPlugin', ['bootstrap' => true]); + + bin/cake plugin load -r MyPlugin + + // Load the routes.php from the plugin + Plugin::load('MyPlugin', ['routes' => true]); + +If you are loading a plugin that only provides CLI tools - like bake - you can +update your ``bootstrap_cli.php`` with:: + + bin/cake plugin load --cli MyPlugin + bin/cake plugin unload --cli MyPlugin + +.. versionadded:: 3.4.0 + As of 3.4.0 the ``--cli`` option is supported + +Unloading Plugins +----------------- + +You can unload a plugin by specifying its name:: + + bin/cake plugin unload MyPlugin + +This will remove the line ``Plugin::load('MyPlugin',...)`` from your +**config/bootstrap.php**. + +Plugin Assets +------------- + +CakePHP by default serves plugins assets using the ``AssetFilter`` dispatcher +filter. While this is a good convenience, it is recommended to symlink / copy +the plugin assets under app's webroot so that they can be directly served by the +web server without invoking PHP. You can do this by running:: + + bin/cake plugin assets symlink + +Running the above command will symlink all plugins assets under app's webroot. +On Windows, which doesn't support symlinks, the assets will be copied in +respective folders instead of being symlinked. + +You can symlink assets of one particular plugin by specifying its name:: + + bin/cake plugin assets symlink MyPlugin + +.. meta:: + :title lang=en: Plugin Shell + :keywords lang=en: plugin,assets,shell,load,unload diff --git a/tl/console-and-shells/repl.rst b/tl/console-and-shells/repl.rst new file mode 100644 index 0000000000000000000000000000000000000000..584b2ba8ff15f671f60dbc1bb97d30f34320bdb8 --- /dev/null +++ b/tl/console-and-shells/repl.rst @@ -0,0 +1,45 @@ +Interactive Console (REPL) +########################## + +The CakePHP app skeleton comes with a built in REPL(Read Eval Print Loop) that +makes it easy to explore some CakePHP and your application in an interactive +console. You can start the interactive console using:: + + $ bin/cake console + +This will bootstrap your application and start an interactive console. At this +point you can interact with your application code and execute queries using your +application's models:: + + $ bin/cake console + + Welcome to CakePHP v3.0.0 Console + --------------------------------------------------------------- + App : App + Path: /Users/mark/projects/cakephp-app/src/ + --------------------------------------------------------------- + >>> $articles = Cake\ORM\TableRegistry::get('Articles'); + // object(Cake\ORM\Table)( + // + // ) + >>> $articles->find()->all(); + +Since your application has been bootstrapped you can also test routing using the +REPL:: + + >>> Cake\Routing\Router::parse('/articles/view/1'); + // [ + // 'controller' => 'Articles', + // 'action' => 'view', + // 'pass' => [ + // 0 => '1' + // ], + // 'plugin' => NULL + // ] + +You can also test generating URL's:: + + >>> Cake\Routing\Router::url(['controller' => 'Articles', 'action' => 'edit', 99]); + // '/articles/edit/99' + +To quit the REPL you can use ``CTRL-C`` or by typing ``exit``. diff --git a/tl/console-and-shells/routes-shell.rst b/tl/console-and-shells/routes-shell.rst new file mode 100644 index 0000000000000000000000000000000000000000..09da865a5f8300d198752d3fc185a22b78458deb --- /dev/null +++ b/tl/console-and-shells/routes-shell.rst @@ -0,0 +1,37 @@ +Routes Shell +############ + +.. versionadded:: 3.1 + The RoutesShell was added in 3.1 + +The RoutesShell provides a simple to use CLI interface for testing and debugging +routes. You can use it to test how routes are parsed, and what URLs routing +parameters will generate. + +Getting a List of all Routes +---------------------------- + +:: + + bin/cake routes + +Testing URL parsing +------------------- + +You can quickly see how a URL will be parsed using the ``check`` method:: + + bin/cake routes check /bookmarks/edit/1 + +If your route contains any query string parameters remember to surround the URL +in quotes:: + + bin/cake routes check "/bookmarks/?page=1&sort=title&direction=desc" + +Testing URL Generation +---------------------- + +You can see how which URL a :term:`routing array` will generate using the +``generate`` method:: + + bin/cake routes generate controller:Bookmarks action:edit 1 + diff --git a/tl/console-and-shells/server-shell.rst b/tl/console-and-shells/server-shell.rst new file mode 100644 index 0000000000000000000000000000000000000000..aa69940a64b228e9859c2fe581a14c4e7552a2b5 --- /dev/null +++ b/tl/console-and-shells/server-shell.rst @@ -0,0 +1,22 @@ +Server Shell +############ + +The ``ServerShell`` lets you stand up a simple webserver using the built in PHP +webserver. While this server is *not* intended for production use it can +be handy in development when you want to quickly try an idea out and don't want +to spend time configuring Apache or Nginx. You can start the server shell with:: + + $ bin/cake server + +You should see the server boot up and attach to port 8765. You can visit the +CLI server by visiting ``http://localhost:8765`` +in your web-browser. You can close the server by pressing ``CTRL-C`` in your +terminal. + +Changing the Port and Document Root +=================================== + +You can customize the port and document root using options:: + + $ bin/cake server --port 8080 --document_root path/to/app + diff --git a/tl/console-and-shells/upgrade-shell.rst b/tl/console-and-shells/upgrade-shell.rst new file mode 100644 index 0000000000000000000000000000000000000000..e8a40d0058be32ab4d9c265e0aafa6778f444021 --- /dev/null +++ b/tl/console-and-shells/upgrade-shell.rst @@ -0,0 +1,15 @@ +.. _upgrade-shell: + +Upgrade Shell +############# + +The upgrade shell will do most of the work to upgrade your CakePHP application +from 2.x to 3.x. + +It is provided by a standalone +`Upgrade plugin `_. Please read the README +file to get all information on how to upgrade your application. + +.. meta:: + :title lang=en: Upgrade Shell + :keywords lang=en: api docs,shell,upgrade diff --git a/tl/contents.rst b/tl/contents.rst new file mode 100644 index 0000000000000000000000000000000000000000..329ab3036081bcd889f0b41580f1001f1ca00f37 --- /dev/null +++ b/tl/contents.rst @@ -0,0 +1,100 @@ +Contents +######## + +.. toctree:: + :hidden: + + index + topics + +.. toctree:: + :maxdepth: 3 + :caption: Preface + + intro + quickstart + appendices/3-x-migration-guide + tutorials-and-examples + contributing + +.. toctree:: + :maxdepth: 3 + :caption: Getting Started + + installation + development/configuration + development/routing + controllers/request-response + controllers/middleware + controllers + views + orm + +.. toctree:: + :maxdepth: 3 + :caption: Using CakePHP + + controllers/components/authentication + bake + core-libraries/caching + console-and-shells + development/debugging + deployment + core-libraries/email + development/errors + core-libraries/events + core-libraries/internationalization-and-localization + core-libraries/logging + core-libraries/form + controllers/components/pagination + plugins + development/rest + security + development/sessions + development/testing + core-libraries/validation + +.. toctree:: + :maxdepth: 3 + :caption: Utility Classes + + core-libraries/app + core-libraries/collections + core-libraries/file-folder + core-libraries/hash + core-libraries/httpclient + core-libraries/inflector + core-libraries/number + core-libraries/registry-objects + core-libraries/text + core-libraries/time + core-libraries/xml + +.. toctree:: + :maxdepth: 3 + :caption: Plugins + + chronos + debug-kit + migrations + elasticsearch + upgrade-tool + +.. toctree:: + :maxdepth: 3 + :caption: Other + + core-libraries/global-constants-and-functions + appendices + +.. toctree:: + :maxdepth: 3 + :caption: Phinx + + phinx + +.. todolist:: + +.. meta:: + :title lang=en: Contents + :keywords lang=en: core libraries,ref search,shells,deployment,appendices,glossary,models diff --git a/tl/contributing.rst b/tl/contributing.rst new file mode 100644 index 0000000000000000000000000000000000000000..e0ca55d733beccd1db7f594857859ec9fca5450d --- /dev/null +++ b/tl/contributing.rst @@ -0,0 +1,18 @@ +Contributing +############ + +There are a number of ways you can contribute to CakePHP. The following sections +cover the various ways you can contribute to CakePHP: + +.. toctree:: + :maxdepth: 1 + + contributing/documentation + contributing/tickets + contributing/code + contributing/cakephp-coding-conventions + contributing/backwards-compatibility + +.. meta:: + :title lang=en: Contributing + :keywords lang=en: coding conventions,documentation,maxdepth diff --git a/tl/contributing/backwards-compatibility.rst b/tl/contributing/backwards-compatibility.rst new file mode 100644 index 0000000000000000000000000000000000000000..1e767439281a6de2722b8ba4c0d0ad7c713c1e6e --- /dev/null +++ b/tl/contributing/backwards-compatibility.rst @@ -0,0 +1,191 @@ +Backwards Compatibility Guide +############################# + +Ensuring that you can upgrade your applications easily and smoothly is important +to us. That's why we only break compatibility at major release milestones. +You might be familiar with `semantic versioning `_, which is +the general guideline we use on all CakePHP projects. In short, semantic +versioning means that only major releases (such as 2.0, 3.0, 4.0) can break +backwards compatibility. Minor releases (such as 2.1, 3.1, 3.2) may introduce new +features, but are not allowed to break compatibility. Bug fix releases (such as 2.1.2, +3.0.1) do not add new features, but fix bugs or enhance performance only. + +.. note:: + + Deprecations are removed with the next major version of the framework. + It is advised to early on adapt your code already each minor as outlined in + the deprecation comments and the migration guides. + +To clarify what changes you can expect in each release tier we have more +detailed information for developers using CakePHP, and for developers working on +CakePHP that helps set expectations of what can be done in minor releases. Major +releases can have as many breaking changes as required. + +Migration Guides +================ + +For each major and minor release, the CakePHP team will provide a migration +guide. These guides explain the new features and any breaking changes that are +in each release. They can be found in the :doc:`/appendices` section of the +cookbook. + +Using CakePHP +============= + +If you are building your application with CakePHP, the following guidelines +explain the stability you can expect. + +Interfaces +---------- + +Outside of major releases, interfaces provided by CakePHP will **not** have any +existing methods changed. New methods may be added, but no existing methods will +be changed. + +Classes +------- + +Classes provided by CakePHP can be constructed and have their public methods and +properties used by application code and outside of major releases backwards +compatibility is ensured. + +.. note:: + + Some classes in CakePHP are marked with the ``@internal`` API doc tag. These + classes are **not** stable and do not have any backwards compatibility + promises. + +In minor releases, new methods may be added to classes, and existing methods may +have new arguments added. Any new arguments will have default values, but if +you've overridden methods with a differing signature you may see fatal errors. +Methods that have new arguments added will be documented in the migration guide +for that release. + +The following table outlines several use cases and what compatibility you can +expect from CakePHP: + ++-------------------------------+--------------------------+ +| If you... | Backwards compatibility? | ++===============================+==========================+ +| Typehint against the class | Yes | ++-------------------------------+--------------------------+ +| Create a new instance | Yes | ++-------------------------------+--------------------------+ +| Extend the class | Yes | ++-------------------------------+--------------------------+ +| Access a public property | Yes | ++-------------------------------+--------------------------+ +| Call a public method | Yes | ++-------------------------------+--------------------------+ +| **Extend a class and...** | ++-------------------------------+--------------------------+ +| Override a public property | Yes | ++-------------------------------+--------------------------+ +| Access a protected property | No [1]_ | ++-------------------------------+--------------------------+ +| Override a protected property | No [1]_ | ++-------------------------------+--------------------------+ +| Override a protected method | No [1]_ | ++-------------------------------+--------------------------+ +| Call a protected method | No [1]_ | ++-------------------------------+--------------------------+ +| Add a public property | No | ++-------------------------------+--------------------------+ +| Add a public method | No | ++-------------------------------+--------------------------+ +| Add an argument | No [1]_ | +| to an overridden method | | ++-------------------------------+--------------------------+ +| Add a default argument value | Yes | +| to an existing method | | +| argument | | ++-------------------------------+--------------------------+ + +Working on CakePHP +================== + +If you are helping make CakePHP even better please keep the following guidelines +in mind when adding/changing functionality: + +In a minor release you can: + ++-------------------------------+--------------------------+ +| In a minor release can you... | ++===============================+==========================+ +| **Classes** | ++-------------------------------+--------------------------+ +| Remove a class | No | ++-------------------------------+--------------------------+ +| Remove an interface | No | ++-------------------------------+--------------------------+ +| Remove a trait | No | ++-------------------------------+--------------------------+ +| Make final | No | ++-------------------------------+--------------------------+ +| Make abstract | No | ++-------------------------------+--------------------------+ +| Change name | Yes [2]_ | ++-------------------------------+--------------------------+ +| **Properties** | ++-------------------------------+--------------------------+ +| Add a public property | Yes | ++-------------------------------+--------------------------+ +| Remove a public property | No | ++-------------------------------+--------------------------+ +| Add a protected property | Yes | ++-------------------------------+--------------------------+ +| Remove a protected property | Yes [3]_ | ++-------------------------------+--------------------------+ +| **Methods** | ++-------------------------------+--------------------------+ +| Add a public method | Yes | ++-------------------------------+--------------------------+ +| Remove a public method | No | ++-------------------------------+--------------------------+ +| Add a protected method | Yes | ++-------------------------------+--------------------------+ +| Move to parent class | Yes | ++-------------------------------+--------------------------+ +| Remove a protected method | Yes [3]_ | ++-------------------------------+--------------------------+ +| Reduce visibility | No | ++-------------------------------+--------------------------+ +| Change method name | Yes [2]_ | ++-------------------------------+--------------------------+ +| Add a new argument with | Yes | +| default value | | ++-------------------------------+--------------------------+ +| Add a new required argument | No | +| to an existing method. | | ++-------------------------------+--------------------------+ +| Remove a default value from | No | +| an existing argument | | ++-------------------------------+--------------------------+ +| Change method type void | Yes | ++-------------------------------+--------------------------+ + +.. [1] Your code *may* be broken by minor releases. Check the migration guide + for details. +.. [2] You can change a class/method name as long as the old name remains + available. This is generally avoided unless renaming has significant + benefit. +.. [3] Avoid whenever possible. Any removals need to be documented in + the migration guide. + +Deprecations +============ + +In each minor release, features may be deprecated. If features are deprecated, +API documentation and runtime warnings will be added. Runtime errors help you +locate code that needs to be updated before it breaks. If you wish to disable +runtime warnings you can do so using the ``Error.errorLevel`` configuration +value:: + + // in config/app.php + // ... + 'Error' => [ + 'errorLevel' => E_ALL ^ E_USER_DEPRECATED, + ] + // ... + +Will disable runtime deprecation warnings. diff --git a/tl/contributing/cakephp-coding-conventions.rst b/tl/contributing/cakephp-coding-conventions.rst new file mode 100644 index 0000000000000000000000000000000000000000..17feeaf39e2a5917abb4c5d3dc6fc8aa42da5af8 --- /dev/null +++ b/tl/contributing/cakephp-coding-conventions.rst @@ -0,0 +1,616 @@ +Coding Standards +################ + +CakePHP developers will use the `PSR-2 coding style guide +`_ in addition to the following rules as +coding standards. + +It is recommended that others developing CakeIngredients follow the same +standards. + +You can use the `CakePHP Code Sniffer +`_ to check that your code +follows required standards. + +Adding New Features +=================== + +No new features should be added, without having their own tests – which +should be passed before committing them to the repository. + +IDE Setup +========= + +Please make sure your IDE is set up to "trim right" on whitespaces. +There should be no trailing spaces per line. + +Most modern IDEs also support an ``.editorconfig`` file. The CakePHP app +skeleton ships with it by default. It already contains best practise defaults. + +We recommend to use the `IdeHelper `_ plugin if you +want to maximize IDE compatibility. It will assist to keep the annotations up-to-date which will make +the IDE fully understand how all classes work together and provides better type-hinting and auto-completion. + +Indentation +=========== + +Four spaces will be used for indentation. + +So, indentation should look like this:: + + // base level + // level 1 + // level 2 + // level 1 + // base level + +Or:: + + $booleanVariable = true; + $stringVariable = 'moose'; + if ($booleanVariable) { + echo 'Boolean value is true'; + if ($stringVariable === 'moose') { + echo 'We have encountered a moose'; + } + } + +In cases where you're using a multi-line function call use the following +guidelines: + +* Opening parenthesis of a multi-line function call must be the last content on + the line. +* Only one argument is allowed per line in a multi-line function call. +* Closing parenthesis of a multi-line function call must be on a line by itself. + +As an example, instead of using the following formatting:: + + $matches = array_intersect_key($this->_listeners, + array_flip(preg_grep($matchPattern, + array_keys($this->_listeners), 0))); + +Use this instead:: + + $matches = array_intersect_key( + $this->_listeners, + array_flip( + preg_grep($matchPattern, array_keys($this->_listeners), 0) + ) + ); + +Line Length +=========== + +It is recommended to keep lines at approximately 100 characters long for better +code readability. A limit of 80 or 120 characters makes it necessary to +distribute complex logic or expressions by function, as well as give functions +and objects shorter, more expressive names. Lines must not be +longer than 120 characters. + +In short: + +* 100 characters is the soft limit. +* 120 characters is the hard limit. + +Control Structures +================== + +Control structures are for example "``if``", "``for``", "``foreach``", +"``while``", "``switch``" etc. Below, an example with "``if``":: + + if ((expr_1) || (expr_2)) { + // action_1; + } elseif (!(expr_3) && (expr_4)) { + // action_2; + } else { + // default_action; + } + +* In the control structures there should be 1 (one) space before the first + parenthesis and 1 (one) space between the last parenthesis and the opening + bracket. +* Always use curly brackets in control structures, even if they are not needed. + They increase the readability of the code, and they give you fewer logical + errors. +* Opening curly brackets should be placed on the same line as the control + structure. Closing curly brackets should be placed on new lines, and they + should have same indentation level as the control structure. The statement + included in curly brackets should begin on a new line, and code contained + within it should gain a new level of indentation. +* Inline assignments should not be used inside of the control structures. + +:: + + // wrong = no brackets, badly placed statement + if (expr) statement; + + // wrong = no brackets + if (expr) + statement; + + // good + if (expr) { + statement; + } + + // wrong = inline assignment + if ($variable = Class::function()) { + statement; + } + + // good + $variable = Class::function(); + if ($variable) { + statement; + } + +Ternary Operator +---------------- + +Ternary operators are permissible when the entire ternary operation fits on one +line. Longer ternaries should be split into ``if else`` statements. Ternary +operators should not ever be nested. Optionally parentheses can be used around +the condition check of the ternary for clarity:: + + // Good, simple and readable + $variable = isset($options['variable']) ? $options['variable'] : true; + + // Nested ternaries are bad + $variable = isset($options['variable']) ? isset($options['othervar']) ? true : false : false; + +Template Files +-------------- + +In template files (.ctp files) developers should use keyword control structures. +Keyword control structures are easier to read in complex template files. Control +structures can either be contained in a larger PHP block, or in separate PHP +tags:: + + You are the admin user.

    '; + endif; + ?> +

    The following is also acceptable:

    + +

    You are the admin user.

    + + +Comparison +========== + +Always try to be as strict as possible. If a non-strict test is deliberate it +might be wise to comment it as such to avoid confusing it for a mistake. + +For testing if a variable is null, it is recommended to use a strict check:: + + if ($value === null) { + // ... + } + +The value to check against should be placed on the right side:: + + // not recommended + if (null === $this->foo()) { + // ... + } + + // recommended + if ($this->foo() === null) { + // ... + } + +Function Calls +============== + +Functions should be called without space between function's name and starting +parenthesis. There should be one space between every parameter of a function +call:: + + $var = foo($bar, $bar2, $bar3); + +As you can see above there should be one space on both sides of equals sign (=). + +Method Definition +================= + +Example of a method definition:: + + public function someFunction($arg1, $arg2 = '') + { + if (expr) { + statement; + } + + return $var; + } + +Parameters with a default value, should be placed last in function definition. +Try to make your functions return something, at least ``true`` or ``false``, so +it can be determined whether the function call was successful:: + + public function connection($dns, $persistent = false) + { + if (is_array($dns)) { + $dnsInfo = $dns; + } else { + $dnsInfo = BD::parseDNS($dns); + } + + if (!($dnsInfo) || !($dnsInfo['phpType'])) { + return $this->addError(); + } + + return true; + } + +There are spaces on both side of the equals sign. + +Typehinting +----------- + +Arguments that expect objects, arrays or callbacks (callable) can be typehinted. +We only typehint public methods, though, as typehinting is not cost-free:: + + /** + * Some method description. + * + * @param \Cake\ORM\Table $table The table class to use. + * @param array $array Some array value. + * @param callable $callback Some callback. + * @param bool $boolean Some boolean value. + */ + public function foo(Table $table, array $array, callable $callback, $boolean) + { + } + +Here ``$table`` must be an instance of ``\Cake\ORM\Table``, ``$array`` must be +an ``array`` and ``$callback`` must be of type ``callable`` (a valid callback). + +Note that if you want to allow ``$array`` to be also an instance of +``\ArrayObject`` you should not typehint as ``array`` accepts only the primitive +type:: + + /** + * Some method description. + * + * @param array|\ArrayObject $array Some array value. + */ + public function foo($array) + { + } + +Anonymous Functions (Closures) +------------------------------ + +Defining anonymous functions follows the `PSR-2 +`_ coding style guide, where they are +declared with a space after the `function` keyword, and a space before and after +the `use` keyword:: + + $closure = function ($arg1, $arg2) use ($var1, $var2) { + // code + }; + +Method Chaining +=============== + +Method chaining should have multiple methods spread across separate lines, and +indented with four spaces:: + + $email->from('foo@example.com') + ->to('bar@example.com') + ->subject('A great message') + ->send(); + +Commenting Code +=============== + +All comments should be written in English, and should in a clear way describe +the commented block of code. + +Comments can include the following `phpDocumentor `_ +tags: + +* `@author `_ +* `@copyright `_ +* `@deprecated `_ + Using the ``@version `` format, where ``version`` + and ``description`` are mandatory. Version refers to the one it got deprecated in. +* `@example `_ +* `@ignore `_ +* `@internal `_ +* `@link `_ +* `@see `_ +* `@since `_ +* `@version `_ + +PhpDoc tags are very much like JavaDoc tags in Java. Tags are only processed if +they are the first thing in a DocBlock line, for example:: + + /** + * Tag example. + * + * @author this tag is parsed, but this @version is ignored + * @version 1.0 this tag is also parsed + */ + +:: + + /** + * Example of inline phpDoc tags. + * + * This function works hard with foo() to rule the world. + * + * @return void + */ + function bar() + { + } + + /** + * Foo function. + * + * @return void + */ + function foo() + { + } + +Comment blocks, with the exception of the first block in a file, should always +be preceded by a newline. + +Variable Types +-------------- + +Variable types for use in DocBlocks: + +Type + Description +mixed + A variable with undefined (or multiple) type. +int + Integer type variable (whole number). +float + Float type (point number). +bool + Logical type (true or false). +string + String type (any value in " " or ' '). +null + Null type. Usually used in conjunction with another type. +array + Array type. +object + Object type. A specific class name should be used if possible. +resource + Resource type (returned by for example mysql\_connect()). + Remember that when you specify the type as mixed, you should indicate + whether it is unknown, or what the possible types are. +callable + Callable function. + +You can also combine types using the pipe char:: + + int|bool + +For more than two types it is usually best to just use ``mixed``. + +When returning the object itself, e.g. for chaining, one should use ``$this`` +instead:: + + /** + * Foo function. + * + * @return $this + */ + public function foo() + { + return $this; + } + +Including Files +=============== + +``include``, ``require``, ``include_once`` and ``require_once`` do not have +parentheses:: + + // wrong = parentheses + require_once('ClassFileName.php'); + require_once ($class); + + // good = no parentheses + require_once 'ClassFileName.php'; + require_once $class; + +When including files with classes or libraries, use only and always the +`require\_once `_ function. + +PHP Tags +======== + +Always use long tags (````) instead of short tags (````). The short +echo should be used in template files (**.ctp**) where appropriate. + +Short Echo +---------- + +The short echo should be used in template files in place of `` + + // good = spaces, no semicolon + + +As of PHP 5.4 the short echo tag (```_ +* FTP: `ftp://ftp.example.com `_ + +The "example.com" domain name has been reserved for this (see :rfc:`2606`) and +is recommended for use in documentation or as examples. + +Files +----- + +File names which do not contain classes should be lowercased and underscored, +for example:: + + long_file_name.php + +Casting +------- + +For casting we use: + +Type + Description +(bool) + Cast to boolean. +(int) + Cast to integer. +(float) + Cast to float. +(string) + Cast to string. +(array) + Cast to array. +(object) + Cast to object. + +Please use ``(int)$var`` instead of ``intval($var)`` and ``(float)$var`` instead +of ``floatval($var)`` when applicable. + +Constants +--------- + +Constants should be defined in capital letters:: + + define('CONSTANT', 1); + +If a constant name consists of multiple words, they should be separated by an +underscore character, for example:: + + define('LONG_NAMED_CONSTANT', 2); + +Careful when using empty()/isset() +================================== + +While ``empty()`` is an easy to use function, it can mask errors and cause +unintended effects when ``'0'`` and ``0`` are given. When variables or +properties are already defined, the usage of ``empty()`` is not recommended. +When working with variables, it is better to rely on type-coercion to boolean +instead of ``empty()``:: + + function manipulate($var) + { + // Not recommended, $var is already defined in the scope + if (empty($var)) { + // ... + } + + // Use boolean type coercion + if (!$var) { + // ... + } + if ($var) { + // ... + } + } + +When dealing with defined properties you should favour ``null`` checks over +``empty()``/``isset()`` checks:: + + class Thing + { + private $property; // Defined + + public function readProperty() + { + // Not recommended as the property is defined in the class + if (!isset($this->property)) { + // ... + } + // Recommended + if ($this->property === null) { + + } + } + } + +When working with arrays, it is better to merge in defaults over using +``empty()`` checks. By merging in defaults, you can ensure that required keys +are defined:: + + function doWork(array $array) + { + // Merge defaults to remove need for empty checks. + $array += [ + 'key' => null, + ]; + + // Not recommended, the key is already set + if (isset($array['key'])) { + // ... + } + + // Recommended + if ($array['key'] !== null) { + // ... + } + } + +.. meta:: + :title lang=en: Coding Standards + :keywords lang=en: curly brackets,indentation level,logical errors,control structures,control structure,expr,coding standards,parenthesis,foreach,readability,moose,new features,repository,developers diff --git a/tl/contributing/code.rst b/tl/contributing/code.rst new file mode 100644 index 0000000000000000000000000000000000000000..32f594b014be42eb5255bcd32bed9f3da633cf3b --- /dev/null +++ b/tl/contributing/code.rst @@ -0,0 +1,146 @@ +Code +#### + +Patches and pull requests are a great way to contribute code back to CakePHP. +Pull requests can be created in GitHub, and are preferred over patch files in +ticket comments. + +Initial Setup +============= + +Before working on patches for CakePHP, it's a good idea to get your environment +setup. You'll need the following software: + +* Git +* PHP |minphpversion| or greater +* PHPUnit 5.7.0 or greater + +Set up your user information with your name/handle and working email address:: + + git config --global user.name 'Bob Barker' + git config --global user.email 'bob.barker@example.com' + +.. note:: + + If you are new to Git, we highly recommend you to read the excellent and + free `ProGit `_ book. + +Get a clone of the CakePHP source code from GitHub: + +* If you don't have a `GitHub `_ account, create one. +* Fork the `CakePHP repository `_ by clicking + the **Fork** button. + +After your fork is made, clone your fork to your local machine:: + + git clone git@github.com:YOURNAME/cakephp.git + +Add the original CakePHP repository as a remote repository. You'll use this +later to fetch changes from the CakePHP repository. This will let you stay up +to date with CakePHP:: + + cd cakephp + git remote add upstream git://github.com/cakephp/cakephp.git + +Now that you have CakePHP setup you should be able to define a ``$test`` +:ref:`database connection `, and +:ref:`run all the tests `. + +Working on a Patch +================== + +Each time you want to work on a bug, feature or enhancement create a topic +branch. + +The branch you create should be based on the version that your fix/enhancement +is for. For example if you are fixing a bug in ``3.x`` you would want to use the +``master`` branch as the base for your branch. If your change is a bug fix for +the 2.x release series, you should use the ``2.x`` branch. This makes merging +your changes in later much simpler, as Github does not let you edit the target +branch:: + + # fixing a bug on 3.x + git fetch upstream + git checkout -b ticket-1234 upstream/master + + # fixing a bug on 2.x + git fetch upstream + git checkout -b ticket-1234 upstream/2.x + +.. tip:: + + Use a descriptive name for your branch, referencing the ticket or feature + name is a good convention. e.g. ticket-1234, feature-awesome + +The above will create a local branch based on the upstream (CakePHP) 2.x branch. +Work on your fix, and make as many commits as you need; but keep in mind the +following: + +* Follow the :doc:`/contributing/cakephp-coding-conventions`. +* Add a test case to show the bug is fixed, or that the new feature works. +* Keep your commits logical, and write good clear and concise commit messages. + +Submitting a Pull Request +========================= + +Once your changes are done and you're ready for them to be merged into CakePHP, +you'll want to update your branch:: + + # Rebase fix on top of master + git checkout master + git fetch upstream + git merge upstream/master + git checkout + git rebase master + +This will fetch + merge in any changes that have happened in CakePHP since you +started. It will then rebase - or replay your changes on top of the current +code. You might encounter a conflict during the ``rebase``. If the rebase quits +early you can see which files are conflicted/un-merged with ``git status``. +Resolve each conflict, and then continue the rebase:: + + git add # do this for each conflicted file. + git rebase --continue + +Check that all your tests continue to pass. Then push your branch to your fork:: + + git push origin + +If you've rebased after pushing your branch, you'll need to use force push:: + + git push --force origin + +Once your branch is on GitHub, you can submit a pull request on GitHub. + +Choosing Where Your Changes will be Merged Into +----------------------------------------------- + +When making pull requests you should make sure you select the correct base +branch, as you cannot edit it once the pull request is created. + +* If your change is a **bugfix** and doesn't introduce new functionality and + only corrects existing behavior that is present in the current release. Then + choose **master** as your merge target. +* If your change is a **new feature** or an addition to the framework, then you + should choose the branch with the next version number. For example if the + current stable release is ``3.2.10``, the branch accepting new features will + be ``3.next``. +* If your change is a breaks existing functionality, or API's then you'll have + to choose then next major release. For example, if the current release is + ``3.2.2`` then the next time existing behavior can be broken will be in + ``4.x`` so you should target that branch. + +.. note:: + + Remember that all code you contribute to CakePHP will be licensed under the + MIT License, and the `Cake Software Foundation + `_ will become the owner of any + contributed code. Contributors should follow the `CakePHP Community + Guidelines `_. + +All bug fixes merged into a maintenance branch will also be merged into upcoming +releases periodically by the core team. + +.. meta:: + :title lang=en: Code + :keywords lang=en: cakephp source code,code patches,test ref,descriptive name,bob barker,initial setup,global user,database connection,clone,repository,user information,enhancement,back patches,checkout diff --git a/tl/contributing/documentation.rst b/tl/contributing/documentation.rst new file mode 100644 index 0000000000000000000000000000000000000000..2145dab58de31dee17338cbcdad1b5f7590465b8 --- /dev/null +++ b/tl/contributing/documentation.rst @@ -0,0 +1,483 @@ +Documentation +############# + +Contributing to the documentation is simple. The files are hosted on +https://github.com/cakephp/docs. Feel free to fork the repo, add your +changes/improvements/translations and give back by issuing a pull request. +You can even edit the docs online with GitHub, without ever downloading the +files -- the "Improve this Doc" button on any given page will direct you to +GitHub's online editor for that page. + +CakePHP documentation is +`continuously integrated `_, +and deployed after each pull request is merged. + +Translations +============ + +Email the docs team (docs at cakephp dot org) or hop on IRC +(#cakephp on freenode) to discuss any translation efforts you would +like to participate in. + +New Translation Language +------------------------ + +We want to provide translations that are as complete as possible. However, there +may be times where a translation file is not up-to-date. You should always +consider the English version as the authoritative version. + +If your language is not in the current languages, please contact us through +Github and we will consider creating a skeleton folder for it. The following +sections are the first one you should consider translating as these +files don't change often: + +- index.rst +- intro.rst +- quickstart.rst +- installation.rst +- /intro folder +- /tutorials-and-examples folder + +Reminder for Docs Administrators +-------------------------------- + +The structure of all language folders should mirror the English folder +structure. If the structure changes for the English version, we should apply +those changes in the other languages. + +For example, if a new English file is created in **en/file.rst**, we should: + +- Add the file in all other languages : **fr/file.rst**, **zh/file.rst**, ... +- Delete the content, but keeping the ``title``, ``meta`` information and + eventual ``toc-tree`` elements. The following note will be added while nobody + has translated the file:: + + File Title + ########## + + .. note:: + The documentation is not currently supported in XX language for this + page. + + Please feel free to send us a pull request on + `Github `_ or use the **Improve This Doc** + button to directly propose your changes. + + You can refer to the English version in the select top menu to have + information about this page's topic. + + // If toc-tree elements are in the English version + .. toctree:: + :maxdepth: 1 + + one-toc-file + other-toc-file + + .. meta:: + :title lang=xx: File Title + :keywords lang=xx: title, description,... + +Translator tips +--------------- + +- Browse and edit in the language you want the content to be + translated to - otherwise you won't see what has already been + translated. +- Feel free to dive right in if your chosen language already + exists on the book. +- Use `Informal Form `_. +- Translate both the content and the title at the same time. +- Do compare to the English content before submitting a correction + (if you correct something, but don't integrate an 'upstream' change + your submission won't be accepted). +- If you need to write an English term, wrap it in ```` tags. + E.g. "asdf asdf *Controller* asdf" or "asdf asdf Kontroller + (*Controller*) asfd" as appropriate. +- Do not submit partial translations. +- Do not edit a section with a pending change. +- Do not use + `HTML entities `_ + for accented characters, the book uses UTF-8. +- Do not significantly change the markup (HTML) or add new content. +- If the original content is missing some info, submit an edit for + that first. + +Documentation Formatting Guide +============================== + +The new CakePHP documentation is written with +`ReST formatted text `_. ReST +(Re Structured Text) is a plain text markup syntax similar to markdown, or +textile. To maintain consistency it is recommended that when adding to the +CakePHP documentation you follow the guidelines here on how to format and +structure your text. + +Line Length +----------- + +Lines of text should be wrapped at 80 columns. The only exception should be +long URLs, and code snippets. + +Headings and Sections +--------------------- + +Section headers are created by underlining the title with punctuation characters +at least the length of the text. + +- ``#`` Is used to denote page titles. +- ``=`` Is used for sections in a page. +- ``-`` Is used for subsections. +- ``~`` Is used for sub-subsections. +- ``^`` Is used for sub-sub-subsections. + +Headings should not be nested more than 5 levels deep. Headings should be +preceded and followed by a blank line. + +Paragraphs +---------- + +Paragraphs are simply blocks of text, with all the lines at the same level of +indentation. Paragraphs should be separated by one blank line. + +Inline Markup +------------- + +* One asterisk: *text* for emphasis (italics) + We'll use it for general highlighting/emphasis. + + * ``*text*``. + +* Two asterisks: **text** for strong emphasis (boldface) + We'll use it for working directories, bullet list subject, table names and + excluding the following word "table". + + * ``**/config/Migrations**``, ``**articles**``, etc. + +* Two backquotes: ``text`` for code samples + We'll use it for names of method options, names of table columns, object + names, excluding the following word "object" and for method/function + names -- include "()". + + * ````cascadeCallbacks````, ````true````, ````id````, + ````PagesController````, ````config()````, etc. + +If asterisks or backquotes appear in running text and could be confused with +inline markup delimiters, they have to be escaped with a backslash. + +Inline markup has a few restrictions: + +* It **may not** be nested. +* Content may not start or end with whitespace: ``* text*`` is wrong. +* Content must be separated from surrounding text by non-word characters. Use a + backslash escaped space to work around that: ``onelong\ *bolded*\ word``. + +Lists +----- + +List markup is very similar to markdown. Unordered lists are indicated by +starting a line with a single asterisk and a space. Numbered lists can be +created with either numerals, or ``#`` for auto numbering:: + + * This is a bullet + * So is this. But this line + has two lines. + + 1. First line + 2. Second line + + #. Automatic numbering + #. Will save you some time. + +Indented lists can also be created, by indenting sections and separating them +with an empty line:: + + * First line + * Second line + + * Going deeper + * Whoah + + * Back to the first level. + +Definition lists can be created by doing the following:: + + term + definition + CakePHP + An MVC framework for PHP + +Terms cannot be more than one line, but definitions can be multi-line and all +lines should be indented consistently. + +Links +----- + +There are several kinds of links, each with their own uses. + +External Links +~~~~~~~~~~~~~~ + +Links to external documents can be done with the following:: + + `External Link to php.net `_ + +The resulting link would look like this: `External Link to php.net `_ + +Links to Other Pages +~~~~~~~~~~~~~~~~~~~~ + +.. rst:role:: doc + + Other pages in the documentation can be linked to using the ``:doc:`` role. + You can link to the specified document using either an absolute or relative + path reference. You should omit the ``.rst`` extension. For example, if + the reference ``:doc:`form``` appears in the document ``core-helpers/html``, + then the link references ``core-helpers/form``. If the reference was + ``:doc:`/core-helpers```, it would always reference ``/core-helpers`` + regardless of where it was used. + +Cross Referencing Links +~~~~~~~~~~~~~~~~~~~~~~~ + +.. rst:role:: ref + + You can cross reference any arbitrary title in any document using the + ``:ref:`` role. Link label targets must be unique across the entire + documentation. When creating labels for class methods, it's best to use + ``class-method`` as the format for your link label. + + The most common use of labels is above a title. Example:: + + .. _label-name: + + Section heading + --------------- + + More content here. + + Elsewhere you could reference the above section using ``:ref:`label-name```. + The link's text would be the title that the link preceded. You can also + provide custom link text using ``:ref:`Link text ```. + +Prevent Sphinx to Output Warnings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sphinx will output warnings if a file is not referenced in a toc-tree. It's +a great way to ensure that all files have a link directed to them, but +sometimes, you don't need to insert a link for a file, eg. for our +`epub-contents` and `pdf-contents` files. In those cases, you can add +``:orphan:`` at the top of the file, to suppress warnings that the file is not +in the toc-tree. + +Describing Classes and their Contents +------------------------------------- + +The CakePHP documentation uses the `phpdomain +`_ to provide custom +directives for describing PHP objects and constructs. Using these directives +and roles is required to give proper indexing and cross referencing features. + +Describing Classes and Constructs +--------------------------------- + +Each directive populates the index, and or the namespace index. + +.. rst:directive:: .. php:global:: name + + This directive declares a new PHP global variable. + +.. rst:directive:: .. php:function:: name(signature) + + Defines a new global function outside of a class. + +.. rst:directive:: .. php:const:: name + + This directive declares a new PHP constant, you can also use it nested + inside a class directive to create class constants. + +.. rst:directive:: .. php:exception:: name + + This directive declares a new Exception in the current namespace. The + signature can include constructor arguments. + +.. rst:directive:: .. php:class:: name + + Describes a class. Methods, attributes, and constants belonging to the class + should be inside this directive's body:: + + .. php:class:: MyClass + + Class description + + .. php:method:: method($argument) + + Method description + + Attributes, methods and constants don't need to be nested. They can also just + follow the class declaration:: + + .. php:class:: MyClass + + Text about the class + + .. php:method:: methodName() + + Text about the method + + .. seealso:: :rst:dir:`php:method`, :rst:dir:`php:attr`, :rst:dir:`php:const` + +.. rst:directive:: .. php:method:: name(signature) + + Describe a class method, its arguments, return value, and exceptions:: + + .. php:method:: instanceMethod($one, $two) + + :param string $one: The first parameter. + :param string $two: The second parameter. + :returns: An array of stuff. + :throws: InvalidArgumentException + + This is an instance method. + +.. rst:directive:: .. php:staticmethod:: ClassName::methodName(signature) + + Describe a static method, its arguments, return value and exceptions, + see :rst:dir:`php:method` for options. + +.. rst:directive:: .. php:attr:: name + + Describe an property/attribute on a class. + +Prevent Sphinx to Output Warnings +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Sphinx will output warnings if a function is referenced in multiple files. It's +a great way to ensure that you did not add a function two times, but +sometimes, you actually want to write a function in two or more files, eg. +`debug object` is referenced in `/development/debugging` and in +`/core-libraries/global-constants-and-functions`. In this case, you can add +``:noindex:`` under the function debug to suppress warnings. Keep only +one reference **without** ``:no-index:`` to still have the function referenced:: + + .. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) + :noindex: + +Cross Referencing +~~~~~~~~~~~~~~~~~ + +The following roles refer to PHP objects and links are generated if a +matching directive is found: + +.. rst:role:: php:func + + Reference a PHP function. + +.. rst:role:: php:global + + Reference a global variable whose name has ``$`` prefix. + +.. rst:role:: php:const + + Reference either a global constant, or a class constant. Class constants + should be preceded by the owning class:: + + DateTime has an :php:const:`DateTime::ATOM` constant. + +.. rst:role:: php:class + + Reference a class by name:: + + :php:class:`ClassName` + +.. rst:role:: php:meth + + Reference a method of a class. This role supports both kinds of methods:: + + :php:meth:`DateTime::setDate` + :php:meth:`Classname::staticMethod` + +.. rst:role:: php:attr + + Reference a property on an object:: + + :php:attr:`ClassName::$propertyName` + +.. rst:role:: php:exc + + Reference an exception. + +Source Code +----------- + +Literal code blocks are created by ending a paragraph with ``::``. The literal +block must be indented, and like all paragraphs be separated by single lines:: + + This is a paragraph:: + + while ($i--) { + doStuff() + } + + This is regular text again. + +Literal text is not modified or formatted, save that one level of indentation +is removed. + +Notes and Warnings +------------------ + +There are often times when you want to inform the reader of an important tip, +special note or a potential hazard. Admonitions in sphinx are used for just +that. There are fives kinds of admonitions. + +* ``.. tip::`` Tips are used to document or re-iterate interesting or important + information. The content of the directive should be written in complete + sentences and include all appropriate punctuation. +* ``.. note::`` Notes are used to document an especially important piece of + information. The content of the directive should be written in complete + sentences and include all appropriate punctuation. +* ``.. warning::`` Warnings are used to document potential stumbling blocks, or + information pertaining to security. The content of the directive should be + written in complete sentences and include all appropriate punctuation. +* ``.. versionadded:: X.Y.Z`` "Version added" admonitions are used to display notes + specific to new features added at a specific version, ``X.Y.Z`` being the version on + which the said feature was added. +* ``.. deprecated:: X.Y.Z`` As opposed to "version added" admonitions, "deprecated" + admonition are used to notify of a deprecated feature, ``X.Y.Z`` being the version on + which the said feature was deprecated. + +All admonitions are made the same:: + + .. note:: + + Indented and preceded and followed by a blank line. Just like a + paragraph. + + This text is not part of the note. + +Samples +~~~~~~~ + +.. tip:: + + This is a helpful tid-bit you probably forgot. + +.. note:: + + You should pay attention here. + +.. warning:: + + It could be dangerous. + +.. versionadded:: 2.6.3 + + This awesome feature was added on version 2.6.3 + +.. deprecated:: 2.6.3 + + This old feature was deprecated on version 2.6.3 + +.. meta:: + :title lang=en: Documentation + :keywords lang=en: partial translations,translation efforts,html entities,text markup,asfd,asdf,structured text,english content,markdown,formatted text,dot org,repo,consistency,translator,freenode,textile,improvements,syntax,cakephp,submission diff --git a/tl/contributing/tickets.rst b/tl/contributing/tickets.rst new file mode 100644 index 0000000000000000000000000000000000000000..fe24b3b48e87ae2b1fd2633bd0b707d14c29bf46 --- /dev/null +++ b/tl/contributing/tickets.rst @@ -0,0 +1,50 @@ +Tickets +####### + +Getting feedback and help from the community in the form of tickets is an +extremely important part of the CakePHP development process. All of CakePHP's +tickets are hosted on `GitHub `_. + +Reporting Bugs +============== + +Well written bug reports are very helpful. There are a few steps to help create +the best bug report possible: + +* **Do**: Please `search `_ + for a similar existing ticket, and ensure someone hasn't already reported your + issue, or that it hasn't already been fixed in the repository. +* **Do**: Please include detailed instructions on **how to reproduce the bug**. + This could be in the form of a test-case or a snippet of code that + demonstrates the issue. Not having a way to reproduce an issue means it's less + likely to get fixed. +* **Do**: Please give as many details as possible about your environment: (OS, + PHP version, CakePHP version). +* **Don't**: Please don't use the ticket system to ask support questions. The + #cakephp IRC channel on `Freenode `__ has many + developers available to help answer your questions. Also have a look at + `Stack Overflow `__. + +Reporting Security Issues +========================= + +If you've found a security issue in CakePHP, please use the following procedure +instead of the normal bug reporting system. Instead of using the bug tracker, +mailing list or IRC please send an email to **security [at] cakephp.org**. +Emails sent to this address go to the CakePHP core team on a private mailing +list. + +For each report, we try to first confirm the vulnerability. Once confirmed, the +CakePHP team will take the following actions: + +* Acknowledge to the reporter that we've received the issue, and are working on + a fix. We ask that the reporter keep the issue confidential until we announce + it. +* Get a fix/patch prepared. +* Prepare a post describing the vulnerability, and the possible exploits. +* Release new versions of all affected versions. +* Prominently feature the problem in the release announcement. + +.. meta:: + :title lang=en: Tickets + :keywords lang=en: bug reporting system,code snippet,reporting security,private mailing,release announcement,google,ticket system,core team,security issue,bug tracker,irc channel,test cases,support questions,bug report,security issues,bug reports,exploits,vulnerability,repository diff --git a/tl/controllers.rst b/tl/controllers.rst new file mode 100644 index 0000000000000000000000000000000000000000..386e011ca6aeceb315c29b3c1debdb851fe79598 --- /dev/null +++ b/tl/controllers.rst @@ -0,0 +1,538 @@ +Controllers +########### + +.. php:namespace:: Cake\Controller + +.. php:class:: Controller + +Controllers are the 'C' in MVC. After routing has been applied and the correct +controller has been found, your controller's action is called. Your controller +should handle interpreting the request data, making sure the correct models +are called, and the right response or view is rendered. Controllers can be +thought of as middle layer between the Model and View. You want to keep your +controllers thin, and your models fat. This will help you reuse +your code and makes your code easier to test. + +Commonly, a controller is used to manage the logic around a single model. For +example, if you were building a site for an online bakery, you might have a +RecipesController managing your recipes and an IngredientsController managing your +ingredients. However, it's also possible to have controllers work with more than +one model. In CakePHP, a controller is named after the primary model it +handles. + +Your application's controllers extend the ``AppController`` class, which in turn +extends the core :php:class:`Controller` class. The ``AppController`` +class can be defined in **src/Controller/AppController.php** and it should +contain methods that are shared between all of your application's controllers. + +Controllers provide a number of methods that handle requests. These are called +*actions*. By default, each public method in +a controller is an action, and is accessible from a URL. An action is responsible +for interpreting the request and creating the response. Usually responses are +in the form of a rendered view, but there are other ways to create responses as +well. + +.. _app-controller: + +The App Controller +================== + +As stated in the introduction, the ``AppController`` class is the parent class +to all of your application's controllers. ``AppController`` itself extends the +:php:class:`Cake\\Controller\\Controller` class included in CakePHP. +``AppController`` is defined in **src/Controller/AppController.php** as +follows:: + + namespace App\Controller; + + use Cake\Controller\Controller; + + class AppController extends Controller + { + } + +Controller attributes and methods created in your ``AppController`` will be +available in all controllers that extend it. Components (which you'll +learn about later) are best used for code that is used in many (but not +necessarily all) controllers. + +You can use your ``AppController`` to load components that will be used in every +controller in your application. CakePHP provides a ``initialize()`` method that +is invoked at the end of a Controller's constructor for this kind of use:: + + namespace App\Controller; + + use Cake\Controller\Controller; + + class AppController extends Controller + { + + public function initialize() + { + // Always enable the CSRF component. + $this->loadComponent('Csrf'); + } + + } + +In addition to the ``initialize()`` method, the older ``$components`` property +will also allow you to declare which components should be loaded. While normal +object-oriented inheritance rules apply, the components and helpers used by +a controller are treated specially. In these cases, ``AppController`` property +values are merged with child controller class arrays. The values in the child +class will always override those in ``AppController``. + +Request Flow +============ + +When a request is made to a CakePHP application, CakePHP's +:php:class:`Cake\\Routing\\Router` and :php:class:`Cake\\Routing\\Dispatcher` +classes use :ref:`routes-configuration` to find and create the correct +controller instance. The request data is encapsulated in a request object. +CakePHP puts all of the important request information into the ``$this->request`` +property. See the section on :ref:`cake-request` for more information on the +CakePHP request object. + +Controller Actions +================== + +Controller actions are responsible for converting the request parameters into a +response for the browser/user making the request. CakePHP uses conventions to +automate this process and remove some boilerplate code you would otherwise need +to write. + +By convention, CakePHP renders a view with an inflected version of the action +name. Returning to our online bakery example, our RecipesController might contain the +``view()``, ``share()``, and ``search()`` actions. The controller would be found +in **src/Controller/RecipesController.php** and contain:: + + // src/Controller/RecipesController.php + + class RecipesController extends AppController + { + public function view($id) + { + // Action logic goes here. + } + + public function share($customerId, $recipeId) + { + // Action logic goes here. + } + + public function search($query) + { + // Action logic goes here. + } + } + +The template files for these actions would be **src/Template/Recipes/view.ctp**, +**src/Template/Recipes/share.ctp**, and **src/Template/Recipes/search.ctp**. The +conventional view file name is the lowercased and underscored version of the +action name. + +Controller actions generally use +``Controller::set()`` to create a context that +``View`` uses to render the view layer. Because of the conventions that +CakePHP uses, you don't need to create and render the view manually. Instead, +once a controller action has completed, CakePHP will handle rendering and +delivering the View. + +If for some reason you'd like to skip the default behavior, you can return a +:php:class:`Cake\\Http\\Response` object from the action with the fully +created response. + +In order for you to use a controller effectively in your own application, we'll +cover some of the core attributes and methods provided by CakePHP's controllers. + +Interacting with Views +====================== + +Controllers interact with views in a number of ways. First, they +are able to pass data to the views, using ``Controller::set()``. You can also +decide which view class to use, and which view file should be +rendered from the controller. + +.. _setting-view_variables: + +Setting View Variables +---------------------- + +.. php:method:: set(string $var, mixed $value) + +The ``Controller::set()`` method is the main way to send data from your +controller to your view. Once you've used ``Controller::set()``, the variable +can be accessed in your view:: + + // First you pass data from the controller: + + $this->set('color', 'pink'); + + // Then, in the view, you can utilize the data: + ?> + + You have selected icing for the cake. + +The ``Controller::set()`` method also takes an +associative array as its first parameter. This can often be a quick way to +assign a set of information to the view:: + + $data = [ + 'color' => 'pink', + 'type' => 'sugar', + 'base_price' => 23.95 + ]; + + // Make $color, $type, and $base_price + // available to the view: + + $this->set($data); + +Setting View Options +-------------------- + +If you want to customize the view class, layout/template paths, helpers or the +theme that will be used when rendering the view, you can use the +``viewBuilder()`` method to get a builder. This builder can be used to define +properties of the view before it is created:: + + $this->viewBuilder() + ->helpers(['MyCustom']) + ->theme('Modern') + ->className('Modern.Admin'); + +The above shows how you can load custom helpers, set the theme and use a custom +view class. + +.. versionadded:: 3.1 + ViewBuilder was added in 3.1 + +Rendering a View +---------------- + +.. php:method:: render(string $view, string $layout) + +The ``Controller::render()`` method is automatically called at the end of each requested +controller action. This method performs all the view logic (using the data +you've submitted using the ``Controller::set()`` method), places the view inside its +``View::$layout``, and serves it back to the end user. + +The default view file used by render is determined by convention. +If the ``search()`` action of the RecipesController is requested, +the view file in **src/Template/Recipes/search.ctp** will be rendered:: + + namespace App\Controller; + + class RecipesController extends AppController + { + // ... + public function search() + { + // Render the view in src/Template/Recipes/search.ctp + $this->render(); + } + // ... + } + +Although CakePHP will automatically call it after every action's logic +(unless you've set ``$this->autoRender`` to ``false``), you can use it to specify +an alternate view file by specifying a view file name as first argument of +``Controller::render()`` method. + +If ``$view`` starts with '/', it is assumed to be a view or +element file relative to the **src/Template** folder. This allows +direct rendering of elements, very useful in AJAX calls:: + + // Render the element in src/Template/Element/ajaxreturn.ctp + $this->render('/Element/ajaxreturn'); + +The second parameter ``$layout`` of ``Controller::render()`` allows you to specify the layout +with which the view is rendered. + +Rendering a Specific Template +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In your controller, you may want to render a different view than the +conventional one. You can do this by calling ``Controller::render()`` directly. Once you +have called ``Controller::render()``, CakePHP will not try to re-render the view:: + + namespace App\Controller; + + class PostsController extends AppController + { + public function my_action() + { + $this->render('custom_file'); + } + } + +This would render **src/Template/Posts/custom_file.ctp** instead of +**src/Template/Posts/my_action.ctp**. + +You can also render views inside plugins using the following syntax: +``$this->render('PluginName.PluginController/custom_file')``. +For example:: + + namespace App\Controller; + + class PostsController extends AppController + { + public function my_action() + { + $this->render('Users.UserDetails/custom_file'); + } + } + +This would render **plugins/Users/src/Template/UserDetails/custom_file.ctp** + +Redirecting to Other Pages +========================== + +.. php:method:: redirect(string|array $url, integer $status) + +The flow control method you'll use most often is ``Controller::redirect()``. +This method takes its first parameter in the form of a +CakePHP-relative URL. When a user has successfully placed an order, +you might wish to redirect him to a receipt screen. :: + + public function place_order() + { + // Logic for finalizing order goes here + if ($success) { + return $this->redirect( + ['controller' => 'Orders', 'action' => 'thanks'] + ); + } + return $this->redirect( + ['controller' => 'Orders', 'action' => 'confirm'] + ); + } + +The method will return the response instance with appropriate headers set. +You should return the response instance from your action to prevent +view rendering and let the dispatcher handle actual redirection. + +You can also use a relative or absolute URL as the $url argument:: + + return $this->redirect('/orders/thanks'); + return $this->redirect('http://www.example.com'); + +You can also pass data to the action:: + + return $this->redirect(['action' => 'edit', $id]); + +The second parameter of ``Controller::redirect()`` allows you to define an HTTP +status code to accompany the redirect. You may want to use 301 +(moved permanently) or 303 (see other), depending on the nature of +the redirect. + +If you need to redirect to the referer page you can use:: + + return $this->redirect($this->referer()); + +An example using query strings and hash would look like:: + + return $this->redirect([ + 'controller' => 'Orders', + 'action' => 'confirm', + '?' => [ + 'product' => 'pizza', + 'quantity' => 5 + ], + '#' => 'top' + ]); + +The generated URL would be:: + + http://www.example.com/orders/confirm?product=pizza&quantity=5#top + +Redirecting to Another Action on the Same Controller +---------------------------------------------------- + +.. php:method:: setAction($action, $args...) + +If you need to forward the current action to a different action on the *same* +controller, you can use ``Controller::setAction()`` to update the request object, modify the +view template that will be rendered and forward execution to the named action:: + + // From a delete action, you can render the updated + // list page. + $this->setAction('index'); + +Loading Additional Models +========================= + +.. php:method:: loadModel(string $modelClass, string $type) + +The ``loadModel()`` function comes handy when you need to use a model +table/collection that is not the controller's default one:: + + // In a controller method. + $this->loadModel('Articles'); + $recentArticles = $this->Articles->find('all', [ + 'limit' => 5, + 'order' => 'Articles.created DESC' + ]); + +If you are using a table provider other than the built-in ORM you can +link that table system into CakePHP's controllers by connecting its +factory method:: + + // In a controller method. + $this->modelFactory( + 'ElasticIndex', + ['ElasticIndexes', 'factory'] + ); + +After registering a table factory, you can use ``loadModel`` to load +instances:: + + // In a controller method. + $this->loadModel('Locations', 'ElasticIndex'); + +.. note:: + + The built-in ORM's TableRegistry is connected by default as the 'Table' + provider. + +Paginating a Model +================== + +.. php:method:: paginate() + +This method is used for paginating results fetched by your models. +You can specify page sizes, model find conditions and more. See the +:doc:`pagination ` section for more details on +how to use ``paginate()``. + +The ``$paginate`` attribute gives you an easy way to customize how ``paginate()`` +behaves:: + + class ArticlesController extends AppController + { + public $paginate = [ + 'Articles' => [ + 'conditions' => ['published' => 1] + ] + ]; + } + +Configuring Components to Load +============================== + +.. php:method:: loadComponent($name, $config = []) + +In your Controller's ``initialize()`` method you can define any components you +want loaded, and any configuration data for them:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Csrf'); + $this->loadComponent('Comments', Configure::read('Comments')); + } + +.. php:attr:: components + +The ``$components`` property on your controllers allows you to configure +components. Configured components and their dependencies will be created by +CakePHP for you. Read the :ref:`configuring-components` section for more +information. As mentioned earlier the ``$components`` property will be merged +with the property defined in each of your controller's parent classes. + +Configuring Helpers to Load +=========================== + +.. php:attr:: helpers + +Let's look at how to tell a CakePHP Controller that you plan to use +additional MVC classes:: + + class RecipesController extends AppController + { + public $helpers = ['Form']; + } + +Each of these variables are merged with their inherited values, +therefore it is not necessary (for example) to redeclare the +``FormHelper``, or anything that is declared in your ``AppController``. + +.. deprecated:: 3.0 + Loading Helpers from the controller is provided for backwards compatibility + reasons. You should see :ref:`configuring-helpers` for how to load helpers. + +.. _controller-life-cycle: + +Request Life-cycle Callbacks +============================ + +CakePHP controllers trigger several events/callbacks that you can use to insert +logic around the request life-cycle: + +Event List +---------- + +* ``Controller.initialize`` +* ``Controller.startup`` +* ``Controller.beforeRedirect`` +* ``Controller.beforeRender`` +* ``Controller.shutdown`` + +Controller Callback Methods +--------------------------- + +By default the following callback methods are connected to related events if the +methods are implemented by your controllers + +.. php:method:: beforeFilter(Event $event) + + Called during the ``Controller.initialize`` event which occurs before every + action in the controller. It's a handy place to check for an active session + or inspect user permissions. + + .. note:: + + The beforeFilter() method will be called for missing actions. + + Returning a response from a ``beforeFilter`` method will not prevent other + listeners of the same event from being called. You must explicitly + :ref:`stop the event `. + +.. php:method:: beforeRender(Event $event) + + Called during the ``Controller.beforeRender`` event which occurs after + controller action logic, but before the view is rendered. This callback is + not used often, but may be needed if you are calling + :php:meth:`~Cake\\Controller\\Controller::render()` manually before the end + of a given action. + +.. php:method:: afterFilter(Event $event) + + Called during the ``Controller.shutdown`` event which is triggered after + every controller action, and after rendering is complete. This is the last + controller method to run. + +In addition to controller life-cycle callbacks, :doc:`/controllers/components` +also provide a similar set of callbacks. + +Remember to call ``AppController``'s callbacks within child controller callbacks +for best results:: + + //use Cake\Event\Event; + public function beforeFilter(Event $event) + { + parent::beforeFilter($event); + } + +More on Controllers +=================== + +.. toctree:: + :maxdepth: 1 + + controllers/pages-controller + controllers/components + +.. meta:: + :title lang=en: Controllers + :keywords lang=en: correct models,controller class,controller controller,core library,single model,request data,middle man,bakery,mvc,attributes,logic,recipes diff --git a/tl/controllers/components.rst b/tl/controllers/components.rst new file mode 100644 index 0000000000000000000000000000000000000000..f14eaa83d79f2c15040e045af9a7a9114fc82a64 --- /dev/null +++ b/tl/controllers/components.rst @@ -0,0 +1,323 @@ +Components +########## + +Components are packages of logic that are shared between controllers. +CakePHP comes with a fantastic set of core components you can use to aid in +various common tasks. You can also create your own components. If you find +yourself wanting to copy and paste things between controllers, you should +consider creating your own component to contain the functionality. Creating +components keeps controller code clean and allows you to reuse code between +different controllers. + +For more information on the components included in CakePHP, check out the +chapter for each component: + +.. toctree:: + :maxdepth: 1 + + /controllers/components/authentication + /controllers/components/cookie + /controllers/components/csrf + /controllers/components/flash + /controllers/components/security + /controllers/components/pagination + /controllers/components/request-handling + +.. _configuring-components: + +Configuring Components +====================== + +Many of the core components require configuration. Some examples of components +requiring configuration are :doc:`/controllers/components/authentication` and +:doc:`/controllers/components/cookie`. Configuration for these components, +and for components in general, is usually done via ``loadComponent()`` in your +Controller's ``initialize()`` method or via the ``$components`` array:: + + class PostsController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth', [ + 'authorize' => 'Controller', + 'loginAction' => ['controller' => 'Users', 'action' => 'login'] + ]); + $this->loadComponent('Cookie', ['expires' => '1 day']); + } + + } + +You can configure components at runtime using the ``config()`` method. Often, +this is done in your controller's ``beforeFilter()`` method. The above could +also be expressed as:: + + public function beforeFilter(Event $event) + { + $this->Auth->config('authorize', ['controller']); + $this->Auth->config('loginAction', ['controller' => 'Users', 'action' => 'login']); + + $this->Cookie->config('name', 'CookieMonster'); + } + +Like helpers, components implement a ``config()`` method that is used to get and +set any configuration data for a component:: + + // Read config data. + $this->Auth->config('loginAction'); + + // Set config + $this->Csrf->config('cookieName', 'token'); + +As with helpers, components will automatically merge their ``$_defaultConfig`` +property with constructor configuration to create the ``$_config`` property +which is accessible with ``config()``. + +Aliasing Components +------------------- + +One common setting to use is the ``className`` option, which allows you to +alias components. This feature is useful when you want to +replace ``$this->Auth`` or another common Component reference with a custom +implementation:: + + // src/Controller/PostsController.php + class PostsController extends AppController + { + public function initialize() + { + $this->loadComponent('Auth', [ + 'className' => 'MyAuth' + ]); + } + } + + // src/Controller/Component/MyAuthComponent.php + use Cake\Controller\Component\AuthComponent; + + class MyAuthComponent extends AuthComponent + { + // Add your code to override the core AuthComponent + } + +The above would *alias* ``MyAuthComponent`` to ``$this->Auth`` in your +controllers. + +.. note:: + + Aliasing a component replaces that instance anywhere that component is used, + including inside other Components. + +Loading Components on the Fly +----------------------------- + +You might not need all of your components available on every controller +action. In situations like this you can load a component at runtime using the +``loadComponent()`` method in your controller:: + + // In a controller action + $this->loadComponent('OneTimer'); + $time = $this->OneTimer->getTime(); + +.. note:: + + Keep in mind that components loaded on the fly will not have missed + callbacks called. If you rely on the ``beforeFilter`` or ``startup`` + callbacks being called, you may need to call them manually depending on when + you load your component. + +Using Components +================ + +Once you've included some components in your controller, using them is pretty +simple. Each component you use is exposed as a property on your controller. If +you had loaded up the :php:class:`Cake\\Controller\\Component\\FlashComponent` +in your controller, you could access it like so:: + + class PostsController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Flash'); + } + + public function delete() + { + if ($this->Post->delete($this->request->getData('Post.id')) { + $this->Flash->success('Post deleted.'); + return $this->redirect(['action' => 'index']); + } + } + +.. note:: + + Since both Models and Components are added to Controllers as + properties they share the same 'namespace'. Be sure to not give a + component and a model the same name. + +.. _creating-a-component: + +Creating a Component +==================== + +Suppose our application needs to perform a complex mathematical operation in +many different parts of the application. We could create a component to house +this shared logic for use in many different controllers. + +The first step is to create a new component file and class. Create the file in +**src/Controller/Component/MathComponent.php**. The basic structure for the +component would look something like this:: + + namespace App\Controller\Component; + + use Cake\Controller\Component; + + class MathComponent extends Component + { + public function doComplexOperation($amount1, $amount2) + { + return $amount1 + $amount2; + } + } + +.. note:: + + All components must extend :php:class:`Cake\\Controller\\Component`. Failing + to do this will trigger an exception. + +Including your Component in your Controllers +-------------------------------------------- + +Once our component is finished, we can use it in the application's +controllers by loading it during the controller's ``initialize()`` method. +Once loaded, the controller will be given a new attribute named after the +component, through which we can access an instance of it:: + + // In a controller + // Make the new component available at $this->Math, + // as well as the standard $this->Csrf + public function initialize() + { + parent::initialize(); + $this->loadComponent('Math'); + $this->loadComponent('Csrf'); + } + +When including Components in a Controller you can also declare a +set of parameters that will be passed on to the Component's +constructor. These parameters can then be handled by +the Component:: + + // In your controller. + public function initialize() + { + parent::initialize(); + $this->loadComponent('Math', [ + 'precision' => 2, + 'randomGenerator' => 'srand' + ]); + $this->loadComponent('Csrf'); + } + +The above would pass the array containing precision and randomGenerator to +``MathComponent::initialize()`` in the ``$config`` parameter. + +Using Other Components in your Component +---------------------------------------- + +Sometimes one of your components may need to use another component. +In this case you can include other components in your component the exact same +way you include them in controllers - using the ``$components`` var:: + + // src/Controller/Component/CustomComponent.php + namespace App\Controller\Component; + + use Cake\Controller\Component; + + class CustomComponent extends Component + { + // The other component your component uses + public $components = ['Existing']; + + // Execute any other additional setup for your component. + public function initialize(array $config) + { + $this->Existing->foo(); + } + + public function bar() + { + // ... + } + } + + // src/Controller/Component/ExistingComponent.php + namespace App\Controller\Component; + + use Cake\Controller\Component; + + class ExistingComponent extends Component + { + + public function foo() + { + // ... + } + } + +.. note:: + + In contrast to a component included in a controller + no callbacks will be triggered on a component's component. + +Accessing a Component's Controller +---------------------------------- + +From within a Component you can access the current controller through the +registry:: + + $controller = $this->_registry->getController(); + +You can access the controller in any callback method from the event +object:: + + $controller = $event->getSubject(); + +Component Callbacks +=================== + +Components also offer a few request life-cycle callbacks that allow them to +augment the request cycle. + +.. php:method:: beforeFilter(Event $event) + + Is called before the controller's + beforeFilter method, but *after* the controller's initialize() method. + +.. php:method:: startup(Event $event) + + Is called after the controller's beforeFilter + method but before the controller executes the current action + handler. + +.. php:method:: beforeRender(Event $event) + + Is called after the controller executes the requested action's logic, + but before the controller renders views and layout. + +.. php:method:: shutdown(Event $event) + + Is called before output is sent to the browser. + +.. php:method:: beforeRedirect(Event $event, $url, Response $response) + + Is invoked when the controller's redirect + method is called but before any further action. If this method + returns ``false`` the controller will not continue on to redirect the + request. The $url, and $response parameters allow you to inspect and modify + the location or any other headers in the response. + +.. meta:: + :title lang=en: Components + :keywords lang=en: array controller,core libraries,authentication request,array name,access control lists,public components,controller code,core components,cookiemonster,login cookie,configuration settings,functionality,logic,sessions,cakephp,doc diff --git a/tl/controllers/components/authentication.rst b/tl/controllers/components/authentication.rst new file mode 100644 index 0000000000000000000000000000000000000000..957756913db3cd5f218decf8249dcd3cf2abd770 --- /dev/null +++ b/tl/controllers/components/authentication.rst @@ -0,0 +1,1091 @@ + +Authentication +############## + +.. php:class:: AuthComponent(ComponentCollection $collection, array $config = []) + +Identifying, authenticating, and authorizing users is a common part of +almost every web application. In CakePHP AuthComponent provides a +pluggable way to do these tasks. AuthComponent allows you to combine +authentication objects and authorization objects to create flexible +ways of identifying and checking user authorization. + +.. _authentication-objects: + +Suggested Reading Before Continuing +=================================== + +Configuring authentication requires several steps including defining +a users table, creating a model, controller & views, etc. + +This is all covered step by step in the +:doc:`CMS Tutorial `. + +If you are looking for existing authentication and/or authorization solutions +for CakePHP, have a look at the +`Authentication and Authorization `_ section of the Awesome CakePHP list. + +Authentication +============== + +Authentication is the process of identifying users by provided +credentials and ensuring that users are who they say they are. +Generally, this is done through a username and password, that are checked +against a known list of users. In CakePHP, there are several built-in +ways of authenticating users stored in your application. + +* ``FormAuthenticate`` allows you to authenticate users based on form POST + data. Usually, this is a login form that users enter information into. +* ``BasicAuthenticate`` allows you to authenticate users using Basic HTTP + authentication. +* ``DigestAuthenticate`` allows you to authenticate users using Digest + HTTP authentication. + +By default ``AuthComponent`` uses ``FormAuthenticate``. + +Choosing an Authentication Type +------------------------------- + +Generally, you'll want to offer form based authentication. It is the easiest for +users using a web-browser to use. If you are building an API or webservice, you +may want to consider basic authentication or digest authentication. The key +differences between digest and basic authentication are mostly related to how +passwords are handled. In basic authentication, the username and password are +transmitted as plain-text to the server. This makes basic authentication +un-suitable for applications without SSL, as you would end up exposing sensitive +passwords. Digest authentication uses a digest hash of the username, password, +and a few other details. This makes digest authentication more appropriate for +applications without SSL encryption. + +You can also use authentication systems like OpenID as well; however, OpenID is +not part of CakePHP core. + +Configuring Authentication Handlers +----------------------------------- + +You configure authentication handlers using the ``authenticate`` config. +You can configure one or many handlers for authentication. Using +multiple handlers allows you to support different ways of logging users +in. When logging users in, authentication handlers are checked in the +order they are declared. Once one handler is able to identify the user, +no other handlers will be checked. Conversely, you can halt all +authentication by throwing an exception. You will need to catch any +thrown exceptions and handle them as needed. + +You can configure authentication handlers in your controller's +``beforeFilter()`` or ``initialize()`` methods. You can pass +configuration information into each authentication object using an +array:: + + // Simple setup + $this->Auth->config('authenticate', ['Form']); + + // Pass settings in + $this->Auth->config('authenticate', [ + 'Basic' => ['userModel' => 'Members'], + 'Form' => ['userModel' => 'Members'] + ]); + +In the second example, you'll notice that we had to declare the +``userModel`` key twice. To help you keep your code DRY, you can use the +``all`` key. This special key allows you to set settings that are passed +to every attached object. The ``all`` key is also exposed as +``AuthComponent::ALL``:: + + // Pass settings in using 'all' + $this->Auth->config('authenticate', [ + AuthComponent::ALL => ['userModel' => 'Members'], + 'Basic', + 'Form' + ]); + +In the above example, both ``Form`` and ``Basic`` will get the settings +defined for the 'all' key. Any settings passed to a specific +authentication object will override the matching key in the 'all' key. +The core authentication objects support the following configuration +keys. + +- ``fields`` The fields to use to identify a user by. You can use keys + ``username`` and ``password`` to specify your username and password fields + respectively. +- ``userModel`` The model name of the users table; defaults to Users. +- ``finder`` The finder method to use to fetch a user record. Defaults to 'all'. +- ``passwordHasher`` Password hasher class; Defaults to ``Default``. +- The ``scope`` and ``contain`` options have been deprecated as of 3.1. Use + a custom finder instead to modify the query to fetch a user record. +- The ``userFields`` option has been deprecated as of 3.1. Use ``select()`` in + your custom finder. + +To configure different fields for user in your ``initialize()`` method:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth', [ + 'authenticate' => [ + 'Form' => [ + 'fields' => ['username' => 'email', 'password' => 'passwd'] + ] + ] + ]); + } + +Do not put other ``Auth`` configuration keys, such as ``authError``, ``loginAction``, etc., +within the ``authenticate`` or ``Form`` element. They should be at the same level as +the authenticate key. The setup above with other Auth configuration +should look like:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth', [ + 'loginAction' => [ + 'controller' => 'Users', + 'action' => 'login', + 'plugin' => 'Users' + ], + 'authError' => 'Did you really think you are allowed to see that?', + 'authenticate' => [ + 'Form' => [ + 'fields' => ['username' => 'email'] + ] + ], + 'storage' => 'Session' + ]); + } + +In addition to the common configuration, Basic authentication supports +the following keys: + +- ``realm`` The realm being authenticated. Defaults to ``env('SERVER_NAME')``. + +In addition to the common configuration Digest authentication supports +the following keys: + +- ``realm`` The realm authentication is for. Defaults to the servername. +- ``nonce`` A nonce used for authentication. Defaults to ``uniqid()``. +- ``qop`` Defaults to auth; no other values are supported at this time. +- ``opaque`` A string that must be returned unchanged by clients. Defaults + to ``md5($config['realm'])``. + +.. note:: + To find the user record, the database is queried only using the username. + The password check is done in PHP. This is necessary because hashing + algorithms like bcrypt (which is used by default) generate a new hash + each time, even for the same string and you can't just do simple string + comparison in SQL to check if the password matches. + +Customizing Find Query +---------------------- + +You can customize the query used to fetch the user record using the ``finder`` +option in authenticate class config:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth', [ + 'authenticate' => [ + 'Form' => [ + 'finder' => 'auth' + ] + ], + ]); + } + +This will require your ``UsersTable`` to have finder method ``findAuth()``. +In the example shown below the query is modified to fetch only required fields +and add a condition. You must ensure that you select the fields you need to +authenticate a user, such as ``username`` and ``password``:: + + public function findAuth(\Cake\ORM\Query $query, array $options) + { + $query + ->select(['id', 'username', 'password']) + ->where(['Users.active' => 1]); + + return $query; + } + +.. note:: + ``finder`` option is available since 3.1. Prior to that you can use ``scope`` + and ``contain`` options to modify a query. + +Identifying Users and Logging Them In +------------------------------------- + +.. php:method:: identify() + +You need to manually call ``$this->Auth->identify()`` to identify the user using +credentials provided in request. Then use ``$this->Auth->setUser()`` +to log the user in, i.e., save user info to session. + +When authenticating users, attached authentication objects are checked +in the order they are attached. Once one of the objects can identify +the user, no other objects are checked. A sample login function for +working with a login form could look like:: + + public function login() + { + if ($this->request->is('post')) { + $user = $this->Auth->identify(); + if ($user) { + $this->Auth->setUser($user); + return $this->redirect($this->Auth->redirectUrl()); + } else { + $this->Flash->error(__('Username or password is incorrect')); + } + } + } + +The above code will attempt to first identify a user by using the POST data. +If successful we set the user info to the session so that it persists across requests +and then redirect to either the last page they were visiting or a URL specified in the +``loginRedirect`` config. If the login is unsuccessful, a flash message is set. + +.. warning:: + + ``$this->Auth->setUser($data)`` will log the user in with whatever data is + passed to the method. It won't actually check the credentials against an + authentication class. + +Redirecting Users After Login +----------------------------- + +.. php:method:: redirectUrl + +After logging a user in, you'll generally want to redirect them back to where +they came from. Pass a URL in to set the destination a user should be redirected +to after logging in. + +If no parameter is passed, the returned URL will use the following rules: + +- Returns the normalized URL from the ``redirect`` query string value if it is + present and for the same domain the current app is running on. Before 3.4.0, + the ``Auth.redirect`` session value was used. +- If there is no query string/session value and there is a config + ``loginRedirect``, the ``loginRedirect`` value is returned. +- If there is no redirect value and no ``loginRedirect``, ``/`` is returned. + +Creating Stateless Authentication Systems +----------------------------------------- + +Basic and digest are stateless authentication schemes and don't require an +initial POST or a form. If using only basic/digest authenticators you don't +require a login action in your controller. Stateless authentication will +re-verify the user's credentials on each request, this creates a small amount of +additional overhead, but allows clients to login without using cookies and +makes AuthComponent more suitable for building APIs. + +For stateless authenticators, the ``storage`` config should be set to ``Memory`` +so that AuthComponent does not use a session to store user record. You may also +want to set config ``unauthorizedRedirect`` to ``false`` so that AuthComponent +throws a ``ForbiddenException`` instead of the default behavior of redirecting to +referrer. + +Authentication objects can implement a ``getUser()`` method that can be used to +support user login systems that don't rely on cookies. A typical getUser method +looks at the request/environment and uses the information there to confirm the +identity of the user. HTTP Basic authentication for example uses +``$_SERVER['PHP_AUTH_USER']`` and ``$_SERVER['PHP_AUTH_PW']`` for the username +and password fields. + +.. note:: + + In case authentication does not work like expected, check if queries + are executed at all (see ``BaseAuthenticate::_query($username)``). + In case no queries are executed check if ``$_SERVER['PHP_AUTH_USER']`` + and ``$_SERVER['PHP_AUTH_PW']`` do get populated by the webserver. + If you are using Apache with FastCGI-PHP you might need to add this line + to your **.htaccess** file in webroot:: + + RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] + +On each request, these values, ``PHP_AUTH_USER`` and ``PHP_AUTH_PW``, are used to +re-identify the user and ensure they are the valid user. As with authentication +object's ``authenticate()`` method, the ``getUser()`` method should return +an array of user information on the success or ``false`` on failure. :: + + public function getUser(ServerRequest $request) + { + $username = env('PHP_AUTH_USER'); + $pass = env('PHP_AUTH_PW'); + + if (empty($username) || empty($pass)) { + return false; + } + return $this->_findUser($username, $pass); + } + +The above is how you could implement the getUser method for HTTP basic +authentication. The ``_findUser()`` method is part of ``BaseAuthenticate`` +and identifies a user based on a username and password. + +.. _basic-authentication: + +Using Basic Authentication +-------------------------- + +Basic authentication allows you to create a stateless authentication that can be +used in intranet applications or for simple API scenarios. Basic authentication +credentials will be rechecked on each request. + +.. warning:: + Basic authentication transmits credentials in plain-text. You should use + HTTPS when using Basic authentication. + +To use basic authentication, you'll need to configure AuthComponent:: + + $this->loadComponent('Auth', [ + 'authenticate' => [ + 'Basic' => [ + 'fields' => ['username' => 'username', 'password' => 'api_key'], + 'userModel' => 'Users' + ], + ], + 'storage' => 'Memory', + 'unauthorizedRedirect' => false + ]); + +Here we're using username + API key as our fields and use the Users model. + +Creating API Keys for Basic Authentication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Because basic HTTP sends credentials in plain-text, it is unwise to have users +send their login password. Instead, an opaque API key is generally used. You can +generate these API tokens randomly using libraries from CakePHP:: + + namespace App\Model\Table; + + use Cake\Auth\DefaultPasswordHasher; + use Cake\Utility\Text; + use Cake\Event\Event; + use Cake\ORM\Table; + use Cake\Utility\Security; + + class UsersTable extends Table + { + public function beforeSave(Event $event) + { + $entity = $event->getData('entity'); + + if ($entity->isNew()) { + $hasher = new DefaultPasswordHasher(); + + // Generate an API 'token' + $entity->api_key_plain = Security::hash(Security::randomBytes(32), 'sha256', false); + + // Bcrypt the token so BasicAuthenticate can check + // it during login. + $entity->api_key = $hasher->hash($entity->api_key_plain); + } + return true; + } + } + +The above generates a random hash for each user as they are saved. The above +code assumes you have two columns ``api_key`` - to store the hashed API key, and +``api_key_plain`` - to the plaintext version of the API key, so we can display +it to the user later on. Using a key instead of a password means that even over +plain HTTP, your users can use an opaque token instead of their original +password. It is also wise to include logic allowing API keys to be regenerated +at a user's request. + +Using Digest Authentication +--------------------------- + +Digest authentication offers an improved security model over basic +authentication, as the user's credentials are never sent in the request header. +Instead, a hash is sent. + +To use digest authentication, you'll need to configure ``AuthComponent``:: + + $this->loadComponent('Auth', [ + 'authenticate' => [ + 'Digest' => [ + 'fields' => ['username' => 'username', 'password' => 'digest_hash'], + 'userModel' => 'Users' + ], + ], + 'storage' => 'Memory', + 'unauthorizedRedirect' => false + ]); + +Here we're using username + digest_hash as our fields and use the Users model. + +Hashing Passwords For Digest Authentication +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Because Digest authentication requires a password hashed in the format +defined by the RFC, in order to correctly hash a password for use with +Digest authentication you should use the special password hashing +function on ``DigestAuthenticate``. If you are going to be combining +digest authentication with any other authentication strategies, it's also +recommended that you store the digest password in a separate column, +from the normal password hash:: + + namespace App\Model\Table; + + use Cake\Auth\DigestAuthenticate; + use Cake\Event\Event; + use Cake\ORM\Table; + + class UsersTable extends Table + { + public function beforeSave(Event $event) + { + $entity = $event->getData('entity'); + + // Make a password for digest auth. + $entity->digest_hash = DigestAuthenticate::password( + $entity->username, + $entity->plain_password, + env('SERVER_NAME') + ); + return true; + } + } + +Passwords for digest authentication need a bit more information than +other password hashes, based on the RFC for digest authentication. + +.. note:: + + The third parameter of ``DigestAuthenticate::password()`` must match the + 'realm' config value defined when DigestAuthentication was configured + in ``AuthComponent::$authenticate``. This defaults to ``env('SCRIPT_NAME')``. + You may wish to use a static string if you want consistent hashes in multiple environments. + +Creating Custom Authentication Objects +-------------------------------------- + +Because authentication objects are pluggable, you can create custom +authentication objects in your application or plugins. If for example, +you wanted to create an OpenID authentication object. In +**src/Auth/OpenidAuthenticate.php** you could put the following:: + + namespace App\Auth; + + use Cake\Auth\BaseAuthenticate; + use Cake\Http\ServerRequest; + use Cake\Http\Response; + + class OpenidAuthenticate extends BaseAuthenticate + { + public function authenticate(ServerRequest $request, Response $response) + { + // Do things for OpenID here. + // Return an array of user if they could authenticate the user, + // return false if not. + } + } + +Authentication objects should return ``false`` if they cannot identify the +user and an array of user information if they can. It's not required +that you extend ``BaseAuthenticate``, only that your authentication object +implements ``Cake\Event\EventListenerInterface``. The ``BaseAuthenticate`` class +provides a number of helpful methods that are commonly used. You can +also implement a ``getUser()`` method if your authentication object needs +to support stateless or cookie-less authentication. See the sections on +basic and digest authentication below for more information. + +``AuthComponent`` triggers two events, ``Auth.afterIdentify`` and ``Auth.logout``, +after a user has been identified and before a user is logged out respectively. +You can set callback functions for these events by returning a mapping array +from ``implementedEvents()`` method of your authenticate class:: + + public function implementedEvents() + { + return [ + 'Auth.afterIdentify' => 'afterIdentify', + 'Auth.logout' => 'logout' + ]; + } + +Using Custom Authentication Objects +----------------------------------- + +Once you've created your custom authentication objects, you can use them +by including them in ``AuthComponent``'s authenticate array:: + + $this->Auth->config('authenticate', [ + 'Openid', // app authentication object. + 'AuthBag.Openid', // plugin authentication object. + ]); + +.. note:: + Note that when using simple notation there's no 'Authenticate' word when + initiating the authentication object. Instead, if using namespaces, you'll + need to set the full namespace of the class, including the 'Authenticate' word. + +Handling Unauthenticated Requests +--------------------------------- + +When an unauthenticated user tries to access a protected page first the +``unauthenticated()`` method of the last authenticator in the chain is called. +The authenticate object can handle sending response or redirection by returning +a response object to indicate no further action is necessary. Due to this, the +order in which you specify the authentication provider in ``authenticate`` +config matters. + +If authenticator returns null, ``AuthComponent`` redirects user to the login action. +If it's an AJAX request and config ``ajaxLogin`` is specified that element +is rendered else a 403 HTTP status code is returned. + +Displaying Auth Related Flash Messages +-------------------------------------- + +In order to display the session error messages that Auth generates, you +need to add the following code to your layout. Add the following two +lines to the **src/Template/Layout/default.ctp** file in the body section:: + + // Only this is necessary after 3.4.0 + echo $this->Flash->render(); + + // Prior to 3.4.0 this will be required as well. + echo $this->Flash->render('auth'); + +You can customize the error messages and flash settings ``AuthComponent`` +uses. Using ``flash`` config you can configure the parameters +``AuthComponent`` uses for setting flash messages. The available keys are + +- ``key`` - The key to use, defaults to 'default'. Prior to 3.4.0, the key + defaulted to 'auth'. +- ``element`` - The element name to use for rendering, defaults to null. +- ``params`` - The array of additional params to use, defaults to ``[]``. + +In addition to the flash message settings you can customize other error +messages ``AuthComponent`` uses. In your controller's ``beforeFilter()``, or +component settings you can use ``authError`` to customize the error used +for when authorization fails:: + + $this->Auth->config('authError', "Woopsie, you are not authorized to access this area."); + +Sometimes, you want to display the authorization error only after +the user has already logged-in. You can suppress this message by setting +its value to boolean ``false``. + +In your controller's ``beforeFilter()`` or component settings:: + + if (!$this->Auth->user()) { + $this->Auth->config('authError', false); + } + +.. _hashing-passwords: + +Hashing Passwords +----------------- + +You are responsible for hashing the passwords before they are persisted to the +database, the easiest way is to use a setter function in your User entity:: + + namespace App\Model\Entity; + + use Cake\Auth\DefaultPasswordHasher; + use Cake\ORM\Entity; + + class User extends Entity + { + + // ... + + protected function _setPassword($password) + { + if (strlen($password) > 0) { + return (new DefaultPasswordHasher)->hash($password); + } + } + + // ... + } + +``AuthComponent`` is configured by default to use the ``DefaultPasswordHasher`` +when validating user credentials so no additional configuration is required in +order to authenticate users. + +``DefaultPasswordHasher`` uses the bcrypt hashing algorithm internally, which +is one of the stronger password hashing solutions used in the industry. While it +is recommended that you use this password hasher class, the case may be that you +are managing a database of users whose password was hashed differently. + +Creating Custom Password Hasher Classes +--------------------------------------- + +In order to use a different password hasher, you need to create the class in +**src/Auth/LegacyPasswordHasher.php** and implement the +``hash()`` and ``check()`` methods. This class needs to extend the +``AbstractPasswordHasher`` class:: + + namespace App\Auth; + + use Cake\Auth\AbstractPasswordHasher; + + class LegacyPasswordHasher extends AbstractPasswordHasher + { + + public function hash($password) + { + return sha1($password); + } + + public function check($password, $hashedPassword) + { + return sha1($password) === $hashedPassword; + } + } + +Then you are required to configure the ``AuthComponent`` to use your own password +hasher:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth', [ + 'authenticate' => [ + 'Form' => [ + 'passwordHasher' => [ + 'className' => 'Legacy', + ] + ] + ] + ]); + } + +Supporting legacy systems is a good idea, but it is even better to keep your +database with the latest security advancements. The following section will +explain how to migrate from one hashing algorithm to CakePHP's default. + +Changing Hashing Algorithms +--------------------------- + +CakePHP provides a clean way to migrate your users' passwords from one algorithm +to another, this is achieved through the ``FallbackPasswordHasher`` class. +Assuming you are migrating your app from CakePHP 2.x which uses ``sha1`` password hashes, you +can configure the ``AuthComponent`` as follows:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth', [ + 'authenticate' => [ + 'Form' => [ + 'passwordHasher' => [ + 'className' => 'Fallback', + 'hashers' => [ + 'Default', + 'Weak' => ['hashType' => 'sha1'] + ] + ] + ] + ] + ]); + } + +The first name appearing in the ``hashers`` key indicates which of the classes +is the preferred one, but it will fallback to the others in the list if the +check was unsuccessful. + +When using the ``WeakPasswordHasher`` you will need to +set the ``Security.salt`` configure the value to ensure passwords are salted. + +In order to update old users' passwords on the fly, you can change the login +function accordingly:: + + public function login() + { + if ($this->request->is('post')) { + $user = $this->Auth->identify(); + if ($user) { + $this->Auth->setUser($user); + if ($this->Auth->authenticationProvider()->needsPasswordRehash()) { + $user = $this->Users->get($this->Auth->user('id')); + $user->password = $this->request->getData('password'); + $this->Users->save($user); + } + return $this->redirect($this->Auth->redirectUrl()); + } + ... + } + } + +As you can see we are just setting the plain password again so the setter +function in the entity will hash the password as shown in the previous example and +then save the entity. + +Manually Logging Users In +------------------------- + +.. php:method:: setUser(array $user) + +Sometimes the need arises where you need to manually log a user in, such +as just after they registered for your application. You can do this by +calling ``$this->Auth->setUser()`` with the user data you want to 'login':: + + public function register() + { + $user = $this->Users->newEntity($this->request->getData()); + if ($this->Users->save($user)) { + $this->Auth->setUser($user->toArray()); + return $this->redirect([ + 'controller' => 'Users', + 'action' => 'home' + ]); + } + } + +.. warning:: + + Be sure to manually add the new User id to the array passed to the ``setUser()`` + method. Otherwise, you won't have the user id available. + +Accessing the Logged In User +---------------------------- + +.. php:method:: user($key = null) + +Once a user is logged in, you will often need some particular +information about the current user. You can access the currently logged +in user using ``AuthComponent::user()``:: + + // From inside a controller or other component. + $this->Auth->user('id'); + +If the current user is not logged in or the key doesn't exist, null will +be returned. + +Logging Users Out +----------------- + +.. php:method:: logout() + +Eventually, you'll want a quick way to de-authenticate someone and +redirect them to where they need to go. This method is also useful if +you want to provide a 'Log me out' link inside a members' area of your +application:: + + public function logout() + { + return $this->redirect($this->Auth->logout()); + } + +Logging out users that logged in with Digest or Basic auth is difficult +to accomplish for all clients. Most browsers will retain credentials +for the duration they are still open. Some clients can be forced to +logout by sending a 401 status code. Changing the authentication realm +is another solution that works for some clients. + +Deciding When to run Authentication +----------------------------------- + +In some cases you may want to use ``$this->Auth->user()`` in the +``beforeFilter(Event $event)`` method. This is achievable by using the +``checkAuthIn`` config key. The following changes which event for which initial +authentication checks should be done:: + + //Set up AuthComponent to authenticate in initialize() + $this->Auth->config('checkAuthIn', 'Controller.initialize'); + +Default value for ``checkAuthIn`` is ``'Controller.startup'`` - but by using +``'Controller.initialize'`` initial authentication is done before ``beforeFilter()`` +method. + +.. _authorization-objects: + +Authorization +============= + +Authorization is the process of ensuring that an +identified/authenticated user is allowed to access the resources they +are requesting. If enabled ``AuthComponent`` can automatically check +authorization handlers and ensure that logged in users are allowed to +access the resources they are requesting. There are several built-in +authorization handlers and you can create custom ones for your +application or as part of a plugin. + +- ``ControllerAuthorize`` Calls ``isAuthorized()`` on the active controller, + and uses the return of that to authorize a user. This is often the most + simple way to authorize users. + +.. note:: + + The ``ActionsAuthorize`` & ``CrudAuthorize`` adapter available in CakePHP + 2.x have now been moved to a separate plugin `cakephp/acl `_. + +Configuring Authorization Handlers +---------------------------------- + +You configure authorization handlers using the ``authorize`` config key. +You can configure one or many handlers for authorization. Using +multiple handlers allows you to support different ways of checking +authorization. When authorization handlers are checked, they will be +called in the order they are declared. Handlers should return ``false``, if +they are unable to check authorization, or the check has failed. +Handlers should return ``true`` if they were able to check authorization +successfully. Handlers will be called in sequence until one passes. If +all checks fail, the user will be redirected to the page they came from. +Additionally, you can halt all authorization by throwing an exception. +You will need to catch any thrown exceptions and handle them. + +You can configure authorization handlers in your controller's +``beforeFilter()`` or ``initialize()`` methods. You can pass +configuration information into each authorization object, using an +array:: + + // Basic setup + $this->Auth->config('authorize', ['Controller']); + + // Pass settings in + $this->Auth->config('authorize', [ + 'Actions' => ['actionPath' => 'controllers/'], + 'Controller' + ]); + +Much like ``authenticate``, ``authorize``, helps you +keep your code DRY, by using the ``all`` key. This special key allows you +to set settings that are passed to every attached object. The ``all`` key +is also exposed as ``AuthComponent::ALL``:: + + // Pass settings in using 'all' + $this->Auth->config('authorize', [ + AuthComponent::ALL => ['actionPath' => 'controllers/'], + 'Actions', + 'Controller' + ]); + +In the above example, both the ``Actions`` and ``Controller`` will get the +settings defined for the 'all' key. Any settings passed to a specific +authorization object will override the matching key in the 'all' key. + +If an authenticated user tries to go to a URL he's not authorized to access, +he's redirected back to the referrer. If you do not want such redirection +(mostly needed when using stateless authentication adapter) you can set config +option ``unauthorizedRedirect`` to ``false``. This causes ``AuthComponent`` +to throw a ``ForbiddenException`` instead of redirecting. + +Creating Custom Authorize Objects +--------------------------------- + +Because authorize objects are pluggable, you can create custom authorize +objects in your application or plugins. If for example, you wanted to +create an LDAP authorize object. In +**src/Auth/LdapAuthorize.php** you could put the +following:: + + namespace App\Auth; + + use Cake\Auth\BaseAuthorize; + use Cake\Http\ServerRequest; + + class LdapAuthorize extends BaseAuthorize + { + public function authorize($user, ServerRequest $request) + { + // Do things for ldap here. + } + } + +Authorize objects should return ``false`` if the user is denied access, or +if the object is unable to perform a check. If the object is able to +verify the user's access, ``true`` should be returned. It's not required +that you extend ``BaseAuthorize``, only that your authorize object +implements an ``authorize()`` method. The ``BaseAuthorize`` class provides +a number of helpful methods that are commonly used. + +Using Custom Authorize Objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Once you've created your custom authorize object, you can use them by +including them in your ``AuthComponent``'s authorize array:: + + $this->Auth->config('authorize', [ + 'Ldap', // app authorize object. + 'AuthBag.Combo', // plugin authorize object. + ]); + +Using No Authorization +---------------------- + +If you'd like to not use any of the built-in authorization objects and +want to handle things entirely outside of ``AuthComponent``, you can set +``$this->Auth->config('authorize', false);``. By default ``AuthComponent`` +starts off with ``authorize`` set to ``false``. If you don't use an +authorization scheme, make sure to check authorization yourself in your +controller's ``beforeFilter()`` or with another component. + +Making Actions Public +--------------------- + +.. php:method:: allow($actions = null) + +There are often times controller actions that you wish to remain +entirely public or that don't require users to be logged in. +``AuthComponent`` is pessimistic and defaults to denying access. You can +mark actions as public actions by using ``AuthComponent::allow()``. By +marking actions as public, ``AuthComponent`` will not check for a logged in +user nor will authorize objects to be checked:: + + // Allow all actions + $this->Auth->allow(); + + // Allow only the index action. + $this->Auth->allow('index'); + + // Allow only the view and index actions. + $this->Auth->allow(['view', 'index']); + +By calling it empty you allow all actions to be public. +For a single action, you can provide the action name as a string. Otherwise, use an array. + +.. note:: + + You should not add the "login" action of your ``UsersController`` to allow list. + Doing so would cause problems with the normal functioning of ``AuthComponent``. + +Making Actions Require Authorization +------------------------------------ + +.. php:method:: deny($actions = null) + +By default all actions require authorization. However, after making actions +public you want to revoke the public access. You can do so using +``AuthComponent::deny()``:: + + // Deny all actions. + $this->Auth->deny(); + + // Deny one action + $this->Auth->deny('add'); + + // Deny a group of actions. + $this->Auth->deny(['add', 'edit']); + +By calling it empty you deny all actions. +For a single action, you can provide the action name as a string. Otherwise, use an array. + +Using ControllerAuthorize +------------------------- + +ControllerAuthorize allows you to handle authorization checks in a +controller callback. This is ideal when you have very simple +authorization or you need to use a combination of models and components +to do your authorization and don't want to create a custom authorize +object. + +The callback is always called ``isAuthorized()`` and it should return a +boolean as to whether or not the user is allowed to access resources in +the request. The callback is passed the active user so it can be +checked:: + + class AppController extends Controller + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth', [ + 'authorize' => 'Controller', + ]); + } + + public function isAuthorized($user = null) + { + // Any registered user can access public functions + if (!$this->request->getParam('prefix')) { + return true; + } + + // Only admins can access admin functions + if ($this->request->getParam('prefix') === 'admin') { + return (bool)($user['role'] === 'admin'); + } + + // Default deny + return false; + } + } + +The above callback would provide a very simple authorization system +where only users with role = admin could access actions that were in +the admin prefix. + +Configuration options +===================== + +The following settings can all be defined either in your controller's +``initialize()`` method or using ``$this->Auth->config()`` in your ``beforeFilter()``: + +ajaxLogin + The name of an optional view element to render when an AJAX request is made + with an invalid or expired session. +allowedActions + Controller actions for which user validation is not required. +authenticate + Set to an array of Authentication objects you want to use when + logging users in. There are several core authentication objects; + see the section on :ref:`authentication-objects`. +authError + Error to display when user attempts to access an object or action to which + they do not have access. + + You can suppress authError message from being displayed by setting this + value to boolean ``false``. +authorize + Set to an array of Authorization objects you want to use when + authorizing users on each request; see the section on + :ref:`authorization-objects`. +flash + Settings to use when Auth needs to do a flash message with + ``FlashComponent::set()``. + Available keys are: + + - ``element`` - The element to use; defaults to 'default'. + - ``key`` - The key to use; defaults to 'auth'. + - ``params`` - The array of additional params to use; defaults to '[]'. + +loginAction + A URL (defined as a string or array) to the controller action that handles + logins. Defaults to ``/users/login``. +loginRedirect + The URL (defined as a string or array) to the controller action users + should be redirected to after logging in. This value will be ignored if the + user has an ``Auth.redirect`` value in their session. +logoutRedirect + The default action to redirect to after the user is logged out. While + ``AuthComponent`` does not handle post-logout redirection, a redirect URL will + be returned from :php:meth:`AuthComponent::logout()`. Defaults to + ``loginAction``. +unauthorizedRedirect + Controls handling of unauthorized access. By default unauthorized user is + redirected to the referrer URL or ``loginAction`` or '/'. + If set to ``false``, a ForbiddenException exception is thrown instead of + redirecting. +storage + Storage class to use for persisting user record. When using stateless + authenticator you should set this to ``Memory``. Defaults to ``Session``. + You can pass config options to storage class using array format. For e.g. to + use a custom session key you can set ``storage`` to ``['className' => 'Session', 'key' => 'Auth.Admin']``. +checkAuthIn + Name of the event in which initial auth checks should be done. Defaults + to ``Controller.startup``. You can set it to ``Controller.initialize`` + if you want the check to be done before controller's ``beforeFilter()`` + method is run. + +You can get current configuration values by calling ``$this->Auth->config()``:: +only the configuration option:: + + $this->Auth->config('loginAction'); + + $this->redirect($this->Auth->config('loginAction')); + +This is useful if you want to redirect a user to the ``login`` route for example. +Without a parameter, the full configuration will be returned. + +Testing Actions Protected By AuthComponent +========================================== + +See the :ref:`testing-authentication` section for tips on how to test controller +actions that are protected by ``AuthComponent``. + +.. meta:: + :title lang=en: Authentication + :keywords lang=en: authentication handlers,array php,basic authentication,web application,different ways,credentials,exceptions,cakephp,logging diff --git a/tl/controllers/components/cookie.rst b/tl/controllers/components/cookie.rst new file mode 100644 index 0000000000000000000000000000000000000000..1827f47549b2cc34449b57774eb6aec52eb539d8 --- /dev/null +++ b/tl/controllers/components/cookie.rst @@ -0,0 +1,137 @@ +Cookie +###### + +.. php:namespace:: Cake\Controller\Component + +.. php:class:: CookieComponent(ComponentRegistry $collection, array $config = []) + +The CookieComponent is a wrapper around the native PHP ``setcookie()`` method. It +makes it easier to manipulate cookies, and automatically encrypt cookie data. +Cookies added through CookieComponent will only be sent if the controller action +completes. + +.. deprecated:: 3.5.0 + You should use :ref:`encrypted-cookie-middleware` instead of + ``CookieComponent``. + +Configuring Cookies +=================== + +Cookies can be configured either globally or per top-level name. The global +configuration data will be merged with the top-level configuration. So only need +to override the parts that are different. To configure the global settings use +the ``config()`` method:: + + $this->Cookie->config('path', '/'); + $this->Cookie->config([ + 'expires' => '+10 days', + 'httpOnly' => true + ]); + +To configure a specific key use the ``configKey()`` method:: + + $this->Cookie->configKey('User', 'path', '/'); + $this->Cookie->configKey('User', [ + 'expires' => '+10 days', + 'httpOnly' => true + ]); + +There are a number of configurable values for cookies: + +expires + How long the cookies should last for. Defaults to 1 month. +path + The path on the server in which the cookie will be available on. + If path is set to '/foo/', the cookie will only be available within the + /foo/ directory and all sub-directories such as /foo/bar/ of domain. + The default value is app's base path. +domain + The domain that the cookie is available. To make the cookie + available on all subdomains of example.com set domain to '.example.com'. +secure + Indicates that the cookie should only be transmitted over a secure HTTPS + connection. When set to ``true``, the cookie will only be set if a + secure connection exists. +key + Encryption key used when encrypted cookies are enabled. Defaults to Security.salt. +httpOnly + Set to ``true`` to make HTTP only cookies. Cookies that are HTTP only + are not accessible in JavaScript. Defaults to ``false``. +encryption + Type of encryption to use. Defaults to 'aes'. Can also be 'rijndael' for + backwards compatibility. + +Using the Component +=================== + +The CookieComponent offers a number of methods for working with Cookies. + +.. php:method:: write(mixed $key, mixed $value = null) + + The write() method is the heart of the cookie component. $key is the + cookie variable name you want, and the $value is the information to + be stored:: + + $this->Cookie->write('name', 'Larry'); + + You can also group your variables by using dot notation in the + key parameter:: + + $this->Cookie->write('User.name', 'Larry'); + $this->Cookie->write('User.role', 'Lead'); + + If you want to write more than one value to the cookie at a time, + you can pass an array:: + + $this->Cookie->write('User', + ['name' => 'Larry', 'role' => 'Lead'] + ); + + All values in the cookie are encrypted with AES by default. If you want to + store the values as plain text, be sure to configure the key space:: + + $this->Cookie->configKey('User', 'encryption', false); + +.. php:method:: read(mixed $key = null) + + This method is used to read the value of a cookie variable with the + name specified by $key. :: + + // Outputs "Larry" + echo $this->Cookie->read('name'); + + // You can also use the dot notation for read + echo $this->Cookie->read('User.name'); + + // To get the variables which you had grouped + // using the dot notation as an array use the following + $this->Cookie->read('User'); + + // This outputs something like ['name' => 'Larry', 'role' => 'Lead'] + + .. warning:: + CookieComponent cannot interact with bare strings values that contain + ``,``. The component will attempt to interpret these values as + arrays, leading to incorrect results. Instead you should use + ``$request->getCookie()``. + +.. php:method:: check($key) + + :param string $key: The key to check. + + Used to check whether a key/path exists and has a non-null value. + +.. php:method:: delete(mixed $key) + + Deletes a cookie variable of the name in $key. Works with dot + notation:: + + // Delete a variable + $this->Cookie->delete('bar'); + + // Delete the cookie variable bar, but not everything under foo + $this->Cookie->delete('foo.bar'); + +.. meta:: + :title lang=en: Cookie + :keywords lang=en: array controller,php setcookie,cookie string,controller setup,string domain,default description,string name,session cookie,integers,variables,domain name,null diff --git a/tl/controllers/components/csrf.rst b/tl/controllers/components/csrf.rst new file mode 100644 index 0000000000000000000000000000000000000000..c007cfe69e9aa8973718e7d41fbbffeb5a1596d6 --- /dev/null +++ b/tl/controllers/components/csrf.rst @@ -0,0 +1,97 @@ +Cross Site Request Forgery +########################## + +By enabling the CSRF Component you get protection against attacks. `CSRF +`_ or Cross Site +Request Forgery is a common vulnerability in web applications. It allows an +attacker to capture and replay a previous request, and sometimes submit data +requests using image tags or resources on other domains. + +The CsrfComponent works by setting a cookie to the user's browser. When forms +are created with the :php:class:`Cake\\View\\Helper\\FormHelper`, a hidden field +is added containing the CSRF token. During the ``Controller.startup`` event, if +the request is a POST, PUT, DELETE, PATCH request the component will compare the +request data & cookie value. If either is missing or the two values mismatch the +component will throw a +:php:class:`Cake\\Network\\Exception\\InvalidCsrfTokenException`. + +.. note:: + You should always verify the HTTP method being used before executing to avoid + side-effects. You should :ref:`check the HTTP method ` or + use :php:meth:`Cake\\Http\\ServerRequest::allowMethod()` to ensure the correct + HTTP method is used. + +.. versionadded:: 3.1 + + The exception type changed from + :php:class:`Cake\\Network\\Exception\\ForbiddenException` to + :php:class:`Cake\\Network\\Exception\\InvalidCsrfTokenException`. + +.. deprecated:: 3.5.0 + You should use :ref:`csrf-middleware` instead of + ``CsrfComponent``. + +Using the CsrfComponent +======================= + +Simply by adding the ``CsrfComponent`` to your components array, +you can benefit from the CSRF protection it provides:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Csrf'); + } + +Settings can be passed into the component through your component's settings. +The available configuration options are: + +- ``cookieName`` The name of the cookie to send. Defaults to ``csrfToken``. +- ``expiry`` How long the CSRF token should last. Defaults to browser session. + Accepts ``strtotime`` values as of 3.1 +- ``secure`` Whether or not the cookie will be set with the Secure flag. That is, + the cookie will only be set on a HTTPS connection and any attempt over normal HTTP + will fail. Defaults to ``false``. +- ``field`` The form field to check. Defaults to ``_csrfToken``. Changing this + will also require configuring FormHelper. + +When enabled, you can access the current CSRF token on the request object:: + + $token = $this->request->getParam('_csrfToken'); + +Integration with FormHelper +=========================== + +The CsrfComponent integrates seamlessly with ``FormHelper``. Each time you +create a form with FormHelper, it will insert a hidden field containing the CSRF +token. + +.. note:: + + When using the CsrfComponent you should always start your forms with the + FormHelper. If you do not, you will need to manually create hidden inputs in + each of your forms. + +CSRF Protection and AJAX Requests +================================= + +In addition to request data parameters, CSRF tokens can be submitted through +a special ``X-CSRF-Token`` header. Using a header often makes it easier to +integrate a CSRF token with JavaScript heavy applications, or XML/JSON based API +endpoints. + +Disabling the CSRF Component for Specific Actions +================================================= + +While not recommended, you may want to disable the CsrfComponent on certain +requests. You can do this using the controller's event dispatcher, during the +``beforeFilter()`` method:: + + public function beforeFilter(Event $event) + { + $this->getEventManager()->off($this->Csrf); + } + +.. meta:: + :title lang=en: Csrf + :keywords lang=en: configurable parameters,security component,configuration parameters,invalid request,csrf,submission diff --git a/tl/controllers/components/flash.rst b/tl/controllers/components/flash.rst new file mode 100644 index 0000000000000000000000000000000000000000..39e07b53266b32cb9321b144e1d3aff4cd611b85 --- /dev/null +++ b/tl/controllers/components/flash.rst @@ -0,0 +1,109 @@ +Flash +##### + +.. php:namespace:: Cake\Controller\Component + +.. php:class:: FlashComponent(ComponentCollection $collection, array $config = []) + +FlashComponent provides a way to set one-time notification messages to be +displayed after processing a form or acknowledging data. CakePHP refers to these +messages as "flash messages". FlashComponent writes flash messages to +``$_SESSION``, to be rendered in a View using +:doc:`FlashHelper `. + +Setting Flash Messages +====================== + +FlashComponent provides two ways to set flash messages: its ``__call()`` magic +method and its ``set()`` method. To furnish your application with verbosity, +FlashComponent's ``__call()`` magic method allows you use a method name that +maps to an element located under the **src/Template/Element/Flash** directory. +By convention, camelcased methods will map to the lowercased and underscored +element name:: + + // Uses src/Template/Element/Flash/success.ctp + $this->Flash->success('This was successful'); + + // Uses src/Template/Element/Flash/great_success.ctp + $this->Flash->greatSuccess('This was greatly successful'); + +Alternatively, to set a plain-text message without rendering an element, you can +use the ``set()`` method:: + + $this->Flash->set('This is a message'); + +.. versionadded:: 3.1 + + Flash messages now stack. Successive calls to ``set()`` or ``__call()`` with + the same key will append the messages in the ``$_SESSION``. If you want to + keep the old behavior (one message even after consecutive calls), set the + ``clear`` parameter to ``true`` when configuring the Component. + +FlashComponent's ``__call()`` and ``set()`` methods optionally take a second +parameter, an array of options: + +* ``key`` Defaults to 'flash'. The array key found under the ``Flash`` key in + the session. +* ``element`` Defaults to ``null``, but will automatically be set when using the + ``__call()`` magic method. The element name to use for rendering. +* ``params`` An optional array of keys/values to make available as variables + within an element. + +.. versionadded:: 3.1 + + A new key ``clear`` was added. This key expects a ``bool`` and allows you + to delete all messages in the current stack and start a new one. + +An example of using these options:: + + // In your Controller + $this->Flash->success('The user has been saved', [ + 'key' => 'positive', + 'params' => [ + 'name' => $user->name, + 'email' => $user->email + ] + ]); + + // In your View + Flash->render('positive') ?> + + +
    + : , . +
    + +Note that the parameter ``element`` will be always overridden while using +``__call()``. In order to retrieve a specific element from a plugin, you should +set the ``plugin`` parameter. For example:: + + // In your Controller + $this->Flash->warning('My message', ['plugin' => 'PluginName']); + +The code above will use the **warning.ctp** element under +**plugins/PluginName/src/Template/Element/Flash** for rendering the flash +message. + +.. note:: + + By default, CakePHP escapes the content in flash messages to prevent cross + site scripting. User data in your flash messages will be HTML encoded and + safe to be printed. If you want to include HTML in your flash messages, you + need to pass the ``escape`` option and adjust your flash message templates + to allow disabling escaping when the escape option is passed. + +HTML in Flash Messages +====================== + +.. versionadded:: 3.3.3 + +It is possible to output HTML in flash messages by using the ``'escape'`` option +key:: + + $this->Flash->info(sprintf('%s %s', h($highlight), h($message)), ['escape' => false]); + +Make sure that you escape the input manually, then. In the above example +``$highlight`` and ``$message`` are non-HTML input and therefore escaped. + +For more information about rendering your flash messages, please refer to the +:doc:`FlashHelper ` section. diff --git a/tl/controllers/components/pagination.rst b/tl/controllers/components/pagination.rst new file mode 100644 index 0000000000000000000000000000000000000000..a62ee44b3fa77bf9e426fcd3c53d3d2bae04bda7 --- /dev/null +++ b/tl/controllers/components/pagination.rst @@ -0,0 +1,294 @@ +Pagination +########## + +.. php:namespace:: Cake\Controller\Component + +.. php:class:: PaginatorComponent + +One of the main obstacles of creating flexible and user-friendly web +applications is designing an intuitive user interface. Many applications tend to +grow in size and complexity quickly, and designers and programmers alike find +they are unable to cope with displaying hundreds or thousands of records. +Refactoring takes time, and performance and user satisfaction can suffer. + +Displaying a reasonable number of records per page has always been a critical +part of every application and used to cause many headaches for developers. +CakePHP eases the burden on the developer by providing a quick, easy way to +paginate data. + +Pagination in CakePHP is offered by a component in the controller, to make +building paginated queries easier. In the View +:php:class:`~Cake\\View\\Helper\\PaginatorHelper` is used to make the generation +of pagination links & buttons simple. + +Using Controller::paginate() +============================ + +In the controller, we start by defining the default query conditions pagination +will use in the ``$paginate`` controller variable. These conditions, serve as +the basis for your pagination queries. They are augmented by the ``sort``, ``direction``, +``limit``, and ``page`` parameters passed in from the URL. It is important to note +that the ``order`` key must be defined in an array structure like below:: + + class ArticlesController extends AppController + { + public $paginate = [ + 'limit' => 25, + 'order' => [ + 'Articles.title' => 'asc' + ] + ]; + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Paginator'); + } + } + +You can also include any of the options supported by +:php:meth:`~Cake\\ORM\\Table::find()`, such as ``fields``:: + + class ArticlesController extends AppController + { + public $paginate = [ + 'fields' => ['Articles.id', 'Articles.created'], + 'limit' => 25, + 'order' => [ + 'Articles.title' => 'asc' + ] + ]; + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Paginator'); + } + } + +While you can pass most of the query options from the paginate property it is +often cleaner and simpler to bundle up your pagination options into +a :ref:`custom-find-methods`. You can define the finder pagination uses by +setting the ``finder`` option:: + + class ArticlesController extends AppController + { + public $paginate = [ + 'finder' => 'published', + ]; + } + +Because custom finder methods can also take in options, this is how you pass in +options into a custom finder method within the paginate property:: + + class ArticlesController extends AppController + { + // find articles by tag + public function tags() + { + $tags = $this->request->getParam('pass'); + + $customFinderOptions = [ + 'tags' => $tags + ]; + // the custom finder method is called findTagged inside ArticlesTable.php + // it should look like this: + // public function findTagged(Query $query, array $options) { + // hence you use tagged as the key + $this->paginate = [ + 'finder' => [ + 'tagged' => $customFinderOptions + ] + ]; + $articles = $this->paginate($this->Articles); + $this->set(compact('articles', 'tags')); + } + } + +In addition to defining general pagination values, you can define more than one +set of pagination defaults in the controller, you just name the keys of the +array after the model you wish to configure:: + + class ArticlesController extends AppController + { + public $paginate = [ + 'Articles' => [], + 'Authors' => [], + ]; + } + +The values of the ``Articles`` and ``Authors`` keys could contain all the properties +that a model/key less ``$paginate`` array could. + +Once the ``$paginate`` property has been defined, we can use the +:php:meth:`~Cake\\Controller\\Controller::paginate()` method to create the +pagination data, and add the ``PaginatorHelper`` if it hasn't already been +added. The controller's paginate method will return the result set of the +paginated query, and set pagination metadata to the request. You can access the +pagination metadata at ``$this->request->getParam('paging')``. A more complete +example of using ``paginate()`` would be:: + + class ArticlesController extends AppController + { + public function index() + { + $this->set('articles', $this->paginate()); + } + } + +By default the ``paginate()`` method will use the default model for +a controller. You can also pass the resulting query of a find method:: + + public function index() + { + $query = $this->Articles->find('popular')->where(['author_id' => 1]); + $this->set('articles', $this->paginate($query)); + } + +If you want to paginate a different model you can provide a query for it, the +table object itself, or its name:: + + // Using a query + $comments = $this->paginate($commentsTable->find()); + + // Using the model name. + $comments = $this->paginate('Comments'); + + // Using a table object. + $comments = $this->paginate($commentTable); + +Using the Paginator Directly +============================ + +If you need to paginate data from another component you may want to use the +PaginatorComponent directly. It features a similar API to the controller +method:: + + $articles = $this->Paginator->paginate($articleTable->find(), $config); + + // Or + $articles = $this->Paginator->paginate($articleTable, $config); + +The first parameter should be the query object from a find on table object you +wish to paginate results from. Optionally, you can pass the table object and let +the query be constructed for you. The second parameter should be the array of +settings to use for pagination. This array should have the same structure as the +``$paginate`` property on a controller. When paginating a ``Query`` object, the +``finder`` option will be ignored. It is assumed that you are passing in +the query you want paginated. + +.. _paginating-multiple-queries: + +Paginating Multiple Queries +=========================== + +You can paginate multiple models in a single controller action, using the +``scope`` option both in the controller's ``$paginate`` property and in the +call to the ``paginate()`` method:: + + // Paginate property + public $paginate = [ + 'Articles' => ['scope' => 'article'], + 'Tags' => ['scope' => 'tag'] + ]; + + // In a controller action + $articles = $this->paginate($this->Articles, ['scope' => 'article']); + $tags = $this->paginate($this->Tags, ['scope' => 'tag']); + $this->set(compact('articles', 'tags')); + +The ``scope`` option will result in ``PaginatorComponent`` looking in +scoped query string parameters. For example, the following URL could be used to +paginate both tags and articles at the same time:: + + /dashboard?article[page]=1&tag[page]=3 + +See the :ref:`paginator-helper-multiple` section for how to generate scoped HTML +elements and URLs for pagination. + +.. versionadded:: 3.3.0 + Multiple Pagination was added in 3.3.0 + +Control which Fields Used for Ordering +====================================== + +By default sorting can be done on any non-virtual column a table has. This is +sometimes undesirable as it allows users to sort on un-indexed columns that can +be expensive to order by. You can set the whitelist of fields that can be sorted +using the ``sortWhitelist`` option. This option is required when you want to +sort on any associated data, or computed fields that may be part of your +pagination query:: + + public $paginate = [ + 'sortWhitelist' => [ + 'id', 'title', 'Users.username', 'created' + ] + ]; + +Any requests that attempt to sort on fields not in the whitelist will be +ignored. + +Limit the Maximum Number of Rows per Page +========================================= + +The number of results that are fetched per page is exposed to the user as the +``limit`` parameter. It is generally undesirable to allow users to fetch all +rows in a paginated set. The ``maxLimit`` option asserts that no one can set +this limit too high from the outside. By default CakePHP limits the maximum number of rows +that can be fetched to 100. If this default is not appropriate for your +application, you can adjust it as part of the pagination options, for example reducing it to ``10``:: + + public $paginate = [ + // Other keys here. + 'maxLimit' => 10 + ]; + +If the request's limit param is greater than this value, it will be reduced to +the ``maxLimit`` value. + +Joining Additional Associations +=============================== + +Additional associations can be loaded to the paginated table by using the +``contain`` parameter:: + + public function index() + { + $this->paginate = [ + 'contain' => ['Authors', 'Comments'] + ]; + + $this->set('articles', $this->paginate($this->Articles)); + } + +Out of Range Page Requests +========================== + +The PaginatorComponent will throw a ``NotFoundException`` when trying to +access a non-existent page, i.e. page number requested is greater than total +page count. + +So you could either let the normal error page be rendered or use a try catch +block and take appropriate action when a ``NotFoundException`` is caught:: + + use Cake\Network\Exception\NotFoundException; + + public function index() + { + try { + $this->paginate(); + } catch (NotFoundException $e) { + // Do something here like redirecting to first or last page. + // $this->request->getParam('paging') will give you required info. + } + } + +Pagination in the View +====================== + +Check the :php:class:`~Cake\\View\\Helper\\PaginatorHelper` documentation for +how to create links for pagination navigation. + +.. meta:: + :title lang=en: Pagination + :keywords lang=en: order array,query conditions,php class,web applications,headaches,obstacles,complexity,programmers,parameters,paginate,designers,cakephp,satisfaction,developers diff --git a/tl/controllers/components/request-handling.rst b/tl/controllers/components/request-handling.rst new file mode 100644 index 0000000000000000000000000000000000000000..235aa1f88b247a06c29e7a3955d7a5687c4a9ed4 --- /dev/null +++ b/tl/controllers/components/request-handling.rst @@ -0,0 +1,280 @@ +Request Handling +################ + +.. php:class:: RequestHandlerComponent(ComponentCollection $collection, array $config = []) + +The Request Handler component is used in CakePHP to obtain additional +information about the HTTP requests that are made to your application. You can +use it to see what content types clients prefer, automatically parse request +input, define how content types map to view classes or template paths. + +By default RequestHandler will automatically detect AJAX requests based on the +``X-Requested-With`` HTTP header that many JavaScript libraries use. When used +in conjunction with :php:meth:`Cake\\Routing\\Router::extensions()`, +RequestHandler will automatically switch the layout and template files to those +that match non-HTML media types. Furthermore, if a helper with the same name as +the requested extension exists, it will be added to the Controllers Helper +array. Lastly, if XML/JSON data is POST'ed to your Controllers, it will be +parsed into an array which is assigned to ``$this->request->getData()``, and can then +be accessed as you would standard POST data. In order to make use of +RequestHandler it must be included in your ``initialize()`` method:: + + class WidgetsController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + // Rest of controller + } + +Obtaining Request Information +============================= + +Request Handler has several methods that provide information about +the client and its request. + +.. php:method:: accepts($type = null) + + $type can be a string, or an array, or null. If a string, ``accepts()`` + will return ``true`` if the client accepts the content type. If an + array is specified, ``accepts()`` return ``true`` if any one of the content + types is accepted by the client. If null returns an array of the + content-types that the client accepts. For example:: + + class ArticlesController extends AppController + { + + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function beforeFilter(Event $event) + { + if ($this->RequestHandler->accepts('html')) { + // Execute code only if client accepts an HTML (text/html) + // response. + } elseif ($this->RequestHandler->accepts('xml')) { + // Execute XML-only code + } + if ($this->RequestHandler->accepts(['xml', 'rss', 'atom'])) { + // Executes if the client accepts any of the above: XML, RSS + // or Atom. + } + } + } + +Other request 'type' detection methods include: + +.. php:method:: isXml() + + Returns ``true`` if the current request accepts XML as a response. + +.. php:method:: isRss() + + Returns ``true`` if the current request accepts RSS as a response. + +.. php:method:: isAtom() + + Returns ``true`` if the current call accepts an Atom response, false + otherwise. + +.. php:method:: isMobile() + + Returns ``true`` if user agent string matches a mobile web browser, or + if the client accepts WAP content. The supported Mobile User Agent + strings are: + + - Android + - AvantGo + - BlackBerry + - DoCoMo + - Fennec + - iPad + - iPhone + - iPod + - J2ME + - MIDP + - NetFront + - Nokia + - Opera Mini + - Opera Mobi + - PalmOS + - PalmSource + - portalmmm + - Plucker + - ReqwirelessWeb + - SonyEricsson + - Symbian + - UP.Browser + - webOS + - Windows CE + - Windows Phone OS + - Xiino + +.. php:method:: isWap() + + Returns ``true`` if the client accepts WAP content. + +All of the above request detection methods can be used in a similar +fashion to filter functionality intended for specific content +types. For example when responding to AJAX requests, you often will +want to disable browser caching, and change the debug level. +However, you want to allow caching for non-AJAX requests. The +following would accomplish that:: + + if ($this->request->is('ajax')) { + $this->response->disableCache(); + } + // Continue Controller action + +Automatically Decoding Request Data +=================================== + +Add a request data decoder. The handler should contain a callback, and any +additional arguments for the callback. The callback should return +an array of data contained in the request input. For example adding a CSV +handler could look like:: + + class ArticlesController extends AppController + { + public function initialize() + { + parent::initialize(); + $parser = function ($data) { + $rows = str_getcsv($data, "\n"); + foreach ($rows as &$row) { + $row = str_getcsv($row, ','); + } + return $rows; + }; + $this->loadComponent('RequestHandler', [ + 'inputTypeMap' => [ + 'csv' => [$parser] + ] + ]); + } + } + +You can use any `callable `_ for the handling function. +You can also pass additional arguments to the callback, this is useful for +callbacks like ``json_decode``:: + + $this->RequestHandler->addInputType('json', ['json_decode', true]); + + // After 3.1.0 you should use + $this->RequestHandler->config('inputTypeMap.json', ['json_decode', true]); + +The above will make ``$this->request->getData()`` an array of the JSON input data, +without the additional ``true`` you'd get a set of ``stdClass`` objects. + +.. deprecated:: 3.1.0 + As of 3.1.0 the ``addInputType()`` method is deprecated. You should use + ``config()`` to add input types at runtime. + +Checking Content-Type Preferences +================================= + +.. php:method:: prefers($type = null) + +Determines which content-types the client prefers. If no parameter +is given the most likely content type is returned. If $type is an +array the first type the client accepts will be returned. +Preference is determined primarily by the file extension parsed by +Router if one has been provided, and secondly by the list of +content-types in ``HTTP_ACCEPT``:: + + $this->RequestHandler->prefers('json'); + +Responding To Requests +====================== + +.. php:method:: renderAs($controller, $type) + +Change the render mode of a controller to the specified type. Will +also append the appropriate helper to the controller's helper array +if available and not already in the array:: + + // Force the controller to render an xml response. + $this->RequestHandler->renderAs($this, 'xml'); + +This method will also attempt to add a helper that matches your current content +type. For example if you render as ``rss``, the ``RssHelper`` will be added. + +.. php:method:: respondAs($type, $options) + +Sets the response header based on content-type map names. This method lets you +set a number of response properties at once:: + + $this->RequestHandler->respondAs('xml', [ + // Force download + 'attachment' => true, + 'charset' => 'UTF-8' + ]); + +.. php:method:: responseType() + +Returns the current response type Content-type header or null if one has yet to +be set. + +Taking Advantage of HTTP Cache Validation +========================================= + +The HTTP cache validation model is one of the processes used for cache +gateways, also known as reverse proxies, to determine if they can serve a +stored copy of a response to the client. Under this model, you mostly save +bandwidth, but when used correctly you can also save some CPU processing, +reducing this way response times. + +Enabling the RequestHandlerComponent in your controller automatically activates +a check done before rendering the view. This check compares the response object +against the original request to determine whether the response was not modified +since the last time the client asked for it. + +If response is evaluated as not modified, then the view rendering process is +stopped, saving processing time, saving bandwidth and no content is returned to +the client. The response status code is then set to ``304 Not Modified``. + +You can opt-out this automatic checking by setting the ``checkHttpCache`` +setting to ``false``:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler', [ + 'checkHttpCache' => false + ]); + } + +Using Custom ViewClasses +======================== + +When using JsonView/XmlView you might want to override the default serialization +with a custom View class, or add View classes for other types. + +You can map existing and new types to your custom classes. You can also set this +automatically by using the ``viewClassMap`` setting:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler', [ + 'viewClassMap' => [ + 'json' => 'ApiKit.MyJson', + 'xml' => 'ApiKit.MyXml', + 'csv' => 'ApiKit.Csv' + ] + ]); + } + +.. deprecated:: 3.1.0 + As of 3.1.0 the ``viewClassMap()`` method is deprecated. You should use + ``config()`` to change the viewClassMap at runtime. + +.. meta:: + :title lang=en: Request Handling + :keywords lang=en: handler component,javascript libraries,public components,null returns,model data,request data,content types,file extensions,ajax,meth,content type,array,conjunction,cakephp,insight,php diff --git a/tl/controllers/components/security.rst b/tl/controllers/components/security.rst new file mode 100644 index 0000000000000000000000000000000000000000..864a220ed6840e36e245c1c6b1927ca988690fe0 --- /dev/null +++ b/tl/controllers/components/security.rst @@ -0,0 +1,263 @@ +Security +######## + +.. php:class:: SecurityComponent(ComponentCollection $collection, array $config = []) + +The Security Component creates an easy way to integrate tighter +security in your application. It provides methods for various tasks like: + +* Restricting which HTTP methods your application accepts. +* Form tampering protection +* Requiring that SSL be used. +* Limiting cross controller communication. + +Like all components it is configured through several configurable parameters. +All of these properties can be set directly or through setter methods of the +same name in your controller's ``beforeFilter()``. + +By using the Security Component you automatically get form tampering protection. +Hidden token fields will automatically be inserted into forms and checked by the +Security component. + +If you are using Security component's form protection features and +other components that process form data in their ``startup()`` +callbacks, be sure to place Security Component before those +components in your ``initialize()`` method. + +.. note:: + + When using the Security Component you **must** use the FormHelper to create + your forms. In addition, you must **not** override any of the fields' "name" + attributes. The Security Component looks for certain indicators that are + created and managed by the FormHelper (especially those created in + :php:meth:`~Cake\\View\\Helper\\FormHelper::create()` and + :php:meth:`~Cake\\View\\Helper\\FormHelper::end()`). Dynamically altering + the fields that are submitted in a POST request (e.g. disabling, deleting + or creating new fields via JavaScript) is likely to cause the request to be + send to the blackhole callback. + + You should always verify the HTTP method being used before executing to avoid + side-effects. You should :ref:`check the HTTP method ` or + use :php:meth:`Cake\\Http\\ServerRequest::allowMethod()` to ensure the correct + HTTP method is used. + +Handling Blackhole Callbacks +============================ + +.. php:method:: blackHole(object $controller, string $error = '', SecurityException $exception = null) + +If an action is restricted by the Security Component it is +'black-holed' as an invalid request which will result in a 400 error +by default. You can configure this behavior by setting the +``blackHoleCallback`` configuration option to a callback function +in the controller. + +By configuring a callback method you can customize how the blackhole process +works:: + + public function beforeFilter(Event $event) + { + $this->Security->setConfig('blackHoleCallback', 'blackhole'); + } + + public function blackhole($type) + { + // Handle errors. + } + +.. note:: + + use ``$this->Security->config()`` for CakePHP versions prior to 3.4 + +The ``$type`` parameter can have the following values: + +* 'auth' Indicates a form validation error, or a controller/action mismatch + error. +* 'secure' Indicates an SSL method restriction failure. + +.. versionadded:: cakephp/cakephp 3.2.6 + + As of v3.2.6 an additional parameter is included in the blackHole callback, + an instance of the ``Cake\Controller\Exception\SecurityException`` is + included as a second parameter. + +Restrict Actions to SSL +======================= + +.. php:method:: requireSecure() + + Sets the actions that require a SSL-secured request. Takes any + number of arguments. Can be called with no arguments to force all + actions to require a SSL-secured. + +.. php:method:: requireAuth() + + Sets the actions that require a valid Security Component generated + token. Takes any number of arguments. Can be called with no + arguments to force all actions to require a valid authentication. + +Restricting Cross Controller Communication +========================================== + +allowedControllers + A list of controllers which can send requests + to this controller. + This can be used to control cross controller requests. +allowedActions + A list of actions which are allowed to send requests + to this controller's actions. + This can be used to control cross controller requests. + +These configuration options allow you to restrict cross controller +communication. Set them with the ``setConfig()`` method, or +``config()`` if you are using a CakePHP version below 3.4. + +Form Tampering Prevention +========================= + +By default the ``SecurityComponent`` prevents users from tampering with forms in +specific ways. The ``SecurityComponent`` will prevent the following things: + +* Unknown fields cannot be added to the form. +* Fields cannot be removed from the form. +* Values in hidden inputs cannot be modified. + +Preventing these types of tampering is accomplished by working with the ``FormHelper`` +and tracking which fields are in a form. The values for hidden fields are +tracked as well. All of this data is combined and turned into a hash. When +a form is submitted, the ``SecurityComponent`` will use the POST data to build the same +structure and compare the hash. + +.. note:: + + The SecurityComponent will **not** prevent select options from being + added/changed. Nor will it prevent radio options from being added/changed. + +unlockedFields + Set to a list of form fields to exclude from POST validation. Fields can be + unlocked either in the Component, or with + :php:meth:`FormHelper::unlockField()`. Fields that have been unlocked are + not required to be part of the POST and hidden unlocked fields do not have + their values checked. + +validatePost + Set to ``false`` to completely skip the validation of POST + requests, essentially turning off form validation. + +The above configuration options can be set with ``setConfig()`` or +``config()`` for CakePHP versions below 3.4. + +Usage +===== + +Using the security component is generally done in the controller's +``beforeFilter()``. You would specify the security restrictions you +want and the Security Component will enforce them on its startup:: + + namespace App\Controller; + + use App\Controller\AppController; + use Cake\Event\Event; + + class WidgetsController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Security'); + } + + public function beforeFilter(Event $event) + { + if ($this->request->getParam('admin')) { + $this->Security->requireSecure(); + } + } + } + +The above example would force all actions that had admin routing to +require secure SSL requests:: + + namespace App\Controller; + + use App\Controller\AppController; + use Cake\Event\Event; + + class WidgetsController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Security', ['blackHoleCallback' => 'forceSSL']); + } + + public function beforeFilter(Event $event) + { + if ($this->request->getParam('admin')) { + $this->Security->requireSecure(); + } + } + + public function forceSSL() + { + return $this->redirect('https://' . env('SERVER_NAME') . $this->request->getRequestTarget()); + } + } + +.. note:: + + Use ``$this->request->here()`` for CakePHP versions prior to 3.4.0 + +This example would force all actions that had admin routing to require secure +SSL requests. When the request is black holed, it will call the nominated +``forceSSL()`` callback which will redirect non-secure requests to secure +requests automatically. + +.. _security-csrf: + +CSRF Protection +=============== + +CSRF or Cross Site Request Forgery is a common vulnerability in web +applications. It allows an attacker to capture and replay a previous request, +and sometimes submit data requests using image tags or resources on other +domains. To enable CSRF protection features use the +:doc:`/controllers/components/csrf`. + +Disabling Security Component for Specific Actions +================================================= + +There may be cases where you want to disable all security checks for an action +(ex. AJAX requests). You may "unlock" these actions by listing them in +``$this->Security->unlockedActions`` in your ``beforeFilter()``. The +``unlockedActions`` property will **not** affect other features of +``SecurityComponent``:: + + namespace App\Controller; + + use App\Controller\AppController; + use Cake\Event\Event; + + class WidgetController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Security'); + } + + public function beforeFilter(Event $event) + { + $this->Security->setConfig('unlockedActions', ['edit']); + } + } + +.. note:: + + Use ``$this->Security->config()`` for CakePHP versions prior to 3.4.0 + +This example would disable all security checks for the edit action. + +.. meta:: + :title lang=en: Security + :keywords lang=en: configurable parameters,security component,configuration parameters,invalid request,protection features,tighter security,holing,php class,meth,404 error,period of inactivity,csrf,array,submission,security class,disable security,unlockActions diff --git a/tl/controllers/middleware.rst b/tl/controllers/middleware.rst new file mode 100644 index 0000000000000000000000000000000000000000..cac60286650d3fb8ac9d2d79812a8e51ea6a264c --- /dev/null +++ b/tl/controllers/middleware.rst @@ -0,0 +1,439 @@ +Middleware +########## + +Middleware objects give you the ability to 'wrap' your application in re-usable, +composable layers of Request handling, or response building logic. Visually, +your application ends up at the center, and middleware is wrapped aroud the app +like an onion. Here we can see an application wrapped with Routes, Assets, +Exception Handling and CORS header middleware. + +.. image:: /_static/img/middleware-setup.png + +When a request is handled by your application it enters from the outermost +middleware. Each middleware can either delegate the request/response to the next +layer, or return a response. Returning a response prevents lower layers from +ever seeing the request. An example of that is the AssetMiddleware handling +a request for a plugin image during development. + +.. image:: /_static/img/middleware-request.png + +If no middleware take action to handle the request, a controller will be located +and have its action invoked, or an exception will be raised generating an error +page. + +Middleware are part of the new HTTP stack in CakePHP that leverages the PSR-7 +request and response interfaces. Because CakePHP is leveraging the PSR-7 +standard you can use any PSR-7 compatible middleware available on `The Packagist +`__. + +Middleware in CakePHP +===================== + +CakePHP provides several middleware to handle common tasks in web applications: + +* ``Cake\Error\Middleware\ErrorHandlerMiddleware`` traps exceptions from the + wrapped middleware and renders an error page using the + :doc:`/development/errors` Exception handler. +* ``Cake\Routing\AssetMiddleware`` checks whether the request is referring to a + theme or plugin asset file, such as a CSS, JavaScript or image file stored in + either a plugin's webroot folder or the corresponding one for a Theme. +* ``Cake\Routing\Middleware\RoutingMiddleware`` uses the ``Router`` to parse the + incoming URL and assign routing parameters to the request. +* ``Cake\I18n\Middleware\LocaleSelectorMiddleware`` enables automatic language + switching from the ``Accept-Language`` header sent by the browser. +* ``Cake\Http\Middleware\SecurityHeadersMiddleware`` makes it easy to add + security related headers like ``X-Frame-Options`` to responses. +* ``Cake\Http\Middleware\EncryptedCookieMiddleware`` gives you the ability to + manipulate encrypted cookies in case you need to manipulate cookie with + obfuscated data. +* ``Cake\Http\Middleware\CsrfProtectionMiddleware`` adds CSRF protection to your + application. + +.. _using-middleware: + +Using Middleware +================ + +Middleware can be applied to your application globally, or to individual +routing scopes. + +To apply middleware to all requests, use the ``middleware`` method of your +``App\Application`` class. If you don't have an ``App\Application`` class, see +the section on :ref:`adding-http-stack` for more information. Your application's +``middleware`` hook method will be called at the beginning of the request +process, you can use the ``MiddlewareQueue`` object to attach middleware:: + + namespace App; + + use Cake\Http\BaseApplication; + use Cake\Error\Middleware\ErrorHandlerMiddleware; + + class Application extends BaseApplication + { + public function middleware($middlewareQueue) + { + // Bind the error handler into the middleware queue. + $middlewareQueue->add(new ErrorHandlerMiddleware()); + return $middlewareQueue; + } + } + +In addition to adding to the end of the ``MiddlewareQueue`` you can do +a variety of operations:: + + $layer = new \App\Middleware\CustomMiddleware; + + // Added middleware will be last in line. + $middlewareQueue->add($layer); + + // Prepended middleware will be first in line. + $middlewareQueue->prepend($layer); + + // Insert in a specific slot. If the slot is out of + // bounds, it will be added to the end. + $middlewareQueue->insertAt(2, $layer); + + // Insert before another middleware. + // If the named class cannot be found, + // an exception will be raised. + $middlewareQueue->insertBefore( + 'Cake\Error\Middleware\ErrorHandlerMiddleware', + $layer + ); + + // Insert after another middleware. + // If the named class cannot be found, the + // middleware will added to the end. + $middlewareQueue->insertAfter( + 'Cake\Error\Middleware\ErrorHandlerMiddleware', + $layer + ); + +In addition to applying middleware to your entire application, you can apply +middleware to specific sets of routes using :ref:`connecting-scoped-middleware`. + +Adding Middleware from Plugins +------------------------------ + +After the middleware queue has been prepared by the application, the +``Server.buildMiddleware`` event is triggered. This event can be useful to add +middleware from plugins. Plugins can register listeners in their bootstrap +scripts, that add middleware:: + + // In ContactManager plugin bootstrap.php + use Cake\Event\EventManager; + + EventManager::instance()->on( + 'Server.buildMiddleware', + function ($event, $middlewareQueue) { + $middlewareQueue->add(new ContactPluginMiddleware()); + }); + +PSR-7 Requests and Responses +============================ + +Middleware and the new HTTP stack are built on top of the `PSR-7 Request +& Response Interfaces `__. While all +middleware will be exposed to these interfaces, your controllers, components, +and views will *not*. + +Interacting with Requests +------------------------- + +The ``RequestInterface`` provides methods for interacting with the headers, +method, URI, and body of a request. To interact with the headers, you can:: + + // Read a header as text + $value = $request->getHeaderLine('Content-Type'); + + // Read header as an array + $value = $request->getHeader('Content-Type'); + + // Read all the headers as an associative array. + $headers = $request->getHeaders(); + +Requests also give access to the cookies and uploaded files they contain:: + + // Get an array of cookie values. + $cookies = $request->getCookieParams(); + + // Get a list of UploadedFile objects + $files = $request->getUploadedFiles(); + + // Read the file data. + $files[0]->getStream(); + $files[0]->getSize(); + $files[0]->getClientFileName(); + + // Move the file. + $files[0]->moveTo($targetPath); + +Requests contain a URI object, which contains methods for interacting with the +requested URI:: + + // Get the URI + $uri = $request->getUri(); + + // Read data out of the URI. + $path = $uri->getPath(); + $query = $uri->getQuery(); + $host = $uri->getHost(); + +Lastly, you can interact with a request's 'attributes'. CakePHP uses these +attributes to carry framework specific request parameters. There are a few +important attributes in any request handled by CakePHP: + +* ``base`` contains the base directory for your application if there is one. +* ``webroot`` contains the webroot directory for your application. +* ``params`` contains the results of route matching once routing rules have been + processed. +* ``session`` contains an instance of CakePHP's ``Session`` object. See + :ref:`accessing-session-object` for more information on how to use the session + object. + +Interacting with Responses +-------------------------- + +The methods available to create a server response are the same as those +available when interacting with :ref:`httpclient-response-objects`. While the +interface is the same the usage scenarios are different. + +When modifying the response, it is important to remember that responses are +**immutable**. You must always remember to store the results of any setter +method. For example:: + + // This does *not* modify $response. The new object was not + // assigned to a variable. + $response->withHeader('Content-Type', 'application/json'); + + // This works! + $newResponse = $response->withHeader('Content-Type', 'application/json'); + +Most often you'll be setting headers and response bodies on requests:: + + // Assign headers and a status code + $response = $response->withHeader('Content-Type', 'application/json') + ->withHeader('Pragma', 'no-cache') + ->withStatus(422); + + // Write to the body + $body = $response->getBody(); + $body->write(json_encode(['errno' => $errorCode])); + +Creating Middleware +=================== + +Middleware can either be implemented as anonymous functions (Closures), or as +invokable classes. While Closures are suitable for smaller tasks they make +testing harder, and can create a complicated ``Application`` class. Middleware +classes in CakePHP have a few conventions: + +* Middleware class files should be put in **src/Middleware**. For example: + **src/Middleware/CorsMiddleware.php** +* Middleware classes should be suffixed with ``Middleware``. For example: + ``LinkMiddleware``. +* Middleware are expected to implement the middleware protocol. + +While not a formal interface (yet), Middleware do have a soft-interface or +'protocol'. The protocol is as follows: + +#. Middleware must implement ``__invoke($request, $response, $next)``. +#. Middleware must return an object implementing the PSR-7 ``ResponseInterface``. + +Middleware can return a response either by calling ``$next`` or by creating +their own response. We can see both options in our simple middleware:: + + // In src/Middleware/TrackingCookieMiddleware.php + namespace App\Middleware; + use Cake\I18n\Time; + + class TrackingCookieMiddleware + { + public function __invoke($request, $response, $next) + { + // Calling $next() delegates control to the *next* middleware + // In your application's queue. + $response = $next($request, $response); + + // When modifying the response, you should do it + // *after* calling next. + if (!$request->getCookie('landing_page')) { + $expiry = new Time('+ 1 year'); + $response = $response->withCookie('landing_page' ,[ + 'value' => $request->here(), + 'expire' => $expiry->format('U'), + ]); + } + return $response; + } + } + +Now that we've made a very simple middleware, let's attach it to our +application:: + + // In src/Application.php + namespace App; + + use App\Middleware\TrackingCookieMiddleware; + + class Application + { + public function middleware($middlewareQueue) + { + // Add your simple middleware onto the queue + $middlewareQueue->add(new TrackingCookieMiddleware()); + + // Add some more middleware onto the queue + + return $middlewareQueue; + } + } + +.. _security-header-middleware: + +Adding Security Headers +======================= + +The ``SecurityHeaderMiddleware`` layer makes it easy to apply security related +headers to your application. Once setup the middleware can apply the following +headers to responses: + +* ``X-Content-Type-Options`` +* ``X-Download-Options`` +* ``X-Frame-Options`` +* ``X-Permitted-Cross-Domain-Policies`` +* ``Referrer-Policy`` + +This middleware is configured using a fluent interface before it is applied to +your application's middleware stack:: + + use Cake\Http\Middleware\SecurityHeadersMiddleware; + + $headers = new SecurityHeadersMiddleware(); + $headers + ->setCrossDomainPolicy() + ->setReferrerPolicy() + ->setXFrameOptions() + ->setXssProtection() + ->noOpen() + ->noSniff(); + + $middlewareQueue->add($headers); + +.. versionadded:: 3.5.0 + The ``SecurityHeadersMiddleware`` was added in 3.5.0 + +.. _encrypted-cookie-middleware: + +Encrypted Cookie Middleware +=========================== + +If your application has cookies that contain data you want to obfuscate and +protect against user tampering, you can use CakePHP's encrypted cookie +middleware to transparently encrypt and decrypt cookie data via middleware. +Cookie data is encrypted with via OpenSSL using AES:: + + use Cake\Http\Middleware\EncryptedCookieMiddleware; + + $cookies = new EncryptedCookieMiddleware( + // Names of cookies to protect + ['secrets', 'protected'], + Configure::read('Security.cookieKey') + ); + + $middlewareQueue->add($cookies); + +.. note:: + It is recommended that the encryption key you use for cookie data, is used + *exclusively* for cookie data. + +The encryption algorithms and padding style used by the cookie middleware are +backwards compatible with ``CookieComponent`` from earlier versions of CakePHP. + +.. versionadded:: 3.5.0 + The ``EncryptedCookieMiddleware`` was added in 3.5.0 + +.. _csrf-middleware: + +Cross Site Request Forgery (CSRF) Middleware +============================================ + +CSRF protection can be applied to your entire application, or to specific scopes +by applying the ``CsrfProtectionMiddleware`` to your middleware stack:: + + use Cake\Http\Middleware\CsrfProtectionMiddleware; + + $options = [ + // ... + ]; + $csrf = new CsrfProtectionMiddleware($options); + + $middlewareQueue->add($csrf); + +Options can be passed into the middleware's constructor. +The available configuration options are: + +- ``cookieName`` The name of the cookie to send. Defaults to ``csrfToken``. +- ``expiry`` How long the CSRF token should last. Defaults to browser session. +- ``secure`` Whether or not the cookie will be set with the Secure flag. That is, + the cookie will only be set on a HTTPS connection and any attempt over normal HTTP + will fail. Defaults to ``false``. +- ``httpOnly`` Whether or not the cookie will be set with the HttpOnly flag. Defaults to ``false``. +- ``field`` The form field to check. Defaults to ``_csrfToken``. Changing this + will also require configuring FormHelper. + +When enabled, you can access the current CSRF token on the request object:: + + $token = $this->request->getParam('_csrfToken'); + +.. versionadded:: 3.5.0 + The ``CsrfProtectionMiddleware`` was added in 3.5.0 + +Integration with FormHelper +--------------------------- + +The ``CsrfProtectionMiddleware`` integrates seamlessly with ``FormHelper``. Each +time you create a form with ``FormHelper``, it will insert a hidden field containing +the CSRF token. + +.. note:: + + When using CSRF protection you should always start your forms with the + ``FormHelper``. If you do not, you will need to manually create hidden inputs in + each of your forms. + +CSRF Protection and AJAX Requests +--------------------------------- + +In addition to request data parameters, CSRF tokens can be submitted through +a special ``X-CSRF-Token`` header. Using a header often makes it easier to +integrate a CSRF token with JavaScript heavy applications, or XML/JSON based API +endpoints. + +The CSRF Token can be obtained via the Cookie ``csrfToken``. + +.. _adding-http-stack: + +Adding the new HTTP Stack to an Existing Application +==================================================== + +Using HTTP Middleware in an existing application requires a few changes to your +application. + +#. First update your **webroot/index.php**. Copy the file contents from the `app + skeleton `__. +#. Create an ``Application`` class. See the :ref:`using-middleware` section + above for how to do that. Or copy the example in the `app skeleton + `__. +#. Create **config/requirements.php** if it doesn't exist and add the contents from the `app skeleton `__. + +Once those three steps are complete, you are ready to start re-implementing any +application/plugin dispatch filters as HTTP middleware. + +If you are running tests you will also need to update your +**tests/bootstrap.php** by copying the file contents from the `app skeleton +`_. + +.. meta:: + :title lang=en: Http Middleware + :keywords lang=en: http, middleware, psr-7, request, response, wsgi, application, baseapplication diff --git a/tl/controllers/pages-controller.rst b/tl/controllers/pages-controller.rst new file mode 100644 index 0000000000000000000000000000000000000000..32d55cfaa3fb71917293a5cd3ad26a0a9d802b55 --- /dev/null +++ b/tl/controllers/pages-controller.rst @@ -0,0 +1,17 @@ +The Pages Controller +#################### + +CakePHP's official skeleton app ships with a default controller **PagesController.php**. +This is a simple and optional controller for serving up static content. The home page +you see after installation is generated using this controller and the view +file **src/Template/Pages/home.ctp**. If you make the view file +**src/Template/Pages/about_us.ctp** you can access it using the URL +**http://example.com/pages/about_us**. You are free to modify the Pages +Controller to meet your needs. + +When you "bake" an app using Composer the Pages Controller is created in your +**src/Controller/** folder. + +.. meta:: + :title lang=en: The Pages Controller + :keywords lang=en: pages controller,default controller,cakephp,ships,php,file folder,home page diff --git a/tl/controllers/request-response.rst b/tl/controllers/request-response.rst new file mode 100644 index 0000000000000000000000000000000000000000..98947599360cb3770476bef1360fb670d84d1152 --- /dev/null +++ b/tl/controllers/request-response.rst @@ -0,0 +1,1081 @@ +Request & Response Objects +########################## + +.. php:namespace:: Cake\Http + +The request and response objects provide an abstraction around HTTP requests and +responses. The request object in CakePHP allows you to introspect an incoming +request, while the response object allows you to effortlessly create HTTP +responses from your controllers. + +.. index:: $this->request +.. _cake-request: + +Request +======= + +.. php:class:: ServerRequest + +``ServerRequest`` is the default request object used in CakePHP. It centralizes a +number of features for interrogating and interacting with request data. +On each request one Request is created and then passed by reference to the +various layers of an application that use request data. By default the request +is assigned to ``$this->request``, and is available in Controllers, Cells, Views +and Helpers. You can also access it in Components using the controller +reference. Some of the duties ``ServerRequest`` performs include: + +* Processing the GET, POST, and FILES arrays into the data structures you are + familiar with. +* Providing environment introspection pertaining to the request. Information + like the headers sent, the client's IP address, and the subdomain/domain + names the server your application is running on. +* Providing access to request parameters both as array indexes and object + properties. + +As of 3.4.0, CakePHP's request object implements the `PSR-7 +ServerRequestInterface `_ making it easier to +use libraries from outside of CakePHP. + +Request Parameters +------------------ + +The request exposes the routing parameters through the ``getParam()`` method:: + + $controllerName = $this->request->getParam('controller'); + + // Prior to 3.4.0 + $controllerName = $this->request->param('controller'); + +All :ref:`route-elements` are accessed through this interface. + +In addition to :ref:`route-elements`, you also often need access to +:ref:`passed-arguments`. These are both available on the request object as +well:: + + // Passed arguments + $passedArgs = $this->request->getParam('pass'); + +Will all provide you access to the passed arguments. There +are several important/useful parameters that CakePHP uses internally, these +are also all found in the routing parameters: + +* ``plugin`` The plugin handling the request. Will be null when there is no + plugin. +* ``controller`` The controller handling the current request. +* ``action`` The action handling the current request. +* ``prefix`` The prefix for the current action. See :ref:`prefix-routing` for + more information. + +Query String Parameters +----------------------- + +.. php:method:: getQuery($name) + +Query string parameters can be read using the ``getQuery()`` method:: + + // URL is /posts/index?page=1&sort=title + $page = $this->request->getQuery('page'); + + // Prior to 3.4.0 + $page = $this->request->query('page'); + +You can either directly access the query property, or you can use +``getQuery()`` method to read the URL query array in an error-free manner. +Any keys that do not exist will return ``null``:: + + $foo = $this->request->getQuery('value_that_does_not_exist'); + // $foo === null + + // You can also provide default values + $foo = $this->request->getQuery('does_not_exist', 'default val'); + +If you want to access all the query parameters you can use +``getQueryParams()``:: + + $query = $this->request->getQueryParams(); + +.. versionadded:: 3.4.0 + ``getQueryParams()`` and ``getQuery()`` were added in 3.4.0 + +Request Body Data +----------------- + +.. php:method:: getData($name, $default = null) + +All POST data can be accessed using +:php:meth:`Cake\\Http\\ServerRequest::getData()`. Any form data that +contains a ``data`` prefix will have that data prefix removed. For example:: + + // An input with a name attribute equal to 'MyModel[title]' is accessible at + $title = $this->request->getData('MyModel.title'); + +Any keys that do not exist will return ``null``:: + + $foo = $this->request->getData('Value.that.does.not.exist'); + // $foo == null + +PUT, PATCH or DELETE Data +------------------------- + +.. php:method:: input($callback, [$options]) + +When building REST services, you often accept request data on ``PUT`` and +``DELETE`` requests. Any ``application/x-www-form-urlencoded`` request body data +will automatically be parsed and set to ``$this->data`` for ``PUT`` and +``DELETE`` requests. If you are accepting JSON or XML data, see below for how +you can access those request bodies. + +When accessing the input data, you can decode it with an optional function. +This is useful when interacting with XML or JSON request body content. +Additional parameters for the decoding function can be passed as arguments to +``input()``:: + + $jsonData = $this->request->input('json_decode'); + +Environment Variables (from $_SERVER and $_ENV) +----------------------------------------------- + +.. php:method:: env($key, $value = null) + +``ServerRequest::env()`` is a wrapper for ``env()`` global function and acts as +a getter/setter for enviromnent variables without having to modify globals +``$_SERVER`` and ``$_ENV``:: + + // Get the host + $host = $this->request->env('HTTP_HOST'); + + // Set a value, generally helpful in testing. + $this->request->env('REQUEST_METHOD', 'POST'); + +To access all the environment variables in a request use ``getServerParams()``:: + + $env = $this->request->getServerParams(); + +.. versionadded:: 3.4.0 + ``getServerParams()`` was added in 3.4.0 + +XML or JSON Data +---------------- + +Applications employing :doc:`/development/rest` often exchange data in +non-URL-encoded post bodies. You can read input data in any format using +:php:meth:`~Cake\\Http\\ServerRequest::input()`. By providing a decoding function, +you can receive the content in a deserialized format:: + + // Get JSON encoded data submitted to a PUT/POST action + $jsonData = $this->request->input('json_decode'); + +Some deserializing methods require additional parameters when called, such as +the 'as array' parameter on ``json_decode``. If you want XML converted into a +DOMDocument object, :php:meth:`~Cake\\Http\\ServerRequest::input()` supports +passing in additional parameters as well:: + + // Get XML encoded data submitted to a PUT/POST action + $data = $this->request->input('Cake\Utility\Xml::build', ['return' => 'domdocument']); + +Path Information +---------------- + +The request object also provides useful information about the paths in your +application. The ``base`` and ``webroot`` attributes are useful for +generating URLs, and determining whether or not your application is in a +subdirectory. The attributes you can use are:: + + // Assume the current request URL is /subdir/articles/edit/1?page=1 + + // Holds /subdir/articles/edit/1?page=1 + $here = $request->getRequestTarget(); + + // Holds /subdir + $base = $request->getAttribute('base'); + + // Holds /subdir/ + $base = $request->getAttribute('webroot'); + + // Prior to 3.4.0 + $webroot = $request->webroot; + $base = $request->base; + $here = $request->here(); + +.. _check-the-request: + +Checking Request Conditions +--------------------------- + +.. php:method:: is($type, $args...) + +The request object provides an easy way to inspect certain conditions in a given +request. By using the ``is()`` method you can check a number of common +conditions, as well as inspect other application specific request criteria:: + + $isPost = $this->request->is('post'); + +You can also extend the request detectors that are available, by using +:php:meth:`Cake\\Http\\ServerRequest::addDetector()` to create new kinds of +detectors. There are four different types of detectors that you can create: + +* Environment value comparison - Compares a value fetched from :php:func:`env()` + for equality with the provided value. +* Pattern value comparison - Pattern value comparison allows you to compare a + value fetched from :php:func:`env()` to a regular expression. +* Option based comparison - Option based comparisons use a list of options to + create a regular expression. Subsequent calls to add an already defined + options detector will merge the options. +* Callback detectors - Callback detectors allow you to provide a 'callback' type + to handle the check. The callback will receive the request object as its only + parameter. + +.. php:method:: addDetector($name, $options) + +Some examples would be:: + + // Add an environment detector. + $this->request->addDetector( + 'post', + ['env' => 'REQUEST_METHOD', 'value' => 'POST'] + ); + + // Add a pattern value detector. + $this->request->addDetector( + 'iphone', + ['env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'] + ); + + // Add an option detector + $this->request->addDetector('internalIp', [ + 'env' => 'CLIENT_IP', + 'options' => ['192.168.0.101', '192.168.0.100'] + ]); + + // Add a callback detector. Must be a valid callable. + $this->request->addDetector( + 'awesome', + function ($request) { + return $request->getParam('awesome'); + } + ); + + // Add a detector that uses additional arguments. As of 3.3.0 + $this->request->addDetector( + 'controller', + function ($request, $name) { + return $request->getParam('controller') === $name; + } + ); + +``Request`` also includes methods like +:php:meth:`Cake\\Http\\ServerRequest::domain()`, +:php:meth:`Cake\\Http\\ServerRequest::subdomains()` and +:php:meth:`Cake\\Http\\ServerRequest::host()` to help applications with subdomains, +have a slightly easier life. + +There are several built-in detectors that you can use: + +* ``is('get')`` Check to see whether the current request is a GET. +* ``is('put')`` Check to see whether the current request is a PUT. +* ``is('patch')`` Check to see whether the current request is a PATCH. +* ``is('post')`` Check to see whether the current request is a POST. +* ``is('delete')`` Check to see whether the current request is a DELETE. +* ``is('head')`` Check to see whether the current request is HEAD. +* ``is('options')`` Check to see whether the current request is OPTIONS. +* ``is('ajax')`` Check to see whether the current request came with + X-Requested-With = XMLHttpRequest. +* ``is('ssl')`` Check to see whether the request is via SSL. +* ``is('flash')`` Check to see whether the request has a User-Agent of Flash. +* ``is('requested')`` Check to see whether the request has a query param + 'requested' with value 1. +* ``is('json')`` Check to see whether the request has 'json' extension and + accept 'application/json' mimetype. +* ``is('xml')`` Check to see whether the request has 'xml' extension and accept + 'application/xml' or 'text/xml' mimetype. + +.. versionadded:: 3.3.0 + Detectors can take additional parameters as of 3.3.0. + +Session Data +------------ + +To access the session for a given request use the ``session()`` method:: + + $userName = $this->request->session()->read('Auth.User.name'); + +For more information, see the :doc:`/development/sessions` documentation for how +to use the session object. + +Host and Domain Name +-------------------- + +.. php:method:: domain($tldLength = 1) + +Returns the domain name your application is running on:: + + // Prints 'example.org' + echo $request->domain(); + +.. php:method:: subdomains($tldLength = 1) + +Returns the subdomains your application is running on as an array:: + + // Returns ['my', 'dev'] for 'my.dev.example.org' + $subdomains = $request->subdomains(); + +.. php:method:: host() + +Returns the host your application is on:: + + // Prints 'my.dev.example.org' + echo $request->host(); + +Reading the HTTP Method +----------------------- + +.. php:method:: getMethod() + +Returns the HTTP method the request was made with:: + + // Output POST + echo $request->getMethod(); + + // Prior to 3.4.0 + echo $request->method(); + +Restricting Which HTTP method an Action Accepts +----------------------------------------------- + +.. php:method:: allowMethod($methods) + +Set allowed HTTP methods. If not matched, will throw +``MethodNotAllowedException``. The 405 response will include the required +``Allow`` header with the passed methods:: + + public function delete() + { + // Only accept POST and DELETE requests + $this->request->allowMethod(['post', 'delete']); + ... + } + +Reading HTTP Headers +-------------------- + +Allows you to access any of the ``HTTP_*`` headers that were used +for the request. For example:: + + // Get the header as a string + $userAgent = $this->request->getHeaderLine('User-Agent'); + + // Get an array of all values. + $acceptHeader = $this->request->getHeader('Accept'); + + // Check if a header exists + $hasAcceptHeader = $this->request->hasHeader('Accept'); + + // Prior to 3.4.0 + $userAgent = $this->request->header('User-Agent'); + +While some apache installs don't make the ``Authorization`` header accessible, +CakePHP will make it available through apache specific methods as required. + +.. php:method:: referer($local = false) + +Returns the referring address for the request. + +.. php:method:: clientIp() + +Returns the current visitor's IP address. + +Trusting Proxy Headers +---------------------- + +If your application is behind a load balancer or running on a cloud service, you +will often get the load balancer host, port and scheme in your requests. Often +load balancers will also send ``HTTP-X-Forwarded-*`` headers with the original +values. The forwarded headers will not be used by CakePHP out of the box. To +have the request object use these headers set the ``trustProxy`` property to +``true``:: + + $this->request->trustProxy = true; + + // These methods will now use the proxied headers. + $port = $this->request->port(); + $host = $this->request->host(); + $scheme = $this->request->scheme(); + $clientIp = $this->request->clientIp(); + +Checking Accept Headers +----------------------- + +.. php:method:: accepts($type = null) + +Find out which content types the client accepts, or check whether it accepts a +particular type of content. + +Get all types:: + + $accepts = $this->request->accepts(); + +Check for a single type:: + + $acceptsJson = $this->request->accepts('application/json'); + +.. php:method:: acceptLanguage($language = null) + +Get all the languages accepted by the client, +or check whether a specific language is accepted. + +Get the list of accepted languages:: + + $acceptsLanguages = $this->request->acceptLanguage(); + +Check whether a specific language is accepted:: + + $acceptsSpanish = $this->request->acceptLanguage('es-es'); + +.. _request-cookies: + +Cookies +------- + +Request cookies can be read through a number of methods:: + + // Get the cookie value, or null if the cookie is missing. + $rememberMe = $this->request->getCookie('remember_me'); + + // Read the value, or get the default of 0 + $rememberMe = $this->request->getCookie('remember_me', 0); + + // Get all cookies as an hash + $cookies = $this->request->getCookieParams(); + + // Get a CookieCollection instance (starting with 3.5.0) + $cookies = $this->request->getCookieCollection() + +See the :php:class:`Cake\\Http\\Cookie\\CookieCollection` documentation for how +to work with cookie collection. + +.. versionadded:: 3.5.0 + ``ServerRequest::getCookieCollection()`` was added in 3.5.0 + +.. index:: $this->response + +Response +======== + +.. php:class:: Response + +:php:class:`Cake\\Http\\Response` is the default response class in CakePHP. +It encapsulates a number of features and functionality for generating HTTP +responses in your application. It also assists in testing, as it can be +mocked/stubbed allowing you to inspect headers that will be sent. +Like :php:class:`Cake\\Http\\ServerRequest`, :php:class:`Cake\\Http\\Response` +consolidates a number of methods previously found on :php:class:`Controller`, +:php:class:`RequestHandlerComponent` and :php:class:`Dispatcher`. The old +methods are deprecated in favour of using :php:class:`Cake\\Http\\Response`. + +``Response`` provides an interface to wrap the common response-related +tasks such as: + +* Sending headers for redirects. +* Sending content type headers. +* Sending any header. +* Sending the response body. + +Dealing with Content Types +-------------------------- + +.. php:method:: withType($contentType = null) + +You can control the Content-Type of your application's responses with +:php:meth:`Cake\\Http\\Response::withType()`. If your application needs to deal +with content types that are not built into Response, you can map them with +``type()`` as well:: + + // Add a vCard type + $this->response->type(['vcf' => 'text/v-card']); + + // Set the response Content-Type to vcard. + $this->response = $this->response->withType('vcf'); + + // Prior to 3.4.0 + $this->response->type('vcf'); + +Usually, you'll want to map additional content types in your controller's +:php:meth:`~Controller::beforeFilter()` callback, so you can leverage the +automatic view switching features of :php:class:`RequestHandlerComponent` if you +are using it. + +.. _cake-response-file: + +Sending Files +------------- + +.. php:method:: withFile($path, $options = []) + +There are times when you want to send files as responses for your requests. +You can accomplish that by using :php:meth:`Cake\\Http\\Response::withFile()`:: + + public function sendFile($id) + { + $file = $this->Attachments->getFile($id); + $response = $this->response->withFile($file['path']); + // Return the response to prevent controller from trying to render + // a view. + return $response; + } + + // Prior to 3.4.0 + $file = $this->Attachments->getFile($id); + $this->response->file($file['path']); + // Return the response to prevent controller from trying to render + // a view. + return $this->response; + +As shown in the above example, you must pass the file path to the method. +CakePHP will send a proper content type header if it's a known file type listed +in `Cake\\Http\\Reponse::$_mimeTypes`. You can add new types prior to calling +:php:meth:`Cake\\Http\\Response::withFile()` by using the +:php:meth:`Cake\\Http\\Response::withType()` method. + +If you want, you can also force a file to be downloaded instead of displayed in +the browser by specifying the options:: + + $response = $this->response->withFile( + $file['path'], + ['download' => true, 'name' => 'foo'] + ); + + // Prior to 3.4.0 + $this->response->file( + $file['path'], + ['download' => true, 'name' => 'foo'] + ); + +The supported options are: + +name + The name allows you to specify an alternate file name to be sent to + the user. +download + A boolean value indicating whether headers should be set to force + download. + +Sending a String as File +------------------------ + +You can respond with a file that does not exist on the disk, such as a pdf or an +ics generated on the fly from a string:: + + public function sendIcs() + { + $icsString = $this->Calendars->generateIcs(); + $response = $this->response; + $response->body($icsString); + + $response = $response->withType('ics'); + + // Optionally force file download + $response = $response->withDownload('filename_for_download.ics'); + + // Return response object to prevent controller from trying to render + // a view. + return $response; + } + +Callbacks can also return the body as a string:: + + $path = '/some/file.png'; + $this->response->body(function () use ($path) { + return file_get_contents($path); + }); + +Setting Headers +--------------- + +.. php:method:: withHeader($header, $value) + +Setting headers is done with the :php:meth:`Cake\\Http\\Response::withHeader()` +method. Like all of the PSR-7 interface methods, this method returns a *new* +instance with the new header:: + + // Add/replace a header + $response = $response->withHeader('X-Extra', 'My header'); + + // Set multiple headers + $response = $response->withHeader('X-Extra', 'My header') + ->withHeader('Location', 'http://example.com'); + + // Append a value to an existing header + $response = $response->withAddedHeader('Set-Cookie', 'remember_me=1'); + + // Prior to 3.4.0 - Set a header + $this->response->header('Location', 'http://example.com'); + +Headers are not sent when set. Instead, they are held until the response is +emitted by ``Cake\Http\Server``. + +You can now use the convenience method +:php:meth:`Cake\\Http\\Response::withLocation()` to directly set or get the +redirect location header. + +Setting the Body +---------------- + +.. php:method:: withStringBody($string) + +To set a string as the response body, do the following:: + + // Set a string into the body + $response = $response->withStringBody('My Body'); + + // If you want a json response + $response = $response->withType('application/json') + ->withStringBody(json_encode(['Foo' => 'bar'])); + +.. versionadded:: 3.4.3 + ``withStringBody()`` was added in 3.4.3 + +.. php:method:: withBody($body) + +To set the response body, use the ``withBody()`` method, which is provided by the +:php:class:`Zend\\Diactoros\\MessageTrait`:: + + $response = $response->withBody($stream); + + // Prior to 3.4.0 - Set the body + $this->response->body('My Body'); + +Be sure that ``$stream`` is a :php:class:`Psr\\Http\\Message\\StreamInterface` object. +See below on how to create a new stream. + +You can also stream responses from files using :php:class:`Zend\\Diactoros\\Stream` streams:: + + // To stream from a file + use Zend\Diactoros\Stream; + + $stream = new Stream('/path/to/file', 'rb'); + $response = $response->withBody($stream); + +You can also stream responses from a callback using the ``CallbackStream``. This +is useful when you have resources like images, CSV files or PDFs you need to +stream to the client:: + + // Streaming from a callback + use Cake\Http\CallbackStream; + + // Create an image. + $img = imagecreate(100, 100); + // ... + + $stream = new CallbackStream(function () use ($img) { + imagepng($img); + }); + $response = $response->withBody($stream); + + // Prior to 3.4.0 you can use the following to create streaming responses. + $file = fopen('/some/file.png', 'r'); + $this->response->body(function () use ($file) { + rewind($file); + fpassthru($file); + fclose($file); + }); + +Setting the Character Set +------------------------- + +.. php:method:: withCharset($charset) + +Sets the charset that will be used in the response:: + + $this->response = $this->response->withCharset('UTF-8'); + + // Prior to 3.4.0 + $this->response->charset('UTF-8'); + +Interacting with Browser Caching +-------------------------------- + +.. php:method:: withDisabledCache() + +You sometimes need to force browsers not to cache the results of a controller +action. :php:meth:`Cake\\Http\\Response::withDisabledCache()` is intended for just +that:: + + public function index() + { + // Disable caching + $this->response = $this->response->withDisabledCache(); + + // Prior to 3.4.0 + $this->response->disableCache(); + } + +.. warning:: + + Disabling caching from SSL domains while trying to send + files to Internet Explorer can result in errors. + +.. php:method:: withCache($since, $time = '+1 day') + +You can also tell clients that you want them to cache responses. By using +:php:meth:`Cake\\Http\\Response::withCache()`:: + + public function index() + { + // Enable caching + $this->response = $this->response->withCache('-1 minute', '+5 days'); + } + +The above would tell clients to cache the resulting response for 5 days, +hopefully speeding up your visitors' experience. +The ``withCache()`` method sets the ``Last-Modified`` value to the first +argument. ``Expires`` header and the ``max-age`` directive are set based on the +second parameter. Cache-Control's ``public`` directive is set as well. + +.. _cake-response-caching: + +Fine Tuning HTTP Cache +---------------------- + +One of the best and easiest ways of speeding up your application is to use HTTP +cache. Under this caching model, you are only required to help clients decide if +they should use a cached copy of the response by setting a few headers such as +modified time and response entity tag. + +Rather than forcing you to code the logic for caching and for invalidating +(refreshing) it once the data has changed, HTTP uses two models, expiration and +validation, which usually are much simpler to use. + +Apart from using :php:meth:`Cake\\Http\\Response::withCache()`, you can also use +many other methods to fine-tune HTTP cache headers to take advantage of browser +or reverse proxy caching. + +The Cache Control Header +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: withSharable($public, $time = null) + +Used under the expiration model, this header contains multiple indicators that +can change the way browsers or proxies use the cached content. A +``Cache-Control`` header can look like this:: + + Cache-Control: private, max-age=3600, must-revalidate + +``Response`` class helps you set this header with some utility methods that will +produce a final valid ``Cache-Control`` header. The first is the +``withSharable()`` method, which indicates whether a response is to be +considered sharable across different users or clients. This method actually +controls the ``public`` or ``private`` part of this header. Setting a response +as private indicates that all or part of it is intended for a single user. To +take advantage of shared caches, the control directive must be set as public. + +The second parameter of this method is used to specify a ``max-age`` for the +cache, which is the number of seconds after which the response is no longer +considered fresh:: + + public function view() + { + // ... + // Set the Cache-Control as public for 3600 seconds + $this->response = $this->response->withSharable(true, 3600); + } + + public function my_data() + { + // ... + // Set the Cache-Control as private for 3600 seconds + $this->response = $this->response->withSharable(false, 3600); + } + +``Response`` exposes separate methods for setting each of the directives in +the ``Cache-Control`` header. + +The Expiration Header +~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: withExpires($time) + +You can set the ``Expires`` header to a date and time after which the response +is no longer considered fresh. This header can be set using the +``withExpires()`` method:: + + public function view() + { + $this->response = $this->response->withExpires('+5 days'); + } + +This method also accepts a :php:class:`DateTime` instance or any string that can +be parsed by the :php:class:`DateTime` class. + +The Etag Header +~~~~~~~~~~~~~~~ + +.. php:method:: withEtag($tag, $weak = false) + +Cache validation in HTTP is often used when content is constantly changing, and +asks the application to only generate the response contents if the cache is no +longer fresh. Under this model, the client continues to store pages in the +cache, but it asks the application every time +whether the resource has changed, instead of using it directly. +This is commonly used with static resources such as images and other assets. + +The ``withEtag()`` method (called entity tag) is a string +that uniquely identifies the requested resource, as a checksum does for a file, +in order to determine whether it matches a cached resource. + +To take advantage of this header, you must either call the +``checkNotModified()`` method manually or include the +:doc:`/controllers/components/request-handling` in your controller:: + + public function index() + { + $articles = $this->Articles->find('all'); + $response = $this->response->withEtag($this->Articles->generateHash($articles)); + if ($response->checkNotModified($this->request)) { + return $response; + } + $this->response = $response; + // ... + } + +.. note:: + + Most proxy users should probably consider using the Last Modified Header + instead of Etags for performance and compatibility reasons. + +The Last Modified Header +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: withModified($time) + +Also, under the HTTP cache validation model, you can set the ``Last-Modified`` +header to indicate the date and time at which the resource was modified for the +last time. Setting this header helps CakePHP tell caching clients whether the +response was modified or not based on their cache. + +To take advantage of this header, you must either call the +``checkNotModified()`` method manually or include the +:doc:`/controllers/components/request-handling` in your controller:: + + public function view() + { + $article = $this->Articles->find()->first(); + $response = $this->response->withModified($article->modified); + if ($response->checkNotModified($this->request)) { + return $response; + } + $this->response; + // ... + } + +The Vary Header +~~~~~~~~~~~~~~~ + +.. php:method:: withVary($header) + +In some cases, you might want to serve different content using the same URL. +This is often the case if you have a multilingual page or respond with different +HTML depending on the browser. Under such circumstances you can use the ``Vary`` +header:: + + $response = $this->response->withVary('User-Agent'); + $response = $this->response->withVary('Accept-Encoding', 'User-Agent'); + $response = $this->response->withVary('Accept-Language'); + +Sending Not-Modified Responses +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: checkNotModified(Request $request) + +Compares the cache headers for the request object with the cache header from the +response and determines whether it can still be considered fresh. If so, deletes +the response content, and sends the `304 Not Modified` header:: + + // In a controller action. + if ($this->response->checkNotModified($this->request)) { + return $this->response; + } + +.. _response-cookies: + +Setting Cookies +=============== + +Cookies can be added to response using either an array or a :php:class:`Cake\\Http\\Cookie\\Cookie` +object:: + + // Add a cookie as an array using the immutable API (3.4.0+) + $this->response = $this->response->withCookie('remember_me', [ + 'value' => 'yes', + 'path' => '/', + 'httpOnly' => true, + 'secure' => false, + 'expire' => strtotime('+1 year') + ]); + + // Before 3.4.0 + $this->response->cookie('remember', [ + 'value' => 'yes', + 'path' => '/', + 'httpOnly' => true, + 'secure' => false, + 'expire' => strtotime('+1 year') + ]); + +See the :ref:`creating-cookies` section for how to use the cookie object. You +can use ``withExpiredCookie()`` to send an expired cookie in the response. This +will make the browser remove its local cookie:: + + // As of 3.5.0 + $this->response = $this->response->withExpiredCookie('remember_me'); + +.. _cors-headers: + +Setting Cross Origin Request Headers (CORS) +=========================================== + +As of 3.2 you can use the ``cors()`` method to define `HTTP Access Control +`__ +related headers with a fluent interface:: + + $this->response->cors($this->request) + ->allowOrigin(['*.cakephp.org']) + ->allowMethods(['GET', 'POST']) + ->allowHeaders(['X-CSRF-Token']) + ->allowCredentials() + ->exposeHeaders(['Link']) + ->maxAge(300) + ->build(); + +CORS related headers will only be applied to the response if the following +criteria are met: + +#. The request has an ``Origin`` header. +#. The request's ``Origin`` value matches one of the allowed Origin values. + +.. versionadded:: 3.2 + The ``CorsBuilder`` was added in 3.2 + +Common Mistakes with Immutable Responses +======================================== + +As of CakePHP 3.4.0, response objects offer a number of methods that treat +responses as immutable objects. Immutable objects help prevent difficult to +track accidental side-effects, and reduce mistakes caused by method calls caused +by refactoring that change ordering. While they offer a number of benefits, +immutable objects can take some getting used to. Any method that starts with +``with`` operates on the response in an immutable fashion, and will **always** +return a **new** instance. Forgetting to retain the modified instance is the most +frequent mistake people make when working with immutable objects:: + + $this->response->withHeader('X-CakePHP', 'yes!'); + +In the above code, the response will be lacking the ``X-CakePHP`` header, as the +return value of the ``withHeader()`` method was not retained. To correct the +above code you would write:: + + $this->response = $this->response->withHeader('X-CakePHP', 'yes!'); + +.. php:namespace:: Cake\Http\Cookie + +Cookie Collections +================== + +.. php:class:: CookieCollection + +``CookieCollection`` objects are accessible from the request and response objects. +They let you interact with groups of cookies using immutable patterns, which +allow the immutability of the request and response to be preserved. + +.. _creating-cookies: + +Creating Cookies +---------------- + +.. php:class:: Cookie + +``Cookie`` objects can be defined through constructor objects, or by using the +fluent interface that follows immutable patterns:: + + use Cake\Http\Cookie\Cookie; + + // All arguments in the constructor + $cookie = new Cookie( + 'remember_me', // name + 1, // value + new DateTime('+1 year'), // expiration time, if applicable + '/', // path, if applicable + 'example.com', // domain, if applicable + false, // secure only? + true // http only ? + ); + + // Using the builder methods + $cookie = (new Cookie('remember_me')) + ->withValue('1') + ->withExpiry(new DateTime('+1 year')) + ->withPath('/') + ->withDomain('example.com') + ->withSecure(false) + ->withHttpOnly(true); + +Once you have created a cookie, you can add it to a new or existing +``CookieCollection``:: + + use Cake\Http\Cookie\CookieCollection; + + // Create a new collection + $cookies = new CookieCollection([$cookie]); + + // Add to an existing collection + $cookies = $cookies->add($cookie); + + // Remove a cookie by name + $cookies = $cookies->remove('remember_me'); + +.. note:: + Remember that collections are immutable and adding cookies into, or removing + cookies from a collection, creates a *new* collection object. + +You should use the ``withCookie()`` method to add cookies to ``Response`` +objects as it is simpler to use:: + + $response = $this->response->withCookie($cookie); + +Cookies set to responses can be encrypted using the +:ref:`encrypted-cookie-middleware`. + +Reading Cookies +--------------- + +Once you have a ``CookieCollection`` instance, you can access the cookies it +contains:: + + // Check if a cookie exists + $cookies->has('remember_me'); + + // Get the number of cookies in the collection + count($cookies); + + // Get a cookie instance + $cookie = $cookies->get('remember_me'); + +Once you have a ``Cookie`` object you can interact with it's state and modify +it. Keep in mind that cookies are immutable, so you'll need to update the +collection if you modify a cookie:: + + // Get the value + $value = $cookie->getValue() + + // Access data inside a JSON value + $id = $cookie->read('User.id'); + + // Check state + $cookie->isHttpOnly(); + $cookie->isSecure(); + +.. versionadded:: 3.5.0 + ``CookieCollection`` and ``Cookie`` were added in 3.5.0. + +.. meta:: + :title lang=en: Request and Response objects + :keywords lang=en: request controller,request parameters,array indexes,purpose index,response objects,domain information,request object,request data,interrogating,params,previous versions,introspection,dispatcher,rout,data structures,arrays,ip address,migration,indexes,cakephp,PSR-7,immutable diff --git a/tl/core-libraries/app.rst b/tl/core-libraries/app.rst new file mode 100644 index 0000000000000000000000000000000000000000..f7dac1f0d3d0158f8a596f43b672dc4e77d8dfbc --- /dev/null +++ b/tl/core-libraries/app.rst @@ -0,0 +1,125 @@ +App Class +######### + +.. php:namespace:: Cake\Core + +.. php:class:: App + +The App class is responsible for resource location and path management. + +Finding Classes +=============== + +.. php:staticmethod:: classname($name, $type = '', $suffix = '') + +This method is used to resolve classnames throughout CakePHP. It resolves +the short form names CakePHP uses and returns the fully resolved classname:: + + // Resolve a short classname with the namespace + suffix. + App::classname('Auth', 'Controller/Component', 'Component'); + // Returns Cake\Controller\Component\AuthComponent + + // Resolve a plugin name. + App::classname('DebugKit.Toolbar', 'Controller/Component', 'Component'); + // Returns DebugKit\Controller\Component\ToolbarComponent + + // Names with \ in them will be returned unaltered. + App::classname('App\Cache\ComboCache'); + // Returns App\Cache\ComboCache + +When resolving classes, the ``App`` namespace will be tried, and if the +class does not exist the ``Cake`` namespace will be attempted. If both +classnames do not exist, ``false`` will be returned. + +Finding Paths to Namespaces +=========================== + +.. php:staticmethod:: path(string $package, string $plugin = null) + +Used to get locations for paths based on conventions:: + + // Get the path to Controller/ in your application + App::path('Controller'); + +This can be done for all namespaces that are part of your application. You +can also fetch paths for a plugin:: + + // Returns the component paths in DebugKit + App::path('Component', 'DebugKit'); + +``App::path()`` will only return the default path, and will not be able to +provide any information about additional paths the autoloader is configured +for. + +.. php:staticmethod:: core(string $package) + +Used for finding the path to a package inside CakePHP:: + + // Get the path to Cache engines. + App::core('Cache/Engine'); + +Locating Plugins +================ + +.. php:staticmethod:: Plugin::path(string $plugin) + +Plugins can be located with Plugin. Using ``Plugin::path('DebugKit');`` +for example, will give you the full path to the DebugKit plugin:: + + $path = Plugin::path('DebugKit'); + +Locating Themes +=============== + +Since themes are plugins, you can use the methods above to get the path to +a theme. + +Loading Vendor Files +==================== + +Ideally vendor files should be autoloaded with ``Composer``, if you have vendor +files that cannot be autoloaded or installed with Composer you will need to use +``require`` to load them. + +If you cannot install a library with Composer, it is best to install each library in +a directory following Composer's convention of ``vendor/$author/$package``. +If you had a library called AcmeLib, you could install it into +``vendor/Acme/AcmeLib``. Assuming it did not use PSR-0 compatible classnames +you could autoload the classes within it using ``classmap`` in your +application's ``composer.json``:: + + "autoload": { + "psr-4": { + "App\\": "src/", + "App\\Test\\": "tests/" + }, + "classmap": [ + "vendor/Acme/AcmeLib" + ] + } + +If your vendor library does not use classes, and instead provides functions, you +can configure Composer to load these files at the beginning of each request +using the ``files`` autoloading strategy:: + + "autoload": { + "psr-4": { + "App\\": "src/", + "App\\Test\\": "tests/" + }, + "files": [ + "vendor/Acme/AcmeLib/functions.php" + ] + } + +After configuring the vendor libraries you will need to regenerate your +application's autoloader using:: + + $ php composer.phar dump-autoload + +If you happen to not be using Composer in your application, you will need to +manually load all vendor libraries yourself. + +.. meta:: + :title lang=en: App Class + :keywords lang=en: compatible implementation,model behaviors,path management,loading files,php class,class loading,model behavior,class location,component model,management class,autoloader,classname,directory location,override,conventions,lib,textile,cakephp,php classes,loaded diff --git a/tl/core-libraries/caching.rst b/tl/core-libraries/caching.rst new file mode 100644 index 0000000000000000000000000000000000000000..bced884d8196eae2f442a7a03bfe06fa5519fc74 --- /dev/null +++ b/tl/core-libraries/caching.rst @@ -0,0 +1,598 @@ +Caching +####### + +.. php:namespace:: Cake\Cache + +.. php:class:: Cache + +Caching can be used to make reading from expensive or slow resources faster, by +maintaining a second copy of the required data in a faster or closer storage +system. For example, you can store the results of expensive queries, or remote +webservice access that doesn't frequently change in a cache. Once in the cache, +reading data from the cache is much cheaper than accessing the remote resource. + +Caching in CakePHP is facilitated by the ``Cache`` class. +This class provides a static interface and uniform API to +interact with various Caching implementations. CakePHP +provides several cache engines, and provides a simple interface if you need to +build your own backend. The built-in caching engines are: + +* ``FileCache`` File cache is a simple cache that uses local files. It + is the slowest cache engine, and doesn't provide as many features for + atomic operations. However, since disk storage is often quite cheap, + storing large objects, or elements that are infrequently written + work well in files. +* ``ApcCache`` APC cache uses the PHP `APCu `_ extension. + This extension uses shared memory on the webserver to store objects. + This makes it very fast, and able to provide atomic read/write features. +* ``Wincache`` Wincache uses the `Wincache `_ + extension. Wincache is similar to APC in features and performance, but + optimized for Windows and IIS. +* ``XcacheEngine`` `Xcache `_ + is a PHP extension that provides similar features to APC. +* ``MemcachedEngine`` Uses the `Memcached `_ + extension. +* ``RedisEngine`` Uses the `phpredis `_ + extension. Redis provides a fast and persistent cache system similar to + Memcached, also provides atomic operations. + +Regardless of the CacheEngine you choose to use, your application interacts with +:php:class:`Cake\\Cache\\Cache`. + +.. _cache-configuration: + +Configuring Cache Engines +========================= + +.. php:staticmethod:: config($key, $config = null) + +Your application can configure any number of 'engines' during its bootstrap +process. Cache engine configurations are defined in **config/app.php**. + +For optimal performance CakePHP requires two cache engines to be defined. + +* ``_cake_core_`` is used for storing file maps, and parsed results of + :doc:`/core-libraries/internationalization-and-localization` files. +* ``_cake_model_``, is used to store schema descriptions for your applications + models. + +Using multiple engine configurations also lets you incrementally change the +storage as needed. For example in your **config/app.php** you could put the +following:: + + // ... + 'Cache' => [ + 'short' => [ + 'className' => 'File', + 'duration' => '+1 hours', + 'path' => CACHE, + 'prefix' => 'cake_short_' + ], + // Using a fully namespaced name. + 'long' => [ + 'className' => 'Cake\Cache\Engine\FileEngine', + 'duration' => '+1 week', + 'probability' => 100, + 'path' => CACHE . 'long' . DS, + ] + ] + // ... + +Configuration options can also be provided as a :term:`DSN` string. This is +useful when working with environment variables or :term:`PaaS` providers:: + + Cache::config('short', [ + 'url' => 'memcached://user:password@cache-host/?timeout=3600&prefix=myapp_', + ]); + +When using a DSN string you can define any additional parameters/options as +query string arguments. + +You can also configure Cache engines at runtime:: + + // Using a short name + Cache::config('short', [ + 'className' => 'File', + 'duration' => '+1 hours', + 'path' => CACHE, + 'prefix' => 'cake_short_' + ]); + + // Using a fully namespaced name. + Cache::config('long', [ + 'className' => 'Cake\Cache\Engine\FileEngine', + 'duration' => '+1 week', + 'probability' => 100, + 'path' => CACHE . 'long' . DS, + ]); + + // Using a constructed object. + $object = new FileEngine($config); + Cache::config('other', $object); + +The name of these engine configurations ('short' and 'long') are used as the ``$config`` +parameter for :php:meth:`Cake\\Cache\\Cache::write()` and +:php:meth:`Cake\\Cache\\Cache::read()`. When configuring cache engines you can +refer to the class name using the following syntaxes:: + + // Short name (in App\ or Cake namespaces) + Cache::config('long', ['className' => 'File']); + + // Plugin short name + Cache::config('long', ['className' => 'MyPlugin.SuperCache']); + + // Full namespace + Cache::config('long', ['className' => 'Cake\Cache\Engine\FileEngine']); + + // An object implementing CacheEngineInterface + Cache::config('long', ['className' => $myCache]); + +.. note:: + + When using the FileEngine you might need to use the ``mask`` option to + ensure cache files are made with the correct permissions. + +Engine Options +-------------- + +Each engine accepts the following options: + +* ``duration`` Specify how long items in this cache configuration last. + Specified as a ``strototime()`` compatible expression. +* ``groups`` List of groups or 'tags' associated to every key stored in this + config. handy for deleting a complete group from cache. +* ``prefix`` Prepended to all entries. Good for when you need to share + a keyspace with either another cache config or another application. +* ``probability``` Probability of hitting a cache gc cleanup. Setting to 0 will disable + ``Cache::gc()`` from ever being called automatically. + +FileEngine Options +------------------- + +FileEngine uses the following engine specific options: + +* ``isWindows`` Automatically populated with whether the host is windows or not +* ``lock`` Should files be locked before writing to them? +* ``mask`` The mask used for created files +* ``path`` Path to where cachefiles should be saved. Defaults to system's temp dir. + +RedisEngine Options +------------------- + +RedisEngine uses the following engine specific options: + +* ``port`` The port your Redis server is running on. +* ``host`` The host your Redis server is running on. +* ``database`` The database number to use for connection. +* ``password`` Redis server password. +* ``persistent`` Should a persistent connection be made to Redis. +* ``timeout`` Connection timeout for Redis. +* ``unix_socket`` Path to a unix socket for Redist. + +MemcacheEngine Options +---------------------- + +- ``compress`` Whether to compress data. +- ``username`` Login to access the Memcache server. +- ``password`` Password to access the Memcache server. +- ``persistent`` The name of the persistent connection. All configurations using + the same persistent value will share a single underlying connection. +- ``serialize`` The serializer engine used to serialize data. Available engines are php, + igbinary and json. Beside php, the memcached extension must be compiled with the + appropriate serializer support. +- ``servers`` String or array of memcached servers. If an array MemcacheEngine will use + them as a pool. +- ``options`` Additional options for the memcached client. Should be an array of option => value. + Use the ``\Memcached::OPT_*`` constants as keys. + +.. _cache-configuration-fallback: + +Configuring Cache Fallbacks +--------------------------- + +In the event that an engine is not available, such as the ``FileEngine`` trying +to write to an unwritable folder or the ``RedisEngine`` failing to connect to +Redis, the engine will fall back to the noop ``NullEngine`` and trigger a loggable +error. This prevents the application from throwing an uncaught exception due to +cache failure. + +You can configure Cache configurations to fall back to a specified config using +the ``fallback`` configuration key:: + + Cache::config('redis', [ + 'className' => 'Redis', + 'duration' => '+1 hours', + 'prefix' => 'cake_redis_', + 'host' => '127.0.0.1', + 'port' => 6379, + 'fallback' => 'default', + ]); + +If the Redis server unexpectedly failed, writing to the ``redis`` cache +configuration would fall back to writing to the ``default`` cache configuration. +If writing to the ``default`` cache configuration *also* failed in this scenario, the +engine would fall back once again to the ``NullEngine`` and prevent the application +from throwing an uncaught exception. + +.. versionadded:: 3.5.0 + Cache engine fallbacks were added. + +Removing Configured Cache Engines +--------------------------------- + +.. php:staticmethod:: drop($key) + +Once a configuration is created you cannot change it. Instead you should drop +the configuration and re-create it using :php:meth:`Cake\\Cache\\Cache::drop()` and +:php:meth:`Cake\\Cache\\Cache::config()`. Dropping a cache engine will remove +the config and destroy the adapter if it was constructed. + +Writing to a Cache +================== + +.. php:staticmethod:: write($key, $value, $config = 'default') + +``Cache::write()`` will write a $value to the Cache. You can read or +delete this value later by referring to it by ``$key``. You may +specify an optional configuration to store the cache in as well. If +no ``$config`` is specified, default will be used. ``Cache::write()`` +can store any type of object and is ideal for storing results of +model finds:: + + if (($posts = Cache::read('posts')) === false) { + $posts = $someService->getAllPosts(); + Cache::write('posts', $posts); + } + +Using ``Cache::write()`` and ``Cache::read()`` to reduce the number +of trips made to the database to fetch posts. + +.. note:: + + If you plan to cache the result of queries made with the CakePHP ORM, + it is better to use the built-in cache capabilities of the Query object + as described in the :ref:`caching-query-results` section + +Writing Multiple Keys at Once +----------------------------- + +.. php:staticmethod:: writeMany($data, $config = 'default') + +You may find yourself needing to write multiple cache keys at once. While you +can use multiple calls to ``write()``, ``writeMany()`` allows CakePHP to use +more efficient storage API's where available. For example using ``writeMany()`` +save multiple network connections when using Memcached:: + + $result = Cache::writeMany([ + 'article-' . $slug => $article, + 'article-' . $slug . '-comments' => $comments + ]); + + // $result will contain + ['article-first-post' => true, 'article-first-post-comments' => true] + +Read Through Caching +-------------------- + +.. php:staticmethod:: remember($key, $callable, $config = 'default') + +Cache makes it easy to do read-through caching. If the named cache key exists, +it will be returned. If the key does not exist, the callable will be invoked +and the results stored in the cache at the provided key. + +For example, you often want to cache remote service call results. You could use +``remember()`` to make this simple:: + + class IssueService + { + public function allIssues($repo) + { + return Cache::remember($repo . '-issues', function () use ($repo) { + return $this->fetchAll($repo); + }); + } + } + +Reading From a Cache +==================== + +.. php:staticmethod:: read($key, $config = 'default') + +``Cache::read()`` is used to read the cached value stored under +``$key`` from the ``$config``. If ``$config`` is null the default +config will be used. ``Cache::read()`` will return the cached value +if it is a valid cache or ``false`` if the cache has expired or +doesn't exist. The contents of the cache might evaluate false, so +make sure you use the strict comparison operators: ``===`` or +``!==``. + +For example:: + + $cloud = Cache::read('cloud'); + + if ($cloud !== false) { + return $cloud; + } + + // Generate cloud data + // ... + + // Store data in cache + Cache::write('cloud', $cloud); + return $cloud; + +Reading Multiple Keys at Once +----------------------------- + +.. php:staticmethod:: readMany($keys, $config = 'default') + +After you've written multiple keys at once, you'll probably want to read them as +well. While you could use multiple calls to ``read()``, ``readMany()`` allows +CakePHP to use more efficient storage API's where available. For example using +``readMany()`` save multiple network connections when using Memcached:: + + $result = Cache::readMany([ + 'article-' . $slug, + 'article-' . $slug . '-comments' + ]); + // $result will contain + ['article-first-post' => '...', 'article-first-post-comments' => '...'] + +Deleting From a Cache +===================== + +.. php:staticmethod:: delete($key, $config = 'default') + +``Cache::delete()`` will allow you to completely remove a cached +object from the store:: + + // Remove a key + Cache::delete('my_key'); + +Deleting Multiple Keys at Once +------------------------------ + +.. php:staticmethod:: deleteMany($keys, $config = 'default') + +After you've written multiple keys at once, you may want to delete them. While +you could use multiple calls to ``delete()``, ``deleteMany()`` allows CakePHP to use +more efficient storage API's where available. For example using ``deleteMany()`` +save multiple network connections when using Memcached:: + + $result = Cache::deleteMany([ + 'article-' . $slug, + 'article-' . $slug . '-comments' + ]); + // $result will contain + ['article-first-post' => true, 'article-first-post-comments' => true] + +Clearing Cached Data +==================== + +.. php:staticmethod:: clear($check, $config = 'default') + +Destroy all cached values for a cache configuration. In engines like: Apc, +Memcached, and Wincache, the cache configuration's prefix is used to remove +cache entries. Make sure that different cache configurations have different +prefixes:: + + // Will only clear expired keys. + Cache::clear(true); + + // Will clear all keys. + Cache::clear(false); + +.. php:staticmethod:: gc($config) + +Garbage collects entries in the cache configuration. This is primarily +used by FileEngine. It should be implemented by any Cache engine +that requires manual eviction of cached data. + +.. note:: + + Because APC and Wincache use isolated caches for webserver and CLI they + have to be cleared separately (CLI cannot clear webserver and vice versa). + +Using Cache to Store Counters +============================= + +.. php:staticmethod:: increment($key, $offset = 1, $config = 'default') + +.. php:staticmethod:: decrement($key, $offset = 1, $config = 'default') + +Counters in your application are good candidates for storage in a cache. As an +example, a simple countdown for remaining 'slots' in a contest could be stored +in Cache. The Cache class exposes atomic ways to increment/decrement counter +values in an easy way. Atomic operations are important for these values as it +reduces the risk of contention, and ability for two users to simultaneously +lower the value by one, resulting in an incorrect value. + +After setting an integer value you can manipulate it using ``increment()`` and +``decrement()``:: + + Cache::write('initial_count', 10); + + // Later on + Cache::decrement('initial_count'); + + // Or + Cache::increment('initial_count'); + +.. note:: + + Incrementing and decrementing do not work with FileEngine. You should use + APC, Wincache, Redis or Memcached instead. + +Using Cache to Store Common Query Results +========================================= + +You can greatly improve the performance of your application by putting results +that infrequently change, or that are subject to heavy reads into the cache. +A perfect example of this are the results from +:php:meth:`Cake\\ORM\\Table::find()`. The Query object allows you to cache +results using the ``cache()`` method. See the :ref:`caching-query-results` section +for more information. + +Using Groups +============ + +Sometimes you will want to mark multiple cache entries to belong to certain +group or namespace. This is a common requirement for mass-invalidating keys +whenever some information changes that is shared among all entries in the same +group. This is possible by declaring the groups in cache configuration:: + + Cache::config('site_home', [ + 'className' => 'Redis', + 'duration' => '+999 days', + 'groups' => ['comment', 'article'] + ]); + +.. php:method:: clearGroup($group, $config = 'default') + +Let's say you want to store the HTML generated for your homepage in cache, but +would also want to automatically invalidate this cache every time a comment or +post is added to your database. By adding the groups ``comment`` and ``article``, +we have effectively tagged any key stored into this cache configuration with +both group names. + +For instance, whenever a new post is added, we could tell the Cache engine to +remove all entries associated to the ``article`` group:: + + // src/Model/Table/ArticlesTable.php + public function afterSave($event, $entity, $options = []) + { + if ($entity->isNew()) { + Cache::clearGroup('article', 'site_home'); + } + } + +.. php:staticmethod:: groupConfigs($group = null) + +``groupConfigs()`` can be used to retrieve mapping between group and +configurations, i.e.: having the same group:: + + // src/Model/Table/ArticlesTable.php + + /** + * A variation of previous example that clears all Cache configurations + * having the same group + */ + public function afterSave($event, $entity, $options = []) + { + if ($entity->isNew()) { + $configs = Cache::groupConfigs('article'); + foreach ($configs['article'] as $config) { + Cache::clearGroup('article', $config); + } + } + } + +Groups are shared across all cache configs using the same engine and same +prefix. If you are using groups and want to take advantage of group deletion, +choose a common prefix for all your configs. + +Globally Enable or Disable Cache +================================ + +.. php:staticmethod:: disable() + +You may need to disable all Cache read & writes when trying to figure out cache +expiration related issues. You can do this using ``enable()`` and +``disable()``:: + + // Disable all cache reads, and cache writes. + Cache::disable(); + +Once disabled, all reads and writes will return ``null``. + +.. php:staticmethod:: enable() + +Once disabled, you can use ``enable()`` to re-enable caching:: + + // Re-enable all cache reads, and cache writes. + Cache::enable(); + +.. php:staticmethod:: enabled() + +If you need to check on the state of Cache, you can use ``enabled()``. + +Creating a Cache Engine +======================= + +You can provide custom ``Cache`` engines in ``App\Cache\Engine`` as well +as in plugins using ``$plugin\Cache\Engine``. Cache engines must be in a cache +directory. If you had a cache engine named ``MyCustomCacheEngine`` +it would be placed in either **src/Cache/Engine/MyCustomCacheEngine.php**. +Or in **plugins/MyPlugin/src/Cache/Engine/MyCustomCacheEngine.php** as +part of a plugin. Cache configs from plugins need to use the plugin +dot syntax:: + + Cache::config('custom', [ + 'className' => 'MyPlugin.MyCustomCache', + // ... + ]); + +Custom Cache engines must extend :php:class:`Cake\\Cache\\CacheEngine` which +defines a number of abstract methods as well as provides a few initialization +methods. + +The required API for a CacheEngine is + +.. php:class:: CacheEngine + + The base class for all cache engines used with Cache. + +.. php:method:: write($key, $value, $config = 'default') + + :return: boolean for success. + + Write value for a key into cache, optional string $config + specifies configuration name to write to. + +.. php:method:: read($key) + + :return: The cached value or ``false`` for failure. + + Read a key from the cache. Return ``false`` to indicate + the entry has expired or does not exist. + +.. php:method:: delete($key) + + :return: Boolean ``true`` on success. + + Delete a key from the cache. Return ``false`` to indicate that + the entry did not exist or could not be deleted. + +.. php:method:: clear($check) + + :return: Boolean ``true`` on success. + + Delete all keys from the cache. If $check is ``true``, you should + validate that each value is actually expired. + +.. php:method:: clearGroup($group) + + :return: Boolean ``true`` on success. + + Delete all keys from the cache belonging to the same group. + +.. php:method:: decrement($key, $offset = 1) + + :return: Boolean ``true`` on success. + + Decrement a number under the key and return decremented value + +.. php:method:: increment($key, $offset = 1) + + :return: Boolean ``true`` on success. + + Increment a number under the key and return incremented value + +.. php:method:: gc() + + Not required, but used to do clean up when resources expire. + FileEngine uses this to delete files containing expired content. + +.. meta:: + :title lang=en: Caching + :keywords lang=en: uniform api,xcache,cache engine,cache system,atomic operations,php class,disk storage,static methods,php extension,consistent manner,similar features,apc,memcache,queries,cakephp,elements,servers,memory diff --git a/tl/core-libraries/collections.rst b/tl/core-libraries/collections.rst new file mode 100644 index 0000000000000000000000000000000000000000..f3ced5fdc1da697195fd5a65305c0bf8381dbd53 --- /dev/null +++ b/tl/core-libraries/collections.rst @@ -0,0 +1,1177 @@ +Collections +########### + +.. php:namespace:: Cake\Collection + +.. php:class:: Collection + +The collection classes provide a set of tools to manipulate arrays or +``Traversable`` objects. If you have ever used underscore.js, +you have an idea of what you can expect from the collection classes. + +Collection instances are immutable; modifying a collection will instead generate +a new collection. This makes working with collection objects more predictable as +operations are side-effect free. + +Quick Example +============= + +Collections can be created using an array or ``Traversable`` object. You'll also +interact with collections every time you interact with the ORM in CakePHP. +A simple use of a Collection would be:: + + use Cake\Collection\Collection; + + $items = ['a' => 1, 'b' => 2, 'c' => 3]; + $collection = new Collection($items); + + // Create a new collection containing elements + // with a value greater than one. + $overOne = $collection->filter(function ($value, $key, $iterator) { + return $value > 1; + }); + +You can also use the ``collection()`` helper function instead of ``new +Collection()``:: + + $items = ['a' => 1, 'b' => 2, 'c' => 3]; + + // These both make a Collection instance. + $collectionA = new Collection($items); + $collectionB = collection($items); + +The benefit of the helper method is that it is easier to chain off of than +``(new Collection($items))``. + +The :php:trait:`~Cake\\Collection\\CollectionTrait` allows you to integrate +collection-like features into any ``Traversable`` object you have in your +application as well. + +List of Methods +=============== + +.. csv-table:: + :class: docutils internal-toc + + :php:meth:`append`, :php:meth:`avg`, :php:meth:`buffered`, :php:meth:`chunk` + :php:meth:`chunkWithKeys`, :php:meth:`combine`, :php:meth:`compile`, :php:meth:`contains` + :php:meth:`countBy`, :php:meth:`each`, :php:meth:`every`, :php:meth:`extract` + :php:meth:`filter`, :php:meth:`first`, :php:meth:`groupBy`, :php:meth:`indexBy` + :php:meth:`insert`, :php:meth:`isEmpty`, :php:meth:`last`, :php:meth:`listNested` + :php:meth:`map`, :php:meth:`match`, :php:meth:`max`, :php:meth:`median` + :php:meth:`min`, :php:meth:`nest`, :php:meth:`reduce`, :php:meth:`reject` + :php:meth:`sample`, :php:meth:`shuffle`, :php:meth:`skip`, :php:meth:`some` + :php:meth:`sortBy`, :php:meth:`stopWhen`, :php:meth:`sumOf`, :php:meth:`take` + :php:meth:`through`, :php:meth:`transpose`, :php:meth:`unfold`, :php:meth:`zip` + +Iterating +========= + +.. php:method:: each(callable $c) + +Collections can be iterated and/or transformed into new collections with the +``each()`` and ``map()`` methods. The ``each()`` method will not create a new +collection, but will allow you to modify any objects within the collection:: + + $collection = new Collection($items); + $collection = $collection->each(function ($value, $key) { + echo "Element $key: $value"; + }); + +The return of ``each()`` will be the collection object. Each will iterate the +collection immediately applying the callback to each value in the collection. + +.. php:method:: map(callable $c) + +The ``map()`` method will create a new collection based on the output of the +callback being applied to each object in the original collection:: + + $items = ['a' => 1, 'b' => 2, 'c' => 3]; + $collection = new Collection($items); + + $new = $collection->map(function ($value, $key) { + return $value * 2; + }); + + // $result contains ['a' => 2, 'b' => 4, 'c' => 6]; + $result = $new->toArray(); + +The ``map()`` method will create a new iterator which lazily creates +the resulting items when iterated. + +.. php:method:: extract($matcher) + +One of the most common uses for a ``map()`` function is to extract a single +column from a collection. If you are looking to build a list of elements +containing the values for a particular property, you can use the ``extract()`` +method:: + + $collection = new Collection($people); + $names = $collection->extract('name'); + + // $result contains ['mark', 'jose', 'barbara']; + $result = $names->toArray(); + +As with many other functions in the collection class, you are allowed to specify +a dot-separated path for extracting columns. This example will return +a collection containing the author names from a list of articles:: + + $collection = new Collection($articles); + $names = $collection->extract('author.name'); + + // $result contains ['Maria', 'Stacy', 'Larry']; + $result = $names->toArray(); + +Finally, if the property you are looking after cannot be expressed as a path, +you can use a callback function to return it:: + + $collection = new Collection($articles); + $names = $collection->extract(function ($article) { + return $article->author->name . ', ' . $article->author->last_name; + }); + +Often, the properties you need to extract a common key present in multiple +arrays or objects that are deeply nested inside other structures. For those +cases you can use the ``{*}`` matcher in the path key. This matcher is often +helpful when matching HasMany and BelongsToMany association data:: + + $data = [ + [ + 'name' => 'James', + 'phone_numbers' => [ + ['number' => 'number-1'], + ['number' => 'number-2'], + ['number' => 'number-3'], + ] + ], + [ + 'name' => 'James', + 'phone_numbers' => [ + ['number' => 'number-4'], + ['number' => 'number-5'], + ] + ] + ]; + + $numbers = (new Collection($data))->extract('phone_numbers.{*}.number'); + $numbers->toList(); + // Returns ['number-1', 'number-2', 'number-3', 'number-4', 'number-5'] + +This last example uses ``toList()`` unlike other examples, which is important +when we're getting results with possibly duplicate keys. By using ``toList()`` +we'll be guaranteed to get all values even if there are duplicate keys. + +Unlike :php:meth:`Cake\\Utility\\Hash::extract()` this method only supports the +``{*}`` wildcard. All other wildcard and attributes matchers are not supported. + +.. php:method:: combine($keyPath, $valuePath, $groupPath = null) + +Collections allow you to create a new collection made from keys and values in +an existing collection. Both the key and value paths can be specified with +dot notation paths:: + + $items = [ + ['id' => 1, 'name' => 'foo', 'parent' => 'a'], + ['id' => 2, 'name' => 'bar', 'parent' => 'b'], + ['id' => 3, 'name' => 'baz', 'parent' => 'a'], + ]; + $combined = (new Collection($items))->combine('id', 'name'); + + // Result will look like this when converted to array + [ + 1 => 'foo', + 2 => 'bar', + 3 => 'baz', + ]; + +You can also optionally use a ``groupPath`` to group results based on a path:: + + $combined = (new Collection($items))->combine('id', 'name', 'parent'); + + // Result will look like this when converted to array + [ + 'a' => [1 => 'foo', 3 => 'baz'], + 'b' => [2 => 'bar'] + ]; + +Finally you can use *closures* to build keys/values/groups paths dynamically, +for example when working with entities and dates (converted to ``Cake/Time`` +instances by the ORM) you may want to group results by date:: + + $combined = (new Collection($entities))->combine( + 'id', + function ($entity) { return $entity; }, + function ($entity) { return $entity->date->toDateString(); } + ); + + // Result will look like this when converted to array + [ + 'date string like 2015-05-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] + 'date string like 2015-06-01' => ['entity1->id' => entity1, 'entity2->id' => entity2, ..., 'entityN->id' => entityN] + ] + +.. php:method:: stopWhen(callable $c) + +You can stop the iteration at any point using the ``stopWhen()`` method. Calling +it in a collection will create a new one that will stop yielding results if the +passed callable returns false for one of the elements:: + + $items = [10, 20, 50, 1, 2]; + $collection = new Collection($items); + + $new = $collection->stopWhen(function ($value, $key) { + // Stop on the first value bigger than 30 + return $value > 30; + }); + + // $result contains [10, 20]; + $result = $new->toArray(); + +.. php:method:: unfold(callable $c) + +Sometimes the internal items of a collection will contain arrays or iterators +with more items. If you wish to flatten the internal structure to iterate once +over all elements you can use the ``unfold()`` method. It will create a new +collection that will yield every single element nested in the collection:: + + $items = [[1, 2, 3], [4, 5]]; + $collection = new Collection($items); + $new = $collection->unfold(); + + // $result contains [1, 2, 3, 4, 5]; + $result = $new->toList(); + +When passing a callable to ``unfold()`` you can control what elements will be +unfolded from each item in the original collection. This is useful for returning +data from paginated services:: + + $pages = [1, 2, 3, 4]; + $collection = new Collection($pages); + $items = $collection->unfold(function ($page, $key) { + // An imaginary web service that returns a page of results + return MyService::fetchPage($page)->toArray(); + }); + + $allPagesItems = $items->toList(); + +If you are using PHP 5.5+, you can use the ``yield`` keyword inside ``unfold()`` +to return as many elements for each item in the collection as you may need:: + + $oddNumbers = [1, 3, 5, 7]; + $collection = new Collection($oddNumbers); + $new = $collection->unfold(function ($oddNumber) { + yield $oddNumber; + yield $oddNumber + 1; + }); + + // $result contains [1, 2, 3, 4, 5, 6, 7, 8]; + $result = $new->toList(); + +.. php:method:: chunk($chunkSize) + +When dealing with large amounts of items in a collection, it may make sense to +process the elements in batches instead of one by one. For splitting +a collection into multiple arrays of a certain size, you can use the ``chunk()`` +function:: + + $items = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; + $collection = new Collection($items); + $chunked = $collection->chunk(2); + $chunked->toList(); // [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11]] + +The ``chunk`` function is particularly useful when doing batch processing, for +example with a database result:: + + $collection = new Collection($articles); + $collection->map(function ($article) { + // Change a property in the article + $article->property = 'changed'; + }) + ->chunk(20) + ->each(function ($batch) { + myBulkSave($batch); // This function will be called for each batch + }); + +.. php:method:: chunkWithKeys($chunkSize) + +Much like :php:meth:`chunk()`, ``chunkWithKeys()`` allows you to slice up +a collection into smaller batches but with keys preserved. This is useful when +chunking associative arrays:: + + $collection = new Collection([ + 'a' => 1, + 'b' => 2, + 'c' => 3, + 'd' => [4, 5] + ]); + $chunked = $collection->chunkWithKeys(2)->toList(); + // Creates + [ + ['a' => 1, 'b' => 2], + ['c' => 3, 'd' => [4, 5]] + ] + +.. versionadded:: 3.4.0 + ``chunkWithKeys()`` was added in 3.4.0 + +Filtering +========= + +.. php:method:: filter(callable $c) + +Collections make it easy to filter and create new collections based on +the result of callback functions. You can use ``filter()`` to create a new +collection of elements matching a criteria callback:: + + $collection = new Collection($people); + $ladies = $collection->filter(function ($person, $key) { + return $person->gender === 'female'; + }); + $guys = $collection->filter(function ($person, $key) { + return $person->gender === 'male'; + }); + +.. php:method:: reject(callable $c) + +The inverse of ``filter()`` is ``reject()``. This method does a negative filter, +removing elements that match the filter function:: + + $collection = new Collection($people); + $ladies = $collection->reject(function ($person, $key) { + return $person->gender === 'male'; + }); + +.. php:method:: every(callable $c) + +You can do truth tests with filter functions. To see if every element in +a collection matches a test you can use ``every()``:: + + $collection = new Collection($people); + $allYoungPeople = $collection->every(function ($person) { + return $person->age < 21; + }); + +.. php:method:: some(callable $c) + +You can see if the collection contains at least one element matching a filter +function using the ``some()`` method:: + + $collection = new Collection($people); + $hasYoungPeople = $collection->some(function ($person) { + return $person->age < 21; + }); + +.. php:method:: match(array $conditions) + +If you need to extract a new collection containing only the elements that +contain a given set of properties, you should use the ``match()`` method:: + + $collection = new Collection($comments); + $commentsFromMark = $collection->match(['user.name' => 'Mark']); + +.. php:method:: firstMatch(array $conditions) + +The property name can be a dot-separated path. You can traverse into nested +entities and match the values they contain. When you only need the first +matching element from a collection, you can use ``firstMatch()``:: + + $collection = new Collection($comments); + $comment = $collection->firstMatch([ + 'user.name' => 'Mark', + 'active' => true + ]); + +As you can see from the above, both ``match()`` and ``firstMatch()`` allow you +to provide multiple conditions to match on. In addition, the conditions can be +for different paths, allowing you to express complex conditions to match +against. + +Aggregation +=========== + +.. php:method:: reduce(callable $c) + +The counterpart of a ``map()`` operation is usually a ``reduce``. This +function will help you build a single result out of all the elements in a +collection:: + + $totalPrice = $collection->reduce(function ($accumulated, $orderLine) { + return $accumulated + $orderLine->price; + }, 0); + +In the above example, ``$totalPrice`` will be the sum of all single prices +contained in the collection. Note the second argument for the ``reduce()`` +function takes the initial value for the reduce operation you are +performing:: + + $allTags = $collection->reduce(function ($accumulated, $article) { + return array_merge($accumulated, $article->tags); + }, []); + +.. php:method:: min(string|callable $callback, $type = SORT_NUMERIC) + +To extract the minimum value for a collection based on a property, just use the +``min()`` function. This will return the full element from the collection and +not just the smallest value found:: + + $collection = new Collection($people); + $youngest = $collection->min('age'); + + echo $youngest->name; + +You are also able to express the property to compare by providing a path or a +callback function:: + + $collection = new Collection($people); + $personYoungestChild = $collection->min(function ($person) { + return $person->child->age; + }); + + $personWithYoungestDad = $collection->min('dad.age'); + +.. php:method:: max(string|callable $callback, $type = SORT_NUMERIC) + +The same can be applied to the ``max()`` function, which will return a single +element from the collection having the highest property value:: + + $collection = new Collection($people); + $oldest = $collection->max('age'); + + $personOldestChild = $collection->max(function ($person) { + return $person->child->age; + }); + + $personWithOldestDad = $collection->max('dad.age'); + +.. php:method:: sumOf(string|callable $callback) + +Finally, the ``sumOf()`` method will return the sum of a property of all +elements:: + + $collection = new Collection($people); + $sumOfAges = $collection->sumOf('age'); + + $sumOfChildrenAges = $collection->sumOf(function ($person) { + return $person->child->age; + }); + + $sumOfDadAges = $collection->sumOf('dad.age'); + +.. php:method:: avg($matcher = null) + +Calculate the average value of the elements in the collection. Optionally +provide a matcher path, or function to extract values to generate the average +for:: + + $items = [ + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 200]], + ]; + + // Average: 150 + $average = (new Collection($items))->avg('invoice.total'); + +.. versionadded:: 3.5.0 + +.. php:method:: median($matcher = null) + +Calculate the median value of a set of elements. Optionally provide a matcher +path, or function to extract values to generate the median for:: + + $items = [ + ['invoice' => ['total' => 400]], + ['invoice' => ['total' => 500]], + ['invoice' => ['total' => 100]], + ['invoice' => ['total' => 333]], + ['invoice' => ['total' => 200]], + ]; + + // Median: 333 + $median = (new Collection($items))->median('invoice.total'); + +.. versionadded:: 3.5.0 + +Grouping and Counting +--------------------- + +.. php:method:: groupBy($callback) + +Collection values can be grouped by different keys in a new collection when they +share the same value for a property:: + + $students = [ + ['name' => 'Mark', 'grade' => 9], + ['name' => 'Andrew', 'grade' => 10], + ['name' => 'Stacy', 'grade' => 10], + ['name' => 'Barbara', 'grade' => 9] + ]; + $collection = new Collection($students); + $studentsByGrade = $collection->groupBy('grade'); + + // Result will look like this when converted to array: + [ + 10 => [ + ['name' => 'Andrew', 'grade' => 10], + ['name' => 'Stacy', 'grade' => 10] + ], + 9 => [ + ['name' => 'Mark', 'grade' => 9], + ['name' => 'Barbara', 'grade' => 9] + ] + ] + +As usual, it is possible to provide either a dot-separated path for nested +properties or your own callback function to generate the groups dynamically:: + + $commentsByUserId = $comments->groupBy('user.id'); + + $classResults = $students->groupBy(function ($student) { + return $student->grade > 6 ? 'approved' : 'denied'; + }); + +.. php:method:: countBy($callback) + +If you only wish to know the number of occurrences per group, you can do so by +using the ``countBy()`` method. It takes the same arguments as ``groupBy`` so it +should be already familiar to you:: + + $classResults = $students->countBy(function ($student) { + return $student->grade > 6 ? 'approved' : 'denied'; + }); + + // Result could look like this when converted to array: + ['approved' => 70, 'denied' => 20] + +.. php:method:: indexBy($callback) + +There will be certain cases where you know an element is unique for the property +you want to group by. If you wish a single result per group, you can use the +function ``indexBy()``:: + + $usersById = $users->indexBy('id'); + + // When converted to array result could look like + [ + 1 => 'markstory', + 3 => 'jose_zap', + 4 => 'jrbasso' + ] + +As with the ``groupBy()`` function you can also use a property path or +a callback:: + + $articlesByAuthorId = $articles->indexBy('author.id'); + + $filesByHash = $files->indexBy(function ($file) { + return md5($file); + }); + +.. php:method:: zip($elements) + +The elements of different collections can be grouped together using the +``zip()`` method. It will return a new collection containing an array grouping +the elements from each collection that are placed at the same position:: + + $odds = new Collection([1, 3, 5]); + $pairs = new Collection([2, 4, 6]); + $combined = $odds->zip($pairs)->toList(); // [[1, 2], [3, 4], [5, 6]] + +You can also zip multiple collections at once:: + + $years = new Collection([2013, 2014, 2015, 2016]); + $salaries = [1000, 1500, 2000, 2300]; + $increments = [0, 500, 500, 300]; + + $rows = $years->zip($salaries, $increments)->toList(); + // Returns: + [ + [2013, 1000, 0], + [2014, 1500, 500], + [2015, 2000, 500], + [2016, 2300, 300] + ] + +As you can already see, the ``zip()`` method is very useful for transposing +multidimensional arrays:: + + $data = [ + 2014 => ['jan' => 100, 'feb' => 200], + 2015 => ['jan' => 300, 'feb' => 500], + 2016 => ['jan' => 400, 'feb' => 600], + ] + + // Getting jan and feb data together + + $firstYear = new Collection(array_shift($data)); + $firstYear->zip($data[0], $data[1])->toList(); + + // Or $firstYear->zip(...$data) in PHP >= 5.6 + + // Returns + [ + [100, 300, 400], + [200, 500, 600] + ] + +Sorting +======= + +.. php:method:: sortBy($callback) + +Collection values can be sorted in ascending or descending order based on +a column or custom function. To create a new sorted collection out of the values +of another one, you can use ``sortBy``:: + + $collection = new Collection($people); + $sorted = $collection->sortBy('age'); + +As seen above, you can sort by passing the name of a column or property that +is present in the collection values. You are also able to specify a property +path instead using the dot notation. The next example will sort articles by +their author's name:: + + $collection = new Collection($articles); + $sorted = $collection->sortBy('author.name'); + +The ``sortBy()`` method is flexible enough to let you specify an extractor +function that will let you dynamically select the value to use for comparing two +different values in the collection:: + + $collection = new Collection($articles); + $sorted = $collection->sortBy(function ($article) { + return $article->author->name . '-' . $article->title; + }); + +In order to specify in which direction the collection should be sorted, you need +to provide either ``SORT_ASC`` or ``SORT_DESC`` as the second parameter for +sorting in ascending or descending direction respectively. By default, +collections are sorted in descending direction:: + + $collection = new Collection($people); + $sorted = $collection->sortBy('age', SORT_ASC); + +Sometimes you will need to specify which type of data you are trying to compare +so that you get consistent results. For this purpose, you should supply a third +argument in the ``sortBy()`` function with one of the following constants: + +- **SORT_NUMERIC**: For comparing numbers +- **SORT_STRING**: For comparing string values +- **SORT_NATURAL**: For sorting string containing numbers and you'd like those + numbers to be order in a natural way. For example: showing "10" after "2". +- **SORT_LOCALE_STRING**: For comparing strings based on the current locale. + +By default, ``SORT_NUMERIC`` is used:: + + $collection = new Collection($articles); + $sorted = $collection->sortBy('title', SORT_ASC, SORT_NATURAL); + +.. warning:: + + It is often expensive to iterate sorted collections more than once. If you + plan to do so, consider converting the collection to an array or simply use + the ``compile()`` method on it. + +Working with Tree Data +====================== + +.. php:method:: nest($idPath, $parentPath) + +Not all data is meant to be represented in a linear way. Collections make it +easier to construct and flatten hierarchical or nested structures. Creating +a nested structure where children are grouped by a parent identifier property is +easy with the ``nest()`` method. + +Two parameters are required for this function. The first one is the property +representing the item identifier. The second parameter is the name of the +property representing the identifier for the parent item:: + + $collection = new Collection([ + ['id' => 1, 'parent_id' => null, 'name' => 'Birds'], + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'], + ['id' => 6, 'parent_id' => null, 'name' => 'Fish'], + ]); + + $collection->nest('id', 'parent_id')->toArray(); + // Returns + [ + [ + 'id' => 1, + 'parent_id' => null, + 'name' => 'Birds', + 'children' => [ + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds', 'children' => []], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle', 'children' => []], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull', 'children' => []], + ] + ], + [ + 'id' => 6, + 'parent_id' => null, + 'name' => 'Fish', + 'children' => [ + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish', 'children' => []], + ] + ] + ]; + +Children elements are nested inside the ``children`` property inside each of the +items in the collection. This type of data representation is helpful for +rendering menus or traversing elements up to certain level in the tree. + +.. php:method:: listNested($dir = 'desc', $nestingKey = 'children') + +The inverse of ``nest()`` is ``listNested()``. This method allows you to flatten +a tree structure back into a linear structure. It takes two parameters; the +first one is the traversing mode (asc, desc or leaves), and the second one is +the name of the property containing the children for each element in the +collection. + +Taking the input the nested collection built in the previous example, we can +flatten it:: + + $nested->listNested()->toList(); + + // Returns + [ + ['id' => 1, 'parent_id' => null, 'name' => 'Birds', 'children' => [...]], + ['id' => 2, 'parent_id' => 1, 'name' => 'Land Birds'], + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 6, 'parent_id' => null, 'name' => 'Fish', 'children' => [...]], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'] + ] + +By default, the tree is traversed from the root to the leaves. You can also +instruct it to only return the leaf elements in the tree:: + + $nested->listNested()->toArray(); + + // Returns + [ + ['id' => 3, 'parent_id' => 1, 'name' => 'Eagle'], + ['id' => 4, 'parent_id' => 1, 'name' => 'Seagull'], + ['id' => 5, 'parent_id' => 6, 'name' => 'Clown Fish'] + ] + +Once you have converted a tree into a nested list, you can use the ``printer()`` +method to configure how the list output should be formatted:: + + $nested->listNested()->printer('name', 'id', '--')->toArray(); + + // Returns + [ + 3 => 'Eagle', + 4 => 'Seagull', + 5 -> '--Clown Fish', + ] + +The ``printer()`` method also lets you use a callback to generate the keys and +or values:: + + $nested->listNested()->printer( + function ($el) { + return $el->name; + }, + function ($el) { + return $el->id; + } + ); + +Other Methods +============= + +.. php:method:: isEmpty() + +Allows you to see if a collection contains any elements:: + + $collection = new Collection([]); + // Returns true + $collection->isEmpty(); + + $collection = new Collection([1]); + // Returns false + $collection->isEmpty(); + +.. php:method:: contains($value) + +Collections allow you to quickly check if they contain one particular +value: by using the ``contains()`` method:: + + $items = ['a' => 1, 'b' => 2, 'c' => 3]; + $collection = new Collection($items); + $hasThree = $collection->contains(3); + +Comparisons are performed using the ``===`` operator. If you wish to do looser +comparison types you can use the ``some()`` method. + +.. php:method:: shuffle() + +Sometimes you may wish to show a collection of values in a random order. In +order to create a new collection that will return each value in a randomized +position, use the ``shuffle``:: + + $collection = new Collection(['a' => 1, 'b' => 2, 'c' => 3]); + + // This could return [2, 3, 1] + $collection->shuffle()->toArray(); + +.. php:method:: transpose() + +When you transpose a collection, you get a new collection containing a row made +of the each of the original columns:: + + $items = [ + ['Products', '2012', '2013', '2014'], + ['Product A', '200', '100', '50'], + ['Product B', '300', '200', '100'], + ['Product C', '400', '300', '200'], + ] + $transpose = (new Collection($items))->transpose()->toList(); + + // Returns + [ + ['Products', 'Product A', 'Product B', 'Product C'], + ['2012', '200', '300', '400'], + ['2013', '100', '200', '300'], + ['2014', '50', '100', '200'], + ] + +.. versionadded:: 3.3.0 + ``Collection::transpose()`` was added in 3.3.0. + +Withdrawing Elements +-------------------- + +.. php:method:: sample(int $size) + +Shuffling a collection is often useful when doing quick statistical analysis. +Another common operation when doing this sort of task is withdrawing a few +random values out of a collection so that more tests can be performed on those. +For example, if you wanted to select 5 random users to which you'd like to apply +some A/B tests to, you can use the ``sample()`` function:: + + $collection = new Collection($people); + + // Withdraw maximum 20 random users from this collection + $testSubjects = $collection->sample(20); + +``sample()`` will take at most the number of values you specify in the first +argument. If there are not enough elements in the collection to satisfy the +sample, the full collection in a random order is returned. + +.. php:method:: take(int $size, int $from) + +Whenever you want to take a slice of a collection use the ``take()`` function, +it will create a new collection with at most the number of values you specify in +the first argument, starting from the position passed in the second argument:: + + $topFive = $collection->sortBy('age')->take(5); + + // Take 5 people from the collection starting from position 4 + $nextTopFive = $collection->sortBy('age')->take(5, 4); + +Positions are zero-based, therefore the first position number is ``0``. + +.. php:method:: skip(int $positions) + +While the second argument of ``take()`` can help you skip some elements before +getting them from the collection, you can also use ``skip()`` for the same +purpose as a way to take the rest of the elements after a certain position:: + + $collection = new Collection([1, 2, 3, 4]); + $allExceptFirstTwo = $collection->skip(2)->toList(); // [3, 4] + +.. php:method:: first() + +One of the most common uses of ``take()`` is getting the first element in the +collection. A shortcut method for achieving the same goal is using the +``first()`` method:: + + $collection = new Collection([5, 4, 3, 2]); + $collection->first(); // Returns 5 + +.. php:method:: last() + +Similarly, you can get the last element of a collection using the ``last()`` +method:: + + $collection = new Collection([5, 4, 3, 2]); + $collection->last(); // Returns 2 + +Expanding Collections +--------------------- + +.. php:method:: append(array|Traversable $items) + +You can compose multiple collections into a single one. This enables you to +gather data from various sources, concatenate it, and apply other collection +functions to it very smoothly. The ``append()`` method will return a new +collection containing the values from both sources:: + + $cakephpTweets = new Collection($tweets); + $myTimeline = $cakephpTweets->append($phpTweets); + + // Tweets containing cakefest from both sources + $myTimeline->filter(function ($tweet) { + return strpos($tweet, 'cakefest'); + }); + +.. warning:: + + When appending from different sources, you can expect some keys from both + collections to be the same. For example, when appending two simple arrays. + This can present a problem when converting a collection to an array using + ``toArray()``. If you do not want values from one collection to override + others in the previous one based on their key, make sure that you call + ``toList()`` in order to drop the keys and preserve all values. + +Modifiying Elements +------------------- + +.. php:method:: insert(string $path, array|Traversable $items) + +At times, you may have two separate sets of data that you would like to insert +the elements of one set into each of the elements of the other set. This is +a very common case when you fetch data from a data source that does not support +data-merging or joins natively. + +Collections offer an ``insert()`` method that will allow you to insert each of +the elements in one collection into a property inside each of the elements of +another collection:: + + $users = [ + ['username' => 'mark'], + ['username' => 'juan'], + ['username' => 'jose'] + ]; + + $languages = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'], + ['Javascript', 'Prolog'] + ]; + + $merged = (new Collection($users))->insert('skills', $languages); + +When converted to an array, the ``$merged`` collection will look like this:: + + [ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose', 'skills' => ['Javascript', 'Prolog']] + ]; + +The first parameter for the ``insert()`` method is a dot-separated path of +properties to follow so that the elements can be inserted at that position. The +second argument is anything that can be converted to a collection object. + +Please observe that elements are inserted by the position they are found, thus, +the first element of the second collection is merged into the first +element of the first collection. + +If there are not enough elements in the second collection to insert into the +first one, then the target property will be filled with ``null`` values:: + + $languages = [ + ['PHP', 'Python', 'Ruby'], + ['Bash', 'PHP', 'Javascript'] + ]; + + $merged = (new Collection($users))->insert('skills', $languages); + + // Will yield + [ + ['username' => 'mark', 'skills' => ['PHP', 'Python', 'Ruby']], + ['username' => 'juan', 'skills' => ['Bash', 'PHP', 'Javascript']], + ['username' => 'jose', 'skills' => null] + ]; + +The ``insert()`` method can operate array elements or objects implementing the +``ArrayAccess`` interface. + +Making Collection Methods Reusable +---------------------------------- + +Using closures for collection methods is great when the work to be done is small +and focused, but it can get messy very quickly. This becomes more obvious when +a lot of different methods need to be called or when the length of the closure +methods is more than just a few lines. + +There are also cases when the logic used for the collection methods can be +reused in multiple parts of your application. It is recommended that you +consider extracting complex collection logic to separate classes. For example, +imagine a lengthy closure like this one:: + + $collection + ->map(function ($row, $key) { + if (!empty($row['items'])) { + $row['total'] = collection($row['items'])->sumOf('price'); + } + + if (!empty($row['total'])) { + $row['tax_amount'] = $row['total'] * 0.25; + } + + // More code here... + + return $modifiedRow; + }); + +This can be refactored by creating another class:: + + class TotalOrderCalculator + { + public function __invoke($row, $key) + { + if (!empty($row['items'])) { + $row['total'] = collection($row['items'])->sumOf('price'); + } + + if (!empty($row['total'])) { + $row['tax_amount'] = $row['total'] * 0.25; + } + + // More code here... + + return $modifiedRow; + } + } + + // Use the logic in your map() call + $collection->map(new TotalOrderCalculator) + +.. php:method:: through(callable $c) + +Sometimes a chain of collection method calls can become reusable in other parts +of your application, but only if they are called in that specific order. In +those cases you can use ``through()`` in combination with a class implementing +``__invoke`` to distribute your handy data processing calls:: + + $collection + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... + +The above method calls can be extracted into a new class so they don't need to +be repeated every time:: + + class FinalCheckOutRowProcessor + { + public function __invoke($collection) + { + return $collection + ->map(new ShippingCostCalculator) + ->map(new TotalOrderCalculator) + ->map(new GiftCardPriceReducer) + ->buffered() + ... + } + } + + // Now you can use the through() method to call all methods at once + $collection->through(new FinalCheckOutRowProcessor); + +Optimizing Collections +---------------------- + +.. php:method:: buffered() + +Collections often perform most operations that you create using its functions in +a lazy way. This means that even though you can call a function, it does not +mean it is executed right away. This is true for a great deal of functions in +this class. Lazy evaluation allows you to save resources in situations +where you don't use all the values in a collection. You might not use all the +values when iteration stops early, or when an exception/failure case is reached +early. + +Additionally, lazy evaluation helps speed up some operations. Consider the +following example:: + + $collection = new Collection($oneMillionItems); + $collection = $collection->map(function ($item) { + return $item * 2; + }); + $itemsToShow = $collection->take(30); + +Had the collections not been lazy, we would have executed one million operations, +even though we only wanted to show 30 elements out of it. Instead, our map +operation was only applied to the 30 elements we used. We can also +derive benefits from this lazy evaluation for smaller collections when we +do more than one operation on them. For example: calling ``map()`` twice and +then ``filter()``. + +Lazy evaluation comes with its downside too. You could be doing the same +operations more than once if you optimize a collection prematurely. Consider +this example:: + + $ages = $collection->extract('age'); + + $youngerThan30 = $ages->filter(function ($item) { + return $item < 30; + }); + + $olderThan30 = $ages->filter(function ($item) { + return $item > 30; + }); + +If we iterate both ``youngerThan30`` and ``olderThan30``, the collection would +unfortunately execute the ``extract()`` operation twice. This is because +collections are immutable and the lazy-extracting operation would be done for +both filters. + +Luckily we can overcome this issue with a single function. If you plan to reuse +the values from certain operations more than once, you can compile the results +into another collection using the ``buffered()`` function:: + + $ages = $collection->extract('age')->buffered(); + $youngerThan30 = ... + $olderThan30 = ... + +Now, when both collections are iterated, they will only call the +extracting operation once. + +.. versionadded:: 3.5.0 + Collections initialized with an array are no longer iterated lazily in order to improve performance. + +Making Collections Rewindable +----------------------------- + +The ``buffered()`` method is also useful for converting non-rewindable iterators +into collections that can be iterated more than once:: + + // In PHP 5.5+ + public function results() + { + ... + foreach ($transientElements as $e) { + yield $e; + } + } + $rewindable = (new Collection(results()))->buffered(); + +Cloning Collections +------------------- + +.. php:method:: compile(bool $preserveKeys = true) + +Sometimes you need to get a clone of the elements from another +collection. This is useful when you need to iterate the same set from different +places at the same time. In order to clone a collection out of another use the +``compile()`` method:: + + $ages = $collection->extract('age')->compile(); + + foreach ($ages as $age) { + foreach ($collection as $element) { + echo h($element->name) . ' - ' . $age; + } + } + +.. meta:: + :title lang=en: Collections + :keywords lang=en: collections, cakephp, append, sort, compile, contains, countBy, each, every, extract, filter, first, firstMatch, groupBy, indexBy, jsonSerialize, map, match, max, min, reduce, reject, sample, shuffle, some, random, sortBy, take, toArray, insert, sumOf, stopWhen, unfold, through diff --git a/tl/core-libraries/email.rst b/tl/core-libraries/email.rst new file mode 100644 index 0000000000000000000000000000000000000000..e4712ae70ac68d0cc9aceff54c819533aa993b0b --- /dev/null +++ b/tl/core-libraries/email.rst @@ -0,0 +1,589 @@ +Email +##### + +.. php:namespace:: Cake\Mailer + +.. warning:: + Before version 3.1, the ``Email`` and ``Transport`` classes were under + the ``Cake\Network\Email`` namespace instead of the ``Cake\Mailer`` + namespace. + +.. php:class:: Email(mixed $profile = null) + +``Email`` is a new class to send email. With this +class you can send email from any place inside of your application. + +Basic Usage +=========== + +First of all, you should ensure the class is loaded:: + + use Cake\Mailer\Email; + +After you've loaded ``Email``, you can send an email with the following:: + + $email = new Email('default'); + $email->from(['me@example.com' => 'My Site']) + ->to('you@example.com') + ->subject('About') + ->send('My message'); + +Since ``Email``'s setter methods return the instance of the class, you are able to set its properties with method chaining. + +``Email`` has several methods for defining recipients - ``to()``, ``cc()``, +``bcc()``, ``addTo()``, ``addCc()`` and ``addBcc()``. The main difference being +that the first three will overwrite what was already set and the latter will just +add more recipients to their respective field:: + + $email = new Email(); + $email->to('to@example.com', 'To Example'); + $email->addTo('to2@example.com', 'To2 Example'); + // The email's To recipients are: to@example.com and to2@example.com + $email->to('test@example.com', 'ToTest Example'); + // The email's To recipient is: test@example.com + +.. deprecated:: 3.4.0 + Use ``setFrom()``, ``setTo()``, ``setCc()`` , ``setBcc()`` and ``setSubject()`` instead. + +Choosing the Sender +------------------- + +When sending email on behalf of other people, it's often a good idea to define the +original sender using the Sender header. You can do so using ``sender()``:: + + $email = new Email(); + $email->sender('app@example.com', 'MyApp emailer'); + +.. note:: + + It's also a good idea to set the envelope sender when sending mail on another + person's behalf. This prevents them from getting any messages about + deliverability. + +.. deprecated:: 3.4.0 + Use ``setSender()`` instead. + +.. _email-configuration: + +Configuration +============= + +Configuration for ``Email`` defaults is created using ``config()`` and +``configTransport()``. You should put your email presets in the +**config/app.php** file. The **config/app.default.php** file is an +example of this file. It is not required to define email configuration in +**config/app.php**. ``Email`` can be used without it and use the respective +methods to set all configurations separately or load an array of configs. + +By defining profiles and transports, you can keep your application code free of +configuration data, and avoid duplication that makes maintenance and deployment +more difficult. + +To load a predefined configuration, you can use the ``profile()`` method or pass it +to the constructor of ``Email``:: + + $email = new Email(); + $email->profile('default'); + + // Or in constructor + $email = new Email('default'); + +Instead of passing a string which matches a preset configuration name, you can +also just load an array of options:: + + $email = new Email(); + $email->profile(['from' => 'me@example.org', 'transport' => 'my_custom']); + + // Or in constructor + $email = new Email(['from' => 'me@example.org', 'transport' => 'my_custom']); + +.. versionchanged:: 3.1 + The ``default`` email profile is automatically set when an ``Email`` + instance is created. + +.. deprecated:: 3.4.0 + Use ``setProfile()`` instead of ``profile()``. + +Configuring Transports +---------------------- + +.. php:staticmethod:: configTransport($key, $config) + +Email messages are delivered by transports. Different transports allow you to +send messages via PHP's ``mail()`` function, SMTP servers, or not at all which +is useful for debugging. Configuring transports allows you to keep configuration +data out of your application code and makes deployment simpler as you can simply +change the configuration data. An example transport configuration looks like:: + + use Cake\Mailer\Email; + + // Sample Mail configuration + Email::configTransport('default', [ + 'className' => 'Mail' + ]); + + // Sample SMTP configuration. + Email::configTransport('gmail', [ + 'host' => 'ssl://smtp.gmail.com', + 'port' => 465, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp' + ]); + +You can configure SSL SMTP servers, like Gmail. To do so, put the ``ssl://`` +prefix in the host and configure the port value accordingly. You can also +enable TLS SMTP using the ``tls`` option:: + + use Cake\Mailer\Email; + + Email::configTransport('gmail', [ + 'host' => 'smtp.gmail.com', + 'port' => 587, + 'username' => 'my@gmail.com', + 'password' => 'secret', + 'className' => 'Smtp', + 'tls' => true + ]); + +The above configuration would enable TLS communication for email messages. + +.. warning:: + You will need to have access for less secure apps enabled in your Google + account for this to work: + `Allowing less secure apps to access your + account `__. + +.. note:: +   `Gmail SMTP settings `__. + +.. note:: + To use SSL + SMTP, you will need to have the SSL configured in your PHP + install. + +Configuration options can also be provided as a :term:`DSN` string. This is +useful when working with environment variables or :term:`PaaS` providers:: + + Email::configTransport('default', [ + 'url' => 'smtp://my@gmail.com:secret@smtp.gmail.com:587?tls=true', + ]); + +When using a DSN string you can define any additional parameters/options as +query string arguments. + +.. deprecated:: 3.4.0 + Use ``setConfigTransport()`` instead of ``configTransport()``. + +.. php:staticmethod:: dropTransport($key) + +Once configured, transports cannot be modified. In order to modify a transport +you must first drop it and then reconfigure it. + +.. _email-configurations: + +Configuration Profiles +---------------------- + +Defining delivery profiles allows you to consolidate common email settings into +re-usable profiles. Your application can have as many profiles as necessary. The +following configuration keys are used: + +- ``'from'``: Email or array of sender. See ``Email::from()``. +- ``'sender'``: Email or array of real sender. See ``Email::sender()``. +- ``'to'``: Email or array of destination. See ``Email::to()``. +- ``'cc'``: Email or array of carbon copy. See ``Email::cc()``. +- ``'bcc'``: Email or array of blind carbon copy. See ``Email::bcc()``. +- ``'replyTo'``: Email or array to reply the e-mail. See ``Email::replyTo()``. +- ``'readReceipt'``: Email address or an array of addresses to receive the + receipt of read. See ``Email::readReceipt()``. +- ``'returnPath'``: Email address or an array of addresses to return if have + some error. See ``Email::returnPath()``. +- ``'messageId'``: Message ID of e-mail. See ``Email::messageId()``. +- ``'subject'``: Subject of the message. See ``Email::subject()``. +- ``'message'``: Content of message. Do not set this field if you are using rendered content. +- ``'priority'``: Priority of the email as numeric value (usually from 1 to 5 with 1 being the highest). +- ``'headers'``: Headers to be included. See ``Email::headers()``. +- ``'viewRender'``: If you are using rendered content, set the view classname. + See ``Email::viewRender()``. +- ``'template'``: If you are using rendered content, set the template name. See + ``Email::template()``. +- ``'theme'``: Theme used when rendering template. See ``Email::theme()``. +- ``'layout'``: If you are using rendered content, set the layout to render. If + you want to render a template without layout, set this field to null. See + ``Email::template()``. +- ``'viewVars'``: If you are using rendered content, set the array with + variables to be used in the view. See ``Email::viewVars()``. +- ``'attachments'``: List of files to attach. See ``Email::attachments()``. +- ``'emailFormat'``: Format of email (html, text or both). See ``Email::emailFormat()``. +- ``'transport'``: Transport configuration name. See + :php:meth:`~Cake\\Mailer\\Email::configTransport()`. +- ``'log'``: Log level to log the email headers and message. ``true`` will use + LOG_DEBUG. See also :ref:`logging-levels`. +- ``'helpers'``: Array of helpers used in the email template. ``Email::helpers()``. + +All these configurations are optional, except ``'from'``. + +.. note:: + + The values of above keys using Email or array, like from, to, cc, etc will be passed + as first parameter of corresponding methods. The equivalent for: + ``Email::from('my@example.com', 'My Site')`` + would be defined as ``'from' => ['my@example.com' => 'My Site']`` in your config + +Setting Headers +=============== + +In ``Email`` you are free to set whatever headers you want. When migrating +to use Email, do not forget to put the ``X-`` prefix in your headers. + +See ``Email::headers()`` and ``Email::addHeaders()`` + +.. deprecated:: 3.4.0 + Use ``setHeaders()`` instead of ``headers()``. + +Sending Templated Emails +======================== + +Emails are often much more than just a simple text message. In order +to facilitate that, CakePHP provides a way to send emails using CakePHP's +:doc:`view layer `. + +The templates for emails reside in a special folder in your application's +``Template`` directory called ``Email``. Email views can also use layouts +and elements just like normal views:: + + $email = new Email(); + $email + ->template('welcome', 'fancy') + ->emailFormat('html') + ->to('bob@example.com') + ->from('app@domain.com') + ->send(); + +The above would use **src/Template/Email/html/welcome.ctp** for the view +and **src/Template/Layout/Email/html/fancy.ctp** for the layout. You can +send multipart templated email messages as well:: + + $email = new Email(); + $email + ->template('welcome', 'fancy') + ->emailFormat('both') + ->to('bob@example.com') + ->from('app@domain.com') + ->send(); + +This would use the following template files: + +* **src/Template/Email/text/welcome.ctp** +* **src/Template/Layout/Email/text/fancy.ctp** +* **src/Template/Email/html/welcome.ctp** +* **src/Template/Layout/Email/html/fancy.ctp** + +When sending templated emails you have the option of sending either +``text``, ``html`` or ``both``. + +You can set view variables with ``Email::viewVars()``:: + + $email = new Email('templated'); + $email->viewVars(['value' => 12345]); + +In your email templates you can use these with:: + +

    Here is your value:

    + +You can use helpers in emails as well, much like you can in normal template files. +By default only the ``HtmlHelper`` is loaded. You can load additional +helpers using the ``helpers()`` method:: + + $email->helpers(['Html', 'Custom', 'Text']); + +When setting helpers be sure to include 'Html' or it will be removed from the +helpers loaded in your email template. + +If you want to send email using templates in a plugin you can use the familiar +:term:`plugin syntax` to do so:: + + $email = new Email(); + $email->template('Blog.new_comment', 'Blog.auto_message'); + +The above would use template and layout from the Blog plugin as an example. + +In some cases, you might need to override the default template provided by plugins. +You can do this using themes by telling Email to use appropriate theme using +``Email::theme()`` method:: + + $email = new Email(); + $email->template('Blog.new_comment', 'Blog.auto_message'); + $email->theme('TestTheme'); + +This allows you to override the ``new_comment`` template in your theme without +modifying the Blog plugin. The template file needs to be created in the +following path: +**src/Template/Plugin/TestTheme/Plugin/Blog/Email/text/new_comment.ctp**. + +.. deprecated:: 3.4.0 + Use ``setTemplate()`` instead of ``template()``. Use ``setLayout()`` instead + of the layout argument of ``template()``. Use ``setTheme()`` instead of + ``theme()``. + +Sending Attachments +=================== + +.. php:method:: attachments($attachments) + +You can attach files to email messages as well. There are a few +different formats depending on what kind of files you have, and how +you want the filenames to appear in the recipient's mail client: + +1. String: ``$email->attachments('/full/file/path/file.png')`` will attach this + file with the name file.png. +2. Array: ``$email->attachments(['/full/file/path/file.png'])`` will have + the same behavior as using a string. +3. Array with key: + ``$email->attachments(['photo.png' => '/full/some_hash.png'])`` will + attach some_hash.png with the name photo.png. The recipient will see + photo.png, not some_hash.png. +4. Nested arrays:: + + $email->attachments([ + 'photo.png' => [ + 'file' => '/full/some_hash.png', + 'mimetype' => 'image/png', + 'contentId' => 'my-unique-id' + ] + ]); + + The above will attach the file with different mimetype and with custom + Content ID (when set the content ID the attachment is transformed to inline). + The mimetype and contentId are optional in this form. + + 4.1. When you are using the ``contentId``, you can use the file in the HTML + body like ````. + + 4.2. You can use the ``contentDisposition`` option to disable the + ``Content-Disposition`` header for an attachment. This is useful when + sending ical invites to clients using outlook. + + 4.3 Instead of the ``file`` option you can provide the file contents as + a string using the ``data`` option. This allows you to attach files without + needing file paths to them. + +.. deprecated:: 3.4.0 + Use ``setAttachments()`` instead of ``attachments()``. + +Using Transports +================ + +Transports are classes designed to send the e-mail over some protocol or method. +CakePHP supports the Mail (default), Debug and SMTP transports. + +To configure your method, you must use the :php:meth:`Cake\\Mailer\\Email::transport()` +method or have the transport in your configuration:: + + $email = new Email(); + + // Use a named transport already configured using Email::configTransport() + $email->transport('gmail'); + + // Use a constructed object. + $transport = new DebugTransport(); + $email->transport($transport); + +.. deprecated:: 3.4.0 + Use ``setTransport()`` instead of ``transport()``. + +Creating Custom Transports +-------------------------- + +You are able to create your custom transports to integrate with others email +systems (like SwiftMailer). To create your transport, first create the file +**src/Mailer/Transport/ExampleTransport.php** (where Example is the name of your +transport). To start off your file should look like:: + + namespace App\Mailer\Transport; + + use Cake\Mailer\AbstractTransport; + use Cake\Mailer\Email; + + class ExampleTransport extends AbstractTransport + { + public function send(Email $email) + { + // Do something. + } + } + +You must implement the method ``send(Email $email)`` with your custom logic. +Optionally, you can implement the ``config($config)`` method. ``config()`` is +called before send() and allows you to accept user configurations. By default, +this method puts the configuration in protected attribute ``$_config``. + +If you need to call additional methods on the transport before send, you can use +:php:meth:`Cake\\Mailer\\Email::getTransport()` to get an instance of the transport object. +Example:: + + $yourInstance = $email->getTransport()->transportClass(); + $yourInstance->myCustomMethod(); + $email->send(); + +Relaxing Address Validation Rules +--------------------------------- + +.. php:method:: emailPattern($pattern) + +If you are having validation issues when sending to non-compliant addresses, you +can relax the pattern used to validate email addresses. This is sometimes +necessary when dealing with some Japanese ISP's:: + + $email = new Email('default'); + + // Relax the email pattern, so you can send + // to non-conformant addresses. + $email->emailPattern($newPattern); + +.. deprecated:: 3.4.0 + Use ``setEmailPattern()`` instead of ``emailPattern()``. + +Sending Messages Quickly +======================== + +Sometimes you need a quick way to fire off an email, and you don't necessarily +want do setup a bunch of configuration ahead of time. +:php:meth:`Cake\\Mailer\\Email::deliver()` is intended for that purpose. + +You can create your configuration using +:php:meth:`Cake\\Mailer\\Email::config()`, or use an array with all +options that you need and use the static method ``Email::deliver()``. +Example:: + + Email::deliver('you@example.com', 'Subject', 'Message', ['from' => 'me@example.com']); + +This method will send an email to "you@example.com", from "me@example.com" with +subject "Subject" and content "Message". + +The return of ``deliver()`` is a :php:class:`Cake\\Mailer\\Email` instance with all +configurations set. If you do not want to send the email right away, and wish +to configure a few things before sending, you can pass the 5th parameter as +``false``. + +The 3rd parameter is the content of message or an array with variables (when +using rendered content). + +The 4th parameter can be an array with the configurations or a string with the +name of configuration in ``Configure``. + +If you want, you can pass the to, subject and message as null and do all +configurations in the 4th parameter (as array or using ``Configure``). +Check the list of :ref:`configurations ` to see all accepted configs. + +Sending Emails from CLI +======================= + +When sending emails within a CLI script (Shells, Tasks, ...) you should manually +set the domain name for Email to use. It will serve as the host name for the +message id (since there is no host name in a CLI environment):: + + $email->domain('www.example.org'); + // Results in message ids like ```` (valid) + // Instead of ``` (invalid) + +A valid message id can help to prevent emails ending up in spam folders. + +.. deprecated:: 3.4.0 + Use ``setDomain()`` instead of ``domain()``. + +Creating Reusable Emails +======================== + +.. versionadded:: 3.1.0 + +Mailers allow you to create reusable emails throughout your application. They +can also be used to contain multiple email configurations in one location. This +helps keep your code DRYer and keeps email configuration noise out of other +areas in your application. + +In this example we will be creating a ``Mailer`` that contains user-related +emails. To create our ``UserMailer``, create the file +**src/Mailer/UserMailer.php**. The contents of the file should look like the +following:: + + namespace App\Mailer; + + use Cake\Mailer\Mailer; + + class UserMailer extends Mailer + { + public function welcome($user) + { + $this + ->to($user->email) + ->subject(sprintf('Welcome %s', $user->name)) + ->template('welcome_mail', 'custom'); // By default template with same name as method name is used. + } + + public function resetPassword($user) + { + $this + ->to($user->email) + ->subject('Reset password') + ->set(['token' => $user->token]); + } + } + +In our example we have created two methods, one for sending a welcome email, and +another for sending a password reset email. Each of these methods expect a user +``Entity`` and utilizes its properties for configuring each email. + +We are now able to use our ``UserMailer`` to send out our user-related emails +from anywhere in our application. For example, if we wanted to send our welcome +email we could do the following:: + + namespace App\Controller; + + use Cake\Mailer\MailerAwareTrait; + + class UsersController extends AppController + { + use MailerAwareTrait; + + public function register() + { + $user = $this->Users->newEntity(); + if ($this->request->is('post')) { + $user = $this->Users->patchEntity($user, $this->request->getData()) + if ($this->Users->save($user)) { + $this->getMailer('User')->send('welcome', [$user]); + } + } + $this->set('user', $user); + } + } + +If we wanted to completely separate sending a user their welcome email from our +application's code, we can have our ``UserMailer`` subscribe to the +``Model.afterSave`` event. By subscribing to an event, we can keep our +application's user-related classes completely free of email-related logic and +instructions. For example, we could add the following to our ``UserMailer``:: + + public function implementedEvents() + { + return [ + 'Model.afterSave' => 'onRegistration' + ]; + } + + public function onRegistration(Event $event, EntityInterface $entity, ArrayObject $options) + { + if ($entity->isNew()) { + $this->send('welcome', [$entity]); + } + } + +The mailer object could now be registered as an event listener, and the +``onRegistration()`` method would be invoked every time the ``Model.afterSave`` +event would be fired. For information on how to register event listener objects, +please refer to the :ref:`registering-event-listeners` documentation. + +.. meta:: + :title lang=en: Email + :keywords lang=en: sending mail,email sender,envelope sender,php class,database configuration,sending emails,meth,shells,smtp,transports,attributes,array,config,flexibility,php email,new email,sending email,models diff --git a/tl/core-libraries/events.rst b/tl/core-libraries/events.rst new file mode 100644 index 0000000000000000000000000000000000000000..69bafac8239849c6385ae4feade0c477bbb4402a --- /dev/null +++ b/tl/core-libraries/events.rst @@ -0,0 +1,539 @@ +Events System +############# + +Creating maintainable applications is both a science and an art. It is +well-known that a key for having good quality code is making your objects +loosely coupled and strongly cohesive at the same time. Cohesion means that +all methods and properties for a class are strongly related to the class +itself and it is not trying to do the job other objects should be doing, +while loosely coupling is the measure of how little a class is "wired" +to external objects, and how much that class is depending on them. + +There are certain cases where you need to cleanly communicate with other parts +of an application, without having to hard code dependencies, thus losing +cohesion and increasing class coupling. Using the Observer pattern, which allows +objects to notify other objects and anonymous listeners about changes is +a useful pattern to achieve this goal. + +Listeners in the observer pattern can subscribe to events and choose to act upon +them if they are relevant. If you have used JavaScript, there is a good chance +that you are already familiar with event driven programming. + +CakePHP emulates several aspects of how events are triggered and managed in +popular JavaScript libraries such as jQuery. In the CakePHP implementation, an +event object is dispatched to all listeners. The event object holds information +about the event, and provides the ability to stop event propagation at any +point. Listeners can register themselves or can delegate this task to other +objects and have the chance to alter the state and the event itself for the rest +of the callbacks. + +The event subsystem is at the heart of Model, Behavior, Controller, View and +Helper callbacks. If you've ever used any of them, you are already somewhat +familiar with events in CakePHP. + +Example Event Usage +=================== + +Let's suppose you are building a Cart plugin, and you'd like to focus on just +handling order logic. You don't really want to include shipping logic, emailing +the user or decrementing the item from the stock, but these are important tasks +to the people using your plugin. If you were not using events, you may try to +implement this by attaching behaviors to models, or adding components to your +controllers. Doing so represents a challenge most of the time, since you +would have to come up with the code for externally loading those behaviors or +attaching hooks to your plugin controllers. + +Instead, you can use events to allow you to cleanly separate the concerns of +your code and allow additional concerns to hook into your plugin using events. +For example, in your Cart plugin you have an Orders model that deals with +creating orders. You'd like to notify the rest of the application that an order +has been created. To keep your Orders model clean you could use events:: + + // Cart/Model/Table/OrdersTable.php + namespace Cart\Model\Table; + + use Cake\Event\Event; + use Cake\ORM\Table; + + class OrdersTable extends Table + { + public function place($order) + { + if ($this->save($order)) { + $this->Cart->remove($order); + $event = new Event('Model.Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->eventManager()->dispatch($event); + return true; + } + return false; + } + } + +The above code allows you to notify the other parts of the application +that an order has been created. You can then do tasks like send email +notifications, update stock, log relevant statistics and other tasks in separate +objects that focus on those concerns. + +Accessing Event Managers +======================== + +In CakePHP events are triggered against event managers. Event managers are +available in every Table, View and Controller using ``eventManager()``:: + + $events = $this->eventManager(); + +Each model has a separate event manager, while the View and Controller +share one. This allows model events to be self contained, and allow components +or controllers to act upon events created in the view if necessary. + +Global Event Manager +-------------------- + +In addition to instance level event managers, CakePHP provides a global event +manager that allows you to listen to any event fired in an application. This is +useful when attaching listeners to a specific instance might be cumbersome or +difficult. The global manager is a singleton instance of +:php:class:`Cake\\Event\\EventManager`. Listeners attached to the global +dispatcher will be fired before instance listeners at the same priority. You can +access the global manager using a static method:: + + // In any configuration file or piece of code that executes before the event + use Cake\Event\EventManager; + + EventManager::instance()->on( + 'Model.Order.afterPlace', + $aCallback + ); + +One important thing you should consider is that there are events that will be +triggered having the same name but different subjects, so checking it in the +event object is usually required in any function that gets attached globally in +order to prevent some bugs. Remember that with the flexibility of using the +global manager, some additional complexity is incurred. + +:php:meth:`Cake\\Event\\EventManager::dispatch()` method accepts the event +object as an argument and notifies all listener and callbacks passing this +object along. The listeners will handle all the extra logic around the +``afterPlace`` event, you can log the time, send emails, update user statistics +possibly in separate objects and even delegating it to offline tasks if you have +the need. + +.. _tracking-events: + +Tracking Events +--------------- + +To keep a list of events that are fired on a particular ``EventManager``, you +can enable event tracking. To do so, simply attach an +:php:class:`Cake\\Event\\EventList` to the manager:: + + EventManager::instance()->setEventList(new EventList()); + +After firing an event on the manager, you can retrieve it from the event list:: + + $eventsFired = EventManager::instance()->getEventList(); + $firstEvent = $eventsFired[0]; + +Tracking can be disabled by removing the event list or calling +:php:meth:`Cake\\Event\\EventList::trackEvents(false)`. + +.. versionadded:: 3.2.11 + Event tracking and :php:class:`Cake\\Event\\EventList` were added. + +Core Events +=========== + +There are a number of core events within the framework which your application +can listen to. Each layer of CakePHP emits events that you can use in your +application. + +* :ref:`ORM/Model events ` +* :ref:`Controller events ` +* :ref:`View events ` + +.. _registering-event-listeners: + +Registering Listeners +===================== + +Listeners are the preferred way to register callbacks for an event. This is done +by implementing the :php:class:`Cake\\Event\\EventListenerInterface` interface +in any class you wish to register some callbacks. Classes implementing it need +to provide the ``implementedEvents()`` method. This method must return an +associative array with all event names that the class will handle. + +To continue our previous example, let's imagine we have a UserStatistic class +responsible for calculating a user's purchasing history, and compiling into +global site statistics. This is a great place to use a listener class. Doing so +allows you to concentrate the statistics logic in one place and react to events +as necessary. Our ``UserStatistics`` listener might start out like:: + + use Cake\Event\EventListenerInterface; + + class UserStatistic implements EventListenerInterface + { + public function implementedEvents() + { + return [ + 'Model.Order.afterPlace' => 'updateBuyStatistic', + ]; + } + + public function updateBuyStatistic($event, $order) + { + // Code to update statistics + } + } + + // Attach the UserStatistic object to the Order's event manager + $statistics = new UserStatistic(); + $this->Orders->eventManager()->on($statistics); + +As you can see in the above code, the ``on()`` function will accept instances +of the ``EventListener`` interface. Internally, the event manager will use +``implementedEvents()`` to attach the correct callbacks. + +Registering Anonymous Listeners +------------------------------- + +While event listener objects are generally a better way to implement listeners, +you can also bind any ``callable`` as an event listener. For example if we +wanted to put any orders into the log files, we could use a simple anonymous +function to do so:: + + use Cake\Log\Log; + + $this->Orders->eventManager()->on('Model.Order.afterPlace', function ($event) { + Log::write( + 'info', + 'A new order was placed with id: ' . $event->getSubject()->id + ); + }); + +In addition to anonymous functions you can use any other callable type that PHP +supports:: + + $events = [ + 'email-sending' => 'EmailSender::sendBuyEmail', + 'inventory' => [$this->InventoryManager, 'decrement'], + ]; + foreach ($events as $callable) { + $eventManager->on('Model.Order.afterPlace', $callable); + } + +When working with plugins that don't trigger specific events, you can leverage +event listeners on the default events. Lets take an example 'UserFeedback' +plugin which handles feedback forms from users. From your application you would +like to know when a Feedback record has been saved and ultimately act on it. You +can listen to the global ``Model.afterSave`` event. However, you can take +a more direct approach and only listen to the event you really need:: + + // You can create the following before the + // save operation, ie. config/bootstrap.php + use Cake\ORM\TableRegistry; + // If sending emails + use Cake\Mailer\Email; + + TableRegistry::get('ThirdPartyPlugin.Feedbacks') + ->eventManager() + ->on('Model.afterSave', function($event, $entity) + { + // For example we can send an email to the admin + // Prior to 3.4 use from()/to()/subject() methods + $email = new Email('default'); + $email->setFrom(['info@yoursite.com' => 'Your Site']) + ->setTo('admin@yoursite.com') + ->setSubject('New Feedback - Your Site') + ->send('Body of message'); + }); + +You can use this same approach to bind listener objects. + +Interacting with Existing Listeners +----------------------------------- + +Assuming several event listeners have been registered the presence or absence +of a particular event pattern can be used as the basis of some action.:: + + // Attach listeners to EventManager. + $this->eventManager()->on('User.Registration', [$this, 'userRegistration']); + $this->eventManager()->on('User.Verification', [$this, 'userVerification']); + $this->eventManager()->on('User.Authorization', [$this, 'userAuthorization']); + + // Somewhere else in your application. + $events = $this->eventManager()->matchingListeners('Verification'); + if (!empty($events)) { + // Perform logic related to presence of 'Verification' event listener. + // For example removing the listener if present. + $this->eventManager()->off('User.Verification'); + } else { + // Perform logic related to absence of 'Verification' event listener + } + +.. note:: + + The pattern passed to the ``matchingListeners`` method is case sensitive. + +.. versionadded:: 3.2.3 + + The ``matchingListeners`` method returns an array of events matching + a search pattern. + +.. _event-priorities: + +Establishing Priorities +----------------------- + +In some cases you might want to control the order that listeners are invoked. +For instance, if we go back to our user statistics example. It would be ideal if +this listener was called at the end of the stack. By calling it at the end of +the listener stack, we can ensure that the event was not cancelled, and that no +other listeners raised exceptions. We can also get the final state of the +objects in the case that other listeners have modified the subject or event +object. + +Priorities are defined as an integer when adding a listener. The higher the +number, the later the method will be fired. The default priority for all +listeners is ``10``. If you need your method to be run earlier, using any value +below this default will work. On the other hand if you desire to run the +callback after the others, using a number above ``10`` will do. + +If two callbacks happen to have the same priority value, they will be executed +with a the order they were attached. You set priorities using the ``on()`` +method for callbacks, and declaring it in the ``implementedEvents()`` function +for event listeners:: + + // Setting priority for a callback + $callback = [$this, 'doSomething']; + $this->eventManager()->on( + 'Model.Order.afterPlace', + ['priority' => 2], + $callback + ); + + // Setting priority for a listener + class UserStatistic implements EventListenerInterface + { + public function implementedEvents() + { + return [ + 'Model.Order.afterPlace' => [ + 'callable' => 'updateBuyStatistic', + 'priority' => 100 + ], + ]; + } + } + +As you see, the main difference for ``EventListener`` objects is that you need +to use an array for specifying the callable method and the priority preference. +The ``callable`` key is a special array entry that the manager will read to know +what function in the class it should be calling. + +Getting Event Data as Function Parameters +----------------------------------------- + +When events have data provided in their constructor, the provided data is +converted into arguments for the listeners. An example from the View layer is +the afterRender callback:: + + $this->eventManager() + ->dispatch(new Event('View.afterRender', $this, ['view' => $viewFileName])); + +The listeners of the ``View.afterRender`` callback should have the following +signature:: + + function (Event $event, $viewFileName) + +Each value provided to the Event constructor will be converted into function +parameters in the order they appear in the data array. If you use an associative +array, the result of ``array_values`` will determine the function argument +order. + +.. note:: + + Unlike in 2.x, converting event data to listener arguments is the default + behavior and cannot be disabled. + +Dispatching Events +================== + +Once you have obtained an instance of an event manager you can dispatch events +using :php:meth:`~Cake\\Event\\EventManager::dispatch()`. This method takes an +instance of the :php:class:`Cake\\Event\\Event` class. Let's look at dispatching +an event:: + + // An event listener has to be instantiated before dispatching an event. + // Create a new event and dispatch it. + $event = new Event('Model.Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->eventManager()->dispatch($event); + +:php:class:`Cake\\Event\\Event` accepts 3 arguments in its constructor. The +first one is the event name, you should try to keep this name as unique as +possible, while making it readable. We suggest a convention as follows: +``Layer.eventName`` for general events happening at a layer level (e.g. +``Controller.startup``, ``View.beforeRender``) and ``Layer.Class.eventName`` for +events happening in specific classes on a layer, for example +``Model.User.afterRegister`` or ``Controller.Courses.invalidAccess``. + +The second argument is the ``subject``, meaning the object associated to the +event, usually when it is the same class triggering events about itself, using +``$this`` will be the most common case. Although a Component could trigger +controller events too. The subject class is important because listeners will get +immediate access to the object properties and have the chance to inspect or +change them on the fly. + +Finally, the third argument is any additional event data.This can be any data +you consider useful to pass around so listeners can act upon it. While this can +be an argument of any type, we recommend passing an associative array. + +The :php:meth:`~Cake\\Event\\EventManager::dispatch()` method accepts an event +object as an argument and notifies all subscribed listeners. + +.. _stopping-events: + +Stopping Events +--------------- + +Much like DOM events, you may want to stop an event to prevent additional +listeners from being notified. You can see this in action during model callbacks +(e.g. beforeSave) in which it is possible to stop the saving operation if +the code detects it cannot proceed any further. + +In order to stop events you can either return ``false`` in your callbacks or +call the ``stopPropagation()`` method on the event object:: + + public function doSomething($event) + { + // ... + return false; // Stops the event + } + + public function updateBuyStatistic($event) + { + // ... + $event->stopPropagation(); + } + +Stopping an event will prevent any additional callbacks from being called. +Additionally the code triggering the event may behave differently based on the +event being stopped or not. Generally it does not make sense to stop 'after' +events, but stopping 'before' events is often used to prevent the entire +operation from occurring. + +To check if an event was stopped, you call the ``isStopped()`` method in the +event object:: + + public function place($order) + { + $event = new Event('Model.Order.beforePlace', $this, ['order' => $order]); + $this->eventManager()->dispatch($event); + if ($event->isStopped()) { + return false; + } + if ($this->Orders->save($order)) { + // ... + } + // ... + } + +In the previous example the order would not get saved if the event is stopped +during the ``beforePlace`` process. + +Getting Event Results +--------------------- + +Every time a callback returns a non-null non-false value, it gets stored in the +``$result`` property of the event object. This is useful when you want to allow +callbacks to modify the event execution. Let's take again our ``beforePlace`` +example and let callbacks modify the ``$order`` data. + +Event results can be altered either using the event object result property +directly or returning the value in the callback itself:: + + // A listener callback + public function doSomething($event) + { + // ... + $alteredData = $event->getData('order') + $moreData; + return $alteredData; + } + + // Another listener callback + public function doSomethingElse($event) + { + // ... + $event->setResult(['order' => $alteredData] + $this->result()); + } + + // Using the event result + public function place($order) + { + $event = new Event('Model.Order.beforePlace', $this, ['order' => $order]); + $this->eventManager()->dispatch($event); + if (!empty($event->getResult()['order'])) { + $order = $event->getResult()['order']; + } + if ($this->Orders->save($order)) { + // ... + } + // ... + } + +It is possible to alter any event object property and have the new data passed +to the next callback. In most of the cases, providing objects as event data or +result and directly altering the object is the best solution as the reference is +kept the same and modifications are shared across all callback calls. + +Removing Callbacks and Listeners +-------------------------------- + +If for any reason you want to remove any callback from the event manager just +call the :php:meth:`Cake\\Event\\EventManager::off()` method using as +arguments the first two params you used for attaching it:: + + // Attaching a function + $this->eventManager()->on('My.event', [$this, 'doSomething']); + + // Detaching the function + $this->eventManager()->off('My.event', [$this, 'doSomething']); + + // Attaching an anonymous function. + $myFunction = function ($event) { ... }; + $this->eventManager()->on('My.event', $myFunction); + + // Detaching the anonymous function + $this->eventManager()->off('My.event', $myFunction); + + // Adding a EventListener + $listener = new MyEventLister(); + $this->eventManager()->on($listener); + + // Detaching a single event key from a listener + $this->eventManager()->off('My.event', $listener); + + // Detaching all callbacks implemented by a listener + $this->eventManager()->off($listener); + +Events are a great way of separating concerns in your application and make +classes both cohesive and decoupled from each other. Events can be utilized to +de-couple application code and make extensible plugins. + +Keep in mind that with great power comes great responsibility. Using too many +events can make debugging harder and require additional integration testing. + +Additional Reading +================== + +* :doc:`/orm/behaviors` +* :doc:`/controllers/components` +* :doc:`/views/helpers` +* :ref:`testing-events` + +.. meta:: + :title lang=en: Events system + :keywords lang=en: events, dispatch, decoupling, cakephp, callbacks, triggers, hooks, php diff --git a/tl/core-libraries/file-folder.rst b/tl/core-libraries/file-folder.rst new file mode 100644 index 0000000000000000000000000000000000000000..f4607aa34c91991e266881a2ed599a899396c7b1 --- /dev/null +++ b/tl/core-libraries/file-folder.rst @@ -0,0 +1,445 @@ +Folder & File +############# + +.. php:namespace:: Cake\Filesystem + +The Folder and File utilities are convenience classes to help you read from and +write/append to files; list files within a folder and other common directory +related tasks. + +Basic Usage +=========== + +Ensure the classes are loaded:: + + use Cake\Filesystem\Folder; + use Cake\Filesystem\File; + +Then we can setup a new folder instance:: + + $dir = new Folder('/path/to/folder'); + +and search for all *.ctp* files within that folder using regex:: + + $files = $dir->find('.*\.ctp'); + +Now we can loop through the files and read from or write/append to the contents or +simply delete the file:: + + foreach ($files as $file) { + $file = new File($dir->pwd() . DS . $file); + $contents = $file->read(); + // $file->write('I am overwriting the contents of this file'); + // $file->append('I am adding to the bottom of this file.'); + // $file->delete(); // I am deleting this file + $file->close(); // Be sure to close the file when you're done + } + +Folder API +========== + +.. php:class:: Folder(string $path = false, boolean $create = false, string|boolean $mode = false) + +:: + + // Create a new folder with 0755 permissions + $dir = new Folder('/path/to/folder', true, 0755); + +.. php:attr:: path + + Path of the current folder. :php:meth:`Folder::pwd()` will return the same + information. + +.. php:attr:: sort + + Whether or not the list results should be sorted by name. + +.. php:attr:: mode + + Mode to be used when creating folders. Defaults to ``0755``. Does nothing on + Windows machines. + +.. php:staticmethod:: addPathElement(string $path, string $element) + + Returns $path with $element added, with correct slash in-between:: + + $path = Folder::addPathElement('/a/path/for', 'testing'); + // $path equals /a/path/for/testing + + $element can also be an array:: + + $path = Folder::addPathElement('/a/path/for', ['testing', 'another']); + // $path equals /a/path/for/testing/another + +.. php:method:: cd( $path ) + + Change directory to $path. Returns ``false`` on failure:: + + $folder = new Folder('/foo'); + echo $folder->path; // Prints /foo + $folder->cd('/bar'); + echo $folder->path; // Prints /bar + $false = $folder->cd('/non-existent-folder'); + +.. php:method:: chmod(string $path, integer $mode = false, boolean $recursive = true, array $exceptions = []) + + Change the mode on a directory structure recursively. This includes + changing the mode on files as well:: + + $dir = new Folder(); + $dir->chmod('/path/to/folder', 0755, true, ['skip_me.php']); + +.. php:method:: copy(array|string $options = []) + + Recursively copy a directory. The only parameter $options can either + be a path into copy to or an array of options:: + + $folder1 = new Folder('/path/to/folder1'); + $folder1->copy('/path/to/folder2'); + // Will put folder1 and all its contents into folder2 + + $folder = new Folder('/path/to/folder'); + $folder->copy([ + 'to' => '/path/to/new/folder', + 'from' => '/path/to/copy/from', // Will cause a cd() to occur + 'mode' => 0755, + 'skip' => ['skip-me.php', '.git'], + 'scheme' => Folder::SKIP // Skip directories/files that already exist. + ]); + + There are 3 supported schemes: + + * ``Folder::SKIP`` skip copying/moving files & directories that exist in the + destination directory. + * ``Folder::MERGE`` merge the source/destination directories. Files in the + source directory will replace files in the target directory. Directory + contents will be merged. + * ``Folder::OVERWRITE`` overwrite existing files & directories in the target + directory with those in the source directory. If both the target and + destination contain the same subdirectory, the target directory's contents + will be removed and replaced with the source's. + +.. php:staticmethod:: correctSlashFor(string $path) + + Returns a correct set of slashes for given $path ('\\' for + Windows paths and '/' for other paths). + +.. php:method:: create(string $pathname, integer $mode = false) + + Create a directory structure recursively. Can be used to create + deep path structures like `/foo/bar/baz/shoe/horn`:: + + $folder = new Folder(); + if ($folder->create('foo' . DS . 'bar' . DS . 'baz' . DS . 'shoe' . DS . 'horn')) { + // Successfully created the nested folders + } + +.. php:method:: delete(string $path = null) + + Recursively remove directories if the system allows:: + + $folder = new Folder('foo'); + if ($folder->delete()) { + // Successfully deleted foo and its nested folders + } + +.. php:method:: dirsize() + + Returns the size in bytes of this Folder and its contents. + +.. php:method:: errors() + + Get the error from latest method. + +.. php:method:: find(string $regexpPattern = '.*', boolean $sort = false) + + Returns an array of all matching files in the current directory:: + + // Find all .png in your webroot/img/ folder and sort the results + $dir = new Folder(WWW_ROOT . 'img'); + $files = $dir->find('.*\.png', true); + /* + Array + ( + [0] => cake.icon.png + [1] => test-error-icon.png + [2] => test-fail-icon.png + [3] => test-pass-icon.png + [4] => test-skip-icon.png + ) + */ + +.. note:: + + The folder find and findRecursive methods will only find files. If you + would like to get folders and files see :php:meth:`Folder::read()` or + :php:meth:`Folder::tree()` + +.. php:method:: findRecursive(string $pattern = '.*', boolean $sort = false) + + Returns an array of all matching files in and below the current directory:: + + // Recursively find files beginning with test or index + $dir = new Folder(WWW_ROOT); + $files = $dir->findRecursive('(test|index).*'); + /* + Array + ( + [0] => /var/www/cake/webroot/index.php + [1] => /var/www/cake/webroot/test.php + [2] => /var/www/cake/webroot/img/test-skip-icon.png + [3] => /var/www/cake/webroot/img/test-fail-icon.png + [4] => /var/www/cake/webroot/img/test-error-icon.png + [5] => /var/www/cake/webroot/img/test-pass-icon.png + ) + */ + +.. php:method:: inCakePath(string $path = '') + + Returns ``true`` if the file is in a given CakePath. + +.. php:method:: inPath(string $path = '', boolean $reverse = false) + + Returns ``true`` if the file is in the given path:: + + $Folder = new Folder(WWW_ROOT); + $result = $Folder->inPath(APP); + // $result = true, /var/www/example/ is in /var/www/example/webroot/ + + $result = $Folder->inPath(WWW_ROOT . 'img' . DS, true); + // $result = true, /var/www/example/webroot/ is in /var/www/example/webroot/img/ + +.. php:staticmethod:: isAbsolute(string $path) + + Returns ``true`` if the given $path is an absolute path. + +.. php:staticmethod:: isSlashTerm(string $path) + + Returns ``true`` if given $path ends in a slash (i.e. is slash-terminated):: + + $result = Folder::isSlashTerm('/my/test/path'); + // $result = false + $result = Folder::isSlashTerm('/my/test/path/'); + // $result = true + +.. php:staticmethod:: isWindowsPath(string $path) + + Returns ``true`` if the given $path is a Windows path. + +.. php:method:: messages() + + Get the messages from the latest method. + +.. php:method:: move(array $options) + + Recursive directory move. + +.. php:staticmethod:: normalizePath(string $path) + + Returns a correct set of slashes for given $path ('\\' for + Windows paths and '/' for other paths). + +.. php:method:: pwd() + + Return current path. + +.. php:method:: read(boolean $sort = true, array|boolean $exceptions = false, boolean $fullPath = false) + + Returns an array of the contents of the current directory. The + returned array holds two sub arrays: One of directories and one of files:: + + $dir = new Folder(WWW_ROOT); + $files = $dir->read(true, ['files', 'index.php']); + /* + Array + ( + [0] => Array // Folders + ( + [0] => css + [1] => img + [2] => js + ) + [1] => Array // Files + ( + [0] => .htaccess + [1] => favicon.ico + [2] => test.php + ) + ) + */ + +.. php:method:: realpath(string $path) + + Get the real path (taking ".." and such into account). + +.. php:staticmethod:: slashTerm(string $path) + + Returns $path with added terminating slash (corrected for + Windows or other OS). + +.. php:method:: tree(null|string $path = null, array|boolean $exceptions = true, null|string $type = null) + + Returns an array of nested directories and files in each directory. + +File API +======== + +.. php:class:: File(string $path, boolean $create = false, integer $mode = 755) + +:: + + // Create a new file with 0644 permissions + $file = new File('/path/to/file.php', true, 0644); + +.. php:attr:: Folder + + The Folder object of the file. + +.. php:attr:: name + + The name of the file with the extension. Differs from + :php:meth:`File::name()` which returns the name without the extension. + +.. php:attr:: info + + An array of file info. Use :php:meth:`File::info()` instead. + +.. php:attr:: handle + + Holds the file handler resource if the file is opened. + +.. php:attr:: lock + + Enable locking for file reading and writing. + +.. php:attr:: path + + The current file's absolute path. + +.. php:method:: append(string $data, boolean $force = false) + + Append the given data string to the current file. + +.. php:method:: close() + + Closes the current file if it is opened. + +.. php:method:: copy(string $dest, boolean $overwrite = true) + + Copy the file to $dest. + +.. php:method:: create() + + Creates the file. + +.. php:method:: delete() + + Deletes the file. + +.. php:method:: executable() + + Returns ``true`` if the file is executable. + +.. php:method:: exists() + + Returns ``true`` if the file exists. + +.. php:method:: ext() + + Returns the file extension. + +.. php:method:: Folder() + + Returns the current folder. + +.. php:method:: group() + + Returns the file's group, or ``false`` in case of an error. + +.. php:method:: info() + + Returns the file info. + +.. php:method:: lastAccess( ) + + Returns last access time. + +.. php:method:: lastChange() + + Returns last modified time, or ``false`` in case of an error. + +.. php:method:: md5(integer|boolean $maxsize = 5) + + Get the MD5 Checksum of file with previous check of filesize, + or ``false`` in case of an error. + +.. php:method:: name() + + Returns the file name without extension. + +.. php:method:: offset(integer|boolean $offset = false, integer $seek = 0) + + Sets or gets the offset for the currently opened file. + +.. php:method:: open(string $mode = 'r', boolean $force = false) + + Opens the current file with the given $mode. + +.. php:method:: owner() + + Returns the file's owner. + +.. php:method:: perms() + + Returns the "chmod" (permissions) of the file. + +.. php:staticmethod:: prepare(string $data, boolean $forceWindows = false) + + Prepares a ascii string for writing. Converts line endings to the + correct terminator for the current platform. For Windows "\\r\\n" + will be used, "\\n" for all other platforms. + +.. php:method:: pwd() + + Returns the full path of the file. + +.. php:method:: read(string $bytes = false, string $mode = 'rb', boolean $force = false) + + Return the contents of the current file as a string or return ``false`` on failure. + +.. php:method:: readable() + + Returns ``true`` if the file is readable. + +.. php:method:: safe(string $name = null, string $ext = null) + + Makes filename safe for saving. + +.. php:method:: size() + + Returns the filesize in bytes. + +.. php:method:: writable() + + Returns ``true`` if the file is writable. + +.. php:method:: write(string $data, string $mode = 'w', boolean$force = false) + + Write given data to the current file. + +.. php:method:: mime() + + Get the file's mimetype, returns ``false`` on failure. + +.. php:method:: replaceText( $search, $replace ) + + Replaces text in a file. Returns ``false`` on failure and ``true`` on success. + +.. todo:: + + Better explain how to use each method with both classes. + +.. meta:: + :title lang=en: Folder & File + :description lang=en: The Folder and File utilities are convenience classes to help you read, write, and append to files; list files within a folder and other common directory related tasks. + :keywords lang=en: file,folder,cakephp utility,read file,write file,append file,recursively copy,copy options,folder path,class folder,file php,php files,change directory,file utilities,new folder,directory structure,delete file diff --git a/tl/core-libraries/form.rst b/tl/core-libraries/form.rst new file mode 100644 index 0000000000000000000000000000000000000000..927c7704495f6a6a19070c978a5f0f0369296b43 --- /dev/null +++ b/tl/core-libraries/form.rst @@ -0,0 +1,197 @@ +Modelless Forms +############### + +.. php:namespace:: Cake\Form + +.. php:class:: Form + +Most of the time you will have forms backed by :doc:`ORM entities ` +and :doc:`ORM tables ` or other peristent stores, +but there are times when you'll need to validate user input and then perform an +action if the data is valid. The most common example of this is a contact form. + +Creating a Form +=============== + +Generally when using the Form class you'll want to use a subclass to define your +form. This makes testing easier, and lets you re-use your form. Forms are put +into **src/Form** and usually have ``Form`` as a class suffix. For example, +a simple contact form would look like:: + + // in src/Form/ContactForm.php + namespace App\Form; + + use Cake\Form\Form; + use Cake\Form\Schema; + use Cake\Validation\Validator; + + class ContactForm extends Form + { + + protected function _buildSchema(Schema $schema) + { + return $schema->addField('name', 'string') + ->addField('email', ['type' => 'string']) + ->addField('body', ['type' => 'text']); + } + + protected function _buildValidator(Validator $validator) + { + return $validator->add('name', 'length', [ + 'rule' => ['minLength', 10], + 'message' => 'A name is required' + ])->add('email', 'format', [ + 'rule' => 'email', + 'message' => 'A valid email address is required', + ]); + } + + protected function _execute(array $data) + { + // Send an email. + return true; + } + } + +In the above example we see the 3 hook methods that forms provide: + +* ``_buildSchema`` is used to define the schema data that is used by FormHelper + to create an HTML form. You can define field type, length, and precision. +* ``_buildValidator`` Gets a :php:class:`Cake\\Validation\\Validator` instance + that you can attach validators to. +* ``_execute`` lets you define the behavior you want to happen when + ``execute()`` is called and the data is valid. + +You can always define additional public methods as you need as well. + +Processing Request Data +======================= + +Once you've defined your form, you can use it in your controller to process +and validate request data:: + + // In a controller + namespace App\Controller; + + use App\Controller\AppController; + use App\Form\ContactForm; + + class ContactController extends AppController + { + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('We will get back to you soon.'); + } else { + $this->Flash->error('There was a problem submitting your form.'); + } + } + $this->set('contact', $contact); + } + } + +In the above example, we use the ``execute()`` method to run our form's +``_execute()`` method only when the data is valid, and set flash messages +accordingly. We could have also used the ``validate()`` method to only validate +the request data:: + + $isValid = $form->validate($this->request->getData()); + +Setting Form Values +=================== + +In order to set the values for the fields of a modelless form, one can define +the values using ``$this->request->data()``, like in all other forms created by +the FormHelper:: + + // In a controller + namespace App\Controller; + + use App\Controller\AppController; + use App\Form\ContactForm; + + class ContactController extends AppController + { + public function index() + { + $contact = new ContactForm(); + if ($this->request->is('post')) { + if ($contact->execute($this->request->getData())) { + $this->Flash->success('We will get back to you soon.'); + } else { + $this->Flash->error('There was a problem submitting your form.'); + } + } + + if ($this->request->is('get')) { + // Values from the User Model e.g. + $this->request->data('name', 'John Doe'); + $this->request->data('email','john.doe@example.com'); + } + + $this->set('contact', $contact); + } + } + +Values should only be defined if the request method is GET, otherwise +you will overwrite your previous POST Data which might have been incorrect +and not been saved. + +Getting Form Errors +=================== + +Once a form has been validated you can retrieve the errors from it:: + + $errors = $form->errors(); + /* $errors contains + [ + 'email' => ['A valid email address is required'] + ] + */ + +Invalidating Individual Form Fields from Controller +=================================================== + +It is possible to invalidate individual fields from the controller without the +use of the Validator class. The most common use case for this is when the +validation is done on a remote server. In such case, you must manually +invalidate the fields accordingly to the feedback from the remote server:: + + // in src/Form/ContactForm.php + public function setErrors($errors) + { + $this->_errors = $errors; + } + +According to how the validator class would have returned the errors, ``$errors`` +must be in this format:: + + ["fieldName" => ["validatorName" => "The error message to display"]] + +Now you will be able to invalidate form fields by setting the fieldName, then +set the error messages:: + + // In a controller + $contact = new ContactForm(); + $contact->setErrors(["email" => ["_required" => "Your email is required"]]); + +Proceed to Creating HTML with FormHelper to see the results. + +Creating HTML with FormHelper +============================= + +Once you've created a Form class, you'll likely want to create an HTML form for +it. FormHelper understands Form objects just like ORM entities:: + + echo $this->Form->create($contact); + echo $this->Form->control('name'); + echo $this->Form->control('email'); + echo $this->Form->control('body'); + echo $this->Form->button('Submit'); + echo $this->Form->end(); + +The above would create an HTML form for the ``ContactForm`` we defined earlier. +HTML forms created with FormHelper will use the defined schema and validator to +determine field types, maxlengths, and validation errors. diff --git a/tl/core-libraries/global-constants-and-functions.rst b/tl/core-libraries/global-constants-and-functions.rst new file mode 100644 index 0000000000000000000000000000000000000000..e0c0cedac2e37eec658aca388001537cb75bd98a --- /dev/null +++ b/tl/core-libraries/global-constants-and-functions.rst @@ -0,0 +1,263 @@ +Constants & Functions +##################### + +While most of your day-to-day work in CakePHP will be utilizing core classes and +methods, CakePHP features a number of global convenience functions that may come +in handy. Many of these functions are for use with CakePHP classes (loading +model or component classes), but many others make working with arrays or +strings a little easier. + +We'll also cover some of the constants available in CakePHP applications. Using +these constants will help make upgrades more smooth, but are also convenient +ways to point to certain files or directories in your CakePHP application. + +Global Functions +================ + +Here are CakePHP's globally available functions. Most of them are just +convenience wrappers for other CakePHP functionality, such as debugging and +translating content. + +.. php:function:: \_\_(string $string_id, [$formatArgs]) + + This function handles localization in CakePHP applications. The + ``$string_id`` identifies the ID for a translation. You can supply + additional arguments to replace placeholders in your string:: + + __('You have {0} unread messages', $number); + + You can also provide a name-indexed array of replacements:: + + __('You have {unread} unread messages', ['unread' => $number]); + + .. note:: + + Check out the + :doc:`/core-libraries/internationalization-and-localization` section for + more information. + +.. php:function:: __d(string $domain, string $msg, mixed $args = null) + + Allows you to override the current domain for a single message lookup. + + Useful when internationalizing a plugin: + ``echo __d('PluginName', 'This is my plugin');`` + +.. php:function:: __dn(string $domain, string $singular, string $plural, integer $count, mixed $args = null) + + Allows you to override the current domain for a single plural message + lookup. Returns correct plural form of message identified by ``$singular`` + and ``$plural`` for count ``$count`` from domain ``$domain``. + +.. php:function:: __dx(string $domain, string $context, string $msg, mixed $args = null) + + Allows you to override the current domain for a single message lookup. It + also allows you to specify a context. + + The context is a unique identifier for the translations string that makes it + unique within the same domain. + +.. php:function:: __dxn(string $domain, string $context, string $singular, string $plural, integer $count, mixed $args = null) + + Allows you to override the current domain for a single plural message + lookup. It also allows you to specify a context. Returns correct plural + form of message identified by ``$singular`` and ``$plural`` for count + ``$count`` from domain ``$domain``. Some languages have more than one form + for plural messages dependent on the count. + + The context is a unique identifier for the translations string that makes it + unique within the same domain. + +.. php:function:: __n(string $singular, string $plural, integer $count, mixed $args = null) + + Returns correct plural form of message identified by ``$singular`` and + ``$plural`` for count ``$count``. Some languages have more than one form for + plural messages dependent on the count. + +.. php:function:: __x(string $context, string $msg, mixed $args = null) + + The context is a unique identifier for the translations string that makes it + unique within the same domain. + +.. php:function:: __xn(string $context, string $singular, string $plural, integer $count, mixed $args = null) + + Returns correct plural form of message identified by ``$singular`` and + ``$plural`` for count ``$count`` from domain ``$domain``. It also allows you + to specify a context. Some languages have more than one form for plural + messages dependent on the count. + + The context is a unique identifier for the translations string that makes it + unique within the same domain. + +.. php:function:: collection(mixed $items) + + Convenience wrapper for instantiating a new :php:class:`Cake\\Collection\\Collection` + object, wrapping the passed argument. The ``$items`` parameter takes either + a ``Traversable`` object or an array. + +.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) + + .. versionchanged:: 3.3.0 + Calling this method will return passed ``$var``, so that you can, for instance, + place it in return statements. + + If the core ``$debug`` variable is ``true``, ``$var`` is printed out. + If ``$showHTML`` is ``true`` or left as ``null``, the data is rendered to be + browser-friendly. If ``$showFrom`` is not set to ``false``, the debug output + will start with the line from which it was called. Also see + :doc:`/development/debugging` + +.. php:function:: dd(mixed $var, boolean $showHtml = null) + + It behaves like ``debug()``, but execution is also halted. + If the core ``$debug`` variable is ``true``, ``$var`` is printed. + If ``$showHTML`` is ``true`` or left as ``null``, the data is rendered to be + browser-friendly. Also see :doc:`/development/debugging` + +.. php:function:: pr(mixed $var) + + .. versionchanged:: 3.3.0 + Calling this method will return passed ``$var``, so that you can, for instance, + place it in return statements. + + Convenience wrapper for ``print_r()``, with the addition of + wrapping ``
    `` tags around the output.
    +
    +.. php:function:: pj(mixed $var)
    +
    +    .. versionchanged:: 3.3.0
    +        Calling this method will return passed ``$var``, so that you can, for instance,
    +        place it in return statements.
    +
    +    JSON pretty print convenience function, with the addition of
    +    wrapping ``
    `` tags around the output.
    +
    +    It is meant for debugging the JSON representation of objects and arrays.
    +
    +.. php:function:: env(string $key, string $default = null)
    +
    +    .. versionchanged:: 3.1.1
    +        The ``$default`` parameter has been added.
    +
    +    Gets an environment variable from available sources. Used as a backup if
    +    ``$_SERVER`` or ``$_ENV`` are disabled.
    +
    +    This function also emulates ``PHP_SELF`` and ``DOCUMENT_ROOT`` on
    +    unsupporting servers. In fact, it's a good idea to always use ``env()``
    +    instead of ``$_SERVER`` or ``getenv()`` (especially if you plan to
    +    distribute the code), since it's a full emulation wrapper.
    +
    +.. php:function:: h(string $text, boolean $double = true, string $charset = null)
    +
    +    Convenience wrapper for ``htmlspecialchars()``.
    +
    +.. php:function:: pluginSplit(string $name, boolean $dotAppend = false, string $plugin = null)
    +
    +    Splits a dot syntax plugin name into its plugin and class name. If ``$name``
    +    does not have a dot, then index 0 will be ``null``.
    +
    +    Commonly used like ``list($plugin, $name) = pluginSplit('Users.User');``
    +
    +.. php:function:: namespaceSplit(string $class)
    +
    +    Split the namespace from the classname.
    +
    +    Commonly used like ``list($namespace, $className) = namespaceSplit('Cake\Core\App');``
    +
    +Core Definition Constants
    +=========================
    +
    +Most of the following constants refer to paths in your application.
    +
    +.. php:const:: APP
    +
    +   Absolute path to your application directory, including a trailing slash.
    +
    +.. php:const:: APP_DIR
    +
    +    Equals ``app`` or the name of your application directory.
    +
    +.. php:const:: CACHE
    +
    +    Path to the cache files directory. It can be shared between hosts in a
    +    multi-server setup.
    +
    +.. php:const:: CAKE
    +
    +    Path to the cake directory.
    +
    +.. php:const:: CAKE_CORE_INCLUDE_PATH
    +
    +    Path to the root lib directory.
    +
    +.. php:const:: CONFIG
    +
    +   Path to the config directory.
    +
    +.. php:const:: CORE_PATH
    +
    +   Path to the root directory with ending directory slash.
    +
    +.. php:const:: DS
    +
    +    Short for PHP's ``DIRECTORY_SEPARATOR``, which is ``/`` on Linux and ``\``
    +    on Windows.
    +
    +.. php:const:: LOGS
    +
    +    Path to the logs directory.
    +
    +.. php:const:: ROOT
    +
    +    Path to the root directory.
    +
    +.. php:const:: TESTS
    +
    +    Path to the tests directory.
    +
    +.. php:const:: TMP
    +
    +    Path to the temporary files directory.
    +
    +.. php:const:: WWW\_ROOT
    +
    +    Full path to the webroot.
    +
    +Timing Definition Constants
    +===========================
    +
    +.. php:const:: TIME_START
    +
    +    Unix timestamp in microseconds as a float from when the application started.
    +
    +.. php:const:: SECOND
    +
    +    Equals 1
    +
    +.. php:const:: MINUTE
    +
    +    Equals 60
    +
    +.. php:const:: HOUR
    +
    +    Equals 3600
    +
    +.. php:const:: DAY
    +
    +    Equals 86400
    +
    +.. php:const:: WEEK
    +
    +    Equals 604800
    +
    +.. php:const:: MONTH
    +
    +    Equals 2592000
    +
    +.. php:const:: YEAR
    +
    +    Equals 31536000
    +
    +.. meta::
    +    :title lang=en: Global Constants and Functions
    +    :keywords lang=en: internationalization and localization,global constants,example config,array php,convenience functions,core libraries,component classes,optional number,global functions,string string,core classes,format strings,unread messages,placeholders,useful functions,arrays,parameters,existence,translations
    diff --git a/tl/core-libraries/hash.rst b/tl/core-libraries/hash.rst
    new file mode 100644
    index 0000000000000000000000000000000000000000..1f371922d1b6f14e89f1c5ce8161a9536ed0ae64
    --- /dev/null
    +++ b/tl/core-libraries/hash.rst
    @@ -0,0 +1,875 @@
    +Hash
    +####
    +
    +.. php:namespace:: Cake\Utility
    +
    +.. php:class:: Hash
    +
    +Array management, if done right, can be a very powerful and useful
    +tool for building smarter, more optimized code. CakePHP offers a
    +very useful set of static utilities in the Hash class that allow you
    +to do just that.
    +
    +CakePHP's Hash class can be called from any model or controller in
    +the same way Inflector is called. Example: :php:meth:`Hash::combine()`.
    +
    +.. _hash-path-syntax:
    +
    +Hash Path Syntax
    +================
    +
    +The path syntax described below is used by all the methods in ``Hash``. Not all
    +parts of the path syntax are available in all methods. A path expression is
    +made of any number of tokens. Tokens are composed of two groups. Expressions,
    +are used to traverse the array data, while matchers are used to qualify
    +elements. You apply matchers to expression elements.
    +
    +Expression Types
    +----------------
    +
    ++--------------------------------+--------------------------------------------+
    +| Expression                     | Definition                                 |
    ++================================+============================================+
    +| ``{n}``                        | Represents a numeric key. Will match       |
    +|                                | any string or numeric key.                 |
    ++--------------------------------+--------------------------------------------+
    +| ``{s}``                        | Represents a string. Will match any        |
    +|                                | string value including numeric string      |
    +|                                | values.                                    |
    ++--------------------------------+--------------------------------------------+
    +| ``{*}``                        | Matches any value.                         |
    ++--------------------------------+--------------------------------------------+
    +| ``Foo``                        | Matches keys with the exact same value.    |
    ++--------------------------------+--------------------------------------------+
    +
    +All expression elements are supported by all methods. In addition to expression
    +elements, you can use attribute matching with certain methods. They are ``extract()``,
    +``combine()``, ``format()``, ``check()``, ``map()``, ``reduce()``,
    +``apply()``, ``sort()``, ``insert()``, ``remove()`` and ``nest()``.
    +
    +Attribute Matching Types
    +------------------------
    +
    ++--------------------------------+--------------------------------------------+
    +| Matcher                        | Definition                                 |
    ++================================+============================================+
    +| ``[id]``                       | Match elements with a given array key.     |
    ++--------------------------------+--------------------------------------------+
    +| ``[id=2]``                     | Match elements with id equal to 2.         |
    ++--------------------------------+--------------------------------------------+
    +| ``[id!=2]``                    | Match elements with id not equal to 2.     |
    ++--------------------------------+--------------------------------------------+
    +| ``[id>2]``                     | Match elements with id greater than 2.     |
    ++--------------------------------+--------------------------------------------+
    +| ``[id>=2]``                    | Match elements with id greater than        |
    +|                                | or equal to 2.                             |
    ++--------------------------------+--------------------------------------------+
    +| ``[id<2]``                     | Match elements with id less than 2         |
    ++--------------------------------+--------------------------------------------+
    +| ``[id<=2]``                    | Match elements with id less than           |
    +|                                | or equal to 2.                             |
    ++--------------------------------+--------------------------------------------+
    +| ``[text=/.../]``               | Match elements that have values matching   |
    +|                                | the regular expression inside ``...``.     |
    ++--------------------------------+--------------------------------------------+
    +
    +.. php:staticmethod:: get(array|\ArrayAccess $data, $path, $default = null)
    +
    +    ``get()`` is a simplified version of ``extract()``, it only supports direct
    +    path expressions. Paths with ``{n}``, ``{s}``, ``{*}`` or matchers are not
    +    supported. Use ``get()`` when you want exactly one value out of an array. If
    +    a matching path is not found the default value will be returned.
    +
    +.. php:staticmethod:: extract(array|\ArrayAccess $data, $path)
    +
    +    ``Hash::extract()`` supports all expression, and matcher components of
    +    :ref:`hash-path-syntax`. You can use extract to retrieve data from arrays
    +    or object implementing ``ArrayAccess`` interface, along arbitrary paths
    +    quickly without having to loop through the data structures. Instead you
    +    use path expressions to qualify which elements you want returned ::
    +
    +        // Common Usage:
    +        $users = [
    +            ['id' => 1, 'name' => 'mark'],
    +            ['id' => 2, 'name' => 'jane'],
    +            ['id' => 3, 'name' => 'sally'],
    +            ['id' => 4, 'name' => 'jose'],
    +        ];
    +        $results = Hash::extract($users, '{n}.id');
    +        // $results equals:
    +        // [1,2,3,4];
    +
    +.. php:staticmethod:: Hash::insert(array $data, $path, $values = null)
    +
    +    Inserts ``$values`` into an array as defined by ``$path``::
    +
    +        $a = [
    +            'pages' => ['name' => 'page']
    +        ];
    +        $result = Hash::insert($a, 'files', ['name' => 'files']);
    +        // $result now looks like:
    +        [
    +            [pages] => [
    +                [name] => page
    +            ]
    +            [files] => [
    +                [name] => files
    +            ]
    +        ]
    +
    +    You can use paths using ``{n}``, ``{s}`` and ``{*}`` to insert data into multiple
    +    points::
    +
    +        $users = Hash::insert($users, '{n}.new', 'value');
    +
    +    Attribute matchers work with ``insert()`` as well::
    +
    +        $data = [
    +            0 => ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +            1 => ['Item' => ['id' => 2, 'title' => 'second']],
    +            2 => ['Item' => ['id' => 3, 'title' => 'third']],
    +            3 => ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
    +            4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
    +        ];
    +        $result = Hash::insert($data, '{n}[up].Item[id=4].new', 9);
    +        /* $result now looks like:
    +            [
    +                ['up' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +                ['Item' => ['id' => 2, 'title' => 'second']],
    +                ['Item' => ['id' => 3, 'title' => 'third']],
    +                ['up' => true, 'Item' => ['id' => 4, 'title' => 'fourth', 'new' => 9]],
    +                ['Item' => ['id' => 5, 'title' => 'fifth']],
    +            ]
    +        */
    +
    +.. php:staticmethod:: remove(array $data, $path)
    +
    +    Removes all elements from an array that match ``$path``. ::
    +
    +        $a = [
    +            'pages' => ['name' => 'page'],
    +            'files' => ['name' => 'files']
    +        ];
    +        $result = Hash::remove($a, 'files');
    +        /* $result now looks like:
    +            [
    +                [pages] => [
    +                    [name] => page
    +                ]
    +
    +            ]
    +        */
    +
    +    Using ``{n}``, ``{s}`` and ``{*}`` will allow you to remove multiple values at once.
    +    You can also use attribute matchers with ``remove()``::
    +
    +        $data = [
    +            0 => ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +            1 => ['Item' => ['id' => 2, 'title' => 'second']],
    +            2 => ['Item' => ['id' => 3, 'title' => 'third']],
    +            3 => ['clear' => true, 'Item' => ['id' => 4, 'title' => 'fourth']],
    +            4 => ['Item' => ['id' => 5, 'title' => 'fifth']],
    +        ];
    +        $result = Hash::remove($data, '{n}[clear].Item[id=4]');
    +        /* $result now looks like:
    +            [
    +                ['clear' => true, 'Item' => ['id' => 1, 'title' => 'first']],
    +                ['Item' => ['id' => 2, 'title' => 'second']],
    +                ['Item' => ['id' => 3, 'title' => 'third']],
    +                ['clear' => true],
    +                ['Item' => ['id' => 5, 'title' => 'fifth']],
    +            ]
    +        */
    +
    +.. php:staticmethod:: combine(array $data, $keyPath, $valuePath = null, $groupPath = null)
    +
    +    Creates an associative array using a ``$keyPath`` as the path to build its keys,
    +    and optionally ``$valuePath`` as path to get the values. If ``$valuePath`` is not
    +    specified, or doesn't match anything, values will be initialized to null.
    +    You can optionally group the values by what is obtained when following the
    +    path specified in ``$groupPath``. ::
    +
    +        $a = [
    +            [
    +                'User' => [
    +                    'id' => 2,
    +                    'group_id' => 1,
    +                    'Data' => [
    +                        'user' => 'mariano.iglesias',
    +                        'name' => 'Mariano Iglesias'
    +                    ]
    +                ]
    +            ],
    +            [
    +                'User' => [
    +                    'id' => 14,
    +                    'group_id' => 2,
    +                    'Data' => [
    +                        'user' => 'phpnut',
    +                        'name' => 'Larry E. Masters'
    +                    ]
    +                ]
    +            ],
    +        ];
    +
    +        $result = Hash::combine($a, '{n}.User.id');
    +        /* $result now looks like:
    +            [
    +                [2] =>
    +                [14] =>
    +            ]
    +        */
    +
    +        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.user');
    +        /* $result now looks like:
    +            [
    +                [2] => 'mariano.iglesias'
    +                [14] => 'phpnut'
    +            ]
    +        */
    +
    +        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data');
    +        /* $result now looks like:
    +            [
    +                [2] => [
    +                        [user] => mariano.iglesias
    +                        [name] => Mariano Iglesias
    +                ]
    +                [14] => [
    +                        [user] => phpnut
    +                        [name] => Larry E. Masters
    +                ]
    +            ]
    +        */
    +
    +        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name');
    +        /* $result now looks like:
    +            [
    +                [2] => Mariano Iglesias
    +                [14] => Larry E. Masters
    +            ]
    +        */
    +
    +        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data', '{n}.User.group_id');
    +        /* $result now looks like:
    +            [
    +                [1] => [
    +                        [2] => [
    +                                [user] => mariano.iglesias
    +                                [name] => Mariano Iglesias
    +                        ]
    +                ]
    +                [2] => [
    +                        [14] => [
    +                                [user] => phpnut
    +                                [name] => Larry E. Masters
    +                        ]
    +                ]
    +            ]
    +        */
    +
    +        $result = Hash::combine($a, '{n}.User.id', '{n}.User.Data.name', '{n}.User.group_id');
    +        /* $result now looks like:
    +            [
    +                [1] => [
    +                        [2] => Mariano Iglesias
    +                ]
    +                [2] => [
    +                        [14] => Larry E. Masters
    +                ]
    +            ]
    +        */
    +
    +    You can provide arrays for both ``$keyPath`` and ``$valuePath``. If you do this,
    +    the first value will be used as a format string, for values extracted by the
    +    other paths::
    +
    +        $result = Hash::combine(
    +            $a,
    +            '{n}.User.id',
    +            ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
    +            '{n}.User.group_id'
    +        );
    +        /* $result now looks like:
    +            [
    +                [1] => [
    +                        [2] => mariano.iglesias: Mariano Iglesias
    +                ]
    +                [2] => [
    +                        [14] => phpnut: Larry E. Masters
    +                ]
    +            ]
    +        */
    +
    +        $result = Hash::combine(
    +            $a,
    +            ['%s: %s', '{n}.User.Data.user', '{n}.User.Data.name'],
    +            '{n}.User.id'
    +        );
    +        /* $result now looks like:
    +            [
    +                [mariano.iglesias: Mariano Iglesias] => 2
    +                [phpnut: Larry E. Masters] => 14
    +            ]
    +        */
    +
    +.. php:staticmethod:: format(array $data, array $paths, $format)
    +
    +    Returns a series of values extracted from an array, formatted with a
    +    format string::
    +
    +        $data = [
    +            [
    +                'Person' => [
    +                    'first_name' => 'Nate',
    +                    'last_name' => 'Abele',
    +                    'city' => 'Boston',
    +                    'state' => 'MA',
    +                    'something' => '42'
    +                ]
    +            ],
    +            [
    +                'Person' => [
    +                    'first_name' => 'Larry',
    +                    'last_name' => 'Masters',
    +                    'city' => 'Boondock',
    +                    'state' => 'TN',
    +                    'something' => '{0}'
    +                ]
    +            ],
    +            [
    +                'Person' => [
    +                    'first_name' => 'Garrett',
    +                    'last_name' => 'Woodworth',
    +                    'city' => 'Venice Beach',
    +                    'state' => 'CA',
    +                    'something' => '{1}'
    +                ]
    +            ]
    +        ];
    +
    +        $res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%2$d, %1$s');
    +        /*
    +        [
    +            [0] => 42, Nate
    +            [1] => 0, Larry
    +            [2] => 0, Garrett
    +        ]
    +        */
    +
    +        $res = Hash::format($data, ['{n}.Person.first_name', '{n}.Person.something'], '%1$s, %2$d');
    +        /*
    +        [
    +            [0] => Nate, 42
    +            [1] => Larry, 0
    +            [2] => Garrett, 0
    +        ]
    +        */
    +
    +.. php:staticmethod:: contains(array $data, array $needle)
    +
    +    Determines if one Hash or array contains the exact keys and values
    +    of another::
    +
    +        $a = [
    +            0 => ['name' => 'main'],
    +            1 => ['name' => 'about']
    +        ];
    +        $b = [
    +            0 => ['name' => 'main'],
    +            1 => ['name' => 'about'],
    +            2 => ['name' => 'contact'],
    +            'a' => 'b'
    +        ];
    +
    +        $result = Hash::contains($a, $a);
    +        // true
    +        $result = Hash::contains($a, $b);
    +        // false
    +        $result = Hash::contains($b, $a);
    +        // true
    +
    +.. php:staticmethod:: check(array $data, string $path = null)
    +
    +    Checks if a particular path is set in an array::
    +
    +        $set = [
    +            'My Index 1' => ['First' => 'The first item']
    +        ];
    +        $result = Hash::check($set, 'My Index 1.First');
    +        // $result == true
    +
    +        $result = Hash::check($set, 'My Index 1');
    +        // $result == true
    +
    +        $set = [
    +            'My Index 1' => [
    +                'First' => [
    +                    'Second' => [
    +                        'Third' => [
    +                            'Fourth' => 'Heavy. Nesting.'
    +                        ]
    +                    ]
    +                ]
    +            ]
    +        ];
    +        $result = Hash::check($set, 'My Index 1.First.Second');
    +        // $result == true
    +
    +        $result = Hash::check($set, 'My Index 1.First.Second.Third');
    +        // $result == true
    +
    +        $result = Hash::check($set, 'My Index 1.First.Second.Third.Fourth');
    +        // $result == true
    +
    +        $result = Hash::check($set, 'My Index 1.First.Seconds.Third.Fourth');
    +        // $result == false
    +
    +.. php:staticmethod:: filter(array $data, $callback = ['Hash', 'filter'])
    +
    +    Filters empty elements out of array, excluding '0'. You can also supply a
    +    custom ``$callback`` to filter the array elements. You callback should
    +    return ``false`` to remove elements from the resulting array::
    +
    +        $data = [
    +            '0',
    +            false,
    +            true,
    +            0,
    +            ['one thing', 'I can tell you', 'is you got to be', false]
    +        ];
    +        $res = Hash::filter($data);
    +
    +        /* $res now looks like:
    +            [
    +                [0] => 0
    +                [2] => true
    +                [3] => 0
    +                [4] => [
    +                        [0] => one thing
    +                        [1] => I can tell you
    +                        [2] => is you got to be
    +                ]
    +            ]
    +        */
    +
    +.. php:staticmethod:: flatten(array $data, string $separator = '.')
    +
    +    Collapses a multi-dimensional array into a single dimension::
    +
    +        $arr = [
    +            [
    +                'Post' => ['id' => '1', 'title' => 'First Post'],
    +                'Author' => ['id' => '1', 'user' => 'Kyle'],
    +            ],
    +            [
    +                'Post' => ['id' => '2', 'title' => 'Second Post'],
    +                'Author' => ['id' => '3', 'user' => 'Crystal'],
    +            ],
    +        ];
    +        $res = Hash::flatten($arr);
    +        /* $res now looks like:
    +            [
    +                [0.Post.id] => 1
    +                [0.Post.title] => First Post
    +                [0.Author.id] => 1
    +                [0.Author.user] => Kyle
    +                [1.Post.id] => 2
    +                [1.Post.title] => Second Post
    +                [1.Author.id] => 3
    +                [1.Author.user] => Crystal
    +            ]
    +        */
    +
    +.. php:staticmethod:: expand(array $data, string $separator = '.')
    +
    +    Expands an array that was previously flattened with
    +    :php:meth:`Hash::flatten()`::
    +
    +        $data = [
    +            '0.Post.id' => 1,
    +            '0.Post.title' => First Post,
    +            '0.Author.id' => 1,
    +            '0.Author.user' => Kyle,
    +            '1.Post.id' => 2,
    +            '1.Post.title' => Second Post,
    +            '1.Author.id' => 3,
    +            '1.Author.user' => Crystal,
    +        ];
    +        $res = Hash::expand($data);
    +        /* $res now looks like:
    +        [
    +            [
    +                'Post' => ['id' => '1', 'title' => 'First Post'],
    +                'Author' => ['id' => '1', 'user' => 'Kyle'],
    +            ],
    +            [
    +                'Post' => ['id' => '2', 'title' => 'Second Post'],
    +                'Author' => ['id' => '3', 'user' => 'Crystal'],
    +            ],
    +        ];
    +        */
    +
    +.. php:staticmethod:: merge(array $data, array $merge[, array $n])
    +
    +    This function can be thought of as a hybrid between PHP's
    +    ``array_merge`` and ``array_merge_recursive``. The difference to the two
    +    is that if an array key contains another array then the function
    +    behaves recursive (unlike ``array_merge``) but does not do if for keys
    +    containing strings (unlike ``array_merge_recursive``).
    +
    +    .. note::
    +
    +        This function will work with an unlimited amount of arguments and
    +        typecasts non-array parameters into arrays.
    +
    +    ::
    +
    +        $array = [
    +            [
    +                'id' => '48c2570e-dfa8-4c32-a35e-0d71cbdd56cb',
    +                'name' => 'mysql raleigh-workshop-08 < 2008-09-05.sql ',
    +                'description' => 'Importing an sql dump'
    +            ],
    +            [
    +                'id' => '48c257a8-cf7c-4af2-ac2f-114ecbdd56cb',
    +                'name' => 'pbpaste | grep -i Unpaid | pbcopy',
    +                'description' => 'Remove all lines that say "Unpaid".',
    +            ]
    +        ];
    +        $arrayB = 4;
    +        $arrayC = [0 => "test array", "cats" => "dogs", "people" => 1267];
    +        $arrayD = ["cats" => "felines", "dog" => "angry"];
    +        $res = Hash::merge($array, $arrayB, $arrayC, $arrayD);
    +
    +        /* $res now looks like:
    +        [
    +            [0] => [
    +                    [id] => 48c2570e-dfa8-4c32-a35e-0d71cbdd56cb
    +                    [name] => mysql raleigh-workshop-08 < 2008-09-05.sql
    +                    [description] => Importing an sql dump
    +            ]
    +            [1] => [
    +                    [id] => 48c257a8-cf7c-4af2-ac2f-114ecbdd56cb
    +                    [name] => pbpaste | grep -i Unpaid | pbcopy
    +                    [description] => Remove all lines that say "Unpaid".
    +            ]
    +            [2] => 4
    +            [3] => test array
    +            [cats] => felines
    +            [people] => 1267
    +            [dog] => angry
    +        ]
    +        */
    +
    +.. php:staticmethod:: numeric(array $data)
    +
    +    Checks to see if all the values in the array are numeric::
    +
    +        $data = ['one'];
    +        $res = Hash::numeric(array_keys($data));
    +        // $res is true
    +
    +        $data = [1 => 'one'];
    +        $res = Hash::numeric($data);
    +        // $res is false
    +
    +.. php:staticmethod:: dimensions (array $data)
    +
    +    Counts the dimensions of an array. This method will only
    +    consider the dimension of the first element in the array::
    +
    +        $data = ['one', '2', 'three'];
    +        $result = Hash::dimensions($data);
    +        // $result == 1
    +
    +        $data = ['1' => '1.1', '2', '3'];
    +        $result = Hash::dimensions($data);
    +        // $result == 1
    +
    +        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => '3.1.1']];
    +        $result = Hash::dimensions($data);
    +        // $result == 2
    +
    +        $data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
    +        $result = Hash::dimensions($data);
    +        // $result == 1
    +
    +        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
    +        $result = Hash::dimensions($data);
    +        // $result == 2
    +
    +.. php:staticmethod:: maxDimensions(array $data)
    +
    +    Similar to :php:meth:`~Hash::dimensions()`, however this method returns,
    +    the deepest number of dimensions of any element in the array::
    +
    +        $data = ['1' => '1.1', '2', '3' => ['3.1' => '3.1.1']];
    +        $result = Hash::maxDimensions($data);
    +        // $result == 2
    +
    +        $data = ['1' => ['1.1' => '1.1.1'], '2', '3' => ['3.1' => ['3.1.1' => '3.1.1.1']]];
    +        $result = Hash::maxDimensions($data);
    +        // $result == 3
    +
    +.. php:staticmethod:: map(array $data, $path, $function)
    +
    +    Creates a new array, by extracting ``$path``, and mapping ``$function``
    +    across the results. You can use both expression and matching elements with
    +    this method::
    +
    +        // Call the noop function $this->noop() on every element of $data
    +        $result = Hash::map($data, "{n}", [$this, 'noop']);
    +
    +        public function noop(array $array)
    +        {
    +            // Do stuff to array and return the result
    +            return $array;
    +        }
    +
    +.. php:staticmethod:: reduce(array $data, $path, $function)
    +
    +    Creates a single value, by extracting ``$path``, and reducing the extracted
    +    results with ``$function``. You can use both expression and matching elements
    +    with this method.
    +
    +.. php:staticmethod:: apply(array $data, $path, $function)
    +
    +    Apply a callback to a set of extracted values using ``$function``. The function
    +    will get the extracted values as the first argument::
    +
    +        $data = [
    +            ['date' => '01-01-2016', 'booked' => true],
    +            ['date' => '01-01-2016', 'booked' => false],
    +            ['date' => '02-01-2016', 'booked' => true]
    +        ];
    +        $result = Hash::apply($data, '{n}[booked=true].date', 'array_count_values');
    +        /* $result now looks like:
    +            [
    +                '01-01-2016' => 1,
    +                '02-01-2016' => 1,
    +            ]
    +        */
    +
    +.. php:staticmethod:: sort(array $data, $path, $dir, $type = 'regular')
    +
    +    Sorts an array by any value, determined by a :ref:`hash-path-syntax`
    +    Only expression elements are supported by this method::
    +
    +        $a = [
    +            0 => ['Person' => ['name' => 'Jeff']],
    +            1 => ['Shirt' => ['color' => 'black']]
    +        ];
    +        $result = Hash::sort($a, '{n}.Person.name', 'asc');
    +        /* $result now looks like:
    +            [
    +                [0] => [
    +                        [Shirt] => [
    +                                [color] => black
    +                        ]
    +                ]
    +                [1] => [
    +                        [Person] => [
    +                                [name] => Jeff
    +                        ]
    +                ]
    +            ]
    +        */
    +
    +    ``$dir`` can be either ``asc`` or ``desc``. ``$type``
    +    can be one of the following values:
    +
    +    * ``regular`` for regular sorting.
    +    * ``numeric`` for sorting values as their numeric equivalents.
    +    * ``string`` for sorting values as their string value.
    +    * ``natural`` for sorting values in a human friendly way. Will
    +      sort ``foo10`` below ``foo2`` as an example.
    +
    +.. php:staticmethod:: diff(array $data, array $compare)
    +
    +    Computes the difference between two arrays::
    +
    +        $a = [
    +            0 => ['name' => 'main'],
    +            1 => ['name' => 'about']
    +        ];
    +        $b = [
    +            0 => ['name' => 'main'],
    +            1 => ['name' => 'about'],
    +            2 => ['name' => 'contact']
    +        ];
    +
    +        $result = Hash::diff($a, $b);
    +        /* $result now looks like:
    +            [
    +                [2] => [
    +                        [name] => contact
    +                ]
    +            ]
    +        */
    +
    +.. php:staticmethod:: mergeDiff(array $data, array $compare)
    +
    +    This function merges two arrays and pushes the differences in
    +    data to the bottom of the resultant array.
    +
    +    **Example 1**
    +    ::
    +
    +        $array1 = ['ModelOne' => ['id' => 1001, 'field_one' => 'a1.m1.f1', 'field_two' => 'a1.m1.f2']];
    +        $array2 = ['ModelOne' => ['id' => 1003, 'field_one' => 'a3.m1.f1', 'field_two' => 'a3.m1.f2', 'field_three' => 'a3.m1.f3']];
    +        $res = Hash::mergeDiff($array1, $array2);
    +
    +        /* $res now looks like:
    +            [
    +                [ModelOne] => [
    +                        [id] => 1001
    +                        [field_one] => a1.m1.f1
    +                        [field_two] => a1.m1.f2
    +                        [field_three] => a3.m1.f3
    +                    ]
    +            ]
    +        */
    +
    +    **Example 2**
    +    ::
    +
    +        $array1 = ["a" => "b", 1 => 20938, "c" => "string"];
    +        $array2 = ["b" => "b", 3 => 238, "c" => "string", ["extra_field"]];
    +        $res = Hash::mergeDiff($array1, $array2);
    +        /* $res now looks like:
    +            [
    +                [a] => b
    +                [1] => 20938
    +                [c] => string
    +                [b] => b
    +                [3] => 238
    +                [4] => [
    +                        [0] => extra_field
    +                ]
    +            ]
    +        */
    +
    +.. php:staticmethod:: normalize(array $data, $assoc = true)
    +
    +    Normalizes an array. If ``$assoc`` is ``true``, the resulting array will be
    +    normalized to be an associative array. Numeric keys with values, will be
    +    converted to string keys with null values. Normalizing an array, makes using
    +    the results with :php:meth:`Hash::merge()` easier::
    +
    +        $a = ['Tree', 'CounterCache',
    +            'Upload' => [
    +                'folder' => 'products',
    +                'fields' => ['image_1_id', 'image_2_id']
    +            ]
    +        ];
    +        $result = Hash::normalize($a);
    +        /* $result now looks like:
    +            [
    +                [Tree] => null
    +                [CounterCache] => null
    +                [Upload] => [
    +                        [folder] => products
    +                        [fields] => [
    +                                [0] => image_1_id
    +                                [1] => image_2_id
    +                        ]
    +                ]
    +            ]
    +        */
    +
    +        $b = [
    +            'Cacheable' => ['enabled' => false],
    +            'Limit',
    +            'Bindable',
    +            'Validator',
    +            'Transactional'
    +        ];
    +        $result = Hash::normalize($b);
    +        /* $result now looks like:
    +            [
    +                [Cacheable] => [
    +                        [enabled] => false
    +                ]
    +
    +                [Limit] => null
    +                [Bindable] => null
    +                [Validator] => null
    +                [Transactional] => null
    +            ]
    +        */
    +
    +.. php:staticmethod:: nest(array $data, array $options = [])
    +
    +    Takes a flat array set, and creates a nested, or threaded data structure.
    +
    +    **Options:**
    +
    +    - ``children`` The key name to use in the result set for children. Defaults
    +      to 'children'.
    +    - ``idPath`` The path to a key that identifies each entry. Should be
    +      compatible with :php:meth:`Hash::extract()`. Defaults to ``{n}.$alias.id``
    +    - ``parentPath`` The path to a key that identifies the parent of each entry.
    +      Should be compatible with :php:meth:`Hash::extract()`. Defaults to ``{n}.$alias.parent_id``
    +    - ``root`` The id of the desired top-most result.
    +
    +    For example, if you had the following array of data::
    +
    +        $data = [
    +            ['ThreadPost' => ['id' => 1, 'parent_id' => null]],
    +            ['ThreadPost' => ['id' => 2, 'parent_id' => 1]],
    +            ['ThreadPost' => ['id' => 3, 'parent_id' => 1]],
    +            ['ThreadPost' => ['id' => 4, 'parent_id' => 1]],
    +            ['ThreadPost' => ['id' => 5, 'parent_id' => 1]],
    +            ['ThreadPost' => ['id' => 6, 'parent_id' => null]],
    +            ['ThreadPost' => ['id' => 7, 'parent_id' => 6]],
    +            ['ThreadPost' => ['id' => 8, 'parent_id' => 6]],
    +            ['ThreadPost' => ['id' => 9, 'parent_id' => 6]],
    +            ['ThreadPost' => ['id' => 10, 'parent_id' => 6]]
    +        ];
    +
    +        $result = Hash::nest($data, ['root' => 6]);
    +        /* $result now looks like:
    +            [
    +                (int) 0 => [
    +                    'ThreadPost' => [
    +                        'id' => (int) 6,
    +                        'parent_id' => null
    +                    ],
    +                    'children' => [
    +                        (int) 0 => [
    +                            'ThreadPost' => [
    +                                'id' => (int) 7,
    +                                'parent_id' => (int) 6
    +                            ],
    +                            'children' => []
    +                        ],
    +                        (int) 1 => [
    +                            'ThreadPost' => [
    +                                'id' => (int) 8,
    +                                'parent_id' => (int) 6
    +                            ],
    +                            'children' => []
    +                        ],
    +                        (int) 2 => [
    +                            'ThreadPost' => [
    +                                'id' => (int) 9,
    +                                'parent_id' => (int) 6
    +                            ],
    +                            'children' => []
    +                        ],
    +                        (int) 3 => [
    +                            'ThreadPost' => [
    +                                'id' => (int) 10,
    +                                'parent_id' => (int) 6
    +                            ],
    +                            'children' => []
    +                        ]
    +                    ]
    +                ]
    +            ]
    +            */
    +
    +.. meta::
    +    :title lang=en: Hash
    +    :keywords lang=en: array array,path array,array name,numeric key,regular expression,result set,person name,brackets,syntax,cakephp,elements,php,set path
    diff --git a/tl/core-libraries/httpclient.rst b/tl/core-libraries/httpclient.rst
    new file mode 100644
    index 0000000000000000000000000000000000000000..812c1a2a4cc68688e9193e5b649d7136f54dee0b
    --- /dev/null
    +++ b/tl/core-libraries/httpclient.rst
    @@ -0,0 +1,471 @@
    +Http Client
    +###########
    +
    +.. php:namespace:: Cake\Http
    +
    +.. php:class:: Client(mixed $config = [])
    +
    +CakePHP includes a basic but powerful HTTP client which can be used for
    +making requests. It is a great way to communicate with webservices, and
    +remote APIs.
    +
    +.. versionchanged:: 3.3.0
    +    Prior to 3.3.0 you should use ``Cake\Network\Http\Client``.
    +
    +Doing Requests
    +==============
    +
    +Doing requests is simple and straight forward.  Doing a GET request looks like::
    +
    +    use Cake\Http\Client;
    +
    +    $http = new Client();
    +
    +    // Simple get
    +    $response = $http->get('http://example.com/test.html');
    +
    +    // Simple get with querystring
    +    $response = $http->get('http://example.com/search', ['q' => 'widget']);
    +
    +    // Simple get with querystring & additional headers
    +    $response = $http->get('http://example.com/search', ['q' => 'widget'], [
    +      'headers' => ['X-Requested-With' => 'XMLHttpRequest']
    +    ]);
    +
    +Doing POST and PUT requests is equally simple::
    +
    +    // Send a POST request with application/x-www-form-urlencoded encoded data
    +    $http = new Client();
    +    $response = $http->post('http://example.com/posts/add', [
    +      'title' => 'testing',
    +      'body' => 'content in the post'
    +    ]);
    +
    +    // Send a PUT request with application/x-www-form-urlencoded encoded data
    +    $response = $http->put('http://example.com/posts/add', [
    +      'title' => 'testing',
    +      'body' => 'content in the post'
    +    ]);
    +
    +    // Other methods as well.
    +    $http->delete(...);
    +    $http->head(...);
    +    $http->patch(...);
    +
    +Creating Multipart Requests with Files
    +======================================
    +
    +You can include files in request bodies by including a filehandle in the array::
    +
    +    $http = new Client();
    +    $response = $http->post('http://example.com/api', [
    +      'image' => fopen('/path/to/a/file', 'r'),
    +    ]);
    +
    +The filehandle will be read until its end; it will not be rewound before being read.
    +
    +.. warning::
    +
    +    For compatibility reasons, strings beginning with ``@`` will be evaluated
    +    as local or remote file paths.
    +
    +This functionality is deprecated as of CakePHP 3.0.5
    +and will be removed in a future version. Until that happens, user data being passed
    +to the Http Client must be sanitized as follows::
    +
    +    $response = $http->post('http://example.com/api', [
    +        'search' => ltrim($this->request->getData('search'), '@'),
    +    ]);
    +
    +If it is necessary to preserve leading ``@`` characters in query strings, you can pass
    +a pre-encoded query string from ``http_build_query()``::
    +
    +    $response = $http->post('http://example.com/api', http_build_query([
    +        'search' => $this->request->getData('search'),
    +    ]));
    +
    +Building Multipart Request Bodies by Hand
    +-----------------------------------------
    +
    +There may be times when you need to build a request body in a very specific way.
    +In these situations you can often use ``Cake\Http\Client\FormData`` to craft
    +the specific multipart HTTP request you want::
    +
    +    use Cake\Http\Client\FormData;
    +
    +    $data = new FormData();
    +
    +    // Create an XML part
    +    $xml = $data->newPart('xml', $xmlString);
    +    // Set the content type.
    +    $xml->type('application/xml');
    +    $data->add($xml);
    +
    +    // Create a file upload with addFile()
    +    // This will append the file to the form data as well.
    +    $file = $data->addFile('upload', fopen('/some/file.txt', 'r'));
    +    $file->contentId('abc123');
    +    $file->disposition('attachment');
    +
    +    // Send the request.
    +    $response = $http->post(
    +        'http://example.com/api',
    +        (string)$data,
    +        ['headers' => ['Content-Type' => $data->contentType()]]
    +    );
    +
    +Sending Request Bodies
    +======================
    +
    +When dealing with REST API's you often need to send request bodies that are not
    +form encoded. Http\\Client exposes this through the type option::
    +
    +    // Send a JSON request body.
    +    $http = new Client();
    +    $response = $http->post(
    +      'http://example.com/tasks',
    +      json_encode($data),
    +      ['type' => 'json']
    +    );
    +
    +The ``type`` key can either be a one of 'json', 'xml' or a full mime type.
    +When using the ``type`` option, you should provide the data as a string. If you're
    +doing a GET request that needs both querystring parameters and a request body
    +you can do the following::
    +
    +    // Send a JSON body in a GET request with query string parameters.
    +    $http = new Client();
    +    $response = $http->get(
    +      'http://example.com/tasks',
    +      ['q' => 'test', '_content' => json_encode($data)],
    +      ['type' => 'json']
    +    );
    +
    +.. _http_client_request_options:
    +
    +Request Method Options
    +======================
    +
    +Each HTTP method takes an ``$options`` parameter which is used to provide
    +addition request information.  The following keys can be used in ``$options``:
    +
    +- ``headers`` - Array of additional headers
    +- ``cookie`` - Array of cookies to use.
    +- ``proxy`` - Array of proxy information.
    +- ``auth`` - Array of authentication data, the ``type`` key is used to delegate to
    +  an authentication strategy. By default Basic auth is used.
    +- ``ssl_verify_peer`` - defaults to ``true``. Set to ``false`` to disable SSL certification
    +  verification (not recommended).
    +- ``ssl_verify_peer_name`` - defaults to ``true``. Set to ``false`` to disable
    +  host name verification when verifying SSL certificates (not recommended).
    +- ``ssl_verify_depth`` - defaults to 5. Depth to traverse in the CA chain.
    +- ``ssl_verify_host`` - defaults to ``true``. Validate the SSL certificate against the host name.
    +- ``ssl_cafile`` - defaults to built in cafile. Overwrite to use custom CA bundles.
    +- ``timeout`` - Duration to wait before timing out in seconds.
    +- ``type`` - Send a request body in a custom content type. Requires ``$data`` to
    +  either be a string, or the ``_content`` option to be set when doing GET
    +  requests.
    +- ``redirect`` - Number of redirects to follow. Defaults to ``false``.
    +
    +The options parameter is always the 3rd parameter in each of the HTTP methods.
    +They can also be used when constructing ``Client`` to create
    +:ref:`scoped clients `.
    +
    +Authentication
    +==============
    +
    +``Cake\Http\Client`` supports a few different authentication systems.  Different
    +authentication strategies can be added by developers. Auth strategies are called
    +before the request is sent, and allow headers to be added to the request
    +context.
    +
    +Using Basic Authentication
    +--------------------------
    +
    +An example of basic authentication::
    +
    +    $http = new Client();
    +    $response = $http->get('http://example.com/profile/1', [], [
    +      'auth' => ['username' => 'mark', 'password' => 'secret']
    +    ]);
    +
    +By default ``Cake\Http\Client`` will use basic authentication if there is no
    +``'type'`` key in the auth option.
    +
    +Using Digest Authentication
    +---------------------------
    +
    +An example of basic authentication::
    +
    +    $http = new Client();
    +    $response = $http->get('http://example.com/profile/1', [], [
    +      'auth' => [
    +        'type' => 'digest',
    +        'username' => 'mark',
    +        'password' => 'secret',
    +        'realm' => 'myrealm',
    +        'nonce' => 'onetimevalue',
    +        'qop' => 1,
    +        'opaque' => 'someval'
    +      ]
    +    ]);
    +
    +By setting the 'type' key to 'digest', you tell the authentication subsystem to
    +use digest authentication.
    +
    +OAuth 1 Authentication
    +----------------------
    +
    +Many modern web-services require OAuth authentication to access their API's.
    +The included OAuth authentication assumes that you already have your consumer
    +key and consumer secret::
    +
    +    $http = new Client();
    +    $response = $http->get('http://example.com/profile/1', [], [
    +      'auth' => [
    +        'type' => 'oauth',
    +        'consumerKey' => 'bigkey',
    +        'consumerSecret' => 'secret',
    +        'token' => '...',
    +        'tokenSecret' => '...',
    +        'realm' => 'tickets',
    +      ]
    +    ]);
    +
    +OAuth 2 Authentication
    +----------------------
    +
    +Because OAuth2 is often a single header, there is not a specialized
    +authentication adapter. Instead you can create a client with the access token::
    +
    +    $http = new Client([
    +        'headers' => ['Authorization' => 'Bearer ' . $accessToken]
    +    ]);
    +    $response = $http->get('https://example.com/api/profile/1');
    +
    +Proxy Authentication
    +--------------------
    +
    +Some proxies require authentication to use them. Generally this authentication
    +is Basic, but it can be implemented by any authentication adapter.  By default
    +Http\\Client will assume Basic authentication, unless the type key is set::
    +
    +    $http = new Client();
    +    $response = $http->get('http://example.com/test.php', [], [
    +      'proxy' => [
    +        'username' => 'mark',
    +        'password' => 'testing',
    +        'proxy' => '127.0.0.1:8080',
    +      ]
    +    ]);
    +
    +The second proxy parameter must be a string with an IP or a domain without
    +protocol. The username and password information will be passed through the
    +request headers, while the proxy string will be passed through
    +`stream_context_create()
    +`_.
    +
    +.. _http_client_scoped_client:
    +
    +Creating Scoped Clients
    +=======================
    +
    +Having to re-type the domain name, authentication and proxy settings can become
    +tedious & error prone.  To reduce the chance for mistake and relieve some of the
    +tedium, you can create scoped clients::
    +
    +    // Create a scoped client.
    +    $http = new Client([
    +      'host' => 'api.example.com',
    +      'scheme' => 'https',
    +      'auth' => ['username' => 'mark', 'password' => 'testing']
    +    ]);
    +
    +    // Do a request to api.example.com
    +    $response = $http->get('/test.php');
    +
    +The following information can be used when creating a scoped client:
    +
    +* host
    +* scheme
    +* proxy
    +* auth
    +* port
    +* cookies
    +* timeout
    +* ssl_verify_peer
    +* ssl_verify_depth
    +* ssl_verify_host
    +
    +Any of these options can be overridden by specifying them when doing requests.
    +host, scheme, proxy, port are overridden in the request URL::
    +
    +    // Using the scoped client we created earlier.
    +    $response = $http->get('http://foo.com/test.php');
    +
    +The above will replace the domain, scheme, and port.  However, this request will
    +continue using all the other options defined when the scoped client was created.
    +See :ref:`http_client_request_options` for more information on the options
    +supported.
    +
    +Setting and Managing Cookies
    +============================
    +
    +Http\\Client can also accept cookies when making requests. In addition to
    +accepting cookies, it will also automatically store valid cookies set in
    +responses. Any response with cookies, will have them stored in the originating
    +instance of Http\\Client. The cookies stored in a Client instance are
    +automatically included in future requests to domain + path combinations that
    +match::
    +
    +    $http = new Client([
    +        'host' => 'cakephp.org'
    +    ]);
    +
    +    // Do a request that sets some cookies
    +    $response = $http->get('/');
    +
    +    // Cookies from the first request will be included
    +    // by default.
    +    $response2 = $http->get('/changelogs');
    +
    +You can always override the auto-included cookies by setting them in the
    +request's ``$options`` parameters::
    +
    +    // Replace a stored cookie with a custom value.
    +    $response = $http->get('/changelogs', [], [
    +        'cookies' => ['sessionid' => '123abc']
    +    ]);
    +
    +You can add cookie objects to the client after creating it using the ``addCookie()``
    +method::
    +
    +    use Cake\Http\Cookie\Cookie;
    +
    +    $http = new Client([
    +        'host' => 'cakephp.org'
    +    ]);
    +    $http->addCookie(new Cookie('session', 'abc123'));
    +
    +.. versionadded:: 3.5.0
    +    ``addCookie()`` was added in 3.5.0
    +
    +.. _httpclient-response-objects:
    +
    +Response Objects
    +================
    +
    +.. php:namespace:: Cake\Http\Client
    +
    +.. php:class:: Response
    +
    +Response objects have a number of methods for inspecting the response data.
    +
    +.. versionchanged:: 3.3.0
    +    As of 3.3.0 ``Cake\Http\Client\Response`` implements the `PSR-7
    +    ResponseInterface
    +    `__.
    +
    +Reading Response Bodies
    +-----------------------
    +
    +You read the entire response body as a string::
    +
    +    // Read the entire response as a string.
    +    $response->body();
    +
    +    // As a property
    +    $response->body;
    +
    +You can also access the stream object for the response and use its methods::
    +
    +    // Get a Psr\Http\Message\StreamInterface containing the response body
    +    $stream = $response->getBody();
    +
    +    // Read a stream 100 bytes at a time.
    +    while (!$stream->eof()) {
    +        echo $stream->read(100);
    +    }
    +
    +.. _http-client-xml-json:
    +
    +Reading JSON and XML Response Bodies
    +------------------------------------
    +
    +Since JSON and XML responses are commonly used, response objects provide easy to
    +use accessors to read decoded data. JSON data is decoded into an array, while
    +XML data is decoded into a ``SimpleXMLElement`` tree::
    +
    +    // Get some XML
    +    $http = new Client();
    +    $response = $http->get('http://example.com/test.xml');
    +    $xml = $response->xml;
    +
    +    // Get some JSON
    +    $http = new Client();
    +    $response = $http->get('http://example.com/test.json');
    +    $json = $response->json;
    +
    +The decoded response data is stored in the response object, so accessing it
    +multiple times has no additional cost.
    +
    +Accessing Response Headers
    +--------------------------
    +
    +You can access headers through a few different methods. Header names are always
    +treated as case-insensitive values when accessing them through methods::
    +
    +    // Get all the headers as an associative array.
    +    $response->getHeaders();
    +
    +    // Get a single header as an array.
    +    $response->getHeader('content-type');
    +
    +    // Get a header as a string
    +    $response->getHeaderLine('content-type');
    +
    +    // Get the response encoding
    +    $response->getEncoding();
    +
    +    // Get an array of key=>value for all headers
    +    $response->headers;
    +
    +Accessing Cookie Data
    +---------------------
    +
    +You can read cookies with a few different methods depending on how much
    +data you need about the cookies::
    +
    +    // Get all cookies (full data)
    +    $response->getCookies();
    +
    +    // Get a single cookie's value.
    +    $response->getCookie('session_id');
    +
    +    // Get a the complete data for a single cookie
    +    // includes value, expires, path, httponly, secure keys.
    +    $response->getCookieData('session_id');
    +
    +    // Access the complete data for all cookies.
    +    $response->cookies;
    +
    +Checking the Status Code
    +------------------------
    +
    +Response objects provide a few methods for checking status codes::
    +
    +    // Was the response a 20x
    +    $response->isOk();
    +
    +    // Was the response a 30x
    +    $response->isRedirect();
    +
    +    // Get the status code
    +    $response->getStatusCode();
    +
    +    // __get() helper
    +    $response->code;
    +
    +.. meta::
    +    :title lang=en: HttpClient
    +    :keywords lang=en: array name,array data,query parameter,query string,php class,string query,test type,string data,google,query results,webservices,apis,parameters,cakephp,meth,search results
    diff --git a/tl/core-libraries/inflector.rst b/tl/core-libraries/inflector.rst
    new file mode 100644
    index 0000000000000000000000000000000000000000..daa4706276c81f233d3068daba84c3eb5aba5fcb
    --- /dev/null
    +++ b/tl/core-libraries/inflector.rst
    @@ -0,0 +1,204 @@
    +Inflector
    +#########
    +
    +.. php:namespace:: Cake\Utility
    +
    +.. php:class:: Inflector
    +
    +The Inflector class takes a string and can manipulate it to handle word
    +variations such as pluralizations or camelizing and is normally accessed
    +statically. Example:
    +``Inflector::pluralize('example')`` returns "examples".
    +
    +You can try out the inflections online at `inflector.cakephp.org
    +`_.
    +
    +.. _inflector-methods-summary:
    +
    +Summary of Inflector Methods and Their Output
    +=============================================
    +
    +Quick summary of the Inflector built-in methods and the results they output
    +when provided a multi-word argument:
    +
    ++-------------------+---------------+---------------+
    +| Method            | Argument      | Output        |
    ++===================+===============+===============+
    +| ``pluralize()``   | BigApple      | BigApples     |
    ++                   +---------------+---------------+
    +|                   | big_apple     | big_apples    |
    ++-------------------+---------------+---------------+
    +| ``singularize()`` | BigApples     | BigApple      |
    ++                   +---------------+---------------+
    +|                   | big_apples    | big_apple     |
    ++-------------------+---------------+---------------+
    +| ``camelize()``    | big_apples    | BigApples     |
    ++                   +---------------+---------------+
    +|                   | big apple     | BigApple      |
    ++-------------------+---------------+---------------+
    +| ``underscore()``  | BigApples     | big_apples    |
    ++                   +---------------+---------------+
    +|                   | Big Apples    | big apples    |
    ++-------------------+---------------+---------------+
    +| ``humanize()``    | big_apples    | Big Apples    |
    ++                   +---------------+---------------+
    +|                   | bigApple      | BigApple      |
    ++-------------------+---------------+---------------+
    +| ``classify()``    | big_apples    | BigApple      |
    ++                   +---------------+---------------+
    +|                   | big apple     | BigApple      |
    ++-------------------+---------------+---------------+
    +| ``dasherize()``   | BigApples     | big-apples    |
    ++                   +---------------+---------------+
    +|                   | big apple     | big apple     |
    ++-------------------+---------------+---------------+
    +| ``tableize()``    | BigApple      | big_apples    |
    ++                   +---------------+---------------+
    +|                   | Big Apple     | big apples    |
    ++-------------------+---------------+---------------+
    +| ``variable()``    | big_apple     | bigApple      |
    ++                   +---------------+---------------+
    +|                   | big apples    | bigApples     |
    ++-------------------+---------------+---------------+
    +| ``slug()``        | Big Apple     | big-apple     |
    ++                   +---------------+---------------+
    +|                   | BigApples     | BigApples     |
    ++-------------------+---------------+---------------+
    +
    +Creating Plural & Singular Forms
    +================================
    +
    +.. php:staticmethod:: singularize($singular)
    +.. php:staticmethod:: pluralize($singular)
    +
    +Both ``pluralize`` and ``singularize()`` work on most English nouns. If you need
    +to support other languages, you can use :ref:`inflection-configuration` to
    +customize the rules used::
    +
    +    // Apples
    +    echo Inflector::pluralize('Apple');
    +
    +.. note::
    +
    +    ``pluralize()`` may not always correctly convert a noun that is already in its plural form.
    +
    +.. code-block:: php
    +
    +    // Person
    +    echo Inflector::singularize('People');
    +
    +.. note::
    +
    +    ``singularize()`` may not always correctly convert a noun that is already in its singular form.
    +
    +Creating CamelCase and under_scored Forms
    +=========================================
    +
    +.. php:staticmethod:: camelize($underscored)
    +.. php:staticmethod:: underscore($camelCase)
    +
    +These methods are useful when creating class names, or property names::
    +
    +    // ApplePie
    +    Inflector::camelize('Apple_pie')
    +
    +    // apple_pie
    +    Inflector::underscore('ApplePie');
    +
    +It should be noted that underscore will only convert camelCase formatted words.
    +Words that contains spaces will be lower-cased, but will not contain an
    +underscore.
    +
    +Creating Human Readable Forms
    +=============================
    +
    +.. php:staticmethod:: humanize($underscored)
    +
    +This method is useful when converting underscored forms into "Title Case" forms
    +for human readable values::
    +
    +    // Apple Pie
    +    Inflector::humanize('apple_pie');
    +
    +Creating Table and Class Name Forms
    +===================================
    +
    +.. php:staticmethod:: classify($underscored)
    +.. php:staticmethod:: dasherize($dashed)
    +.. php:staticmethod:: tableize($camelCase)
    +
    +When generating code, or using CakePHP's conventions you may need to inflect
    +table names or class names::
    +
    +    // UserProfileSetting
    +    Inflector::classify('user_profile_settings');
    +
    +    // user-profile-setting
    +    Inflector::dasherize('UserProfileSetting');
    +
    +    // user_profile_settings
    +    Inflector::tableize('UserProfileSetting');
    +
    +Creating Variable Names
    +=======================
    +
    +.. php:staticmethod:: variable($underscored)
    +
    +Variable names are often useful when doing meta-programming tasks that involve
    +generating code or doing work based on conventions::
    +
    +    // applePie
    +    Inflector::variable('apple_pie');
    +
    +Creating URL Safe Strings
    +=========================
    +
    +.. php:staticmethod:: slug($word, $replacement = '-')
    +
    +Slug converts special characters into latin versions and converting unmatched
    +characters and spaces to dashes. The slug method expects UTF-8 encoding::
    +
    +    // apple-puree
    +    Inflector::slug('apple purée');
    +
    +.. note::
    +    ``Inflector::slug()`` has been deprecated since 3.2.7. Use ``Text::slug()``
    +    instead.
    +
    +.. _inflection-configuration:
    +
    +Inflection Configuration
    +========================
    +
    +CakePHP's naming conventions can be really nice - you can name your database
    +table ``big_boxes``, your model ``BigBoxes``, your controller
    +``BigBoxesController``, and everything just works together automatically. The
    +way CakePHP knows how to tie things together is by *inflecting* the words
    +between their singular and plural forms.
    +
    +There are occasions (especially for our non-English speaking friends) where you
    +may run into situations where CakePHP's inflector (the class that pluralizes,
    +singularizes, camelCases, and under\_scores) might not work as you'd like. If
    +CakePHP won't recognize your Foci or Fish, you can tell CakePHP about your
    +special cases.
    +
    +Loading Custom Inflections
    +--------------------------
    +
    +.. php:staticmethod:: rules($type, $rules, $reset = false)
    +
    +Define new inflection and transliteration rules for Inflector to use.  Often,
    +this method is used in your **config/bootstrap.php**::
    +
    +    Inflector::rules('singular', ['/^(bil)er$/i' => '\1', '/^(inflec|contribu)tors$/i' => '\1ta']);
    +    Inflector::rules('uninflected', ['singulars']);
    +    Inflector::rules('irregular', ['phylum' => 'phyla']); // The key is singular form, value is plural form
    +
    +The supplied rules will be merged into the respective inflection sets defined in
    +``Cake/Utility/Inflector``, with the added rules taking precedence over the core
    +rules. You can use ``Inflector::reset()`` to clear rules and restore the
    +original Inflector state.
    +
    +.. meta::
    +    :title lang=en: Inflector
    +    :keywords lang=en: apple orange,word variations,apple pie,person man,latin versions,profile settings,php class,initial state,puree,slug,apples,oranges,user profile,underscore
    diff --git a/tl/core-libraries/internationalization-and-localization.rst b/tl/core-libraries/internationalization-and-localization.rst
    new file mode 100644
    index 0000000000000000000000000000000000000000..5bcc9b9983887b5e384c1147cca0c02b300c9fd5
    --- /dev/null
    +++ b/tl/core-libraries/internationalization-and-localization.rst
    @@ -0,0 +1,650 @@
    +Internationalization & Localization
    +###################################
    +
    +One of the best ways for an application to reach a larger audience is to cater
    +to multiple languages. This can often prove to be a daunting task, but the
    +internationalization and localization features in CakePHP make it much easier.
    +
    +First, it's important to understand some terminology. *Internationalization*
    +refers to the ability of an application to be localized. The term *localization*
    +refers to the adaptation of an application to meet specific language (or
    +culture) requirements (i.e. a "locale"). Internationalization and localization
    +are often abbreviated as i18n and l10n respectively; 18 and 10 are the number
    +of characters between the first and last character.
    +
    +Setting Up Translations
    +=======================
    +
    +There are only a few steps to go from a single-language application to a
    +multi-lingual application, the first of which is to make use of the
    +:php:func:`__()` function in your code. Below is an example of some code for a
    +single-language application::
    +
    +    

    Popular Articles

    + +To internationalize your code, all you need to do is to wrap strings in +:php:func:`__()` like so:: + +

    + +Doing nothing else, these two code examples are functionally identical - they +will both send the same content to the browser. The :php:func:`__()` function +will translate the passed string if a translation is available, or return it +unmodified. + +Language Files +-------------- + +Translations can be made available by using language files stored in the +application. The default format for CakePHP translation files is the +`Gettext `_ format. Files need to be +placed under **src/Locale/** and within this directory, there should be a +subfolder for each language the application needs to support:: + + /src + /Locale + /en_US + default.po + /en_GB + default.po + validation.po + /es + default.po + +The default domain is 'default', therefore the locale folder should at least +contain the **default.po** file as shown above. A domain refers to any arbitrary +grouping of translation messages. When no group is used, then the default group +is selected. + +The core strings messages extracted from the CakePHP library can be stored +separately in a file named **cake.po** in **src/Locale/**. +The `CakePHP localized library `_ houses +translations for the client-facing translated strings in the core (the cake +domain). To use these files, link or copy them into their expected location: +**src/Locale//cake.po**. If your locale is incomplete or incorrect, +please submit a PR in this repository to fix it. + +Plugins can also contain translation files, the convention is to use the +``under_scored`` version of the plugin name as the domain for the translation +messages:: + + MyPlugin + /src + /Locale + /fr + my_plugin.po + /de + my_plugin.po + +Translation folders can either be the two letter ISO code of the language or the +full locale name such as ``fr_FR``, ``es_AR``, ``da_DK`` which contains both the +language and the country where it is spoken. + +An example translation file could look like this: + +.. code-block:: pot + + msgid "My name is {0}" + msgstr "Je m'appelle {0}" + + msgid "I'm {0,number} years old" + msgstr "J'ai {0,number} ans" + +Extract Pot Files with I18n Shell +--------------------------------- + +To create the pot files from `__()` and other internationalized types of +messages that can be found in the application code, you can use the i18n shell. +Please read the :doc:`following chapter ` to +learn more. + +Setting the Default Locale +-------------------------- + +The default locale can be set in your **config/app.php** file by setting +``App.defaultLocale``:: + + 'App' => [ + ... + 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'), + ... + ] + +This will control several aspects of the application, including the default +translations language, the date format, number format and currency whenever any +of those is displayed using the localization libraries that CakePHP provides. + +Changing the Locale at Runtime +------------------------------ + +To change the language for translated strings you can call this method:: + + use Cake\I18n\I18n; + + // Prior to 3.5 use I18n::locale() + I18n::setLocale('de_DE'); + +This will also change how numbers and dates are formatted when using one of the +localization tools. + +Using Translation Functions +=========================== + +CakePHP provides several functions that will help you internationalize your +application. The most frequently used one is :php:func:`__()`. This function +is used to retrieve a single translation message or return the same string if no +translation was found:: + + echo __('Popular Articles'); + +If you need to group your messages, for example, translations inside a plugin, +you can use the :php:func:`__d()` function to fetch messages from another +domain:: + + echo __d('my_plugin', 'Trending right now'); + +.. note:: + + If you want to translate your plugins and they're namespaced, you must name + your domain string ``Namespace/PluginName``. But the related language file + will become ``plugins/Namespace/PluginName/src/Locale/plugin_name.po`` + inside your plugin folder. + +Sometimes translations strings can be ambiguous for people translating them. +This can happen if two strings are identical but refer to different things. For +example, 'letter' has multiple meanings in English. To solve that problem, you +can use the :php:func:`__x()` function:: + + echo __x('written communication', 'He read the first letter'); + + echo __x('alphabet learning', 'He read the first letter'); + +The first argument is the context of the message and the second is the message +to be translated. + +.. code-block:: pot + + msgctxt "written communication" + msgid "He read the first letter" + msgstr "Er las den ersten Brief" + +Using Variables in Translation Messages +--------------------------------------- + +Translation functions allow you to interpolate variables into the messages using +special markers defined in the message itself or in the translated string:: + + echo __("Hello, my name is {0}, I'm {1} years old", ['Sara', 12]); + +Markers are numeric, and correspond to the keys in the passed array. You can +also pass variables as independent arguments to the function:: + + echo __("Small step for {0}, Big leap for {1}", 'Man', 'Humanity'); + +All translation functions support placeholder replacements:: + + __d('validation', 'The field {0} cannot be left empty', 'Name'); + + __x('alphabet', 'He read the letter {0}', 'Z'); + +The ``'`` (single quote) character acts as an escape code in translation +messages. Any variables between single quotes will not be replaced and is +treated as literal text. For example:: + + __("This variable '{0}' be replaced.", 'will not'); + +By using two adjacent quotes your variables will be replaced properly:: + + __("This variable ''{0}'' be replaced.", 'will'); + +These functions take advantage of the +`ICU MessageFormatter `_ +so you can translate messages and localize dates, numbers and currency at the +same time:: + + echo __( + 'Hi {0}, your balance on the {1,date} is {2,number,currency}', + ['Charles', new FrozenTime('2014-01-13 11:12:00'), 1354.37] + ); + + // Returns + Hi Charles, your balance on the Jan 13, 2014, 11:12 AM is $ 1,354.37 + +Numbers in placeholders can be formatted as well with fine grain control of the +output:: + + echo __( + 'You have traveled {0,number} kilometers in {1,number,integer} weeks', + [5423.344, 5.1] + ); + + // Returns + You have traveled 5,423.34 kilometers in 5 weeks + + echo __('There are {0,number,#,###} people on earth', 6.1 * pow(10, 8)); + + // Returns + There are 6,100,000,000 people on earth + +This is the list of formatter specifiers you can put after the word ``number``: + +* ``integer``: Removes the decimal part +* ``currency``: Puts the locale currency symbol and rounds decimals +* ``percent``: Formats the number as a percentage + +Dates can also be formatted by using the word ``date`` after the placeholder +number. A list of extra options follows: + +* ``short`` +* ``medium`` +* ``long`` +* ``full`` + +The word ``time`` after the placeholder number is also accepted and it +understands the same options as ``date``. + +.. note:: + + Named placeholders are supported in PHP 5.5+ and are formatted as + ``{name}``. When using named placeholders pass the variables in an array + using key/value pairs, for example ``['name' => 'Sara', 'age' => 12]``. + + It is recommended to use PHP 5.5 or higher when making use of + internationalization features in CakePHP. The ``php5-intl`` extension must + be installed and the ICU version should be above 48.x.y (to check the ICU + version ``Intl::getIcuVersion()``). + +Plurals +------- + +One crucial part of internationalizing your application is getting your messages +pluralized correctly depending on the language they are shown. CakePHP provides +a couple ways to correctly select plurals in your messages. + +Using ICU Plural Selection +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The first one is taking advantage of the ``ICU`` message format that comes by +default in the translation functions. In the translations file you could have +the following strings + +.. code-block:: pot + + msgid "{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}" + msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{# resultados}}" + + msgid "{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}" + msgstr "{placeholder,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" + +And in the application use the following code to output either of the +translations for such string:: + + __('{0,plural,=0{No records found }=1{Found 1 record} other{Found # records}}', [0]); + + // Returns "Ningún resultado" as the argument {0} is 0 + + __('{0,plural,=0{No records found} =1{Found 1 record} other{Found # records}}', [1]); + + // Returns "1 resultado" because the argument {0} is 1 + + __('{placeholder,plural,=0{No records found} =1{Found 1 record} other{Found {1} records}}', [0, 'many', 'placeholder' => 2]) + + // Returns "many resultados" because the argument {placeholder} is 2 and + // argument {1} is 'many' + +A closer look to the format we just used will make it evident how messages are +built:: + + { [count placeholder],plural, case1{message} case2{message} case3{...} ... } + +The ``[count placeholder]`` can be the array key number of any of the variables +you pass to the translation function. It will be used for selecting the correct +plural form. + +Note that to reference ``[count placeholder]`` within ``{message}`` you have to +use ``#``. + +You can of course use simpler message ids if you don't want to type the full +plural selection sequence in your code + +.. code-block:: pot + + msgid "search.results" + msgstr "{0,plural,=0{Ningún resultado} =1{1 resultado} other{{1} resultados}}" + +Then use the new string in your code:: + + __('search.results', [2, 2]); + + // Returns: "2 resultados" + +The latter version has the downside that there is a need to have a translation +messages file even for the default language, but has the advantage that it makes +the code more readable and leaves the complicated plural selection strings in +the translation files. + +Sometimes using direct number matching in plurals is impractical. For example, +languages like Arabic require a different plural when you refer +to few things and other plural form for many things. In those cases you can +use the ICU matching aliases. Instead of writing:: + + =0{No results} =1{...} other{...} + +You can do:: + + zero{No Results} one{One result} few{...} many{...} other{...} + +Make sure you read the +`Language Plural Rules Guide `_ +to get a complete overview of the aliases you can use for each language. + +Using Gettext Plural Selection +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The second plural selection format accepted is using the built-in capabilities +of Gettext. In this case, plurals will be stored in the ``.po`` +file by creating a separate message translation line per plural form: + +.. code-block:: pot + + # One message identifier for singular + msgid "One file removed" + # Another one for plural + msgid_plural "{0} files removed" + # Translation in singular + msgstr[0] "Un fichero eliminado" + # Translation in plural + msgstr[1] "{0} ficheros eliminados" + +When using this other format, you are required to use another translation +function:: + + // Returns: "10 ficheros eliminados" + $count = 10; + __n('One file removed', '{0} files removed', $count, $count); + + // It is also possible to use it inside a domain + __dn('my_plugin', 'One file removed', '{0} files removed', $count, $count); + +The number inside ``msgstr[]`` is the number assigned by Gettext for the plural +form of the language. Some languages have more than two plural forms, for +example Croatian: + +.. code-block:: pot + + msgid "One file removed" + msgid_plural "{0} files removed" + msgstr[0] "{0} datoteka je uklonjena" + msgstr[1] "{0} datoteke su uklonjene" + msgstr[2] "{0} datoteka je uklonjeno" + +Please visit the `Launchpad languages page `_ +for a detailed explanation of the plural form numbers for each language. + +Creating Your Own Translators +============================= + +If you need to diverge from CakePHP conventions regarding where and how +translation messages are stored, you can create your own translation message +loader. The easiest way to create your own translator is by defining a loader +for a single domain and locale:: + + use Aura\Intl\Package; + + I18n::setTranslator('animals', function () { + $package = new Package( + 'default', // The formatting strategy (ICU) + 'default' // The fallback domain + ); + $package->setMessages([ + 'Dog' => 'Chien', + 'Cat' => 'Chat', + 'Bird' => 'Oiseau' + ... + ]); + + return $package; + }, 'fr_FR'); + +The above code can be added to your **config/bootstrap.php** so that +translations can be found before any translation function is used. The absolute +minimum that is required for creating a translator is that the loader function +should return a ``Aura\Intl\Package`` object. Once the code is in place you can +use the translation functions as usual:: + + // Prior to 3.5 use I18n::locale() + I18n::setLocale('fr_FR'); + __d('animals', 'Dog'); // Returns "Chien" + +As you see, ``Package`` objects take translation messages as an array. You can +pass the ``setMessages()`` method however you like: with inline code, including +another file, calling another function, etc. CakePHP provides a few loader +functions you can reuse if you just need to change where messages are loaded. +For example, you can still use **.po** files, but loaded from another location:: + + use Cake\I18n\MessagesFileLoader as Loader; + + // Load messages from src/Locale/folder/sub_folder/filename.po + // Prior to 3.5 use translator() + I18n::setTranslator( + 'animals', + new Loader('filename', 'folder/sub_folder', 'po'), + 'fr_FR' + ); + +Creating Message Parsers +------------------------ + +It is possible to continue using the same conventions CakePHP uses, but use +a message parser other than ``PoFileParser``. For example, if you wanted to load +translation messages using ``YAML``, you will first need to created the parser +class:: + + namespace App\I18n\Parser; + + class YamlFileParser + { + + public function parse($file) + { + return yaml_parse_file($file); + } + } + +The file should be created in the **src/I18n/Parser** directory of your +application. Next, create the translations file under +**src/Locale/fr_FR/animals.yaml** + +.. code-block:: yaml + + Dog: Chien + Cat: Chat + Bird: Oiseau + +And finally, configure the translation loader for the domain and locale:: + + use Cake\I18n\MessagesFileLoader as Loader; + + // Prior to 3.5 use translator() + I18n::setTranslator( + 'animals', + new Loader('animals', 'fr_FR', 'yaml'), + 'fr_FR' + ); + +.. _creating-generic-translators: + +Creating Generic Translators +---------------------------- + +Configuring translators by calling ``I18n::setTranslator()`` for each domain and +locale you need to support can be tedious, specially if you need to support more +than a few different locales. To avoid this problem, CakePHP lets you define +generic translator loaders for each domain. + +Imagine that you wanted to load all translations for the default domain and for +any language from an external service:: + + use Aura\Intl\Package; + + I18n::config('default', function ($domain, $locale) { + $locale = Locale::parseLocale($locale); + $language = $locale['language']; + $messages = file_get_contents("http://example.com/translations/$lang.json"); + + return new Package( + 'default', // Formatter + null, // Fallback (none for default domain) + json_decode($messages, true) + ) + }); + +The above example calls an example external service to load a JSON file with the +translations and then just build a ``Package`` object for any locale that is +requested in the application. + +If you'd like to change how packages are loaded for all packages, that don't +have specific loaders set you can replace the fallback package loader by using +the ``_fallback`` package:: + + I18n::config('_fallback', function ($domain, $locale) { + // Custom code that yields a package here. + }); + +.. versionadded:: 3.4.0 + Replacing the ``_fallback`` loader was added in 3.4.0 + +Plurals and Context in Custom Translators +----------------------------------------- + +The arrays used for ``setMessages()`` can be crafted to instruct the translator +to store messages under different domains or to trigger Gettext-style plural +selection. The following is an example of storing translations for the same key +in different contexts:: + + [ + 'He reads the letter {0}' => [ + 'alphabet' => 'Él lee la letra {0}', + 'written communication' => 'Él lee la carta {0}' + ] + ] + +Similarly, you can express Gettext-style plurals using the messages array by +having a nested array key per plural form:: + + [ + 'I have read one book' => 'He leído un libro', + 'I have read {0} books' => [ + 'He leído un libro', + 'He leído {0} libros' + ] + ] + +Using Different Formatters +-------------------------- + +In previous examples we have seen that Packages are built using ``default`` as +first argument, and it was indicated with a comment that it corresponded to the +formatter to be used. Formatters are classes responsible for interpolating +variables in translation messages and selecting the correct plural form. + +If you're dealing with a legacy application, or you don't need the power offered +by the ICU message formatting, CakePHP also provides the ``sprintf`` formatter:: + + return Package('sprintf', 'fallback_domain', $messages); + +The messages to be translated will be passed to the ``sprintf()`` function for +interpolating the variables:: + + __('Hello, my name is %s and I am %d years old', 'José', 29); + +It is possible to set the default formatter for all translators created by +CakePHP before they are used for the first time. This does not include manually +created translators using the ``setTranslator()`` and ``config()`` methods:: + + I18n::defaultFormatter('sprintf'); + +Localizing Dates and Numbers +============================ + +When outputting Dates and Numbers in your application, you will often need that +they are formatted according to the preferred format for the country or region +that you wish your page to be displayed. + +In order to change how dates and numbers are displayed you just need to change +the current locale setting and use the right classes:: + + use Cake\I18n\I18n; + use Cake\I18n\Time; + use Cake\I18n\Number; + + // Prior to 3.5 use I18n::locale() + I18n::setLocale('fr-FR'); + + $date = new Time('2015-04-05 23:00:00'); + + echo $date; // Displays 05/04/2015 23:00 + + echo Number::format(524.23); // Displays 524,23 + +Make sure you read the :doc:`/core-libraries/time` and :doc:`/core-libraries/number` +sections to learn more about formatting options. + +By default dates returned for the ORM results use the ``Cake\I18n\Time`` class, +so displaying them directly in you application will be affected by changing the +current locale. + +.. _parsing-localized-dates: + +Parsing Localized Datetime Data +------------------------------- + +When accepting localized data from the request, it is nice to accept datetime +information in a user's localized format. In a controller, or +:doc:`/development/dispatch-filters` you can configure the Date, Time, and +DateTime types to parse localized formats:: + + use Cake\Database\Type; + + // Enable default locale format parsing. + Type::build('datetime')->useLocaleParser(); + + // Configure a custom datetime format parser format. + Type::build('datetime')->useLocaleParser()->setLocaleFormat('dd-M-y'); + + // You can also use IntlDateFormatter constants. + Type::build('datetime')->useLocaleParser() + ->setLocaleFormat([IntlDateFormatter::SHORT, -1]); + +The default parsing format is the same as the default string format. + +Automatically Choosing the Locale Based on Request Data +======================================================= + +By using the ``LocaleSelectorFilter`` in your application, CakePHP will +automatically set the locale based on the current user:: + + // in src/Application.php + use Cake\I18n\Middleware\LocaleSelectorMiddleware; + + // Update the middleware function, adding the new middleware + public function middleware($middleware) + { + // Add middleware and set the valid locales + $middleware->add(new LocaleSelectorMiddleware(['en_US', 'fr_FR'])); + } + + // Prior to 3.3.0, use the DispatchFilter + // in config/bootstrap.php + DispatcherFactory::add('LocaleSelector'); + + // Restrict the locales to only en_US, fr_FR + DispatcherFactory::add('LocaleSelector', ['locales' => ['en_US', 'fr_FR']]); + +The ``LocaleSelectorFilter`` will use the ``Accept-Language`` header to +automatically set the user's preferred locale. You can use the locale list +option to restrict which locales will automatically be used. + +.. meta:: + :title lang=en: Internationalization & Localization + :keywords lang=en: internationalization localization,internationalization and localization,language application,gettext,l10n,pot,i18n,translation,languages diff --git a/tl/core-libraries/logging.rst b/tl/core-libraries/logging.rst new file mode 100644 index 0000000000000000000000000000000000000000..7f99d85e839bc43aab610cacf185b5c5322b1e15 --- /dev/null +++ b/tl/core-libraries/logging.rst @@ -0,0 +1,453 @@ +Logging +####### + +While CakePHP core Configure Class settings can really help you see +what's happening under the hood, there are certain times that +you'll need to log data to the disk in order to find out what's +going on. With technologies like SOAP, AJAX, and REST APIs, debugging can be +rather difficult. + +Logging can also be a way to find out what's been going on in your +application over time. What search terms are being used? What sorts +of errors are my users being shown? How often is a particular query +being executed? + +Logging data in CakePHP is easy - the log() function is provided by the +``LogTrait``, which is the common ancestor for many CakePHP classes. If +the context is a CakePHP class (Controller, Component, View,...), +you can log your data. You can also use ``Log::write()`` directly. +See :ref:`writing-to-logs`. + +.. _log-configuration: + +Logging Configuration +===================== + +Configuring ``Log`` should be done during your application's bootstrap phase. +The **config/app.php** file is intended for just this. You can define +as many or as few loggers as your application needs. Loggers should be +configured using :php:class:`Cake\\Core\\Log`. An example would be:: + + use Cake\Log\Log; + + // Short classname + Log::config('debug', [ + 'className' => 'File', + 'path' => LOGS, + 'levels' => ['notice', 'info', 'debug'], + 'file' => 'debug', + ]); + + // Fully namespaced name. + Log::config('error', [ + 'className' => 'Cake\Log\Engine\FileLog', + 'path' => LOGS, + 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'], + 'file' => 'error', + ]); + +The above creates two loggers. One named ``debug`` the other named ``error``. +Each is configured to handle different levels of messages. They also store their +log messages in separate files, so it's easy to separate debug/notice/info logs +from more serious errors. See the section on :ref:`logging-levels` for more +information on the different levels and what they mean. + +Once a configuration is created you cannot change it. Instead you should drop +the configuration and re-create it using :php:meth:`Cake\\Log\\Log::drop()` and +:php:meth:`Cake\\Log\\Log::config()`. + +It is also possible to create loggers by providing a closure. This is useful +when you need full control over how the logger object is built. The closure +has to return the constructed logger instance. For example:: + + Log::config('special', function () { + return new \Cake\Log\Engine\FileLog(['path' => LOGS, 'file' => 'log']); + }); + +Configuration options can also be provided as a :term:`DSN` string. This is +useful when working with environment variables or :term:`PaaS` providers:: + + Log::config('error', [ + 'url' => 'file:///?levels[]=warning&levels[]=error&file=error', + ]); + +.. note:: + + Loggers are required to implement the ``Psr\Log\LoggerInterface`` interface. + +Creating Log Adapters +--------------------- + +Log adapters can be part of your application, or part of +plugins. If for example you had a database logger called +``DatabaseLog``. As part of your application it would be placed in +**src/Log/Engine/DatabaseLog.php**. As part of a plugin it would be placed in +**plugins/LoggingPack/src/Log/Engine/DatabaseLog.php**. To configure log +adapters you should use :php:meth:`Cake\\Log\\Log::config()`. For example +configuring our DatabaseLog would look like:: + + // For src/Log + Log::config('otherFile', [ + 'className' => 'Database', + 'model' => 'LogEntry', + // ... + ]); + + // For plugin called LoggingPack + Log::config('otherFile', [ + 'className' => 'LoggingPack.Database', + 'model' => 'LogEntry', + // ... + ]); + +When configuring a log adapter the ``className`` parameter is used to +locate and load the log handler. All of the other configuration +properties are passed to the log adapter's constructor as an array. :: + + namespace App\Log\Engine; + use Cake\Log\Engine\BaseLog; + + class DatabaseLog extends BaseLog + { + public function __construct($options = []) + { + parent::__construct($options); + // ... + } + + public function log($level, $message, array $context = []) + { + // Write to the database. + } + } + +CakePHP requires that all logging adapters implement ``Psr\Log\LoggerInterface``. +The class :php:class:`Cake\Log\Engine\BaseLog` is an easy way to satisfy the +interface as it only requires you to implement the ``log()`` method. + +.. _file-log: + +``FileLog`` engine takes the following options: + +* ``size`` Used to implement basic log file rotation. If log file size + reaches specified size the existing file is renamed by appending timestamp + to filename and new log file is created. Can be integer bytes value or + human readable string values like '10MB', '100KB' etc. Defaults to 10MB. +* ``rotate`` Log files are rotated specified times before being removed. + If value is 0, old versions are removed rather then rotated. Defaults to 10. +* ``mask`` Set the file permissions for created files. If left empty the default + permissions are used. + +.. warning:: + + Engines have the suffix ``Log``. You should avoid class names like ``SomeLogLog`` + which include the suffix twice at the end. + +.. note:: + + You should configure loggers during bootstrapping. **config/app.php** is the + conventional place to configure log adapters. + + In debug mode missing directories will be automatically created to avoid unnecessary + errors thrown when using the FileEngine. + +Error and Exception Logging +=========================== + +Errors and Exceptions can also be logged. By configuring the co-responding +values in your app.php file. Errors will be displayed when debug > 0 and logged +when debug is ``false``. To log uncaught exceptions, set the ``log`` option to +``true``. See :doc:`/development/configuration` for more information. + +Interacting with Log Streams +============================ + +You can introspect the configured streams with +:php:meth:`Cake\\Log\\Log::configured()`. The return of ``configured()`` is an +array of all the currently configured streams. You can remove +streams using :php:meth:`Cake\\Log\\Log::drop()`. Once a log stream has been +dropped it will no longer receive messages. + +Using the FileLog Adapter +========================= + +As its name implies FileLog writes log messages to files. The level of log +message being written determines the name of the file the message is stored in. +If a level is not supplied, :php:const:`LOG_ERR` is used which writes to the +error log. The default log location is ``logs/$level.log``:: + + // Executing this inside a CakePHP class + $this->log("Something didn't work!"); + + // Results in this being appended to logs/error.log + // 2007-11-02 10:22:02 Error: Something didn't work! + +The configured directory must be writable by the web server user in +order for logging to work correctly. + +You can configure additional/alternate FileLog locations when configuring +a logger.FileLog accepts a ``path`` which allows for +custom paths to be used:: + + Log::config('custom_path', [ + 'className' => 'File', + 'path' => '/path/to/custom/place/' + ]); + +.. warning:: + If you do not configure a logging adapter, log messages will not be stored. + +.. _syslog-log: + +Logging to Syslog +================= + +In production environments it is highly recommended that you setup your system to +use syslog instead of the files logger. This will perform much better as any +writes will be done in a (almost) non-blocking fashion and your operating system +logger can be configured separately to rotate files, pre-process writes or use +a completely different storage for your logs. + +Using syslog is pretty much like using the default FileLog engine, you just need +to specify ``Syslog`` as the engine to be used for logging. The following +configuration snippet will replace the default logger with syslog, this should +be done in the **bootstrap.php** file:: + + Log::config('default', [ + 'engine' => 'Syslog' + ]); + +The configuration array accepted for the Syslog logging engine understands the +following keys: + +* ``format``: An sprintf template string with two placeholders, the first one + for the error level, and the second for the message itself. This key is + useful to add additional information about the server or process in the + logged message. For example: ``%s - Web Server 1 - %s`` will look like + ``error - Web Server 1 - An error occurred in this request`` after + replacing the placeholders. +* ``prefix``: An string that will be prefixed to every logged message. +* ``flag``: An integer flag to be used for opening the connection to the + logger, by default ``LOG_ODELAY`` will be used. See ``openlog`` documentation + for more options +* ``facility``: The logging slot to use in syslog. By default ``LOG_USER`` is + used. See ``syslog`` documentation for more options + +.. _writing-to-logs: + +Writing to Logs +=============== + +Writing to the log files can be done in 2 different ways. The first +is to use the static :php:meth:`Cake\\Log\\Log::write()` method:: + + Log::write('debug', 'Something did not work'); + +The second is to use the ``log()`` shortcut function available on any +class using the ``LogTrait``. Calling log() will internally call +``Log::write()``:: + + // Executing this inside a class using LogTrait + $this->log("Something did not work!", 'debug'); + +All configured log streams are written to sequentially each time +:php:meth:`Cake\\Log\\Log::write()` is called. If you have not configured any +logging adapters ``log()`` will return ``false`` and no log messages will be +written. + +.. _logging-levels: + +Using Levels +------------ + +CakePHP supports the standard POSIX set of logging levels. Each level represents +an increasing level of severity: + +* Emergency: system is unusable +* Alert: action must be taken immediately +* Critical: critical conditions +* Error: error conditions +* Warning: warning conditions +* Notice: normal but significant condition +* Info: informational messages +* Debug: debug-level messages + +You can refer to these levels by name when configuring loggers, and when writing +log messages. Alternatively, you can use convenience methods like +:php:meth:`Cake\\Log\\Log::error()` to clearly indicate the logging +level. Using a level that is not in the above levels will result in an +exception. + +.. note:: + When ``levels`` is set to an empty value in a logger's configuration, it + will take messages of any level. + +.. _logging-scopes: + +Logging Scopes +-------------- + +Often times you'll want to configure different logging behavior for different +subsystems or parts of your application. Take for example an e-commerce shop. +You'll probably want to handle logging for orders and payments differently than +you do other less critical logs. + +CakePHP exposes this concept as logging scopes. When log messages are written +you can include a scope name. If there is a configured logger for that scope, +the log messages will be directed to those loggers. For example:: + + // Configure logs/shops.log to receive all levels, but only + // those with `orders` and `payments` scope. + Log::config('shops', [ + 'className' => 'File', + 'path' => LOGS, + 'levels' => [], + 'scopes' => ['orders', 'payments'], + 'file' => 'shops.log', + ]); + + // Configure logs/payments.log to receive all levels, but only + // those with `payments` scope. + Log::config('payments', [ + 'className' => 'File', + 'path' => LOGS, + 'levels' => [], + 'scopes' => ['payments'], + 'file' => 'payments.log', + ]); + + Log::warning('this gets written only to shops.log', ['scope' => ['orders']]); + Log::warning('this gets written to both shops.log and payments.log', ['scope' => ['payments']]); + +Scopes can also be passed as a single string or a numerically indexed array. +Note that using this form will limit the ability to pass more data as context:: + + Log::warning('This is a warning', ['orders']); + Log::warning('This is a warning', 'payments'); + +.. note:: + When ``scopes`` is set to an empty array or ``null`` in a logger's + configuration, it will take messages of any scope. Setting it to ``false`` + will only match messages without scope. + +Log API +======= + +.. php:namespace:: Cake\Log + +.. php:class:: Log + + A simple class for writing to logs. + +.. php:staticmethod:: config($key, $config) + + :param string $name: Name for the logger being connected, used + to drop a logger later on. + :param array $config: Array of configuration information and + constructor arguments for the logger. + + Get or set the configuration for a Logger. See :ref:`log-configuration` for + more information. + +.. php:staticmethod:: configured() + + :returns: An array of configured loggers. + + Get the names of the configured loggers. + +.. php:staticmethod:: drop($name) + + :param string $name: Name of the logger you wish to no longer receive + messages. + +.. php:staticmethod:: write($level, $message, $scope = []) + + Write a message into all the configured loggers. + ``$level`` indicates the level of log message being created. + ``$message`` is the message of the log entry being written to. + ``$scope`` is the scope(s) a log message is being created in. + +.. php:staticmethod:: levels() + +Call this method without arguments, eg: `Log::levels()` to obtain current +level configuration. + +Convenience Methods +------------------- + +The following convenience methods were added to log `$message` with the +appropriate log level. + +.. php:staticmethod:: emergency($message, $scope = []) +.. php:staticmethod:: alert($message, $scope = []) +.. php:staticmethod:: critical($message, $scope = []) +.. php:staticmethod:: error($message, $scope = []) +.. php:staticmethod:: warning($message, $scope = []) +.. php:staticmethod:: notice($message, $scope = []) +.. php:staticmethod:: debug($message, $scope = []) +.. php:staticmethod:: info($message, $scope = []) + +Logging Trait +============= + +.. php:trait:: LogTrait + + A trait that provides shortcut methods for logging + +.. php:method:: log($msg, $level = LOG_ERR) + + Log a message to the logs. By default messages are logged as + ERROR messages. If ``$msg`` isn't a string it will be converted with + ``print_r`` before being logged. + +Using Monolog +============= + +Monolog is a popular logger for PHP. Since it implements the same interfaces as +the CakePHP loggers, it is easy to use in your application as the default +logger. + +After installing Monolog using composer, configure the logger using the +``Log::config()`` method:: + + // config/bootstrap.php + + use Monolog\Logger; + use Monolog\Handler\StreamHandler; + + Log::config('default', function () { + $log = new Logger('app'); + $log->pushHandler(new StreamHandler('path/to/your/combined.log')); + return $log; + }); + + // Optionally stop using the now redundant default loggers + Log::drop('debug'); + Log::drop('error'); + +Use similar methods if you want to configure a different logger for your console:: + + // config/bootstrap_cli.php + + use Monolog\Logger; + use Monolog\Handler\StreamHandler; + + Log::config('default', function () { + $log = new Logger('cli'); + $log->pushHandler(new StreamHandler('path/to/your/combined-cli.log')); + return $log; + }); + + // Optionally stop using the now redundant default CLI loggers + Configure::delete('Log.debug'); + Configure::delete('Log.error'); + +.. note:: + + When using a console specific logger, make sure to conditionally configure + your application logger. This will prevent duplicate log entries. + +.. meta:: + :title lang=en: Logging + :description lang=en: Log CakePHP data to the disk to help debug your application over longer periods of time. + :keywords lang=en: cakephp logging,log errors,debug,logging data,cakelog class,ajax logging,soap logging,debugging,logs diff --git a/tl/core-libraries/number.rst b/tl/core-libraries/number.rst new file mode 100644 index 0000000000000000000000000000000000000000..afb1d29499c37846464ccda3414e7d7776729e87 --- /dev/null +++ b/tl/core-libraries/number.rst @@ -0,0 +1,348 @@ +Number +###### + +.. php:namespace:: Cake\I18n + +.. php:class:: Number + +If you need :php:class:`NumberHelper` functionalities outside of a ``View``, +use the ``Number`` class:: + + namespace App\Controller; + + use Cake\I18n\Number; + + class UsersController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth'); + } + + public function afterLogin() + { + $storageUsed = $this->Auth->user('storage_used'); + if ($storageUsed > 5000000) { + // Notify users of quota + $this->Flash->success(__('You are using {0} storage', Number::toReadableSize($storageUsed))); + } + } + } + +.. start-cakenumber + +All of these functions return the formatted number; they do not +automatically echo the output into the view. + +Formatting Currency Values +========================== + +.. php:method:: currency(mixed $value, string $currency = null, array $options = []) + +This method is used to display a number in common currency formats +(EUR, GBP, USD). Usage in a view looks like:: + + // Called as NumberHelper + echo $this->Number->currency($value, $currency); + + // Called as Number + echo Number::currency($value, $currency); + +The first parameter, ``$value``, should be a floating point number +that represents the amount of money you are expressing. The second +parameter is a string used to choose a predefined currency formatting +scheme: + ++---------------------+----------------------------------------------------+ +| $currency | 1234.56, formatted by currency type | ++=====================+====================================================+ +| EUR | €1.234,56 | ++---------------------+----------------------------------------------------+ +| GBP | £1,234.56 | ++---------------------+----------------------------------------------------+ +| USD | $1,234.56 | ++---------------------+----------------------------------------------------+ + +The third parameter is an array of options for further defining the +output. The following options are available: + ++---------------------+----------------------------------------------------+ +| Option | Description | ++=====================+====================================================+ +| before | Text to display before the rendered number. | ++---------------------+----------------------------------------------------+ +| after | Text to display after the rendered number. | ++---------------------+----------------------------------------------------+ +| zero | The text to use for zero values; can be a string | +| | or a number. ie. 0, 'Free!'. | ++---------------------+----------------------------------------------------+ +| places | Number of decimal places to use, ie. 2 | ++---------------------+----------------------------------------------------+ +| precision | Maximal number of decimal places to use, ie. 2 | ++---------------------+----------------------------------------------------+ +| locale | The locale name to use for formatting number, | +| | ie. "fr_FR". | ++---------------------+----------------------------------------------------+ +| fractionSymbol | String to use for fraction numbers, ie. ' cents'. | ++---------------------+----------------------------------------------------+ +| fractionPosition | Either 'before' or 'after' to place the fraction | +| | symbol. | ++---------------------+----------------------------------------------------+ +| pattern | An ICU number pattern to use for formatting the | +| | number ie. #,###.00 | ++---------------------+----------------------------------------------------+ +| useIntlCode | Set to ``true`` to replace the currency symbol | +| | with the international currency code. | ++---------------------+----------------------------------------------------+ + +If $currency value is ``null``, the default currency will be retrieved from +:php:meth:`Cake\\I18n\\Number::defaultCurrency()` + +Setting the Default Currency +============================ + +.. php:method:: defaultCurrency($currency) + +Setter/getter for the default currency. This removes the need to always pass the +currency to :php:meth:`Cake\\I18n\\Number::currency()` and change all +currency outputs by setting other default. If ``$currency`` is set to ``false``, +it will clear the currently stored value. By default, it will retrieve the +``intl.default_locale`` if set and 'en_US' if not. + +Formatting Floating Point Numbers +================================= + +.. php:method:: precision(float $value, int $precision = 3, array $options = []) + +This method displays a number with the specified amount of +precision (decimal places). It will round in order to maintain the +level of precision defined. :: + + // Called as NumberHelper + echo $this->Number->precision(456.91873645, 2); + + // Outputs + 456.92 + + // Called as Number + echo Number::precision(456.91873645, 2); + +Formatting Percentages +====================== + +.. php:method:: toPercentage(mixed $value, int $precision = 2, array $options = []) + ++---------------------+----------------------------------------------------+ +| Option | Description | ++=====================+====================================================+ +| multiply | Boolean to indicate whether the value has to be | +| | multiplied by 100. Useful for decimal percentages. | ++---------------------+----------------------------------------------------+ + +Like :php:meth:`Cake\\I18n\\Number::precision()`, this method formats a number +according to the supplied precision (where numbers are rounded to meet the +given precision). This method also expresses the number as a percentage +and appends the output with a percent sign. :: + + // Called as NumberHelper. Output: 45.69% + echo $this->Number->toPercentage(45.691873645); + + // Called as Number. Output: 45.69% + echo Number::toPercentage(45.691873645); + + // Called with multiply. Output: 45.7% + echo Number::toPercentage(0.45691, 1, [ + 'multiply' => true + ]); + +Interacting with Human Readable Values +====================================== + +.. php:method:: toReadableSize(string $size) + +This method formats data sizes in human readable forms. It provides +a shortcut way to convert bytes to KB, MB, GB, and TB. The size is +displayed with a two-digit precision level, according to the size +of data supplied (i.e. higher sizes are expressed in larger +terms):: + + // Called as NumberHelper + echo $this->Number->toReadableSize(0); // 0 Byte + echo $this->Number->toReadableSize(1024); // 1 KB + echo $this->Number->toReadableSize(1321205.76); // 1.26 MB + echo $this->Number->toReadableSize(5368709120); // 5 GB + + // Called as Number + echo Number::toReadableSize(0); // 0 Byte + echo Number::toReadableSize(1024); // 1 KB + echo Number::toReadableSize(1321205.76); // 1.26 MB + echo Number::toReadableSize(5368709120); // 5 GB + +Formatting Numbers +================== + +.. php:method:: format(mixed $value, array $options = []) + +This method gives you much more control over the formatting of +numbers for use in your views (and is used as the main method by +most of the other NumberHelper methods). Using this method might +looks like:: + + // Called as NumberHelper + $this->Number->format($value, $options); + + // Called as Number + Number::format($value, $options); + +The ``$value`` parameter is the number that you are planning on +formatting for output. With no ``$options`` supplied, the number +1236.334 would output as 1,236. Note that the default precision is +zero decimal places. + +The ``$options`` parameter is where the real magic for this method +resides. + +- If you pass an integer then this becomes the amount of precision + or places for the function. +- If you pass an associated array, you can use the following keys: + ++---------------------+----------------------------------------------------+ +| Option | Description | ++=====================+====================================================+ +| places | Number of decimal places to use, ie. 2 | ++---------------------+----------------------------------------------------+ +| precision | Maximum number of decimal places to use, ie. 2 | ++---------------------+----------------------------------------------------+ +| pattern | An ICU number pattern to use for formatting the | +| | number ie. #,###.00 | ++---------------------+----------------------------------------------------+ +| locale | The locale name to use for formatting number, | +| | ie. "fr_FR". | ++---------------------+----------------------------------------------------+ +| before | Text to display before the rendered number. | ++---------------------+----------------------------------------------------+ +| after | Text to display after the rendered number. | ++---------------------+----------------------------------------------------+ + +Example:: + + // Called as NumberHelper + echo $this->Number->format('123456.7890', [ + 'places' => 2, + 'before' => '¥ ', + 'after' => ' !' + ]); + // Output '¥ 123,456.79 !' + + echo $this->Number->format('123456.7890', [ + 'locale' => 'fr_FR' + ]); + // Output '123 456,79 !' + + // Called as Number + echo Number::format('123456.7890', [ + 'places' => 2, + 'before' => '¥ ', + 'after' => ' !' + ]); + // Output '¥ 123,456.79 !' + + echo Number::format('123456.7890', [ + 'locale' => 'fr_FR' + ]); + // Output '123 456,79 !' + +.. php:method:: ordinal(mixed $value, array $options = []) + +This method will output an ordinal number. + +Examples:: + + echo Number::ordinal(1); + // Output '1st' + + echo Number::ordinal(2); + // Output '2nd' + + echo Number::ordinal(2, [ + 'locale' => 'fr_FR' + ]); + // Output '2e' + + echo Number::ordinal(410); + // Output '410th' + +Format Differences +================== + +.. php:method:: formatDelta(mixed $value, array $options = []) + +This method displays differences in value as a signed number:: + + // Called as NumberHelper + $this->Number->formatDelta($value, $options); + + // Called as Number + Number::formatDelta($value, $options); + +The ``$value`` parameter is the number that you are planning on +formatting for output. With no ``$options`` supplied, the number +1236.334 would output as 1,236. Note that the default precision is +zero decimal places. + +The ``$options`` parameter takes the same keys as :php:meth:`Number::format()` itself: + ++---------------------+----------------------------------------------------+ +| Option | Description | ++=====================+====================================================+ +| places | Number of decimal places to use, ie. 2 | ++---------------------+----------------------------------------------------+ +| precision | Maximum number of decimal places to use, ie. 2 | ++---------------------+----------------------------------------------------+ +| locale | The locale name to use for formatting number, | +| | ie. "fr_FR". | ++---------------------+----------------------------------------------------+ +| before | Text to display before the rendered number. | ++---------------------+----------------------------------------------------+ +| after | Text to display after the rendered number. | ++---------------------+----------------------------------------------------+ + +Example:: + + // Called as NumberHelper + echo $this->Number->formatDelta('123456.7890', [ + 'places' => 2, + 'before' => '[', + 'after' => ']' + ]); + // Output '[+123,456.79]' + + // Called as Number + echo Number::formatDelta('123456.7890', [ + 'places' => 2, + 'before' => '[', + 'after' => ']' + ]); + // Output '[+123,456.79]' + +.. end-cakenumber + +Configure formatters +==================== + +.. php:method:: config(string $locale, int $type = NumberFormatter::DECIMAL, array $options = []) + +This method allows you to configure formatter defaults which persist across calls +to various methods. + +Example:: + + Number::config('en_IN', \NumberFormatter::CURRENCY, [ + 'pattern' => '#,##,##0' + ]); + +.. meta:: + :title lang=en: NumberHelper + :description lang=en: The Number Helper contains convenience methods that enable display numbers in common formats in your views. + :keywords lang=en: number helper,currency,number format,number precision,format file size,format numbers diff --git a/tl/core-libraries/registry-objects.rst b/tl/core-libraries/registry-objects.rst new file mode 100644 index 0000000000000000000000000000000000000000..10d02981588d1db98cdc9dbf50264982a8e45a0d --- /dev/null +++ b/tl/core-libraries/registry-objects.rst @@ -0,0 +1,57 @@ +Registry Objects +################ + +The registry classes provide a simple way to create and retrieve loaded +instances of a given object type. There are registry classes for Components, +Helpers, Tasks, and Behaviors. + +While the examples below will use Components, the same behavior can be expected +for Helpers, Behaviors, and Tasks in addition to Components. + +Loading Objects +=============== + +Objects can be loaded on-the-fly using add() +Example:: + + $this->loadComponent('Acl.Acl'); + $this->addHelper('Flash') + +This will result in the ``Acl`` property and ``Flash`` helper being loaded. +Configuration can also be set on-the-fly. Example:: + + $this->loadComponent('Cookie', ['name' => 'sweet']); + +Any keys and values provided will be passed to the Component's constructor. The +one exception to this rule is ``className``. Classname is a special key that is +used to alias objects in a registry. This allows you to have component names +that do not reflect the classnames, which can be helpful when extending core +components:: + + $this->Auth = $this->loadComponent('Auth', ['className' => 'MyCustomAuth']); + $this->Auth->user(); // Actually using MyCustomAuth::user(); + +Triggering Callbacks +==================== + +Callbacks are not provided by registry objects. You should use the +:doc:`events system ` to dispatch any events/callbacks +for your application. + +Disabling Callbacks +=================== + +In previous versions, collection objects provided a ``disable()`` method to disable +objects from receiving callbacks. You should use the features in the events system to +accomplish this now. For example, you could disable component callbacks in the +following way:: + + // Remove Auth from callbacks. + $this->eventManager()->off($this->Auth); + + // Re-enable Auth for callbacks. + $this->eventManager()->on($this->Auth); + +.. meta:: + :title lang=en: Object Registry + :keywords lang=en: array name,loading components,several different kinds,unified api,loading objects,component names,special key,core components,callbacks,prg,callback,alias,fatal error,collections,memory,priority,priorities diff --git a/tl/core-libraries/security.rst b/tl/core-libraries/security.rst new file mode 100644 index 0000000000000000000000000000000000000000..e3bd92607c5c331670077d20c0d7e685f69c3d6a --- /dev/null +++ b/tl/core-libraries/security.rst @@ -0,0 +1,127 @@ +Security +######## + +.. php:namespace:: Cake\Utility + +.. php:class:: Security + +The `security library +`_ +handles basic security measures such as providing methods for +hashing and encrypting data. + +Encrypting and Decrypting Data +============================== + +.. php:staticmethod:: encrypt($text, $key, $hmacSalt = null) +.. php:staticmethod:: decrypt($cipher, $key, $hmacSalt = null) + +Encrypt ``$text`` using AES-256. The ``$key`` should be a value with a +lots of variance in the data much like a good password. The returned result +will be the encrypted value with an HMAC checksum. + +This method will use either `openssl `_ or `mcrypt +`_ based on what is available on your system. Data +encrypted in one implementation is portable to the other. + +.. warning:: + The `mcrypt `_ extension has been deprecated in + PHP7.1 + +This method should **never** be used to store passwords. Instead you should use +the one way hashing methods provided by +:php:meth:`~Cake\\Utility\\Security::hash()`. An example use would be:: + + // Assuming key is stored somewhere it can be re-used for + // decryption later. + $key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; + $result = Security::encrypt($value, $key); + +If you do not supply an HMAC salt, the ``Security.salt`` value will be used. +Encrypted values can be decrypted using +:php:meth:`Cake\\Utility\\Security::decrypt()`. + +Decrypt a previously encrypted value. The ``$key`` and ``$hmacSalt`` +parameters must match the values used to encrypt or decryption will fail. An +example use would be:: + + // Assuming the key is stored somewhere it can be re-used for + // Decryption later. + $key = 'wt1U5MACWJFTXGenFoZoiLwQGrLgdbHA'; + + $cipher = $user->secrets; + $result = Security::decrypt($cipher, $key); + +If the value cannot be decrypted due to changes in the key or HMAC salt +``false`` will be returned. + +.. _force-mcrypt: + +Choosing a Specific Crypto Implementation +----------------------------------------- + +If you are upgrading an application from CakePHP 2.x, data encrypted in 2.x is +not compatible with openssl. This is because the encrypted data is not fully AES +compliant. If you don't want to go through the trouble of re-encrypting your +data, you can force CakePHP to use ``mcrypt`` using the ``engine()`` method:: + + // In config/bootstrap.php + use Cake\Utility\Crypto\Mcrypt; + + Security::engine(new Mcrypt()); + +The above will allow you to seamlessly read data from older versions of CakePHP, +and encrypt new data to be compatible with OpenSSL. + +Hashing Data +============ + +.. php:staticmethod:: hash( $string, $type = NULL, $salt = false ) + +Create a hash from string using given method. Fallback on next +available method. If ``$salt`` is set to ``true``, the application's salt +value will be used:: + + // Using the application's salt value + $sha1 = Security::hash('CakePHP Framework', 'sha1', true); + + // Using a custom salt value + $sha1 = Security::hash('CakePHP Framework', 'sha1', 'my-salt'); + + // Using the default hash algorithm + $hash = Security::hash('CakePHP Framework'); + +The ``hash()`` method supports the following hashing strategies: + +- md5 +- sha1 +- sha256 + +And any other hash algorithmn that PHP's ``hash()`` function supports. + +.. warning:: + + You should not be using ``hash()`` for passwords in new applications. + Instead you should use the ``DefaultPasswordHasher`` class which uses bcrypt + by default. + +Getting Secure Random Data +========================== + +.. php:staticmethod:: randomBytes($length) + +Get ``$length`` number of bytes from a secure random source. This function draws +data from one of the following sources: + +* PHP's ``random_bytes`` function. +* ``openssl_random_pseudo_bytes`` from the SSL extension. + +If neither source is available a warning will be emitted and an unsafe value +will be used for backwards compatibility reasons. + +.. versionadded:: 3.2.3 + The randomBytes method was added in 3.2.3. + +.. meta:: + :title lang=en: Security + :keywords lang=en: security api,secret password,cipher text,php class,class security,text key,security library,object instance,security measures,basic security,security level,string type,fallback,hash,data security,singleton,inactivity,php encrypt,implementation,php security diff --git a/tl/core-libraries/text.rst b/tl/core-libraries/text.rst new file mode 100644 index 0000000000000000000000000000000000000000..eed85d069a0723d09d895cc72c26e276702aa593 --- /dev/null +++ b/tl/core-libraries/text.rst @@ -0,0 +1,391 @@ +Text +#### + +.. php:namespace:: Cake\Utility + +.. php:class:: Text + +The Text class includes convenience methods for creating and manipulating +strings and is normally accessed statically. Example: +``Text::uuid()``. + +If you need :php:class:`Cake\\View\\Helper\\TextHelper` functionalities outside +of a ``View``, use the ``Text`` class:: + + namespace App\Controller; + + use Cake\Utility\Text; + + class UsersController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth') + }; + + public function afterLogin() + { + $message = $this->Users->find('new_message'); + if (!empty($message)) { + // Notify user of new message + $this->Flash->success(__( + 'You have a new message: {0}', + Text::truncate($message['Message']['body'], 255, ['html' => true]) + )); + } + } + } + +Convert Strings into ASCII +========================== + +.. php:staticmethod:: transliterate($string, $transliteratorId = null) + +Transliterate by default converts all characters in provided string into +equivalent ASCII characters. The method expects UTF-8 encoding. The character +conversion can be controlled using transliteration identifiers which you can +pass using the ``$transliteratorId`` argument or change the default identifier +string using ``Text::setTransliteratorId()``. ICU transliteration identifiers +are basically of form ``:`` and you can specify +multiple conversion pairs separated by ``;``. You can find more info about +transliterator identifiers +`here `_:: + + // apple puree + Text::transliterate('apple purée'); + + // Ubermensch (only latin characters are transliterated) + Text::transliterate('Übérmensch', 'Latin-ASCII;'); + +Creating URL Safe Strings +========================= + +.. php:staticmethod:: slug($string, $options = []) + +Slug transliterates all characters into ASCII versions and converting unmatched +characters and spaces to dashes. The slug method expects UTF-8 encoding. + +You can provide an array of options that controls slug. ``$options`` can also be +a string in which case it will be used as replacement string. The supported +options are: + +* ``replacement`` Replacement string, defaults to '-'. +* ``transliteratorId`` A valid tranliterator id string. If default ``null`` + ``Text::$_defaultTransliteratorId`` to be used. + If ``false`` no transliteration will be done, only non words will be removed. +* ``preserve`` Specific non-word character to preserve. Defaults to ``null``. + For e.g. this option can be set to '.' to generate clean file names:: + + // apple-puree + Text::slug('apple purée'); + + // apple_puree + Text::slug('apple purée', '_'); + + // foo-bar.tar.gz + Text::slug('foo bar.tar.gz', ['preserve' => '.']); + +Generating UUIDs +================ + +.. php:staticmethod:: uuid() + +The UUID method is used to generate unique identifiers as per :rfc:`4122`. The +UUID is a 128-bit string in the format of +``485fc381-e790-47a3-9794-1337c0a8fe68``. :: + + Text::uuid(); // 485fc381-e790-47a3-9794-1337c0a8fe68 + +Simple String Parsing +===================== + +.. php:staticmethod:: tokenize($data, $separator = ',', $leftBound = '(', $rightBound = ')') + +Tokenizes a string using ``$separator``, ignoring any instance of ``$separator`` +that appears between ``$leftBound`` and ``$rightBound``. + +This method can be useful when splitting up data that has regular formatting +such as tag lists:: + + $data = "cakephp 'great framework' php"; + $result = Text::tokenize($data, ' ', "'", "'"); + // Result contains + ['cakephp', "'great framework'", 'php']; + +.. php:method:: parseFileSize(string $size, $default) + +This method unformats a number from a human-readable byte size to an integer +number of bytes:: + + $int = Text::parseFileSize('2GB'); + +Formatting Strings +================== + +.. php:staticmethod:: insert($string, $data, $options = []) + +The insert method is used to create string templates and to allow for key/value +replacements:: + + Text::insert( + 'My name is :name and I am :age years old.', + ['name' => 'Bob', 'age' => '65'] + ); + // Returns: "My name is Bob and I am 65 years old." + +.. php:staticmethod:: cleanInsert($string, $options = []) + +Cleans up a ``Text::insert`` formatted string with given ``$options`` depending +on the 'clean' key in ``$options``. The default method used is text but html is +also available. The goal of this function is to replace all whitespace and +unneeded markup around placeholders that did not get replaced by +``Text::insert``. + +You can use the following options in the options array:: + + $options = [ + 'clean' => [ + 'method' => 'text', // or html + ], + 'before' => '', + 'after' => '' + ]; + +Wrapping Text +============= + +.. php:staticmethod:: wrap($text, $options = []) + +Wraps a block of text to a set width and indents blocks as well. +Can intelligently wrap text so words are not sliced across lines:: + + $text = 'This is the song that never ends.'; + $result = Text::wrap($text, 22); + + // Returns + This is the song that + never ends. + +You can provide an array of options that control how wrapping is done. The +supported options are: + +* ``width`` The width to wrap to. Defaults to 72. +* ``wordWrap`` Whether or not to wrap whole words. Defaults to ``true``. +* ``indent`` The character to indent lines with. Defaults to ''. +* ``indentAt`` The line number to start indenting text. Defaults to 0. + +.. php:staticmethod:: wrapBlock($text, $options = []) + +If you need to ensure that the total width of the generated block won't +exceed a certain length even with internal identation, you need to use +``wrapBlock()`` instead of ``wrap()``. This is particulary useful to generate +text for the console for example. It accepts the same options as ``wrap()``:: + + $text = 'This is the song that never ends. This is the song that never ends.'; + $result = Text::wrapBlock($text, [ + 'width' => 22, + 'indent' => ' → ', + 'indentAt' => 1 + ]); + + // Returns + This is the song that + → never ends. This + → is the song that + → never ends. + +.. start-text + +Highlighting Substrings +======================= + +.. php:method:: highlight(string $haystack, string $needle, array $options = [] ) + +Highlights ``$needle`` in ``$haystack`` using the ``$options['format']`` string +specified or a default string. + +Options: + +- ``format`` string - The piece of HTML with the phrase that will be + highlighted +- ``html`` bool - If ``true``, will ignore any HTML tags, ensuring that only + the correct text is highlighted + +Example:: + + // Called as TextHelper + echo $this->Text->highlight( + $lastSentence, + 'using', + ['format' => '\1'] + ); + + // Called as Text + use Cake\Utility\Text; + + echo Text::highlight( + $lastSentence, + 'using', + ['format' => '\1'] + ); + +Output:: + + Highlights $needle in $haystack using the + $options['format'] string specified or a default string. + +Removing Links +============== + +.. php:method:: stripLinks($text) + +Strips the supplied ``$text`` of any HTML links. + +Truncating Text +=============== + +.. php:method:: truncate(string $text, int $length = 100, array $options) + +If ``$text`` is longer than ``$length``, this method truncates it at ``$length`` +and adds a suffix consisting of ``'ellipsis'``, if defined. If ``'exact'`` is +passed as ``false``, the truncation will occur at the first whitespace after the +point at which ``$length`` is exceeded. If ``'html'`` is passed as ``true``, +HTML tags will be respected and will not be cut off. + +``$options`` is used to pass all extra parameters, and has the following +possible keys by default, all of which are optional:: + + [ + 'ellipsis' => '...', + 'exact' => true, + 'html' => false + ] + +Example:: + + // Called as TextHelper + echo $this->Text->truncate( + 'The killer crept forward and tripped on the rug.', + 22, + [ + 'ellipsis' => '...', + 'exact' => false + ] + ); + + // Called as Text + use Cake\Utility\Text; + + echo Text::truncate( + 'The killer crept forward and tripped on the rug.', + 22, + [ + 'ellipsis' => '...', + 'exact' => false + ] + ); + +Output:: + + The killer crept... + +Truncating the Tail of a String +=============================== + +.. php:method:: tail(string $text, int $length = 100, array $options) + +If ``$text`` is longer than ``$length``, this method removes an initial +substring with length consisting of the difference and prepends a prefix +consisting of ``'ellipsis'``, if defined. If ``'exact'`` is passed as ``false``, +the truncation will occur at the first whitespace prior to the point at which +truncation would otherwise take place. + +``$options`` is used to pass all extra parameters, and has the following +possible keys by default, all of which are optional:: + + [ + 'ellipsis' => '...', + 'exact' => true + ] + +Example:: + + $sampleText = 'I packed my bag and in it I put a PSP, a PS3, a TV, ' . + 'a C# program that can divide by zero, death metal t-shirts' + + // Called as TextHelper + echo $this->Text->tail( + $sampleText, + 70, + [ + 'ellipsis' => '...', + 'exact' => false + ] + ); + + // Called as Text + use Cake\Utility\Text; + + echo Text::tail( + $sampleText, + 70, + [ + 'ellipsis' => '...', + 'exact' => false + ] + ); + +Output:: + + ...a TV, a C# program that can divide by zero, death metal t-shirts + +Extracting an Excerpt +===================== + +.. php:method:: excerpt(string $haystack, string $needle, integer $radius=100, string $ellipsis="...") + +Extracts an excerpt from ``$haystack`` surrounding the ``$needle`` with a number +of characters on each side determined by ``$radius``, and prefix/suffix with +``$ellipsis``. This method is especially handy for search results. The query +string or keywords can be shown within the resulting document. :: + + // Called as TextHelper + echo $this->Text->excerpt($lastParagraph, 'method', 50, '...'); + + // Called as Text + use Cake\Utility\Text; + + echo Text::excerpt($lastParagraph, 'method', 50, '...'); + +Output:: + + ... by $radius, and prefix/suffix with $ellipsis. This method is especially + handy for search results. The query... + +Converting an Array to Sentence Form +==================================== + +.. php:method:: toList(array $list, $and='and', $separator=', ') + +Creates a comma-separated list where the last two items are joined with 'and':: + + $colors = ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet']; + + // Called as TextHelper + echo $this->Text->toList($colors); + + // Called as Text + use Cake\Utility\Text; + + echo Text::toList($colors); + +Output:: + + red, orange, yellow, green, blue, indigo and violet + +.. end-text + +.. meta:: + :title lang=en: Text + :keywords lang=en: slug,transliterate,ascii,array php,array name,string options,data options,result string,class string,string data,string class,placeholders,default method,key value,markup,rfc,replacements,convenience,templates diff --git a/tl/core-libraries/time.rst b/tl/core-libraries/time.rst new file mode 100644 index 0000000000000000000000000000000000000000..c09f656126abac97cba17f7eb42c58f972753521 --- /dev/null +++ b/tl/core-libraries/time.rst @@ -0,0 +1,439 @@ +Date & Time +########### + +.. php:namespace:: Cake\I18n + +.. php:class:: Time + +If you need :php:class:`TimeHelper` functionalities outside of a ``View``, +use the ``Time`` class:: + + use Cake\I18n\Time; + + class UsersController extends AppController + { + + public function initialize() + { + parent::initialize(); + $this->loadComponent('Auth'); + } + + public function afterLogin() + { + $time = new Time($this->Auth->user('date_of_birth')); + if ($time->isToday()) { + // Greet user with a happy birthday message + $this->Flash->success(__('Happy birthday to you...')); + } + } + } + +Under the hood, CakePHP uses `Chronos `_ +to power its ``Time`` utility. Anything you can do with ``Chronos`` and +``DateTime``, you can do with ``Time`` and ``Date``. + +.. note:: + Prior to 3.2.0 CakePHP used `Carbon + `__. + +For more details on Chronos please see `the API documentation +`_. + +.. start-time + +Creating Time Instances +======================= + +There are a few ways to create ``Time`` instances:: + + use Cake\I18n\Time; + + // Create from a string datetime. + $time = Time::createFromFormat( + 'Y-m-d H:i:s', + $datetime, + 'America/New_York' + ); + + // Create from a timestamp + $time = Time::createFromTimestamp($ts); + + // Get the current time. + $time = Time::now(); + + // Or just use 'new' + $time = new Time('2014-01-10 11:11', 'America/New_York'); + + $time = new Time('2 hours ago'); + +The ``Time`` class constructor can take any parameter that the internal ``DateTime`` +PHP class can. When passing a number or numeric string, it will be interpreted +as a UNIX timestamp. + +In test cases you can mock out ``now()`` using ``setTestNow()``:: + + // Fixate time. + $now = new Time('2014-04-12 12:22:30'); + Time::setTestNow($now); + + // Returns '2014-04-12 12:22:30' + $now = Time::now(); + + // Returns '2014-04-12 12:22:30' + $now = Time::parse('now'); + +Manipulation +============ + +Once created, you can manipulate ``Time`` instances using setter methods:: + + $now = Time::now(); + $now->year(2013) + ->month(10) + ->day(31); + +You can also use the methods provided by PHP's built-in ``DateTime`` class:: + + $now->setDate(2013, 10, 31); + +Dates can be modified through subtraction and addition of their components:: + + $now = Time::now(); + $now->subDays(5); + $now->addMonth(1); + + // Using strtotime strings. + $now->modify('+5 days'); + +You can get the internal components of a date by accessing its properties:: + + $now = Time::now(); + echo $now->year; // 2014 + echo $now->month; // 5 + echo $now->day; // 10 + echo $now->timezone; // America/New_York + +It is also allowed to directly assign those properties to modify the date:: + + $time->year = 2015; + $time->timezone = 'Europe/Paris'; + +Formatting +========== + +.. php:staticmethod:: setJsonEncodeFormat($format) + +This method sets the default format used when converting an object to json:: + + Time::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any mutable DateTime + FrozenTime::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any immutable DateTime + Date::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any mutable Date + FrozenDate::setJsonEncodeFormat('yyyy-MM-dd HH:mm:ss'); // For any immutable Date + +.. note:: + This method must be called statically. + +.. php:method:: i18nFormat($format = null, $timezone = null, $locale = null) + +A very common thing to do with ``Time`` instances is to print out formatted +dates. CakePHP makes this a snap:: + + $now = Time::parse('2014-10-31'); + + // Prints a localized datetime stamp. + echo $now; + + // Outputs '10/31/14, 12:00 AM' for the en-US locale + $now->i18nFormat(); + + // Use the full date and time format + $now->i18nFormat(\IntlDateFormatter::FULL); + + // Use full date but short time format + $now->i18nFormat([\IntlDateFormatter::FULL, \IntlDateFormatter::SHORT]); + + // Outputs '2014-10-31 00:00:00' + $now->i18nFormat('yyyy-MM-dd HH:mm:ss'); + +It is possible to specify the desired format for the string to be displayed. +You can either pass `IntlDateFormatter constants +`_ as the first +argument of this function, or pass a full ICU date formatting string as +specified in the following resource: +http://www.icu-project.org/apiref/icu4c/classSimpleDateFormat.html#details. + +You can also format dates with non-gregorian calendars:: + + // Outputs 'Friday, Aban 9, 1393 AP at 12:00:00 AM GMT' + $result = $now->i18nFormat(\IntlDateFormatter::FULL, null, 'en-IR@calendar=persian'); + +The following calendar types are supported: + +* japanese +* buddhist +* chinese +* persian +* indian +* islamic +* hebrew +* coptic +* ethiopic + +.. versionadded:: 3.1 + Non-gregorian calendar support was added in 3.1 + +.. note:: + For constant strings i.e. IntlDateFormatter::FULL Intl uses ICU library + that feeds its data from CLDR (http://cldr.unicode.org/) which version + may vary depending on PHP installation and give different results. + +.. php:method:: nice() + +Print out a predefined 'nice' format:: + + $now = Time::parse('2014-10-31'); + + // Outputs 'Oct 31, 2014 12:00 AM' in en-US + echo $now->nice(); + +You can alter the timezone in which the date is displayed without altering the +``Time`` object itself. This is useful when you store dates in one timezone, but +want to display them in a user's own timezone:: + + $now->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris'); + +Leaving the first parameter as ``null`` will use the default formatting string:: + + $now->i18nFormat(null, 'Europe/Paris'); + +Finally, it is possible to use a different locale for displaying a date:: + + echo $now->i18nFormat(\IntlDateFormatter::FULL, 'Europe/Paris', 'fr-FR'); + + echo $now->nice('Europe/Paris', 'fr-FR'); + +Setting the Default Locale and Format String +-------------------------------------------- + +The default locale in which dates are displayed when using ``nice`` +``i18nFormat`` is taken from the directive +`intl.default_locale `_. +You can, however, modify this default at runtime:: + + Time::setDefaultLocale('es-ES'); // For any mutable DateTime + FrozenTime::setDefaultLocale('es-ES'); // For any immutable DateTime + Date::setDefaultLocale('es-ES'); // For any mutable Date + FrozenDate::setDefaultLocale('es-ES'); // For any immutable Date + +From now on, datetimes will be displayed in the Spanish preferred format unless +a different locale is specified directly in the formatting method. + +Likewise, it is possible to alter the default formatting string to be used for +``i18nFormat``:: + + Time::setToStringFormat(\IntlDateFormatter::SHORT); // For any mutable DateTime + FrozenTime::setToStringFormat(\IntlDateFormatter::SHORT); // For any immutable DateTime + Date::setToStringFormat(\IntlDateFormatter::SHORT); // For any mutable Date + FrozenDate::setToStringFormat(\IntlDateFormatter::SHORT); // For any immutable Date + + // The same method exists on Date, FrozenDate and FrozenTime + Time::setToStringFormat([ + \IntlDateFormatter::FULL, + \IntlDateFormatter::SHORT + ]); + + // The same method exists on Date, FrozenDate and FrozenTime + Time::setToStringFormat('yyyy-MM-dd HH:mm:ss'); + +It is recommended to always use the constants instead of directly passing a date +format string. + +Formatting Relative Times +------------------------- + +.. php:method:: timeAgoInWords(array $options = []) + +Often it is useful to print times relative to the present:: + + $now = new Time('Aug 22, 2011'); + echo $now->timeAgoInWords( + ['format' => 'MMM d, YYY', 'end' => '+1 year'] + ); + // On Nov 10th, 2011 this would display: 2 months, 2 weeks, 6 days ago + +The ``end`` option lets you define at which point after which relative times +should be formatted using the ``format`` option. The ``accuracy`` option lets +us control what level of detail should be used for each interval range:: + + // If $timestamp is 1 month, 1 week, 5 days and 6 hours ago + echo $timestamp->timeAgoInWords([ + 'accuracy' => ['month' => 'month'], + 'end' => '1 year' + ]); + // Outputs '1 month ago' + +By setting ``accuracy`` to a string, you can specify what is the maximum level +of detail you want output:: + + $time = new Time('+23 hours'); + // Outputs 'in about a day' + $result = $time->timeAgoInWords([ + 'accuracy' => 'day' + ]); + +Conversion +========== + +.. php:method:: toQuarter() + +Once created, you can convert ``Time`` instances into timestamps or quarter +values:: + + $time = new Time('2014-06-15'); + $time->toQuarter(); + $time->toUnixString(); + +Comparing With the Present +========================== + +.. php:method:: isYesterday() +.. php:method:: isThisWeek() +.. php:method:: isThisMonth() +.. php:method:: isThisYear() + +You can compare a ``Time`` instance with the present in a variety of ways:: + + $time = new Time('2014-06-15'); + + echo $time->isYesterday(); + echo $time->isThisWeek(); + echo $time->isThisMonth(); + echo $time->isThisYear(); + +Each of the above methods will return ``true``/``false`` based on whether or +not the ``Time`` instance matches the present. + +Comparing With Intervals +======================== + +.. php:method:: isWithinNext($interval) + +You can see if a ``Time`` instance falls within a given range using +``wasWithinLast()`` and ``isWithinNext()``:: + + $time = new Time('2014-06-15'); + + // Within 2 days. + echo $time->isWithinNext(2); + + // Within 2 next weeks. + echo $time->isWithinNext('2 weeks'); + +.. php:method:: wasWithinLast($interval) + +You can also compare a ``Time`` instance within a range in the past:: + + // Within past 2 days. + echo $time->wasWithinLast(2); + + // Within past 2 weeks. + echo $time->wasWithinLast('2 weeks'); + +.. end-time + +Dates +===== + +.. php:class: Date + +.. versionadded:: 3.2 + +The ``Date`` class in CakePHP implements the same API and methods as +:php:class:`Cake\\I18n\\Time` does. The main difference between ``Time`` and +``Date`` is that ``Date`` does not track time components, and is always in UTC. +As an example:: + + use Cake\I18n\Date; + $date = new Date('2015-06-15'); + + $date->modify('+2 hours'); + // Outputs 2015-06-15 00:00:00 + echo $date->format('Y-m-d H:i:s'); + + $date->modify('+36 hours'); + // Outputs 2015-06-15 00:00:00 + echo $date->format('Y-m-d H:i:s'); + +Attempts to modify the timezone on a ``Date`` instance are also ignored:: + + use Cake\I18n\Date; + $date = new Date('2015-06-15'); + $date->setTimezone(new \DateTimeZone('America/New_York')); + + // Outputs UTC + echo $date->format('e'); + +.. _immutable-time: + +Immutable Dates and Times +========================= + +.. php:class:: FrozenTime +.. php:class:: FrozenDate + +CakePHP offers immutable date and time classes that implement the same interface +as their mutable siblings. Immutable objects are useful when you want to prevent +accidental changes to data, or when you want to avoid order based dependency +issues. Take the following code:: + + use Cake\I18n\Time; + $time = new Time('2015-06-15 08:23:45'); + $time->modify('+2 hours'); + + // This method also modifies the $time instance + $this->someOtherFunction($time); + + // Output here is unknown. + echo $time->format('Y-m-d H:i:s'); + +If the method call was re-ordered, or if ``someOtherFunction`` changed the +output could be unexpected. The mutability of our object creates temporal +coupling. If we were to use immutable objects, we could avoid this issue:: + + use Cake\I18n\FrozenTime; + $time = new FrozenTime('2015-06-15 08:23:45'); + $time = $time->modify('+2 hours'); + + // This method's modifications don't change $time + $this->someOtherFunction($time); + + // Output here is known. + echo $time->format('Y-m-d H:i:s'); + +Immutable dates and times are useful in entities as they prevent +accidental modifications, and force changes to be explicit. Using +immutable objects helps the ORM to more easily track changes, and ensure that +date and datetime columns are persisted correctly:: + + // This change will be lost when the article is saved. + $article->updated->modify('+1 hour'); + + // By replacing the time object the property will be saved. + $article->updated = $article->updated->modify('+1 hour'); + +Accepting Localized Request Data +================================ + +When creating text inputs that manipulate dates, you'll probably want to accept +and parse localized datetime strings. See the :ref:`parsing-localized-dates`. + +.. meta:: + :title lang=en: Time + :description lang=en: Time class helps you format time and test time. + :keywords lang=en: time,format time,timezone,unix epoch,time strings,time zone offset,utc,gmt + +Supported Timezones +=================== + +CakePHP supports all valid PHP timezones. For a list of supported timezones, `see this page `_. diff --git a/tl/core-libraries/validation.rst b/tl/core-libraries/validation.rst new file mode 100644 index 0000000000000000000000000000000000000000..a38467afeeb7040152b04a812fb115fef3b74388 --- /dev/null +++ b/tl/core-libraries/validation.rst @@ -0,0 +1,555 @@ +Validation +########## + +.. php:namespace:: Cake\Validation + +The validation package in CakePHP provides features to build validators that can +validate arbitrary arrays of data with ease. You can find a `list of available +Validation rules in the API +`__. + +.. _creating-validators: + +Creating Validators +=================== + +.. php:class:: Validator + +Validator objects define the rules that apply to a set of fields. +Validator objects contain a mapping between fields and validation sets. In +turn, the validation sets contain a collection of rules that apply to the field +they are attached to. Creating a validator is simple:: + + use Cake\Validation\Validator; + + $validator = new Validator(); + +Once created, you can start defining sets of rules for the fields you want to +validate:: + + $validator + ->requirePresence('title') + ->notEmpty('title', 'Please fill this field') + ->add('title', [ + 'length' => [ + 'rule' => ['minLength', 10], + 'message' => 'Titles need to be at least 10 characters long', + ] + ]) + ->allowEmpty('published') + ->add('published', 'boolean', [ + 'rule' => 'boolean' + ]) + ->requirePresence('body') + ->add('body', 'length', [ + 'rule' => ['minLength', 50], + 'message' => 'Articles must have a substantial body.' + ]); + +As seen in the example above, validators are built with a fluent interface that +allows you to define rules for each field you want to validate. + +There were a few methods called in the example above, so let's go over the +various features. The ``add()`` method allows you to add new rules to +a validator. You can either add rules individually or in groups as seen above. + +Requiring Field Presence +------------------------ + +The ``requirePresence()`` method requires the field to be present in any +validated array. If the field is absent, validation will fail. The +``requirePresence()`` method has 4 modes: + +* ``true`` The field's presence is always required. +* ``false`` The field's presence is not required. +* ``create`` The field's presence is required when validating a **create** + operation. +* ``update`` The field's presence is required when validating an **update** + operation. + +By default, ``true`` is used. Key presence is checked by using +``array_key_exists()`` so that null values will count as present. You can set +the mode using the second parameter:: + + $validator->requirePresence('author_id', 'create'); + +If you have multiple fields that are required, you can define them as a list:: + + // Define multiple fields for create + $validator->requirePresence(['author_id', 'title'], 'create'); + + // Define multiple fields for mixed modes + $validator->requirePresence([ + 'author_id' => [ + 'mode' => 'create', + 'message' => 'An author is required.', + ], + 'published' => [ + 'mode' => 'update', + 'message' => 'The published state is required.', + ] + ]); + +.. versionadded:: 3.3.0 + ``requirePresence()`` accepts an array of fields as of 3.3.0 + +Allowing Empty Fields +--------------------- + +The ``allowEmpty()`` and ``notEmpty()`` methods allow you to control which +fields are allowed to be 'empty'. By using the ``notEmpty()`` method, the given +field will be marked invalid when it is empty. You can use ``allowEmpty()`` to +allow a field to be empty. Both ``allowEmpty()`` and ``notEmpty()`` support a +mode parameter that allows you to control when a field can or cannot be empty: + +* ``false`` The field is not allowed to be empty. +* ``create`` The field can be empty when validating a **create** + operation. +* ``update`` The field can be empty when validating an **update** + operation. + +The values ``''``, ``null`` and ``[]`` (empty array) will cause validation +errors when fields are not allowed to be empty. When fields are allowed to be +empty, the values ``''``, ``null``, ``false``, ``[]``, ``0``, ``'0'`` are +accepted. + +An example of these methods in action is:: + + $validator->allowEmpty('published') + ->notEmpty('title', 'Title cannot be empty') + ->notEmpty('body', 'Body cannot be empty', 'create') + ->allowEmpty('header_image', 'update'); + +Marking Rules as the Last to Run +-------------------------------- + +When fields have multiple rules, each validation rule will be run even if the +previous one has failed. This allows you to collect as many validation errors as +you can in a single pass. However, if you want to stop execution after +a specific rule has failed, you can set the ``last`` option to ``true``:: + + $validator = new Validator(); + $validator + ->add('body', [ + 'minLength' => [ + 'rule' => ['minLength', 10], + 'last' => true, + 'message' => 'Comments must have a substantial body.' + ], + 'maxLength' => [ + 'rule' => ['maxLength', 250], + 'message' => 'Comments cannot be too long.' + ] + ]); + +If the minLength rule fails in the example above, the maxLength rule will not be +run. + +Validation Methods Less Verbose +------------------------------- + +Since 3.2, the Validator object has a number of new methods that make building +validators less verbose. For example adding validation rules to a username field +can now look like:: + + $validator = new Validator(); + $validator + ->email('username') + ->ascii('username') + ->lengthBetween('username', [4, 8]); + +Adding Validation Providers +--------------------------- + +The ``Validator``, ``ValidationSet`` and ``ValidationRule`` classes do not +provide any validation methods themselves. Validation rules come from +'providers'. You can bind any number of providers to a Validator object. +Validator instances come with a 'default' provider setup automatically. The +default provider is mapped to the :php:class:`~Cake\\Validation\\Validation` +class. This makes it simple to use the methods on that class as validation +rules. When using Validators and the ORM together, additional providers are +configured for the table and entity objects. You can use the ``setProvider()`` +method to add any additional providers your application needs:: + + $validator = new Validator(); + + // Use an object instance. + $validator->setProvider('custom', $myObject); + + // Use a class name. Methods must be static. + $validator->setProvider('custom', 'App\Model\Validation'); + +Validation providers can be objects, or class names. If a class name is used the +methods must be static. To use a provider other than 'default', be sure to set +the ``provider`` key in your rule:: + + // Use a rule from the table provider + $validator->add('title', 'custom', [ + 'rule' => 'customTableMethod', + 'provider' => 'table' + ]); + +If you wish to add a ``provider`` to all ``Validator`` objects that are created +in the future, you can use the ``addDefaultProvider()`` method as follows:: + + use Cake\Validation\Validator; + + // Use an object instance. + Validator::addDefaultProvider('custom', $myObject); + + // Use a class name. Methods must be static. + Validator::addDefaultProvider('custom', 'App\Model\Validation'); + +.. note:: + + DefaultProviders must be added before the ``Validator`` object is created + therefore **config/bootstrap.php** is the best place to set up your + default providers. + +.. versionadded:: 3.5.0 + +You can use the `Localized plugin `_ to +get providers based on countries. With this plugin, you'll be able to validate +model fields, depending on a country, ie:: + + namespace App\Model\Table; + + use Cake\ORM\Table; + use Cake\Validation\Validator; + + class PostsTable extends Table + { + public function validationDefault(Validator $validator) + { + // add the provider to the validator + $validator->setProvider('fr', 'Localized\Validation\FrValidation'); + // use the provider in a field validation rule + $validator->add('phoneField', 'myCustomRuleNameForPhone', [ + 'rule' => 'phone', + 'provider' => 'fr' + ]); + + return $validator; + } + } + +The localized plugin uses the two letter ISO code of the countries for +validation, like en, fr, de. + +There are a few methods that are common to all classes, defined through the +`ValidationInterface interface `_:: + + phone() to check a phone number + postal() to check a postal code + personId() to check a country specific person ID + +Custom Validation Rules +----------------------- + +In addition to using methods coming from providers, you can also use any +callable, including anonymous functions, as validation rules:: + + // Use a global function + $validator->add('title', 'custom', [ + 'rule' => 'validate_title', + 'message' => 'The title is not valid' + ]); + + // Use an array callable that is not in a provider + $validator->add('title', 'custom', [ + 'rule' => [$this, 'method'], + 'message' => 'The title is not valid' + ]); + + // Use a closure + $extra = 'Some additional value needed inside the closure'; + $validator->add('title', 'custom', [ + 'rule' => function ($value, $context) use ($extra) { + // Custom logic that returns true/false + }, + 'message' => 'The title is not valid' + ]); + + // Use a rule from a custom provider + $validator->add('title', 'custom', [ + 'rule' => 'customRule', + 'provider' => 'custom', + 'message' => 'The title is not unique enough' + ]); + +Closures or callable methods will receive 2 arguments when called. The first +will be the value for the field being validated. The second is a context array +containing data related to the validation process: + +- **data**: The original data passed to the validation method, useful if you + plan to create rules comparing values. +- **providers**: The complete list of rule provider objects, useful if you + need to create complex rules by calling multiple providers. +- **newRecord**: Whether the validation call is for a new record or + a preexisting one. + +If you need to pass additional data to your validation methods such as the +current user's id, you can use a custom dynamic provider from your controller. :: + + $this->Examples->validator('default')->provider('passed', [ + 'count' => $countFromController, + 'userid' => $this->Auth->user('id') + ]); + +Then ensure that your validation method has the second context parameter. :: + + public function customValidationMethod($check, array $context) + { + $userid = $context['providers']['passed']['userid']; + } + +Conditional Validation +---------------------- + +When defining validation rules, you can use the ``on`` key to define when +a validation rule should be applied. If left undefined, the rule will always be +applied. Other valid values are ``create`` and ``update``. Using one of these +values will make the rule apply to only create or update operations. + +Additionally, you can provide a callable function that will determine whether or +not a particular rule should be applied:: + + $validator->add('picture', 'file', [ + 'rule' => ['mimeType', ['image/jpeg', 'image/png']], + 'on' => function ($context) { + return !empty($context['data']['show_profile_picture']); + } + ]); + +You can access the other submitted field values using the ``$context['data']`` +array. +The above example will make the rule for 'picture' optional depending on whether +the value for ``show_profile_picture`` is empty. You could also use the +``uploadedFile`` validation rule to create optional file upload inputs:: + + $validator->add('picture', 'file', [ + 'rule' => ['uploadedFile', ['optional' => true]], + ]); + +The ``allowEmpty()``, ``notEmpty()`` and ``requirePresence()`` methods will also +accept a callback function as their last argument. If present, the callback +determines whether or not the rule should be applied. For example, a field is +sometimes allowed to be empty:: + + $validator->allowEmpty('tax', function ($context) { + return !$context['data']['is_taxable']; + }); + +Likewise, a field can be required to be populated when certain conditions are +met:: + + $validator->notEmpty('email_frequency', 'This field is required', function ($context) { + return !empty($context['data']['wants_newsletter']); + }); + +In the above example, the ``email_frequency`` field cannot be left empty if the +the user wants to receive the newsletter. + +Further it's also possible to require a field to be present under certain +conditions only:: + + $validator->requirePresence('full_name', function ($context) { + if (isset($context['data']['action'])) { + return $context['data']['action'] === 'subscribe'; + } + return false; + }); + $validator->requirePresence('email'); + +This would require the ``full_name`` field to be present only in case the user +wants to create a subscription, while the ``email`` field would always be +required, since it would also be needed when canceling a subscription. + +.. versionadded:: 3.1.1 + The callable support for ``requirePresence()`` was added in 3.1.1 + +Nesting Validators +------------------ + +.. versionadded:: 3.0.5 + +When validating :doc:`/core-libraries/form` with nested data, or when working +with models that contain array data types, it is necessary to validate the +nested data you have. CakePHP makes it simple to add validators to specific +attributes. For example, assume you are working with a non-relational database +and need to store an article and its comments:: + + $data = [ + 'title' => 'Best article', + 'comments' => [ + ['comment' => ''] + ] + ]; + +To validate the comments you would use a nested validator:: + + $validator = new Validator(); + $validator->add('title', 'not-blank', ['rule' => 'notBlank']); + + $commentValidator = new Validator(); + $commentValidator->add('comment', 'not-blank', ['rule' => 'notBlank']); + + // Connect the nested validators. + $validator->addNestedMany('comments', $commentValidator); + + // Get all errors including those from nested validators. + $validator->errors($data); + +You can create 1:1 'relationships' with ``addNested()`` and 1:N 'relationships' +with ``addNestedMany()``. With both methods, the nested validator's errors will +contribute to the parent validator's errors and influence the final result. + +.. _reusable-validators: + +Creating Reusable Validators +---------------------------- + +While defining validators inline where they are used makes for good example +code, it doesn't lead to maintainable applications. Instead, you should +create ``Validator`` sub-classes for your reusable validation logic:: + + // In src/Model/Validation/ContactValidator.php + namespace App\Model\Validation; + + use Cake\Validation\Validator; + + class ContactValidator extends Validator + { + public function __construct() + { + parent::__construct(); + // Add validation rules here. + } + } + +Validating Data +=============== + +Now that you've created a validator and added the rules you want to it, you can +start using it to validate data. Validators are able to validate array +data. For example, if you wanted to validate a contact form before creating and +sending an email you could do the following:: + + use Cake\Validation\Validator; + + $validator = new Validator(); + $validator + ->requirePresence('email') + ->add('email', 'validFormat', [ + 'rule' => 'email', + 'message' => 'E-mail must be valid' + ]) + ->requirePresence('name') + ->notEmpty('name', 'We need your name.') + ->requirePresence('comment') + ->notEmpty('comment', 'You need to give a comment.'); + + $errors = $validator->errors($this->request->getData()); + if (empty($errors)) { + // Send an email. + } + +The ``errors()`` method will return a non-empty array when there are validation +failures. The returned array of errors will be structured like:: + + $errors = [ + 'email' => ['E-mail must be valid'] + ]; + +If you have multiple errors on a single field, an array of error messages will +be returned per field. By default the ``errors()`` method applies rules for +the 'create' mode. If you'd like to apply 'update' rules you can do the +following:: + + $errors = $validator->errors($this->request->getData(), false); + if (empty($errors)) { + // Send an email. + } + +.. note:: + + If you need to validate entities you should use methods like + :php:meth:`~Cake\\ORM\\Table::newEntity()`, + :php:meth:`~Cake\\ORM\\Table::newEntities()`, + :php:meth:`~Cake\\ORM\\Table::patchEntity()`, + :php:meth:`~Cake\\ORM\\Table::patchEntities()` or + :php:meth:`~Cake\\ORM\\Table::save()` as they are designed for that. + +Validating Entities +=================== + +While entities are validated as they are saved, you may also want to validate +entities before attempting to do any saving. Validating entities before +saving is done automatically when using the ``newEntity()``, ``newEntities()``, +``patchEntity()`` or ``patchEntities()``:: + + // In the ArticlesController class + $article = $this->Articles->newEntity($this->request->getData()); + if ($article->errors()) { + // Do work to show error messages. + } + +Similarly, when you need to pre-validate multiple entities at a time, you can +use the ``newEntities()`` method:: + + // In the ArticlesController class + $entities = $this->Articles->newEntities($this->request->getData()); + foreach ($entities as $entity) { + if (!$entity->errors()) { + $this->Articles->save($entity); + } + } + +The ``newEntity()``, ``patchEntity()``, ``newEntities()`` and ``patchEntities()`` +methods allow you to specify which associations are validated, and which +validation sets to apply using the ``options`` parameter:: + + $valid = $this->Articles->newEntity($article, [ + 'associated' => [ + 'Comments' => [ + 'associated' => ['User'], + 'validate' => 'special', + ] + ] + ]); + +Validation is commonly used for user-facing forms or interfaces, and thus it is +not limited to only validating columns in the table schema. However, +maintaining integrity of data regardless where it came from is important. To +solve this problem CakePHP offers a second level of validation which is called +"application rules". You can read more about them in the +:ref:`Applying Application Rules ` section. + +Core Validation Rules +===================== + +CakePHP provides a basic suite of validation methods in the ``Validation`` +class. The Validation class contains a variety of static methods that provide +validators for several common validation situations. + +The `API documentation +`_ for the +``Validation`` class provides a good list of the validation rules that are +available, and their basic usage. + +Some of the validation methods accept additional parameters to define boundary +conditions or valid options. You can provide these boundary conditions and +options as follows:: + + $validator = new Validator(); + $validator + ->add('title', 'minLength', [ + 'rule' => ['minLength', 10] + ]) + ->add('rating', 'validValue', [ + 'rule' => ['range', 1, 5] + ]); + +Core rules that take additional parameters should have an array for the +``rule`` key that contains the rule as the first element, and the additional +parameters as the remaining parameters. diff --git a/tl/core-libraries/xml.rst b/tl/core-libraries/xml.rst new file mode 100644 index 0000000000000000000000000000000000000000..ea520bba05669b67bbd69679b799b02636df27a6 --- /dev/null +++ b/tl/core-libraries/xml.rst @@ -0,0 +1,178 @@ +Xml +### + +.. php:namespace:: Cake\Utility + +.. php:class:: Xml + +The Xml class allows you to transform arrays into SimpleXMLElement or +DOMDocument objects, and back into arrays again. + +Importing Data to Xml Class +=========================== + +.. php:staticmethod:: build($input, array $options = []) + +You can load XML-ish data using ``Xml::build()``. Depending on your +``$options`` parameter, this method will return a SimpleXMLElement (default) +or DOMDocument object. You can use ``Xml::build()`` to build XML +objects from a variety of sources. For example, you can load XML from +strings:: + + $text = ' + + 1 + Best post + ... + '; + $xml = Xml::build($text); + +You can also build Xml objects from local files:: + + // Local file + $xml = Xml::build('/home/awesome/unicorns.xml'); + +You can also build Xml objects using an array:: + + $data = [ + 'post' => [ + 'id' => 1, + 'title' => 'Best post', + 'body' => ' ... ' + ] + ]; + $xml = Xml::build($data); + +If your input is invalid, the Xml class will throw an exception:: + + $xmlString = 'What is XML?'; + try { + $xmlObject = Xml::build($xmlString); // Here will throw an exception + } catch (\Cake\Utility\Exception\XmlException $e) { + throw new InternalErrorException(); + } + +.. note:: + + `DOMDocument `_ and + `SimpleXML `_ implement different API's. + Be sure to use the correct methods on the object you request from Xml. + +Transforming a XML String in Array +================================== + +.. php:staticmethod:: toArray($obj); + +Converting XML strings into arrays is simple with the Xml class as well. By +default you'll get a SimpleXml object back:: + + $xmlString = 'value'; + $xmlArray = Xml::toArray(Xml::build($xmlString)); + +If your XML is invalid a ``Cake\Utility\Exception\XmlException`` will be raised. + +Transforming an Array into a String of XML +========================================== + +:: + + $xmlArray = ['root' => ['child' => 'value']]; + // You can use Xml::build() too. + $xmlObject = Xml::fromArray($xmlArray, ['format' => 'tags']); + $xmlString = $xmlObject->asXML(); + +Your array must have only one element in the "top level" and it can not be +numeric. If the array is not in this format, Xml will throw an exception. +Examples of invalid arrays:: + + // Top level with numeric key + [ + ['key' => 'value'] + ]; + + // Multiple keys in top level + [ + 'key1' => 'first value', + 'key2' => 'other value' + ]; + +By default array values will be output as XML tags. If you want to define +attributes or text values you can prefix the keys that are supposed to be +attributes with ``@``. For value text, use ``@`` as the key:: + + $xmlArray = [ + 'project' => [ + '@id' => 1, + 'name' => 'Name of project, as tag', + '@' => 'Value of project' + ] + ]; + $xmlObject = Xml::fromArray($xmlArray); + $xmlString = $xmlObject->asXML(); + +The content of ``$xmlString`` will be:: + + + Value of projectName of project, as tag + +Using Namespaces +---------------- + +To use XML Namespaces, create a key in your array with the name ``xmlns:`` +in a generic namespace or input the prefix ``xmlns:`` in a custom namespace. See +the samples:: + + $xmlArray = [ + 'root' => [ + 'xmlns:' => 'https://cakephp.org', + 'child' => 'value' + ] + ]; + $xml1 = Xml::fromArray($xmlArray); + + $xmlArray( + 'root' => [ + 'tag' => [ + 'xmlns:pref' => 'https://cakephp.org', + 'pref:item' => [ + 'item 1', + 'item 2' + ] + ] + ] + ); + $xml2 = Xml::fromArray($xmlArray); + +The value of ``$xml1`` and ``$xml2`` will be, respectively:: + + + value + + + item 1item 2 + +Creating a Child +---------------- + +After you have created your XML document, you just use the native interfaces for +your document type to add, remove, or manipulate child nodes:: + + // Using SimpleXML + $myXmlOriginal = 'value'; + $xml = Xml::build($myXmlOriginal); + $xml->root->addChild('young', 'new value'); + + // Using DOMDocument + $myXmlOriginal = 'value'; + $xml = Xml::build($myXmlOriginal, ['return' => 'domdocument']); + $child = $xml->createElement('young', 'new value'); + $xml->firstChild->appendChild($child); + +.. tip:: + + After manipulating your XML using SimpleXMLElement or DomDocument you can + use ``Xml::toArray()`` without a problem. + +.. meta:: + :title lang=en: Xml + :keywords lang=en: array php,xml class,xml objects,post xml,xml object,string url,string data,xml parser,php 5,bakery,constructor,php xml,cakephp,php file,unicorns,meth diff --git a/tl/debug-kit.rst b/tl/debug-kit.rst new file mode 100644 index 0000000000000000000000000000000000000000..a28b701466b9140ff2af2d64acc764733cf3b174 --- /dev/null +++ b/tl/debug-kit.rst @@ -0,0 +1,232 @@ +Debug Kit +######### + +DebugKit provides a debugging toolbar and enhanced debugging tools for CakePHP +applications. It lets you quickly see configuration data, log messages, SQL +queries, and timing data for your application. + +.. warning:: + + DebugKit is only intended for use in single-user local development + environments. You should avoid using DebugKit in shared development + environments, staging environments, or any environment where you need to + keep configuration data and environment variables hidden. + +Installation +============ + +By default DebugKit is installed with the default application skeleton. If +you've removed it and want to re-install it, you can do so by running the +following from your application's ROOT directory (where composer.json file is +located):: + + php composer.phar require --dev cakephp/debug_kit "~3.0" + +Then, you need to enable the plugin by executing the following line:: + + bin/cake plugin load DebugKit + +DebugKit Storage +================ + +By default, DebugKit uses a small SQLite database in your application's ``/tmp`` +directory to store the panel data. If you'd like DebugKit to store its data +elsewhere, you should define a ``debug_kit`` connection. + +Database Configuration +---------------------- + +By default DebugKit will store panel data into a SQLite database in your +application's ``tmp`` directory. If you cannot install pdo_sqlite, you can +configure DebugKit to use a different database by defining a ``debug_kit`` +connection in your **config/app.php** file. For example:: + + /** + * The debug_kit connection stores DebugKit meta-data. + */ + 'debug_kit' => [ + 'className' => 'Cake\Database\Connection', + 'driver' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'localhost', + //'port' => 'nonstandard_port_number', + 'username' => 'dbusername', // Your DB username here + 'password' => 'dbpassword', // Your DB password here + 'database' => 'debug_kit', + 'encoding' => 'utf8', + 'timezone' => 'UTC', + 'cacheMetadata' => true, + 'quoteIdentifiers' => false, + //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'], + ], + +Toolbar Usage +============= + +The DebugKit Toolbar is comprised of several panels, which are shown by clicking +the CakePHP icon in the bottom right-hand corner of your browser window. Once +the toolbar is open, you should see a series of buttons. Each of these buttons +expands into a panel of related information. + +Each panel lets you look at a different aspect of your application: + +* **Cache** See cache usage during a request and clear caches. +* **Environment** Display environment variables related to PHP + CakePHP. +* **History** Displays a list of previous requests, and allows you to load + and view toolbar data from previous requests. +* **Include** View the included files grouped by type. +* **Log** Display any entries made to the log files this request. +* **Packages** Display the list of packages dependencies with their actual + version and allow you to check for outdated packages. +* **Mail** Display all emails sent during the request and allow to preview + emails during development without sending them. +* **Request** Displays information about the current request, GET, POST, Cake + Parameters, Current Route information and Cookies. +* **Session** Display the information currently in the Session. +* **Sql Logs** Displays SQL logs for each database connection. +* **Timer** Display any timers that were set during the request with + ``DebugKit\DebugTimer``, and memory usage collected with + ``DebugKit\DebugMemory``. +* **Variables** Display View variables set in controller. + +Typically, a panel handles the collection and display of a single type +of information such as Logs or Request information. You can choose to view +panels from the toolbar or add your own custom panels. + +Using the History Panel +======================= + +The history panel is one of the most frequently misunderstood features of +DebugKit. It provides a way to view toolbar data from previous requests, +including errors and redirects. + +.. figure:: /_static/img/debug-kit/history-panel.png + :alt: Screenshot of the history panel in debug kit. + +As you can see, the panel contains a list of requests. On the left you can see +a dot marking the active request. Clicking any request data will load the panel +data for that request. When historical data is loaded the panel titles will +transition to indicate that alternative data has been loaded. + +.. only:: html or epub + + .. figure:: /_static/img/debug-kit/history-panel-use.gif + :alt: Video of history panel in action. + +Using The Mail Panel +==================== + +The mail panel allow you to track all emails sent during a request. + +.. only:: html or epub + + .. figure:: /_static/img/debug-kit/mail-panel.gif + :alt: Video of Mail panel in action. + +The mailer preview allows you to easily check emails during development. + +.. only:: html or epub + + .. figure:: /_static/img/debug-kit/mail-previewer.gif + :alt: Video of Mail panel in action. + +Developing Your Own Panels +========================== + +You can create your own custom panels for DebugKit to help in debugging your +applications. + +Creating a Panel Class +---------------------- + +Panel Classes simply need to be placed in the **src/Panel** directory. The +filename should match the classname, so the class ``MyCustomPanel`` would be +expected to have a filename of **src/Panel/MyCustomPanel.php**:: + + namespace App\Panel; + + use DebugKit\DebugPanel; + + /** + * My Custom Panel + */ + class MyCustomPanel extends DebugPanel + { + ... + } + +Notice that custom panels are required to extend the ``DebugPanel`` class. + +Callbacks +--------- + +By default Panel objects have two callbacks, allowing them to hook into the +current request. Panels subscribe to the ``Controller.initialize`` and +``Controller.shutdown`` events. If your panel needs to subscribe to additional +events, you can use the ``implementedEvents()`` method to define all of the events +your panel is interested in. + +You should refer to the built-in panels for some examples on how you can build +panels. + +Panel Elements +-------------- + +Each Panel is expected to have a view element that renders the content from the +panel. The element name must be the underscored inflection of the class name. +For example ``SessionPanel`` has an element named **session_panel.ctp**, and +SqllogPanel has an element named **sqllog_panel.ctp**. These elements should be +located in the root of your **src/Template/Element** directory. + +Custom Titles and Elements +-------------------------- + +Panels should pick up their title and element name by convention. However, if +you need to choose a custom element name or title, you can define methods to +customize your panel's behavior: + +- ``title()`` - Configure the title that is displayed in the toolbar. +- ``elementName()`` - Configure which element should be used for a given panel. + +Panel Hook Methods +------------------ + +You can also implement the following hook methods to customize how your panel +behaves and appears: + +* ``shutdown(Event $event)`` This method typically collects and prepares the + data for the panel. Data is generally stored in ``$this->_data``. +* ``summary()`` Can return a string of summary data to be displayed in the + toolbar even when a panel is collapsed. Often this is a counter, or short + summary information. +* ``data()`` Returns the panel's data to be used as element context. This hook + method lets you further manipulate the data collected in the ``shutdown()`` + method. This method **must** return data that can be serialized. + +Panels in Other Plugins +----------------------- + +Panels provided by :doc:`/plugins` work almost entirely the same as other +plugins, with one minor difference: You must set ``public $plugin`` to be the +name of the plugin directory, so that the panel's Elements can be located at +render time:: + + namespace MyPlugin\Panel; + + use DebugKit\DebugPanel; + + class MyCustomPanel extends DebugPanel + { + public $plugin = 'MyPlugin'; + ... + } + +To use a plugin or app panel, update your application's DebugKit configuration +to include the panel:: + + // in config/bootstrap.php + Configure::write('DebugKit.panels', ['App', 'MyPlugin.MyCustom']); + Plugin::load('DebugKit', ['bootstrap' => true]); + +The above would load all the default panels as well as the ``AppPanel``, and +``MyCustomPanel`` panel from ``MyPlugin``. diff --git a/tl/deployment.rst b/tl/deployment.rst new file mode 100644 index 0000000000000000000000000000000000000000..f0bef2b4fbb1b406f07bc7ef66d50fff7cc990c8 --- /dev/null +++ b/tl/deployment.rst @@ -0,0 +1,125 @@ +Deployment +########## + +Once your application is complete, or even before that you'll want to deploy it. +There are a few things you should do when deploying a CakePHP application. + +Moving files +============ + +You are encouraged to create a git commit and pull or clone that commit or +repository on your server and run ``composer install``. +While this requires some knowledge about git and an existing install of ``git`` +and ``composer`` this process will take care about library dependencies and file +and folder permissions. + +Be aware that when deploying via FTP you will at least have to fix file and +folder permissions. + +You can also use this deployment technique to setup a staging- or demo-server +(pre-production) and keep it in sync with your dev box. + +Adjust config/app.php +===================== + +Adjusting app.php, specifically the value of ``debug`` is extremely important. +Turning debug = ``false`` disables a number of development features that should +never be exposed to the Internet at large. Disabling debug changes the following +types of things: + +* Debug messages, created with :php:func:`pr()`, :php:func:`debug()` and :php:func:`dd()` are + disabled. +* Core CakePHP caches are by default flushed every year (about 365 days), instead of every + 10 seconds as in development. +* Error views are less informative, and give generic error messages instead. +* PHP Errors are not displayed. +* Exception stack traces are disabled. + +In addition to the above, many plugins and application extensions use ``debug`` +to modify their behavior. + +You can check against an environment variable to set the debug level dynamically +between environments. This will avoid deploying an application with debug +``true`` and also save yourself from having to change the debug level each time +before deploying to a production environment. + +For example, you can set an environment variable in your Apache configuration:: + + SetEnv CAKEPHP_DEBUG 1 + +And then you can set the debug level dynamically in **app.php**:: + + $debug = (bool)getenv('CAKEPHP_DEBUG'); + + return [ + 'debug' => $debug, + ..... + ]; + +Check Your Security +=================== + +If you're throwing your application out into the wild, it's a good idea to make +sure it doesn't have any obvious leaks: + +* Ensure you are using the :doc:`/controllers/components/csrf` component. +* You may want to enable the :doc:`/controllers/components/security` component. + It can help prevent several types of form tampering and reduce the possibility + of mass-assignment issues. +* Ensure your models have the correct :doc:`/core-libraries/validation` rules + enabled. +* Check that only your ``webroot`` directory is publicly visible, and that your + secrets (such as your app salt, and any security keys) are private and unique + as well. + +Set Document Root +================= + +Setting the document root correctly on your application is an important step to +keeping your code secure and your application safer. CakePHP applications +should have the document root set to the application's ``webroot``. This +makes the application and configuration files inaccessible through a URL. +Setting the document root is different for different webservers. See the +:ref:`url-rewriting` documentation for webserver specific +information. + +In all cases you will want to set the virtual host/domain's document to be +``webroot/``. This removes the possibility of files outside of the webroot +directory being executed. + +.. _symlink-assets: + +Improve Your Application's Performance +====================================== + +Class loading can take a big share of your application's processing time. +In order to avoid this problem, it is recommended that you run this command in +your production server once the application is deployed:: + + php composer.phar dumpautoload -o + +Since handling static assets, such as images, JavaScript and CSS files of +plugins, through the ``Dispatcher`` is incredibly inefficient, it is strongly +recommended to symlink them for production. This can be done by using +the ``plugin`` shell:: + + bin/cake plugin assets symlink + +The above command will symlink the ``webroot`` directory of all loaded plugins +to appropriate path in the app's ``webroot`` directory. + +If your filesystem doesn't allow creating symlinks the directories will be +copied instead of being symlinked. You can also explicitly copy the directories +using:: + + bin/cake plugin assets copy + +Deploying an update +=================== + +After deployment of an update you might also want to run ``bin/cake orm_cache +clear``, part of the :doc:`/console-and-shells/orm-cache` shell. + +.. meta:: + :title lang=en: Deployment + :keywords lang=en: stack traces,application extensions,set document,installation documentation,development features,generic error,document root,func,debug,caches,error messages,configuration files,webroot,deployment,cakephp,applications diff --git a/tl/development/configuration.rst b/tl/development/configuration.rst new file mode 100644 index 0000000000000000000000000000000000000000..9fe92b1612ed6f1890926154ed8d67d210e36344 --- /dev/null +++ b/tl/development/configuration.rst @@ -0,0 +1,580 @@ +Configuration +############# + +While conventions remove the need to configure all of CakePHP, you'll still need +to configure a few things like your database credentials. + +Additionally, there are optional configuration options that allow you to swap +out default values & implementations with ones tailored to your application. + +.. index:: app.php, app.php.default + +.. index:: configuration + +Configuring your Application +============================ + +Configuration is generally stored in either PHP or INI files, and loaded during +the application bootstrap. CakePHP comes with one configuration file by default, +but if required you can add additional configuration files and load them in +your application's bootstrap code. :php:class:`Cake\\Core\\Configure` is used +for global configuration, and classes like ``Cache`` provide ``config()`` +methods to make configuration simple and transparent. + +Loading Additional Configuration Files +-------------------------------------- + +If your application has many configuration options it can be helpful to split +configuration into multiple files. After creating each of the files in your +**config/** directory you can load them in **bootstrap.php**:: + + use Cake\Core\Configure; + use Cake\Core\Configure\Engine\PhpConfig; + + Configure::config('default', new PhpConfig()); + Configure::load('app', 'default', false); + Configure::load('other_config', 'default'); + +You can also use additional configuration files to provide environment specific +overrides. Each file loaded after **app.php** can redefine previously declared +values allowing you to customize configuration for development or staging +environments. + +General Configuration +--------------------- + +Below is a description of the variables and how they affect your CakePHP +application. + +debug + Changes CakePHP debugging output. ``false`` = Production mode. No error + messages, errors, or warnings shown. ``true`` = Errors and warnings shown. +App.namespace + The namespace to find app classes under. + + .. note:: + + When changing the namespace in your configuration, you will also + need to update your **composer.json** file to use this namespace + as well. Additionally, create a new autoloader by running + ``php composer.phar dumpautoload``. + +.. _core-configuration-baseurl: + +App.baseUrl + Un-comment this definition if you **don’t** plan to use Apache’s + mod\_rewrite with CakePHP. Don’t forget to remove your .htaccess + files too. +App.base + The base directory the app resides in. If ``false`` this + will be auto detected. If not ``false``, ensure your string starts + with a `/` and does NOT end with a `/`. E.g., `/basedir` is a valid + App.base. Otherwise, the AuthComponent will not work properly. +App.encoding + Define what encoding your application uses. This encoding + is used to generate the charset in the layout, and encode entities. + It should match the encoding values specified for your database. +App.webroot + The webroot directory. +App.wwwRoot + The file path to webroot. +App.fullBaseUrl + The fully qualified domain name (including protocol) to your application's + root. This is used when generating absolute URLs. By default this value + is generated using the $_SERVER environment. However, you should define it + manually to optimize performance or if you are concerned about people + manipulating the ``Host`` header. + In a CLI context (from shells) the `fullBaseUrl` cannot be read from $_SERVER, + as there is no webserver involved. You do need to specify it yourself if + you do need to generate URLs from a shell (e.g. when sending emails). +App.imageBaseUrl + Web path to the public images directory under webroot. If you are using + a :term:`CDN` you should set this value to the CDN's location. +App.cssBaseUrl + Web path to the public css directory under webroot. If you are using + a :term:`CDN` you should set this value to the CDN's location. +App.jsBaseUrl + Web path to the public js directory under webroot. If you are using + a :term:`CDN` you should set this value to the CDN's location. +App.paths + Configure paths for non class based resources. Supports the + ``plugins``, ``templates``, ``locales`` subkeys, which allow the definition + of paths for plugins, view templates and locale files respectively. +Security.salt + A random string used in hashing. This value is also used as the + HMAC salt when doing symetric encryption. +Asset.timestamp + Appends a timestamp which is last modified time of the particular + file at the end of asset files URLs (CSS, JavaScript, Image) when + using proper helpers. + Valid values: + + - (bool) ``false`` - Doesn't do anything (default) + - (bool) ``true`` - Appends the timestamp when debug is ``true`` + - (string) 'force' - Always appends the timestamp. + +Database Configuration +---------------------- + +See the :ref:`Database Configuration ` for information +on configuring your database connections. + +Caching Configuration +--------------------- + +See the :ref:`Caching Configuration ` for information on +configuring caching in CakePHP. + +Error and Exception Handling Configuration +------------------------------------------ + +See the :ref:`Error and Exception Configuration ` for +information on configuring error and exception handlers. + +Logging Configuration +--------------------- + +See the :ref:`log-configuration` for information on configuring logging in +CakePHP. + +Email Configuration +------------------- + +See the :ref:`Email Configuration ` for information on +configuring email presets in CakePHP. + +Session Configuration +--------------------- + +See the :ref:`session-configuration` for information on configuring session +handling in CakePHP. + +Routing configuration +--------------------- + +See the :ref:`Routes Configuration ` for more information +on configuring routing and creating routes for your application. + +.. _additional-class-paths: + +Additional Class Paths +====================== + +Additional class paths are setup through the autoloaders your application uses. +When using ``composer`` to generate your autoloader, you could do the following, +to provide fallback paths for controllers in your application:: + + "autoload": { + "psr-4": { + "App\\Controller\\": "/path/to/directory/with/controller/folders/", + "App\\": "src/" + } + } + +The above would setup paths for both the ``App`` and ``App\Controller`` +namespace. The first key will be searched, and if that path does not contain the +class/file the second key will be searched. You can also map a single namespace +to multiple directories with the following:: + + "autoload": { + "psr-4": { + "App\\": ["src/", "/path/to/directory/"] + } + } + +Plugin, View Template and Locale Paths +-------------------------------------- + +Since plugins, view templates and locales are not classes, they cannot have an +autoloader configured. CakePHP provides three Configure variables to setup additional +paths for these resources. In your **config/app.php** you can set these variables:: + + return [ + // More configuration + 'App' => [ + 'paths' => [ + 'plugins' => [ + ROOT . DS . 'plugins' . DS, + '/path/to/other/plugins/' + ], + 'templates' => [ + APP . 'Template' . DS, + APP . 'Template2' . DS + ], + 'locales' => [ + APP . 'Locale' . DS + ] + ] + ] + ]; + +Paths should end with a directory separator, or they will not work properly. + +Inflection Configuration +======================== + +See the :ref:`inflection-configuration` docs for more information. + +.. _environment-variables: + +Environment Variables +===================== + +Many modern cloud providers, like Heroku, let you define environment +variables for configuration data. You can configure your CakePHP through +environment variables in the `12factor app style `_. +Environment variables allow your application to require less state making your +application easier to manage when it is deployed across a number of +environments. + +As you can see in your **app.php**, the ``env()`` function is used to read +configuration from the environment, and build the application configuration. +CakePHP uses :term:`DSN` strings for databases, logs, email transports and cache +configurations allowing you to easily vary these libraries in each environment. + +For local development, CakePHP leverages `dotenv +`_ to allow easy local development using +environment variables. You will see a ``config/.env.default`` in your +application. By copying this file into ``config/.env`` and customizing the +values you can configure your application. + +You should avoid committing the ``config/.env`` file to your repository and +instead use the ``config/.env.default`` as a template with placeholder values so +everyone on your team knows what environment variables are in use and what +should go in each one. + +Once your environment variables have been set, you can use ``env()`` to read +data from the environment:: + + $debug = env('APP_DEBUG', false); + +The second value passed to the env function is the default value. This value +will be used if no environment variable exists for the given key. + +.. versionchanged:: 3.5.0 + dotenv library support was added to the application skeleton. + +Configure Class +=============== + +.. php:namespace:: Cake\Core + +.. php:class:: Configure + +CakePHP's Configure class can be used to store and retrieve +application or runtime specific values. Be careful, this class +allows you to store anything in it, then use it in any other part +of your code: a sure temptation to break the MVC pattern CakePHP +was designed for. The main goal of Configure class is to keep +centralized variables that can be shared between many objects. +Remember to try to live by "convention over configuration" and you +won't end up breaking the MVC structure CakePHP provides. + +Writing Configuration data +-------------------------- + +.. php:staticmethod:: write($key, $value) + +Use ``write()`` to store data in the application's configuration:: + + Configure::write('Company.name','Pizza, Inc.'); + Configure::write('Company.slogan','Pizza for your body and soul'); + +.. note:: + + The :term:`dot notation` used in the ``$key`` parameter can be used to + organize your configuration settings into logical groups. + +The above example could also be written in a single call:: + + Configure::write('Company', [ + 'name' => 'Pizza, Inc.', + 'slogan' => 'Pizza for your body and soul' + ]); + +You can use ``Configure::write('debug', $bool)`` to switch between debug and +production modes on the fly. This is especially handy for JSON interactions +where debugging information can cause parsing problems. + +Reading Configuration Data +-------------------------- + +.. php:staticmethod:: read($key = null, $default = null) + +Used to read configuration data from the application. If a key is supplied, the +data is returned. Using our examples from write() above, we can read that data +back:: + + // Returns 'Pizza Inc.' + Configure::read('Company.name'); + + // Returns 'Pizza for your body and soul' + Configure::read('Company.slogan'); + + Configure::read('Company'); + // Returns: + ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; + + // Returns 'fallback' as Company.nope is undefined. + Configure::read('Company.nope', 'fallback'); + +If ``$key`` is left null, all values in Configure will be returned. + +.. versionchanged:: 3.5.0 + The ``$default`` parameter was added in 3.5.0 + +.. php:staticmethod:: readOrFail($key) + +Reads configuration data just like :php:meth:`Cake\\Core\\Configure::read` +but expects to find a key/value pair. In case the requested pair does not +exist, a :php:class:`RuntimeException` will be thrown:: + + Configure::readOrFail('Company.name'); // Yields: 'Pizza, Inc.' + Configure::readOrFail('Company.geolocation'); // Will throw an exception + + Configure::readOrFail('Company'); + + // Yields: + ['name' => 'Pizza, Inc.', 'slogan' => 'Pizza for your body and soul']; + +.. versionadded:: 3.1.7 + ``Configure::readOrFail()`` was added in 3.1.7 + +Checking to see if Configuration Data is Defined +------------------------------------------------ + +.. php:staticmethod:: check($key) + +Used to check if a key/path exists and has non-null value:: + + $exists = Configure::check('Company.name'); + +Deleting Configuration Data +--------------------------- + +.. php:staticmethod:: delete($key) + +Used to delete information from the application's configuration:: + + Configure::delete('Company.name'); + +Reading & Deleting Configuration Data +------------------------------------- + +.. php:staticmethod:: consume($key) + +Read and delete a key from Configure. This is useful when you want to +combine reading and deleting values in a single operation. + +Reading and writing configuration files +======================================= + +.. php:staticmethod:: config($name, $engine) + +CakePHP comes with two built-in configuration file engines. +:php:class:`Cake\\Core\\Configure\\Engine\\PhpConfig` is able to read PHP config +files, in the same format that Configure has historically read. +:php:class:`Cake\\Core\\Configure\\Engine\\IniConfig` is able to read ini config +files. See the `PHP documentation `_ for more +information on the specifics of ini files. To use a core config engine, you'll +need to attach it to Configure using :php:meth:`Configure::config()`:: + + use Cake\Core\Configure\Engine\PhpConfig; + + // Read config files from config + Configure::config('default', new PhpConfig()); + + // Read config files from another path. + Configure::config('default', new PhpConfig('/path/to/your/config/files/')); + +You can have multiple engines attached to Configure, each reading different +kinds or sources of configuration files. You can interact with attached engines +using a few other methods on Configure. To check which engine aliases are +attached you can use :php:meth:`Configure::configured()`:: + + // Get the array of aliases for attached engines. + Configure::configured(); + + // Check if a specific engine is attached + Configure::configured('default'); + +.. php:staticmethod:: drop($name) + +You can also remove attached engines. ``Configure::drop('default')`` +would remove the default engine alias. Any future attempts to load configuration +files with that engine would fail:: + + Configure::drop('default'); + +.. _loading-configuration-files: + +Loading Configuration Files +--------------------------- + +.. php:staticmethod:: load($key, $config = 'default', $merge = true) + +Once you've attached a config engine to Configure you can load configuration +files:: + + // Load my_file.php using the 'default' engine object. + Configure::load('my_file', 'default'); + +Loaded configuration files merge their data with the existing runtime +configuration in Configure. This allows you to overwrite and add new values into +the existing runtime configuration. By setting ``$merge`` to ``true``, values +will not ever overwrite the existing configuration. + +Creating or Modifying Configuration Files +----------------------------------------- + +.. php:staticmethod:: dump($key, $config = 'default', $keys = []) + +Dumps all or some of the data in Configure into a file or storage system +supported by a config engine. The serialization format is decided by the config +engine attached as $config. For example, if the 'default' engine is +a :php:class:`Cake\\Core\\Configure\\Engine\\PhpConfig`, the generated file will be +a PHP configuration file loadable by the +:php:class:`Cake\\Core\\Configure\\Engine\\PhpConfig` + +Given that the 'default' engine is an instance of PhpConfig. +Save all data in Configure to the file `my_config.php`:: + + Configure::dump('my_config', 'default'); + +Save only the error handling configuration:: + + Configure::dump('error', 'default', ['Error', 'Exception']); + +``Configure::dump()`` can be used to either modify or overwrite +configuration files that are readable with :php:meth:`Configure::load()` + +Storing Runtime Configuration +----------------------------- + +.. php:staticmethod:: store($name, $cacheConfig = 'default', $data = null) + +You can also store runtime configuration values for use in a future request. +Since configure only remembers values for the current request, you will +need to store any modified configuration information if you want to +use it in subsequent requests:: + + // Store the current configuration in the 'user_1234' key in the 'default' cache. + Configure::store('user_1234', 'default'); + +Stored configuration data is persisted in the named cache configuration. See the +:doc:`/core-libraries/caching` documentation for more information on caching. + +Restoring Runtime Configuration +------------------------------- + +.. php:staticmethod:: restore($name, $cacheConfig = 'default') + +Once you've stored runtime configuration, you'll probably need to restore it +so you can access it again. ``Configure::restore()`` does exactly that:: + + // Restore runtime configuration from the cache. + Configure::restore('user_1234', 'default'); + +When restoring configuration information it's important to restore it with +the same key, and cache configuration as was used to store it. Restored +information is merged on top of the existing runtime configuration. + +Configuration Engines +--------------------- + +CakePHP provides the ability to load configuration files from a number of +different sources, and features a pluggable system for `creating your own +configuration engines +`__. +The built in configuration engines are: + +* `JsonConfig `__ +* `IniConfig `__ +* `PhpConfig `__ + +By default your application will use ``PhpConfig``. + +Bootstrapping CakePHP +===================== + +If you have any additional configuration needs, you should add them to your +application's **config/bootstrap.php** file. This file is included before each +request, and CLI command. + +This file is ideal for a number of common bootstrapping tasks: + +- Defining convenience functions. +- Declaring constants. +- Defining cache configuration. +- Defining logging configuration. +- Loading custom inflections. +- Loading configuration files. + +It might be tempting to place formatting functions there in order to use them in +your controllers. As you'll see in the :doc:`/controllers` and :doc:`/views` +sections there are better ways you add custom logic to your application. + +.. _application-bootstrap: + +Application::bootstrap() +------------------------ + +In addition to the **config/bootstrap.php** file which should be used to +configure low-level concerns of your application, you can also use the +``Application::bootstrap()`` hook method to load/initialize plugins, and attach +global event listeners:: + + // in src/Application.php + namespace App; + + use Cake\Core\Plugin; + use Cake\Http\BaseApplication; + + class Application extends BaseApplication + { + public function bootstrap() + { + // Call the parent to `require_once` config/bootstrap.php + parent::bootstrap(); + + Plugin::load('MyPlugin', ['bootstrap' => true, 'routes' => true]); + } + } + +Loading plugins/events in ``Application::bootstrap()`` makes +:ref:`integration-testing` easier as events and routes will be re-processed on +each test method. + +Disabling Generic Tables +======================== + +While utilizing generic table classes - also called auto-tables - when quickly +creating new applications and baking models is useful, generic table class can +make debugging more difficult in some scenarios. + +You can check if any query was emitted from a generic table class via DebugKit +via the SQL panel in DebugKit. If you're still having trouble diagnosing an +issue that could be caused by auto-tables, you can throw an exception when +CakePHP implicitly uses a generic ``Cake\ORM\Table`` instead of your concrete +class like so:: + + // In your bootstrap.php + use Cake\Event\EventManager; + use Cake\Network\Exception\InternalErrorException; + + $isCakeBakeShellRunning = (PHP_SAPI === 'cli' && isset($argv[1]) && $argv[1] === 'bake'); + if (!$isCakeBakeShellRunning) { + EventManager::instance()->on('Model.initialize', function($event) { + $subject = $event->getSubject(); + if (get_class($subject === 'Cake\ORM\Table') { + $msg = sprintf( + 'Missing table class or incorrect alias when registering table class for database table %s.', + $subject->getTable()); + throw new InternalErrorException($msg); + } + }); + } + +.. meta:: + :title lang=en: Configuration + :keywords lang=en: finished configuration,legacy database,database configuration,value pairs,default connection,optional configuration,example database,php class,configuration database,default database,configuration steps,index database,configuration details,class database,host localhost,inflections,key value,database connection,piece of cake,basic web,auto tables,auto-tables,generic table,class diff --git a/tl/development/debugging.rst b/tl/development/debugging.rst new file mode 100644 index 0000000000000000000000000000000000000000..0f5ba1b669e3aff5fa4c4ebd08500b50423732b4 --- /dev/null +++ b/tl/development/debugging.rst @@ -0,0 +1,209 @@ +Debugging +######### + +Debugging is an inevitable and necessary part of any development +cycle. While CakePHP doesn't offer any tools that directly connect +with any IDE or editor, CakePHP does provide several tools to +assist in debugging and exposing what is running under the hood of +your application. + +Basic Debugging +=============== + +.. php:function:: debug(mixed $var, boolean $showHtml = null, $showFrom = true) + :noindex: + +The ``debug()`` function is a globally available function that works +similarly to the PHP function ``print_r()``. The ``debug()`` function +allows you to show the contents of a variable in a number of +different ways. First, if you'd like data to be shown in an +HTML-friendly way, set the second parameter to ``true``. The function +also prints out the line and file it is originating from by +default. + +Output from this function is only shown if the core ``$debug`` variable +has been set to ``true``. + +.. versionadded:: 3.3.0 + + Calling this method will return passed ``$var``, so that you can, for instance, + place it in return statements, for example:: + + return debug($data); // will return $data in any case. + +Also see ``dd()``, ``pr()`` and ``pj()``. + +.. php:function:: stackTrace() + +The ``stackTrace()`` function is available globally, and allows you to output +a stack trace wherever the function is called. + +.. php:function:: breakpoint() + +.. versionadded:: 3.1 + +If you have `Psysh `_ installed you can use this +function in CLI enviroments to open an interactive console with the current +local scope:: + + // Some code + eval(breakpoint()); + +Will open an interactive console that can be used to check local variables +and execute other code. You can exit the interactive debugger and resume the +original execution by running ``quit`` or ``q`` in the interactive session. + +Using the Debugger Class +======================== + +.. php:namespace:: Cake\Error + +.. php:class:: Debugger + +To use the debugger, first ensure that ``Configure::read('debug')`` is +set to ``true``. + +Outputting Values +================= + +.. php:staticmethod:: dump($var, $depth = 3) + +Dump prints out the contents of a variable. It will print out all +properties and methods (if any) of the supplied variable:: + + $foo = [1,2,3]; + + Debugger::dump($foo); + + // Outputs + array( + 1, + 2, + 3 + ) + + // Simple object + $car = new Car(); + + Debugger::dump($car); + + // Outputs + object(Car) { + color => 'red' + make => 'Toyota' + model => 'Camry' + mileage => (int)15000 + } + +Masking Data +------------ + +When dumping data with ``Debugger`` or rendering error pages, you may want to +hide sensitive keys like passwords or API keys. In your ``config/bootstrap.php`` +you can mask specific keys:: + + Debugger::setOutputMask([ + 'password' => 'xxxxx', + 'awsKey' => 'yyyyy', + ]); + +.. versionadded:: 3.4.0 + + Output masking was added in 3.4.0 + +Logging With Stack Traces +========================= + +.. php:staticmethod:: log($var, $level = 7, $depth = 3) + +Creates a detailed stack trace log at the time of invocation. The +``log()`` method prints out data similar to that done by +``Debugger::dump()``, but to the debug.log instead of the output +buffer. Note your **tmp** directory (and its contents) must be +writable by the web server for ``log()`` to work correctly. + +Generating Stack Traces +======================= + +.. php:staticmethod:: trace($options) + +Returns the current stack trace. Each line of the trace includes +the calling method, including which file and line the call +originated from:: + + // In PostsController::index() + pr(Debugger::trace()); + + // Outputs + PostsController::index() - APP/Controller/DownloadsController.php, line 48 + Dispatcher::_invoke() - CORE/src/Routing/Dispatcher.php, line 265 + Dispatcher::dispatch() - CORE/src/Routing/Dispatcher.php, line 237 + [main] - APP/webroot/index.php, line 84 + +Above is the stack trace generated by calling ``Debugger::trace()`` in +a controller action. Reading the stack trace bottom to top shows +the order of currently running functions (stack frames). + +Getting an Excerpt From a File +============================== + +.. php:staticmethod:: excerpt($file, $line, $context) + +Grab an excerpt from the file at $path (which is an absolute +filepath), highlights line number $line with $context number of +lines around it. :: + + pr(Debugger::excerpt(ROOT . DS . LIBS . 'debugger.php', 321, 2)); + + // Will output the following. + Array + ( + [0] => * @access public + [1] => */ + [2] => function excerpt($file, $line, $context = 2) { + + [3] => $data = $lines = array(); + [4] => $data = @explode("\n", file_get_contents($file)); + ) + +Although this method is used internally, it can be handy if you're +creating your own error messages or log entries for custom +situations. + +.. php:staticmethod:: Debugger::getType($var) + +Get the type of a variable. Objects will return their class name + +Using Logging to Debug +====================== + +Logging messages is another good way to debug applications, and you can use +:php:class:`Cake\\Log\\Log` to do logging in your application. All objects that +use ``LogTrait`` have an instance method ``log()`` which can be used +to log messages:: + + $this->log('Got here', 'debug'); + +The above would write ``Got here`` into the debug log. You can use log entries +to help debug methods that involve redirects or complicated loops. You can also +use :php:meth:`Cake\\Log\\Log::write()` to write log messages. This method can be called +statically anywhere in your application one Log has been loaded:: + + // At the top of the file you want to log in. + use Cake\Log\Log; + + // Anywhere that Log has been imported. + Log::debug('Got here'); + +Debug Kit +========= + +DebugKit is a plugin that provides a number of good debugging tools. It +primarily provides a toolbar in the rendered HTML, that provides a plethora of +information about your application and the current request. See the +:doc:`/debug-kit` chapter for how to install and use DebugKit. + +.. meta:: + :title lang=en: Debugging + :description lang=en: Debugging CakePHP with the Debugger class, logging, basic debugging and using the DebugKit plugin. + :keywords lang=en: code excerpt,stack trace,default output,error link,default error,web requests,error report,debugger,arrays,different ways,excerpt from,cakephp,ide,options diff --git a/tl/development/dispatch-filters.rst b/tl/development/dispatch-filters.rst new file mode 100644 index 0000000000000000000000000000000000000000..2cafa6e7b9d94d5aa4cbf69edb586c6353fa268d --- /dev/null +++ b/tl/development/dispatch-filters.rst @@ -0,0 +1,201 @@ +Dispatcher Filters +################## + +.. deprecated:: 3.3.0 + As of 3.3.0 Dispatcher Filters are deprecated. You should use + :doc:`/controllers/middleware` instead now. + +There are several reasons to want a piece of code to be run before any +controller code is executed or right before the response is sent to the client, +such as response caching, header tuning, special authentication or just to +provide access to a mission-critical API response in lesser time than a complete +request dispatching cycle would take. + +CakePHP provides a clean interface for attaching filters to the dispatch +cycle. It is similar to a middleware layer, but re-uses the existing event +subsystem used in other parts of CakePHP. Since they do not work exactly +like traditional middleware, we refer to them as *Dispatcher Filters*. + +Built-in Filters +================ + +CakePHP comes with several dispatcher filters built-in. They handle common +features that all applications are likely to need. The built-in filters are: + +* ``AssetFilter`` checks whether the request is referring to a theme + or plugin asset file, such as a CSS, JavaScript or image file stored in either a + plugin's webroot folder or the corresponding one for a Theme. It will serve the + file accordingly if found, stopping the rest of the dispatching cycle:: + + // Use options to set cacheTime for your static assets + // If not set, this defaults to +1 hour + DispatcherFactory::add('Asset', ['cacheTime' => '+24 hours']); + +* ``RoutingFilter`` applies application routing rules to the request URL. + Populates ``$request->getParam()`` with the results of routing. +* ``ControllerFactory`` uses ``$request->getParam()`` to locate the controller that + will handle the current request. +* ``LocaleSelector`` enables automatic language switching from the ``Accept-Language`` + header sent by the browser. + +Using Filters +============= + +Filters are usually enabled in your application's **bootstrap.php** file, but +you could load them any time before the request is dispatched. Adding +and removing filters is done through :php:class:`Cake\\Routing\\DispatcherFactory`. By +default, the CakePHP application template comes with a couple filter classes +already enabled for all requests; let's take a look at how they are added:: + + DispatcherFactory::add('Routing'); + DispatcherFactory::add('ControllerFactory'); + + // Plugin syntax is also possible + DispatcherFactory::add('PluginName.DispatcherName'); + + // Use options to set priority + DispatcherFactory::add('Asset', ['priority' => 1]); + +Dispatcher filters with higher ``priority`` (lower numbers) - will be executed +first. Priority defaults to ``10``. + +While using the string name is convenient, you can also pass instances into +``add()``:: + + use Cake\Routing\Filter\RoutingFilter; + + DispatcherFactory::add(new RoutingFilter()); + +Configuring Filter Order +------------------------ + +When adding filters, you can control the order they are invoked in using +event handler priorities. While filters can define a default priority using the +``$_priority`` property, you can set a specific priority when attaching the +filter:: + + DispatcherFactory::add('Asset', ['priority' => 1]); + DispatcherFactory::add(new AssetFilter(['priority' => 1])); + +The higher the priority the later this filter will be invoked. + +Conditionally Applying Filters +------------------------------ + +If you don't want to run a filter on every request, you can use conditions to +only apply it some of the time. You can apply conditions using the ``for`` and +``when`` options. The ``for`` option lets you match on URL substrings, while the +``when`` option allows you to run a callable:: + + // Only runs on requests starting with `/blog` + DispatcherFactory::add('BlogHeader', ['for' => '/blog']); + + // Only run on GET requests. + DispatcherFactory::add('Cache', [ + 'when' => function ($request, $response) { + return $request->is('get'); + } + ]); + +The callable provided to ``when`` should return ``true`` when the filter should run. +The callable can expect to get the current request and response as arguments. + +Building a Filter +================= + +To create a filter, define a class in **src/Routing/Filter**. In this example, +we'll be making a filter that adds a tracking cookie for the first landing +page. First, create the file. Its contents should look like:: + + namespace App\Routing\Filter; + + use Cake\Event\Event; + use Cake\Routing\DispatcherFilter; + + class TrackingCookieFilter extends DispatcherFilter + { + + public function beforeDispatch(Event $event) + { + $request = $event->getData('request'); + $response = $event->getData('response'); + if (!$request->getCookie('landing_page')) { + $response->cookie([ + 'name' => 'landing_page', + 'value' => $request->here(), + 'expire' => '+ 1 year', + ]); + } + } + } + +Save this file into **src/Routing/Filter/TrackingCookieFilter.php**. As you can see, like other +classes in CakePHP, dispatcher filters have a few conventions: + +* Class names end in ``Filter``. +* Classes are in the ``Routing\Filter`` namespace. For example, + ``App\Routing\Filter``. +* Generally filters extend ``Cake\Routing\DispatcherFilter``. + +``DispatcherFilter`` exposes two methods that can be overridden in subclasses, +they are ``beforeDispatch()`` and ``afterDispatch()``. These methods are +executed before or after any controller is executed respectively. Both methods +receive a :php:class:`Cake\\Event\\Event` object containing the ``ServerRequest`` and +``Response`` objects (:php:class:`Cake\\Http\\ServerRequest` and +:php:class:`Cake\\Http\\Response` instances) inside the ``$data`` property. + +While our filter was pretty simple, there are a few other interesting things we +can do in filter methods. By returning an ``Response`` object, you can +short-circuit the dispatch process and prevent the controller from being called. +When returning a response, you should also remember to call +``$event->stopPropagation()`` so other filters are not called. + +.. note:: + + When a beforeDispatch method returns a response, the controller, and + afterDispatch event will not be invoked. + +Let's now create another filter for altering response headers in any public +page, in our case it would be anything served from the ``PagesController``:: + + namespace App\Routing\Filter; + + use Cake\Event\Event; + use Cake\Routing\DispatcherFilter; + + class HttpCacheFilter extends DispatcherFilter + { + + public function afterDispatch(Event $event) + { + $request = $event->getData('request'); + $response = $event->getData('response'); + + if ($response->statusCode() === 200) { + $response->sharable(true); + $response->expires(strtotime('+1 day')); + } + } + } + + // In our bootstrap.php + DispatcherFactory::add('HttpCache', ['for' => '/pages']) + +This filter will send a expiration header to 1 day in the future for +all responses produced by the pages controller. You could of course do the same +in the controller, this is just an example of what could be done with filters. +For instance, instead of altering the response, you could cache it using +:php:class:`Cake\\Cache\\Cache` and serve the response from the ``beforeDispatch()`` +callback. + +While powerful, dispatcher filters have the potential to make your application +more difficult to maintain. Filters are an extremely powerful tool when used +wisely and adding response handlers for each URL in your app is not a good use for +them. Keep in mind that not everything needs to be a filter; `Controllers` and +`Components` are usually a more accurate choice for adding any request handling +code to your app. + +.. meta:: + :title lang=en: Dispatcher Filters + :description lang=en: Dispatcher filters are a middleware layer for CakePHP allowing to alter the request or response before it is sent + :keywords lang=en: middleware, filters, dispatcher, request, response, rack, application stack, events, beforeDispatch, afterDispatch, router diff --git a/tl/development/errors.rst b/tl/development/errors.rst new file mode 100644 index 0000000000000000000000000000000000000000..d9606987ebf19abad5eeaef73fb711f56a242db3 --- /dev/null +++ b/tl/development/errors.rst @@ -0,0 +1,584 @@ +Error & Exception Handling +########################## + +CakePHP applications come with error and exception handling setup for you. PHP +errors are trapped and displayed or logged. Uncaught exceptions are rendered +into error pages automatically. + +.. _error-configuration: + +Error & Exception Configuration +=============================== + +Error configuration is done in your application's **config/app.php** file. By +default CakePHP uses ``Cake\Error\ErrorHandler`` to handle both PHP errors and +exceptions by default. The error configuration allows you to customize error +handling for your application. The following options are supported: + +* ``errorLevel`` - int - The level of errors you are interested in capturing. + Use the built-in PHP error constants, and bitmasks to select the level of + error you are interested in. +* ``trace`` - bool - Include stack traces for errors in log files. Stack + traces will be included in the log after each error. This is helpful for + finding where/when errors are being raised. +* ``exceptionRenderer`` - string - The class responsible for rendering uncaught + exceptions. If you choose a custom class you should place the file for that + class in **src/Error**. This class needs to implement a ``render()`` method. +* ``log`` - bool - When ``true``, exceptions + their stack traces will be + logged to :php:class:`Cake\\Log\\Log`. +* ``skipLog`` - array - An array of exception classnames that should not be + logged. This is useful to remove NotFoundExceptions or other common, but + uninteresting log messages. +* ``extraFatalErrorMemory`` - int - Set to the number of megabytes to increase + the memory limit by when a fatal error is encountered. This allows breathing + room to complete logging or error handling. + +By default, PHP errors are displayed when ``debug`` is ``true``, and logged +when debug is ``false``. The fatal error handler will be called independent +of ``debug`` level or ``errorLevel`` configuration, but the result will be +different based on ``debug`` level. The default behavior for fatal errors is +show a page to internal server error (``debug`` disabled) or a page with the +message, file and line (``debug`` enabled). + +.. note:: + + If you use a custom error handler, the supported options will + depend on your handler. + +.. php:class:: ExceptionRenderer(Exception $exception) + +Changing Exception Handling +=========================== + +Exception handling offers 3 ways to tailor how exceptions are handled. Each +approach gives you different amounts of control over the exception handling +process. + +#. *Customize the error templates* This allows you to change the rendered view + templates as you would any other template in your application. +#. *Customize the ErrorController* This allows you to control how exception + pages are rendered. +#. *Customize the ExceptionRenderer* This allows you to control how exception + pages and logging are performed. +#. *Create & register your own error handler* This gives you complete + control over how errors & exceptions are handled, logged and rendered. + +.. _error-views: + +Customize Error Templates +========================= + +The default error handler renders all uncaught exceptions your application +raises with the help of ``Cake\Error\ExceptionRenderer``, and your application's +``ErrorController``. + +The error page views are located at **src/Template/Error/**. All 4xx errors use +the **error400.ctp** template, and 5xx errors use the **error500.ctp**. Your +error templates will have the following variables available: + +* ``message`` The exception message. +* ``code`` The exception code. +* ``url`` The request URL. +* ``error`` The exception object. + +In debug mode if your error extends ``Cake\Core\Exception\Exception`` the +data returned by ``getAttributes()`` will be exposed as view variables as well. + +.. note:: + You will need to set ``debug`` to false, to see your **error404** and + **error500** templates. In debug mode, you'll see CakePHP's development + error page. + +Customize the Error Page Layout +------------------------------- + +By default error templates use **src/Template/Layout/error.ctp** for a layout. +You can use the ``layout`` property to pick a different layout:: + + // inside src/Template/Error/error400.ctp + $this->layout = 'my_error'; + +The above would use **src/Template/Layout/my_error.ctp** as the layout for your +error pages. + +Many exceptions raised by CakePHP will render specific view templates in debug +mode. With debug turned off all exceptions raised by CakePHP will use either +**error400.ctp** or **error500.ctp** based on their status code. + +Customize the ErrorController +============================= + +The ``App\Controller\ErrorController`` class is used by CakePHP's exception +rendering to render the error page view and receives all the standard request +life-cycle events. By modifying this class you can control which components are +used and which templates are rendered. + +Change the ExceptionRenderer +============================ + +If you want to control the entire exception rendering and logging process you +can use the ``Error.exceptionRenderer`` option in **config/app.php** to choose +a class that will render exception pages. Changing the ExceptionRenderer is +useful when you want to provide custom error pages for application specific +exception classes. + +Your custom exception renderer class should be placed in **src/Error**. Let's +assume our application uses ``App\Exception\MissingWidgetException`` to indicate +a missing widget. We could create an exception renderer that renders specific +error pages when this error is handled:: + + // In src/Error/AppExceptionRenderer.php + namespace App\Error; + + use Cake\Error\ExceptionRenderer; + + class AppExceptionRenderer extends ExceptionRenderer + { + public function missingWidget($error) + { + $response = $this->controller->response; + + return $response->withStringBody('Oops that widget is missing.'); + } + } + + // In config/app.php + 'Error' => [ + 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', + // ... + ], + // ... + +The above would handle our ``MissingWidgetException``, +and allow us to provide custom display/handling logic for those application +exceptions. + +Exception rendering methods receive the handled exception as an argument, and +should return a ``Response`` object. You can also implement methods to add +additional logic when handling CakePHP errors:: + + // In src/Error/AppExceptionRenderer.php + namespace App\Error; + + use Cake\Error\ExceptionRenderer; + + class AppExceptionRenderer extends ExceptionRenderer + { + public function notFound($error) + { + // Do something with NotFoundException objects. + } + } + +Changing the ErrorController Class +---------------------------------- + +The exception renderer dictates which controller is used for exception +rendering. If you want to change which controller is used to render exceptions, +override the ``_getController()`` method in your exception renderer:: + + // in src/Error/AppExceptionRenderer + namespace App\Error; + + use App\Controller\SuperCustomErrorController; + use Cake\Error\ExceptionRenderer; + + class AppExceptionRenderer extends ExceptionRenderer + { + protected function _getController($exception) + { + return new SuperCustomErrorController(); + } + } + + // in config/app.php + 'Error' => [ + 'exceptionRenderer' => 'App\Error\AppExceptionRenderer', + // ... + ], + // ... + + +Creating your Own Error Handler +=============================== + +By replacing the error handler you can customize the entire error & exception +handling process. By extending ``Cake\Error\BaseErrorHandler`` you can customize +display logic more simply. As an example, we could build a class called +``AppError`` to handle our errors:: + + // In config/bootstrap.php + use App\Error\AppError; + + $errorHandler = new AppError(); + $errorHandler->register(); + + // In src/Error/AppError.php + namespace App\Error; + + use Cake\Error\BaseErrorHandler; + + class AppError extends BaseErrorHandler + { + public function _displayError($error, $debug) + { + echo 'There has been an error!'; + } + + public function _displayException($exception) + { + echo 'There has been an exception!'; + } + } + +The ``BaseErrorHandler`` defines two abstract methods. ``_displayError()`` is +used when errors are triggered. The ``_displayException()`` method is called +when there is an uncaught exception. + +Changing Fatal Error Behavior +----------------------------- + +Error handlers convert fatal errors into exceptions and re-use the +exception handling logic to render an error page. If you do not want to show the +standard error page, you can override it:: + + // In src/Error/AppError.php + namespace App\Error; + + use Cake\Error\BaseErrorHandler; + + class AppError extends BaseErrorHandler + { + // Other methods. + + public function handleFatalError($code, $description, $file, $line) + { + return 'A fatal error has happened'; + } + } + +.. index:: application exceptions + +Creating your own Application Exceptions +======================================== + +You can create your own application exceptions using any of the built in `SPL +exceptions `_, ``Exception`` +itself, or :php:exc:`Cake\\Core\\Exception\\Exception`. +If your application contained the following exception:: + + use Cake\Core\Exception\Exception; + + class MissingWidgetException extends Exception + { + } + +You could provide nice development errors, by creating +**src/Template/Error/missing_widget.ctp**. When in production mode, the above +error would be treated as a 500 error and use the **error500** template. + +If your exceptions have a code between ``400`` and ``506`` the exception code +will be used as the HTTP response code. + +The constructor for :php:exc:`Cake\\Core\\Exception\\Exception` allows you to +pass in additional data. This additional data is interpolated into the the +``_messageTemplate``. This allows you to create data rich exceptions, that +provide more context around your errors:: + + use Cake\Core\Exception\Exception; + + class MissingWidgetException extends Exception + { + // Context data is interpolated into this format string. + protected $_messageTemplate = 'Seems that %s is missing.'; + + // You can set a default exception code as well. + protected $_defaultCode = 404; + } + + throw new MissingWidgetException(['widget' => 'Pointy']); + +When rendered, this your view template would have a ``$widget`` variable set. If +you cast the exception as a string or use its ``getMessage()`` method you will +get ``Seems that Pointy is missing.``. + +Logging Exceptions +------------------ + +Using the built-in exception handling, you can log all the exceptions that are +dealt with by ErrorHandler by setting the ``log`` option to ``true`` in your +**config/app.php**. Enabling this will log every exception to +:php:class:`Cake\\Log\\Log` and the configured loggers. + +.. note:: + + If you are using a custom exception handler this setting will have + no effect. Unless you reference it inside your implementation. + + +.. php:namespace:: Cake\Network\Exception + +.. _built-in-exceptions: + +Built in Exceptions for CakePHP +=============================== + +HTTP Exceptions +--------------- + +There are several built-in exceptions inside CakePHP, outside of the +internal framework exceptions, there are several +exceptions for HTTP methods + +.. php:exception:: BadRequestException + + Used for doing 400 Bad Request error. + +.. php:exception:: UnauthorizedException + + Used for doing a 401 Unauthorized error. + +.. php:exception:: ForbiddenException + + Used for doing a 403 Forbidden error. + +.. versionadded:: 3.1 + + InvalidCsrfTokenException has been added. + +.. php:exception:: InvalidCsrfTokenException + + Used for doing a 403 error caused by an invalid CSRF token. + +.. php:exception:: NotFoundException + + Used for doing a 404 Not found error. + +.. php:exception:: MethodNotAllowedException + + Used for doing a 405 Method Not Allowed error. + +.. php:exception:: NotAcceptableException + + Used for doing a 406 Not Acceptable error. + + .. versionadded:: 3.1.7 NotAcceptableException has been added. + +.. php:exception:: ConflictException + + Used for doing a 409 Conflict error. + + .. versionadded:: 3.1.7 ConflictException has been added. + +.. php:exception:: GoneException + + Used for doing a 410 Gone error. + + .. versionadded:: 3.1.7 GoneException has been added. + +For more details on HTTP 4xx error status codes see :rfc:`2616#section-10.4`. + +.. php:exception:: InternalErrorException + + Used for doing a 500 Internal Server Error. + +.. php:exception:: NotImplementedException + + Used for doing a 501 Not Implemented Errors. + +.. php:exception:: ServiceUnavailableException + + Used for doing a 503 Service Unavailable error. + + .. versionadded:: 3.1.7 Service Unavailable has been added. + +For more details on HTTP 5xx error status codes see :rfc:`2616#section-10.5`. + +You can throw these exceptions from your controllers to indicate failure states, +or HTTP errors. An example use of the HTTP exceptions could be rendering 404 +pages for items that have not been found:: + + use Cake\Network\Exception\NotFoundException; + + public function view($id = null) + { + $article = $this->Articles->findById($id)->first(); + if (empty($article)) { + throw new NotFoundException(__('Article not found')); + } + $this->set('article', $article); + $this->set('_serialize', ['article']); + } + +By using exceptions for HTTP errors, you can keep your code both clean, and give +RESTful responses to client applications and users. + +Using HTTP Exceptions in your Controllers +----------------------------------------- + +You can throw any of the HTTP related exceptions from your controller actions +to indicate failure states. For example:: + + use Cake\Network\Exception\NotFoundException; + + public function view($id = null) + { + $article = $this->Articles->findById($id)->first(); + if (empty($article)) { + throw new NotFoundException(__('Article not found')); + } + $this->set('article', 'article'); + $this->set('_serialize', ['article']); + } + +The above would cause the configured exception handler to catch and +process the :php:exc:`NotFoundException`. By default this will create an error +page, and log the exception. + +Other Built In Exceptions +------------------------- + +In addition, CakePHP uses the following exceptions: + +.. php:namespace:: Cake\View\Exception + +.. php:exception:: MissingViewException + + The chosen view class could not be found. + +.. php:exception:: MissingTemplateException + + The chosen template file could not be found. + +.. php:exception:: MissingLayoutException + + The chosen layout could not be found. + +.. php:exception:: MissingHelperException + + The chosen helper could not be found. + +.. php:exception:: MissingElementException + + The chosen element file could not be found. + +.. php:exception:: MissingCellException + + The chosen cell class could not be found. + +.. php:exception:: MissingCellViewException + + The chosen cell view file could not be found. + +.. php:namespace:: Cake\Controller\Exception + +.. php:exception:: MissingComponentException + + A configured component could not be found. + +.. php:exception:: MissingActionException + + The requested controller action could not be found. + +.. php:exception:: PrivateActionException + + Accessing private/protected/_ prefixed actions. + +.. php:namespace:: Cake\Console\Exception + +.. php:exception:: ConsoleException + + A console library class encounter an error. + +.. php:exception:: MissingTaskException + + A configured task could not found. + +.. php:exception:: MissingShellException + + The shell class could not be found. + +.. php:exception:: MissingShellMethodException + + The chosen shell class has no method of that name. + +.. php:namespace:: Cake\Database\Exception + +.. php:exception:: MissingConnectionException + + A model's connection is missing. + +.. php:exception:: MissingDriverException + + A database driver could not be found. + +.. php:exception:: MissingExtensionException + + A PHP extension is missing for the database driver. + +.. php:namespace:: Cake\ORM\Exception + +.. php:exception:: MissingTableException + + A model's table could not be found. + +.. php:exception:: MissingEntityException + + A model's entity could not be found. + +.. php:exception:: MissingBehaviorException + + A model's behavior could not be found. + +.. php:exception:: PersistenceFailedException + + An entity couldn't be saved/deleted while using :php:meth:`Cake\\ORM\\Table::saveOrFail()` or + :php:meth:`Cake\\ORM\\Table::deleteOrFail()`. + + .. versionadded:: 3.4.1 PersistenceFailedException has been added. + +.. php:namespace:: Cake\Datasource\Exception + +.. php:exception:: RecordNotFoundException + + The requested record could not be found. This will also set HTTP response + headers to 404. + +.. php:namespace:: Cake\Routing\Exception + +.. php:exception:: MissingControllerException + + The requested controller could not be found. + +.. php:exception:: MissingRouteException + + The requested URL cannot be reverse routed or cannot be parsed. + +.. php:exception:: MissingDispatcherFilterException + + The dispatcher filter could not be found. + +.. php:namespace:: Cake\Core\Exception + +.. php:exception:: Exception + + Base exception class in CakePHP. All framework layer exceptions thrown by + CakePHP will extend this class. + +These exception classes all extend :php:exc:`Exception`. +By extending Exception, you can create your own 'framework' errors. + +.. php:method:: responseHeader($header = null, $value = null) + + See :php:func:`Cake\\Network\\Request::header()` + +All Http and Cake exceptions extend the Exception class, which has a method +to add headers to the response. For instance when throwing a 405 +MethodNotAllowedException the rfc2616 says:: + + "The response MUST include an Allow header containing a list of valid + methods for the requested resource." + +.. meta:: + :title lang=en: Error & Exception Handling + :keywords lang=en: stack traces,error constants,error array,default displays,anonymous functions,error handlers,default error,error level,exception handler,php error,error handler,write error,core classes,exception handling,configuration error,application code,callback,custom error,exceptions,bitmasks,fatal error, http status codes diff --git a/tl/development/rest.rst b/tl/development/rest.rst new file mode 100644 index 0000000000000000000000000000000000000000..bf67352a044374448d279318104138af02ce60d1 --- /dev/null +++ b/tl/development/rest.rst @@ -0,0 +1,173 @@ +REST +#### + +Many newer application programmers are realizing the need to open their core +functionality to a greater audience. Providing easy, unfettered access to your +core API can help get your platform accepted, and allows for mashups and easy +integration with other systems. + +While other solutions exist, REST is a great way to provide easy access to the +logic you've created in your application. It's simple, usually XML-based (we're +talking simple XML, nothing like a SOAP envelope), and depends on HTTP headers +for direction. Exposing an API via REST in CakePHP is simple. + +The Simple Setup +================ + +The fastest way to get up and running with REST is to add a few lines to setup +:ref:`resource routes ` in your config/routes.php file. + +Once the router has been set up to map REST requests to certain controller +actions, we can move on to creating the logic in our controller actions. A basic +controller might look something like this:: + + // src/Controller/RecipesController.php + class RecipesController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function index() + { + $recipes = $this->Recipes->find('all'); + $this->set([ + 'recipes' => $recipes, + '_serialize' => ['recipes'] + ]); + } + + public function view($id) + { + $recipe = $this->Recipes->get($id); + $this->set([ + 'recipe' => $recipe, + '_serialize' => ['recipe'] + ]); + } + + public function add() + { + $recipe = $this->Recipes->newEntity($this->request->getData()); + if ($this->Recipes->save($recipe)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + $this->set([ + 'message' => $message, + 'recipe' => $recipe, + '_serialize' => ['message', 'recipe'] + ]); + } + + public function edit($id) + { + $recipe = $this->Recipes->get($id); + if ($this->request->is(['post', 'put'])) { + $recipe = $this->Recipes->patchEntity($recipe, $this->request->getData()); + if ($this->Recipes->save($recipe)) { + $message = 'Saved'; + } else { + $message = 'Error'; + } + } + $this->set([ + 'message' => $message, + '_serialize' => ['message'] + ]); + } + + public function delete($id) + { + $recipe = $this->Recipes->get($id); + $message = 'Deleted'; + if (!$this->Recipes->delete($recipe)) { + $message = 'Error'; + } + $this->set([ + 'message' => $message, + '_serialize' => ['message'] + ]); + } + } + +RESTful controllers often use parsed extensions to serve up different views +based on different kinds of requests. Since we're dealing with REST requests, +we'll be making XML views. You can make JSON views using CakePHP's +built-in :doc:`/views/json-and-xml-views`. By using the built in +:php:class:`XmlView` we can define a ``_serialize`` view variable. This special +view variable is used to define which view variables ``XmlView`` should +serialize into XML. + +If we wanted to modify the data before it is converted into XML we should not +define the ``_serialize`` view variable, and instead use template files. We place +the REST views for our RecipesController inside **src/Template/Recipes/xml**. We can also use +the :php:class:`Xml` for quick-and-easy XML output in those views. Here's what +our index view might look like:: + + // src/Template/Recipes/xml/index.ctp + // Do some formatting and manipulation on + // the $recipes array. + $xml = Xml::fromArray(['response' => $recipes]); + echo $xml->asXML(); + +When serving up a specific content type using :php:meth:`Cake\\Routing\\Router::extensions()`, +CakePHP automatically looks for a view helper that matches the type. +Since we're using XML as the content type, there is no built-in helper, +however if you were to create one it would automatically be loaded +for our use in those views. + +The rendered XML will end up looking something like this:: + + + + 234 + 2008-06-13 + 2008-06-14 + + 23423 + Billy + Bob + + + 245 + Yummy yummmy + + + ... + + +Creating the logic for the edit action is a bit trickier, but not by much. Since +you're providing an API that outputs XML, it's a natural choice to receive XML +as input. Not to worry, the +:php:class:`Cake\\Controller\\Component\\RequestHandler` and +:php:class:`Cake\\Routing\\Router` classes make things much easier. If a POST or +PUT request has an XML content-type, then the input is run through CakePHP's +:php:class:`Xml` class, and the array representation of the data is assigned to +``$this->request->getData()``. Because of this feature, handling XML and POST data in +parallel is seamless: no changes are required to the controller or model code. +Everything you need should end up in ``$this->request->getData()``. + +Accepting Input in Other Formats +================================ + +Typically REST applications not only output content in alternate data formats, +but also accept data in different formats. In CakePHP, the +:php:class:`RequestHandlerComponent` helps facilitate this. By default, +it will decode any incoming JSON/XML input data for POST/PUT requests +and supply the array version of that data in ``$this->request->getData()``. +You can also wire in additional deserializers for alternate formats if you +need them, using :php:meth:`RequestHandler::addInputType()`. + +RESTful Routing +=============== + +CakePHP's Router makes connecting RESTful resource routes easy. See the section +on :ref:`resource-routes` for more information. + +.. meta:: + :title lang=en: REST + :keywords lang=en: application programmers,default routes,core functionality,result format,mashups,recipe database,request method,easy access,config,soap,recipes,logic,audience,cakephp,running,api diff --git a/tl/development/routing.rst b/tl/development/routing.rst new file mode 100644 index 0000000000000000000000000000000000000000..f00d841026d3aedf63e9bba6209cc9d6a276f10a --- /dev/null +++ b/tl/development/routing.rst @@ -0,0 +1,1488 @@ +Routing +####### + +.. php:namespace:: Cake\Routing + +.. php:class:: Router + +Routing provides you tools that map URLs to controller actions. By defining +routes, you can separate how your application is implemented from how its URL's +are structured. + +Routing in CakePHP also encompasses the idea of reverse routing, where an array +of parameters can be transformed into a URL string. By using reverse routing, +you can re-factor your application's URL structure without having to update all +your code. + +.. index:: routes.php + +Quick Tour +========== + +This section will teach you by example the most common uses of the CakePHP +Router. Typically you want to display something as a landing page, so you add +this to your **routes.php** file:: + + use Cake\Routing\Router; + + // Using the scoped route builder. + Router::scope('/', function ($routes) { + $routes->connect('/', ['controller' => 'Articles', 'action' => 'index']); + }); + + // Using the static method. + Router::connect('/', ['controller' => 'Articles', 'action' => 'index']); + +``Router`` provides two interfaces for connecting routes. The static method is +a backwards compatible interface, while the scoped builders offer more terse +syntax when building multiple routes, and better performance. + +This will execute the index method in the ``ArticlesController`` when the +homepage of your site is visited. Sometimes you need dynamic routes that will +accept multiple parameters, this would be the case, for example of a route for +viewing an article's content:: + + $routes->connect('/articles/*', ['controller' => 'Articles', 'action' => 'view']); + +The above route will accept any URL looking like ``/articles/15`` and invoke the +method ``view(15)`` in the ``ArticlesController``. This will not, though, +prevent people from trying to access URLs looking like ``/articles/foobar``. If +you wish, you can restrict some parameters to conform to a regular expression:: + + $routes->connect( + '/articles/:id', + ['controller' => 'Articles', 'action' => 'view'], + ) + ->setPatterns(['id' => '\d+']) + ->setPass(['id']); + + // Prior to 3.5 use the options array + $routes->connect( + '/articles/:id', + ['controller' => 'Articles', 'action' => 'view'], + ['id' => '\d+', 'pass' => ['id']] + ); + +The previous example changed the star matcher by a new placeholder ``:id``. +Using placeholders allows us to validate parts of the URL, in this case we used +the ``\d+`` regular expression so that only digits are matched. Finally, we told +the Router to treat the ``id`` placeholder as a function argument to the +``view()`` function by specifying the ``pass`` option. More on using this +option later. + +The CakePHP Router can also reverse match routes. That means that from an +array containing matching parameters, it is capable of generating a URL string:: + + use Cake\Routing\Router; + + echo Router::url(['controller' => 'Articles', 'action' => 'view', 'id' => 15]); + // Will output + /articles/15 + +Routes can also be labelled with a unique name, this allows you to quickly +reference them when building links instead of specifying each of the routing +parameters:: + + // In routes.php + $routes->connect( + '/login', + ['controller' => 'Users', 'action' => 'login'], + ['_name' => 'login'] + ); + + use Cake\Routing\Router; + + echo Router::url(['_name' => 'login']); + // Will output + /login + +To help keep your routing code DRY, the Router has the concept of 'scopes'. +A scope defines a common path segment, and optionally route defaults. Any routes +connected inside a scope will inherit the path/defaults from their wrapping +scopes:: + + Router::scope('/blog', ['plugin' => 'Blog'], function ($routes) { + $routes->connect('/', ['controller' => 'Articles']); + }); + +The above route would match ``/blog/`` and send it to +``Blog\Controller\ArticlesController::index()``. + +The application skeleton comes with a few routes to get you started. Once you've +added your own routes, you can remove the default routes if you don't need them. + +.. index:: :controller, :action, :plugin +.. index:: greedy star, trailing star +.. _connecting-routes: +.. _routes-configuration: + +Connecting Routes +================= + +.. php:method:: connect($route, $defaults = [], $options = []) + +To keep your code :term:`DRY` you should use 'routing scopes'. Routing +scopes not only let you keep your code DRY, they also help Router optimize its +operation. This method defaults to the ``/`` scope. To create a scope and connect +some routes we'll use the ``scope()`` method:: + + // In config/routes.php + use Cake\Routing\Route\DashedRoute; + + Router::scope('/', function ($routes) { + // Connect the generic fallback routes. + $routes->fallbacks(DashedRoute::class); + }); + +The ``connect()`` method takes up to three parameters: the URL template you wish +to match, the default values for your route elements, and the options for the +route. Options frequently include regular expression rules to help the router +match elements in the URL. + +The basic format for a route definition is:: + + $routes->connect( + '/url/template', + ['default' => 'defaultValue'], + ['option' => 'matchingRegex'] + ); + +The first parameter is used to tell the router what sort of URL you're trying to +control. The URL is a normal slash delimited string, but can also contain +a wildcard (\*) or :ref:`route-elements`. Using a wildcard tells the router +that you are willing to accept any additional arguments supplied. Routes without +a \* only match the exact template pattern supplied. + +Once you've specified a URL, you use the last two parameters of ``connect()`` to +tell CakePHP what to do with a request once it has been matched. The second +parameter is an associative array. The keys of the array should be named after +the route elements the URL template represents. The values in the array are the +default values for those keys. Let's look at some basic examples before we +start using the third parameter of ``connect()``:: + + $routes->connect( + '/pages/*', + ['controller' => 'Pages', 'action' => 'display'] + ); + +This route is found in the routes.php file distributed with CakePHP. It matches +any URL starting with ``/pages/`` and hands it to the ``display()`` action of +the ``PagesController``. A request to ``/pages/products`` would be mapped to +``PagesController->display('products')``. + +In addition to the greedy star ``/*`` there is also the ``/**`` trailing star +syntax. Using a trailing double star, will capture the remainder of a URL as a +single passed argument. This is useful when you want to use an argument that +included a ``/`` in it:: + + $routes->connect( + '/pages/**', + ['controller' => 'Pages', 'action' => 'show'] + ); + +The incoming URL of ``/pages/the-example-/-and-proof`` would result in a single +passed argument of ``the-example-/-and-proof``. + +You can use the second parameter of ``connect()`` to provide any routing +parameters that are composed of the default values of the route:: + + $routes->connect( + '/government', + ['controller' => 'Pages', 'action' => 'display', 5] + ); + +This example shows how you can use the second parameter of ``connect()`` to +define default parameters. If you built a site that features products for +different categories of customers, you might consider creating a route. This +allows you to link to ``/government`` rather than ``/pages/display/5``. + +A common use for routing is to create URL segments that don't match your +controller or model names. Let's say that instead of accessing our regular URL +at ``/users/some_action/5``, we'd like to be able to access it by +``/cooks/some_action/5``. The following route takes care of that:: + + $routes->connect( + '/cooks/:action/*', ['controller' => 'Users'] + ); + +This is telling the Router that any URL beginning with ``/cooks/`` should be +sent to the ``UsersController``. The action called will depend on the value of +the ``:action`` parameter. By using :ref:`route-elements`, you can create +variable routes, that accept user input or variables. The above route also uses +the greedy star. The greedy star indicates that this route should accept any +additional positional arguments given. These arguments will be made available in +the :ref:`passed-arguments` array. + +When generating URLs, routes are used too. Using +``['controller' => 'Users', 'action' => 'some_action', 5]`` as +a URL will output ``/cooks/some_action/5`` if the above route is the +first match found. + +The routes we've connected so far will match any HTTP verb. If you are building +a REST API you'll often want to map HTTP actions to different controller methods. +The ``RouteBuilder`` provides helper methods that make defining routes for +specific HTTP verbs simpler:: + + // Create a route that only responds to GET requests. + $routes->get( + '/cooks/:id', + ['controller' => 'Users', 'action' => 'view'], + 'users:view' + ); + + // Create a route that only responds to PUT requests + $routes->put( + '/cooks/:id', + ['controller' => 'Users', 'action' => 'update'], + 'users:update' + ); + +The above routes map the same URL to different controller actions based on the +HTTP verb used. GET requests will go to the 'view' action, while PUT requests +will go to the 'update' action. There are HTTP helper methods for: + +* GET +* POST +* PUT +* PATCH +* DELETE +* OPTIONS +* HEAD + +All of these methods return the route instance allowing you to leverage the +:ref:`fluent setters ` to further configure your route. + +.. versionadded:: 3.5.0 + The HTTP verb helper methods were added in 3.5.0 + +.. _route-elements: + +Route Elements +-------------- + +You can specify your own route elements and doing so gives you the +power to define places in the URL where parameters for controller +actions should lie. When a request is made, the values for these +route elements are found in ``$this->request->getParam()`` in the controller. +When you define a custom route element, you can optionally specify a regular +expression - this tells CakePHP how to know if the URL is correctly formed or +not. If you choose to not provide a regular expression, any non ``/`` character +will be treated as part of the parameter:: + + $routes->connect( + '/:controller/:id', + ['action' => 'view'] + )->setPatterns(['id' => '[0-9]+']); + + // Prior to 3.5 use the options array + $routes->connect( + '/:controller/:id', + ['action' => 'view'], + ['id' => '[0-9]+'] + ); + +The above example illustrates how to create a quick way to view +models from any controller by crafting a URL that looks like +``/controllername/:id``. The URL provided to ``connect()`` specifies two +route elements: ``:controller`` and ``:id``. The ``:controller`` element +is a CakePHP default route element, so the router knows how to match and +identify controller names in URLs. The ``:id`` element is a custom +route element, and must be further clarified by specifying a +matching regular expression in the third parameter of ``connect()``. + +CakePHP does not automatically produce lowercased and dashed URLs when using the +``:controller`` parameter. If you need this, the above example could be +rewritten like so:: + + use Cake\Routing\Route\DashedRoute; + + // Create a builder with a different route class. + $routes->scope('/', function ($routes) { + $routes->setRouteClass(DashedRoute::class); + $routes->connect('/:controller/:id', ['action' => 'view']) + ->setPatterns(['id' => '[0-9]+']); + + // Prior to 3.5 use options array + $routes->connect( + '/:controller/:id', + ['action' => 'view'], + ['id' => '[0-9]+'] + ); + }); + +The ``DashedRoute`` class will make sure that the ``:controller`` and +``:plugin`` parameters are correctly lowercased and dashed. + +If you need lowercased and underscored URLs while migrating from a CakePHP +2.x application, you can instead use the ``InflectedRoute`` class. + +.. note:: + + Patterns used for route elements must not contain any capturing + groups. If they do, Router will not function correctly. + +Once this route has been defined, requesting ``/apples/5`` would call the ``view()`` +method of the ApplesController. Inside the ``view()`` method, you would need to +access the passed ID at ``$this->request->getParam('id')``. + +If you have a single controller in your application and you do not want the +controller name to appear in the URL, you can map all URLs to actions in your +controller. For example, to map all URLs to actions of the ``home`` controller, +e.g have URLs like ``/demo`` instead of ``/home/demo``, you can do the +following:: + + $routes->connect('/:action', ['controller' => 'Home']); + +If you would like to provide a case insensitive URL, you can use regular +expression inline modifiers:: + + // Prior to 3.5 use the options array instead of setPatterns() + $routes->connect( + '/:userShortcut', + ['controller' => 'Teachers', 'action' => 'profile', 1], + )->setPatterns(['userShortcut' => '(?i:principal)']); + +One more example, and you'll be a routing pro:: + + // Prior to 3.5 use the options array instead of setPatterns() + $routes->connect( + '/:controller/:year/:month/:day', + ['action' => 'index'] + )->setPatterns([ + 'year' => '[12][0-9]{3}', + 'month' => '0[1-9]|1[012]', + 'day' => '0[1-9]|[12][0-9]|3[01]' + ]); + +This is rather involved, but shows how powerful routes can be. The URL supplied +has four route elements. The first is familiar to us: it's a default route +element that tells CakePHP to expect a controller name. + +Next, we specify some default values. Regardless of the controller, +we want the ``index()`` action to be called. + +Finally, we specify some regular expressions that will match years, +months and days in numerical form. Note that parenthesis (grouping) +are not supported in the regular expressions. You can still specify +alternates, as above, but not grouped with parenthesis. + +Once defined, this route will match ``/articles/2007/02/01``, +``/articles/2004/11/16``, handing the requests to +the ``index()`` actions of their respective controllers, with the date +parameters in ``$this->request->getParam()``. + +There are several route elements that have special meaning in +CakePHP, and should not be used unless you want the special meaning + +* ``controller`` Used to name the controller for a route. +* ``action`` Used to name the controller action for a route. +* ``plugin`` Used to name the plugin a controller is located in. +* ``prefix`` Used for :ref:`prefix-routing` +* ``_ext`` Used for :ref:`File extentions routing `. +* ``_base`` Set to ``false`` to remove the base path from the generated URL. If + your application is not in the root directory, this can be used to generate + URLs that are 'cake relative'. +* ``_scheme`` Set to create links on different schemes like `webcal` or `ftp`. + Defaults to the current scheme. +* ``_host`` Set the host to use for the link. Defaults to the current host. +* ``_port`` Set the port if you need to create links on non-standard ports. +* ``_full`` If ``true`` the `FULL_BASE_URL` constant will be prepended to + generated URLs. +* ``#`` Allows you to set URL hash fragments. +* ``_ssl`` Set to ``true`` to convert the generated URL to https or ``false`` + to force http. +* ``_method`` Define the HTTP verb/method to use. Useful when working with + :ref:`resource-routes`. +* ``_name`` Name of route. If you have setup named routes, you can use this key + to specify it. + +.. _route-fluent-methods: + +Configuring Route Options +------------------------- + +There are a number of route options that can be set on each route. After +connecting a route you can use its fluent builder methods to further configure +the route. These methods replace many of the keys in the ``$options`` parameter +of ``connect()``:: + + $routes->connect( + '/:lang/articles/:slug', + ['controller' => 'Articles', 'action' => 'view'], + ) + // Allow GET and POST requests. + ->setMethods(['GET', 'POST']) + + // Only match on the blog subdomain. + ->setHost('blog.example.com') + + // Set the route elements that should be converted to passed arguments + ->setPass(['slug']) + + // Set the matching patterns for route elements + ->setPatterns([ + 'slug' => '[a-z0-9-_]+', + 'lang' => 'en|fr|es', + ]) + + // Also allow JSON file extensions + ->setExtensions(['json']) + + // Set lang to be a persistent parameter + ->setPersist(['lang']); + +.. versionadded:: 3.5.0 + Fluent builder methods were added in 3.5.0 + +Passing Parameters to Action +---------------------------- + +When connecting routes using :ref:`route-elements` you may want to have routed +elements be passed arguments instead. The ``pass`` option whitelists which route +elements should also be made available as arguments passed into the controller +functions:: + + // src/Controller/BlogsController.php + public function view($articleId = null, $slug = null) + { + // Some code here... + } + + // routes.php + Router::scope('/', function ($routes) { + $routes->connect( + '/blog/:id-:slug', // E.g. /blog/3-CakePHP_Rocks + ['controller' => 'Blogs', 'action' => 'view'] + ) + // Define the route elements in the route template + // to pass as function arguments. Order matters since this + // will simply map ":id" to $articleId in your action + ->setPass(['id', 'slug']) + // Define a pattern that `id` must match. + ->setPatterns([ + 'id' => '[0-9]+', + ]); + }); + +Now thanks to the reverse routing capabilities, you can pass in the URL array +like below and CakePHP will know how to form the URL as defined in the routes:: + + // view.ctp + // This will return a link to /blog/3-CakePHP_Rocks + echo $this->Html->link('CakePHP Rocks', [ + 'controller' => 'Blog', + 'action' => 'view', + 'id' => 3, + 'slug' => 'CakePHP_Rocks' + ]); + + // You can also used numerically indexed parameters. + echo $this->Html->link('CakePHP Rocks', [ + 'controller' => 'Blog', + 'action' => 'view', + 3, + 'CakePHP_Rocks' + ]); + +.. _named-routes: + +Using Named Routes +------------------ + +Sometimes you'll find typing out all the URL parameters for a route too verbose, +or you'd like to take advantage of the performance improvements that named +routes have. When connecting routes you can specifiy a ``_name`` option, this +option can be used in reverse routing to identify the route you want to use:: + + // Connect a route with a name. + $routes->connect( + '/login', + ['controller' => 'Users', 'action' => 'login'], + ['_name' => 'login'] + ); + + // Name a verb specific route (3.5.0+) + $routes->post( + '/logout', + ['controller' => 'Users', 'action' => 'logout'], + 'logout' + ); + + // Generate a URL using a named route. + $url = Router::url(['_name' => 'logout']); + + // Generate a URL using a named route, + // with some query string args. + $url = Router::url(['_name' => 'login', 'username' => 'jimmy']); + +If your route template contains any route elements like ``:controller`` you'll +need to supply those as part of the options to ``Router::url()``. + +.. note:: + + Route names must be unique across your entire application. The same + ``_name`` cannot be used twice, even if the names occur inside a different + routing scope. + +When building named routes, you will probably want to stick to some conventions +for the route names. CakePHP makes building up route names easier by allowing +you to define name prefixes in each scope:: + + Router::scope('/api', ['_namePrefix' => 'api:'], function ($routes) { + // This route's name will be `api:ping` + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); + }); + // Generate a URL for the ping route + Router::url(['_name' => 'api:ping']); + + // Use namePrefix with plugin() + Router::plugin('Contacts', ['_namePrefix' => 'contacts:'], function ($routes) { + // Connect routes. + }); + + // Or with prefix() + Router::prefix('Admin', ['_namePrefix' => 'admin:'], function ($routes) { + // Connect routes. + }); + +You can also use the ``_namePrefix`` option inside nested scopes and it works as +you'd expect:: + + Router::plugin('Contacts', ['_namePrefix' => 'contacts:'], function ($routes) { + $routes->scope('/api', ['_namePrefix' => 'api:'], function ($routes) { + // This route's name will be `contacts:api:ping` + $routes->get('/ping', ['controller' => 'Pings'], 'ping'); + }); + }); + + // Generate a URL for the ping route + Router::url(['_name' => 'contacts:api:ping']); + +Routes connected in named scopes will only have names added if the route is also +named. Nameless routes will not have the ``_namePrefix`` applied to them. + +.. versionadded:: 3.1 + The ``_namePrefix`` option was added in 3.1 + +.. index:: admin routing, prefix routing +.. _prefix-routing: + +Prefix Routing +-------------- + +.. php:staticmethod:: prefix($name, $callback) + +Many applications require an administration section where +privileged users can make changes. This is often done through a +special URL such as ``/admin/users/edit/5``. In CakePHP, prefix routing +can be enabled by using the ``prefix`` scope method:: + + use Cake\Routing\Route\DashedRoute; + + Router::prefix('admin', function ($routes) { + // All routes here will be prefixed with `/admin` + // And have the prefix => admin route element added. + $routes->fallbacks(DashedRoute::class); + }); + +Prefixes are mapped to sub-namespaces in your application's ``Controller`` +namespace. By having prefixes as separate controllers you can create smaller and +simpler controllers. Behavior that is common to the prefixed and non-prefixed +controllers can be encapsulated using inheritance, +:doc:`/controllers/components`, or traits. Using our users example, accessing +the URL ``/admin/users/edit/5`` would call the ``edit()`` method of our +**src/Controller/Admin/UsersController.php** passing 5 as the first parameter. +The view file used would be **src/Template/Admin/Users/edit.ctp** + +You can map the URL /admin to your ``index()`` action of pages controller using +following route:: + + Router::prefix('admin', function ($routes) { + // Because you are in the admin scope, + // you do not need to include the /admin prefix + // or the admin route element. + $routes->connect('/', ['controller' => 'Pages', 'action' => 'index']); + }); + +When creating prefix routes, you can set additional route parameters using +the ``$options`` argument:: + + Router::prefix('admin', ['param' => 'value'], function ($routes) { + // Routes connected here are prefixed with '/admin' and + // have the 'param' routing key set. + $routes->connect('/:controller'); + }); + +You can define prefixes inside plugin scopes as well:: + + Router::plugin('DebugKit', function ($routes) { + $routes->prefix('admin', function ($routes) { + $routes->connect('/:controller'); + }); + }); + +The above would create a route template like ``/debug_kit/admin/:controller``. +The connected route would have the ``plugin`` and ``prefix`` route elements set. + +When defining prefixes, you can nest multiple prefixes if necessary:: + + Router::prefix('manager', function ($routes) { + $routes->prefix('admin', function ($routes) { + $routes->connect('/:controller'); + }); + }); + +The above would create a route template like ``/manager/admin/:controller``. +The connected route would have the ``prefix`` route element set to +``manager/admin``. + +The current prefix will be available from the controller methods through +``$this->request->getParam('prefix')`` + +When using prefix routes it's important to set the prefix option. Here's how to +build this link using the HTML helper:: + + // Go into a prefixed route. + echo $this->Html->link( + 'Manage articles', + ['prefix' => 'manager', 'controller' => 'Articles', 'action' => 'add'] + ); + + // Leave a prefix + echo $this->Html->link( + 'View Post', + ['prefix' => false, 'controller' => 'Articles', 'action' => 'view', 5] + ); + +.. note:: + + You should connect prefix routes *before* you connect fallback routes. + +.. index:: plugin routing + +Plugin Routing +-------------- + +.. php:staticmethod:: plugin($name, $options = [], $callback) + +Routes for :doc:`/plugins` should be created using the ``plugin()`` +method. This method creates a new routing scope for the plugin's routes:: + + Router::plugin('DebugKit', function ($routes) { + // Routes connected here are prefixed with '/debug_kit' and + // have the plugin route element set to 'DebugKit'. + $routes->connect('/:controller'); + }); + +When creating plugin scopes, you can customize the path element used with the +``path`` option:: + + Router::plugin('DebugKit', ['path' => '/debugger'], function ($routes) { + // Routes connected here are prefixed with '/debugger' and + // have the plugin route element set to 'DebugKit'. + $routes->connect('/:controller'); + }); + +When using scopes you can nest plugin scopes within prefix scopes:: + + Router::prefix('admin', function ($routes) { + $routes->plugin('DebugKit', function ($routes) { + $routes->connect('/:controller'); + }); + }); + +The above would create a route that looks like ``/admin/debug_kit/:controller``. +It would have the ``prefix``, and ``plugin`` route elements set. The +:ref:`plugin-routes` section has more information on building plugin routes. + +Creating Links to Plugin Routes +------------------------------- + +You can create links that point to a plugin, by adding the plugin key to your +URL array:: + + echo $this->Html->link( + 'New todo', + ['plugin' => 'Todo', 'controller' => 'TodoItems', 'action' => 'create'] + ); + +Conversely if the active request is a plugin request and you want to create +a link that has no plugin you can do the following:: + + echo $this->Html->link( + 'New todo', + ['plugin' => null, 'controller' => 'Users', 'action' => 'profile'] + ); + +By setting ``'plugin' => null`` you tell the Router that you want to +create a link that is not part of a plugin. + +SEO-Friendly Routing +-------------------- + +Some developers prefer to use dashes in URLs, as it's perceived to give +better search engine rankings. The ``DashedRoute`` class can be used in your +application with the ability to route plugin, controller, and camelized action +names to a dashed URL. + +For example, if we had a ``ToDo`` plugin, with a ``TodoItems`` controller, and a +``showItems()`` action, it could be accessed at ``/to-do/todo-items/show-items`` +with the following router connection:: + + use Cake\Routing\Route\DashedRoute; + + Router::plugin('ToDo', ['path' => 'to-do'], function ($routes) { + $routes->fallbacks(DashedRoute::class); + }); + +Matching Specific HTTP Methods +------------------------------ + +Routes can match specific HTTP methods using the HTTP verb helper methods:: + + Router::scope('/', function($routes) { + // This route only matches on POST requests. + $routes->post( + '/reviews/start', + ['controller' => 'Reviews', 'action' => 'start'] + ); + + // Match multiple verbs + // Prior to 3.5 use $options['_method'] to set method + $routes->connect( + '/reviews/start', + [ + 'controller' => 'Reviews', + 'action' => 'start', + ] + )->setMethods(['POST', 'PUT']); + }); + +You can match multiple HTTP methods by using an array. Because the ``_method`` +parameter is a routing key, it participates in both URL parsing and URL +generation. To generate URLs for method specific routes you'll need to include +the ``_method`` key when generating the URL:: + + $url = Router::url([ + 'controller' => 'Reviews', + 'action' => 'start', + '_method' => 'POST', + ]); + +Matching Specific Hostnames +--------------------------- + +Routes can use the ``_host`` option to only match specific hosts. You can use +the ``*.`` wildcard to match any subdomain:: + + Router::scope('/', function($routes) { + // This route only matches on http://images.example.com + // Prior to 3.5 use the _host option + $routes->connect( + '/images/default-logo.png', + ['controller' => 'Images', 'action' => 'default'] + )->setHost('images.example.com'); + + // This route only matches on http://*.example.com + $routes->connect( + '/images/old-log.png', + ['controller' => 'Images', 'action' => 'oldLogo'] + )->setHost('images.example.com'); + }); + +The ``_host`` option is also used in URL generation. If your ``_host`` option +specifies an exact domain, that domain will be included in the generated URL. +However, if you use a wildcard, then you will need to provide the ``_host`` +parameter when generating URLs:: + + // If you have this route + $routes->connect( + '/images/old-log.png', + ['controller' => 'Images', 'action' => 'oldLogo'] + )->setHost('images.example.com'); + + // You need this to generate a url + echo Router::url([ + 'controller' => 'Images', + 'action' => 'oldLogo', + '_host' => 'images.example.com', + ]); + +.. versionadded:: 3.4.0 + The ``_host`` option was added in 3.4.0 + +.. index:: file extensions +.. _file-extensions: + +Routing File Extensions +----------------------- + +.. php:staticmethod:: extensions(string|array|null $extensions, $merge = true) + +To handle different file extensions with your routes, you can define extensions +on a global, as well as on a scoped level. Defining global extensions can be +achieved via the routers static :php:meth:`Router::extensions()` method:: + + Router::extensions(['json', 'xml']); + // ... + +This will affect **all** routes that are being connected **afterwards**, no matter +their scope. + +In order to restrict extensions to specific scopes, you can define them using the +:php:meth:`Cake\\Routing\\RouteBuilder::setExtensions()` method:: + + Router::scope('/', function ($routes) { + // Prior to 3.5.0 use `extensions()` + $routes->setExtensions(['json', 'xml']); + }); + +This will enable the named extensions for all routes that are being connected in +that scope **after** the ``setExtensions()`` call, including those that are being +connected in nested scopes. Similar to the global :php:meth:`Router::extensions()` +method, any routes connected prior to the call will not inherit the extensions. + +.. note:: + + Setting the extensions should be the first thing you do in a scope, as the + extensions will only be applied to routes connected **after** the extensions + are set. + + Also be aware that re-opened scopes will **not** inherit extensions defined in + previously opened scopes. + +By using extensions, you tell the router to remove any matching file extensions, +and then parse what remains. If you want to create a URL such as +/page/title-of-page.html you would create your route using:: + + Router::scope('/page', function ($routes) { + // Prior to 3.5.0 use `extensions()` + $routes->setExtensions(['json', 'xml', 'html']); + $routes->connect( + '/:title', + ['controller' => 'Pages', 'action' => 'view'] + )->setPass(['title']); + }); + +Then to create links which map back to the routes simply use:: + + $this->Html->link( + 'Link title', + ['controller' => 'Pages', 'action' => 'view', 'title' => 'super-article', '_ext' => 'html'] + ); + +File extensions are used by :doc:`/controllers/components/request-handling` +to do automatic view switching based on content types. + +.. _connecting-scoped-middleware: + +Connecting Scoped Middleware +---------------------------- + +While Middleware can be applied to your entire application, applying middleware +to specific routing scopes offers more flexibility, as you can apply middleware +only where it is needed allowing your middleware to not concern itself with +how/where it is being applied. + +Before middleware can be applied to a scope, it needs to be +registered into the route collection:: + + // in config/routes.php + use Cake\Http\Middleware\CsrfProtectionMiddleware; + use Cake\Http\Middleware\EncryptedCookieMiddleware; + + Router::scope('/', function ($routes) { + $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); + $routes->registerMiddleware('cookies', new EncryptedCookieMiddleware()); + }); + +Once registered, scoped middleware can be applied to specific +scopes:: + + $routes->scope('/cms', function ($routes) { + // Enable CSRF & cookies middleware + $routes->applyMiddleware('csrf', 'cookies'); + $routes->get('/articles/:action/*', ['controller' => 'Articles']) + }); + +In situations where you have nested scopes, inner scopes will inherit the +middleware applied in the containing scope:: + + $routes->scope('/api', function ($routes) { + $routes->applyMiddleware('ratelimit', 'auth.api'); + $routes->scope('/v1', function ($routes) { + $routes->applyMiddleware('v1compat'); + // Define routes here. + }); + }); + +In the above example, the routes defined in ``/v1`` will have 'ratelimit', +'auth.api', and 'v1compat' middleware applied. If you re-open a scope, the +middleware applied to routes in each scope will be isolated:: + + $routes->scope('/blog', function ($routes) { + $routes->applyMiddleware('auth'); + // Connect the authenticated actions for the blog here. + }); + $routes->scope('/blog', function ($routes) { + // Connect the public actions for the blog here. + }); + +In the above example, the two uses of the ``/blog`` scope do not share +middleware. However, both of these scopes will inherit middleware defined in +their enclosing scopes. + +Grouping Middleware +------------------- + +To help keep your route code :abbr:`DRY (Do not Repeat Yourself)` middleware can +be combined into groups. Once combined groups can be applied like middleware +can:: + + $routes->registerMiddleware('cookie', new EncryptedCookieMiddleware()); + $routes->registerMiddleware('auth', new AuthenticationMiddleware()); + $routes->registerMiddleware('csrf', new CsrfProtectionMiddleware()); + $routes->middlewareGroup('web', ['cookie', 'auth', 'csrf']); + + // Apply the group + $routes->applyMiddleware('web'); + +.. versionadded:: 3.5.0 + Scoped middleware & middleware groups were added in 3.5.0 + +.. _resource-routes: + +Creating RESTful Routes +======================= + +Router makes it easy to generate RESTful routes for your controllers. RESTful +routes are helpful when you are creating API endpoints for your application. If +we wanted to allow REST access to a recipe controller, we'd do something like +this:: + + // In config/routes.php... + + Router::scope('/', function ($routes) { + // Prior to 3.5.0 use `extensions()` + $routes->setExtensions(['json']); + $routes->resources('Recipes'); + }); + +The first line sets up a number of default routes for easy REST +access where method specifies the desired result format (e.g. xml, +json, rss). These routes are HTTP Request Method sensitive. + +=========== ===================== ============================== +HTTP format URL.format Controller action invoked +=========== ===================== ============================== +GET /recipes.format RecipesController::index() +----------- --------------------- ------------------------------ +GET /recipes/123.format RecipesController::view(123) +----------- --------------------- ------------------------------ +POST /recipes.format RecipesController::add() +----------- --------------------- ------------------------------ +PUT /recipes/123.format RecipesController::edit(123) +----------- --------------------- ------------------------------ +PATCH /recipes/123.format RecipesController::edit(123) +----------- --------------------- ------------------------------ +DELETE /recipes/123.format RecipesController::delete(123) +=========== ===================== ============================== + +CakePHP's Router class uses a number of different indicators to +detect the HTTP method being used. Here they are in order of +preference: + +#. The \_method POST variable +#. The X\_HTTP\_METHOD\_OVERRIDE +#. The REQUEST\_METHOD header + +The \_method POST variable is helpful in using a browser as a +REST client (or anything else that can do POST). Just set +the value of \_method to the name of the HTTP request method you +wish to emulate. + +Creating Nested Resource Routes +------------------------------- + +Once you have connected resources in a scope, you can connect routes for +sub-resources as well. Sub-resource routes will be prepended by the original +resource name and a id parameter. For example:: + + Router::scope('/api', function ($routes) { + $routes->resources('Articles', function ($routes) { + $routes->resources('Comments'); + }); + }); + +Will generate resource routes for both ``articles`` and ``comments``. The +comments routes will look like:: + + /api/articles/:article_id/comments + /api/articles/:article_id/comments/:id + +You can get the ``article_id`` in ``CommentsController`` by:: + + $this->request->getParam('article_id'); + +By default resource routes map to the same prefix as the containing scope. If +you have both nested and non-nested resource controllers you can use a different +controller in each context by using prefixes:: + + Router::scope('/api', function ($routes) { + $routes->resources('Articles', function ($routes) { + $routes->resources('Comments', ['prefix' => 'articles']); + }); + }); + +The above would map the 'Comments' resource to the +``App\Controller\Articles\CommentsController``. Having separate controllers lets +you keep your controller logic simpler. The prefixes created this way are +compatible with :ref:`prefix-routing`. + +.. note:: + + While you can nest resources as deeply as you require, it is not recommended + to nest more than 2 resources together. + +.. versionadded:: 3.3 + The ``prefix`` option was added to ``resources()`` in 3.3. + +Limiting the Routes Created +--------------------------- + +By default CakePHP will connect 6 routes for each resource. If you'd like to +only connect specific resource routes you can use the ``only`` option:: + + $routes->resources('Articles', [ + 'only' => ['index', 'view'] + ]); + +Would create read only resource routes. The route names are ``create``, +``update``, ``view``, ``index``, and ``delete``. + +Changing the Controller Actions Used +------------------------------------ + +You may need to change the controller action names that are used when connecting +routes. For example, if your ``edit()`` action is called ``put()`` you can +use the ``actions`` key to rename the actions used:: + + $routes->resources('Articles', [ + 'actions' => ['update' => 'put', 'create' => 'add'] + ]); + +The above would use ``put()`` for the ``edit()`` action, and ``add()`` +instead of ``create()``. + +Mapping Additional Resource Routes +---------------------------------- + +You can map additional resource methods using the ``map`` option:: + + $routes->resources('Articles', [ + 'map' => [ + 'deleteAll' => [ + 'action' => 'deleteAll', + 'method' => 'DELETE' + ] + ] + ]); + // This would connect /articles/deleteAll + +In addition to the default routes, this would also connect a route for +`/articles/delete_all`. By default the path segment will match the key name. You +can use the 'path' key inside the resource definition to customize the path +name:: + + $routes->resources('Articles', [ + 'map' => [ + 'updateAll' => [ + 'action' => 'updateAll', + 'method' => 'DELETE', + 'path' => '/update_many' + ], + ] + ]); + // This would connect /articles/update_many + +If you define 'only' and 'map', make sure that your mapped methods are also in +the 'only' list. + +.. _custom-rest-routing: + +Custom Route Classes for Resource Routes +---------------------------------------- + +You can provide ``connectOptions`` key in the ``$options`` array for +``resources()`` to provide custom setting used by ``connect()``:: + + Router::scope('/', function ($routes) { + $routes->resources('Books', [ + 'connectOptions' => [ + 'routeClass' => 'ApiRoute', + ] + ]; + }); + +URL Inflection for Resource Routes +---------------------------------- + +By default, multi-worded controllers' URL fragments are the underscored +form of the controller's name. E.g., ``BlogPostsController``'s URL fragment +would be **/blog_posts**. + +You can specify an alternative inflection type using the ``inflect`` option:: + + Router::scope('/', function ($routes) { + $routes->resources('BlogPosts', [ + 'inflect' => 'dasherize' // Will use ``Inflector::dasherize()`` + ]); + }); + +The above will generate URLs styled like: **/blog-posts**. + +.. note:: + + As of CakePHP 3.1 the official app skeleton uses ``DashedRoute`` as its + default route class. Using the ``'inflect' => 'dasherize'`` option when + connecting resource routes is recommended for URL consistency. + +Changing the Path Element +------------------------- + +By default resource routes use an inflected form of the resource name for the +URL segment. You can set a custom URL segment with the ``path`` option:: + + Router::scope('/', function ($routes) { + $routes->resources('BlogPosts', ['path' => 'posts']); + }); + +.. versionadded:: 3.5.0 + The ``path`` option was added in 3.5.0 + +.. index:: passed arguments +.. _passed-arguments: + +Passed Arguments +================ + +Passed arguments are additional arguments or path segments that are +used when making a request. They are often used to pass parameters +to your controller methods. :: + + http://localhost/calendars/view/recent/mark + +In the above example, both ``recent`` and ``mark`` are passed arguments to +``CalendarsController::view()``. Passed arguments are given to your controllers +in three ways. First as arguments to the action method called, and secondly they +are available in ``$this->request->getParam('pass')`` as a numerically indexed +array. When using custom routes you can force particular parameters to go into +the passed arguments as well. + +If you were to visit the previously mentioned URL, and you +had a controller action that looked like:: + + class CalendarsController extends AppController + { + public function view($arg1, $arg2) + { + debug(func_get_args()); + } + } + +You would get the following output:: + + Array + ( + [0] => recent + [1] => mark + ) + +This same data is also available at ``$this->request->getParam('pass')`` in your +controllers, views, and helpers. The values in the pass array are numerically +indexed based on the order they appear in the called URL:: + + debug($this->request->getParam('pass')); + +Either of the above would output:: + + Array + ( + [0] => recent + [1] => mark + ) + +When generating URLs, using a :term:`routing array` you add passed +arguments as values without string keys in the array:: + + ['controller' => 'Articles', 'action' => 'view', 5] + +Since ``5`` has a numeric key, it is treated as a passed argument. + +Generating URLs +=============== + +.. php:staticmethod:: url($url = null, $full = false) + +Generating URLs or Reverse routing is a feature in CakePHP that is used to +allow you to change your URL structure without having to modify all your +code. By using :term:`routing arrays ` to define your URLs, you +can later configure routes and the generated URLs will automatically update. + +If you create URLs using strings like:: + + $this->Html->link('View', '/articles/view/' . $id); + +And then later decide that ``/articles`` should really be called +'posts' instead, you would have to go through your entire +application renaming URLs. However, if you defined your link like:: + + $this->Html->link( + 'View', + ['controller' => 'Articles', 'action' => 'view', $id] + ); + +Then when you decided to change your URLs, you could do so by defining a +route. This would change both the incoming URL mapping, as well as the +generated URLs. + +When using array URLs, you can define both query string parameters and +document fragments using special keys:: + + Router::url([ + 'controller' => 'Articles', + 'action' => 'index', + '?' => ['page' => 1], + '#' => 'top' + ]); + + // Will generate a URL like. + /articles/index?page=1#top + +Router will also convert any unknown parameters in a routing array to +querystring parameters. The ``?`` is offered for backwards compatibility with +older versions of CakePHP. + +You can also use any of the special route elements when generating URLs: + +* ``_ext`` Used for :ref:`file-extensions` routing. +* ``_base`` Set to ``false`` to remove the base path from the generated URL. If + your application is not in the root directory, this can be used to generate + URLs that are 'cake relative'. +* ``_scheme`` Set to create links on different schemes like ``webcal`` or + ``ftp``. Defaults to the current scheme. +* ``_host`` Set the host to use for the link. Defaults to the current host. +* ``_port`` Set the port if you need to create links on non-standard ports. +* ``_method`` Define the HTTP verb the URL is for. +* ``_full`` If ``true`` the ``FULL_BASE_URL`` constant will be prepended to + generated URLs. +* ``_ssl`` Set to ``true`` to convert the generated URL to https or ``false`` + to force http. +* ``_name`` Name of route. If you have setup named routes, you can use this key + to specify it. + +.. _redirect-routing: + +Redirect Routing +================ + +Redirect routing allows you to issue HTTP status 30x redirects for +incoming routes, and point them at different URLs. This is useful +when you want to inform client applications that a resource has moved +and you don't want to expose two URLs for the same content. + +Redirection routes are different from normal routes as they perform an actual +header redirection if a match is found. The redirection can occur to +a destination within your application or an outside location:: + + Router::scope('/', function ($routes) { + $routes->redirect( + '/home/*', + ['controller' => 'Articles', 'action' => 'view'], + ['persist' => true] + // Or ['persist'=>['id']] for default routing where the + // view action expects $id as an argument. + ); + }) + +Redirects ``/home/*`` to ``/articles/view`` and passes the parameters to +``/articles/view``. Using an array as the redirect destination allows +you to use other routes to define where a URL string should be +redirected to. You can redirect to external locations using +string URLs as the destination:: + + Router::scope('/', function ($routes) { + $routes->redirect('/articles/*', 'http://google.com', ['status' => 302]); + }); + +This would redirect ``/articles/*`` to ``http://google.com`` with a +HTTP status of 302. + +.. _custom-route-classes: + +Custom Route Classes +==================== + +Custom route classes allow you to extend and change how individual routes parse +requests and handle reverse routing. Route classes have a few conventions: + +* Route classes are expected to be found in the ``Routing\\Route`` namespace of + your application or plugin. +* Route classes should extend :php:class:`Cake\\Routing\\Route`. +* Route classes should implement one or both of ``match()`` and/or ``parse()``. + +The ``parse()`` method is used to parse an incoming URL. It should generate an +array of request parameters that can be resolved into a controller & action. +Return ``false`` from this method to indicate a match failure. + +The ``match()`` method is used to match an array of URL parameters and create a +string URL. If the URL parameters do not match the route ``false`` should be +returned. + +You can use a custom route class when making a route by using the ``routeClass`` +option:: + + $routes->connect( + '/:slug', + ['controller' => 'Articles', 'action' => 'view'], + ['routeClass' => 'SlugRoute'] + ); + + // Or by setting the routeClass in your scope. + $routes->scope('/', function ($routes) { + //Prior to 3.5.0 use `routeClass()` + $routes->setRouteClass('SlugRoute'); + $routes->connect( + '/:slug', + ['controller' => 'Articles', 'action' => 'view'] + ); + }); + +This route would create an instance of ``SlugRoute`` and allow you +to implement custom parameter handling. You can use plugin route classes using +standard :term:`plugin syntax`. + +Default Route Class +------------------- + +.. php:staticmethod:: defaultRouteClass($routeClass = null) + +If you want to use an alternate route class for all your routes besides the +default ``Route``, you can do so by calling ``Router::defaultRouteClass()`` +before setting up any routes and avoid having to specify the ``routeClass`` +option for each route. For example using:: + + use Cake\Routing\Route\InflectedRoute; + + Router::defaultRouteClass(InflectedRoute::class); + +will cause all routes connected after this to use the ``InflectedRoute`` route class. +Calling the method without an argument will return current default route class. + +Fallbacks Method +---------------- + +.. php:method:: fallbacks($routeClass = null) + +The fallbacks method is a simple shortcut for defining default routes. The +method uses the passed routing class for the defined rules or if no class is +provided the class returned by ``Router::defaultRouteClass()`` is used. + +Calling fallbacks like so:: + + use Cake\Routing\Route\DashedRoute; + + $routes->fallbacks(DashedRoute::class); + +Is equivalent to the following explicit calls:: + + use Cake\Routing\Route\DashedRoute; + + $routes->connect('/:controller', ['action' => 'index'], ['routeClass' => DashedRoute::class]); + $routes->connect('/:controller/:action/*', [], ['routeClass' => DashedRoute::class]); + +.. note:: + + Using the default route class (``Route``) with fallbacks, or any route + with ``:plugin`` and/or ``:controller`` route elements will result in + inconsistent URL case. + +Creating Persistent URL Parameters +================================== + +You can hook into the URL generation process using URL filter functions. Filter +functions are called *before* the URLs are matched against the routes, this +allows you to prepare URLs before routing. + +Callback filter functions should expect the following parameters: + +- ``$params`` The URL params being processed. +- ``$request`` The current request. + +The URL filter function should *always* return the params even if unmodified. + +URL filters allow you to implement features like persistent parameters:: + + Router::addUrlFilter(function ($params, $request) { + if ($request->getParam('lang') && !isset($params['lang'])) { + $params['lang'] = $request->getParam('lang'); + } + return $params; + }); + +Filter functions are applied in the order they are connected. + +Another use case is changing a certain route on runtime (plugin routes for +example):: + + Router::addUrlFilter(function ($params, $request) { + if (empty($params['plugin']) || $params['plugin'] !== 'MyPlugin' || empty($params['controller'])) { + return $params; + } + if ($params['controller'] === 'Languages' && $params['action'] === 'view') { + $params['controller'] = 'Locations'; + $params['action'] = 'index'; + $params['language'] = $params[0]; + unset($params[0]); + } + return $params; + }); + +This will alter the following route:: + + Router::url(['plugin' => 'MyPlugin', 'controller' => 'Languages', 'action' => 'view', 'es']); + +into this:: + + Router::url(['plugin' => 'MyPlugin', 'controller' => 'Locations', 'action' => 'index', 'language' => 'es']); + +Handling Named Parameters in URLs +================================= + +Although named parameters were removed in CakePHP 3.0, applications may have +published URLs containing them. You can continue to accept URLs containing +named parameters. + +In your controller's ``beforeFilter()`` method you can call +``parseNamedParams()`` to extract any named parameters from the passed +arguments:: + + public function beforeFilter(Event $event) + { + parent::beforeFilter($event); + Router::parseNamedParams($this->request); + } + +This will populate ``$this->request->getParam('named')`` with any named parameters +found in the passed arguments. Any passed argument that was interpreted as a +named parameter, will be removed from the list of passed arguments. + +.. toctree:: + :glob: + :maxdepth: 1 + + /development/dispatch-filters + +.. meta:: + :title lang=en: Routing + :keywords lang=en: controller actions,default routes,mod rewrite,code index,string url,php class,incoming requests,dispatcher,url url,meth,maps,match,parameters,array,config,cakephp,apache,router diff --git a/tl/development/sessions.rst b/tl/development/sessions.rst new file mode 100644 index 0000000000000000000000000000000000000000..8cc5b3b01fa9c0cf59bb27d780f20f4db4377b41 --- /dev/null +++ b/tl/development/sessions.rst @@ -0,0 +1,413 @@ +Sessions +######## + +CakePHP provides a wrapper and suite of utility features on top of PHP's native +``session`` extension. Sessions allow you to identify unique users across +requests and store persistent data for specific users. Unlike Cookies, session +data is not available on the client side. Usage of ``$_SESSION`` is generally +avoided in CakePHP, and instead usage of the Session classes is preferred. + +.. _session-configuration: + +Session Configuration +===================== + +Session configuration is generally defined in ``/config/app.php``. The available +options are: + +* ``Session.timeout`` - The number of *minutes* before CakePHP's session + handler expires the session. + +* ``Session.defaults`` - Allows you to use the built-in default session + configurations as a base for your session configuration. See below for the + built-in defaults. + +* ``Session.handler`` - Allows you to define a custom session handler. The core + database and cache session handlers use this. See below for additional + information on Session handlers. + +* ``Session.ini`` - Allows you to set additional session ini settings for your + config. This combined with ``Session.handler`` replace the custom session + handling features of previous versions + +* ``Session.cookie`` - The name of the cookie to use. Defaults to 'CAKEPHP'. + +* ``Session.cookiePath`` - The url path for which session cookie is set. Maps to + the ``session.cookie_path`` php.ini config. Defaults to base path of app. + +CakePHP's defaults ``session.cookie_secure`` to ``true``, when your application +is on an SSL protocol. If your application serves from both SSL and non-SSL +protocols, then you might have problems with sessions being lost. If you need +access to the session on both SSL and non-SSL domains you will want to disable +this:: + + Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_secure' => false + ] + ]); + +The session cookie path defaults to app's base path. To change this you can use +the ``session.cookie_path`` ini value. For example if you want your session to +persist across all subdomains you can do:: + + Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_path' => '/', + 'session.cookie_domain' => '.yourdomain.com' + ] + ]); + +By default PHP sets the session cookie to expire as soon as the browser is +closed, regardless of the configured ``Session.timeout`` value. The cookie +timeout is controlled by the ``session.cookie_lifetime`` ini value and can be +configured using:: + + Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + // Invalidate the cookie after 30 minutes without visiting + // any page on the site. + 'session.cookie_lifetime' => 1800 + ] + ]); + +The difference between ``Session.timeout`` and the ``session.cookie_lifetime`` +value is that the latter relies on the client telling the truth about the +cookie. If you require stricter timeout checking, without relying on what the +client reports, you should use ``Session.timeout``. + +Please note that ``Session.timeout`` corresponds to the total time of +inactivity for a user (i.e. the time without visiting any page where the session +is used), and does not limit the total amount of minutes a user can stay +on the site. + +Built-in Session Handlers & Configuration +========================================= + +CakePHP comes with several built-in session configurations. You can either use +these as the basis for your session configuration, or you can create a fully +custom solution. To use defaults, simply set the 'defaults' key to the name of +the default you want to use. You can then override any sub setting by declaring +it in your Session config:: + + Configure::write('Session', [ + 'defaults' => 'php' + ]); + +The above will use the built-in 'php' session configuration. You could augment +part or all of it by doing the following:: + + Configure::write('Session', [ + 'defaults' => 'php', + 'cookie' => 'my_app', + 'timeout' => 4320 // 3 days + ]); + +The above overrides the timeout and cookie name for the 'php' session +configuration. The built-in configurations are: + +* ``php`` - Saves sessions with the standard settings in your php.ini file. +* ``cake`` - Saves sessions as files inside ``tmp/sessions``. This is a + good option when on hosts that don't allow you to write outside your own home + dir. +* ``database`` - Use the built-in database sessions. See below for more + information. +* ``cache`` - Use the built-in cache sessions. See below for more information. + +Session Handlers +---------------- + +Session handlers can also be defined in the session config array. By defining +the 'handler.engine' config key, you can name the class name, or provide +a handler instance. The class/object must implement the +native PHP ``SessionHandlerInterface``. Implementing this interface will allow +``Session`` to automatically map the methods for the handler. Both the core +Cache and Database session handlers use this method for saving sessions. +Additional settings for the handler should be placed inside the handler array. +You can then read those values out from inside your handler:: + + 'Session' => [ + 'handler' => [ + 'engine' => 'DatabaseSession', + 'model' => 'CustomSessions' + ] + ] + +The above shows how you could setup the Database session handler with an +application model. When using class names as your handler.engine, CakePHP will +expect to find your class in the ``Network\Session`` namespace. For example, if +you had an ``AppSessionHandler`` class, the file should be +**src/Network/Session/AppSessionHandler.php**, and the class name should be +``App\Network\Session\AppSessionHandler``. You can also use session handlers +from inside plugins. By setting the engine to ``MyPlugin.PluginSessionHandler``. + +Database Sessions +----------------- + +If you need to use a database to store your session data, configure as follows:: + + 'Session' => [ + 'defaults' => 'database' + ] + +This configuration requires a database table, having this schema:: + + CREATE TABLE `sessions` ( + `id` char(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL, + `created` datetime DEFAULT CURRENT_TIMESTAMP, -- Optional + `modified` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -- Optional + `data` blob DEFAULT NULL, -- for PostgreSQL use bytea instead of blob + `expires` int(10) unsigned DEFAULT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +You can find a copy of the schema for the sessions table in the `application skeleton `_ in ``config/schema/sessions.sql``. + +You can also use your own ``Table`` class to handle the saving of the sessions:: + + 'Session' => [ + 'defaults' => 'database', + 'handler' => [ + 'engine' => 'DatabaseSession', + 'model' => 'CustomSessions' + ] + ] + +The above will tell Session to use the built-in 'database' defaults, and +specify that a Table called ``CustomSessions`` will be the delegate for saving +session information to the database. + +Cache Sessions +-------------- + +The Cache class can be used to store sessions as well. This allows you to store +sessions in a cache like APC, Memcached, or XCache. There are some caveats to +using cache sessions, in that if you exhaust the cache space, sessions will +start to expire as records are evicted. + +To use Cache based sessions you can configure you Session config like:: + + Configure::write('Session', [ + 'defaults' => 'cache', + 'handler' => [ + 'config' => 'session' + ] + ]); + +This will configure Session to use the ``CacheSession`` class as the +delegate for saving the sessions. You can use the 'config' key which cache +configuration to use. The default cache configuration is ``'default'``. + +Setting ini directives +====================== + +The built-in defaults attempt to provide a common base for session +configuration. You may need to tweak specific ini flags as well. CakePHP +exposes the ability to customize the ini settings for both default +configurations, as well as custom ones. The ``ini`` key in the session settings, +allows you to specify individual configuration values. For example you can use +it to control settings like ``session.gc_divisor``:: + + Configure::write('Session', [ + 'defaults' => 'php', + 'ini' => [ + 'session.cookie_name' => 'MyCookie', + 'session.cookie_lifetime' => 1800, // Valid for 30 minutes + 'session.gc_divisor' => 1000, + 'session.cookie_httponly' => true + ] + ]); + +Creating a Custom Session Handler +================================= + +Creating a custom session handler is straightforward in CakePHP. In this +example we'll create a session handler that stores sessions both in the Cache +(APC) and the database. This gives us the best of fast IO of APC, +without having to worry about sessions evaporating when the cache fills up. + +First we'll need to create our custom class and put it in +**src/Network/Session/ComboSession.php**. The class should look +something like:: + + namespace App\Network\Session; + + use Cake\Cache\Cache; + use Cake\Core\Configure; + use Cake\Network\Session\DatabaseSession; + + class ComboSession extends DatabaseSession + { + public $cacheKey; + + public function __construct() + { + $this->cacheKey = Configure::read('Session.handler.cache'); + parent::__construct(); + } + + // Read data from the session. + public function read($id) + { + $result = Cache::read($id, $this->cacheKey); + if ($result) { + return $result; + } + return parent::read($id); + } + + // Write data into the session. + public function write($id, $data) + { + Cache::write($id, $data, $this->cacheKey); + return parent::write($id, $data); + } + + // Destroy a session. + public function destroy($id) + { + Cache::delete($id, $this->cacheKey); + return parent::destroy($id); + } + + // Removes expired sessions. + public function gc($expires = null) + { + return Cache::gc($this->cacheKey) && parent::gc($expires); + } + } + +Our class extends the built-in ``DatabaseSession`` so we don't have to duplicate +all of its logic and behavior. We wrap each operation with +a :php:class:`Cake\\Cache\\Cache` operation. This lets us fetch sessions from +the fast cache, and not have to worry about what happens when we fill the cache. +Using this session handler is also easy. In your **app.php** make the session +block look like the following:: + + 'Session' => [ + 'defaults' => 'database', + 'handler' => [ + 'engine' => 'ComboSession', + 'model' => 'Session', + 'cache' => 'apc' + ] + ], + // Make sure to add a apc cache config + 'Cache' => [ + 'apc' => ['engine' => 'Apc'] + ] + +Now our application will start using our custom session handler for reading and +writing session data. + +.. php:class:: Session + +.. _accessing-session-object: + +Accessing the Session Object +============================ + +You can access the session data any place you have access to a request object. +This means the session is accessible from: + +* Controllers +* Views +* Helpers +* Cells +* Components + +In addition to the basic session object, you can also use the +:php:class:`Cake\\View\\Helper\\SessionHelper` to interact with the session in +your views. A basic example of session usage would be:: + + $name = $this->request->session()->read('User.name'); + + // If you are accessing the session multiple times, + // you will probably want a local variable. + $session = $this->request->session(); + $name = $session->read('User.name'); + +Reading & Writing Session Data +============================== + +.. php:method:: read($key) + +You can read values from the session using :php:meth:`Hash::extract()` +compatible syntax:: + + $session->read('Config.language'); + +.. php:method:: write($key, $value) + +``$key`` should be the dot separated path you wish to write ``$value`` to:: + + $session->write('Config.language', 'en'); + +You may also specify one or multiple hashes like so:: + + $session->write([ + 'Config.theme' => 'blue', + 'Config.language' => 'en', + ]); + +.. php:method:: delete($key) + +When you need to delete data from the session, you can use ``delete()``:: + + $session->delete('Some.value'); + +.. php:staticmethod:: consume($key) + +When you need to read and delete data from the session, you can use +``consume()``:: + + $session->consume('Some.value'); + +.. php:method:: check($key) + +If you want to see if data exists in the session, you can use ``check()``:: + + if ($session->check('Config.language')) { + // Config.language exists and is not null. + } + +Destroying the Session +====================== + +.. php:method:: destroy() + +Destroying the session is useful when users log out. To destroy a session, use +the ``destroy()`` method:: + + $session->destroy(); + +Destroying a session will remove all serverside data in the session, but will +**not** remove the session cookie. + +Rotating Session Identifiers +============================ + +.. php:method:: renew() + +While ``AuthComponent`` automatically renews the session id when users login and +logout, you may need to rotate the session id's manually. To do this use the +``renew()`` method:: + + $session->renew(); + +Flash Messages +============== + +Flash messages are small messages displayed to end users once. They are often +used to present error messages, or confirm that actions took place successfully. + +To set and display flash messages you should use +:doc:`/controllers/components/flash` and +:doc:`/views/helpers/flash` + +.. meta:: + :title lang=en: Sessions + :keywords lang=en: session defaults,session classes,utility features,session timeout,session ids,persistent data,session key,session cookie,session data,last session,core database,security level,useragent,security reasons,session id,attr,countdown,regeneration,sessions,config diff --git a/tl/development/testing.rst b/tl/development/testing.rst new file mode 100644 index 0000000000000000000000000000000000000000..a1846a63d17286bbeb96bbc8d92b28008fac0c7f --- /dev/null +++ b/tl/development/testing.rst @@ -0,0 +1,2082 @@ +Testing +####### + +CakePHP comes with comprehensive testing support built-in. CakePHP comes with +integration for `PHPUnit `_. In addition to the features +offered by PHPUnit, CakePHP offers some additional features to make testing +easier. This section will cover installing PHPUnit, and getting started with +Unit Testing, and how you can use the extensions that CakePHP offers. + +Installing PHPUnit +================== + +CakePHP uses PHPUnit as its underlying test framework. PHPUnit is the de-facto +standard for unit testing in PHP. It offers a deep and powerful set of features +for making sure your code does what you think it does. PHPUnit can be installed +through using either a `PHAR package `__ or +`Composer `_. + +Install PHPUnit with Composer +----------------------------- + +To install PHPUnit with Composer: + +.. code-block:: bash + + $ php composer.phar require --dev phpunit/phpunit:"^5.7|^6.0" + + // Before CakePHP 3.4.1 + $ php composer.phar require --dev phpunit/phpunit:"<6.0" + +This will add the dependency to the ``require-dev`` section of your +``composer.json``, and then install PHPUnit along with any dependencies. + +You can now run PHPUnit using: + +.. code-block:: bash + + $ vendor/bin/phpunit + +Using the PHAR File +------------------- + +After you have downloaded the **phpunit.phar** file, you can use it to run your +tests: + +.. code-block:: bash + + php phpunit.phar + +.. tip:: + + As a convenience you can make phpunit.phar available globally + on Unix or Linux with the following: + + .. code-block:: shell + + chmod +x phpunit.phar + sudo mv phpunit.phar /usr/local/bin/phpunit + phpunit --version + + Please refer to the PHPUnit documentation for instructions regarding + `Globally installing the PHPUnit PHAR on Windows `__. + +Test Database Setup +=================== + +Remember to have debug enabled in your **config/app.php** file before running +any tests. Before running any tests you should be sure to add a ``test`` +datasource configuration to **config/app.php**. This configuration is used by +CakePHP for fixture tables and data:: + + 'Datasources' => [ + 'test' => [ + 'datasource' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'dbhost', + 'username' => 'dblogin', + 'password' => 'dbpassword', + 'database' => 'test_database' + ], + ], + +.. note:: + + It's a good idea to make the test database and your actual database + different databases. This will prevent embarrassing mistakes later. + +Checking the Test Setup +======================= + +After installing PHPUnit and setting up your ``test`` datasource configuration +you can make sure you're ready to write and run your own tests by running your +application's tests: + +.. code-block:: bash + + # For phpunit.phar + $ php phpunit.phar + + # For Composer installed phpunit + $ vendor/bin/phpunit + +The above should run any tests you have, or let you know that no tests were run. +To run a specific test you can supply the path to the test as a parameter to +PHPUnit. For example, if you had a test case for ArticlesTable class you could +run it with: + +.. code-block:: bash + + $ vendor/bin/phpunit tests/TestCase/Model/Table/ArticlesTableTest + +You should see a green bar with some additional information about the tests run, +and number passed. + +.. note:: + + If you are on a Windows system you probably won't see any colours. + +Test Case Conventions +===================== + +Like most things in CakePHP, test cases have some conventions. Concerning +tests: + +#. PHP files containing tests should be in your + ``tests/TestCase/[Type]`` directories. +#. The filenames of these files should end in **Test.php** instead + of just .php. +#. The classes containing tests should extend ``Cake\TestSuite\TestCase``, + ``Cake\TestSuite\IntegrationTestCase`` or ``\PHPUnit\Framework\TestCase``. +#. Like other classnames, the test case classnames should match the filename. + **RouterTest.php** should contain ``class RouterTest extends TestCase``. +#. The name of any method containing a test (i.e. containing an + assertion) should begin with ``test``, as in ``testPublished()``. + You can also use the ``@test`` annotation to mark methods as test methods. + +.. versionadded:: 3.4.1 + Support for PHPUnit 6 was addded. If you're using a PHPUnit version lower + than 5.7.0, your tests classes should either extends CakePHP's classes or + ``PHPUnit_Framework_TestCase``. + +Creating Your First Test Case +============================= + +In the following example, we'll create a test case for a very simple helper +method. The helper we're going to test will be formatting progress bar HTML. +Our helper looks like:: + + namespace App\View\Helper; + + use Cake\View\Helper; + + class ProgressHelper extends Helper + { + public function bar($value) + { + $width = round($value / 100, 2) * 100; + return sprintf( + '
    +
    +
    ', $width); + } + } + +This is a very simple example, but it will be useful to show how you can create +a simple test case. After creating and saving our helper, we'll create the test +case file in **tests/TestCase/View/Helper/ProgressHelperTest.php**. In that file +we'll start with the following:: + + namespace App\Test\TestCase\View\Helper; + + use App\View\Helper\ProgressHelper; + use Cake\TestSuite\TestCase; + use Cake\View\View; + + class ProgressHelperTest extends TestCase + { + public function setUp() + { + + } + + public function testBar() + { + + } + } + +We'll flesh out this skeleton in a minute. We've added two methods to start +with. First is ``setUp()``. This method is called before every *test* method +in a test case class. Setup methods should initialize the objects needed for the +test, and do any configuration needed. In our setup method we'll add the +following:: + + public function setUp() + { + parent::setUp(); + $View = new View(); + $this->Progress = new ProgressHelper($View); + } + +Calling the parent method is important in test cases, as ``TestCase::setUp()`` +does a number things like backing up the values in +:php:class:`~Cake\\Core\\Configure` and, storing the paths in +:php:class:`~Cake\\Core\\App`. + +Next, we'll fill out the test method. We'll use some assertions to ensure that +our code creates the output we expect:: + + public function testBar() + { + $result = $this->Progress->bar(90); + $this->assertContains('width: 90%', $result); + $this->assertContains('progress-bar', $result); + + $result = $this->Progress->bar(33.3333333); + $this->assertContains('width: 33%', $result); + } + +The above test is a simple one but shows the potential benefit of using test +cases. We use ``assertContains()`` to ensure that our helper is returning a +string that contains the content we expect. If the result did not contain the +expected content the test would fail, and we would know that our code is +incorrect. + +By using test cases you can describe the relationship between a set of +known inputs and their expected output. This helps you be more confident of the +code you're writing as you can ensure that the code you wrote fulfills the +expectations and assertions your tests make. Additionally because tests are +code, they are easy to re-run whenever you make a change. This helps prevent +the creation of new bugs. + +.. _running-tests: + +Running Tests +============= + +Once you have PHPUnit installed and some test cases written, you'll want to run +the test cases very frequently. It's a good idea to run tests before committing +any changes to help ensure you haven't broken anything. + +By using ``phpunit`` you can run your application tests. To run your +application's tests you can simply run: + +.. code-block:: bash + + # composer installs + $ vendor/bin/phpunit + + # phar file + php phpunit.phar + +If you have cloned the `CakePHP source from GitHub `__ +and wish to run CakePHP's unit-tests don't forget to execute the following ``Composer`` +command prior to running ``phpunit`` so that any dependencies are installed: + +.. code-block:: bash + + $ composer install --dev + +From your application's root directory. To run tests for a plugin that is part +of your application source, first ``cd`` into the plugin directory, then use +``phpunit`` command that matches how you installed phpunit: + +.. code-block:: bash + + cd plugins + + # Using composer installed phpunit + ../vendor/bin/phpunit + + # Using phar file + php ../phpunit.phar + +To run tests on a standalone plugin, you should first install the project in +a separate directory and install its dependencies: + +.. code-block:: bash + + git clone git://github.com/cakephp/debug_kit.git + cd debug_kit + php ~/composer.phar install + php ~/phpunit.phar + +Filtering Test Cases +-------------------- + +When you have larger test cases, you will often want to run a subset of the test +methods when you are trying to work on a single failing case. With the +CLI runner you can use an option to filter test methods: + +.. code-block:: bash + + $ phpunit --filter testSave tests/TestCase/Model/Table/ArticlesTableTest + +The filter parameter is used as a case-sensitive regular expression for +filtering which test methods to run. + +Generating Code Coverage +------------------------ + +You can generate code coverage reports from the command line using PHPUnit's +built-in code coverage tools. PHPUnit will generate a set of static HTML files +containing the coverage results. You can generate coverage for a test case by +doing the following: + +.. code-block:: bash + + $ phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest + +This will put the coverage results in your application's webroot directory. You +should be able to view the results by going to +``http://localhost/your_app/coverage``. + +If you are using PHP 5.6.0 or greater, you can use `phpdbg `__ +to generate coverage instead of xdebug. ``phpdbg`` is generally faster at +generating coverage: + +.. code-block:: bash + + $ phpdbg -qrr phpunit --coverage-html webroot/coverage tests/TestCase/Model/Table/ArticlesTableTest + +Combining Test Suites for Plugins +--------------------------------- + +Often times your application will be composed of several plugins. In these +situations it can be pretty tedious to run tests for each plugin. You can make +running tests for each of the plugins that compose your application by adding +additional ```` sections to your application's **phpunit.xml.dist** +file: + +.. code-block:: xml + + + + ./tests/TestCase/ + + + + + ./plugins/Forum/tests/TestCase/ + + + +Any additional test suites added to the ```` element will +automatically be run when you use ``phpunit``. + +If you are using ```` to use fixtures from plugins that you have +installed with composer, the plugin's ``composer.json`` file should add the +fixture namespace to the autoload section. Example:: + + "autoload": { + "psr-4": { + "PluginName\\Test\\Fixture\\": "tests/Fixture/" + } + }, + +Test Case Lifecycle Callbacks +============================= + +Test cases have a number of lifecycle callbacks you can use when doing testing: + +* ``setUp`` is called before every test method. Should be used to create the + objects that are going to be tested, and initialize any data for the test. + Always remember to call ``parent::setUp()`` +* ``tearDown`` is called after every test method. Should be used to cleanup after + the test is complete. Always remember to call ``parent::tearDown()``. +* ``setupBeforeClass`` is called once before test methods in a case are started. + This method must be *static*. +* ``tearDownAfterClass`` is called once after test methods in a case are started. + This method must be *static*. + +.. _test-fixtures: + +Fixtures +======== + +When testing code that depends on models and the database, one can use +**fixtures** as a way to generate temporary data tables loaded with sample data +that can be used by the test. The benefit of using fixtures is that your test +has no chance of disrupting live application data. In addition, you can begin +testing your code prior to actually developing live content for an application. + +CakePHP uses the connection named ``test`` in your **config/app.php** +configuration file. If this connection is not usable, an exception will be +raised and you will not be able to use database fixtures. + +CakePHP performs the following during the course of a fixture based +test case: + +#. Creates tables for each of the fixtures needed. +#. Populates tables with data, if data is provided in fixture. +#. Runs test methods. +#. Empties the fixture tables. +#. Removes fixture tables from database. + +Test Connections +---------------- + +By default CakePHP will alias each connection in your application. Each +connection defined in your application's bootstrap that does not start with +``test_`` will have a ``test_`` prefixed alias created. Aliasing connections +ensures, you don't accidentally use the wrong connection in test cases. +Connection aliasing is transparent to the rest of your application. For example +if you use the 'default' connection, instead you will get the ``test`` +connection in test cases. If you use the 'replica' connection, the test suite +will attempt to use 'test_replica'. + +Creating Fixtures +----------------- + +When creating a fixture you will mainly define two things: how the table is +created (which fields are part of the table), and which records will be +initially populated to the table. Let's create our first fixture, that will be +used to test our own Article model. Create a file named **ArticlesFixture.php** +in your **tests/Fixture** directory, with the following content:: + + namespace App\Test\Fixture; + + use Cake\TestSuite\Fixture\TestFixture; + + class ArticlesFixture extends TestFixture + { + // Optional. Set this property to load fixtures to a different test datasource + public $connection = 'test'; + + public $fields = [ + 'id' => ['type' => 'integer'], + 'title' => ['type' => 'string', 'length' => 255, 'null' => false], + 'body' => 'text', + 'published' => ['type' => 'integer', 'default' => '0', 'null' => false], + 'created' => 'datetime', + 'modified' => 'datetime', + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']] + ] + ]; + public $records = [ + [ + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:39:23', + 'modified' => '2007-03-18 10:41:31' + ], + [ + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:41:23', + 'modified' => '2007-03-18 10:43:31' + ], + [ + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:43:23', + 'modified' => '2007-03-18 10:45:31' + ] + ]; + } + +.. note:: + + It is recommended to not manually add values to auto incremental columns, + as it interferes with the sequence generation in PostgreSQL and SQLServer. + +The ``$connection`` property defines the datasource of which the fixture will +use. If your application uses multiple datasources, you should make the +fixtures match the model's datasources but prefixed with ``test_``. +For example if your model uses the ``mydb`` datasource, your fixture should use +the ``test_mydb`` datasource. If the ``test_mydb`` connection doesn't exist, +your models will use the default ``test`` datasource. Fixture datasources must +be prefixed with ``test`` to reduce the possibility of accidentally truncating +all your application's data when running tests. + +We use ``$fields`` to specify which fields will be part of this table, and how +they are defined. The format used to define these fields is the same used with +:php:class:`Cake\\Database\\Schema\\Table`. The keys available for table +definition are: + +type + CakePHP internal data type. Currently supported: + + - ``string``: maps to ``VARCHAR`` or ``CHAR`` + - ``uuid``: maps to ``UUID`` + - ``text``: maps to ``TEXT`` + - ``integer``: maps to ``INT`` + - ``biginteger``: maps to ``BIGINTEGER`` + - ``decimal``: maps to ``DECIMAL`` + - ``float``: maps to ``FLOAT`` + - ``datetime``: maps to ``DATETIME`` + - ``timestamp``: maps to ``TIMESTAMP`` + - ``time``: maps to ``TIME`` + - ``date``: maps to ``DATE`` + - ``binary``: maps to ``BLOB`` +fixed + Used with string types to create CHAR columns in platforms that support + them. +length + Set to the specific length the field should take. +precision + Set the number of decimal places used on float & decimal fields. +null + Set to either ``true`` (to allow NULLs) or ``false`` (to disallow NULLs). +default + Default value the field takes. + +We can define a set of records that will be populated after the fixture table is +created. The format is fairly straight forward, ``$records`` is an array of +records. Each item in ``$records`` should be a single row. Inside each row, +should be an associative array of the columns and values for the row. Just keep +in mind that each record in the $records array must have a key for **every** +field specified in the ``$fields`` array. If a field for a particular record +needs to have a ``null`` value, just specify the value of that key as ``null``. + +Dynamic Data and Fixtures +------------------------- + +Since records for a fixture are declared as a class property, you cannot use +functions or other dynamic data to define fixtures. To solve this problem, you +can define ``$records`` in the ``init()`` function of your fixture. For example +if you wanted all the created and modified timestamps to reflect today's date +you could do the following:: + + namespace App\Test\Fixture; + + use Cake\TestSuite\Fixture\TestFixture; + + class ArticlesFixture extends TestFixture + { + public $fields = [ + 'id' => ['type' => 'integer'], + 'title' => ['type' => 'string', 'length' => 255, 'null' => false], + 'body' => 'text', + 'published' => ['type' => 'integer', 'default' => '0', 'null' => false], + 'created' => 'datetime', + 'modified' => 'datetime', + '_constraints' => [ + 'primary' => ['type' => 'primary', 'columns' => ['id']], + ] + ]; + + public function init() + { + $this->records = [ + [ + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => '1', + 'created' => date('Y-m-d H:i:s'), + 'modified' => date('Y-m-d H:i:s'), + ], + ]; + parent::init(); + } + } + +When overriding ``init()`` remember to always call ``parent::init()``. + +Importing Table Information +--------------------------- + +Defining the schema in fixture files can be really handy when creating plugins +or libraries or if you are creating an application that needs to be portable +between database vendors. Redefining the schema in fixtures can become difficult +to maintain in larger applications. Because of this CakePHP provides the ability +to import the schema from an existing connection and use the reflected table +definition to create the table definition used in the test suite. + +Let's start with an example. Assuming you have a table named articles available +in your application, change the example fixture given in the previous section +(**tests/Fixture/ArticlesFixture.php**) to:: + + class ArticlesFixture extends TestFixture + { + public $import = ['table' => 'articles']; + } + +If you want to use a different connection use:: + + class ArticlesFixture extends TestFixture + { + public $import = ['table' => 'articles', 'connection' => 'other']; + } + +.. versionadded:: 3.1.7 + +Usually, you have a Table class along with your fixture, as well. You can also +use that to retrieve the table name:: + + class ArticlesFixture extends TestFixture + { + public $import = ['model' => 'Articles']; + } + +Since this uses ``TableRegistry::get()``, it also supports plugin syntax. + +You can naturally import your table definition from an existing model/table, but +have your records defined directly on the fixture as it was shown on previous +section. For example:: + + class ArticlesFixture extends TestFixture + { + public $import = ['table' => 'articles']; + public $records = [ + [ + 'title' => 'First Article', + 'body' => 'First Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:39:23', + 'modified' => '2007-03-18 10:41:31' + ], + [ + 'title' => 'Second Article', + 'body' => 'Second Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:41:23', + 'modified' => '2007-03-18 10:43:31' + ], + [ + 'title' => 'Third Article', + 'body' => 'Third Article Body', + 'published' => '1', + 'created' => '2007-03-18 10:43:23', + 'modified' => '2007-03-18 10:45:31' + ] + ]; + } + +Finally, it's possible to not load/create any schema in a fixture. This is useful if you +already have a test database setup with all the empty tables created. By +defining neither ``$fields`` nor ``$import``, a fixture will only insert its +records and truncate the records on each test method. + +Loading Fixtures in your Test Cases +----------------------------------- + +After you've created your fixtures, you'll want to use them in your test cases. +In each test case you should load the fixtures you will need. You should load a +fixture for every model that will have a query run against it. To load fixtures +you define the ``$fixtures`` property in your model:: + + class ArticlesTest extends TestCase + { + public $fixtures = ['app.articles', 'app.comments']; + } + +The above will load the Article and Comment fixtures from the application's +Fixture directory. You can also load fixtures from CakePHP core, or plugins:: + + class ArticlesTest extends TestCase + { + public $fixtures = ['plugin.DebugKit.articles', 'plugin.MyVendorName/MyPlugin.messages', 'core.comments']; + } + +Using the ``core`` prefix will load fixtures from CakePHP, and using a plugin +name as the prefix, will load the fixture from the named plugin. + +You can control when your fixtures are loaded by setting +:php:attr:`Cake\\TestSuite\\TestCase::$autoFixtures` to ``false`` and later load +them using :php:meth:`Cake\\TestSuite\\TestCase::loadFixtures()`:: + + class ArticlesTest extends TestCase + { + public $fixtures = ['app.articles', 'app.comments']; + public $autoFixtures = false; + + public function testMyFunction() + { + $this->loadFixtures('Articles', 'Comments'); + } + } + +You can load fixtures in subdirectories. Using multiple directories can make it +easier to organize your fixtures if you have a larger application. To load +fixtures in subdirectories, simply include the subdirectory name in the fixture +name:: + + class ArticlesTest extends CakeTestCase + { + public $fixtures = ['app.blog/articles', 'app.blog/comments']; + } + +In the above example, both fixtures would be loaded from +``tests/Fixture/blog/``. + +Testing Table Classes +===================== + +Let's say we already have our Articles Table class defined in +**src/Model/Table/ArticlesTable.php**, and it looks like:: + + namespace App\Model\Table; + + use Cake\ORM\Table; + use Cake\ORM\Query; + + class ArticlesTable extends Table + { + public function findPublished(Query $query, array $options) + { + $query->where([ + $this->alias() . '.published' => 1 + ]); + return $query; + } + } + +We now want to set up a test that will test this table class. Let's now create +a file named **ArticlesTableTest.php** in your **tests/TestCase/Model/Table** directory, +with the following contents:: + + namespace App\Test\TestCase\Model\Table; + + use App\Model\Table\ArticlesTable; + use Cake\ORM\TableRegistry; + use Cake\TestSuite\TestCase; + + class ArticlesTableTest extends TestCase + { + public $fixtures = ['app.articles']; + } + +In our test cases' variable ``$fixtures`` we define the set of fixtures that +we'll use. You should remember to include all the fixtures that will have +queries run against them. + +Creating a Test Method +---------------------- + +Let's now add a method to test the function ``published()`` in the Articles +table. Edit the file **tests/TestCase/Model/Table/ArticlesTableTest.php** so it +now looks like this:: + + namespace App\Test\TestCase\Model\Table; + + use App\Model\Table\ArticlesTable; + use Cake\ORM\TableRegistry; + use Cake\TestSuite\TestCase; + + class ArticlesTableTest extends TestCase + { + public $fixtures = ['app.articles']; + + public function setUp() + { + parent::setUp(); + $this->Articles = TableRegistry::get('Articles'); + } + + public function testFindPublished() + { + $query = $this->Articles->find('published'); + $this->assertInstanceOf('Cake\ORM\Query', $query); + $result = $query->hydrate(false)->toArray(); + $expected = [ + ['id' => 1, 'title' => 'First Article'], + ['id' => 2, 'title' => 'Second Article'], + ['id' => 3, 'title' => 'Third Article'] + ]; + + $this->assertEquals($expected, $result); + } + } + +You can see we have added a method called ``testFindPublished()``. We start by +creating an instance of our ``ArticlesTable`` class, and then run our +``find('published')`` method. In ``$expected`` we set what we expect should be +the proper result (that we know since we have defined which records are +initially populated to the article table.) We test that the result equals our +expectation by using the ``assertEquals()`` method. See the :ref:`running-tests` +section for more information on how to run your test case. + +Mocking Model Methods +--------------------- + +There will be times you'll want to mock methods on models when testing them. You +should use ``getMockForModel`` to create testing mocks of table classes. It +avoids issues with reflected properties that normal mocks have:: + + public function testSendingEmails() + { + $model = $this->getMockForModel('EmailVerification', ['send']); + $model->expects($this->once()) + ->method('send') + ->will($this->returnValue(true)); + + $model->verifyEmail('test@example.com'); + } + +In your ``tearDown()`` method be sure to remove the mock with:: + + TableRegistry::clear(); + +.. _integration-testing: + +Controller Integration Testing +============================== + +While you can test controller classes in a similar fashion to Helpers, Models, +and Components, CakePHP offers a specialized ``IntegrationTestCase`` class. +Using this class as the base class for your controller test cases allows you to +test controllers from a high level. + +If you are unfamiliar with integration testing, it is a testing approach that +makes it easy to test multiple units in concert. The integration testing +features in CakePHP simulate an HTTP request being handled by your application. +For example, testing your controller will also exercise any components, models +and helpers that would be involved in handling a given request. This gives you a +more high level test of your application and all its working parts. + +Say you have a typical ArticlesController, and its corresponding model. The +controller code looks like:: + + namespace App\Controller; + + use App\Controller\AppController; + + class ArticlesController extends AppController + { + public $helpers = ['Form', 'Html']; + + public function index($short = null) + { + if ($this->request->is('post')) { + $article = $this->Articles->newEntity($this->request->getData()); + if ($this->Articles->save($article)) { + // Redirect as per PRG pattern + return $this->redirect(['action' => 'index']); + } + } + if (!empty($short)) { + $result = $this->Articles->find('all', [ + 'fields' => ['id', 'title'] + ]); + } else { + $result = $this->Articles->find(); + } + + $this->set([ + 'title' => 'Articles', + 'articles' => $result + ]); + } + } + +Create a file named **ArticlesControllerTest.php** in your +**tests/TestCase/Controller** directory and put the following inside:: + + namespace App\Test\TestCase\Controller; + + use Cake\ORM\TableRegistry; + use Cake\TestSuite\IntegrationTestCase; + + class ArticlesControllerTest extends IntegrationTestCase + { + public $fixtures = ['app.articles']; + + public function testIndex() + { + $this->get('/articles'); + + $this->assertResponseOk(); + // More asserts. + } + + public function testIndexQueryData() + { + $this->get('/articles?page=1'); + + $this->assertResponseOk(); + // More asserts. + } + + public function testIndexShort() + { + $this->get('/articles/index/short'); + + $this->assertResponseOk(); + $this->assertResponseContains('Articles'); + // More asserts. + } + + public function testIndexPostData() + { + $data = [ + 'user_id' => 1, + 'published' => 1, + 'slug' => 'new-article', + 'title' => 'New Article', + 'body' => 'New Body' + ]; + $this->post('/articles', $data); + + $this->assertResponseSuccess(); + $articles = TableRegistry::get('Articles'); + $query = $articles->find()->where(['title' => $data['title']]); + $this->assertEquals(1, $query->count()); + } + } + +This example shows a few of the request sending methods and a few of the +assertions that ``IntegrationTestCase`` provides. Before you can do any +assertions you'll need to dispatch a request. You can use one of the following +methods to send a request: + +* ``get()`` Sends a GET request. +* ``post()`` Sends a POST request. +* ``put()`` Sends a PUT request. +* ``delete()`` Sends a DELETE request. +* ``patch()`` Sends a PATCH request. +* ``options()`` Sends an OPTIONS request. +* ``head()`` Sends a HEAD request. + +All of the methods except ``get()`` and ``delete()`` accept a second parameter +that allows you to send a request body. After dispatching a request you can use +the various assertions provided by ``IntegrationTestCase`` or PHPUnit to +ensure your request had the correct side-effects. + +.. versionadded:: 3.5.0 + ``options()`` and ``head()`` were added in 3.5.0. + +Setting up the Request +---------------------- + +The ``IntegrationTestCase`` class comes with a number of helpers to make it easy +to configure the requests you will send to your application under test:: + + // Set cookies + $this->cookie('name', 'Uncle Bob'); + + // Set session data + $this->session(['Auth.User.id' => 1]); + + // Configure headers + $this->configRequest([ + 'headers' => ['Accept' => 'application/json'] + ]); + +The state set by these helper methods is reset in the ``tearDown()`` method. + +.. _testing-authentication: + +Testing Actions That Require Authentication +------------------------------------------- + +If you are using ``AuthComponent`` you will need to stub out the session data +that AuthComponent uses to validate a user's identity. You can use helper +methods in ``IntegrationTestCase`` to do this. Assuming you had an +``ArticlesController`` that contained an add method, and that add method +required authentication, you could write the following tests:: + + public function testAddUnauthenticatedFails() + { + // No session data set. + $this->get('/articles/add'); + + $this->assertRedirect(['controller' => 'Users', 'action' => 'login']); + } + + public function testAddAuthenticated() + { + // Set session data + $this->session([ + 'Auth' => [ + 'User' => [ + 'id' => 1, + 'username' => 'testing', + // other keys. + ] + ] + ]); + $this->get('/articles/add'); + + $this->assertResponseOk(); + // Other assertions. + } + +Testing Stateless Authentication and APIs +----------------------------------------- + +To test APIs that use stateless authentication, such as Basic authentication, +you can configure the request to inject environment conditions or headers that +simulate actual authentication request headers. + +When testing Basic or Digest Authentication, you can add the environment +variables that `PHP creates `_ +automatically. These environment variables used in the authentication adapter +outlined in :ref:`basic-authentication`:: + + public function testBasicAuthentication() + { + $this->configRequest([ + 'environment' => [ + 'PHP_AUTH_USER' => 'username', + 'PHP_AUTH_PW' => 'password', + ] + ]); + + $this->get('/api/posts'); + $this->assertResponseOk(); + } + +If you are testing other forms of authentication, such as OAuth2, you can set +the Authorization header directly:: + + public function testOauthToken() + { + $this->configRequest([ + 'headers' => [ + 'authorization' => 'Bearer: oauth-token' + ] + ]); + + $this->get('/api/posts'); + $this->assertResponseOk(); + } + +The headers key in ``configRequest()`` can be used to configure any additional +HTTP headers needed for an action. + +Testing Actions Protected by CsrfComponent or SecurityComponent +--------------------------------------------------------------- + +When testing actions protected by either SecurityComponent or CsrfComponent you +can enable automatic token generation to ensure your tests won't fail due to +token mismatches:: + + public function testAdd() + { + $this->enableCsrfToken(); + $this->enableSecurityToken(); + $this->post('/posts/add', ['title' => 'Exciting news!']); + } + +It is also important to enable debug in tests that use tokens to prevent the +SecurityComponent from thinking the debug token is being used in a non-debug +environment. When testing with other methods like ``requireSecure()`` you +can use ``configRequest()`` to set the correct environment variables:: + + // Fake out SSL connections. + $this->configRequest([ + 'environment' => ['HTTPS' => 'on'] + ]); + +.. versionadded:: 3.1.2 + The ``enableCsrfToken()`` and ``enableSecurityToken()`` methods were added + in 3.1.2 + +Integration Testing PSR-7 Middleware +------------------------------------ + +Integration testing can also be used to test your entire PSR-7 application and +:doc:`/controllers/middleware`. By default ``IntegrationTestCase`` will +auto-detect the presence of an ``App\Application`` class and automatically +enable integration testing of your Application. You can toggle this behavior +with the ``useHttpServer()`` method:: + + public function setUp() + { + // Enable PSR-7 integration testing. + $this->useHttpServer(true); + + // Disable PSR-7 integration testing. + $this->useHttpServer(false); + } + +You can customize the application class name used, and the constructor +arguments, by using the ``configApplication()`` method:: + + public function setUp() + { + $this->configApplication('App\App', [CONFIG]); + } + +After enabling the PSR-7 mode, and possibly configuring your application class, +you can use the remaining ``IntegrationTestCase`` features as normal. + +You should also take care to try and use :ref:`application-bootstrap` to load +any plugins containing events/routes. Doing so will ensure that your +events/routes are connected for each test case. + +.. versionadded:: 3.3.0 + PSR-7 Middleware and the ``useHttpServer()`` method were added in 3.3.0. + +Testing with Encrypted Cookies +------------------------------ + +If you use the :php:class:`Cake\\Controller\\Component\\CookieComponent` in your +controllers, your cookies are likely encrypted. As of 3.1.7, CakePHP provides +helper methods for interacting with encrypted cookies in your test cases:: + + // Set a cookie using AES and the default key. + $this->cookieEncrypted('my_cookie', 'Some secret values'); + + // Assume this action modifies the cookie. + $this->get('/bookmarks/index'); + + $this->assertCookieEncrypted('An updated value', 'my_cookie'); + +.. versionadded:: 3.1.7 + + ``assertCookieEncrypted`` and ``cookieEncrypted`` were added in 3.1.7. + +Testing Flash Messages +---------------------- + +If you want to assert the presence of flash messages in the session and not the +rendered HTML, you can use ``enableRetainFlashMessages()`` in your tests to +retain flash messages in the session so you can write assertions:: + + $this->enableRetainFlashMessages(); + $this->get('/bookmarks/delete/9999'); + + $this->assertSession('That bookmark does not exist', 'Flash.flash.0.message'); + +.. versionadded:: 3.4.7 + ``enableRetainFlashMessages()`` was added in 3.4.7 + +Testing a JSON Responding Controller +------------------------------------ + +JSON is a friendly and common format to use when building a web service. +Testing the endpoints of your web service is very simple with CakePHP. Let us +begin with a simple example controller that responds in JSON:: + + class MarkersController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function view($id) + { + $marker = $this->Markers->get($id); + $this->set([ + '_serialize' => ['marker'], + 'marker' => $marker, + ]); + } + } + +Now we create the file **tests/TestCase/Controller/MarkersControllerTest.php** +and make sure our web service is returning the proper response:: + + class MarkersControllerTest extends IntegrationTestCase + { + public function testGet() + { + $this->configRequest([ + 'headers' => ['Accept' => 'application/json'] + ]); + $result = $this->get('/markers/view/1.json'); + + // Check that the response was a 200 + $this->assertResponseOk(); + + $expected = [ + ['id' => 1, 'lng' => 66, 'lat' => 45], + ]; + $expected = json_encode($expected, JSON_PRETTY_PRINT); + $this->assertEquals($expected, $this->_response->body()); + } + } + +We use the ``JSON_PRETTY_PRINT`` option as CakePHP's built in JsonView will use +that option when ``debug`` is enabled. + +Disabling Error Handling Middleware in Tests +-------------------------------------------- + +When debugging tests that are failing because your application is encountering +errors it can be helpful to temporarily disable the error handling middleware to +allow the underlying error to bubble up. You can use +``disableErrorHandlerMiddleware()`` to do this:: + + public function testGetMissing() + { + $this->disableErrorHandlerMiddleware(); + $this->get('/markers/not-there'); + $this->assertResponseCode(404); + } + +In the above example, the test would fail and the underlying exception message +and stack trace would be displayed instead of the rendered error page being +checked. + +.. versionadded:: 3.5.0 + +Assertion methods +----------------- + +The ``IntegrationTestCase`` class provides a number of assertion methods that +make testing responses much simpler. Some examples are:: + + // Check for a 2xx response code + $this->assertResponseOk(); + + // Check for a 2xx/3xx response code + $this->assertResponseSuccess(); + + // Check for a 4xx response code + $this->assertResponseError(); + + // Check for a 5xx response code + $this->assertResponseFailure(); + + // Check for a specific response code, e.g. 200 + $this->assertResponseCode(200); + + // Check the Location header + $this->assertRedirect(['controller' => 'Articles', 'action' => 'index']); + + // Check that no Location header has been set + $this->assertNoRedirect(); + + // Check a part of the Location header + $this->assertRedirectContains('/articles/edit/'); + + // Assert not empty response content + $this->assertResponseNotEmpty(); + + // Assert empty response content + $this->assertResponseEmpty(); + + // Assert response content + $this->assertResponseEquals('Yeah!'); + + // Assert partial response content + $this->assertResponseContains('You won!'); + $this->assertResponseNotContains('You lost!'); + + // Assert layout + $this->assertLayout('default'); + + // Assert which template was rendered (if any) + $this->assertTemplate('index'); + + // Assert data in the session + $this->assertSession(1, 'Auth.User.id'); + + // Assert response header. + $this->assertHeader('Content-Type', 'application/json'); + + // Assert view variables + $user = $this->viewVariable('user'); + $this->assertEquals('jose', $user->username); + + // Assert cookies in the response + $this->assertCookie('1', 'thingid'); + + // Check the content type + $this->assertContentType('application/json'); + +In addition to the above assertion methods, you can also use all of the +assertions in `TestSuite +`_ and those +found in `PHPUnit +`__. + +Comparing test results to a file +-------------------------------- + +For some types of test, it may be easier to compare the result of a test to the +contents of a file - for example, when testing the rendered output of a view. +The ``StringCompareTrait`` adds a simple assert method for this purpose. + +Usage involves using the trait, setting the comparison base path and calling +``assertSameAsFile``:: + + use Cake\TestSuite\StringCompareTrait; + use Cake\TestSuite\TestCase; + + class SomeTest extends TestCase + { + use StringCompareTrait; + + public function setUp() + { + $this->_compareBasePath = APP . 'tests' . DS . 'comparisons' . DS; + parent::setUp(); + } + + public function testExample() + { + $result = ...; + $this->assertSameAsFile('example.php', $result); + } + } + +The above example will compare ``$result`` to the contents of the file +``APP/tests/comparisons/example.php``. + +A mechanism is provided to write/update test files, by setting the environment +variable ``UPDATE_TEST_COMPARISON_FILES``, which will create and/or update test +comparison files as they are referenced: + +.. code-block:: bash + + phpunit + ... + FAILURES! + Tests: 6, Assertions: 7, Failures: 1 + + UPDATE_TEST_COMPARISON_FILES=1 phpunit + ... + OK (6 tests, 7 assertions) + + git status + ... + # Changes not staged for commit: + # (use "git add ..." to update what will be committed) + # (use "git checkout -- ..." to discard changes in working directory) + # + # modified: tests/comparisons/example.php + +.. _console-integration-testing: + +Console Integration Testing +=========================== + +To make testing console applications easier, CakePHP comes with a +``ConsoleIntegrationTestCase`` class that can be used to test console applications +and assert against their results. + +.. versionadded:: 3.5.0 + + The ``ConsoleIntegrationTestCase`` was added. + +To get started testing your console application, create a test case that extends +``Cake\TestSuite\ConsoleIntegrationTestCase``. This class contains a method +``exec()`` that is used to execute your command. You can pass the same string +you would use in the CLI to this method. + +Let's start with a very simple shell, located in **src/Shell/MyConsoleShell.php**:: + + namespace App\Shell; + + use Cake\Console\ConsoleOptionParser; + use Cake\Console\Shell; + + class MyConsoleShell extends Shell + { + public function getOptionParser() + { + $parser = new ConsoleOptionParser(); + $parser->setDescription('My cool console app'); + + return $parser; + } + } + +To write an integration test for this shell, we would create a test case in +**tests/TestCase/Shell/MyConsoleShellTest.php** that extends +``Cake\TestSuite\ConsoleIntegrationTestCase``. This shell doesn't do much at the +moment, but let's just test that our shell's description is displayed in ``stdout``:: + + namespace App\Test\TestCase\Shell; + + use Cake\TestSuite\ConsoleIntegrationTestCase; + + class MyConsoleShellTest extends ConsoleIntegrationTestCase + { + public function testDescriptionOutput() + { + $this->exec('my_console'); + $this->assertOutputContains('My cool console app'); + } + } + +Our test passes! While this is very trivial example, it shows that creating an +integration test case for console applications is quite easy. Let's continue by +adding some subcommands and options to our shell:: + + namespace App\Shell; + + use Cake\Console\ConsoleOptionParser; + use Cake\I18n\FrozenTime; + + class MyConsoleShell extends Shell + { + public function getOptionParser() + { + $parser = new ConsoleOptionParser(); + + $updateModifiedParser = new ConsoleOptionParser(); + $updateModifiedParser->addArgument('table', [ + 'help' => 'Table to update', + 'required' => true + ]); + + $parser + ->setDescription('My cool console app') + ->addSubcommand('updateModified', [ + 'parser' => $updateModifiedParser + ]); + + return $parser; + } + + public function updateModified() + { + $table = $this->args[0]; + $this->loadModel($table); + $this->{$table}->query() + ->update() + ->set([ + 'modified' => new FrozenTime() + ]) + ->execute(); + } + } + +This is a more complete shell that has a subcommand with its own parser. Let's +test the ``updateModified`` subcommand. Modify your test case to the following +snippet of code:: + + namespace Cake\Test\TestCase\Shell; + + use Cake\Console\Shell; + use Cake\I18n\FrozenTime; + use Cake\ORM\TableRegistry; + use Cake\TestSuite\ConsoleIntegrationTestCase; + + class MyConsoleShellTest extends ConsoleIntegrationTestCase + { + public $fixtures = [ + // assumes you have a UsersFixture + 'app.users' + ]; + + public function testDescriptionOutput() + { + $this->exec('my_console'); + $this->assertOutputContains('My cool console app'); + } + + public function testUpdateModified() + { + $now = new FrozenTime('2017-01-01 00:00:00'); + FrozenTime::setTestNow($now); + + $this->loadFixtures('Users'); + + $this->exec('my_console update_modified Users'); + $this->assertExitCode(Shell::CODE_SUCCESS); + + $user = TableRegistry::get('Users')->get(1); + $this->assertSame($user->modified->timestamp, $now->timestamp); + + FrozenTime::setTestNow(null); + } + } + +As you can see from the ``testUpdateModified`` method, we are testing that our +``update_modified`` subcommand updates the table that we are passing as the first +argument. First, we assert that the shell exited with the proper status code, +``0``. Then we check that our subcommand did its work, that is, updated the +table we provided and set the ``modified`` column to the current time. + +Remember, ``exec()`` will take the same string you type into your CLI, so you +can include options and arguments in your command string. + +Testing Interactive Shells +-------------------------- + +Consoles are often interactive. Testing interactive shells with the +``Cake\TestSuite\ConsoleIntegrationTestCase`` class only requires passing the +inputs you expect as the second parameter of ``exec()``. They should be +included as an array in the order that you expect them. + +Continuing with our example shell, let's add an interactive subcommand. Update +the shell class to the following:: + + namespace App\Shell; + + use Cake\Console\ConsoleOptionParser; + use Cake\Console\Shell; + use Cake\I18n\FrozenTime; + + class MyConsoleShell extends Shell + { + public function getOptionParser() + { + $parser = new ConsoleOptionParser(); + + $updateModifiedParser = new ConsoleOptionParser(); + $updateModifiedParser->addArgument('table', [ + 'help' => 'Table to update', + 'required' => true + ]); + + $parser + ->setDescription('My cool console app') + ->addSubcommand('updateModified', [ + 'parser' => $updateModifiedParser + ]) + // add a new subcommand + ->addSubcommand('bestFramework'); + + return $parser; + } + + public function updateModified() + { + $table = $this->args[0]; + $this->loadModel($table); + $this->{$table}->query() + ->update() + ->set([ + 'modified' => new FrozenTime() + ]) + ->execute(); + } + + // create this interactive subcommand + public function bestFramework() + { + $this->out('Hi there!'); + + $framework = $this->in('What is the best PHP framework?'); + if ($framework !== 'CakePHP') { + $this->err("I disagree that '$framework' is the best."); + $this->_stop(Shell::CODE_ERROR); + } + + $this->out('I agree!'); + } + } + +Now that we have an interactive subcommand, we can add a test case that tests +that we receive the proper response, and one that tests that we receive an +incorrect response. Add the following methods to +**tests/TestCase/Shell/MyConsoleShellTest.php**:: + + public function testBestFramework() + { + $this->exec('my_console best_framework', [ + 'CakePHP' + ]); + $this->assertExitCode(Shell::CODE_SUCCESS); + $this->assertOutputContains('I agree!'); + } + + public function testBestFrameworkWrongAnswer() + { + $this->exec('my_console best_framework', [ + 'my homemade framework' + ]); + $this->assertExitCode(Shell::CODE_ERROR); + $this->assertErrorRegExp("/I disagree that \'(.+)\' is the best\./"); + } + +As you can see from the ``testBestFramework``, it responds to the first input +request with "CakePHP". Since this is the correct answer according to our +subcommand, the shell will exit successfully after outputting a response. + +The second test case, ``testBestFrameworkWrongAnswer``, provides an incorrect +answer which causes our shell to fail and exit with ``1``. We also assert +that ``stderr`` was given our error, which includes the name of the incorrect +answer. + +Testing the CommandRunner +------------------------- + +To test shells that are dispatched using the ``CommandRunner`` class, enable it +in your test case with the following method:: + + $this->useCommandRunner(); + +.. versionadded:: 3.5.0 + + The ``CommandRunner`` class was added. + +Assertion methods +----------------- + +The ``Cake\TestSuite\ConsoleIntegrationTestCase`` class provides a number of +assertion methods that make it easy to assert against console output:: + + // assert that the shell exited with the expected code + $this->assertExitCode($expected); + + // assert that stdout contains a string + $this->assertOutputContains($expected); + + // assert that stderr contains a string + $this->assertErrorContains($expected); + + // assert that stdout matches a regular expression + $this->assertOutputRegExp($expected); + + // assert that stderr matches a regular expression + $this->assertErrorRegExp($expected); + +Testing Views +============= + +Generally most applications will not directly test their HTML code. Doing so is +often results in fragile, difficult to maintain test suites that are prone to +breaking. When writing functional tests using :php:class:`IntegrationTestCase` +you can inspect the rendered view content by setting the ``return`` option to +'view'. While it is possible to test view content using IntegrationTestCase, +a more robust and maintainable integration/view testing can be accomplished +using tools like `Selenium webdriver `_. + +Testing Components +================== + +Let's pretend we have a component called PagematronComponent in our application. +This component helps us set the pagination limit value across all the +controllers that use it. Here is our example component located in +**src/Controller/Component/PagematronComponent.php**:: + + class PagematronComponent extends Component + { + public $controller = null; + + public function setController($controller) + { + $this->controller = $controller; + // Make sure the controller is using pagination + if (!isset($this->controller->paginate)) { + $this->controller->paginate = []; + } + } + + public function startup(Event $event) + { + $this->setController($event->getSubject()); + } + + public function adjust($length = 'short') + { + switch ($length) { + case 'long': + $this->controller->paginate['limit'] = 100; + break; + case 'medium': + $this->controller->paginate['limit'] = 50; + break; + default: + $this->controller->paginate['limit'] = 20; + break; + } + } + } + +Now we can write tests to ensure our paginate ``limit`` parameter is being set +correctly by the ``adjust()`` method in our component. We create the file +**tests/TestCase/Controller/Component/PagematronComponentTest.php**:: + + namespace App\Test\TestCase\Controller\Component; + + use App\Controller\Component\PagematronComponent; + use Cake\Controller\Controller; + use Cake\Controller\ComponentRegistry; + use Cake\Event\Event; + use Cake\Http\ServerRequest; + use Cake\Http\Response; + use Cake\TestSuite\TestCase; + + class PagematronComponentTest extends TestCase + { + + public $component = null; + public $controller = null; + + public function setUp() + { + parent::setUp(); + // Setup our component and fake test controller + $request = new ServerRequest(); + $response = new Response(); + $this->controller = $this->getMockBuilder('Cake\Controller\Controller') + ->setConstructorArgs([$request, $response]) + ->setMethods(null) + ->getMock(); + $registry = new ComponentRegistry($this->controller); + $this->component = new PagematronComponent($registry); + $event = new Event('Controller.startup', $this->controller); + $this->component->startup($event); + } + + public function testAdjust() + { + // Test our adjust method with different parameter settings + $this->component->adjust(); + $this->assertEquals(20, $this->controller->paginate['limit']); + + $this->component->adjust('medium'); + $this->assertEquals(50, $this->controller->paginate['limit']); + + $this->component->adjust('long'); + $this->assertEquals(100, $this->controller->paginate['limit']); + } + + public function tearDown() + { + parent::tearDown(); + // Clean up after we're done + unset($this->component, $this->controller); + } + } + +Testing Helpers +=============== + +Since a decent amount of logic resides in Helper classes, it's +important to make sure those classes are covered by test cases. + +First we create an example helper to test. The ``CurrencyRendererHelper`` will +help us display currencies in our views and for simplicity only has one method +``usd()``:: + + // src/View/Helper/CurrencyRendererHelper.php + namespace App\View\Helper; + + use Cake\View\Helper; + + class CurrencyRendererHelper extends Helper + { + public function usd($amount) + { + return 'USD ' . number_format($amount, 2, '.', ','); + } + } + +Here we set the decimal places to 2, decimal separator to dot, thousands +separator to comma, and prefix the formatted number with 'USD' string. + +Now we create our tests:: + + // tests/TestCase/View/Helper/CurrencyRendererHelperTest.php + + namespace App\Test\TestCase\View\Helper; + + use App\View\Helper\CurrencyRendererHelper; + use Cake\TestSuite\TestCase; + use Cake\View\View; + + class CurrencyRendererHelperTest extends TestCase + { + public $helper = null; + + // Here we instantiate our helper + public function setUp() + { + parent::setUp(); + $View = new View(); + $this->helper = new CurrencyRendererHelper($View); + } + + // Testing the usd() function + public function testUsd() + { + $this->assertEquals('USD 5.30', $this->helper->usd(5.30)); + + // We should always have 2 decimal digits + $this->assertEquals('USD 1.00', $this->helper->usd(1)); + $this->assertEquals('USD 2.05', $this->helper->usd(2.05)); + + // Testing the thousands separator + $this->assertEquals( + 'USD 12,000.70', + $this->helper->usd(12000.70) + ); + } + } + +Here, we call ``usd()`` with different parameters and tell the test suite to +check if the returned values are equal to what is expected. + +Save this and execute the test. You should see a green bar and messaging +indicating 1 pass and 4 assertions. + +When you are testing a Helper which uses other helpers, be sure to mock the +View clases ``loadHelpers`` method. + +.. _testing-events: + +Testing Events +============== + +The :doc:`/core-libraries/events` is a great way to decouple your application +code, but sometimes when testing, you tend to test the results of events in the +test cases that execute those events. This is an additional form of coupling +that can be removed by using ``assertEventFired`` and ``assertEventFiredWith`` +instead. + +Expanding on the Orders example, say we have the following tables:: + + class OrdersTable extends Table + { + public function place($order) + { + if ($this->save($order)) { + // moved cart removal to CartsTable + $event = new Event('Model.Order.afterPlace', $this, [ + 'order' => $order + ]); + $this->eventManager()->dispatch($event); + return true; + } + return false; + } + } + + class CartsTable extends Table + { + public function implementedEvents() + { + return [ + 'Model.Order.afterPlace' => 'removeFromCart' + ]; + } + + public function removeFromCart(Event $event) + { + $order = $event->getData('order'); + $this->delete($order->cart_id); + } + } + +.. note:: + To assert that events are fired, you must first enable + :ref:`tracking-events` on the event manager you wish to assert against. + +To test the ``OrdersTable`` above, we enable tracking in ``setUp()`` then assert +that the event was fired, and assert that the ``$order`` entity was passed in +the event data:: + + namespace App\Test\TestCase\Model\Table; + + use App\Model\Table\OrdersTable; + use Cake\Event\EventList; + use Cake\ORM\TableRegistry; + use Cake\TestSuite\TestCase; + + class OrdersTableTest extends TestCase + { + public $fixtures = ['app.orders']; + + public function setUp() + { + parent::setUp(); + $this->Orders = TableRegistry::get('Orders'); + // enable event tracking + $this->Orders->eventManager()->setEventList(new EventList()); + } + + public function testPlace() + { + $order = new Order([ + 'user_id' => 1, + 'item' => 'Cake', + 'quantity' => 42, + ]); + + $this->assertTrue($this->Orders->place($order)); + + $this->assertEventFired('Model.Order.afterPlace', $this->Orders->eventManager()); + $this->assertEventFiredWith('Model.Order.afterPlace', 'order', $order, $this->Orders->eventManager()); + } + } + +By default, the global ``EventManager`` is used for assertions, so testing +global events does not require passing the event manager:: + + $this->assertEventFired('My.Global.Event'); + $this->assertEventFiredWith('My.Global.Event', 'user', 1); + +.. versionadded:: 3.2.11 + + Event tracking, ``assertEventFired()``, and ``assertEventFiredWith`` were + added. + +Creating Test Suites +==================== + +If you want several of your tests to run at the same time, you can create a test +suite. A test suite is composed of several test cases. You can either create +test suites in your application's **phpunit.xml** file. A simple example +would be: + +.. code-block:: xml + + + + src/Model + src/Service/UserServiceTest.php + src/Model/Cloud/ImagesTest.php + + + +Creating Tests for Plugins +========================== + +Tests for plugins are created in their own directory inside the plugins +folder. :: + + /src + /plugins + /Blog + /tests + /TestCase + /Fixture + +They work just like normal tests but you have to remember to use the naming +conventions for plugins when importing classes. This is an example of a testcase +for the ``BlogPost`` model from the plugins chapter of this manual. A difference +from other tests is in the first line where 'Blog.BlogPost' is imported. You +also need to prefix your plugin fixtures with ``plugin.blog.blog_posts``:: + + namespace Blog\Test\TestCase\Model\Table; + + use Blog\Model\Table\BlogPostsTable; + use Cake\TestSuite\TestCase; + + class BlogPostsTableTest extends TestCase + { + // Plugin fixtures located in /plugins/Blog/tests/Fixture/ + public $fixtures = ['plugin.blog.blog_posts']; + + public function testSomething() + { + // Test something. + } + } + +If you want to use plugin fixtures in the app tests you can +reference them using ``plugin.pluginName.fixtureName`` syntax in the +``$fixtures`` array. Additionally if you use vendor plugin name or fixture +directories you can use the following: ``plugin.vendorName/pluginName.folderName/fixtureName``. + +Before you can use fixtures you should double check that your ``phpunit.xml`` +contains the fixture listener:: + + + + + + + + + + +You should also ensure that your fixtures are loadable. Ensure the following is +present in your **composer.json** file:: + + "autoload-dev": { + "psr-4": { + "MyPlugin\\Test\\": "plugins/MyPlugin/tests/" + } + } + +.. note:: + + Remember to run ``composer.phar dumpautoload`` when adding new autoload + mappings. + +Generating Tests with Bake +========================== + +If you use :doc:`bake ` to +generate scaffolding, it will also generate test stubs. If you need to +re-generate test case skeletons, or if you want to generate test skeletons for +code you wrote, you can use ``bake``: + +.. code-block:: bash + + bin/cake bake test + +```` should be one of: + +#. Entity +#. Table +#. Controller +#. Component +#. Behavior +#. Helper +#. Shell +#. Cell + +While ```` should be the name of the object you want to bake a test +skeleton for. + +Integration with Jenkins +======================== + +`Jenkins `_ is a continuous integration server, that can +help you automate the running of your test cases. This helps ensure that all +your tests stay passing and your application is always ready. + +Integrating a CakePHP application with Jenkins is fairly straightforward. The +following assumes you've already installed Jenkins on \*nix system, and are able +to administer it. You also know how to create jobs, and run builds. If you are +unsure of any of these, refer to the `Jenkins documentation `_ . + +Create a Job +------------ + +Start off by creating a job for your application, and connect your repository +so that jenkins can access your code. + +Add Test Database Config +------------------------ + +Using a separate database just for Jenkins is generally a good idea, as it stops +bleed through and avoids a number of basic problems. Once you've created a new +database in a database server that jenkins can access (usually localhost). Add +a *shell script step* to the build that contains the following: + +.. code-block:: bash + + cat > config/app_local.php <<'CONFIG' + [ + 'test' => [ + 'datasource' => 'Database/Mysql', + 'host' => 'localhost', + 'database' => 'jenkins_test', + 'username' => 'jenkins', + 'password' => 'cakephp_jenkins', + 'encoding' => 'utf8' + ] + ] + ]; + CONFIG + +Then uncomment the following line in your **config/bootstrap.php** file:: + + //Configure::load('app_local', 'default'); + +By creating an **app_local.php** file, you have an easy way to define +configuration specific to Jenkins. You can use this same configuration file to +override any other configuration files you need on Jenkins. + +It's often a good idea to drop and re-create the database before each build as +well. This insulates you from chained failures, where one broken build causes +others to fail. Add another *shell script step* to the build that contains the +following: + +.. code-block:: bash + + mysql -u jenkins -pcakephp_jenkins -e 'DROP DATABASE IF EXISTS jenkins_test; CREATE DATABASE jenkins_test'; + +Add your Tests +-------------- + +Add another *shell script step* to your build. In this step install your +dependencies and run the tests for your application. Creating a junit log file, +or clover coverage is often a nice bonus, as it gives you a nice graphical view +of your testing results: + +.. code-block:: bash + + # Download Composer if it is missing. + test -f 'composer.phar' || curl -sS https://getcomposer.org/installer | php + # Install dependencies + php composer.phar install + vendor/bin/phpunit --log-junit junit.xml --coverage-clover clover.xml + +If you use clover coverage, or the junit results, make sure to configure those +in Jenkins as well. Failing to configure those steps will mean you won't see the +results. + +Run a Build +----------- + +You should be able to run a build now. Check the console output and make any +necessary changes to get a passing build. + +.. meta:: + :title lang=en: Testing + :keywords lang=en: phpunit,test database,database configuration,database setup,database test,public test,test framework,running one,test setup,de facto standard,pear,runners,array,databases,cakephp,php,integration diff --git a/tl/elasticsearch.rst b/tl/elasticsearch.rst new file mode 100644 index 0000000000000000000000000000000000000000..6e3b42e7c1423297ff310a73e8941c4ceedc56d6 --- /dev/null +++ b/tl/elasticsearch.rst @@ -0,0 +1,320 @@ +ElasticSearch +############# + +The ElasticSearch plugin provides an ORM-like abstraction on top of +`elasticsearch `_. The plugin +provides features that make testing, indexing documents and searching your +indexes easier. + +Installation +============ + +To install the ElasticSearch plugin, you can use ``composer``. From your +application's ROOT directory (where composer.json file is located) run the +following:: + + php composer.phar require cakephp/elastic-search "@stable" + +You will need to add the following line to your application's +**config/bootstrap.php** file:: + + Plugin::load('Cake/ElasticSearch', ['bootstrap' => true]); + +Additionally, you will need to configure the 'elastic' datasource connection in +your **config/app.php** file. An example configuration would be:: + + // in config/app.php + 'Datasources' => [ + // other datasources + 'elastic' => [ + 'className' => 'Cake\ElasticSearch\Datasource\Connection', + 'driver' => 'Cake\ElasticSearch\Datasource\Connection', + 'host' => '127.0.0.1', + 'port' => 9200, + 'index' => 'my_apps_index', + ], + ] + +Overview +======== + +The ElasticSearch plugin makes it easier to interact with an elasticsearch index +and provides an interface similar to the :doc:`/orm`. To get started you should +create a ``Type`` object. ``Type`` objects are the "Repository" or table-like +class in elasticsearch:: + + // in src/Model/Type/ArticlesType.php + namespace App\Model\Type; + + use Cake\ElasticSearch\Type; + + class ArticlesType extends Type + { + } + +You can then use your type class in your controllers:: + + public function beforeFilter(Event $event) + { + parent::beforeFilter($event); + // Load the Type using the 'Elastic' provider. + $this->loadModel('Articles', 'Elastic'); + } + + public function add() + { + $article = $this->Articles->newEntity(); + if ($this->request->is('post')) { + $article = $this->Articles->patchEntity($article, $this->request->getData()); + if ($this->Articles->save($article)) { + $this->Flash->success('It saved'); + } + } + $this->set(compact('article')); + } + +We would also need to create a basic view for our indexed articles:: + + // in src/Template/Articles/add.ctp + Form->create($article) ?> + Form->control('title') ?> + Form->control('body') ?> + Form->button('Save') ?> + Form->end() ?> + +You should now be able to submit the form and have a new document added to +elasticsearch. + +Document Objects +================ + +Like the ORM, the Elasticsearch ODM uses :doc:`/orm/entities`-like classes. The +base class you should inherit from is ``Cake\ElasticSearch\Document``. Document +classes are found in the ``Model\Document`` namespace in your application or +plugin:: + + namespace App\Model\Document; + + use Cake\ElasticSearch\Document; + + class Article extends Document + { + } + +Outside of constructor logic that makes Documents work with data from +elasticsearch, the interface and functionality provided by ``Document`` are the +same as those in :doc:`/orm/entities` + +Searching Indexed Documents +=========================== + +After you've indexed some documents you will want to search through them. The +ElasticSearch plugin provides a query builder that allows you to build search +queries:: + + $query = $this->Articles->find() + ->where([ + 'title' => 'special', + 'or' => [ + 'tags in' => ['cake', 'php'], + 'tags not in' => ['c#', 'java'] + ] + ]); + + foreach ($query as $article) { + echo $article->title; + } + +You can use the ``FilterBuilder`` to add filtering conditions:: + + $query->where(function ($builder) { + return $builder->and( + $builder->gt('views', 99), + $builder->term('author.name', 'sally') + ); + }); + +The `FilterBuilder source +`_ +has the complete list of methods with examples for many commonly used methods. + +Validating Data & Using Application Rules +========================================= + +Like the ORM, the ElasticSearch plugin lets you validate data when marshalling +documents. Validating request data, and applying application rules works the +same as it does with the relational ORM. See the :ref:`validating-request-data` +and :ref:`application-rules` sections for more information. + +.. Need information on nested validators. + +Saving New Documents +==================== + +When you're ready to index some data into elasticsearch, you'll first need to +convert your data into a ``Document`` that can be indexed:: + + $article = $this->Articles->newEntity($data); + if ($this->Articles->save($article)) { + // Document was indexed + } + +When marshalling a document, you can specify which embedded documents you wish +to marshall using the ``associated`` key:: + + $article = $this->Articles->newEntity($data, ['associated' => ['Comments']]); + +Saving a document will trigger the following events: + +* ``Model.beforeSave`` - Fired before the document is saved. You can prevent the + save operation from happening by stopping this event. +* ``Model.buildRules`` - Fired when the rules checker is built for the first + time. +* ``Model.afterSave`` - Fired after the document is saved. + +.. note:: + There are no events for embedded documents, as the parent document and all + of its embedded documents are saved as one operation. + +Updating Existing Documents +=========================== + +When you need to re-index data, you can patch existing entities and re-save +them:: + + $query = $this->Articles->find()->where(['user.name' => 'jill']); + foreach ($query as $doc) { + $doc->set($newProperties); + $this->Articles->save($doc); + } + +Deleting Documents +================== + +After retrieving a document you can delete it:: + + $doc = $this->Articles->get($id); + $this->Articles->delete($doc); + +You can also delete documents matching specific conditions:: + + $this->Articles->deleteAll(['user.name' => 'bob']); + +Embedding Documents +=================== + +By defining embedded documents, you can attach entity classes to specific +property paths in your documents. This allows you to provide custom behavior to +the documents within a parent document. For example, you may want the comments +embedded in an article to have specific application specific methods. You can +use ``embedOne`` and ``embedMany`` to define embedded documents:: + + // in src/Model/Type/ArticlesType.php + namespace App\Model\Type; + + use Cake\ElasticSearch\Type; + + class ArticlesType extends Type + { + public function initialize() + { + $this->embedOne('User'); + $this->embedMany('Comments', [ + 'entityClass' => 'MyComment' + ]); + } + } + +The above would create two embedded documents on the ``Article`` document. The +``User`` embed will convert the ``user`` property to instances of +``App\Model\Document\User``. To get the Comments embed to use a class name +that does not match the property name, we can use the ``entityClass`` option to +configure a custom class name. + +Once we've setup our embedded documents, the results of ``find()`` and ``get()`` +will return objects with the correct embedded document classes:: + + $article = $this->Articles->get($id); + // Instance of App\Model\Document\User + $article->user; + + // Array of App\Model\Document\Comment instances + $article->comments; + +Getting Type Instances +====================== + +Like the ORM, the ElasticSearch plugin provides a factory/registry for getting +``Type`` instances:: + + use Cake\ElasticSearch\TypeRegistry; + + $articles = TypeRegistry::get('Articles'); + +Flushing the Registry +--------------------- + +During test cases you may want to flush the registry. Doing so is often useful +when you are using mock objects, or modifying a type's dependencies:: + + TypeRegistry::flush(); + +Test Fixtures +============= + +The ElasticSearch plugin provides seamless test suite integration. Just like +database fixtures, you can create test fixtures for elasticsearch. We could +define a test fixture for our Articles type with the following:: + + namespace App\Test\Fixture; + + use Cake\ElasticSearch\TestSuite\TestFixture; + + /** + * Articles fixture + */ + class ArticlesFixture extends TestFixture + { + /** + * The table/type for this fixture. + * + * @var string + */ + public $table = 'articles'; + + /** + * The mapping data. + * + * @var array + */ + public $schema = [ + 'id' => ['type' => 'integer'], + 'user' => [ + 'type' => 'nested', + 'properties' => [ + 'username' => ['type' => 'string'], + ] + ] + 'title' => ['type' => 'string'], + 'body' => ['type' => 'string'], + ]; + + public $records = [ + [ + 'user' => [ + 'username' => 'billy' + ], + 'title' => 'First Post', + 'body' => 'Some content' + ] + ]; + } + +The ``schema`` property uses the `native elasticsearch mapping format +`_. +You can safely omit the type name and top level ``properties`` key. Once your +fixtures are created you can use them in your test cases by including them in +your test's ``fixtures`` properties:: + + public $fixtures = ['app.articles']; + diff --git a/tl/epub-contents.rst b/tl/epub-contents.rst new file mode 100644 index 0000000000000000000000000000000000000000..b3081f5421d6952a598f16ac2d593d3a36c434e7 --- /dev/null +++ b/tl/epub-contents.rst @@ -0,0 +1,66 @@ +:orphan: + +Contents +######## + +.. toctree:: + :maxdepth: 3 + + intro + quickstart + appendices/3-0-migration-guide + tutorials-and-examples + contributing + + installation + development/configuration + development/routing + controllers/request-response + controllers + views + orm + + controllers/components/authentication + core-libraries/caching + bake + console-and-shells + development/debugging + deployment + core-libraries/email + development/errors + core-libraries/events + core-libraries/internationalization-and-localization + core-libraries/logging + core-libraries/form + controllers/components/pagination + plugins + development/rest + security + development/sessions + development/testing + core-libraries/validation + + core-libraries/app + core-libraries/collections + core-libraries/file-folder + core-libraries/hash + core-libraries/httpclient + core-libraries/inflector + core-libraries/number + core-libraries/registry-objects + core-libraries/text + core-libraries/time + core-libraries/xml + + core-libraries/global-constants-and-functions + chronos + debug-kit + migrations + elasticsearch + appendices + +.. todolist:: + +.. meta:: + :title lang=en: Contents + :keywords lang=en: core libraries,ref search,shells,deployment,appendices,glossary,models diff --git a/tl/index.rst b/tl/index.rst new file mode 100644 index 0000000000000000000000000000000000000000..c4575f49758173d786ecb6a2f5945ecfa96b1ac0 --- /dev/null +++ b/tl/index.rst @@ -0,0 +1,57 @@ +Welcome +####### + +CakePHP 3 is a web development framework running on **PHP 7.2** (min. PHP |minphpversion|). +Read :doc:`CakePHP at a Glance ` to get an introduction to the +fundamentals of CakePHP 3. + +The CakePHP cookbook is an openly developed and community editable documentation +project. Notice the pencil icon button fixated against the right wall; it will +direct you to the GitHub online editor of the active page, allowing you to +contribute any additions, deletions, or corrections to the documentation. + +.. container:: offline-download + + **Read the Book Anywhere** + + .. image:: /_static/img/read-the-book.jpg + + Enjoy the CakePHP cookbook almost anywhere. Available as both a PDF and + EPUB, you can now read it on more devices, as well as offline. + + - `PDF <../_downloads/en/CakePHPCookbook.pdf>`_ + - `EPUB <../_downloads/en/CakePHPCookbook.epub>`_ + - `Original Source `_ + +Getting Help +============ + +If you're stuck, there are a number of places :doc:`you can get help +`. + +First Steps +=========== + +Learning a new framework can be intimidating and exciting at the same time. To +help you along, we have created a cookbook packed with examples and recipes to +get the common tasks completed. If you are new, you should start off with the +:doc:`/quickstart` as it will give you a quick tour of what +CakePHP has to offer and how it works. + +After you've finished the Bookmarker Tutorial, you can brush up on the key +elements in a CakePHP application: + +* The :ref:`CakePHP request cycle ` +* The :doc:`conventions ` that CakePHP + uses. +* :doc:`Controllers ` handle requests and co-ordinate your models + and the responses your application creates. +* :doc:`Views ` are the presentation layer in your application. They + give you powerful tools to create HTML, JSON and the other outputs your + application needs. +* :doc:`Models ` are the key ingredient in any application. They handle + validation, and domain logic within your application. + +.. meta:: + :title lang=en: .. CakePHP Cookbook documentation master file, created by + :keywords lang=en: doc models,documentation master,presentation layer,documentation project,quickstart,original source,sphinx,liking,cookbook,validity,conventions,validation,cakephp,accuracy,storage and retrieval,heart,blog,project hope diff --git a/tl/installation.rst b/tl/installation.rst new file mode 100644 index 0000000000000000000000000000000000000000..ee2294d24e86419029bcbebdf51255e583b23ad6 --- /dev/null +++ b/tl/installation.rst @@ -0,0 +1,582 @@ +Installation +############ + +CakePHP is simple and easy to install. The minimum requirements are a web server +and a copy of CakePHP, that's it! While this chapter focuses primarily on +setting up on Apache (because it's simple to install and setup), CakePHP will +run on a variety of web servers such as nginx, LightHTTPD, or Microsoft IIS. + +Requirements +============ + +- HTTP Server. For example: Apache. Having mod\_rewrite is preferred, but + by no means required. +- PHP |minphpversion| or greater (including PHP 7.1). +- mbstring PHP extension +- intl PHP extension +- simplexml PHP extension + +.. note:: + + In both XAMPP and WAMP, the mbstring extension is working by default. + + In XAMPP, intl extension is included but you have to uncomment + ``extension=php_intl.dll`` in **php.ini** and restart the server through + the XAMPP Control Panel. + + In WAMP, the intl extension is "activated" by default but not working. + To make it work you have to go to php folder (by default) + **C:\\wamp\\bin\\php\\php{version}**, copy all the files that looks like + **icu*.dll** and paste them into the apache bin directory + **C:\\wamp\\bin\\apache\\apache{version}\\bin**. Then restart all services + and it should be OK. + +While a database engine isn't required, we imagine that most applications will +utilize one. CakePHP supports a variety of database storage engines: + +- MySQL (5.1.10 or greater) +- PostgreSQL +- Microsoft SQL Server (2008 or higher) +- SQLite 3 + +.. note:: + + All built-in drivers require PDO. You should make sure you have the correct + PDO extensions installed. + +Installing CakePHP +================== + +Before starting you should make sure that your PHP version is up to date: + +.. code-block:: bash + + php -v + +You should have PHP |minphpversion| (CLI) or higher. +Your webserver's PHP version must also be of |minphpversion| or higher, and should be +the same version your command line interface (CLI) uses. + +Installing Composer +------------------- + +CakePHP uses `Composer `_, a dependency management tool, +as the officially supported method for installation. + +- Installing Composer on Linux and macOS + + #. Run the installer script as described in the + `official Composer documentation `_ + and follow the instructions to install Composer. + #. Execute the following command to move the composer.phar to a directory + that is in your path:: + + mv composer.phar /usr/local/bin/composer + +- Installing Composer on Windows + + For Windows systems, you can download Composer's Windows installer + `here `__. Further + instructions for Composer's Windows installer can be found within the + README `here `__. + +Create a CakePHP Project +------------------------ + +Now that you've downloaded and installed Composer, create a new +CakePHP application into my_app_name folder. For this just run the +following composer command: + +.. code-block:: bash + + php composer.phar create-project --prefer-dist cakephp/app my_app_name + +Or if Composer is installed globally: + +.. code-block:: bash + + composer self-update && composer create-project --prefer-dist cakephp/app my_app_name + +Once Composer finishes downloading the application skeleton and the core CakePHP +library, you should have a functioning CakePHP application installed via +Composer. Be sure to keep the composer.json and composer.lock files with the +rest of your source code. + +You can now visit the path to where you installed your CakePHP application and +see the default home page. To change the content of this page, edit +**src/Template/Pages/home.ctp**. + +Although composer is the recommended installation method, there are +pre-installed downloads available on +`Github `__. +Those downloads contain the app skeleton with all vendor packages installed. +Also it includes the ``composer.phar`` so you have everything you need for +further use. + +Keeping Up To Date with the Latest CakePHP Changes +-------------------------------------------------- + +By default this is what your application **composer.json** looks like:: + + "require": { + "cakephp/cakephp": "3.5.*" + } + +Each time you run ``php composer.phar update`` you will receive patch +releases for this minor version. You can instead change this to ``^3.5`` to +also receive the latest stable minor releases of the ``3.x`` branch. + +If you want to stay up to date with the latest unreleased changes in CakePHP, +designate **dev-master** as the package version in your application's +**composer.json**:: + + "require": { + "cakephp/cakephp": "dev-master" + } + +Be aware that this is not recommended, as your application can break when the next major +version is released. Additionally, composer does not cache development +branches, so it slows down consecutive composer installs/updates. + +Installation using Oven +----------------------- + +Another quick way to install CakePHP is `Oven `_. +It is a simple PHP script which checks the necessary system requirements, +installs the CakePHP application skeleton, and sets up the development environment. + +After the installation completes, your CakePHP application is ready to go! + +.. note:: + + IMPORTANT: This is not a deployment script. It is aimed to help developers install + CakePHP for the first time and set up a development environment quickly. Production + environments should consider several other factors, like file permissions, + virtualhost configuration, etc. + +Permissions +=========== + +CakePHP uses the **tmp** directory for a number of different operations. +Model descriptions, cached views, and session information are a few +examples. The **logs** directory is used to write log files by the default +``FileLog`` engine. + +As such, make sure the directories **logs**, **tmp** and all its subdirectories +in your CakePHP installation are writable by the web server user. Composer's +installation process makes **tmp** and its subfolders globally writeable to get +things up and running quickly but you can update the permissions for better +security and keep them writable only for the web server user. + +One common issue is that **logs** and **tmp** directories and subdirectories +must be writable both by the web server and the command line user. On a UNIX +system, if your web server user is different from your command line user, you +can run the following commands from your application directory just once in your +project to ensure that permissions will be setup properly: + +.. code-block:: bash + + HTTPDUSER=`ps aux | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\ -f1` + setfacl -R -m u:${HTTPDUSER}:rwx tmp + setfacl -R -d -m u:${HTTPDUSER}:rwx tmp + setfacl -R -m u:${HTTPDUSER}:rwx logs + setfacl -R -d -m u:${HTTPDUSER}:rwx logs + +In order to use the CakePHP console tools, you need to ensure that +``bin/cake`` file is executable. On \*nix or macOS, you can +execute: + +.. code-block:: bash + + chmod +x bin/cake + +On Windows, the **.bat** file should be executable already. If you are using +a Vagrant, or any other virtualized environment, any shared directories need to +be shared with execute permissions (Please refer to your virtualized +environment's documentation on how to do this). + +If, for whatever reason, you cannot change the permissions of the ``bin/cake`` +file, you can run the CakePHP console with: + +.. code-block:: bash + + php bin/cake.php + +Development Server +================== + +A development installation is the fastest way to setup CakePHP. In this +example, we use CakePHP's console to run PHP's built-in web server which +will make your application available at **http://host:port**. From the app +directory, execute: + +.. code-block:: bash + + bin/cake server + +By default, without any arguments provided, this will serve your application at +**http://localhost:8765/**. + +If there is conflict with **localhost** or port 8765, you can tell +the CakePHP console to run the web server on a specific host and/or port +utilizing the following arguments: + +.. code-block:: bash + + bin/cake server -H 192.168.13.37 -p 5673 + +This will serve your application at **http://192.168.13.37:5673/**. + +That's it! Your CakePHP application is up and running without having to +configure a web server. + +.. note:: + + Try ``bin/cake server -H 0.0.0.0`` if the server is unreachable from other hosts. + +.. warning:: + + The development server should *never* be used in a production environment. + It is only intended as a basic development server. + +If you'd prefer to use a real web server, you should be able to move your CakePHP +install (including the hidden files) inside your web server's document root. You +should then be able to point your web-browser at the directory you moved the +files into and see your application in action. + +Production +========== + +A production installation is a more flexible way to setup CakePHP. Using this +method allows an entire domain to act as a single CakePHP application. This +example will help you install CakePHP anywhere on your filesystem and make it +available at http://www.example.com. Note that this installation may require the +rights to change the ``DocumentRoot`` on Apache webservers. + +After installing your application using one of the methods above into the +directory of your choosing - we'll assume you chose /cake_install - your +production setup will look like this on the file system:: + + /cake_install/ + bin/ + config/ + logs/ + plugins/ + src/ + tests/ + tmp/ + vendor/ + webroot/ (this directory is set as DocumentRoot) + .gitignore + .htaccess + .travis.yml + composer.json + index.php + phpunit.xml.dist + README.md + +Developers using Apache should set the ``DocumentRoot`` directive for the domain +to: + +.. code-block:: apacheconf + + DocumentRoot /cake_install/webroot + +If your web server is configured correctly, you should now find your CakePHP +application accessible at http://www.example.com. + +Fire It Up +========== + +Alright, let's see CakePHP in action. Depending on which setup you used, you +should point your browser to http://example.com/ or http://localhost:8765/. At +this point, you'll be presented with CakePHP's default home, and a message that +tells you the status of your current database connection. + +Congratulations! You are ready to :doc:`create your first CakePHP application +`. + +.. _url-rewriting: + +URL Rewriting +============= + +Apache +------ + +While CakePHP is built to work with mod\_rewrite out of the box–and usually +does–we've noticed that a few users struggle with getting everything to play +nicely on their systems. + +Here are a few things you might try to get it running correctly. First look at +your httpd.conf. (Make sure you are editing the system httpd.conf rather than a +user- or site-specific httpd.conf.) + +These files can vary between different distributions and Apache versions. You +may also take a look at http://wiki.apache.org/httpd/DistrosDefaultLayout for +further information. + +#. Make sure that an .htaccess override is allowed and that AllowOverride is set + to All for the correct DocumentRoot. You should see something similar to: + + .. code-block:: apacheconf + + # Each directory to which Apache has access can be configured with respect + # to which services and features are allowed and/or disabled in that + # directory (and its subdirectories). + # + # First, we configure the "default" to be a very restrictive set of + # features. + + Options FollowSymLinks + AllowOverride All + # Order deny,allow + # Deny from all + + +#. Make sure you are loading mod\_rewrite correctly. You should see something + like: + + .. code-block:: apacheconf + + LoadModule rewrite_module libexec/apache2/mod_rewrite.so + + In many systems these will be commented out by default, so you may just need + to remove the leading # symbols. + + After you make changes, restart Apache to make sure the settings are active. + + Verify that your .htaccess files are actually in the right directories. Some + operating systems treat files that start with '.' as hidden and therefore + won't copy them. + +#. Make sure your copy of CakePHP comes from the downloads section of the site + or our Git repository, and has been unpacked correctly, by checking for + .htaccess files. + + CakePHP app directory (will be copied to the top directory of your + application by bake): + + .. code-block:: apacheconf + + + RewriteEngine on + RewriteRule ^$ webroot/ [L] + RewriteRule (.*) webroot/$1 [L] + + + CakePHP webroot directory (will be copied to your application's web root by + bake): + + .. code-block:: apacheconf + + + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + + + If your CakePHP site still has problems with mod\_rewrite, you might want to + try modifying settings for Virtual Hosts. On Ubuntu, edit the file + **/etc/apache2/sites-available/default** (location is + distribution-dependent). In this file, ensure that ``AllowOverride None`` is + changed to ``AllowOverride All``, so you have: + + .. code-block:: apacheconf + + + Options FollowSymLinks + AllowOverride All + + + Options Indexes FollowSymLinks MultiViews + AllowOverride All + Order Allow,Deny + Allow from all + + + On macOS, another solution is to use the tool + `virtualhostx `_ to make a Virtual + Host to point to your folder. + + For many hosting services (GoDaddy, 1and1), your web server is being + served from a user directory that already uses mod\_rewrite. If you are + installing CakePHP into a user directory + (http://example.com/~username/cakephp/), or any other URL structure that + already utilizes mod\_rewrite, you'll need to add RewriteBase statements to + the .htaccess files CakePHP uses (.htaccess, webroot/.htaccess). + + This can be added to the same section with the RewriteEngine directive, so + for example, your webroot .htaccess file would look like: + + .. code-block:: apacheconf + + + RewriteEngine On + RewriteBase /path/to/app + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + + + The details of those changes will depend on your setup, and can include + additional things that are not related to CakePHP. Please refer to Apache's + online documentation for more information. + +#. (Optional) To improve production setup, you should prevent invalid assets + from being parsed by CakePHP. Modify your webroot .htaccess to something + like: + + .. code-block:: apacheconf + + + RewriteEngine On + RewriteBase /path/to/app/ + RewriteCond %{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_URI} !^/(webroot/)?(img|css|js)/(.*)$ + RewriteRule ^ index.php [L] + + + The above will prevent incorrect assets from being sent to index.php + and instead display your web server's 404 page. + + Additionally you can create a matching HTML 404 page, or use the default + built-in CakePHP 404 by adding an ``ErrorDocument`` directive: + + .. code-block:: apacheconf + + ErrorDocument 404 /404-not-found + +nginx +----- + +nginx does not make use of .htaccess files like Apache, so it is necessary to +create those rewritten URLs in the site-available configuration. This is usually +found in ``/etc/nginx/sites-available/your_virtual_host_conf_file``. Depending +on your setup, you will have to modify this, but at the very least, you will +need PHP running as a FastCGI instance. +The following configuration redirects the request to ``webroot/index.php``: + +.. code-block:: nginx + + location / { + try_files $uri $uri/ /index.php?$args; + } + +A sample of the server directive is as follows: + +.. code-block:: nginx + + server { + listen 80; + listen [::]:80; + server_name www.example.com; + return 301 http://example.com$request_uri; + } + + server { + listen 80; + listen [::]:80; + server_name example.com; + + root /var/www/example.com/public/webroot; + index index.php; + + access_log /var/www/example.com/log/access.log; + error_log /var/www/example.com/log/error.log; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + location ~ \.php$ { + try_files $uri =404; + include fastcgi_params; + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_intercept_errors on; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + } + } + +.. note:: + Recent configurations of PHP-FPM are set to listen to the unix php-fpm + socket instead of TCP port 9000 on address 127.0.0.1. If you get 502 bad + gateway errors from the above configuration, try update ``fastcgi_pass`` to + use the unix socket path (eg: fastcgi_pass + unix:/var/run/php/php7.1-fpm.sock;) instead of the TCP port. + +IIS7 (Windows hosts) +-------------------- + +IIS7 does not natively support .htaccess files. While there are +add-ons that can add this support, you can also import htaccess +rules into IIS to use CakePHP's native rewrites. To do this, follow +these steps: + +#. Use `Microsoft's Web Platform Installer `_ + to install the URL `Rewrite Module 2.0 `_ + or download it directly (`32-bit `_ / + `64-bit `_). +#. Create a new file called web.config in your CakePHP root folder. +#. Using Notepad or any XML-safe editor, copy the following + code into your new web.config file: + +.. code-block:: xml + + + + + + + + + + + + + + + + + + + + + + + +Once the web.config file is created with the correct IIS-friendly rewrite rules, +CakePHP's links, CSS, JavaScript, and rerouting should work correctly. + +I Can't Use URL Rewriting +------------------------- + +If you don't want or can't get mod\_rewrite (or some other compatible module) +running on your server, you will need to use CakePHP's built in pretty URLs. +In **config/app.php**, uncomment the line that looks like:: + + 'App' => [ + // ... + // 'baseUrl' => env('SCRIPT_NAME'), + ] + +Also remove these .htaccess files:: + + /.htaccess + webroot/.htaccess + +This will make your URLs look like +www.example.com/index.php/controllername/actionname/param rather than +www.example.com/controllername/actionname/param. + +.. _GitHub: http://github.com/cakephp/cakephp +.. _Composer: http://getcomposer.org + +.. meta:: + :title lang=en: Installation + :keywords lang=en: apache mod rewrite,microsoft sql server,tar bz2,tmp directory,database storage,archive copy,tar gz,source application,current releases,web servers,microsoft iis,copyright notices,database engine,bug fixes,lighthttpd,repository,enhancements,source code,cakephp,incorporate diff --git a/tl/intro.rst b/tl/intro.rst new file mode 100644 index 0000000000000000000000000000000000000000..eeac3f9d2970745f21721d95b6c65a5675b588ab --- /dev/null +++ b/tl/intro.rst @@ -0,0 +1,175 @@ +CakePHP at a Glance +################### + +CakePHP is designed to make common web-development tasks simple, and easy. By +providing an all-in-one toolbox to get you started the various parts of CakePHP +work well together or separately. + +The goal of this overview is to introduce the general concepts in CakePHP, and +give you a quick overview of how those concepts are implemented in CakePHP. If +you are itching to get started on a project, you can :doc:`start with the +tutorial `, or :doc:`dive into the docs +`. + +Conventions Over Configuration +============================== + +CakePHP provides a basic organizational structure that covers class names, +filenames, database table names, and other conventions. While the conventions +take some time to learn, by following the conventions CakePHP provides you can +avoid needless configuration and make a uniform application structure that makes +working with various projects simple. The :doc:`conventions chapter +` covers the various conventions that CakePHP uses. + +The Model Layer +=============== + +The Model layer represents the part of your application that implements the +business logic. It is responsible for retrieving data and converting it into the +primary meaningful concepts in your application. This includes processing, +validating, associating or other tasks related to handling data. + +In the case of a social network, the Model layer would take care of +tasks such as saving the user data, saving friends' associations, storing +and retrieving user photos, finding suggestions for new friends, etc. +The model objects can be thought of as "Friend", "User", "Comment", or +"Photo". If we wanted to load some data from our ``users`` table we could do:: + + use Cake\ORM\TableRegistry; + + $users = TableRegistry::get('Users'); + $query = $users->find(); + foreach ($query as $row) { + echo $row->username; + } + +You may notice that we didn't have to write any code before we could start +working with our data. By using conventions, CakePHP will use standard classes +for table and entity classes that have not yet been defined. + +If we wanted to make a new user and save it (with validation) we would do +something like:: + + use Cake\ORM\TableRegistry; + + $users = TableRegistry::get('Users'); + $user = $users->newEntity(['email' => 'mark@example.com']); + $users->save($user); + +The View Layer +============== + +The View layer renders a presentation of modeled data. Being separate from the +Model objects, it is responsible for using the information it has available +to produce any presentational interface your application might need. + +For example, the view could use model data to render an HTML view template containing it, +or a XML formatted result for others to consume:: + + // In a view template file, we'll render an 'element' for each user. + +
  • + element('user_info', ['user' => $user]) ?> +
  • + + +The View layer provides a number of extension points like :ref:`view-templates`, :ref:`view-elements` +and :doc:`/views/cells` to let you re-use your presentation logic. + +The View layer is not only limited to HTML or text representation of the data. +It can be used to deliver common data formats like JSON, XML, and through +a pluggable architecture any other format you may need, such as CSV. + +The Controller Layer +==================== + +The Controller layer handles requests from users. It is responsible for +rendering a response with the aid of both the Model and the View layers. + +A controller can be seen as a manager that ensures that all resources needed for +completing a task are delegated to the correct workers. It waits for petitions +from clients, checks their validity according to authentication or authorization +rules, delegates data fetching or processing to the model, selects the type of +presentational data that the clients are accepting, and finally delegates the +rendering process to the View layer. An example of a user registration +controller would be:: + + public function add() + { + $user = $this->Users->newEntity(); + if ($this->request->is('post')) { + $user = $this->Users->patchEntity($user, $this->request->getData()); + if ($this->Users->save($user, ['validate' => 'registration'])) { + $this->Flash->success(__('You are now registered.')); + } else { + $this->Flash->error(__('There were some problems.')); + } + } + $this->set('user', $user); + } + +You may notice that we never explicitly rendered a view. CakePHP's conventions +will take care of selecting the right view and rendering it with the view data +we prepared with ``set()``. + +.. _request-cycle: + +CakePHP Request Cycle +===================== + +Now that you are familiar with the different layers in CakePHP, lets review how +a request cycle works in CakePHP: + +.. figure:: /_static/img/typical-cake-request.png + :align: center + :alt: Flow diagram showing a typical CakePHP request + +The typical CakePHP request cycle starts with a user requesting a page or +resource in your application. At a high level each request goes through the +following steps: + +#. The webserver rewrite rules direct the request to **webroot/index.php**. +#. Your Application is loaded and bound to an ``HttpServer``. +#. Your application's middleware is initialized. +#. A request and response is dispatched through the PSR-7 Middleware that your + application uses. Typically this includes error trapping and routing. +#. If no response is returned from the middleware and the request contains + routing information, a controller & action are selected. +#. The controller's action is called and the controller interacts with the + required Models and Components. +#. The controller delegates response creation to the View to generate the output + resulting from the model data. +#. The view uses Helpers and Cells to generate the response body and headers. +#. The response is sent back out through the :doc:`/controllers/middleware`. +#. The ``HttpServer`` emits the response to the webserver. + +Just the Start +============== + +Hopefully this quick overview has piqued your interest. Some other great +features in CakePHP are: + +* A :doc:`caching ` framework that integrates with + Memcached, Redis and other backends. +* Powerful :doc:`code generation tools + ` so you can start immediately. +* :doc:`Integrated testing framework ` so you can ensure + your code works perfectly. + +The next obvious steps are to :doc:`download CakePHP `, read the +:doc:`tutorial and build something awesome +`. + +Additional Reading +================== + +.. toctree:: + :maxdepth: 1 + + /intro/where-to-get-help + /intro/conventions + /intro/cakephp-folder-structure + +.. meta:: + :title lang=en: Getting Started + :keywords lang=en: folder structure,table names,initial request,database table,organizational structure,rst,filenames,conventions,mvc,web page,sit diff --git a/tl/intro/cakephp-folder-structure.rst b/tl/intro/cakephp-folder-structure.rst new file mode 100644 index 0000000000000000000000000000000000000000..7bf747b0b3e95d5f0c8b0259af66c191515dc642 --- /dev/null +++ b/tl/intro/cakephp-folder-structure.rst @@ -0,0 +1,55 @@ +CakePHP Folder Structure +######################## + +After you've downloaded the CakePHP application skeleton, there are a few top +level folders you should see: + +- The *bin* folder holds the Cake console executables. +- The *config* folder holds the (few) :doc:`/development/configuration` files + CakePHP uses. Database connection details, bootstrapping, core configuration files + and more should be stored here. +- The *plugins* folder is where the :doc:`/plugins` your application uses are stored. +- The *logs* folder normally contains your log files, depending on your log + configuration. +- The *src* folder will be where your application’s source files will be placed. +- The *tests* folder will be where you put the test cases for your application. +- The *tmp* folder is where CakePHP stores temporary data. The actual data it + stores depends on how you have CakePHP configured, but this folder + is usually used to store translation messages, model descriptions and sometimes + session information. +- The *vendor* folder is where CakePHP and other application dependencies will + be installed by `Composer `_. Editing these files is not + advised, as Composer will overwrite your changes next time you update. +- The *webroot* directory is the public document root of your application. It + contains all the files you want to be publicly reachable. + + Make sure that the *tmp* and *logs* folders exist and are writable, + otherwise the performance of your application will be severely + impacted. In debug mode, CakePHP will warn you, if these directories are not + writable. + +The src Folder +============== + +CakePHP's *src* folder is where you will do most of your application +development. Let's look a little closer at the folders inside +*src*. + +Controller + Contains your application's controllers and their components. +Locale + Stores string files for internationalization. +Model + Contains your application's tables, entities and behaviors. +Shell + Contains the console commands and console tasks for your application. + For more information see :doc:`/console-and-shells`. +View + Presentational classes are placed here: views, cells, helpers. +Template + Presentational files are placed here: elements, error pages, + layouts, and view template files. + +.. meta:: + :title lang=en: CakePHP Folder Structure + :keywords lang=en: internal libraries,core configuration,model descriptions,external vendors,connection details,folder structure,party libraries,personal commitment,database connection,internationalization,configuration files,folders,application development,readme,lib,configured,logs,config,third party,cakephp diff --git a/tl/intro/conventions.rst b/tl/intro/conventions.rst new file mode 100644 index 0000000000000000000000000000000000000000..d407cbc3b7129961b26cb7860e4c3f79f9001ba6 --- /dev/null +++ b/tl/intro/conventions.rst @@ -0,0 +1,161 @@ +CakePHP Conventions +################### + +We are big fans of convention over configuration. While it takes a bit of time +to learn CakePHP's conventions, you save time in the long run. By following +conventions, you get free functionality, and you liberate yourself from the +maintenance nightmare of tracking config files. Conventions also make for a very +uniform development experience, allowing other developers to jump in and help. + +Controller Conventions +====================== + +Controller class names are plural, PascalCased, and end in ``Controller``. +``UsersController`` and ``ArticleCategoriesController`` are both examples of +conventional controller names. + +Public methods on Controllers are often exposed as 'actions' accessible through +a web browser. For example the ``/users/view`` maps to the ``view()`` method +of the ``UsersController`` out of the box. Protected or private methods +cannot be accessed with routing. + +URL Considerations for Controller Names +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +As you've just seen, single word controllers map to a simple lower case URL +path. For example, ``UsersController`` (which would be defined in the file name +**UsersController.php**) is accessed from http://example.com/users. + +While you can route multiple word controllers in any way you like, the +convention is that your URLs are lowercase and dashed using the ``DashedRoute`` +class, therefore ``/article-categories/view-all`` is the correct form to access +the ``ArticleCategoriesController::viewAll()`` action. + +When you create links using ``this->Html->link()``, you can use the following +conventions for the url array:: + + $this->Html->link('link-title', [ + 'prefix' => 'MyPrefix' // PascalCased + 'plugin' => 'MyPlugin', // PascalCased + 'controller' => 'ControllerName', // PascalCased + 'action' => 'actionName' // camelBacked + ] + +For more information on CakePHP URLs and parameter handling, see +:ref:`routes-configuration`. + +.. _file-and-classname-conventions: + +File and Class Name Conventions +=============================== + +In general, filenames match the class names, and follow the PSR-4 standard for +autoloading. The following are some examples of class names and their filenames: + +- The Controller class ``LatestArticlesController`` would be found in a file + named **LatestArticlesController.php** +- The Component class ``MyHandyComponent`` would be found in a file named + **MyHandyComponent.php** +- The Table class ``OptionValuesTable`` would be found in a file named + **OptionValuesTable.php**. +- The Entity class ``OptionValue`` would be found in a file named + **OptionValue.php**. +- The Behavior class ``EspeciallyFunkableBehavior`` would be found in a file + named **EspeciallyFunkableBehavior.php** +- The View class ``SuperSimpleView`` would be found in a file named + **SuperSimpleView.php** +- The Helper class ``BestEverHelper`` would be found in a file named + **BestEverHelper.php** + +Each file would be located in the appropriate folder/namespace in your app +folder. + +.. _model-and-database-conventions: + +Database Conventions +==================== + +Table names corresponding to CakePHP models are plural and underscored. For +example ``users``, ``article_categories``, and ``user_favorite_pages`` +respectively. + +Field/Column names with two or more words are underscored: ``first_name``. + +Foreign keys in hasMany, belongsTo/hasOne relationships are recognized by +default as the (singular) name of the related table followed by ``_id``. So if +Users hasMany Articles, the ``articles`` table will refer to the ``users`` +table via a ``user_id`` foreign key. For a table like ``article_categories`` +whose name contains multiple words, the foreign key would be +``article_category_id``. + +Join tables, used in BelongsToMany relationships between models, should be named +after the model tables they will join or the bake command won't work, arranged in +alphabetical order (``articles_tags`` rather than ``tags_articles``). If you +need to add additional columns on the junction table you should create +a separate entity/table class for that table. + +In addition to using an auto-incrementing integer as primary keys, you can also +use UUID columns. CakePHP will create UUID values automatically using +(:php:meth:`Cake\\Utility\\Text::uuid()`) whenever you save new records using +the ``Table::save()`` method. + +Model Conventions +================= + +Table class names are plural, PascalCased and end in ``Table``. ``UsersTable``, +``ArticleCategoriesTable``, and ``UserFavoritePagesTable`` are all examples of +table class names matching the ``users``, ``article_categories`` and +``user_favorite_pages`` tables respectively. + +Entity class names are singular PascalCased and have no suffix. ``User``, +``ArticleCategory``, and ``UserFavoritePage`` are all examples of entity names +matching the ``users``, ``article_categories`` and ``user_favorite_pages`` +tables respectively. + +View Conventions +================ + +View template files are named after the controller functions they display, in an +underscored form. The ``viewAll()`` function of the ``ArticlesController`` class +will look for a view template in **src/Template/Articles/view_all.ctp**. + +The basic pattern is +**src/Template/Controller/underscored_function_name.ctp**. + +.. note:: + + By default CakePHP uses English inflections. If you have database + tables/columns that use another language, you will need to add inflection + rules (from singular to plural and vice-versa). You can use + :php:class:`Cake\\Utility\\Inflector` to define your custom inflection + rules. See the documentation about :doc:`/core-libraries/inflector` for more + information. + +Summarized +========== + +By naming the pieces of your application using CakePHP conventions, you gain +functionality without the hassle and maintenance tethers of configuration. +Here's a final example that ties the conventions together: + +- Database table: "articles" +- Table class: ``ArticlesTable``, found at **src/Model/Table/ArticlesTable.php** +- Entity class: ``Article``, found at **src/Model/Entity/Article.php** +- Controller class: ``ArticlesController``, found at + **src/Controller/ArticlesController.php** +- View template, found at **src/Template/Articles/index.ctp** + +Using these conventions, CakePHP knows that a request to +http://example.com/articles/ maps to a call on the ``index()`` function of the +ArticlesController, where the Articles model is automatically available (and +automatically tied to the 'articles' table in the database), and renders to a +file. None of these relationships have been configured by any means other than +by creating classes and files that you'd need to create anyway. + +Now that you've been introduced to CakePHP's fundamentals, you might try a run +through the :doc:`/tutorials-and-examples/cms/installation` to see how things fit +together. + +.. meta:: + :title lang=en: CakePHP Conventions + :keywords lang=en: web development experience,maintenance nightmare,index method,legacy systems,method names,php class,uniform system,config files,tenets,articles,conventions,conventional controller,best practices,maps,visibility,news articles,functionality,logic,cakephp,developers diff --git a/tl/intro/where-to-get-help.rst b/tl/intro/where-to-get-help.rst new file mode 100644 index 0000000000000000000000000000000000000000..2e94aec35e842104058426da4618900d5a9262f9 --- /dev/null +++ b/tl/intro/where-to-get-help.rst @@ -0,0 +1,141 @@ +Where to Get Help +################# + +The Official CakePHP website +============================ + +`https://cakephp.org `_ + +The Official CakePHP website is always a great place to visit. It features links +to oft-used developer tools, screencasts, donation opportunities, and downloads. + +The Cookbook +============ + +`https://book.cakephp.org `_ + +This manual should probably be the first place you go to get answers. As with +many other open source projects, we get new folks regularly. Try your best to +answer your questions on your own first. Answers may come slower, but will +remain longer – and you'll also be lightening our support load. Both the manual +and the API have an online component. + +The Bakery +========== + +`https://bakery.cakephp.org `_ + +The CakePHP Bakery is a clearing house for all things regarding CakePHP. Check +it out for tutorials, case studies, and code examples. Once you're acquainted +with CakePHP, log on and share your knowledge with the community and gain +instant fame and fortune. + +The API +======= + +`https://api.cakephp.org/ `_ + +Straight to the point and straight from the core developers, the CakePHP API +(Application Programming Interface) is the most comprehensive documentation +around for all the nitty gritty details of the internal workings of the +framework. It's a straight forward code reference, so bring your propeller hat. + +The Test Cases +============== + +If you ever feel the information provided in the API is not sufficient, check +out the code of the test cases provided with CakePHP. They can serve as +practical examples for function and data member usage for a class. :: + + tests/TestCase/ + +The IRC Channel +=============== + +**IRC Channels on irc.freenode.net:** + +- `#cakephp `_ -- General Discussion +- `#cakephp-docs `_ -- Documentation +- `#cakephp-bakery `_ -- Bakery +- `#cakephp-fr `_ -- French Canal. + +If you're stumped, give us a holler in the CakePHP IRC channel. +Someone from the `development team `_ +is usually there, especially during the daylight hours for North and South +America users. We'd love to hear from you, whether you need some help, want to +find users in your area, or would like to donate your brand new sports car. + +.. _cakephp-official-communities: + +Official CakePHP Forum +====================== +`CakePHP Official Forum `_ + +Our official forum where you can ask for help, suggest ideas and have a talk +about CakePHP. It's a perfect place for quickly finding answers and help others. +Join the CakePHP family by signing up. + +Stackoverflow +============= + +`https://stackoverflow.com/ `_ + +Tag your questions with ``cakephp`` and the specific version you are using to +enable existing users of stackoverflow to find your questions. + +Where to get Help in your Language +================================== + +Brazilian Portuguese +-------------------- + +- `Brazilian CakePHP Community `_ + +Danish +------ + +- `Danish CakePHP Slack Channel `_ + +French +------ + +- `French CakePHP Community `_ + +German +------ + +- `German CakePHP Slack Channel `_ +- `German CakePHP Facebook Group `_ + +Iranian +------- + +- `Iranian CakePHP Community `_ + +Dutch +----- + +- `Dutch CakePHP Slack Channel `_ + +Japanese +-------- + +- `Japanese CakePHP Slack Channel `_ +- `Japanese CakePHP Facebook Group `_ + +Portuguese +---------- + +- `Portuguese CakePHP Google Group `_ + +Spanish +------- + +- `Spanish CakePHP Slack Channel `_ +- `Spanish CakePHP IRC Channel `_ +- `Spanish CakePHP Google Group `_ + +.. meta:: + :title lang=en: Where to Get Help + :description lang=en: Where to get help with CakePHP: The official CakePHP website, The Cookbook, The Bakery, The API, in the test cases, the IRC channel, The CakePHP Google Group or CakePHP Questions. + :keywords lang=en: cakephp,cakephp help,help with cakephp,where to get help,cakephp irc,cakephp questions,cakephp api,cakephp test cases,open source projects,channel irc,code reference,irc channel,developer tools,test case,bakery diff --git a/tl/migrations.rst b/tl/migrations.rst new file mode 100644 index 0000000000000000000000000000000000000000..34404f8d773879e3dd5f5aae0028a7c222421e83 --- /dev/null +++ b/tl/migrations.rst @@ -0,0 +1,982 @@ +Migrations +########## + +Migrations is a plugin supported by the core team that helps you do schema +changes in your database by writing PHP files that can be tracked using your +version control system. + +It allows you to evolve your database tables over time. Instead of writing +schema modifications in SQL, this plugin allows you to use an intuitive set of +methods to implement your database changes. + +This plugin is a wrapper for the database migrations library `Phinx `_. + +Installation +============ + +By default Migrations is installed with the default application skeleton. If +you've removed it and want to re-install it, you can do so by running the +following from your application's ROOT directory (where composer.json file is +located):: + + $ php composer.phar require cakephp/migrations "@stable" + + // Or if composer is installed globally + + $ composer require cakephp/migrations "@stable" + +To use the plugin you'll need to load it in your application's +**config/bootstrap.php** file. You can use +:ref:`CakePHP's Plugin shell ` to load and unload plugins from +your **config/bootstrap.php**:: + + $ bin/cake plugin load Migrations + +Or you can load the plugin by editing your **config/bootstrap.php** file and +adding the following statement:: + + Plugin::load('Migrations'); + +Additionally, you will need to configure the default database configuration for your +application in your **config/app.php** file as explained in the +:ref:`Database Configuration section `. + +Overview +======== + +A migration is basically a single PHP file that describes the changes to operate +to the database. A migration file can create or drop tables, add or remove +columns, create indexes and even insert data into your database. + +Here's an example of a migration:: + + table('products'); + $table->addColumn('name', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => false, + ]); + $table->addColumn('description', 'text', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('created', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('modified', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->create(); + } + } + +This migration will add a table to your database named ``products`` with the +following column definitions: + +- ``id`` column of type ``integer`` as primary key +- ``name`` column of type ``string`` +- ``description`` column of type ``text`` +- ``created`` column of type ``datetime`` +- ``modified`` column of type ``datetime`` + +.. tip:: + + The primary key column named ``id`` will be added **implicitly**. + +.. note:: + + Note that this file describes how the database will look **after** + applying the migration. At this point no ``products`` table exists in + your database, we have merely created a file that is able to both create + the ``products`` table with the specified columns as well as drop it + when a ``rollback`` operation of the migration is performed. + +Once the file has been created in the **config/Migrations** folder, you will be +able to execute the following ``migrations`` command to create the table in +your database:: + + bin/cake migrations migrate + +The following ``migrations`` command will perform a ``rollback`` and drop the +table from your database:: + + bin/cake migrations rollback + +Creating Migrations +=================== + +Migration files are stored in the **config/Migrations** directory of your +application. The name of the migration files are prefixed with the date in +which they were created, in the format **YYYYMMDDHHMMSS_MigrationName.php**. +Here are examples of migration filenames: + +* 20160121163850_CreateProducts.php +* 20160210133047_AddRatingToProducts.php + +The easiest way to create a migrations file is by using the +:doc:`/bake/usage` CLI command. + +Please make sure you read the official +`Phinx documentation `_ +in order to know the complete list of methods you can use for writing migration +files. + +.. note:: + + When using the ``bake`` option, you can still modify the migration before + running them if so desired. + +Syntax +------ + +The ``bake`` command syntax follows the form below:: + + $ bin/cake bake migration CreateProducts name:string description:text created modified + +When using ``bake`` to create tables, add columns and so on, to your +database, you will usually provide two things: + +* the name of the migration you will generate (``CreateProducts`` in our + example) +* the columns of the table that will be added or removed in the migration + (``name:string description:text created modified`` in our example) + +Due to the conventions, not all schema changes can be performed via these shell +commands. + +Additionally you can create an empty migrations file if you want full control +over what needs to be executed, by omitting to specify a columns definition:: + + $ bin/cake migrations create MyCustomMigration + +Migrations file name +~~~~~~~~~~~~~~~~~~~~ + +Migration names can follow any of the following patterns: + +* (``/^(Create)(.*)/``) Creates the specified table. +* (``/^(Drop)(.*)/``) Drops the specified table. + Ignores specified field arguments +* (``/^(Add).*(?:To)(.*)/``) Adds fields to the specified + table +* (``/^(Remove).*(?:From)(.*)/``) Removes fields from the + specified table +* (``/^(Alter)(.*)/``) Alters the specified table. An alias + for CreateTable and AddField. + +You can also use the ``underscore_form`` as the name for your migrations i.e. +``create_products``. + +.. versionadded:: cakephp/migrations 1.5.2 + + As of v1.5.2 of the `migrations plugin `_, + the migration filename will be automatically camelized. This version of the + plugin is only available with a release of CakePHP >= to 3.1. Prior to this + version of the plugin the migration name would be in the underscore form, + i.e. ``20160121164955_create_products.php``. + +.. warning:: + + Migration names are used as migration class names, and thus may collide with + other migrations if the class names are not unique. In this case, it may be + necessary to manually override the name at a later date, or simply change + the name you are specifying. + +Columns definition +~~~~~~~~~~~~~~~~~~ + +When using columns in the command line, it may be handy to remember that they +follow the following pattern:: + + fieldName:fieldType?[length]:indexType:indexName + +For instance, the following are all valid ways of specifying an email field: + +* ``email:string?`` +* ``email:string:unique`` +* ``email:string?[50]`` +* ``email:string:unique:EMAIL_INDEX`` +* ``email:string[120]:unique:EMAIL_INDEX`` + +The question mark following the fieldType will make the column nullable. + +The ``length`` parameter for the ``fieldType`` is optional and should always be +written between bracket. + +Fields named ``created`` and ``modified``, as well as any field with a ``_at`` +suffix, will automatically be set to the type ``datetime``. + +Field types are those generically made available by the ``Phinx`` library. Those +can be: + +* string +* text +* integer +* biginteger +* float +* decimal +* datetime +* timestamp +* time +* date +* binary +* boolean +* uuid + +There are some heuristics to choosing fieldtypes when left unspecified or set to +an invalid value. Default field type is ``string``: + +* id: integer +* created, modified, updated: datetime + +Creating a table +---------------- + +You can use ``bake`` to create a table:: + + $ bin/cake bake migration CreateProducts name:string description:text created modified + +The command line above will generate a migration file that resembles:: + + table('products'); + $table->addColumn('name', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => false, + ]); + $table->addColumn('description', 'text', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('created', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->addColumn('modified', 'datetime', [ + 'default' => null, + 'null' => false, + ]); + $table->create(); + } + } + +Adding columns to an existing table +----------------------------------- + +If the migration name in the command line is of the form "AddXXXToYYY" and is +followed by a list of column names and types then a migration file containing +the code for creating the columns will be generated:: + + $ bin/cake bake migration AddPriceToProducts price:decimal + +Executing the command line above will generate:: + + table('products'); + $table->addColumn('price', 'decimal') + ->update(); + } + } + +Adding a column as index to a table +----------------------------------- + +It is also possible to add indexes to columns:: + + $ bin/cake bake migration AddNameIndexToProducts name:string:index + +will generate:: + + table('products'); + $table->addColumn('name', 'string') + ->addIndex(['name']) + ->update(); + } + } + +Specifying field length +----------------------- + +.. versionadded:: cakephp/migrations 1.4 + +If you need to specify a field length, you can do it within brackets in the +field type, ie:: + + $ bin/cake bake migration AddFullDescriptionToProducts full_description:string[60] + +Executing the command line above will generate:: + + table('products'); + $table->addColumn('full_description', 'string', [ + 'default' => null, + 'limit' => 60, + 'null' => false, + ]) + ->update(); + } + } + +If no length is specified, lengths for certain type of columns are defaulted: + +* string: 255 +* integer: 11 +* biginteger: 20 + +Removing a column from a table +------------------------------ + +In the same way, you can generate a migration to remove a column by using the +command line, if the migration name is of the form "RemoveXXXFromYYY":: + + $ bin/cake bake migration RemovePriceFromProducts price + +creates the file:: + + table('products'); + $table->removeColumn('price') + ->save(); + } + } + +.. note:: + + The `removeColumn` command is not reversible, so must be called in the + `up` method. A corresponding `addColumn` call should be added to the + `down` method. + +Generating migrations from an existing database +=============================================== + +If you are dealing with a pre-existing database and want to start using +migrations, or to version control the initial schema of your application's +database, you can run the ``migration_snapshot`` command:: + + $ bin/cake bake migration_snapshot Initial + +It will generate a migration file called **YYYYMMDDHHMMSS_Initial.php** +containing all the create statements for all tables in your database. + +By default, the snapshot will be created by connecting to the database defined +in the ``default`` connection configuration. +If you need to bake a snapshot from a different datasource, you can use the +``--connection`` option:: + + $ bin/cake bake migration_snapshot Initial --connection my_other_connection + +You can also make sure the snapshot includes only the tables for which you have +defined the corresponding model classes by using the ``--require-table`` flag:: + + $ bin/cake bake migration_snapshot Initial --require-table + +When using the ``--require-table`` flag, the shell will look through your +application ``Table`` classes and will only add the model tables in the snapshot +. + +The same logic will be applied implicitly if you wish to bake a snapshot for a +plugin. To do so, you need to use the ``--plugin`` option:: + + $ bin/cake bake migration_snapshot Initial --plugin MyPlugin + +Only the tables which have a ``Table`` object model class defined will be added +to the snapshot of your plugin. + +.. note:: + + When baking a snapshot for a plugin, the migration files will be created + in your plugin's **config/Migrations** directory. + +Be aware that when you bake a snapshot, it is automatically added to the phinx +log table as migrated. + +Generating a diff between two database states +============================================= + +.. versionadded:: cakephp/migrations 1.6.0 + +You can generate a migrations file that will group all the differences between +two database states using the ``migration_diff`` bake template. To do so, you +can use the following command:: + + $ bin/cake bake migration_diff NameOfTheMigrations + +In order to have a point of comparison from your current database state, the +migrations shell will generate a "dump" file after each ``migrate`` or +``rollback`` call. The dump file is a file containing the full schema state of +your database at a given point in time. + +Once a dump file is generated, every modifications you do directly in your +database management system will be added to the migration file generated when +you call the ``bake migration_diff`` command. + +By default, the diff will be created by connecting to the database defined +in the ``default`` connection configuration. +If you need to bake a diff from a different datasource, you can use the +``--connection`` option:: + + $ bin/cake bake migration_diff NameOfTheMigrations --connection my_other_connection + +If you want to use the diff feature on an application that already has a +migrations history, you need to manually create the dump file that will be used +as comparison:: + + $ bin/cake migrations dump + +The database state must be the same as it would be if you just migrated all +your migrations before you create a dump file. +Once the dump file is generated, you can start doing changes in your database +and use the ``bake migration_diff`` command whenever you see fit. + +.. note:: + + The migrations shell can not detect column renamings. + +The commands +============ + +``migrate`` : Applying Migrations +--------------------------------- + +Once you have generated or written your migration file, you need to execute the +following command to apply the changes to your database:: + + # Run all the migrations + $ bin/cake migrations migrate + + # Migrate to a specific version using the ``--target`` option + # or ``-t`` for short. + # The value is the timestamp that is prefixed to the migrations file name:: + $ bin/cake migrations migrate -t 20150103081132 + + # By default, migration files are looked for in the **config/Migrations** + # directory. You can specify the directory using the ``--source`` option + # or ``-s`` for short. + # The following example will run migrations in the **config/Alternate** + # directory + $ bin/cake migrations migrate -s Alternate + + # You can run migrations to a different connection than the ``default`` one + # using the ``--connection`` option or ``-c`` for short + $ bin/cake migrations migrate -c my_custom_connection + + # Migrations can also be run for plugins. Simply use the ``--plugin`` option + # or ``-p`` for short + $ bin/cake migrations migrate -p MyAwesomePlugin + +``rollback`` : Reverting Migrations +----------------------------------- + +The Rollback command is used to undo previous migrations executed by this +plugin. It is the reverse action of the ``migrate`` command:: + + # You can rollback to the previous migration by using the + # ``rollback`` command:: + $ bin/cake migrations rollback + + # You can also pass a migration version number to rollback + # to a specific version:: + $ bin/cake migrations rollback -t 20150103081132 + +You can also use the ``--source``, ``--connection`` and ``--plugin`` options +just like for the ``migrate`` command. + +``status`` : Migrations Status +------------------------------ + +The Status command prints a list of all migrations, along with their current +status. You can use this command to determine which migrations have been run:: + + $ bin/cake migrations status + +You can also output the results as a JSON formatted string using the +``--format`` option (or ``-f`` for short):: + + $ bin/cake migrations status --format json + +You can also use the ``--source``, ``--connection`` and ``--plugin`` options +just like for the ``migrate`` command. + +``mark_migrated`` : Marking a migration as migrated +--------------------------------------------------- + +.. versionadded:: 1.4.0 + +It can sometimes be useful to mark a set of migrations as migrated without +actually running them. +In order to do this, you can use the ``mark_migrated`` command. +The command works seamlessly as the other commands. + +You can mark all migrations as migrated using this command:: + + $ bin/cake migrations mark_migrated + +You can also mark all migrations up to a specific version as migrated using +the ``--target`` option:: + + $ bin/cake migrations mark_migrated --target=20151016204000 + +If you do not want the targeted migration to be marked as migrated during the +process, you can use the ``--exclude`` flag with it:: + + $ bin/cake migrations mark_migrated --target=20151016204000 --exclude + +Finally, if you wish to mark only the targeted migration as migrated, you can +use the ``--only`` flag:: + + $ bin/cake migrations mark_migrated --target=20151016204000 --only + +You can also use the ``--source``, ``--connection`` and ``--plugin`` options +just like for the ``migrate`` command. + +.. note:: + + When you bake a snapshot with the ``cake bake migration_snapshot`` + command, the created migration will automatically be marked as migrated. + +.. deprecated:: 1.4.0 + + The following way of using the command has been deprecated. Use it only + if you are using a version of the plugin < 1.4.0. + +This command expects the migration version number as argument:: + + $ bin/cake migrations mark_migrated 20150420082532 + +If you wish to mark all migrations as migrated, you can use the ``all`` special +value. If you use it, it will mark all found migrations as migrated:: + + $ bin/cake migrations mark_migrated all + +``seed`` : Seeding your database +-------------------------------- + +As of 1.5.5, you can use the ``migrations`` shell to seed your database. This +leverages the `Phinx library seed feature `_. +By default, seed files will be looked for in the ``config/Seeds`` directory of +your application. Please make sure you follow +`Phinx instructions to build your seed files `_. + +As for migrations, a ``bake`` interface is provided for seed files:: + + # This will create a ArticlesSeed.php file in the directory config/Seeds of your application + # By default, the table the seed will try to alter is the "tableized" version of the seed filename + $ bin/cake bake seed Articles + + # You specify the name of the table the seed files will alter by using the ``--table`` option + $ bin/cake bake seed Articles --table my_articles_table + + # You can specify a plugin to bake into + $ bin/cake bake seed Articles --plugin PluginName + + # You can specify an alternative connection when generating a seeder. + $ bin/cake bake seed Articles --connection connection + +.. versionadded:: cakephp/migrations 1.6.4 + + Options ``--data``, ``--limit`` and ``--fields`` were added to export + data from your database. + +As of 1.6.4, the ``bake seed`` command allows you to create a seed file with +data exported from your database by using the ``--data`` flag:: + + $ bin/cake bake seed --data Articles + +By default, it will export all the rows found in your table. You can limit the +number of rows exported by using the ``--limit`` option:: + + # Will only export the first 10 rows found + $ bin/cake bake seed --data --limit 10 Articles + +If you only want to include a selection of fields from the table in your seed +file, you can use the ``--fields`` option. It takes the list of fields to +include as a comma separated value string:: + + # Will only export the fields `id`, `title` and `excerpt` + $ bin/cake bake seed --data --fields id,title,excerpt Articles + +.. tip:: + + Of course you can use both the ``--limit`` and ``--fields`` options in the + same command call. + +To seed your database, you can use the ``seed`` subcommand:: + + # Without parameters, the seed subcommand will run all available seeders + # in the target directory, in alphabetical order. + $ bin/cake migrations seed + + # You can specify only one seeder to be run using the `--seed` option + $ bin/cake migrations seed --seed ArticlesSeed + + # You can run seeders from an alternative directory + $ bin/cake migrations seed --source AlternativeSeeds + + # You can run seeders from a plugin + $ bin/cake migrations seed --plugin PluginName + + # You can run seeders from a specific connection + $ bin/cake migrations seed --connection connection + +Be aware that, as opposed to migrations, seeders are not tracked, which means +that the same seeder can be applied multiple times. + +Calling a Seeder from another Seeder +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: cakephp/migrations 1.6.2 + +Usually when seeding, the order in which to insert the data must be respected +to not encounter constraints violations. Since Seeders are executed in the +alphabetical order by default, you can use the ``\Migrations\AbstractSeed::call()`` +method to define your own sequence of seeders execution:: + + use Migrations\AbstractSeed; + + class DatabaseSeed extends AbstractSeed + { + public function run() + { + $this->call('AnotherSeed'); + $this->call('YetAnotherSeed'); + + // You can use the plugin dot syntax to call seeders from a plugin + $this->call('PluginName.FromPluginSeed'); + } + } + +.. note:: + + Make sure to extend the Migrations plugin ``AbstractSeed`` class if you want + to be able to use the ``call()`` method. This class was added with release + 1.6.2. + +``dump`` : Generating a dump file for the diff baking feature +------------------------------------------------------------- + +The Dump command creates a file to be used with the ``migration_diff`` bake +template:: + + $ bin/cake migrations dump + +Each generated dump file is specific to the Connection it is generated from (and +is suffixed as such). This allows the ``bake migration_diff`` command to +properly compute diff in case your application is dealing with multiple database +possibly from different database vendors. + +Dump files are created in the same directory as your migrations files. + +You can also use the ``--source``, ``--connection`` and ``--plugin`` options +just like for the ``migrate`` command. + +Using Migrations In Plugins +=========================== + +Plugins can also provide migration files. This makes plugins that are intended +to be distributed much more portable and easy to install. All commands in the +Migrations plugin support the ``--plugin`` or ``-p`` option that will scope the +execution to the migrations relative to that plugin:: + + $ bin/cake migrations status -p PluginName + + $ bin/cake migrations migrate -p PluginName + +Running Migrations in a non-shell environment +============================================= + +.. versionadded:: cakephp/migrations 1.2.0 + +Since the release of version 1.2 of the migrations plugin, you can run +migrations from a non-shell environment, directly from an app, by using the new +``Migrations`` class. This can be handy in case you are developing a plugin +installer for a CMS for instance. +The ``Migrations`` class allows you to run the following commands from the +migrations shell: + +* migrate +* rollback +* markMigrated +* status +* seed + +Each of these commands has a method defined in the ``Migrations`` class. + +Here is how to use it:: + + use Migrations\Migrations; + + $migrations = new Migrations(); + + // Will return an array of all migrations and their status + $status = $migrations->status(); + + // Will return true if success. If an error occurred, an exception will be thrown + $migrate = $migrations->migrate(); + + // Will return true if success. If an error occurred, an exception will be thrown + $rollback = $migrations->rollback(); + + // Will return true if success. If an error occurred, an exception will be thrown + $markMigrated = $migrations->markMigrated(20150804222900); + + // Will return true if success. If an error occurred, an exception will be thrown + $seeded = $migrations->seed(); + +The methods can accept an array of parameters that should match options from +the commands:: + + use Migrations\Migrations; + + $migrations = new Migrations(); + + // Will return an array of all migrations and their status + $status = $migrations->status(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); + +You can pass any options the shell commands would take. +The only exception is the ``markMigrated`` command which is expecting the +version number of the migrations to mark as migrated as first argument. Pass +the array of parameters as the second argument for this method. + +Optionally, you can pass these parameters in the constructor of the class. +They will be used as default and this will prevent you from having to pass +them on each method call:: + + use Migrations\Migrations; + + $migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); + + // All the following calls will be done with the parameters passed to the Migrations class constructor + $status = $migrations->status(); + $migrate = $migrations->migrate(); + +If you need to override one or more default parameters for one call, you can +pass them to the method:: + + use Migrations\Migrations; + + $migrations = new Migrations(['connection' => 'custom', 'source' => 'MyMigrationsFolder']); + + // This call will be made with the "custom" connection + $status = $migrations->status(); + // This one with the "default" connection + $migrate = $migrations->migrate(['connection' => 'default']); + +Tips and tricks +=============== + +Creating Custom Primary Keys +---------------------------- + +If you need to avoid the automatic creation of the ``id`` primary key when +adding new tables to the database, you can use the second argument of the +``table()`` method:: + + table('products', ['id' => false, 'primary_key' => ['id']]); + $table + ->addColumn('id', 'uuid') + ->addColumn('name', 'string') + ->addColumn('description', 'text') + ->create(); + } + } + +The above will create a ``CHAR(36)`` ``id`` column that is also the primary key. + +.. note:: + + When specifying a custom primary key on the command line, you must note + it as the primary key in the id field, otherwise you may get an error + regarding duplicate id fields, i.e.:: + + $ bin/cake bake migration CreateProducts id:uuid:primary name:string description:text created modified + +Additionally, since Migrations 1.3, a new way to deal with primary key was +introduced. To do so, your migration class should extend the new +``Migrations\AbstractMigration`` class. +You can specify a ``autoId`` property in the Migration class and set it to +``false``, which will turn off the automatic ``id`` column creation. You will +need to manually create the column that will be used as a primary key and add +it to the table declaration:: + + table('products'); + $table + ->addColumn('id', 'integer', [ + 'autoIncrement' => true, + 'limit' => 11 + ]) + ->addPrimaryKey('id') + ->addColumn('name', 'string') + ->addColumn('description', 'text') + ->create(); + } + } + +Compared to the previous way of dealing with primary key, this method gives you +the ability to have more control over the primary key column definition: +unsigned or not, limit, comment, etc. + +All baked migrations and snapshot will use this new way when necessary. + +.. warning:: + + Dealing with primary key can only be done on table creation operations. + This is due to limitations for some database servers the plugin supports. + +Collations +---------- + +If you need to create a table with a different collation than the database +default one, you can define it with the ``table()`` method, as an option:: + + table('categories', [ + 'collation' => 'latin1_german1_ci' + ]) + ->addColumn('title', 'string', [ + 'default' => null, + 'limit' => 255, + 'null' => false, + ]) + ->create(); + } + } + +Note however this can only be done on table creation : there is currently +no way of adding a column to an existing table with a different collation than +the table or the database. +Only ``MySQL`` and ``SqlServer`` supports this configuration key for the time +being. + +Updating columns name and using Table objects +--------------------------------------------- + +If you use a CakePHP ORM Table object to manipulate values from your database +along with renaming or removing a column, make sure you create a new instance of +your Table object after the ``update()`` call. The Table object registry is +cleared after an ``update()`` call in order to refresh the schema that is +reflected and stored in the Table object upon instantiation. + +Migrations and Deployment +------------------------- + +If you use the plugin when deploying your application, be sure to clear the ORM +cache so it renews the column metadata of your tables. +Otherwise, you might end up having errors about columns not existing when +performing operations on those new columns. +The CakePHP Core includes a :doc:`ORM Cache Shell ` +that you can use to perform this operation:: + + $ bin/cake orm_cache clear + +Be sure to read the :doc:`ORM Cache Shell ` +section of the cookbook if you want to know more about this shell. + +Renaming a table +---------------- + +The plugin gives you the ability to rename a table, using the ``rename()`` +method. +In your migration file, you can do the following:: + + public function up() + { + $this->table('old_table_name') + ->rename('new_table_name'); + } + +Skipping the ``schema.lock`` file generation +-------------------------------------------- + +.. versionadded:: cakephp/migrations 1.6.5 + +In order for the diff feature to work, a **.lock** file is generated everytime +you migrate, rollback or bake a snapshot, to keep track of the state of your +database schema at any given point in time. You can skip this file generation, +for instance when deploying on your production environment, by using the +``--no-lock`` option for the aforementioned command:: + + $ bin/cake migrations migrate --no-lock + + $ bin/cake migrations rollback --no-lock + + $ bin/cake bake migration_snapshot MyMigration --no-lock + diff --git a/tl/orm.rst b/tl/orm.rst new file mode 100644 index 0000000000000000000000000000000000000000..731a796db045d36142f77614fd552176ffd2359f --- /dev/null +++ b/tl/orm.rst @@ -0,0 +1,127 @@ +Database Access & ORM +##################### + +In CakePHP working with data through the database is done with two primary object +types. The first are **repositories** or **table objects**. These objects +provide access to collections of data. They allow you to save new records, +modify/delete existing ones, define relations, and perform bulk operations. The +second type of objects are **entities**. Entities represent individual records +and allow you to define row/record level behavior & functionality. + +These two classes are usually responsible for managing almost everything +that happens regarding your data, its validity, interactions and evolution +of the information workflow in your domain of work. + +CakePHP's built-in ORM specializes in relational databases, but can be extended +to support alternative datasources. + +The CakePHP ORM borrows ideas and concepts from both ActiveRecord and Datamapper +patterns. It aims to create a hybrid implementation that combines aspects of +both patterns to create a fast, simple to use ORM. + +Before we get started exploring the ORM, make sure you :ref:`configure your +database connections `. + +.. note:: + + If you are familiar with previous versions of CakePHP, you should read the + :doc:`/appendices/orm-migration` for important differences between CakePHP 3.0 + and older versions of CakePHP. + +Quick Example +============= + +To get started you don't have to write any code. If you've followed the :ref:`CakePHP +conventions for your database tables ` +you can just start using the ORM. For example if we wanted to load some data from our ``articles`` +table we could do:: + + use Cake\ORM\TableRegistry; + + $articles = TableRegistry::get('Articles'); + + $query = $articles->find(); + + foreach ($query as $row) { + echo $row->title; + } + +Note that we didn't have to create any code or wire any configuration up. +The conventions in CakePHP allow us to skip some boilerplate code and allow the +framework to insert base classes when your application has not created +a concrete class. If we wanted to customize our ArticlesTable class adding some +associations or defining some additional methods we would add the following to +**src/Model/Table/ArticlesTable.php** after the ``find(); + + foreach ($query as $row) { + // Each row is now an instance of our Article class. + echo $row->title; + } + +CakePHP uses naming conventions to link the Table and Entity class together. If +you need to customize which entity a table uses you can use the +``entityClass()`` method to set a specific classname. + +See the chapters on :doc:`/orm/table-objects` and :doc:`/orm/entities` for more +information on how to use table objects and entities in your application. + +More Information +================ + +.. toctree:: + :maxdepth: 2 + + orm/database-basics + orm/query-builder + orm/table-objects + orm/entities + orm/retrieving-data-and-resultsets + orm/validation + orm/saving-data + orm/deleting-data + orm/associations + orm/behaviors + orm/schema-system + console-and-shells/orm-cache diff --git a/tl/orm/associations.rst b/tl/orm/associations.rst new file mode 100644 index 0000000000000000000000000000000000000000..0ef201df57ba7782719aca3acc5458d307c1c7fe --- /dev/null +++ b/tl/orm/associations.rst @@ -0,0 +1,690 @@ +Associations - Linking Tables Together +###################################### + +Defining relations between different objects in your application should be +a natural process. For example, an article may have many comments, and belong +to an author. Authors may have many articles and comments. CakePHP makes +managing these associations easy. The four association types in CakePHP are: +hasOne, hasMany, belongsTo, and belongsToMany. + +============= ===================== ======================================= +Relationship Association Type Example +============= ===================== ======================================= +one to one hasOne A user has one profile. +------------- --------------------- --------------------------------------- +one to many hasMany A user can have multiple articles. +------------- --------------------- --------------------------------------- +many to one belongsTo Many articles belong to a user. +------------- --------------------- --------------------------------------- +many to many belongsToMany Tags belong to many articles. +============= ===================== ======================================= + +Associations are defined during the ``initialize()`` method of your table +object. Methods matching the association type allow you to define the +associations in your application. For example if we wanted to define a belongsTo +association in our ArticlesTable:: + + namespace App\Model\Table; + + use Cake\ORM\Table; + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->belongsTo('Authors'); + } + } + +The simplest form of any association setup takes the table alias you want to +associate with. By default all of the details of an association will use the +CakePHP conventions. If you want to customize how your associations are handled +you can modify them with setters:: + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->belongsTo('Authors', [ + 'className' => 'Publishing.Authors' + ]) + ->setForeignKey('authorid') + ->setProperty('person'); + } + } + +You can also use arrays to customize your associations:: + + $this->belongsTo('Authors', [ + 'className' => 'Publishing.Authors', + 'foreignKey' => 'authorid', + 'propertyName' => 'person' + ]); + +Arrays, however, do not offer the typehinting and autocomplete benefit, the fluent interface does. + +The same table can be used multiple times to define different types of +associations. For example consider a case where you want to separate +approved comments and those that have not been moderated yet:: + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->hasMany('Comments') + ->setConditions(['approved' => true]); + + $this->hasMany('UnapprovedComments', [ + 'className' => 'Comments' + ]) + ->setConditions(['approved' => false]) + ->setProperty('unapproved_comments'); + } + } + +As you can see, by specifying the ``className`` key, it is possible to use the +same table as different associations for the same table. You can even create +self-associated tables to create parent-child relationships:: + + class CategoriesTable extends Table + { + public function initialize(array $config) + { + $this->hasMany('SubCategories', [ + 'className' => 'Categories' + ]); + + $this->belongsTo('ParentCategories', [ + 'className' => 'Categories' + ]); + } + } + +You can also setup associations in mass by making a single call to +``Table::addAssociations()`` which accepts an array containing a set of +table names indexed by association type as an argument:: + + class PostsTable extends Table + { + public function initialize(array $config) + { + $this->addAssociations([ + 'belongsTo' => [ + 'Users' => ['className' => 'App\Model\Table\UsersTable'] + ], + 'hasMany' => ['Comments'], + 'belongsToMany' => ['Tags'] + ]); + } + } + +Each association type accepts multiple associations where the keys are the +aliases, and the values are association config data. If numeric keys are used +the values will be treated as association aliases. + +.. _has-one-associations: + +HasOne Associations +=================== + +Let's set up a Users Table with a hasOne relationship to an Addresses Table. + +First, your database tables need to be keyed correctly. For a hasOne +relationship to work, one table has to contain a foreign key that points to a +record in the other. In this case the addresses table will contain a field +called ``user_id``. The basic pattern is: + +**hasOne:** the *other* model contains the foreign key. + +====================== ================== +Relation Schema +====================== ================== +Users hasOne Addresses addresses.user\_id +---------------------- ------------------ +Doctors hasOne Mentors mentors.doctor\_id +====================== ================== + +.. note:: + + It is not mandatory to follow CakePHP conventions, you can override the use + of any foreignKey in your associations definitions. Nevertheless sticking + to conventions will make your code less repetitive, easier to read and to + maintain. + +If we had the ``UsersTable`` and ``AddressesTable`` classes made we could make +the association with the following code:: + + class UsersTable extends Table + { + public function initialize(array $config) + { + $this->hasOne('Addresses'); + } + } + +If you need more control, you can define your associations using the setters. +For example, you might want to limit the association to include only certain +records:: + + class UsersTable extends Table + { + public function initialize(array $config) + { + $this->hasOne('Addresses') + ->setName('Addresses') + ->setConditions(['Addresses.primary' => '1']) + ->setDependent(true); + } + } + +Possible keys for hasOne association arrays include: + +- **className**: the class name of the table being associated to the current + model. If you're defining a 'User hasOne Address' relationship, the className + key should equal 'Addresses'. +- **foreignKey**: the name of the foreign key found in the other table. This is + especially handy if you need to define multiple hasOne relationships. The + default value for this key is the underscored, singular name of the current + model, suffixed with '\_id'. In the example above it would default to + 'user\_id'. +- **bindingKey**: The name of the column in the current table, that will be used + for matching the ``foreignKey``. If not specified, the primary key (for + example the id column of the ``Users`` table) will be used. +- **conditions**: an array of find() compatible conditions such as + ``['Addresses.primary' => true]`` +- **joinType**: the type of the join to use in the SQL query, default + is LEFT. You can use INNER if your hasOne association is always present. +- **dependent**: When the dependent key is set to ``true``, and an entity is + deleted, the associated model records are also deleted. In this case we set it + to ``true`` so that deleting a User will also delete her associated Address. +- **cascadeCallbacks**: When this and **dependent** are ``true``, cascaded + deletes will load and delete entities so that callbacks are properly + triggered. When ``false``, ``deleteAll()`` is used to remove associated data + and no callbacks are triggered. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & singular name of the association so ``address`` in our example. +- **strategy**: Defines the query strategy to use. Defaults to 'join'. The other + valid value is 'select', which utilizes a separate query instead. +- **finder**: The finder method to use when loading associated records. + +Once this association has been defined, find operations on the Users table can +contain the Address record if it exists:: + + // In a controller or table method. + $query = $users->find('all')->contain(['Addresses']); + foreach ($query as $user) { + echo $user->address->street; + } + +The above would emit SQL that is similar to:: + + SELECT * FROM users INNER JOIN addresses ON addresses.user_id = users.id; + +.. _belongs-to-associations: + +BelongsTo Associations +====================== + +Now that we have Address data access from the User table, let's define +a belongsTo association in the Addresses table in order to get access to related +User data. The belongsTo association is a natural complement to the hasOne and +hasMany associations - it allows us to see related data from the other +direction. + +When keying your database tables for a belongsTo relationship, follow this +convention: + +**belongsTo:** the *current* model contains the foreign key. + +========================= ================== +Relation Schema +========================= ================== +Addresses belongsTo Users addresses.user\_id +------------------------- ------------------ +Mentors belongsTo Doctors mentors.doctor\_id +========================= ================== + +.. tip:: + + If a Table contains a foreign key, it belongs to the other Table. + +We can define the belongsTo association in our Addresses table as follows:: + + class AddressesTable extends Table + { + public function initialize(array $config) + { + $this->belongsTo('Users'); + } + } + +We can also define a more specific relationship using the setters:: + + class AddressesTable extends Table + { + public function initialize(array $config) + { + // Prior to 3.4 version, use foreignKey() and joinType() + $this->belongsTo('Users') + ->setForeignKey('user_id') + ->setJoinType('INNER'); + } + } + +Possible keys for belongsTo association arrays include: + +- **className**: the class name of the model being associated to the current + model. If you're defining a 'Profile belongsTo User' relationship, the + className key should equal 'Users'. +- **foreignKey**: the name of the foreign key found in the current table. This + is especially handy if you need to define multiple belongsTo relationships to + the same model. The default value for this key is the underscored, singular + name of the other model, suffixed with ``_id``. +- **bindingKey**: The name of the column in the other table, that will be used + for matching the ``foreignKey``. If not specified, the primary key (for + example the id column of the ``Users`` table) will be used. +- **conditions**: an array of find() compatible conditions or SQL strings such + as ``['Users.active' => true]`` +- **joinType**: the type of the join to use in the SQL query, default is LEFT + which may not fit your needs in all situations, INNER may be helpful when you + want everything from your main and associated models or nothing at all. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & singular name of the association so ``user`` in our example. +- **strategy**: Defines the query strategy to use. Defaults to 'join'. The other + valid value is 'select', which utilizes a separate query instead. +- **finder**: The finder method to use when loading associated records. + +Once this association has been defined, find operations on the Addresses table can +contain the User record if it exists:: + + // In a controller or table method. + $query = $addresses->find('all')->contain(['Users']); + foreach ($query as $address) { + echo $address->user->username; + } + +The above would emit SQL that is similar to:: + + SELECT * FROM addresses LEFT JOIN users ON addresses.user_id = users.id; + +.. _has-many-associations: + +HasMany Associations +==================== + +An example of a hasMany association is "Article hasMany Comments". Defining this +association will allow us to fetch an article's comments when the article is +loaded. + +When creating your database tables for a hasMany relationship, follow this +convention: + +**hasMany:** the *other* model contains the foreign key. + +========================== =================== +Relation Schema +========================== =================== +Article hasMany Comment Comment.article\_id +-------------------------- ------------------- +Product hasMany Option Option.product\_id +-------------------------- ------------------- +Doctor hasMany Patient Patient.doctor\_id +========================== =================== + +We can define the hasMany association in our Articles model as follows:: + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->hasMany('Comments'); + } + } + +We can also define a more specific relationship using the setters:: + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->hasMany('Comments') + ->setForeignKey('article_id') + ->setDependent(true); + } + } + +Sometimes you may want to configure composite keys in your associations:: + + // Within ArticlesTable::initialize() call + $this->hasMany('Reviews') + ->setForeignKey([ + 'article_id', + 'article_hash' + ]); + +Relying on the example above, we have passed an array containing the desired +composite keys to ``setForeignKey()``. By default the ``bindingKey`` would be +automatically defined as ``id`` and ``hash`` respectively, but let's assume that +you need to specify different binding fields than the defaults, you can setup it +manually with ``setBindingKey()``:: + + // Within ArticlesTable::initialize() call + $this->hasMany('Reviews') + ->setForeignKey([ + 'article_id', + 'article_hash' + ]) + ->setBindingKey([ + 'whatever_id', + 'whatever_hash' + ]); + +It is important to note that ``foreignKey`` values refers to the **reviews** +table and ``bindingKey`` values refers to the **articles** table. + +Possible keys for hasMany association arrays include: + +- **className**: the class name of the model being associated to + the current model. If you're defining a 'User hasMany Comment' + relationship, the className key should equal 'Comments'. +- **foreignKey**: the name of the foreign key found in the other + table. This is especially handy if you need to define multiple + hasMany relationships. The default value for this key is the + underscored, singular name of the actual model, suffixed with + '\_id'. +- **bindingKey**: The name of the column in the current table, that will be used + for matching the ``foreignKey``. If not specified, the primary key (for + example the id column of the ``Articles`` table) will be used. +- **conditions**: an array of find() compatible conditions or SQL + strings such as ``['Comments.visible' => true]`` +- **sort**: an array of find() compatible order clauses or SQL + strings such as ``['Comments.created' => 'ASC']`` +- **dependent**: When dependent is set to ``true``, recursive model + deletion is possible. In this example, Comment records will be + deleted when their associated Article record has been deleted. +- **cascadeCallbacks**: When this and **dependent** are ``true``, cascaded + deletes will load and delete entities so that callbacks are properly + triggered. When ``false``, ``deleteAll()`` is used to remove associated data + and no callbacks are triggered. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & plural name of the association so ``comments`` in our example. +- **strategy**: Defines the query strategy to use. Defaults to 'select'. The + other valid value is 'subquery', which replaces the ``IN`` list with an + equivalent subquery. +- **saveStrategy**: Either 'append' or 'replace'. Defaults to 'append'. When 'append' the current + records are appended to any records in the database. When 'replace' associated + records not in the current set will be removed. If the foreign key is a nullable + column or if ``dependent`` is true records will be orphaned. +- **finder**: The finder method to use when loading associated records. + +Once this association has been defined, find operations on the Articles table +can contain the Comment records if they exist:: + + // In a controller or table method. + $query = $articles->find('all')->contain(['Comments']); + foreach ($query as $article) { + echo $article->comments[0]->text; + } + +The above would emit SQL that is similar to:: + + SELECT * FROM articles; + SELECT * FROM comments WHERE article_id IN (1, 2, 3, 4, 5); + +When the subquery strategy is used, SQL similar to the following will be +generated:: + + SELECT * FROM articles; + SELECT * FROM comments WHERE article_id IN (SELECT id FROM articles); + +You may want to cache the counts for your hasMany associations. This is useful +when you often need to show the number of associated records, but don't want to +load all the records just to count them. For example, the comment count on any +given article is often cached to make generating lists of articles more +efficient. You can use the :doc:`CounterCacheBehavior +` to cache counts of associated records. + +You should make sure that your database tables do not contain columns that match +association property names. If for example you have counter fields that conflict +with association properties, you must either rename the association property, or +the column name. + +.. _belongs-to-many-associations: + +BelongsToMany Associations +========================== + +.. note:: + + In 3.0 and onward ``hasAndBelongsToMany`` / ``HABTM`` has been renamed to + ``belongsToMany`` / ``BTM``. + +An example of a BelongsToMany association is "Article BelongsToMany Tags", where +the tags from one article are shared with other articles. BelongsToMany is +often referred to as "has and belongs to many", and is a classic "many to many" +association. + +The main difference between hasMany and BelongsToMany is that the link between +the models in a BelongsToMany association is not exclusive. For example, we are +joining our Articles table with a Tags table. Using 'funny' as a Tag for my +Article, doesn't "use up" the tag. I can also use it on the next article +I write. + +Three database tables are required for a BelongsToMany association. In the +example above we would need tables for ``articles``, ``tags`` and +``articles_tags``. The ``articles_tags`` table contains the data that links +tags and articles together. The joining table is named after the two tables +involved, separated with an underscore by convention. In its simplest form, this +table consists of ``article_id`` and ``tag_id``. + +**belongsToMany** requires a separate join table that includes both *model* +names. + +============================ ================================================================ +Relationship Join Table Fields +============================ ================================================================ +Article belongsToMany Tag articles_tags.id, articles_tags.tag_id, articles_tags.article_id +---------------------------- ---------------------------------------------------------------- +Patient belongsToMany Doctor doctors_patients.id, doctors_patients.doctor_id, + doctors_patients.patient_id. +============================ ================================================================ + +We can define the belongsToMany association in both our models as follows:: + + // In src/Model/Table/ArticlesTable.php + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->belongsToMany('Tags'); + } + } + + // In src/Model/Table/TagsTable.php + class TagsTable extends Table + { + public function initialize(array $config) + { + $this->belongsToMany('Articles'); + } + } + +We can also define a more specific relationship using configuration:: + + // In src/Model/Table/TagsTable.php + class TagsTable extends Table + { + public function initialize(array $config) + { + $this->belongsToMany('Articles', [ + 'joinTable' => 'articles_tags', + ]); + } + } + +Possible keys for belongsToMany association arrays include: + +- **className**: the class name of the model being associated to + the current model. If you're defining a 'Article belongsToMany Tag' + relationship, the className key should equal 'Tags'. +- **joinTable**: The name of the join table used in this + association (if the current table doesn't adhere to the naming + convention for belongsToMany join tables). By default this table + name will be used to load the Table instance for the join table. +- **foreignKey**: The name of the foreign key that references the current model + found on the join table, or list in case of composite foreign keys. + This is especially handy if you need to define multiple + belongsToMany relationships. The default value for this key is the + underscored, singular name of the current model, suffixed with '\_id'. +- **bindingKey**: The name of the column in the current table, that will be used + for matching the ``foreignKey``. Defaults to the primary key. +- **targetForeignKey**: The name of the foreign key that references the target + model found on the join model, or list in case of composite foreign keys. + The default value for this key is the underscored, singular name of + the target model, suffixed with '\_id'. +- **conditions**: an array of ``find()`` compatible conditions. If you have + conditions on an associated table, you should use a 'through' model, and + define the necessary belongsTo associations on it. +- **sort**: an array of find() compatible order clauses. +- **dependent**: When the dependent key is set to ``false``, and an entity is + deleted, the data of the join table will not be deleted. +- **through**: Allows you to provide either the alias of the Table instance you + want used on the join table, or the instance itself. This makes customizing + the join table keys possible, and allows you to customize the behavior of the + pivot table. +- **cascadeCallbacks**: When this is ``true``, cascaded deletes will load and + delete entities so that callbacks are properly triggered on join table + records. When ``false``, ``deleteAll()`` is used to remove associated data and + no callbacks are triggered. This defaults to ``false`` to help reduce + overhead. +- **propertyName**: The property name that should be filled with data from the + associated table into the source table results. By default this is the + underscored & plural name of the association, so ``tags`` in our example. +- **strategy**: Defines the query strategy to use. Defaults to 'select'. The + other valid value is 'subquery', which replaces the ``IN`` list with an + equivalent subquery. +- **saveStrategy**: Either 'append' or 'replace'. Defaults to 'replace'. + Indicates the mode to be used for saving associated entities. The former will + only create new links between both side of the relation and the latter will + do a wipe and replace to create the links between the passed entities when + saving. +- **finder**: The finder method to use when loading associated records. + +Once this association has been defined, find operations on the Articles table can +contain the Tag records if they exist:: + + // In a controller or table method. + $query = $articles->find('all')->contain(['Tags']); + foreach ($query as $article) { + echo $article->tags[0]->text; + } + +The above would emit SQL that is similar to:: + + SELECT * FROM articles; + SELECT * FROM tags + INNER JOIN articles_tags ON ( + tags.id = article_tags.tag_id + AND article_id IN (1, 2, 3, 4, 5) + ); + +When the subquery strategy is used, SQL similar to the following will be +generated:: + + SELECT * FROM articles; + SELECT * FROM tags + INNER JOIN articles_tags ON ( + tags.id = article_tags.tag_id + AND article_id IN (SELECT id FROM articles) + ); + +.. _using-the-through-option: + +Using the 'through' Option +-------------------------- + +If you plan on adding extra information to the join/pivot table, or if you need +to use join columns outside of the conventions, you will need to define the +``through`` option. The ``through`` option provides you full control over how +the belongsToMany association will be created. + +It is sometimes desirable to store additional data with a many to many +association. Consider the following:: + + Student BelongsToMany Course + Course BelongsToMany Student + +A Student can take many Courses and a Course can be taken by many Students. This +is a simple many to many association. The following table would suffice:: + + id | student_id | course_id + +Now what if we want to store the number of days that were attended by the +student on the course and their final grade? The table we'd want would be:: + + id | student_id | course_id | days_attended | grade + +The way to implement our requirement is to use a **join model**, otherwise known +as a **hasMany through** association. That is, the association is a model +itself. So, we can create a new model CoursesMemberships. Take a look at the +following models:: + + class StudentsTable extends Table + { + public function initialize(array $config) + { + $this->belongsToMany('Courses', [ + 'through' => 'CoursesMemberships', + ]); + } + } + + class CoursesTable extends Table + { + public function initialize(array $config) + { + $this->belongsToMany('Students', [ + 'through' => 'CoursesMemberships', + ]); + } + } + + class CoursesMembershipsTable extends Table + { + public function initialize(array $config) + { + $this->belongsTo('Students'); + $this->belongsTo('Courses'); + } + } + +The CoursesMemberships join table uniquely identifies a given Student's +participation on a Course in addition to extra meta-information. + +Default Association Conditions +------------------------------ + +The ``finder`` option allows you to use a :ref:`custom finder +` to load associated record data. This lets you encapsulate +your queries better and keep your code DRY'er. There are some limitations when +using finders to load data in associations that are loaded using joins +(belongsTo/hasOne). Only the following aspects of the query will be applied to +the root query: + +- WHERE conditions. +- Additional joins. +- Contained associations. + +Other aspects of the query, such as selected columns, order, group by, having +and other sub-statements, will not be applied to the root query. Associations +that are *not* loaded through joins (hasMany/belongsToMany), do not have the +above restrictions and can also use result formatters or map/reduce functions. + +Loading Associations +-------------------- + +Once you've defined your associations you can :ref:`eager load associations +` when fetching results. diff --git a/tl/orm/behaviors.rst b/tl/orm/behaviors.rst new file mode 100644 index 0000000000000000000000000000000000000000..02d8e7319b7839dda1356ceacaad2d8a70ef8cc5 --- /dev/null +++ b/tl/orm/behaviors.rst @@ -0,0 +1,327 @@ +Behaviors +######### + +Behaviors are a way to organize and enable horizontal re-use of Model layer +logic. Conceptually they are similar to traits. However, behaviors are +implemented as separate classes. This allows them to hook into the +life-cycle callbacks that models emit, while providing trait-like features. + +Behaviors provide a convenient way to package up behavior that is common across +many models. For example, CakePHP includes a ``TimestampBehavior``. Many +models will want timestamp fields, and the logic to manage these fields is +not specific to any one model. It is these kinds of scenarios that behaviors are +a perfect fit for. + +Using Behaviors +=============== + +.. include:: ./table-objects.rst + :start-after: start-behaviors + :end-before: end-behaviors + +Core Behaviors +============== + +.. toctree:: + :maxdepth: 1 + + /orm/behaviors/counter-cache + /orm/behaviors/timestamp + /orm/behaviors/translate + /orm/behaviors/tree + +Creating a Behavior +=================== + +In the following examples we will create a very simple ``SluggableBehavior``. +This behavior will allow us to populate a slug field with the results of +``Text::slug()`` based on another field. + +Before we create our behavior we should understand the conventions for +behaviors: + +- Behavior files are located in **src/Model/Behavior**, or + ``MyPlugin\Model\Behavior``. +- Behavior classes should be in the ``App\Model\Behavior`` namespace, or + ``MyPlugin\Model\Behavior`` namespace. +- Behavior class names end in ``Behavior``. +- Behaviors extend ``Cake\ORM\Behavior``. + +To create our sluggable behavior. Put the following into +**src/Model/Behavior/SluggableBehavior.php**:: + + namespace App\Model\Behavior; + + use Cake\ORM\Behavior; + + class SluggableBehavior extends Behavior + { + } + +Similar to tables, behaviors also have an ``initialize()`` hook where you can +put your behavior's initialization code, if required:: + + public function initialize(array $config) + { + // Some initialization code here + } + +We can now add this behavior to one of our table classes. In this example we'll +use an ``ArticlesTable``, as articles often have slug properties for creating +friendly URLs:: + + namespace App\Model\Table; + + use Cake\ORM\Table; + + class ArticlesTable extends Table + { + + public function initialize(array $config) + { + $this->addBehavior('Sluggable'); + } + } + +Our new behavior doesn't do much of anything right now. Next, we'll add a mixin +method and an event listener so that when we save entities we can automatically +slug a field. + +Defining Mixin Methods +---------------------- + +Any public method defined on a behavior will be added as a 'mixin' method on the +table object it is attached to. If you attach two behaviors that provide the +same methods an exception will be raised. If a behavior provides the same method +as a table class, the behavior method will not be callable from the table. +Behavior mixin methods will receive the exact same arguments that are provided +to the table. For example, if our SluggableBehavior defined the following +method:: + + public function slug($value) + { + return Text::slug($value, $this->_config['replacement']); + } + +It could be invoked using:: + + $slug = $articles->slug('My article name'); + +Limiting or Renaming Exposed Mixin Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When creating behaviors, there may be situations where you don't want to expose +public methods as mixin methods. In these cases you can use the +``implementedMethods`` configuration key to rename or exclude mixin methods. For +example if we wanted to prefix our slug() method we could do the following:: + + protected $_defaultConfig = [ + 'implementedMethods' => [ + 'superSlug' => 'slug', + ] + ]; + +Applying this configuration will make ``slug()`` not callable, however it will +add a ``superSlug()`` mixin method to the table. Notably if our behavior +implemented other public methods they would **not** be available as mixin +methods with the above configuration. + +Since the exposed methods are decided by configuration you can also +rename/remove mixin methods when adding a behavior to a table. For example:: + + // In a table's initialize() method. + $this->addBehavior('Sluggable', [ + 'implementedMethods' => [ + 'superSlug' => 'slug', + ] + ]); + +Defining Event Listeners +------------------------ + +Now that our behavior has a mixin method to slug fields, we can implement +a callback listener to automatically slug a field when entities are saved. We'll +also modify our slug method to accept an entity instead of just a plain value. Our +behavior should now look like:: + + namespace App\Model\Behavior; + + use ArrayObject; + use Cake\Datasource\EntityInterface; + use Cake\Event\Event; + use Cake\ORM\Behavior; + use Cake\ORM\Entity; + use Cake\ORM\Query; + use Cake\Utility\Text; + + class SluggableBehavior extends Behavior + { + protected $_defaultConfig = [ + 'field' => 'title', + 'slug' => 'slug', + 'replacement' => '-', + ]; + + public function slug(Entity $entity) + { + $config = $this->config(); + $value = $entity->get($config['field']); + $entity->set($config['slug'], Text::slug($value, $config['replacement'])); + } + + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + { + $this->slug($entity); + } + + } + +The above code shows a few interesting features of behaviors: + +- Behaviors can define callback methods by defining methods that follow the + :ref:`table-callbacks` conventions. +- Behaviors can define a default configuration property. This property is merged + with the overrides when a behavior is attached to the table. + +To prevent the saving from continuing simply stop event propagation in your callback:: + + public function beforeSave(Event $event, EntityInterface $entity, ArrayObject $options) + { + if (...) { + $event->stopPropagation(); + return; + } + $this->slug($entity); + } + +Defining Finders +---------------- + +Now that we are able to save articles with slug values, we should implement +a finder method so we can fetch articles by their slug. Behavior finder +methods, use the same conventions as :ref:`custom-find-methods` do. Our +``find('slug')`` method would look like:: + + public function findSlug(Query $query, array $options) + { + return $query->where(['slug' => $options['slug']]); + } + +Once our behavior has the above method we can call it:: + + $article = $articles->find('slug', ['slug' => $value])->first(); + +Limiting or Renaming Exposed Finder Methods +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When creating behaviors, there may be situations where you don't want to expose +finder methods, or you need to rename finders to avoid duplicated methods. In +these cases you can use the ``implementedFinders`` configuration key to rename +or exclude finder methods. For example if we wanted to rename our ``find(slug)`` +method we could do the following:: + + protected $_defaultConfig = [ + 'implementedFinders' => [ + 'slugged' => 'findSlug', + ] + ]; + +Applying this configuration will make ``find('slug')`` trigger an error. However +it will make ``find('slugged')`` available. Notably if our behavior implemented +other finder methods they would **not** be available, as they are not included +in the configuration. + +Since the exposed methods are decided by configuration you can also +rename/remove finder methods when adding a behavior to a table. For example:: + + // In a table's initialize() method. + $this->addBehavior('Sluggable', [ + 'implementedFinders' => [ + 'slugged' => 'findSlug', + ] + ]); + +Transforming Request Data into Entity Properties +================================================ + +Behaviors can define logic for how the custom fields they provide are +marshalled by implementing the ``Cake\ORM\PropertyMarshalInterface``. This +interface requires a single method to be implemented:: + + public function buildMarshalMap($marshaller, $map, $options) + { + return [ + 'custom_behavior_field' => function ($value, $entity) { + // Transform the value as necessary + return $value . '123'; + } + ]; + } + +The ``TranslateBehavior`` has a non-trivial implementation of this interface +that you might want to refer to. + +.. versionadded:: 3.3.0 + The ability for behaviors to participate in marshalling was added in 3.3.0 + +Removing Loaded Behaviors +========================= + +To remove a behavior from your table you can call the ``removeBehavior()`` method:: + + // Remove the loaded behavior + $this->removeBehavior('Sluggable'); + +Accessing Loaded Behaviors +========================== + +Once you've attached behaviors to your Table instance you can introspect the +loaded behaviors, or access specific behaviors using the ``BehaviorRegistry``:: + + // See which behaviors are loaded + $table->behaviors()->loaded(); + + // Check if a specific behavior is loaded. + // Remember to omit plugin prefixes. + $table->behaviors()->has('CounterCache'); + + // Get a loaded behavior + // Remember to omit plugin prefixes + $table->behaviors()->get('CounterCache'); + +Re-configuring Loaded Behaviors +------------------------------- + +To modify the configuration of an already loaded behavior you can combine the +``BehaviorRegistry::get`` command with ``config`` command provided by the +``InstanceConfigTrait`` trait. + +For example if a parent (e.g. an ``AppTable``) class loaded the ``Timestamp`` +behavior you could do the following to add, modify or remove the configurations +for the behavior. In this case, we will add an event we want Timestamp to +respond to:: + + namespace App\Model\Table; + + use App\Model\Table\AppTable; // similar to AppController + + class UsersTable extends AppTable + { + public function initialize(array $options) + { + parent::initialize($options); + + // e.g. if our parent calls $this->addBehavior('Timestamp'); + // and we want to add an additional event + if ($this->behaviors()->has('Timestamp')) { + $this->behaviors()->get('Timestamp')->config([ + 'events' => [ + 'Users.login' => [ + 'last_login' => 'always' + ], + ], + ]); + } + } + } + diff --git a/tl/orm/behaviors/counter-cache.rst b/tl/orm/behaviors/counter-cache.rst new file mode 100644 index 0000000000000000000000000000000000000000..4acd68cf2fbe518c8cb1b4c5caa672ce8369f544 --- /dev/null +++ b/tl/orm/behaviors/counter-cache.rst @@ -0,0 +1,118 @@ +CounterCache +############ + +.. php:namespace:: Cake\ORM\Behavior + +.. php:class:: CounterCacheBehavior + +Often times web applications need to display counts of related objects. For +example, when showing a list of articles you may want to display how many +comments it has. Or when showing a user you might want to show how many +friends/followers she has. The CounterCache behavior is intended for these +situations. CounterCache will update a field in the associated models assigned +in the options when it is invoked. The fields should exist in the database and +be of the type INT. + +Basic Usage +=========== + +You enable the CounterCache behavior like any other behavior, but it won't do +anything until you configure some relations and the field counts that should be +stored on each of them. Using our example below, we could cache the comment +count for each article with the following:: + + class CommentsTable extends Table + { + public function initialize(array $config) + { + $this->addBehavior('CounterCache', [ + 'Articles' => ['comment_count'] + ]); + } + } + +The CounterCache configuration should be a map of relation names and the +specific configuration for that relation. + +The counter's value will be updated each time an entity is saved or deleted. The +counter **will not** be updated when you use ``updateAll()`` or ``deleteAll()``, +or execute SQL you have written. + +Advanced Usage +============== + +If you need to keep a cached counter for less than all of the related records, +you can supply additional conditions or finder methods to generate a +counter value:: + + // Use a specific find method. + // In this case find(published) + $this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'finder' => 'published' + ] + ] + ]); + +If you don't have a custom finder method you can provide an array of conditions +to find records instead:: + + $this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'conditions' => ['Comments.spam' => false] + ] + ] + ]); + +If you want CounterCache to update multiple fields, for example both showing a +conditional count and a basic count you can add these fields in the array:: + + $this->addBehavior('CounterCache', [ + 'Articles' => ['comment_count', + 'published_comment_count' => [ + 'finder' => 'published' + ] + ] + ]); + +If you want to calculate the CounterCache field value on your own, you can set +the ``ignoreDirty`` option to ``true``. +This will prevent the field from being recalculated if you've set it dirty +before:: + + $this->addBehavior('CounterCache', [ + 'Articles' => [ + 'comment_count' => [ + 'ignoreDirty' => true + ] + ] + ]); + +Lastly, if a custom finder and conditions are not suitable you can provide +a callback method. This callable must return the count value to be stored:: + + $this->addBehavior('CounterCache', [ + 'Articles' => [ + 'rating_avg' => function ($event, $entity, $table) { + return 4.5; + } + ] + ]); + +It may also return a Query object that produces the count value and will be used +as a subquery in the update statement. The ``$table`` parameter refers to the +table object holding the behavior (not the target relation) for convenience. + +.. note:: + + The CounterCache behavior works for ``belongsTo`` associations only. For + example for "Comments belongsTo Articles", you need to add the CounterCache + behavior to the ``CommentsTable`` in order to generate ``comment_count`` for + Articles table. + + It is possible though to make this work for ``belongsToMany`` associations. + You need to enable the CounterCache behavior in a custom ``through`` table + configured in association options. See how to configure a custom join table + :ref:`using-the-through-option`. diff --git a/tl/orm/behaviors/timestamp.rst b/tl/orm/behaviors/timestamp.rst new file mode 100644 index 0000000000000000000000000000000000000000..e5527a9c5d7cff72eaf8fcc18b673ef98107f8f7 --- /dev/null +++ b/tl/orm/behaviors/timestamp.rst @@ -0,0 +1,91 @@ +Timestamp +######### + +.. php:namespace:: Cake\ORM\Behavior + +.. php:class:: TimestampBehavior + +The timestamp behavior allows your table objects to update one or more +timestamps on each model event. This is primarily used to populate data into +``created`` and ``modified`` fields. However, with some additional +configuration, you can update any timestamp/datetime column on any event a table +publishes. + +Basic Usage +=========== + +You enable the timestamp behavior like any other behavior:: + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->addBehavior('Timestamp'); + } + } + +The default configuration will do the following: + +- When a new entity is saved the ``created`` and ``modified`` fields will be set + to the current time. +- When an entity is updated, the ``modified`` field is set to the current time. + +Using and Configuring the Behavior +================================== + +If you need to modify fields with different names, or want to update additional +timestamp fields on custom events you can use some additional configuration:: + + class OrdersTable extends Table + { + public function initialize(array $config) + { + $this->addBehavior('Timestamp', [ + 'events' => [ + 'Model.beforeSave' => [ + 'created_at' => 'new', + 'updated_at' => 'always', + ], + 'Orders.completed' => [ + 'completed_at' => 'always' + ] + ] + ]); + } + } + +As you can see above, in addition to the standard ``Model.beforeSave`` event, we +are also updating the ``completed_at`` column when orders are completed. + +Updating Timestamps on Entities +=============================== + +Sometimes you'll want to update just the timestamps on an entity without +changing any other properties. This is sometimes referred to as 'touching' +a record. In CakePHP you can use the ``touch()`` method to do exactly this:: + + // Touch based on the Model.beforeSave event. + $articles->touch($article); + + // Touch based on a specific event. + $orders->touch($order, 'Orders.completed'); + +After you have saved the entity, the field is updated. + +Touching records can be useful when you want to signal that a parent resource +has changed when a child resource is created/updated. For example: updating an +article when a new comment is added. + +Saving Updates Without Modifying Timestamps +=========================================== + +To disable the automatic modification of the ``updated`` timestamp column when +saving an entity you can mark the attribute as 'dirty':: + + // Mark the modified column as dirty making + // the current value be set on update. + $order->setDirty('modified', true); + + // Prior to 3.4.0 + $order->dirty('modified', true); + diff --git a/tl/orm/behaviors/translate.rst b/tl/orm/behaviors/translate.rst new file mode 100644 index 0000000000000000000000000000000000000000..de3ba11c62e9507458ae971c60d83fd7f0a40b05 --- /dev/null +++ b/tl/orm/behaviors/translate.rst @@ -0,0 +1,478 @@ +Translate +######### + +.. php:namespace:: Cake\ORM\Behavior + +.. php:class:: TranslateBehavior + +The Translate behavior allows you to create and retrieve translated copies +of your entities in multiple languages. It does so by using a separate +``i18n`` table where it stores the translation for each of the fields of any +given Table object that it's bound to. + +.. warning:: + + The TranslateBehavior does not support composite primary keys at this point + in time. + +A Quick Tour +============ + +After creating the ``i18n`` table in your database attach the behavior to any +Table object you want to make translatable:: + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->addBehavior('Translate', ['fields' => ['title']]); + } + } + +Now, select a language to be used for retrieving entities by changing +the application language, which will affect all translations:: + + // In a controller. Change the locale, e.g. to Spanish + I18n::setLocale('es'); + $this->loadModel('Articles'); + +Then, get an existing entity:: + + $article = $this->Articles->get(12); + echo $article->title; // Echoes 'A title', not translated yet + +Next, translate your entity:: + + $article->title = 'Un Artículo'; + $this->Articles->save($article); + +You can try now getting your entity again:: + + $article = $this->Articles->get(12); + echo $article->title; // Echoes 'Un Artículo', yay piece of cake! + +Working with multiple translations can be done by using a special trait +in your Entity class:: + + use Cake\ORM\Behavior\Translate\TranslateTrait; + use Cake\ORM\Entity; + + class Article extends Entity + { + use TranslateTrait; + } + +Now you can find all translations for a single entity:: + + $article = $this->Articles->find('translations')->first(); + echo $article->translation('es')->title; // 'Un Artículo' + + echo $article->translation('en')->title; // 'An Article'; + +It is equally easy to save multiple translations at once:: + + $article->translation('es')->title = 'Otro Título'; + $article->translation('fr')->title = 'Un autre Titre'; + $this->Articles->save($article); + +If you want to go deeper on how it works or how to tune the +behavior for your needs, keep on reading the rest of this chapter. + +Initializing the i18n Database Table +==================================== + +In order to use the behavior, you need to create a ``i18n`` table with the +correct schema. Currently the only way of loading the ``i18n`` table is by +manually running the following SQL script in your database: + +.. code-block:: sql + + CREATE TABLE i18n ( + id int NOT NULL auto_increment, + locale varchar(6) NOT NULL, + model varchar(255) NOT NULL, + foreign_key int(10) NOT NULL, + field varchar(255) NOT NULL, + content text, + PRIMARY KEY (id), + UNIQUE INDEX I18N_LOCALE_FIELD(locale, model, foreign_key, field), + INDEX I18N_FIELD(model, foreign_key, field) + ); + +The schema is also available as sql file in **/config/schema/i18n.sql**. + +A note on language abbreviations: The Translate Behavior doesn't impose any +restrictions on the language identifier, the possible values are only restricted +by the ``locale`` column type/size. ``locale`` is defined as ``varchar(6)`` in +case you want to use abbreviations like ``es-419`` (Spanish for Latin America, +language abbreviation with area code `UN M.49 +`_). + +.. tip:: + + It's wise to use the same language abbreviations as required for + :doc:`Internationalization and Localization + `. Thus you are + consistent and switching the language works identical for both, the + ``Translate Behaviour`` and ``Internationalization and Localization``. + +So it's recommended to use either the two letter ISO code of the language like +``en``, ``fr``, ``de`` or the full locale name such as ``fr_FR``, ``es_AR``, +``da_DK`` which contains both the language and the country where it is spoken. + +Attaching the Translate Behavior to Your Tables +=============================================== + +Attaching the behavior can be done in the ``initialize()`` method in your Table +class:: + + class ArticlesTable extends Table + { + + public function initialize(array $config) + { + $this->addBehavior('Translate', ['fields' => ['title', 'body']]); + } + } + +The first thing to note is that you are required to pass the ``fields`` key in +the configuration array. This list of fields is needed to tell the behavior what +columns will be able to store translations. + +Using a Separate Translations Table +----------------------------------- + +If you wish to use a table other than ``i18n`` for translating a particular +repository, you can specify it in the behavior's configuration. This is common +when you have multiple tables to translate and you want a cleaner separation +of the data that is stored for each different table:: + + class ArticlesTable extends Table + { + + public function initialize(array $config) + { + $this->addBehavior('Translate', [ + 'fields' => ['title', 'body'], + 'translationTable' => 'ArticlesI18n' + ]); + } + } + +You need to make sure that any custom table you use has the columns ``field``, +``foreign_key``, ``locale`` and ``model``. + +Reading Translated Content +========================== + +As shown above you can use the ``locale()`` method to choose the active +translation for entities that are loaded:: + + // Load I18n core functions at the beginning of your Controller: + use Cake\I18n\I18n; + + // Then you can change the language in your action: + I18n::setLocale('es'); + $this->loadModel('Articles'); + + // All entities in results will contain spanish translation + $results = $this->Articles->find()->all(); + +This method works with any finder in your tables. For example, you can +use TranslateBehavior with ``find('list')``:: + + I18n::setLocale('es'); + $data = $this->Articles->find('list')->toArray(); + + // Data will contain + [1 => 'Mi primer artículo', 2 => 'El segundo artículo', 15 => 'Otro articulo' ...] + +Retrieve All Translations For An Entity +--------------------------------------- + +When building interfaces for updating translated content, it is often helpful to +show one or more translation(s) at the same time. You can use the +``translations`` finder for this:: + + // Find the first article with all corresponding translations + $article = $this->Articles->find('translations')->first(); + +In the example above you will get a list of entities back that have a +``_translations`` property set. This property will contain a list of translation +data entities. For example the following properties would be accessible:: + + // Outputs 'en' + echo $article->_translations['en']->locale; + + // Outputs 'title' + echo $article->_translations['en']->field; + + // Outputs 'My awesome post!' + echo $article->_translations['en']->body; + +A more elegant way for dealing with this data is by adding a trait to the entity +class that is used for your table:: + + use Cake\ORM\Behavior\Translate\TranslateTrait; + use Cake\ORM\Entity; + + class Article extends Entity + { + use TranslateTrait; + } + +This trait contains a single method called ``translation``, which lets you +access or create new translation entities on the fly:: + + // Outputs 'title' + echo $article->translation('en')->title; + + // Adds a new translation data entity to the article + $article->translation('de')->title = 'Wunderbar'; + +Limiting the Translations to be Retrieved +----------------------------------------- + +You can limit the languages that are fetched from the database for a particular +set of records:: + + $results = $this->Articles->find('translations', [ + 'locales' => ['en', 'es'] + ]); + $article = $results->first(); + $spanishTranslation = $article->translation('es'); + $englishTranslation = $article->translation('en'); + +Preventing Retrieval of Empty Translations +------------------------------------------ + +Translation records can contain any string, if a record has been translated +and stored as an empty string ('') the translate behavior will take and use +this to overwrite the original field value. + +If this is undesired, you can ignore translations which are empty using the +``allowEmptyTranslations`` config key:: + + class ArticlesTable extends Table + { + + public function initialize(array $config) + { + $this->addBehavior('Translate', [ + 'fields' => ['title', 'body'], + 'allowEmptyTranslations' => false + ]); + } + } + +The above would only load translated data that had content. + +Retrieving All Translations For Associations +-------------------------------------------- + +It is also possible to find translations for any association in a single find +operation:: + + $article = $this->Articles->find('translations')->contain([ + 'Categories' => function ($query) { + return $query->find('translations'); + } + ])->first(); + + // Outputs 'Programación' + echo $article->categories[0]->translation('es')->name; + +This assumes that ``Categories`` has the TranslateBehavior attached to it. It +simply uses the query builder function for the ``contain`` clause to use the +``translations`` custom finder in the association. + +Retrieving one language without using I18n::locale +-------------------------------------------------- + +calling ``I18n::setLocale('es');`` changes the default locale for all translated +finds, there may be times you wish to retrieve translated content without +modifying the application's state. For these scenarios use the behavior +``setLocale()`` method:: + + I18n::setLocale('en'); // reset for illustration + + $this->loadModel('Articles'); + $this->Articles->locale('es'); // specific locale + + $article = $this->Articles->get(12); + echo $article->title; // Echoes 'Un Artículo', yay piece of cake! + +Note that this only changes the locale of the Articles table, it would not +affect the langauge of associated data. To affect associated data it's necessary +to call locale on each table for example:: + + I18n::setLocale('en'); // reset for illustration + + $this->loadModel('Articles'); + $this->Articles->locale('es'); + $this->Articles->Categories->locale('es'); + + $data = $this->Articles->find('all', ['contain' => ['Categories']]); + +This example also assumes that ``Categories`` has the TranslateBehavior attached +to it. + +Querying Translated Fields +-------------------------- + +TranslateBehavior does not substitute find conditions by default. You need to use +``translationField()`` method to compose find conditions on translated fields:: + + $this->Articles->locale('es'); + $data = $this->Articles->find()->where([ + $this->Articles->translationField('title') => 'Otro Título' + ]); + +Saving in Another Language +========================== + +The philosophy behind the TranslateBehavior is that you have an entity +representing the default language, and multiple translations that can override +certain fields in such entity. Keeping this in mind, you can intuitively save +translations for any given entity. For example, given the following setup:: + + // in src/Model/Table/ArticlesTable.php + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->addBehavior('Translate', ['fields' => ['title', 'body']]); + } + } + + // in src/Model/Entity/Article.php + class Article extends Entity + { + use TranslateTrait; + } + + // In a Controller + $articles = $this->loadModel('Articles'); + $article = new Article([ + 'title' => 'My First Article', + 'body' => 'This is the content', + 'footnote' => 'Some afterwords' + ]); + + $this->Articles->save($article); + +So, after you save your first article, you can now save a translation for it, +there are a couple ways to do it. The first one is setting the language directly +into the entity:: + + $article->_locale = 'es'; + $article->title = 'Mi primer Artículo'; + + $this->Articles->save($article); + +After the entity has been saved, the translated field will be persisted as well, +one thing to note is that values from the default language that were not +overridden will be preserved:: + + // Outputs 'This is the content' + echo $article->body; + + // Outputs 'Mi primer Artículo' + echo $article->title; + +Once you override the value, the translation for that field will be saved and +can be retrieved as usual:: + + $article->body = 'El contendio'; + $this->Articles->save($article); + +The second way to use for saving entities in another language is to set the +default language directly to the table:: + + I18n::setLocale('es'); + $article->title = 'Mi Primer Artículo'; + $this->Articles->save($article); + +Setting the language directly in the table is useful when you need to both +retrieve and save entities for the same language or when you need to save +multiple entities at once. + +.. _saving-multiple-translations: + +Saving Multiple Translations +============================ + +It is a common requirement to be able to add or edit multiple translations to +any database record at the same time. This can be done using the +``TranslateTrait``:: + + use Cake\ORM\Behavior\Translate\TranslateTrait; + use Cake\ORM\Entity; + + class Article extends Entity + { + use TranslateTrait; + } + +Now, You can populate translations before saving them:: + + $translations = [ + 'fr' => ['title' => "Un article"], + 'es' => ['title' => 'Un artículo'] + ]; + + foreach ($translations as $lang => $data) { + $article->translation($lang)->set($data, ['guard' => false]); + } + + $this->Articles->save($article); + +As of 3.3.0, working with multiple translations has been streamlined. You can +create form controls for your translated fields:: + + // In a view template. + Form->create($article); ?> +
    + French + Form->control('_translations.fr.title'); ?> + Form->control('_translations.fr.body'); ?> +
    +
    + Spanish + Form->control('_translations.es.title'); ?> + Form->control('_translations.es.body'); ?> +
    + +In your controller, you can marshal the data as normal:: + + $article = $this->Articles->newEntity($this->request->getData()); + $this->Articles->save($article); + +This will result in your article, the french and spanish translations all being +persisted. You'll need to remember to add ``_translations`` into the +``$_accessible`` fields of your entity as well. + +Validating Translated Entities +------------------------------ + +When attaching ``TranslateBehavior`` to a model, you can define the validator +that should be used when translation records are created/modified by the +behavior during ``newEntity()`` or ``patchEntity()``:: + + class ArticlesTable extends Table + { + public function initialize(array $config) + { + $this->addBehavior('Translate', [ + 'fields' => ['title'], + 'validator' => 'translated' + ]); + } + } + +The above will use the validator created by ``validationTranslated`` to +validated translated entities. + +.. versionadded:: 3.3.0 + Validating translated entities, and streamlined translation saving was added + in 3.3.0 diff --git a/tl/orm/behaviors/tree.rst b/tl/orm/behaviors/tree.rst new file mode 100644 index 0000000000000000000000000000000000000000..5f42d608d80c1f1bbf1d87e515d1a969597c6024 --- /dev/null +++ b/tl/orm/behaviors/tree.rst @@ -0,0 +1,302 @@ +Tree +#### + +.. php:namespace:: Cake\ORM\Behavior + +.. php:class:: TreeBehavior + +It's fairly common to want to store hierarchical data in a database +table. Examples of such data might be categories with unlimited +subcategories, data related to a multilevel menu system or a +literal representation of hierarchy such as departments in a company. + +Relational databases are usually not well suited for storing and retrieving this +type of data, but there are a few known techniques that can make them effective +for working with multi-level information. + +The TreeBehavior helps you maintain a hierarchical data structure in the +database that can be queried without much overhead and helps reconstruct the +tree data for finding and displaying processes. + +Requirements +============ + +This behavior requires the following columns in your table: + +- ``parent_id`` (nullable) The column holding the ID of the parent row +- ``lft`` (integer, signed) Used to maintain the tree structure +- ``rght`` (integer, signed) Used to maintain the tree structure + +You can configure the name of those fields should you need to customize them. +More information on the meaning of the fields and how they are used can be found +in this article describing the `MPTT logic `_ + +.. warning:: + + The TreeBehavior does not support composite primary keys at this point in + time. + +A Quick Tour +============ + +You enable the Tree behavior by adding it to the Table you want to store +hierarchical data in:: + + class CategoriesTable extends Table + { + public function initialize(array $config) + { + $this->addBehavior('Tree'); + } + } + +Once added, you can let CakePHP build the internal structure if the table is +already holding some rows:: + + $categories = TableRegistry::get('Categories'); + $categories->recover(); + +You can verify it works by getting any row from the table and asking for the +count of descendants it has:: + + $node = $categories->get(1); + echo $categories->childCount($node); + +Getting a flat list of the descendants for a node is equally easy:: + + $descendants = $categories->find('children', ['for' => 1]); + + foreach ($descendants as $category) { + echo $category->name . "\n"; + } + +If you need to pass conditions you do so as per normal:: + + $descendants = $categories + ->find('children', ['for' => 1]) + ->where(['name LIKE' => '%Foo%']); + + foreach ($descendants as $category) { + echo $category->name . "\n"; + } + +If you instead need a threaded list, where children for each node are nested +in a hierarchy, you can stack the 'threaded' finder:: + + $children = $categories + ->find('children', ['for' => 1]) + ->find('threaded') + ->toArray(); + + foreach ($children as $child) { + echo "{$child->name} has " . count($child->children) . " direct children"; + } + +Traversing threaded results usually requires recursive functions in, but if you +only require a result set containing a single field from each level so you can +display a list, in an HTML select for example, it is better to use the +'treeList' finder:: + + $list = $categories->find('treeList'); + + // In a CakePHP template file: + echo $this->Form->control('categories', ['options' => $list]); + + // Or you can output it in plain text, for example in a CLI script + foreach ($list as $categoryName) { + echo $categoryName . "\n"; + } + +The output will be similar to:: + + My Categories + _Fun + __Sport + ___Surfing + ___Skating + _Trips + __National + __International + +The ``treeList`` finder takes a number of options: + +* ``keyPath``: A dot separated path to fetch the field to use for the array key, + or a closure to return the key out of the provided row. +* ``valuePath``: A dot separated path to fetch the field to use for the array + value, or a closure to return the value out of the provided row. +* ``spacer``: A string to be used as prefix for denoting the depth in the tree + for each item + +An example of all options in use is:: + + $query = $categories->find('treeList', [ + 'keyPath' => 'url', + 'valuePath' => 'id', + 'spacer' => ' ' + ]); + +One very common task is to find the tree path from a particular node to the root +of the tree. This is useful, for example, for adding the breadcrumbs list for +a menu structure:: + + $nodeId = 5; + $crumbs = $categories->find('path', ['for' => $nodeId]); + + foreach ($crumbs as $crumb) { + echo $crumb->name . ' > '; + } + +Trees constructed with the TreeBehavior cannot be sorted by any column other +than ``lft``, this is because the internal representation of the tree depends on +this sorting. Luckily, you can reorder the nodes inside the same level without +having to change their parent:: + + $node = $categories->get(5); + + // Move the node so it shows up one position up when listing children. + $categories->moveUp($node); + + // Move the node to the top of the list inside the same level. + $categories->moveUp($node, true); + + // Move the node to the bottom. + $categories->moveDown($node, true); + +Configuration +============= + +If the default column names that are used by this behavior don't match your own +schema, you can provide aliases for them:: + + public function initialize(array $config) + { + $this->addBehavior('Tree', [ + 'parent' => 'ancestor_id', // Use this instead of parent_id + 'left' => 'tree_left', // Use this instead of lft + 'right' => 'tree_right' // Use this instead of rght + ]); + } + +Node Level (Depth) +================== + +Knowing the depth of tree nodes can be useful when you want to retrieve nodes +only upto a certain level for e.g. when generating menus. You can use the +``level`` option to specify the field that will save level of each node:: + + $this->addBehavior('Tree', [ + 'level' => 'level', // Defaults to null, i.e. no level saving + ]); + +If you don't want to cache the level using a db field you can use +``TreeBehavior::getLevel()`` method to get level of a node. + +Scoping and Multi Trees +======================= + +Sometimes you want to persist more than one tree structure inside the same +table, you can achieve that by using the 'scope' configuration. For example, in +a locations table you may want to create one tree per country:: + + class LocationsTable extends Table + { + + public function initialize(array $config) + { + $this->addBehavior('Tree', [ + 'scope' => ['country_name' => 'Brazil'] + ]); + } + + } + +In the previous example, all tree operations will be scoped to only the rows +having the column ``country_name`` set to 'Brazil'. You can change the scoping +on the fly by using the 'config' function:: + + $this->behaviors()->Tree->config('scope', ['country_name' => 'France']); + +Optionally, you can have a finer grain control of the scope by passing a closure +as the scope:: + + $this->behaviors()->Tree->config('scope', function ($query) { + $country = $this->getConfigureContry(); // A made-up function + return $query->where(['country_name' => $country]); + }); + +Recovering with custom sort field +================================= + +.. versionadded:: 3.0.14 + +By default, recover() sorts the items using the primary key. This works great +if this is a numeric (auto increment) column, but can lead to weird results if you +use UUIDs. + +If you need custom sorting for the recovery, you can set a +custom order clause in your config:: + + $this->addBehavior('Tree', [ + 'recoverOrder' => ['country_name' => 'DESC'], + ]); + +Saving Hierarchical Data +======================== + +When using the Tree behavior, you usually don't need to worry about the +internal representation of the hierarchical structure. The positions where nodes +are placed in the tree are deduced from the 'parent_id' column in each of your +entities:: + + $aCategory = $categoriesTable->get(10); + $aCategory->parent_id = 5; + $categoriesTable->save($aCategory); + +Providing inexistent parent ids when saving or attempting to create a loop in +the tree (making a node child of itself) will throw an exception. + +You can make a node a root in the tree by setting the 'parent_id' column to +null:: + + $aCategory = $categoriesTable->get(10); + $aCategory->parent_id = null; + $categoriesTable->save($aCategory); + +Children for the new root node will be preserved. + +Deleting Nodes +============== + +Deleting a node and all its sub-tree (any children it may have at any depth in +the tree) is trivial:: + + $aCategory = $categoriesTable->get(10); + $categoriesTable->delete($aCategory); + +The TreeBehavior will take care of all internal deleting operations for you. It +is also possible to only delete one node and re-assign all children to the +immediately superior parent node in the tree:: + + $aCategory = $categoriesTable->get(10); + $categoriesTable->removeFromTree($aCategory); + $categoriesTable->delete($aCategory); + +All children nodes will be kept and a new parent will be assigned to them. + +The deletion of a node is based off of the lft and rght values of the entity. This +is important to note when looping through the various children of a node for +conditional deletes:: + + $descendants = $teams->find('children', ['for' => 1]); + + foreach ($descendants as $descendant) { + $team = $teams->get($descendant->id); // search for the up-to-date entity object + if ($team->expired) { + $teams->delete($team); // deletion reorders the lft and rght of database entries + } + } + +The TreeBehavior reorders the lft and rght values of records in the table when a node +is deleted. As such, the lft and rght values of the entities inside ``$descendants`` +(saved before the delete operation) will be inaccurate. Entities will have to be loaded +and modified on the fly to prevent inconsistencies in the table. diff --git a/tl/orm/database-basics.rst b/tl/orm/database-basics.rst new file mode 100644 index 0000000000000000000000000000000000000000..7597197587d1a1afb3d412650e84dd9fdbf7e5c5 --- /dev/null +++ b/tl/orm/database-basics.rst @@ -0,0 +1,953 @@ +Database Basics +############### + +The CakePHP database access layer abstracts and provides help with most aspects +of dealing with relational databases such as, keeping connections to the server, +building queries, preventing SQL injections, inspecting and altering schemas, +and with debugging and profiling queries sent to the database. + +Quick Tour +========== + +The functions described in this chapter illustrate what is possible to do with +the lower-level database access API. If instead you want to learn more about the +complete ORM, you can read the :doc:`/orm/query-builder` and +:doc:`/orm/table-objects` sections. + +The easiest way to create a database connection is using a ``DSN`` string:: + + use Cake\Datasource\ConnectionManager; + + $dsn = 'mysql://root:password@localhost/my_database'; + ConnectionManager::config('default', ['url' => $dsn]); + +Once created, you can access the connection object to start using it:: + + $connection = ConnectionManager::get('default'); + +Supported Databases +------------------- + +CakePHP supports the following relational database servers: + +* MySQL 5.1+ +* SQLite 3 +* PostgreSQL 8.3+ +* SQLServer 2008+ +* Oracle (through a community plugin) + +You will need the correct PDO extension installed for each of the above database +drivers. Procedural APIs are not supported. + +The Oracle database is supported through the +`Driver for Oracle Database `_ +community plugin. + +.. _running-select-statements: + +Running Select Statements +------------------------- + +Running raw SQL queries is a breeze:: + + use Cake\Datasource\ConnectionManager; + + $connection = ConnectionManager::get('default'); + $results = $connection->execute('SELECT * FROM articles')->fetchAll('assoc'); + +You can use prepared statements to insert parameters:: + + $results = $connection + ->execute('SELECT * FROM articles WHERE id = :id', ['id' => 1]) + ->fetchAll('assoc'); + +It is also possible to use complex data types as arguments:: + + $results = $connection + ->execute( + 'SELECT * FROM articles WHERE created >= :created', + ['created' => new DateTime('1 day ago')], + ['created' => 'datetime'] + ) + ->fetchAll('assoc'); + +Instead of writing the SQL manually, you can use the query builder:: + + $results = $connection + ->newQuery() + ->select('*') + ->from('articles') + ->where(['created >' => new DateTime('1 day ago'), ['created' => 'datetime']]) + ->order(['title' => 'DESC']) + ->execute() + ->fetchAll('assoc'); + +Running Insert Statements +------------------------- + +Inserting rows in the database is usually a matter of a couple lines:: + + use Cake\Datasource\ConnectionManager; + + $connection = ConnectionManager::get('default'); + $connection->insert('articles', [ + 'title' => 'A New Article', + 'created' => new DateTime('now') + ], ['created' => 'datetime']); + +Running Update Statements +------------------------- + +Updating rows in the database is equally intuitive, the following example will +update the article with **id** 10:: + + use Cake\Datasource\ConnectionManager; + $connection = ConnectionManager::get('default'); + $connection->update('articles', ['title' => 'New title'], ['id' => 10]); + +Running Delete Statements +------------------------- + +Similarly, the ``delete()`` method is used to delete rows from the database, the +following example deletes the article with **id** 10:: + + use Cake\Datasource\ConnectionManager; + $connection = ConnectionManager::get('default'); + $connection->delete('articles', ['id' => 10]); + +.. _database-configuration: + +Configuration +============= + +By convention database connections are configured in **config/app.php**. The +connection information defined in this file is fed into +:php:class:`Cake\\Datasource\\ConnectionManager` creating the connection configuration +your application will be using. Sample connection information can be found in +**config/app.default.php**. A sample connection configuration would look +like:: + + 'Datasources' => [ + 'default' => [ + 'className' => 'Cake\Database\Connection', + 'driver' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'localhost', + 'username' => 'my_app', + 'password' => 'sekret', + 'database' => 'my_app', + 'encoding' => 'utf8', + 'timezone' => 'UTC', + 'cacheMetadata' => true, + ] + ], + +The above will create a 'default' connection, with the provided parameters. You +can define as many connections as you want in your configuration file. You can +also define additional connections at runtime using +:php:meth:`Cake\\Datasource\\ConnectionManager::config()`. An example of that +would be:: + + use Cake\Datasource\ConnectionManager; + + ConnectionManager::config('default', [ + 'className' => 'Cake\Database\Connection', + 'driver' => 'Cake\Database\Driver\Mysql', + 'persistent' => false, + 'host' => 'localhost', + 'username' => 'my_app', + 'password' => 'sekret', + 'database' => 'my_app', + 'encoding' => 'utf8', + 'timezone' => 'UTC', + 'cacheMetadata' => true, + ]); + +Configuration options can also be provided as a :term:`DSN` string. This is +useful when working with environment variables or :term:`PaaS` providers:: + + ConnectionManager::config('default', [ + 'url' => 'mysql://my_app:sekret@localhost/my_app?encoding=utf8&timezone=UTC&cacheMetadata=true', + ]); + +When using a DSN string you can define any additional parameters/options as +query string arguments. + +By default, all Table objects will use the ``default`` connection. To +use a non-default connection, see :ref:`configuring-table-connections`. + +There are a number of keys supported in database configuration. A full list is +as follows: + +className + The fully namespaced class name of the class that represents the connection to a database server. + This class is responsible for loading the database driver, providing SQL + transaction mechanisms and preparing SQL statements among other things. +driver + The class name of the driver used to implements all specificities for + a database engine. This can either be a short classname using :term:`plugin syntax`, + a fully namespaced name, or a constructed driver instance. + Examples of short classnames are Mysql, Sqlite, Postgres, and Sqlserver. +persistent + Whether or not to use a persistent connection to the database. This option + is not supported by SqlServer. As of CakePHP version 3.4.13 an exception is + thrown if you attempt to set ``persistent`` to ``true`` with SqlServer. +host + The database server's hostname (or IP address). +username + The username for the account. +password + The password for the account. +database + The name of the database for this connection to use. Avoid using ``.`` in + your database name. Because of how it complicates identifier quoting CakePHP + does not support ``.`` in database names. The path to your SQLite database + should be an absolute path (e.g. ``ROOT . DS . 'my_app.db'``) to avoid + incorrect paths caused by relative paths. +port (*optional*) + The TCP port or Unix socket used to connect to the server. +encoding + Indicates the character set to use when sending SQL statements to + the server. This defaults to the database's default encoding for + all databases other than DB2. If you wish to use UTF-8 encoding + with MySQL connections you must use 'utf8' without the + hyphen. +timezone + Server timezone to set. +schema + Used in PostgreSQL database setups to specify which schema to use. +unix_socket + Used by drivers that support it to connect via Unix socket files. If you are + using PostgreSQL and want to use Unix sockets, leave the host key blank. +ssl_key + The file path to the SSL key file. (Only supported by MySQL). +ssl_cert + The file path to the SSL certificate file. (Only supported by MySQL). +ssl_ca + The file path to the SSL certificate authority. (Only supported by MySQL). +init + A list of queries that should be sent to the database server as + when the connection is created. +log + Set to ``true`` to enable query logging. When enabled queries will be logged + at a ``debug`` level with the ``queriesLog`` scope. +quoteIdentifiers + Set to ``true`` if you are using reserved words or special characters in + your table or column names. Enabling this setting will result in queries + built using the :doc:`/orm/query-builder` having identifiers quoted when + creating SQL. It should be noted that this decreases performance because + each query needs to be traversed and manipulated before being executed. +flags + An associative array of PDO constants that should be passed to the + underlying PDO instance. See the PDO documentation for the flags supported + by the driver you are using. +cacheMetadata + Either boolean ``true``, or a string containing the cache configuration to + store meta data in. Having metadata caching disable is not advised and can + result in very poor performance. See the :ref:`database-metadata-cache` + section for more information. +mask + Set the permissions on the generated database file. (Only supported by SQLite) + +At this point, you might want to take a look at the +:doc:`/intro/conventions`. The correct naming for your tables (and the addition +of some columns) can score you some free functionality and help you avoid +configuration. For example, if you name your database table big\_boxes, your +table BigBoxesTable, and your controller BigBoxesController, everything will +work together automatically. By convention, use underscores, lower case, and +plural forms for your database table names - for example: bakers, +pastry\_stores, and savory\_cakes. + +.. php:namespace:: Cake\Datasource + +Managing Connections +==================== + +.. php:class:: ConnectionManager + +The ``ConnectionManager`` class acts as a registry to access database +connections your application has. It provides a place that other objects can get +references to existing connections. + +Accessing Connections +--------------------- + +.. php:staticmethod:: get($name) + +Once configured connections can be fetched using +:php:meth:`Cake\\Datasource\\ConnectionManager::get()`. This method will +construct and load a connection if it has not been built before, or return the +existing known connection:: + + use Cake\Datasource\ConnectionManager; + + $conn = ConnectionManager::get('default'); + +Attempting to load connections that do not exist will throw an exception. + +Creating Connections at Runtime +------------------------------- + +Using ``config()`` and ``get()`` you can create new connections that are not +defined in your configuration files at runtime:: + + ConnectionManager::config('my_connection', $config); + $conn = ConnectionManager::get('my_connection'); + +See the :ref:`database-configuration` for more information on the configuration +data used when creating connections. + +.. _database-data-types: + +.. php:namespace:: Cake\Database + +Data Types +========== + +.. php:class:: Type + +Since not every database vendor includes the same set of data types, or +the same names for similar data types, CakePHP provides a set of abstracted +data types for use with the database layer. The types CakePHP supports are: + +string + Generally backed by CHAR or VARCHAR columns. Using the ``fixed`` option + will force a CHAR column. In SQL Server, NCHAR and NVARCHAR types are used. +text + Maps to TEXT types. +uuid + Maps to the UUID type if a database provides one, otherwise this will + generate a CHAR(36) field. +integer + Maps to the INTEGER type provided by the database. BIT is not yet supported + at this moment. +smallinteger + Maps to the SMALLINT type provided by the database. +tinyinteger + Maps to the TINYINT or SMALLINT type provided by the database. In MySQL + ``TINYINT(1)`` is treated as a boolean. +biginteger + Maps to the BIGINT type provided by the database. +float + Maps to either DOUBLE or FLOAT depending on the database. The ``precision`` + option can be used to define the precision used. +decimal + Maps to the DECIMAL type. Supports the ``length`` and ``precision`` + options. +boolean + Maps to BOOLEAN except in MySQL, where TINYINT(1) is used to represent + booleans. BIT(1) is not yet supported at this moment. +binary + Maps to the BLOB or BYTEA type provided by the database. +date + Maps to a timezone naive DATE column type. The return value of this column + type is :php:class:`Cake\\I18n\\Date` which extends the native ``DateTime`` + class. +datetime + Maps to a timezone naive DATETIME column type. In PostgreSQL, and SQL Server + this turns into a TIMESTAMP type. The default return value of this column + type is :php:class:`Cake\\I18n\\Time` which extends the built-in + ``DateTime`` class and `Chronos `_. +timestamp + Maps to the TIMESTAMP type. +time + Maps to a TIME type in all databases. +json + Maps to a JSON type if it's available, otherwise it maps to TEXT. The 'json' + type was added in 3.3.0 + +These types are used in both the schema reflection features that CakePHP +provides, and schema generation features CakePHP uses when using test fixtures. + +Each type can also provide translation functions between PHP and SQL +representations. These methods are invoked based on the type hints provided when +doing queries. For example a column that is marked as 'datetime' will +automatically convert input parameters from ``DateTime`` instances into a +timestamp or formatted datestrings. Likewise, 'binary' columns will accept file +handles, and generate file handles when reading data. + +.. versionchanged:: 3.3.0 + The ``json`` type was added. + +.. versionchanged:: 3.5.0 + The ``smallinteger`` and ``tinyinteger`` types were added. + +.. _adding-custom-database-types: + +Adding Custom Types +------------------- + +.. php:staticmethod:: map($name, $class) + +If you need to use vendor specific types that are not built into CakePHP you can +add additional new types to CakePHP's type system. Type classes are expected to +implement the following methods: + +* ``toPHP``: Casts given value from a database type to a PHP equivalent. +* ``toDatabase``: Casts given value from a PHP type to one acceptable by a database. +* ``toStatement``: Casts given value to its Statement equivalent. +* ``marshal``: Marshals flat data into PHP objects. + +An easy way to fulfill the basic interface is to extend +:php:class:`Cake\\Database\\Type`. For example if we wanted to add a JSON type, +we could make the following type class:: + + // in src/Database/Type/JsonType.php + + namespace App\Database\Type; + + use Cake\Database\Driver; + use Cake\Database\Type; + use PDO; + + class JsonType extends Type + { + + public function toPHP($value, Driver $driver) + { + if ($value === null) { + return null; + } + return json_decode($value, true); + } + + public function marshal($value) + { + if (is_array($value) || $value === null) { + return $value; + } + return json_decode($value, true); + } + + public function toDatabase($value, Driver $driver) + { + return json_encode($value); + } + + public function toStatement($value, Driver $driver) + { + if ($value === null) { + return PDO::PARAM_NULL; + } + return PDO::PARAM_STR; + } + + } + +By default the ``toStatement()`` method will treat values as strings which will +work for our new type. Once we've created our new type, we need to add it into +the type mapping. During our application bootstrap we should do the following:: + + use Cake\Database\Type; + + Type::map('json', 'App\Database\Type\JsonType'); + +.. versionadded:: 3.3.0 + The JsonType described in this example was added to the core. + +We can then overload the reflected schema data to use our new type, and +CakePHP's database layer will automatically convert our JSON data when creating +queries. You can use the custom types you've created by mapping the types in +your Table's :ref:`_initializeSchema() method `:: + + use Cake\Database\Schema\TableSchema; + + class WidgetsTable extends Table + { + + protected function _initializeSchema(TableSchema $schema) + { + $schema->columnType('widget_prefs', 'json'); + return $schema; + } + + } + +.. _mapping-custom-datatypes-to-sql-expressions: + +Mapping Custom Datatypes to SQL Expressions +------------------------------------------- + +.. versionadded:: 3.3.0 + Support for mapping custom data types to SQL expressions was added in 3.3.0. + +The previous example maps a custom datatype for a 'json' column type which is +easily represented as a string in a SQL statement. Complex SQL data +types cannot be represented as strings/integers in SQL queries. When working +with these datatypes your Type class needs to implement the +``Cake\Database\Type\ExpressionTypeInterface`` interface. This interface lets +your custom type represent a value as a SQL expression. As an example, we'll +build a simple Type class for handling ``POINT`` type data out of MySQL. First +we'll define a 'value' object that we can use to represent ``POINT`` data in +PHP:: + + // in src/Database/Point.php + namespace App\Database; + + // Our value object is immutable. + class Point + { + protected $_lat; + protected $_long; + + // Factory method. + public static function parse($value) + { + // Parse the data from MySQL. + return new static($value[0], $value[1]); + } + + public function __construct($lat, $long) + { + $this->_lat = $lat; + $this->_long = $long; + } + + public function lat() + { + return $this->_lat; + } + + public function long() + { + return $this->_long; + } + } + +With our value object created, we'll need a Type class to map data into this +value object and into SQL expressions:: + + namespace App\Database\Type; + + use App\Database\Point; + use Cake\Database\Expression\FunctionExpression; + use Cake\Database\Type as BaseType; + use Cake\Database\Type\ExpressionTypeInterface; + + class PointType extends BaseType implements ExpressionTypeInterface + { + public function toPHP($value, Driver $d) + { + return Point::parse($value); + } + + public function marshal($value) + { + if (is_string($value)) { + $value = explode(',', $value); + } + if (is_array($value)) { + return new Point($value[0], $value[1]); + } + return null; + } + + public function toExpression($value) + { + if ($value instanceof Point) { + return new FunctionExpression( + 'POINT', + [ + $value->lat(), + $value->long() + ] + ); + } + if (is_array($value)) { + return new FunctionExpression('POINT', [$value[0], $value[1]]); + } + // Handle other cases. + } + } + +The above class does a few interesting things: + +* The ``toPHP`` method handles parsing the SQL query results into a value + object. +* The ``marshal`` method handles converting, data such as given request data, into our value object. + We're going to accept string values like ``'10.24,12.34`` and arrays for now. +* The ``toExpression`` method handles converting our value object into the + equivalent SQL expressions. In our example the resulting SQL would be + something like ``POINT(10.24, 12.34)``. + +Once we've built our custom type, we'll need to :ref:`connect our type +to our table class `. + +.. _immutable-datetime-mapping: + +Enabling Immutable DateTime Objects +----------------------------------- + +.. versionadded:: 3.2 + Immutable date/time objects were added in 3.2. + +Because Date/Time objects are easily mutated in place, CakePHP allows you to +enable immutable value objects. This is best done in your application's +**config/bootstrap.php** file:: + + Type::build('datetime')->useImmutable(); + Type::build('date')->useImmutable(); + Type::build('time')->useImmutable(); + Type::build('timestamp')->useImmutable(); + +.. note:: + New applications will have immutable objects enabled by default. + +Connection Classes +================== + +.. php:class:: Connection + +Connection classes provide a simple interface to interact with database +connections in a consistent way. They are intended as a more abstract interface to +the driver layer and provide features for executing queries, logging queries, and doing +transactional operations. + +.. _database-queries: + +Executing Queries +----------------- + +.. php:method:: query($sql) + +Once you've gotten a connection object, you'll probably want to issue some +queries with it. CakePHP's database abstraction layer provides wrapper features +on top of PDO and native drivers. These wrappers provide a similar interface to +PDO. There are a few different ways you can run queries depending on the type of +query you need to run and what kind of results you need back. The most basic +method is ``query()`` which allows you to run already completed SQL queries:: + + $stmt = $conn->query('UPDATE articles SET published = 1 WHERE id = 2'); + +.. php:method:: execute($sql, $params, $types) + +The ``query()`` method does not allow for additional parameters. If you need +additional parameters you should use the ``execute()`` method, which allows for +placeholders to be used:: + + $stmt = $conn->execute( + 'UPDATE articles SET published = ? WHERE id = ?', + [1, 2] + ); + +Without any type hinting information, ``execute`` will assume all placeholders +are string values. If you need to bind specific types of data, you can use their +abstract type names when creating a query:: + + $stmt = $conn->execute( + 'UPDATE articles SET published_date = ? WHERE id = ?', + [new DateTime('now'), 2], + ['date', 'integer'] + ); + +.. php:method:: newQuery() + +This allows you to use rich data types in your applications and properly convert +them into SQL statements. The last and most flexible way of creating queries is +to use the :doc:`/orm/query-builder`. This approach allows you to build complex and +expressive queries without having to use platform specific SQL:: + + $query = $conn->newQuery(); + $query->update('articles') + ->set(['published' => true]) + ->where(['id' => 2]); + $stmt = $query->execute(); + +When using the query builder, no SQL will be sent to the database server until +the ``execute()`` method is called, or the query is iterated. Iterating a query +will first execute it and then start iterating over the result set:: + + $query = $conn->newQuery(); + $query->select('*') + ->from('articles') + ->where(['published' => true]); + + foreach ($query as $row) { + // Do something with the row. + } + +.. note:: + + When you have an instance of :php:class:`Cake\\ORM\\Query` you can use + ``all()`` to get the result set for SELECT queries. + +Using Transactions +------------------ + +The connection objects provide you a few simple ways you do database +transactions. The most basic way of doing transactions is through the ``begin()``, +``commit()`` and ``rollback()`` methods, which map to their SQL equivalents:: + + $conn->begin(); + $conn->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); + $conn->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); + $conn->commit(); + +.. php:method:: transactional(callable $callback) + +In addition to this interface connection instances also provide the +``transactional()`` method which makes handling the begin/commit/rollback calls +much simpler:: + + $conn->transactional(function ($conn) { + $conn->execute('UPDATE articles SET published = ? WHERE id = ?', [true, 2]); + $conn->execute('UPDATE articles SET published = ? WHERE id = ?', [false, 4]); + }); + +In addition to basic queries, you can execute more complex queries using either +the :doc:`/orm/query-builder` or :doc:`/orm/table-objects`. The transactional method will +do the following: + +- Call ``begin``. +- Call the provided closure. +- If the closure raises an exception, a rollback will be issued. The original + exception will be re-thrown. +- If the closure returns ``false``, a rollback will be issued. +- If the closure executes successfully, the transaction will be committed. + +Interacting with Statements +=========================== + +When using the lower level database API, you will often encounter statement +objects. These objects allow you to manipulate the underlying prepared statement +from the driver. After creating and executing a query object, or using +``execute()`` you will have a ``StatementDecorator`` instance. It wraps the +underlying basic statement object and provides a few additional features. + +Preparing a Statement +--------------------- + +You can create a statement object using ``execute()``, or ``prepare()``. The +``execute()`` method returns a statement with the provided values bound to it. +While ``prepare()`` returns an incomplete statement:: + + // Statements from execute will have values bound to them already. + $stmt = $conn->execute( + 'SELECT * FROM articles WHERE published = ?', + [true] + ); + + // Statements from prepare will be parameters for placeholders. + // You need to bind parameters before attempting to execute it. + $stmt = $conn->prepare('SELECT * FROM articles WHERE published = ?'); + +Once you've prepared a statement you can bind additional data and execute it. + +.. _database-basics-binding-values: + +Binding Values +-------------- + +Once you've created a prepared statement, you may need to bind additional data. +You can bind multiple values at once using the ``bind()`` method, or bind +individual elements using ``bindValue``:: + + $stmt = $conn->prepare( + 'SELECT * FROM articles WHERE published = ? AND created > ?' + ); + + // Bind multiple values + $stmt->bind( + [true, new DateTime('2013-01-01')], + ['boolean', 'date'] + ); + + // Bind a single value + $stmt->bindValue(1, true, 'boolean'); + $stmt->bindValue(2, new DateTime('2013-01-01'), 'date'); + +When creating statements you can also use named array keys instead of +positional ones:: + + $stmt = $conn->prepare( + 'SELECT * FROM articles WHERE published = :published AND created > :created' + ); + + // Bind multiple values + $stmt->bind( + ['published' => true, 'created' => new DateTime('2013-01-01')], + ['published' => 'boolean', 'created' => 'date'] + ); + + // Bind a single value + $stmt->bindValue('published', true, 'boolean'); + $stmt->bindValue('created', new DateTime('2013-01-01'), 'date'); + +.. warning:: + + You cannot mix positional and named array keys in the same statement. + +Executing & Fetching Rows +------------------------- + +After preparing a statement and binding data to it, you can execute it and fetch +rows. Statements should be executed using the ``execute()`` method. Once +executed, results can be fetched using ``fetch()``, ``fetchAll()`` or iterating +the statement:: + + $stmt->execute(); + + // Read one row. + $row = $stmt->fetch('assoc'); + + // Read all rows. + $rows = $stmt->fetchAll('assoc'); + + // Read rows through iteration. + foreach ($rows as $row) { + // Do work + } + +.. note:: + + Reading rows through iteration will fetch rows in 'both' mode. This means + you will get both the numerically indexed and associatively indexed results. + +Getting Row Counts +------------------ + +After executing a statement, you can fetch the number of affected rows:: + + $rowCount = count($stmt); + $rowCount = $stmt->rowCount(); + +Checking Error Codes +-------------------- + +If your query was not successful, you can get related error information +using the ``errorCode()`` and ``errorInfo()`` methods. These methods work the +same way as the ones provided by PDO:: + + $code = $stmt->errorCode(); + $info = $stmt->errorInfo(); + +.. todo:: + Possibly document CallbackStatement and BufferedStatement + +.. _database-query-logging: + +Query Logging +============= + +Query logging can be enabled when configuring your connection by setting the +``log`` option to ``true``. You can also toggle query logging at runtime, using +``logQueries``:: + + // Turn query logging on. + $conn->logQueries(true); + + // Turn query logging off + $conn->logQueries(false); + +When query logging is enabled, queries will be logged to +:php:class:`Cake\\Log\\Log` using the 'debug' level, and the 'queriesLog' scope. +You will need to have a logger configured to capture this level & scope. Logging +to ``stderr`` can be useful when working on unit tests, and logging to +files/syslog can be useful when working with web requests:: + + use Cake\Log\Log; + + // Console logging + Log::config('queries', [ + 'className' => 'Console', + 'stream' => 'php://stderr', + 'scopes' => ['queriesLog'] + ]); + + // File logging + Log::config('queries', [ + 'className' => 'File', + 'path' => LOGS, + 'file' => 'queries.log', + 'scopes' => ['queriesLog'] + ]); + +.. note:: + + Query logging is only intended for debugging/development uses. You should + never leave query logging on in production as it will negatively impact the + performance of your application. + +.. _identifier-quoting: + +Identifier Quoting +================== + +By default CakePHP does **not** quote identifiers in generated SQL queries. The +reason for this is identifier quoting has a few drawbacks: + +* Performance overhead - Quoting identifiers is much slower and complex than not doing it. +* Not necessary in most cases - In non-legacy databases that follow CakePHP's + conventions there is no reason to quote identifiers. + +If you are using a legacy schema that requires identifier quoting you can enable +it using the ``quoteIdentifiers`` setting in your +:ref:`database-configuration`. You can also enable this feature at runtime:: + + $conn->driver()->autoQuoting(true); + +When enabled, identifier quoting will cause additional query traversal that +converts all identifiers into ``IdentifierExpression`` objects. + +.. note:: + + SQL snippets contained in QueryExpression objects will not be modified. + +.. _database-metadata-cache: + +Metadata Caching +================ + +CakePHP's ORM uses database reflection to determine the schema, indexes and +foreign keys your application contains. Because this metadata changes +infrequently and can be expensive to access, it is typically cached. By default, +metadata is stored in the ``_cake_model_`` cache configuration. You can define +a custom cache configuration using the ``cacheMetatdata`` option in your +datasource configuration:: + + 'Datasources' => [ + 'default' => [ + // Other keys go here. + + // Use the 'orm_metadata' cache config for metadata. + 'cacheMetadata' => 'orm_metadata', + ] + ], + +You can also configure the metadata caching at runtime with the +``cacheMetadata()`` method:: + + // Disable the cache + $connection->cacheMetadata(false); + + // Enable the cache + $connection->cacheMetadata(true); + + // Use a custom cache config + $connection->cacheMetadata('orm_metadata'); + +CakePHP also includes a CLI tool for managing metadata caches. See the +:doc:`/console-and-shells/orm-cache` chapter for more information. + +Creating Databases +================== + +If you want to create a connection without selecting a database you can omit +the database name:: + + $dsn = 'mysql://root:password@localhost/'; + +You can now use your connection object to execute queries that create/modify +databases. For example to create a database:: + + $connection->query("CREATE DATABASE IF NOT EXISTS my_database"); + +.. note:: + + When creating a database it is a good idea to set the character set and + collation parameters. If these values are missing, the database will set + whatever system default values it uses. + +.. meta:: + :title lang=en: Database Basics + :keywords lang=en: SQL,MySQL,MariaDB,PostGres,Postgres,postgres,PostgreSQL,PostGreSQL,postGreSql,select,insert,update,delete,statement,configuration,connection,database,data,types,custom,,executing,queries,transactions,prepared,statements,binding,fetching,row,count,error,codes,query,logging,identifier,quoting,metadata,caching diff --git a/tl/orm/deleting-data.rst b/tl/orm/deleting-data.rst new file mode 100644 index 0000000000000000000000000000000000000000..46aae4971b80b01ecb84ae7b0844e68100f0bb5d --- /dev/null +++ b/tl/orm/deleting-data.rst @@ -0,0 +1,105 @@ +Deleting Data +############# + +.. php:namespace:: Cake\ORM + +.. php:class:: Table + :noindex: + +.. php:method:: delete(Entity $entity, $options = []) + +Once you've loaded an entity you can delete it by calling the originating +table's delete method:: + + // In a controller. + $entity = $this->Articles->get(2); + $result = $this->Articles->delete($entity); + +When deleting entities a few things happen: + +1. The :ref:`delete rules ` will be applied. If the rules + fail, deletion will be prevented. +2. The ``Model.beforeDelete`` event is triggered. If this event is stopped, the + delete will be aborted and the event's result will be returned. +3. The entity will be deleted. +4. All dependent associations will be deleted. If associations are being deleted + as entities, additional events will be dispatched. +5. Any junction table records for BelongsToMany associations will be removed. +6. The ``Model.afterDelete`` event will be triggered. + +By default all deletes happen within a transaction. You can disable the +transaction with the atomic option:: + + $result = $this->Articles->delete($entity, ['atomic' => false]); + +Cascading Deletes +----------------- + +When deleting entities, associated data can also be deleted. If your HasOne and +HasMany associations are configured as ``dependent``, delete operations will +'cascade' to those entities as well. By default entities in associated tables +are removed using :php:meth:`Cake\\ORM\\Table::deleteAll()`. You can elect to +have the ORM load related entities, and delete them individually by setting the +``cascadeCallbacks`` option to ``true``. A sample HasMany association with both +these options enabled would be:: + + // In a Table's initialize method. + $this->hasMany('Comments', [ + 'dependent' => true, + 'cascadeCallbacks' => true, + ]); + +.. note:: + + Setting ``cascadeCallbacks`` to ``true``, results in considerably slower deletes + when compared to bulk deletes. The cascadeCallbacks option should only be + enabled when your application has important work handled by event listeners. + +Bulk Deletes +------------ + +.. php:method:: deleteAll($conditions) + +There may be times when deleting rows one by one is not efficient or useful. +In these cases it is more performant to use a bulk-delete to remove many rows at +once:: + + // Delete all the spam + function destroySpam() + { + return $this->deleteAll(['is_spam' => true]); + } + +A bulk-delete will be considered successful if 1 or more rows are deleted. + +.. warning:: + + deleteAll will *not* trigger beforeDelete/afterDelete events. If you need those + first load a collection of records and delete them. + +Strict Deletes +-------------- + +.. php:method:: deleteOrFail($entity, $options = []) + +Using this method will throw an +:php:exc:`Cake\\ORM\\Exception\\PersistenceFailedException` if : + +* the entity is new +* the entity has no primary key value +* application rules checks failed +* the delete was aborted by a callback. + +If you want to track down the entity that failed to save, you can use the +:php:meth:`Cake\\ORM\Exception\\PersistenceFailedException::getEntity()` method:: + + try { + $table->deleteOrFail($entity); + } catch (\Cake\ORM\Exception\PersistenceFailedException $e) { + echo $e->getEntity(); + } + +As this internally performs a :php:meth:`Cake\\ORM\\Table::delete()` call, all +corresponding delete events will be triggered. + +.. versionadded:: 3.4.1 diff --git a/tl/orm/entities.rst b/tl/orm/entities.rst new file mode 100644 index 0000000000000000000000000000000000000000..c9b3c0cf4ca017f6a705651508748217f9e43dc7 --- /dev/null +++ b/tl/orm/entities.rst @@ -0,0 +1,532 @@ +Entities +######## + +.. php:namespace:: Cake\ORM + +.. php:class:: Entity + +While :doc:`/orm/table-objects` represent and provide access to a collection of +objects, entities represent individual rows or domain objects in your +application. Entities contain persistent properties and methods to manipulate and +access the data they contain. + +Entities are created for you by CakePHP each time you use ``find()`` on a table +object. + +Creating Entity Classes +======================= + +You don't need to create entity classes to get started with the ORM in CakePHP. +However, if you want to have custom logic in your entities you will need to +create classes. By convention entity classes live in **src/Model/Entity/**. If +our application had an ``articles`` table we could create the following entity:: + + // src/Model/Entity/Article.php + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class Article extends Entity + { + } + +Right now this entity doesn't do very much. However, when we load data from our +articles table, we'll get instances of this class. + +.. note:: + + If you don't define an entity class CakePHP will use the basic Entity class. + +Creating Entities +================= + +Entities can be directly instantiated:: + + use App\Model\Entity\Article; + + $article = new Article(); + +When instantiating an entity you can pass the properties with the data you want +to store in them:: + + use App\Model\Entity\Article; + + $article = new Article([ + 'id' => 1, + 'title' => 'New Article', + 'created' => new DateTime('now') + ]); + +The preferred way of getting new entities is using the ``newEntity()`` method from the +``Table`` objects:: + + use Cake\ORM\TableRegistry; + + $article = TableRegistry::get('Articles')->newEntity(); + $article = TableRegistry::get('Articles')->newEntity([ + 'id' => 1, + 'title' => 'New Article', + 'created' => new DateTime('now') + ]); + +``$article`` will be an instance of ``App\Model\Entity\Article`` or fallback to +``Cake\ORM\Entity` instance if you haven't created the ``Article`` class. + +Accessing Entity Data +===================== + +Entities provide a few ways to access the data they contain. Most commonly you +will access the data in an entity using object notation:: + + use App\Model\Entity\Article; + + $article = new Article; + $article->title = 'This is my first post'; + echo $article->title; + +You can also use the ``get()`` and ``set()`` methods:: + + $article->set('title', 'This is my first post'); + echo $article->get('title'); + +When using ``set()`` you can update multiple properties at once using an array:: + + $article->set([ + 'title' => 'My first post', + 'body' => 'It is the best ever!' + ]); + +.. warning:: + + When updating entities with request data you should whitelist which fields + can be set with mass assignment. + +Accessors & Mutators +==================== + +In addition to the simple get/set interface, entities allow you to provide +accessors and mutator methods. These methods let you customize how properties +are read or set. + +Accessors use the convention of ``_get`` followed by the CamelCased version of +the field name. + +.. php:method:: get($field) + +They receive the basic value stored in the ``_properties`` array +as their only argument. Accessors will be used when saving entities, so be +careful when defining methods that format data, as the formatted data will be +persisted. For example:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class Article extends Entity + { + protected function _getTitle($title) + { + return ucwords($title); + } + } + +The accessor would be run when getting the property through any of these two ways:: + + echo $user->title; + echo $user->get('title'); + +You can customize how properties get set by defining a mutator: + +.. php:method:: set($field = null, $value = null) + +Mutator methods should always return the value that should be stored in the +property. As you can see above, you can also use mutators to set other +calculated properties. When doing this, be careful to not introduce any loops, +as CakePHP will not prevent infinitely looping mutator methods. + +Mutators allow you to convert properties as they are set, or create calculated +data. Mutators and accessors are applied when properties are read using object +notation, or using ``get()`` and ``set()``. For example:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + use Cake\Utility\Text; + + class Article extends Entity + { + protected function _setTitle($title) + { + return Text::slug($title); + } + } + +The mutator would be run when setting the property through any of these two +ways:: + + $user->title = 'foo'; // slug is set as well + $user->set('title', 'foo'); // slug is set as well + +.. warning:: + + Accessors are also run before entities are persisted to the database. + If you want to transform fields but not persist that transformation, + we recommend using virtual properties as those are not persisted. + +.. _entities-virtual-properties: + +Creating Virtual Properties +--------------------------- + +By defining accessors you can provide access to fields/properties that do not +actually exist. For example if your users table has ``first_name`` and +``last_name`` you could create a method for the full name:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class User extends Entity + { + protected function _getFullName() + { + return $this->_properties['first_name'] . ' ' . + $this->_properties['last_name']; + } + } + +You can access virtual properties as if they existed on the entity. The property +name will be the lower case and underscored version of the method:: + + echo $user->full_name; + +Do bear in mind that virtual properties cannot be used in finds. If you want +virtual properties to be part of JSON or array representations of your entities, +see :ref:`exposing-virtual-properties`. + +Checking if an Entity Has Been Modified +======================================= + +.. php:method:: dirty($field = null, $dirty = null) + +You may want to make code conditional based on whether or not properties have +changed in an entity. For example, you may only want to validate fields when +they change:: + + // See if the title has been modified. + $article->dirty('title'); + +You can also flag fields as being modified. This is handy when appending into +array properties:: + + // Add a comment and mark the field as changed. + $article->comments[] = $newComment; + $article->dirty('comments', true); + +In addition you can also base your conditional code on the original property +values by using the ``getOriginal()`` method. This method will either return +the original value of the property if it has been modified or its actual value. + +You can also check for changes to any property in the entity:: + + // See if the entity has changed + $article->dirty(); + +To remove the dirty mark from fields in an entity, you can use the ``clean()`` +method:: + + $article->clean(); + +When creating a new entity, you can avoid the fields from being marked as dirty +by passing an extra option:: + + $article = new Article(['title' => 'New Article'], ['markClean' => true]); + +To get a list of all dirty properties of an ``Entity`` you may call:: + + $dirtyFields = $entity->getDirty(); + +.. versionadded:: 3.4.3 + + ``getDirty()`` has been added. + +Validation Errors +================= + +.. php:method:: errors($field = null, $errors = null) + +After you :ref:`save an entity ` any validation errors will be +stored on the entity itself. You can access any validation errors using the +``getErrors()`` or ``getError()`` method:: + + // Get all the errors + $errors = $user->getErrors(); + // Prior to 3.4.0 + $errors = $user->errors(); + + // Get the errors for a single field. + $errors = $user->getError('password'); + // Prior to 3.4.0 + $errors = $user->errors('password'); + +The ``setErrors()`` or ``setError()`` method can also be used to set the errors on an entity, making +it easier to test code that works with error messages:: + + $user->setError('password', ['Password is required']); + $user->setErrors(['password' => ['Password is required'], 'username' => ['Username is required']]); + // Prior to 3.4.0 + $user->errors('password', ['Password is required.']); + +.. _entities-mass-assignment: + +Mass Assignment +=============== + +While setting properties to entities in bulk is simple and convenient, it can +create significant security issues. Bulk assigning user data from the request +into an entity allows the user to modify any and all columns. When using +anonymous entity classes or creating the entity class with the :doc:`/bake` +CakePHP does not protect against mass-assignment. + +The ``_accessible`` property allows you to provide a map of properties and +whether or not they can be mass-assigned. The values ``true`` and ``false`` +indicate whether a field can or cannot be mass-assigned:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class Article extends Entity + { + protected $_accessible = [ + 'title' => true, + 'body' => true + ]; + } + +In addition to concrete fields there is a special ``*`` field which defines the +fallback behavior if a field is not specifically named:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class Article extends Entity + { + protected $_accessible = [ + 'title' => true, + 'body' => true, + '*' => false, + ]; + } + +.. note:: If the ``*`` property is not defined it will default to ``false``. + +Avoiding Mass Assignment Protection +----------------------------------- + +When creating a new entity using the ``new`` keyword you can tell it to not +protect itself against mass assignment:: + + use App\Model\Entity\Article; + + $article = new Article(['id' => 1, 'title' => 'Foo'], ['guard' => false]); + +Modifying the Guarded Fields at Runtime +--------------------------------------- + +You can modify the list of guarded fields at runtime using the ``accessible`` +method:: + + // Make user_id accessible. + $article->accessible('user_id', true); + + // Make title guarded. + $article->accessible('title', false); + +.. note:: + + Modifying accessible fields affects only the instance the method is called + on. + +When using the ``newEntity()`` and ``patchEntity()`` methods in the ``Table`` +objects you can customize mass assignment protection with options. Please refer +to the :ref:`changing-accessible-fields` section for more information. + +Bypassing Field Guarding +------------------------ + +There are some situations when you want to allow mass-assignment to guarded +fields:: + + $article->set($properties, ['guard' => false]); + +By setting the ``guard`` option to ``false``, you can ignore the accessible +field list for a single call to ``set()``. + +Checking if an Entity was Persisted +----------------------------------- + +It is often necessary to know if an entity represents a row that is already +in the database. In those situations use the ``isNew()`` method:: + + if (!$article->isNew()) { + echo 'This article was saved already!'; + } + +If you are certain that an entity has already been persisted, you can use +``isNew()`` as a setter:: + + $article->isNew(false); + + $article->isNew(true); + +.. _lazy-load-associations: + +Lazy Loading Associations +========================= + +While eager loading associations is generally the most efficient way to access +your associations, there may be times when you need to lazily load associated +data. Before we get into how to lazy load associations, we should discuss the +differences between eager loading and lazy loading associations: + +Eager loading + Eager loading uses joins (where possible) to fetch data from the + database in as *few* queries as possible. When a separate query is required, + like in the case of a HasMany association, a single query is emitted to + fetch *all* the associated data for the current set of objects. +Lazy loading + Lazy loading defers loading association data until it is absolutely + required. While this can save CPU time because possibly unused data is not + hydrated into objects, it can result in many more queries being emitted to + the database. For example looping over a set of articles & their comments + will frequently emit N queries where N is the number of articles being + iterated. + +While lazy loading is not included by CakePHP's ORM, you can just use one of the +community plugins to do so. We recommend `the LazyLoad Plugin +`__ + +After adding the plugin to your entity, you will be able to do the following:: + + $article = $this->Articles->findById($id); + + // The comments property was lazy loaded + foreach ($article->comments as $comment) { + echo $comment->body; + } + +Creating Re-usable Code with Traits +=================================== + +You may find yourself needing the same logic in multiple entity classes. PHP's +traits are a great fit for this. You can put your application's traits in +**src/Model/Entity**. By convention traits in CakePHP are suffixed with +``Trait`` so they can be discernible from classes or interfaces. Traits are +often a good complement to behaviors, allowing you to provide functionality for +the table and entity objects. + +For example if we had SoftDeletable plugin, it could provide a trait. This trait +could give methods for marking entities as 'deleted', the method ``softDelete`` +could be provided by a trait:: + + // SoftDelete/Model/Entity/SoftDeleteTrait.php + + namespace SoftDelete\Model\Entity; + + trait SoftDeleteTrait + { + public function softDelete() + { + $this->set('deleted', true); + } + } + +You could then use this trait in your entity class by importing it and including +it:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + use SoftDelete\Model\Entity\SoftDeleteTrait; + + class Article extends Entity + { + use SoftDeleteTrait; + } + +Converting to Arrays/JSON +========================= + +When building APIs, you may often need to convert entities into arrays or JSON +data. CakePHP makes this simple:: + + // Get an array. + // Associations will be converted with toArray() as well. + $array = $user->toArray(); + + // Convert to JSON + // Associations will be converted with jsonSerialize hook as well. + $json = json_encode($user); + +When converting an entity to an JSON, the virtual & hidden field lists are +applied. Entities are recursively converted to JSON as well. This means that if you +eager loaded entities and their associations CakePHP will correctly handle +converting the associated data into the correct format. + +.. _exposing-virtual-properties: + +Exposing Virtual Properties +--------------------------- + +By default virtual fields are not exported when converting entities to +arrays or JSON. In order to expose virtual properties you need to make them +visible. When defining your entity class you can provide a list of virtual +properties that should be exposed:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class User extends Entity + { + protected $_virtual = ['full_name']; + } + +This list can be modified at runtime using ``virtualProperties``:: + + $user->virtualProperties(['full_name', 'is_admin']); + +Hiding Properties +----------------- + +There are often fields you do not want exported in JSON or array formats. For +example it is often unwise to expose password hashes or account recovery +questions. When defining an entity class, define which properties should be +hidden:: + + namespace App\Model\Entity; + + use Cake\ORM\Entity; + + class User extends Entity + { + protected $_hidden = ['password']; + } + +This list can be modified at runtime using ``hiddenProperties``:: + + $user->hiddenProperties(['password', 'recovery_question']); + +Storing Complex Types +===================== + +Accessor & Mutator methods on entities are not intended to contain the logic for +serializing and unserializing complex data coming from the database. Refer to +the :ref:`saving-complex-types` section to understand how your application can +store more complex data types like arrays and objects. + +.. meta:: + :title lang=en: Entities + :keywords lang=en: entity, entities, single row, individual record diff --git a/tl/orm/query-builder.rst b/tl/orm/query-builder.rst new file mode 100644 index 0000000000000000000000000000000000000000..5819c56b59ae64c73fc22cea2d786f3b68bdcc60 --- /dev/null +++ b/tl/orm/query-builder.rst @@ -0,0 +1,1450 @@ +Query Builder +############# + +.. php:namespace:: Cake\ORM + +.. php:class:: Query + +The ORM's query builder provides a simple to use fluent interface for creating +and running queries. By composing queries together, you can create advanced +queries using unions and subqueries with ease. + +Underneath the covers, the query builder uses PDO prepared statements which +protect against SQL injection attacks. + +The Query Object +================ + +The easiest way to create a ``Query`` object is to use ``find()`` from a +``Table`` object. This method will return an incomplete query ready to be +modified. You can also use a table's connection object to access the lower level +Query builder that does not include ORM features, if necessary. See the +:ref:`database-queries` section for more information:: + + use Cake\ORM\TableRegistry; + $articles = TableRegistry::get('Articles'); + + // Start a new query. + $query = $articles->find(); + +When inside a controller, you can use the automatic table variable that is +created using the conventions system:: + + // Inside ArticlesController.php + + $query = $this->Articles->find(); + +Selecting Rows From A Table +--------------------------- + +:: + + use Cake\ORM\TableRegistry; + + $query = TableRegistry::get('Articles')->find(); + + foreach ($query as $article) { + debug($article->title); + } + +For the remaining examples, assume that ``$articles`` is a +:php:class:`~Cake\\ORM\\Table`. When inside controllers, you can use +``$this->Articles`` instead of ``$articles``. + +Almost every method in a ``Query`` object will return the same query, this means +that ``Query`` objects are lazy, and will not be executed unless you tell them +to:: + + $query->where(['id' => 1]); // Return the same query object + $query->order(['title' => 'DESC']); // Still same object, no SQL executed + +You can of course chain the methods you call on Query objects:: + + $query = $articles + ->find() + ->select(['id', 'name']) + ->where(['id !=' => 1]) + ->order(['created' => 'DESC']); + + foreach ($query as $article) { + debug($article->created); + } + +If you try to call ``debug()`` on a Query object, you will see its internal +state and the SQL that will be executed in the database:: + + debug($articles->find()->where(['id' => 1])); + + // Outputs + // ... + // 'sql' => 'SELECT * FROM articles where id = ?' + // ... + +You can execute a query directly without having to use ``foreach`` on it. +The easiest way is to either call the ``all()`` or ``toArray()`` methods:: + + $resultsIteratorObject = $articles + ->find() + ->where(['id >' => 1]) + ->all(); + + foreach ($resultsIteratorObject as $article) { + debug($article->id); + } + + $resultsArray = $articles + ->find() + ->where(['id >' => 1]) + ->toArray(); + + foreach ($resultsArray as $article) { + debug($article->id); + } + + debug($resultsArray[0]->title); + +In the above example, ``$resultsIteratorObject`` will be an instance of +``Cake\ORM\ResultSet``, an object you can iterate and apply several extracting +and traversing methods on. + +Often, there is no need to call ``all()``, you can simply iterate the +Query object to get its results. Query objects can also be used directly as the +result object; trying to iterate the query, calling ``toArray()`` or some of the +methods inherited from :doc:`Collection `, will +result in the query being executed and results returned to you. + +Selecting A Single Row From A Table +----------------------------------- + +You can use the ``first()`` method to get the first result in the query:: + + $article = $articles + ->find() + ->where(['id' => 1]) + ->first(); + + debug($article->title); + +Getting A List Of Values From A Column +-------------------------------------- + +:: + + // Use the extract() method from the collections library + // This executes the query as well + $allTitles = $articles->find()->extract('title'); + + foreach ($allTitles as $title) { + echo $title; + } + +You can also get a key-value list out of a query result:: + + $list = $articles->find('list'); + + foreach ($list as $id => $title) { + echo "$id : $title" + } + +For more information on how to customize the fields used for populating the list +refer to :ref:`table-find-list` section. + +Queries Are Collection Objects +------------------------------ + +Once you get familiar with the Query object methods, it is strongly encouraged +that you visit the :doc:`Collection ` section to +improve your skills in efficiently traversing the data. In short, it is +important to remember that anything you can call on a Collection object, you +can also do in a Query object:: + + // Use the combine() method from the collections library + // This is equivalent to find('list') + $keyValueList = $articles->find()->combine('id', 'title'); + + // An advanced example + $results = $articles->find() + ->where(['id >' => 1]) + ->order(['title' => 'DESC']) + ->map(function ($row) { // map() is a collection method, it executes the query + $row->trimmedTitle = trim($row->title); + return $row; + }) + ->combine('id', 'trimmedTitle') // combine() is another collection method + ->toArray(); // Also a collections library method + + foreach ($results as $id => $trimmedTitle) { + echo "$id : $trimmedTitle"; + } + +How Are Queries Lazily Evaluated +-------------------------------- + +Query objects are lazily evaluated. This means a query is not executed until one +of the following things occur: + +- The query is iterated with ``foreach()``. +- The query's ``execute()`` method is called. This will return the underlying + statement object, and is to be used with insert/update/delete queries. +- The query's ``first()`` method is called. This will return the first result in the set + built by ``SELECT`` (it adds ``LIMIT 1`` to the query). +- The query's ``all()`` method is called. This will return the result set and + can only be used with ``SELECT`` statements. +- The query's ``toArray()`` method is called. + +Until one of these conditions are met, the query can be modified without additional +SQL being sent to the database. It also means that if a Query hasn't been +evaluated, no SQL is ever sent to the database. Once executed, modifying and +re-evaluating a query will result in additional SQL being run. + +If you want to take a look at what SQL CakePHP is generating, you can turn +database :ref:`query logging ` on. + +The following sections will show you everything there is to know about using and +combining the Query object methods to construct SQL statements and extract data. + +Selecting Data +============== + +Most web applications make heavy use of ``SELECT`` queries. CakePHP makes +building them a snap. To limit the fields fetched, you can use the ``select()`` +method:: + + $query = $articles->find(); + $query->select(['id', 'title', 'body']); + foreach ($query as $row) { + debug($row->title); + } + +You can set aliases for fields by providing fields as an associative array:: + + // Results in SELECT id AS pk, title AS aliased_title, body ... + $query = $articles->find(); + $query->select(['pk' => 'id', 'aliased_title' => 'title', 'body']); + +To select distinct fields, you can use the ``distinct()`` method:: + + // Results in SELECT DISTINCT country FROM ... + $query = $articles->find(); + $query->select(['country']) + ->distinct(['country']); + +To set some basic conditions you can use the ``where()`` method:: + + // Conditions are combined with AND + $query = $articles->find(); + $query->where(['title' => 'First Post', 'published' => true]); + + // You can call where() multiple times + $query = $articles->find(); + $query->where(['title' => 'First Post']) + ->where(['published' => true]); + +See the :ref:`advanced-query-conditions` section to find out how to construct +more complex ``WHERE`` conditions. To apply ordering, you can use the ``order`` +method:: + + $query = $articles->find() + ->order(['title' => 'ASC', 'id' => 'ASC']); + +When calling ``order()`` multiple times on a query, multiple clauses will be appended. +However, when using finders you may sometimes need to overwrite the ``ORDER BY``. +Set the second parameter of ``order()`` (as well as ``orderAsc()`` or ``orderDesc()``) to +``Query::OVERWRITE`` or to ``true``:: + + $query = $articles->find() + ->order(['title' => 'ASC']); + // Later, overwrite the ORDER BY clause instead of appending to it. + $query = $articles->find() + ->order(['created' => 'DESC'], Query::OVERWRITE); + +.. versionadded:: 3.0.12 + + In addition to ``order``, the ``orderAsc`` and ``orderDesc`` methods can be + used when you need to sort on complex expressions:: + + $query = $articles->find(); + $concat = $query->func()->concat([ + 'title' => 'identifier', + 'synopsis' => 'identifier' + ]); + $query->orderAsc($concat); + +To limit the number of rows or set the row offset you can use the ``limit()`` +and ``page()`` methods:: + + // Fetch rows 50 to 100 + $query = $articles->find() + ->limit(50) + ->page(2); + +As you can see from the examples above, all the methods that modify the query +provide a fluent interface, allowing you to build a query through chained method +calls. + +Selecting All Fields From a Table +--------------------------------- + +By default a query will select all fields from a table, the exception is when you +call the ``select()`` function yourself and pass certain fields:: + + // Only select id and title from the articles table + $articles->find()->select(['id', 'title']); + +If you wish to still select all fields from a table after having called +``select($fields)``, you can pass the table instance to ``select()`` for this +purpose:: + + // Only all fields from the articles table including + // a calculated slug field. + $query = $articlesTable->find(); + $query + ->select(['slug' => $query->func()->concat(['title' => 'identifier', '-', 'id' => 'identifier'])]) + ->select($articlesTable); // Select all fields from articles + +.. versionadded:: 3.1 + Passing a table object to select() was added in 3.1. + +.. _using-sql-functions: + +Using SQL Functions +------------------- + +CakePHP's ORM offers abstraction for some commonly used SQL functions. Using the +abstraction allows the ORM to select the platform specific implementation of the +function you want. For example, ``concat`` is implemented differently in MySQL, +PostgreSQL and SQL Server. Using the abstraction allows your code to be +portable:: + + // Results in SELECT COUNT(*) count FROM ... + $query = $articles->find(); + $query->select(['count' => $query->func()->count('*')]); + +A number of commonly used functions can be created with the ``func()`` method: + +- ``sum()`` Calculate a sum. The arguments will be treated as literal values. +- ``avg()`` Calculate an average. The arguments will be treated as literal + values. +- ``min()`` Calculate the min of a column. The arguments will be treated as + literal values. +- ``max()`` Calculate the max of a column. The arguments will be treated as + literal values. +- ``count()`` Calculate the count. The arguments will be treated as literal + values. +- ``concat()`` Concatenate two values together. The arguments are treated as + bound parameters unless marked as literal. +- ``coalesce()`` Coalesce values. The arguments are treated as bound parameters + unless marked as literal. +- ``dateDiff()`` Get the difference between two dates/times. The arguments are + treated as bound parameters unless marked as literal. +- ``now()`` Take either 'time' or 'date' as an argument allowing you to get + either the current time, or current date. +- ``extract()`` Returns the specified date part from the SQL expression. +- ``dateAdd()`` Add the time unit to the date expression. +- ``dayOfWeek()`` Returns a FunctionExpression representing a call to SQL + WEEKDAY function. + +.. versionadded:: 3.1 + + ``extract()``, ``dateAdd()`` and ``dayOfWeek()`` methods have been added. + +When providing arguments for SQL functions, there are two kinds of parameters +you can use, literal arguments and bound parameters. Identifier/Literal parameters allow +you to reference columns or other SQL literals. Bound parameters can be used to +safely add user data to SQL functions. For example:: + + $query = $articles->find()->innerJoinWith('Categories'); + $concat = $query->func()->concat([ + 'Articles.title' => 'identifier', + ' - CAT: ', + 'Categories.name' => 'identifier', + ' - Age: ', + '(DATEDIFF(NOW(), Articles.created))' => 'literal', + ]); + $query->select(['link_title' => $concat]); + +By making arguments with a value of ``literal``, the ORM will know that +the key should be treated as a literal SQL value. By making arguments with +a value of ``identifier``, the ORM will know that the key should be treated +as a field identifier. The above would generate the following SQL on MySQL:: + + SELECT CONCAT(Articles.title, :c0, Categories.name, :c1, (DATEDIFF(NOW(), Articles.created))) FROM articles; + +The ``:c0`` value will have the ``' - CAT:'`` text bound when the query is +executed. + +In addition to the above functions, the ``func()`` method can be used to create +any generic SQL function such as ``year``, ``date_format``, ``convert``, etc. +For example:: + + $query = $articles->find(); + $year = $query->func()->year([ + 'created' => 'identifier' + ]); + $time = $query->func()->date_format([ + 'created' => 'identifier', + "'%H:%i'" => 'literal' + ]); + $query->select([ + 'yearCreated' => $year, + 'timeCreated' => $time + ]); + +Would result in:: + + SELECT YEAR(created) as yearCreated, DATE_FORMAT(created, '%H:%i') as timeCreated FROM articles; + +You should remember to use the function builder whenever you need to put +untrusted data into SQL functions or stored procedures:: + + // Use a stored procedure + $query = $articles->find(); + $lev = $query->func()->levenshtein([$search, 'LOWER(title)' => 'literal']); + $query->where(function ($exp) use ($lev) { + return $exp->between($lev, 0, $tolerance); + }); + + // Generated SQL would be + WHERE levenshtein(:c0, lower(street)) BETWEEN :c1 AND :c2 + +Aggregates - Group and Having +----------------------------- + +When using aggregate functions like ``count`` and ``sum`` you may want to use +``group by`` and ``having`` clauses:: + + $query = $articles->find(); + $query->select([ + 'count' => $query->func()->count('view_count'), + 'published_date' => 'DATE(created)' + ]) + ->group('published_date') + ->having(['count >' => 3]); + +Case statements +--------------- + +The ORM also offers the SQL ``case`` expression. The ``case`` expression allows +for implementing ``if ... then ... else`` logic inside your SQL. This can be useful +for reporting on data where you need to conditionally sum or count data, or where you +need to specific data based on a condition. + +If we wished to know how many published articles are in our database, we could use the following SQL:: + + SELECT + COUNT(CASE WHEN published = 'Y' THEN 1 END) AS number_published, + COUNT(CASE WHEN published = 'N' THEN 1 END) AS number_unpublished + FROM articles + +To do this with the query builder, we'd use the following code:: + + $query = $articles->find(); + $publishedCase = $query->newExpr() + ->addCase( + $query->newExpr()->add(['published' => 'Y']), + 1, + 'integer' + ); + $unpublishedCase = $query->newExpr() + ->addCase( + $query->newExpr()->add(['published' => 'N']), + 1, + 'integer' + ); + + $query->select([ + 'number_published' => $query->func()->count($publishedCase), + 'number_unpublished' => $query->func()->count($unpublishedCase) + ]); + +The ``addCase`` function can also chain together multiple statements to create +``if .. then .. [elseif .. then .. ] [ .. else ]`` logic inside your SQL. + +If we wanted to classify cities into SMALL, MEDIUM, or LARGE based on population +size, we could do the following:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->addCase( + [ + $q->newExpr()->lt('population', 100000), + $q->newExpr()->between('population', 100000, 999000), + $q->newExpr()->gte('population', 999001), + ], + ['SMALL', 'MEDIUM', 'LARGE'], # values matching conditions + ['string', 'string', 'string'] # type of each value + ); + }); + # WHERE CASE + # WHEN population < 100000 THEN 'SMALL' + # WHEN population BETWEEN 100000 AND 999000 THEN 'MEDIUM' + # WHEN population >= 999001 THEN 'LARGE' + # END + +Any time there are fewer case conditions than values, ``addCase`` will +automatically produce an ``if .. then .. else`` statement:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->addCase( + [ + $q->newExpr()->eq('population', 0), + ], + ['DESERTED', 'INHABITED'], # values matching conditions + ['string', 'string'] # type of each value + ); + }); + # WHERE CASE + # WHEN population = 0 THEN 'DESERTED' ELSE 'INHABITED' END + +Getting Arrays Instead of Entities +---------------------------------- + +While ORMs and object result sets are powerful, creating entities is sometimes +unnecessary. For example, when accessing aggregated data, building an Entity may +not make sense. The process of converting the database results to entities is +called hydration. If you wish to disable this process you can do this:: + + $query = $articles->find(); + $query->hydrate(false); // Results as arrays instead of entities + $result = $query->toList(); // Execute the query and return the array + +After executing those lines, your result should look similar to this:: + + [ + ['id' => 1, 'title' => 'First Article', 'body' => 'Article 1 body' ...], + ['id' => 2, 'title' => 'Second Article', 'body' => 'Article 2 body' ...], + ... + ] + +.. _format-results: + +Adding Calculated Fields +------------------------ + +After your queries, you may need to do some post-processing. If you need to add +a few calculated fields or derived data, you can use the ``formatResults()`` +method. This is a lightweight way to map over the result sets. If you need more +control over the process, or want to reduce results you should use +the :ref:`Map/Reduce ` feature instead. If you were querying a list +of people, you could calculate their age with a result formatter:: + + // Assuming we have built the fields, conditions and containments. + $query->formatResults(function (\Cake\Collection\CollectionInterface $results) { + return $results->map(function ($row) { + $row['age'] = $row['birth_date']->diff(new \DateTime)->y; + return $row; + }); + }); + +As you can see in the example above, formatting callbacks will get a +``ResultSetDecorator`` as their first argument. The second argument will be +the Query instance the formatter was attached to. The ``$results`` argument can +be traversed and modified as necessary. + +Result formatters are required to return an iterator object, which will be used +as the return value for the query. Formatter functions are applied after all the +Map/Reduce routines have been executed. Result formatters can be applied from +within contained associations as well. CakePHP will ensure that your formatters +are properly scoped. For example, doing the following would work as you may +expect:: + + // In a method in the Articles table + $query->contain(['Authors' => function ($q) { + return $q->formatResults(function (\Cake\Collection\CollectionInterface $authors) { + return $authors->map(function ($author) { + $author['age'] = $author['birth_date']->diff(new \DateTime)->y; + return $author; + }); + }); + }]); + + // Get results + $results = $query->all(); + + // Outputs 29 + echo $results->first()->author->age; + +As seen above, the formatters attached to associated query builders are scoped +to operate only on the data in the association. CakePHP will ensure that +computed values are inserted into the correct entity. + +.. _advanced-query-conditions: + +Advanced Conditions +=================== + +The query builder makes it simple to build complex ``where`` clauses. +Grouped conditions can be expressed by providing combining ``where()``, +``andWhere()`` and ``orWhere()``. The ``where()`` method works similar to the +conditions arrays in previous versions of CakePHP:: + + $query = $articles->find() + ->where([ + 'author_id' => 3, + 'OR' => [['view_count' => 2], ['view_count' => 3]], + ]); + +The above would generate SQL like:: + + SELECT * FROM articles WHERE author_id = 3 AND (view_count = 2 OR view_count = 3) + +If you'd prefer to avoid deeply nested arrays, you can use the ``orWhere()`` and +``andWhere()`` methods to build your queries. Each method sets the combining +operator used between the current and previous condition. For example:: + + $query = $articles->find() + ->where(['author_id' => 2]) + ->orWhere(['author_id' => 3]); + +The above will output SQL similar to:: + + SELECT * FROM articles WHERE (author_id = 2 OR author_id = 3) + +By combining ``orWhere()`` and ``andWhere()``, you can express complex +conditions that use a mixture of operators:: + + $query = $articles->find() + ->where(['author_id' => 2]) + ->orWhere(['author_id' => 3]) + ->andWhere([ + 'published' => true, + 'view_count >' => 10 + ]) + ->orWhere(['promoted' => true]); + +The above generates SQL similar to:: + + SELECT * + FROM articles + WHERE ( + ( + (author_id = 2 OR author_id = 3) + AND + (published = 1 AND view_count > 10) + ) + OR promoted = 1 + ) + +By using functions as the parameters to ``orWhere()`` and ``andWhere()``, +you can compose conditions together with the expression objects:: + + $query = $articles->find() + ->where(['title LIKE' => '%First%']) + ->andWhere(function ($exp) { + return $exp->or_([ + 'author_id' => 2, + 'is_highlighted' => true + ]); + }); + +The above would create SQL like:: + + SELECT * + FROM articles + WHERE ( + title LIKE '%First%' + AND + (author_id = 2 OR is_highlighted = 1) + ) + +The expression object that is passed into ``where()`` functions has two kinds of +methods. The first type of methods are **combinators**. The ``and_()`` and +``or_()`` methods create new expression objects that change **how** conditions +are combined. The second type of methods are **conditions**. Conditions are +added into an expression where they are combined with the current combinator. + +For example, calling ``$exp->and_(...)`` will create a new ``Expression`` object +that combines all conditions it contains with ``AND``. While ``$exp->or_()`` +will create a new ``Expression`` object that combines all conditions added to it +with ``OR``. An example of adding conditions with an ``Expression`` object would +be:: + + $query = $articles->find() + ->where(function ($exp) { + return $exp + ->eq('author_id', 2) + ->eq('published', true) + ->notEq('spam', true) + ->gt('view_count', 10); + }); + +Since we started off using ``where()``, we don't need to call ``and_()``, as +that happens implicitly. The above shows a few new condition +methods being combined with ``AND``. The resulting SQL would look like:: + + SELECT * + FROM articles + WHERE ( + author_id = 2 + AND published = 1 + AND spam != 1 + AND view_count > 10) + +.. deprecated:: 3.5.0 + As of 3.5.0 the ``orWhere()`` method is deprecated. This method creates + hard to predict SQL based on the current query state. + Use ``where()`` instead as it has more predicatable and easier + to understand behavior. + +However, if we wanted to use both ``AND`` & ``OR`` conditions we could do the +following:: + + $query = $articles->find() + ->where(function ($exp) { + $orConditions = $exp->or_(['author_id' => 2]) + ->eq('author_id', 5); + return $exp + ->add($orConditions) + ->eq('published', true) + ->gte('view_count', 10); + }); + +Which would generate the SQL similar to:: + + SELECT * + FROM articles + WHERE ( + (author_id = 2 OR author_id = 5) + AND published = 1 + AND view_count >= 10) + +The ``or_()`` and ``and_()`` methods also allow you to use functions as their +parameters. This is often easier to read than method chaining:: + + $query = $articles->find() + ->where(function ($exp) { + $orConditions = $exp->or_(function ($or) { + return $or->eq('author_id', 2) + ->eq('author_id', 5); + }); + return $exp + ->not($orConditions) + ->lte('view_count', 10); + }); + +You can negate sub-expressions using ``not()``:: + + $query = $articles->find() + ->where(function ($exp) { + $orConditions = $exp->or_(['author_id' => 2]) + ->eq('author_id', 5); + return $exp + ->not($orConditions) + ->lte('view_count', 10); + }); + +Which will generate the following SQL looking like:: + + SELECT * + FROM articles + WHERE ( + NOT (author_id = 2 OR author_id = 5) + AND view_count <= 10) + +It is also possible to build expressions using SQL functions:: + + $query = $articles->find() + ->where(function ($exp, $q) { + $year = $q->func()->year([ + 'created' => 'identifier' + ]); + return $exp + ->gte($year, 2014) + ->eq('published', true); + }); + +Which will generate the following SQL looking like:: + + SELECT * + FROM articles + WHERE ( + YEAR(created) >= 2014 + AND published = 1 + ) + +When using the expression objects you can use the following methods to create +conditions: + +- ``eq()`` Creates an equality condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->eq('population', '10000'); + }); + # WHERE population = 10000 + +- ``notEq()`` Creates an inequality condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->notEq('population', '10000'); + }); + # WHERE population != 10000 + +- ``like()`` Creates a condition using the ``LIKE`` operator:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->like('name', '%A%'); + }); + # WHERE name LIKE "%A%" + +- ``notLike()`` Creates a negated ``LIKE`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->notLike('name', '%A%'); + }); + # WHERE name NOT LIKE "%A%" + +- ``in()`` Create a condition using ``IN``:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->in('country_id', ['AFG', 'USA', 'EST']); + }); + # WHERE country_id IN ('AFG', 'USA', 'EST') + +- ``notIn()`` Create a negated condition using ``IN``:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->notIn('country_id', ['AFG', 'USA', 'EST']); + }); + # WHERE country_id NOT IN ('AFG', 'USA', 'EST') + +- ``gt()`` Create a ``>`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->gt('population', '10000'); + }); + # WHERE population > 10000 + +- ``gte()`` Create a ``>=`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->gte('population', '10000'); + }); + # WHERE population >= 10000 + +- ``lt()`` Create a ``<`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->lt('population', '10000'); + }); + # WHERE population < 10000 + +- ``lte()`` Create a ``<=`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->lte('population', '10000'); + }); + # WHERE population <= 10000 + +- ``isNull()`` Create an ``IS NULL`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->isNull('population'); + }); + # WHERE (population) IS NULL + +- ``isNotNull()`` Create a negated ``IS NULL`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->isNotNull('population'); + }); + # WHERE (population) IS NOT NULL + +- ``between()`` Create a ``BETWEEN`` condition:: + + $query = $cities->find() + ->where(function ($exp, $q) { + return $exp->between('population', 999, 5000000); + }); + # WHERE population BETWEEN 999 AND 5000000, + +- ``exists()`` Create a condition using ``EXISTS``:: + + $subquery = $cities->find() + ->select(['id']) + ->where(function ($exp, $q) { + return $exp->equalFields('countries.id', 'cities.country_id'); + }) + ->andWhere(['population >', 5000000]); + + $query = $countries->find() + ->where(function ($exp, $q) use ($subquery) { + return $exp->exists($subquery); + }); + # WHERE EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) + +- ``notExists()`` Create a negated condition using ``EXISTS``:: + + $subquery = $cities->find() + ->select(['id']) + ->where(function ($exp, $q) { + return $exp->equalFields('countries.id', 'cities.country_id'); + }) + ->andWhere(['population >', 5000000]); + + $query = $countries->find() + ->where(function ($exp, $q) use ($subquery) { + return $exp->notExists($subquery); + }); + # WHERE NOT EXISTS (SELECT id FROM cities WHERE countries.id = cities.country_id AND population > 5000000) + +In situations when you can't get, or don't want to use the builder methods to +create the conditions you want you can also use snippets of SQL in where +clauses:: + + // Compare two fields to each other + $query->where(['Categories.parent_id != Parents.id']); + +.. warning:: + + The field names used in expressions, and SQL snippets should **never** + contain untrusted content. See the :ref:`using-sql-functions` section for + how to safely include unsafe data into function calls. + +Automatically Creating IN Clauses +--------------------------------- + +When building queries using the ORM, you will generally not have to indicate the +data types of the columns you are interacting with, as CakePHP can infer the +types based on the schema data. If in your queries you'd like CakePHP to +automatically convert equality to ``IN`` comparisons, you'll need to indicate +the column data type:: + + $query = $articles->find() + ->where(['id' => $ids], ['id' => 'integer[]']); + + // Or include IN to automatically cast to an array. + $query = $articles->find() + ->where(['id IN' => $ids]); + +The above will automatically create ``id IN (...)`` instead of ``id = ?``. This +can be useful when you do not know whether you will get a scalar or array of +parameters. The ``[]`` suffix on any data type name indicates to the query +builder that you want the data handled as an array. If the data is not an array, +it will first be cast to an array. After that, each value in the array will +be cast using the :ref:`type system `. This works with +complex types as well. For example, you could take a list of DateTime objects +using:: + + $query = $articles->find() + ->where(['post_date' => $dates], ['post_date' => 'date[]']); + +Automatic IS NULL Creation +-------------------------- + +When a condition value is expected to be ``null`` or any other value, you can +use the ``IS`` operator to automatically create the correct expression:: + + $query = $categories->find() + ->where(['parent_id IS' => $parentId]); + +The above will create ``parent_id` = :c1`` or ``parent_id IS NULL`` depending on +the type of ``$parentId`` + +Automatic IS NOT NULL Creation +------------------------------ + +When a condition value is expected not to be ``null`` or any other value, you +can use the ``IS NOT`` operator to automatically create the correct expression:: + + $query = $categories->find() + ->where(['parent_id IS NOT' => $parentId]); + +The above will create ``parent_id` != :c1`` or ``parent_id IS NOT NULL`` +depending on the type of ``$parentId`` + +Raw Expressions +--------------- + +When you cannot construct the SQL you need using the query builder, you can use +expression objects to add snippets of SQL to your queries:: + + $query = $articles->find(); + $expr = $query->newExpr()->add('1 + 1'); + $query->select(['two' => $expr]); + +``Expression`` objects can be used with any query builder methods like +``where()``, ``limit()``, ``group()``, ``select()`` and many other methods. + +.. warning:: + + Using expression objects leaves you vulnerable to SQL injection. You should + avoid interpolating user data into expressions. + +Getting Results +=============== + +Once you've made your query, you'll want to retrieve rows from it. There are +a few ways of doing this:: + + // Iterate the query + foreach ($query as $row) { + // Do stuff. + } + + // Get the results + $results = $query->all(); + +You can use :doc:`any of the collection ` methods +on your query objects to pre-process or transform the results:: + + // Use one of the collection methods. + $ids = $query->map(function ($row) { + return $row->id; + }); + + $maxAge = $query->max(function ($max) { + return $max->age; + }); + +You can use ``first`` or ``firstOrFail`` to retrieve a single record. These +methods will alter the query adding a ``LIMIT 1`` clause:: + + // Get just the first row + $row = $query->first(); + + // Get the first row or an exception. + $row = $query->firstOrFail(); + +.. _query-count: + +Returning the Total Count of Records +------------------------------------ + +Using a single query object, it is possible to obtain the total number of rows +found for a set of conditions:: + + $total = $articles->find()->where(['is_active' => true])->count(); + +The ``count()`` method will ignore the ``limit``, ``offset`` and ``page`` +clauses, thus the following will return the same result:: + + $total = $articles->find()->where(['is_active' => true])->limit(10)->count(); + +This is useful when you need to know the total result set size in advance, +without having to construct another ``Query`` object. Likewise, all result +formatting and map-reduce routines are ignored when using the ``count()`` +method. + +Moreover, it is possible to return the total count for a query containing group +by clauses without having to rewrite the query in any way. For example, consider +this query for retrieving article ids and their comments count:: + + $query = $articles->find(); + $query->select(['Articles.id', $query->func()->count('Comments.id')]) + ->matching('Comments') + ->group(['Articles.id']); + $total = $query->count(); + +After counting, the query can still be used for fetching the associated +records:: + + $list = $query->all(); + +Sometimes, you may want to provide an alternate method for counting the total +records of a query. One common use case for this is providing +a cached value or an estimate of the total rows, or to alter the query to remove +expensive unneeded parts such as left joins. This becomes particularly handy +when using the CakePHP built-in pagination system which calls the ``count()`` +method:: + + $query = $query->where(['is_active' => true])->counter(function ($query) { + return 100000; + }); + $query->count(); // Returns 100000 + +In the example above, when the pagination component calls the count method, it +will receive the estimated hard-coded number of rows. + +.. _caching-query-results: + +Caching Loaded Results +---------------------- + +When fetching entities that don't change often you may want to cache the +results. The ``Query`` class makes this simple:: + + $query->cache('recent_articles'); + +Will enable caching on the query's result set. If only one argument is provided +to ``cache()`` then the 'default' cache configuration will be used. You can +control which caching configuration is used with the second parameter:: + + // String config name. + $query->cache('recent_articles', 'dbResults'); + + // Instance of CacheEngine + $query->cache('recent_articles', $memcache); + +In addition to supporting static keys, the ``cache()`` method accepts a function +to generate the key. The function you give it will receive the query as an +argument. You can then read aspects of the query to dynamically generate the +cache key:: + + // Generate a key based on a simple checksum + // of the query's where clause + $query->cache(function ($q) { + return 'articles-' . md5(serialize($q->clause('where'))); + }); + +The cache method makes it simple to add cached results to your custom finders or +through event listeners. + +When the results for a cached query are fetched the following happens: + +1. The ``Model.beforeFind`` event is triggered. +2. If the query has results set, those will be returned. +3. The cache key will be resolved and cache data will be read. If the cache data + is not empty, those results will be returned. +4. If the cache misses, the query will be executed and a new ``ResultSet`` will be + created. This ``ResultSet`` will be written to the cache and returned. + +.. note:: + + You cannot cache a streaming query result. + +Loading Associations +==================== + +The builder can help you retrieve data from multiple tables at the same time +with the minimum amount of queries possible. To be able to fetch associated +data, you first need to setup associations between the tables as described in +the :doc:`/orm/associations` section. This technique of combining queries +to fetch associated data from other tables is called **eager loading**. + +.. include:: ./retrieving-data-and-resultsets.rst + :start-after: start-contain + :end-before: end-contain + +Filtering by Associated Data +---------------------------- + +.. include:: ./retrieving-data-and-resultsets.rst + :start-after: start-filtering + :end-before: end-filtering + +.. _adding-joins: + +Adding Joins +------------ + +In addition to loading related data with ``contain()``, you can also add +additional joins with the query builder:: + + $query = $articles->find() + ->hydrate(false) + ->join([ + 'table' => 'comments', + 'alias' => 'c', + 'type' => 'LEFT', + 'conditions' => 'c.article_id = articles.id', + ]); + +You can append multiple joins at the same time by passing an associative array +with multiple joins:: + + $query = $articles->find() + ->hydrate(false) + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => 'c.article_id = articles.id', + ], + 'u' => [ + 'table' => 'users', + 'type' => 'INNER', + 'conditions' => 'u.id = articles.user_id', + ] + ]); + +As seen above, when adding joins the alias can be the outer array key. Join +conditions can also be expressed as an array of conditions:: + + $query = $articles->find() + ->hydrate(false) + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => [ + 'c.created >' => new DateTime('-5 days'), + 'c.moderated' => true, + 'c.article_id = articles.id' + ] + ], + ], ['c.created' => 'datetime', 'c.moderated' => 'boolean']); + +When creating joins by hand and using array based conditions, you need to +provide the datatypes for each column in the join conditions. By providing +datatypes for the join conditions, the ORM can correctly convert data types into +SQL. In addition to ``join()`` you can use ``rightJoin()``, ``leftJoin()`` and +``innerJoin()`` to create joins:: + + // Join with an alias and string conditions + $query = $articles->find(); + $query->leftJoin( + ['Authors' => 'authors'], + ['Authors.id = Articles.author_id']); + + // Join with an alias, array conditions, and types + $query = $articles->find(); + $query->innerJoin( + ['Authors' => 'authors'], + [ + 'Authors.promoted' => true, + 'Authors.created' => new DateTime('-5 days'), + 'Authors.id = Articles.author_id' + ], + ['Authors.promoted' => 'boolean', 'Authors.created' => 'datetime']); + +It should be noted that if you set the ``quoteIdentifiers`` option to ``true`` when +defining your ``Connection``, join conditions between table fields should be set as follow:: + + $query = $articles->find() + ->join([ + 'c' => [ + 'table' => 'comments', + 'type' => 'LEFT', + 'conditions' => [ + 'c.article_id' => new \Cake\Database\Expression\IdentifierExpression('articles.id') + ] + ], + ]); + +This ensures that all of your identifiers will be quoted across the Query, avoiding errors with +some database Drivers (PostgreSQL notably) + +Inserting Data +============== + +Unlike earlier examples, you should not use ``find()`` to create insert queries. +Instead, create a new ``Query`` object using ``query()``:: + + $query = $articles->query(); + $query->insert(['title', 'body']) + ->values([ + 'title' => 'First post', + 'body' => 'Some body text' + ]) + ->execute(); + +To insert multiple rows with only one query, you can chain the ``values()`` +method as many times as you need:: + + $query = $articles->query(); + $query->insert(['title', 'body']) + ->values([ + 'title' => 'First post', + 'body' => 'Some body text' + ]) + ->values([ + 'title' => 'Second post', + 'body' => 'Another body text' + ]) + ->execute(); + +Generally, it is easier to insert data using entities and +:php:meth:`~Cake\\ORM\\Table::save()`. By composing a ``SELECT`` and +``INSERT`` query together, you can create ``INSERT INTO ... SELECT`` style +queries:: + + $select = $articles->find() + ->select(['title', 'body', 'published']) + ->where(['id' => 3]); + + $query = $articles->query() + ->insert(['title', 'body', 'published']) + ->values($select) + ->execute(); + +.. note:: + Inserting records with the query builder will not trigger events such as + ``Model.afterSave``. Instead you should use the :doc:`ORM to save + data `. + +.. _query-builder-updating-data: + +Updating Data +============= + +As with insert queries, you should not use ``find()`` to create update queries. +Instead, create new a ``Query`` object using ``query()``:: + + $query = $articles->query(); + $query->update() + ->set(['published' => true]) + ->where(['id' => $id]) + ->execute(); + +Generally, it is easier to update data using entities and +:php:meth:`~Cake\\ORM\\Table::patchEntity()`. + +.. note:: + Updating records with the query builder will not trigger events such as + ``Model.afterSave``. Instead you should use the :doc:`ORM to save + data `. + +Deleting Data +============= + +As with insert queries, you should not use ``find()`` to create delete queries. +Instead, create new a query object using ``query()``:: + + $query = $articles->query(); + $query->delete() + ->where(['id' => $id]) + ->execute(); + +Generally, it is easier to delete data using entities and +:php:meth:`~Cake\\ORM\\Table::delete()`. + +SQL Injection Prevention +======================== + +While the ORM and database abstraction layers prevent most SQL injections +issues, it is still possible to leave yourself vulnerable through improper use. + +When using condition arrays, the key/left-hand side as well as single value +entries must not contain user data:: + + $query->where([ + // Data on the key/left-hand side is unsafe, as it will be + // inserted into the generated query as-is + $userData => $value, + + // The same applies to single value entries, they are not + // safe to use with user data in any form + $userData, + "MATCH (comment) AGAINST ($userData)", + 'created < NOW() - ' . $userData + ]); + +When using the expression builder, column names must not contain user data:: + + $query->where(function ($exp) use ($userData, $values) { + // Column names in all expressions are not safe. + return $exp->in($userData, $values); + }); + +When building function expressions, function names should never contain user +data:: + + // Not safe. + $query->func()->{$userData}($arg1); + + // Also not safe to use an array of + // user data in a function expression + $query->func()->coalesce($userData); + +Raw expressions are never safe:: + + $expr = $query->newExpr()->add($userData); + $query->select(['two' => $expr]); + +Binding values +-------------- + +It is possible to protect against many unsafe situations by using bindings. +Similar to :ref:`binding values to prepared statements `, +values can be bound to queries using the :php:meth:`Cake\\Database\\Query::bind()` +method. + +The following example would be a safe variant of the unsafe, SQL injection prone +example given above:: + + $query + ->where([ + 'MATCH (comment) AGAINST (:userData)', + 'created < NOW() - :moreUserData' + ]) + ->bind(':userData', $userData, 'string') + ->bind(':moreUserData', $moreUserData, 'datetime'); + +.. note:: + + Unlike :php:meth:`Cake\\Database\\StatementInterface::bindValue()`, + ``Query::bind()`` requires to pass the named placeholders including the + colon! + +More Complex Queries +==================== + +The query builder is capable of building complex queries like ``UNION`` queries +and sub-queries. + +Unions +------ + +Unions are created by composing one or more select queries together:: + + $inReview = $articles->find() + ->where(['need_review' => true]); + + $unpublished = $articles->find() + ->where(['published' => false]); + + $unpublished->union($inReview); + +You can create ``UNION ALL`` queries using the ``unionAll()`` method:: + + $inReview = $articles->find() + ->where(['need_review' => true]); + + $unpublished = $articles->find() + ->where(['published' => false]); + + $unpublished->unionAll($inReview); + +Subqueries +---------- + +Subqueries are a powerful feature in relational databases and building them in +CakePHP is fairly intuitive. By composing queries together, you can make +subqueries:: + + $matchingComment = $articles->association('Comments')->find() + ->select(['article_id']) + ->distinct() + ->where(['comment LIKE' => '%CakePHP%']); + + $query = $articles->find() + ->where(['id IN' => $matchingComment]); + +Subqueries are accepted anywhere a query expression can be used. For example, in +the ``select()`` and ``join()`` methods. + +Adding Locking Statements +------------------------- + +Most relational database vendors support taking out locks when doing select +operations. You can use the ``epilog()`` method for this:: + + // In MySQL + $query->epilog('FOR UPDATE'); + +The ``epilog()`` method allows you to append raw SQL to the end of queries. You +should never put raw user data into ``epilog()``. + +Executing Complex Queries +------------------------- + +While the query builder makes it easy to build most queries, very complex +queries can be tedious and complicated to build. You may want to :ref:`execute +the desired SQL directly `. + +Executing SQL directly allows you to fine tune the query that will be run. +However, doing so doesn't let you use ``contain`` or other higher level ORM +features. diff --git a/tl/orm/retrieving-data-and-resultsets.rst b/tl/orm/retrieving-data-and-resultsets.rst new file mode 100644 index 0000000000000000000000000000000000000000..38672cfb8ebf7f434ba8fcd8ed1f9b4ca15f68c1 --- /dev/null +++ b/tl/orm/retrieving-data-and-resultsets.rst @@ -0,0 +1,1302 @@ +Retrieving Data & Results Sets +############################## + +.. php:namespace:: Cake\ORM + +.. php:class:: Table + +While table objects provide an abstraction around a 'repository' or collection +of objects, when you query for individual records you get 'entity' objects. +While this section discusses the different ways you can find and load entities, +you should read the :doc:`/orm/entities` section for more information on +entities. + +Debugging Queries and ResultSets +================================ + +Since the ORM now returns Collections and Entities, debugging these objects can +be more complicated than in previous CakePHP versions. There are now various +ways to inspect the data returned by the ORM. + +- ``debug($query)`` Shows the SQL and bound params, does not show results. +- ``debug($query->all())`` Shows the ResultSet properties (not the results). +- ``debug($query->toArray())`` An easy way to show each of the results. +- ``debug(iterator_to_array($query))`` Shows query results in an array format. +- ``debug(json_encode($query, JSON_PRETTY_PRINT))`` More human readable results. +- ``debug($query->first())`` Show the properties of a single entity. +- ``debug((string)$query->first())`` Show the properties of a single entity as JSON. + +Getting a Single Entity by Primary Key +====================================== + +.. php:method:: get($id, $options = []) + +It is often convenient to load a single entity from the database when editing or +viewing entities and their related data. You can do this by using ``get()``:: + + // In a controller or table method. + + // Get a single article + $article = $articles->get($id); + + // Get a single article, and related comments + $article = $articles->get($id, [ + 'contain' => ['Comments'] + ]); + +If the get operation does not find any results a +``Cake\Datasource\Exception\RecordNotFoundException`` will be raised. You can +either catch this exception yourself, or allow CakePHP to convert it into a 404 +error. + +Like ``find()``, ``get()`` also has caching integrated. You can use the +``cache`` option when calling ``get()`` to perform read-through caching:: + + // In a controller or table method. + + // Use any cache config or CacheEngine instance & a generated key + $article = $articles->get($id, [ + 'cache' => 'custom', + ]); + + // Use any cache config or CacheEngine instance & specific key + $article = $articles->get($id, [ + 'cache' => 'custom', 'key' => 'mykey' + ]); + + // Explicitly disable caching + $article = $articles->get($id, [ + 'cache' => false + ]); + +Optionally you can ``get()`` an entity using :ref:`custom-find-methods`. For +example you may want to get all translations for an entity. You can achieve that +by using the ``finder`` option:: + + $article = $articles->get($id, [ + 'finder' => 'translations', + ]); + +Using Finders to Load Data +========================== + +.. php:method:: find($type, $options = []) + +Before you can work with entities, you'll need to load them. The easiest way to +do this is using the ``find()`` method. The find method provides an easy and +extensible way to find the data you are interested in:: + + // In a controller or table method. + + // Find all the articles + $query = $articles->find('all'); + +The return value of any ``find()`` method is always +a :php:class:`Cake\\ORM\\Query` object. The Query class allows you to further +refine a query after creating it. Query objects are evaluated lazily, and do not +execute until you start fetching rows, convert it to an array, or when the +``all()`` method is called:: + + // In a controller or table method. + + // Find all the articles. + // At this point the query has not run. + $query = $articles->find('all'); + + // Iteration will execute the query. + foreach ($query as $row) { + } + + // Calling all() will execute the query + // and return the result set. + $results = $query->all(); + + // Once we have a result set we can get all the rows + $data = $results->toArray(); + + // Converting the query to an array will execute it. + $data = $query->toArray(); + +.. note:: + + Once you've started a query you can use the :doc:`/orm/query-builder` + interface to build more complex queries, adding additional conditions, + limits, or include associations using the fluent interface. + +:: + + // In a controller or table method. + $query = $articles->find('all') + ->where(['Articles.created >' => new DateTime('-10 days')]) + ->contain(['Comments', 'Authors']) + ->limit(10); + +You can also provide many commonly used options to ``find()``. This can help +with testing as there are fewer methods to mock:: + + // In a controller or table method. + $query = $articles->find('all', [ + 'conditions' => ['Articles.created >' => new DateTime('-10 days')], + 'contain' => ['Authors', 'Comments'], + 'limit' => 10 + ]); + +The list of options supported by find() are: + +- ``conditions`` provide conditions for the WHERE clause of your query. +- ``limit`` Set the number of rows you want. +- ``offset`` Set the page offset you want. You can also use ``page`` to make + the calculation simpler. +- ``contain`` define the associations to eager load. +- ``fields`` limit the fields loaded into the entity. Only loading some fields + can cause entities to behave incorrectly. +- ``group`` add a GROUP BY clause to your query. This is useful when using + aggregating functions. +- ``having`` add a HAVING clause to your query. +- ``join`` define additional custom joins. +- ``order`` order the result set. + +Any options that are not in this list will be passed to beforeFind listeners +where they can be used to modify the query object. You can use the +``getOptions()`` method on a query object to retrieve the options used. While +you can pass query objects to your controllers, we recommend that you package +your queries up as :ref:`custom-find-methods` instead. Using custom finder +methods will let you re-use your queries and make testing easier. + +By default queries and result sets will return :doc:`/orm/entities` objects. You +can retrieve basic arrays by disabling hydration:: + + $query->enableHydration(false); + // Prior to 3.4.0 + $query->hydrate(false); + + // $data is ResultSet that contains array data. + $data = $query->all(); + +.. _table-find-first: + +Getting the First Result +======================== + +The ``first()`` method allows you to fetch only the first row from a query. If +the query has not been executed, a ``LIMIT 1`` clause will be applied:: + + // In a controller or table method. + $query = $articles->find('all', [ + 'order' => ['Articles.created' => 'DESC'] + ]); + $row = $query->first(); + +This approach replaces ``find('first')`` in previous versions of CakePHP. You +may also want to use the ``get()`` method if you are loading entities by primary +key. + +.. note:: + + The ``first()`` method will return ``null`` if no results are found. + +Getting a Count of Results +========================== + +Once you have created a query object, you can use the ``count()`` method to get +a result count of that query:: + + // In a controller or table method. + $query = $articles->find('all', [ + 'conditions' => ['Articles.title LIKE' => '%Ovens%'] + ]); + $number = $query->count(); + +See :ref:`query-count` for additional usage of the ``count()`` method. + +.. _table-find-list: + +Finding Key/Value Pairs +======================= + +It is often useful to generate an associative array of data from your +application's data. For example, this is very useful when creating `` + +.. note:: + + Since this is an *edit* form, a hidden ``input`` field is generated to + override the default HTTP method. + +Options for Form Creation +------------------------- + +The ``$options`` array is where most of the form configuration +happens. This special array can contain a number of different +key-value pairs that affect the way the form tag is generated. +Valid values: + +* ``'type'`` - Allows you to choose the type of form to create. If no type is + provided then it will be autodetected based on the form context. + Valid values: + + * ``'get'`` - Will set the form method to HTTP GET. + * ``'file'`` - Will set the form method to POST and the ``'enctype'`` to + "multipart/form-data". + * ``'post'`` - Will set the method to POST. + * ``'put', 'delete', 'patch'`` - Will override the HTTP method with PUT, + DELETE or PATCH respectively, when the form is submitted. + +* ``'method'`` - Valid values are the same as above. Allows you to explicitly + override the form's method. + +* ``'url'`` - Specify the URL the form will submit to. Can be a string or a URL + array. + +* ``'encoding'`` - Sets the ``accept-charset`` encoding for the form. Defaults + to ``Configure::read('App.encoding')``. + +* ``'enctype'`` - Allows you to set the form encoding explicitly. + +* ``'templates'`` - The templates you want to use for this form. Any templates + provided will be merged on top of the already loaded templates. Can be either + a filename (without extension) from ``/config`` or an array of templates to use. + +* ``'context'`` - Additional options for the form context class. (For example + the ``EntityContext`` accepts a ``'table'`` option that allows you to set the + specific Table class the form should be based on.) + +* ``'idPrefix'`` - Prefix for generated ID attributes. + +* ``'templateVars'`` - Allows you to provide template variables for the + ``formStart`` template. + +.. tip:: + + Besides the above options you can provide, in the ``$options`` argument, + any valid HTML attributes that you want to pass to the created ``form`` + element. + +.. _form-values-from-query-string: + +Getting form values from the query string +----------------------------------------- + +.. versionadded:: 3.4.0 + +A FormHelper's values sources define where its rendered elements, such as +input-tags, receive their values from. + +By default FormHelper draws its values from the 'context'. The default +contexts, such as ``EntityContext``, will fetch data from the current entity, or +from ``$request->getData()``. + +If however, you are building a form that needs to read from the query string, +you can use ``valueSource()`` to change where ``FormHelper`` reads data input +data from:: + + // Prioritize query string over context: + echo $this->Form->create($article, [ + 'valueSources' => ['query', 'context'] + ]); + + // Same effect: + echo $this->Form + ->setValueSources(['query', 'context']) + ->create($articles); + + // Only read data from the query string + echo $this->Form->create($article); + $this->Form->setValueSources('query'); + + // Same effect: + echo $this->Form->create($article, ['valueSources' => 'query']); + +The supported sources are ``context``, ``data`` and ``query``. You can use one +or more sources. Any widgets generated by ``FormHelper`` will gather their +values from the sources, in the order you setup. + +The value sources will be reset to the default (``['context']``) when ``end()`` +is called. + +Changing the HTTP Method for a Form +----------------------------------- + +By using the ``type`` option you can change the HTTP method a form will use:: + + echo $this->Form->create($article, ['type' => 'get']); + +Output: + +.. code-block:: html + +
    + +Specifying a ``'file'`` value for ``type``, changes the form submission method +to 'post', and includes an ``enctype`` of "multipart/form-data" on the form tag. +This is to be used if there are any file elements inside the form. The absence +of the proper ``enctype`` attribute will cause the file uploads not to function. + +E.g. :: + + echo $this->Form->create($article, ['type' => 'file']); + +Output: + +.. code-block:: html + + + +When using ``'put'``, ``'patch'`` or ``'delete'`` as ``'type'`` values, your +form will be functionally equivalent to a 'post' form, but when submitted, the +HTTP request method will be overridden with 'PUT', 'PATCH' or 'DELETE', +respectively. +This allows CakePHP to emulate proper REST support in web browsers. + +Setting a URL for the Form +-------------------------- + +Using the ``'url'`` option allows you to point the form to a specific action in +your current controller or another controller in your application. + +For example, +if you'd like to point the form to the ``login()`` action of the current +controller, you would supply an ``$options`` array, like the following:: + + echo $this->Form->create($article, ['url' => ['action' => 'login']]); + +Output: + +.. code-block:: html + + + +If the desired form action isn't in the current controller, you can specify +a complete URL for the form action. The supplied URL can be relative to your +CakePHP application:: + + echo $this->Form->create(null, [ + 'url' => ['controller' => 'Articles', 'action' => 'publish'] + ]); + +Output: + +.. code-block:: html + + + +Or you can point to an external domain:: + + echo $this->Form->create(null, [ + 'url' => 'http://www.google.com/search', + 'type' => 'get' + ]); + +Output: + +.. code-block:: html + + + +Use ``'url' => false`` if you don't want to output a URL as the form action. + +Using Custom Validators +----------------------- + +Often models will have multiple validation sets, and you will want FormHelper to +mark fields required based on a the specific validation rules your controller +action is going to apply. For example, your Users table has specific validation +rules that only apply when an account is being registered:: + + echo $this->Form->create($user, [ + 'context' => ['validator' => 'register'] + ]); + +The above will use the rules defined in the ``register`` validator, which are +defined by ``UsersTable::validationRegister()``, for ``$user`` and all +related associations. If you are creating a form for associated entities, you +can define validation rules for each association by using an array:: + + echo $this->Form->create($user, [ + 'context' => [ + 'validator' => [ + 'Users' => 'register', + 'Comments' => 'default' + ] + ] + ]); + +The above would use ``register`` for the user, and ``default`` for the user's +comments. + +Creating context classes +------------------------ + +While the built-in context classes are intended to cover the basic cases you'll +encounter you may need to build a new context class if you are using a different +ORM. In these situations you need to implement the +`Cake\\View\\Form\\ContextInterface +`_ . Once +you have implemented this interface you can wire your new context into the +FormHelper. It is often best to do this in a ``View.beforeRender`` event +listener, or in an application view class:: + + $this->Form->addContextProvider('myprovider', function ($request, $data) { + if ($data['entity'] instanceof MyOrmClass) { + return new MyProvider($request, $data); + } + }); + +Context factory functions are where you can add logic for checking the form +options for the correct type of entity. If matching input data is found you can +return an object. If there is no match return null. + +.. _automagic-form-elements: + +Creating Form Controls +====================== + +.. php:method:: control(string $fieldName, array $options = []) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. +* ``$options`` - An optional array that can include both + :ref:`control-specific-options`, and options of the other methods (which + ``control()`` employs internally to generate various HTML elements) as + well as any valid HTML attributes. + +The ``control()`` method lets you to generate complete form controls. These +controls will include a wrapping ``div``, ``label``, control widget, and validation error if +necessary. By using the metadata in the form context, this method will choose an +appropriate control type for each field. Internally ``control()`` uses the other +methods of FormHelper. + +.. tip:: + + Please note that while the fields generated by the ``control()`` method are + called generically "inputs" on this page, technically speaking, the + ``control()`` method can generate not only all of the HTML ``input`` type + elements, but also other HTML form elements (e.g. ``select``, + ``button``, ``textarea``). + +By default the ``control()`` method will employ the following widget templates:: + + 'inputContainer' => '
    {{content}}
    ' + 'input' => '' + +In case of validation errors it will also use:: + + 'inputContainerError' => '
    {{content}}{{error}}
    ' + +The type of control created (when we provide no additional options to specify the +generated element type) is inferred via model introspection and +depends on the column datatype: + +Column Type + Resulting Form Field +string, uuid (char, varchar, etc.) + text +boolean, tinyint(1) + checkbox +decimal + number +float + number +integer + number +text + textarea +text, with name of password, passwd + password +text, with name of email + email +text, with name of tel, telephone, or phone + tel +date + day, month, and year selects +datetime, timestamp + day, month, year, hour, minute, and meridian selects +time + hour, minute, and meridian selects +binary + file + +The ``$options`` parameter allows you to choose a specific control type if +you need to:: + + echo $this->Form->control('published', ['type' => 'checkbox']); + +.. tip:: + + As a small subtlety, generating specific elements via the ``control()`` + form method will always also generate the wrapping ``div``, by default. + Generating the same type of element via one of the specific form methods + (e.g. ``$this->Form->checkbox('published');``) in most cases won't generate + the wrapping ``div``. Depending on your needs you can use one or another. + +.. _html5-required: + +The wrapping ``div`` will have a ``required`` class name appended if the +validation rules for the model's field indicate that it is required and not +allowed to be empty. You can disable automatic ``required`` flagging using the +``'required'`` option:: + + echo $this->Form->control('title', ['required' => false]); + +To skip browser validation triggering for the whole form you can set option +``'formnovalidate' => true`` for the input button you generate using +:php:meth:`~Cake\\View\\Helper\\FormHelper::submit()` or set ``'novalidate' => +true`` in options for :php:meth:`~Cake\\View\\Helper\\FormHelper::create()`. + +For example, let's assume that your Users model includes fields for a +*username* (varchar), *password* (varchar), *approved* (datetime) and +*quote* (text). You can use the ``control()`` method of the FormHelper to +create appropriate controls for all of these form fields:: + + echo $this->Form->create($user); + // The following generates a Text input + echo $this->Form->control('username'); + // The following generates a Password input + echo $this->Form->control('password'); + // Assuming 'approved' is a datetime or timestamp field the following + //generates: Day, Month, Year, Hour, Minute + echo $this->Form->control('approved'); + // The following generates a Textarea element + echo $this->Form->control('quote'); + + echo $this->Form->button('Add'); + echo $this->Form->end(); + +A more extensive example showing some options for a date field:: + + echo $this->Form->control('birth_dt', [ + 'label' => 'Date of birth', + 'minYear' => date('Y') - 70, + 'maxYear' => date('Y') - 18, + ]); + +Besides the specific :ref:`control-specific-options`, +you also can specify any option accepted by corresponding specific method +for the chosen (or inferred by CakePHP) +control type and any HTML attribute (for instance ``onfocus``). + +If you want to create a ``select`` form field while using a *belongsTo* (or +*hasOne*) relation, you can add the following to your UsersController +(assuming your User *belongsTo* Group):: + + $this->set('groups', $this->Users->Groups->find('list')); + +Afterwards, add the following to your view template:: + + echo $this->Form->control('group_id', ['options' => $groups]); + +To make a ``select`` box for a *belongsToMany* Groups association you can +add the following to your UsersController:: + + $this->set('groups', $this->Users->Groups->find('list')); + +Afterwards, add the following to your view template:: + + echo $this->Form->control('groups._ids', ['options' => $groups]); + +If your model name consists of two or more words (e.g. +"UserGroups"), when passing the data using ``set()`` you should name your +data in a pluralised and +`lower camelCased `_ +format as follows:: + + $this->set('userGroups', $this->UserGroups->find('list')); + +.. note:: + + You should not use ``FormHelper::control()`` to generate submit buttons. Use + :php:meth:`~Cake\\View\\Helper\\FormHelper::submit()` instead. + +Field Naming Conventions +------------------------ + +When creating control widgets you should name your fields after the matching +attributes in the form's entity. For example, if you created a form for an +``$article`` entity, you would create fields named after the properties. E.g. +``title``, ``body`` and ``published``. + +You can create controls for associated models, or arbitrary models by passing in +``association.fieldname`` as the first parameter:: + + echo $this->Form->control('association.fieldname'); + +Any dots in your field names will be converted into nested request data. For +example, if you created a field with a name ``0.comments.body`` you would get +a name attribute that looks like ``0[comments][body]``. This convention makes it +easy to save data with the ORM. Details for the various association types can +be found in the :ref:`associated-form-inputs` section. + +When creating datetime related controls, FormHelper will append a field-suffix. +You may notice additional fields named ``year``, ``month``, ``day``, ``hour``, +``minute``, or ``meridian`` being added. These fields will be automatically +converted into ``DateTime`` objects when entities are marshalled. + +.. _control-specific-options: + +Options for Control +------------------- + +``FormHelper::control()`` supports a large number of options via its ``$options`` +argument. In addition to its own options, ``control()`` accepts options for the +inferred/chosen generated control types (e.g. for ``checkbox`` or ``textarea``), +as well as HTML attributes. This subsection will cover the options specific to +``FormHelper::control()``. + +* ``$options['type']`` - A string that specifies the widget type + to be generated. In addition to the field types found in the + :ref:`automagic-form-elements`, you can also create ``'file'``, + ``'password'``, and any other type supported by HTML5. By specifying a + ``'type'`` you will force the type of the generated control, overriding model + introspection. Defaults to ``null``. + + E.g. :: + + echo $this->Form->control('field', ['type' => 'file']); + echo $this->Form->control('email', ['type' => 'email']); + + Output: + + .. code-block:: html + +
    + + +
    + + +* ``$options['label']`` - Either a string caption or an array of + :ref:`options for the label`. You can set this key to the + string you would like to be displayed within the label that usually + accompanies the ``input`` HTML element. Defaults to ``null``. + + E.g. :: + + echo $this->Form->control('name', [ + 'label' => 'The User Alias' + ]); + + Output: + + .. code-block:: html + +
    + + +
    + + Alternatively, set this key to ``false`` to disable the generation of the + ``label`` element. + + E.g. :: + + echo $this->Form->control('name', ['label' => false]); + + Output: + + .. code-block:: html + +
    + +
    + + Set this to an array to provide additional options for the + ``label`` element. If you do this, you can use a ``'text'`` key in + the array to customize the label text. + + E.g. :: + + echo $this->Form->control('name', [ + 'label' => [ + 'class' => 'thingy', + 'text' => 'The User Alias' + ] + ]); + + Output: + + .. code-block:: html + +
    + + +
    + +* ``$options['options']`` - You can provide in here an array containing + the elements to be generated for widgets such as ``radio`` or ``select``, + which require an array of items as an argument (see + :ref:`create-radio-button` and :ref:`create-select-picker` for more details). + Defaults to ``null``. + +* ``$options['error']`` - Using this key allows you to override the default + model error messages and can be used, for example, to set i18n messages. To + disable the error message output & field classes set the ``'error'`` key to + ``false``. Defaults to ``null``. + + E.g. :: + + echo $this->Form->control('name', ['error' => false]); + + To override the model error messages use an array with + the keys matching the original validation error messages. + + E.g. :: + + $this->Form->control('name', [ + 'error' => ['Not long enough' => __('This is not long enough')] + ]); + + As seen above you can set the error message for each validation + rule you have in your models. In addition you can provide i18n + messages for your forms. + +* ``$options['nestedInput']`` - Used with checkboxes and radio buttons. + Controls whether the input element is generated + inside or outside the ``label`` element. When ``control()`` generates a + checkbox or a radio button, you can set this to ``false`` to force the + generation of the HTML ``input`` element outside of the ``label`` element. + + On the other hand you can set this to ``true`` for any control type to force the + generated input element inside the label. If you change this for radio buttons + then you need to also modify the default + :ref:`radioWrapper` template. Depending on the generated + control type it defaults to ``true`` or ``false``. + +* ``$options['templates']`` - The templates you want to use for this input. Any + specified templates will be merged on top of the already loaded templates. + This option can be either a filename (without extension) in ``/config`` that + contains the templates you want to load, or an array of templates to use. + +* ``$options['labelOptions']`` - Set this to ``false`` to disable labels around + nestedWidgets or set it to an array of attributes to be provided to the + ``label`` tag. + +Generating Specific Types of Controls +===================================== + +In addition to the generic ``control()`` method, ``FormHelper`` has specific +methods for generating a number of different types of controls. These can be used +to generate just the control widget itself, and combined with other methods like +:php:meth:`~Cake\\View\\Helper\\FormHelper::label()` and +:php:meth:`~Cake\\View\\Helper\\FormHelper::error()` to generate fully custom +form layouts. + +.. _general-control-options: + +Common Options For Specific Controls +------------------------------------ + +Many of the various control element methods support a common set of options which, +depending on the form method used, must be provided inside the ``$options`` or +in the ``$attributes`` array argument. All of these options are also supported +by the ``control()`` method. +To reduce repetition, the common options shared by all control methods are +as follows: + +* ``'id'`` - Set this key to force the value of the DOM id for the control. + This will override the ``'idPrefix'`` that may be set. + +* ``'default'`` - Used to set a default value for the control field. The + value is used if the data passed to the form does not contain a value for the + field (or if no data is passed at all). An explicit default value will + override any default values defined in the schema. + + Example usage:: + + echo $this->Form->text('ingredient', ['default' => 'Sugar']); + + Example with ``select`` field (size "Medium" will be selected as + default):: + + $sizes = ['s' => 'Small', 'm' => 'Medium', 'l' => 'Large']; + echo $this->Form->select('size', $sizes, ['default' => 'm']); + + .. note:: + + You cannot use ``default`` to check a checkbox - instead you might + set the value in ``$this->request->getData()`` in your controller, + or set the control option ``'checked'`` to ``true``. + + Beware of using ``false`` to assign a default value. A ``false`` value is + used to disable/exclude options of a control field, so ``'default' => false`` + would not set any value at all. Instead use ``'default' => 0``. + +* ``'value'`` - Used to set a specific value for the control field. This + will override any value that may else be injected from the context, such as + Form, Entity or ``request->getData()`` etc. + + .. note:: + + If you want to set a field to not render its value fetched from + context or valuesSource you will need to set ``'value'`` to ``''`` + (instead of setting it to ``null``). + +In addition to the above options, you can mixin any HTML attribute you wish to +use. Any non-special option name will be treated as an HTML attribute, and +applied to the generated HTML control element. + +.. versionchanged:: 3.3.0 + As of 3.3.0, FormHelper will automatically use any default values defined + in your database schema. You can disable this behavior by setting + the ``schemaDefault`` option to ``false``. + +Creating Input Elements +======================= + +The rest of the methods available in the FormHelper are for +creating specific form elements. Many of these methods also make +use of a special ``$options`` or ``$attributes`` parameter. In this case, +however, this parameter is used primarily to specify HTML tag attributes +(such as the value or DOM id of an element in the form). + +Creating Text Inputs +-------------------- + +.. php:method:: text(string $name, array $options) + +* ``$name`` - A field name in the form ``'Modelname.fieldname'``. +* ``$options`` - An optional array including any of the + :ref:`general-control-options` as well as any valid HTML attributes. + +Creates a simple ``input`` HTML element of ``text`` type. + +E.g. :: + + echo $this->Form->text('username', ['class' => 'users']); + +Will output: + +.. code-block:: html + + + +Creating Password Inputs +------------------------ + +.. php:method:: password(string $fieldName, array $options) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. +* ``$options`` - An optional array including any of the + :ref:`general-control-options` as well as any valid HTML attributes. + +Creates a simple ``input`` element of ``password`` type. + +E.g. :: + + echo $this->Form->password('password'); + +Will output: + +.. code-block:: html + + + +Creating Hidden Inputs +---------------------- + +.. php:method:: hidden(string $fieldName, array $options) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. +* ``$options`` - An optional array including any of the + :ref:`general-control-options` as well as any valid HTML attributes. + +Creates a hidden form input. + +E.g. :: + + echo $this->Form->hidden('id'); + +Will output: + +.. code-block:: html + + + +Creating Textareas +------------------ + +.. php:method:: textarea(string $fieldName, array $options) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, of the specific textarea options (see below) + as well as any valid HTML attributes. + +Creates a textarea control field. The default widget template used is:: + + 'textarea' => '' + +For example:: + + echo $this->Form->textarea('notes'); + +Will output: + +.. code-block:: html + + + +If the form is being edited (i.e. the array ``$this->request->getData()`` +contains the information previously saved for the ``User`` entity), the value +corresponding to ``notes`` field will automatically be added to the HTML +generated. + +Example: + +.. code-block:: html + + + +**Options for Textarea** + +In addition to the :ref:`general-control-options`, ``textarea()`` supports a +couple of specific options: + +* ``'escape'`` - Determines whether or not the contents of the textarea should + be escaped. Defaults to ``true``. + + E.g. :: + + echo $this->Form->textarea('notes', ['escape' => false]); + // OR.... + echo $this->Form->control('notes', ['type' => 'textarea', 'escape' => false]); + +* ``'rows', 'cols'`` - You can use these two keys to set the HTML attributes + which specify the number of rows and columns for the ``textarea`` field. + + E.g. :: + + echo $this->Form->textarea('comment', ['rows' => '5', 'cols' => '5']); + + Output: + + .. code-block:: html + + + +Creating Select, Checkbox and Radio Controls +-------------------------------------------- + +These controls share some commonalities and a few options and thus, they are +all grouped in this subsection for easier reference. + +.. _checkbox-radio-select-options: + +Options for Select, Checkbox and Radio Controls +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +You can find below the options which are shared by ``select()``, +``checkbox()`` and ``radio()`` (the options particular only to one of the +methods are described in each method's own section.) + +* ``'value'`` - Sets or selects the value of the affected element(s): + + * For checkboxes, it sets the HTML ``'value'`` attribute assigned + to the ``input`` element to whatever you provide as value. + + * For radio buttons or select pickers it defines which element will be + selected when the form is rendered (in this case ``'value'`` must be + assigned a valid, existent element value). May also be used in + combination with any select-type control, + such as ``date()``, ``time()``, ``dateTime()``:: + + echo $this->Form->time('close_time', [ + 'value' => '13:30:00' + ]); + + .. note:: + + The ``'value'`` key for ``date()`` and ``dateTime()`` controls may also have + as value a UNIX timestamp, or a DateTime object. + + For a ``select`` control where you set the ``'multiple'`` attribute to + ``true``, you can provide an array with the values you want to select + by default:: + + // HTML ' + +May also use:: + + 'optgroup' => '{{content}}' + 'selectMultiple' => '' + +**Attributes for Select Pickers** + +* ``'multiple'`` - If set to ``true`` allows multiple selections in the select + picker. If set to ``'checkbox'``, multiple checkboxes will be created instead. + Defaults to ``null``. + +* ``'escape'`` - Boolean. If ``true`` the contents of the ``option`` elements + inside the select picker will be HTML entity encoded. Defaults to ``true``. + +* ``'val'`` - Allows preselecting a value in the select picker. + +* ``'disabled'`` - Controls the ``disabled`` attribute. If set to ``true`` + disables the whole select picker. If set to an array it will disable + only those specific ``option`` elements whose values are provided in + the array. + +The ``$options`` argument allows you to manually specify +the contents of the ``option`` elements of a ``select`` control. + +E.g. :: + + echo $this->Form->select('field', [1, 2, 3, 4, 5]); + +Output: + +.. code-block:: html + + + +The array for ``$options`` can also be supplied as key-value pairs. + +E.g. :: + + echo $this->Form->select('field', [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2', + 'Value 3' => 'Label 3' + ]); + +Output: + +.. code-block:: html + + + +If you would like to generate a ``select`` with optgroups, just pass +data in hierarchical format (nested array). This works on multiple +checkboxes and radio buttons too, but instead of ``optgroup`` it wraps +the elements in ``fieldset`` elements. + +For example:: + + $options = [ + 'Group 1' => [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ], + 'Group 2' => [ + 'Value 3' => 'Label 3' + ] + ]; + echo $this->Form->select('field', $options); + +Output: + +.. code-block:: html + + + +To generate HTML attributes within an ``option`` tag:: + + $options = [ + ['text' => 'Description 1', 'value' => 'value 1', 'attr_name' => 'attr_value 1'], + ['text' => 'Description 2', 'value' => 'value 2', 'attr_name' => 'attr_value 2'], + ['text' => 'Description 3', 'value' => 'value 3', 'other_attr_name' => 'other_attr_value'], + ]; + echo $this->Form->select('field', $options); + +Output: + +.. code-block:: html + + + +**Controlling Select Pickers via Attributes** + +By using specific options in the ``$attributes`` parameter you can control +certain behaviors of the ``select()`` method. + +* ``'empty'`` - Set the ``'empty'`` key in the ``$attributes`` argument + to ``true`` (the default value is ``false``) to add a blank option with an + empty value at the top of your dropdown list. + + For example:: + + $options = ['M' => 'Male', 'F' => 'Female']; + echo $this->Form->select('gender', $options, ['empty' => true]); + + Will output: + + .. code-block:: html + + + +* ``'escape'`` - The ``select()`` method allows for an attribute + called ``'escape'`` which accepts a boolean value and determines + whether to HTML entity encode the contents of the ``select``'s ``option`` + elements. + + E.g. :: + + // This will prevent HTML-encoding the contents of each option element + $options = ['M' => 'Male', 'F' => 'Female']; + echo $this->Form->select('gender', $options, ['escape' => false]); + +* ``'multiple'`` - If set to ``true``, the select picker will allow + multiple selections. + + E.g. :: + + echo $this->Form->select('field', $options, ['multiple' => true]); + + Alternatively, set ``'multiple'`` to ``'checkbox'`` in order to output a + list of related checkboxes:: + + $options = [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ]; + echo $this->Form->select('field', $options, [ + 'multiple' => 'checkbox' + ]); + + Output: + + .. code-block:: html + + +
    + +
    +
    + +
    + +* ``'disabled'`` - This option can be set in order to disable all or some + of the ``select``'s ``option`` items. To disable all items set ``'disabled'`` + to ``true``. To disable only certain items, assign to ``'disabled'`` + an array containing the keys of the items to be disabled. + + E.g. :: + + $options = [ + 'M' => 'Masculine', + 'F' => 'Feminine', + 'N' => 'Neuter' + ]; + echo $this->Form->select('gender', $options, [ + 'disabled' => ['M', 'N'] + ]); + + Will output: + + .. code-block:: html + + + + This option also works when ``'multiple'`` is set to ``'checkbox'``:: + + $options = [ + 'Value 1' => 'Label 1', + 'Value 2' => 'Label 2' + ]; + echo $this->Form->select('field', $options, [ + 'multiple' => 'checkbox', + 'disabled' => ['Value 1'] + ]); + + Output: + + .. code-block:: html + + +
    + +
    +
    + +
    + +Creating File Inputs +-------------------- + +.. php:method:: file(string $fieldName, array $options) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. +* ``$options`` - An optional array including any of the + :ref:`general-control-options` as well as any valid HTML attributes. + +Creates a file upload field in the form. +The widget template used by default is:: + + 'file' => '' + +To add a file upload field to a form, you must first make sure that +the form enctype is set to ``'multipart/form-data'``. + +So start off with a ``create()`` method such as the following:: + + echo $this->Form->create($document, ['enctype' => 'multipart/form-data']); + // OR + echo $this->Form->create($document, ['type' => 'file']); + +Next add a line that looks like either of the following two lines +to your form's view template file:: + + echo $this->Form->control('submittedfile', [ + 'type' => 'file' + ]); + + // OR + echo $this->Form->file('submittedfile'); + +.. note:: + + Due to the limitations of HTML itself, it is not possible to put + default values into input fields of type 'file'. Each time the form + is displayed, the value inside will be empty. + +Upon submission, file fields provide an expanded data array to the +script receiving the form data. + +For the example above, the values in the submitted data array would +be organized as follows, if CakePHP was installed on a Windows +server (the key ``'tmp_name'`` will contain a different path +in a Unix environment):: + + $this->request->data['submittedfile'] + + // would contain the following array: + [ + 'name' => 'conference_schedule.pdf', + 'type' => 'application/pdf', + 'tmp_name' => 'C:/WINDOWS/TEMP/php1EE.tmp', + 'error' => 0, // On Windows this can be a string. + 'size' => 41737, + ]; + +This array is generated by PHP itself, so for more detail on the +way PHP handles data passed via file fields +`read the PHP manual section on file uploads `_. + +.. note:: + + When using ``$this->Form->file()``, remember to set the form + encoding-type, by setting the ``'type'`` option to ``'file'`` in + ``$this->Form->create()``. + +Creating Date & Time Related Controls +------------------------------------- + +The date and time related methods share a number of common traits and options +and hence are grouped together into this subsection. + +.. _datetime-options: + +Common Options for Date & Time Controls +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These options are common for the date and time related controls: + +* ``'empty'`` - If ``true`` an extra, empty, ``option`` HTML element is + added inside ``select`` at the top of the list. If a string, that string is + displayed as the empty element. Defaults to ``true``. + +* ``'default'`` | ``value`` - Use either of the two to set the default value to + be shown by the field. A value in ``$this->request->getData()`` matching the field + name will override this value. If no default is provided ``time()`` will + be used. + +* ``'year', 'month', 'day', 'hour', 'minute', 'second', 'meridian'`` - These + options allow you to control which control elements are generated or not. + By setting any of these options to ``false`` you can disable the generation + of that specific that select picker (if by default it would be rendered in + the used method). In addition each option allows you to pass HTML attributes + to that specific ``select`` element. + +.. _date-options: + +Options for Date-Related Controls +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These options are concerning the date-related methods - i.e. ``year()``, +``month()``, ``day()``, ``dateTime()`` and ``date()``: + +* ``'monthNames'`` - If ``false``, 2 digit numbers will be used instead of text + for displaying months in the select picker. If set to an array (e.g. + ``['01' => 'Jan', '02' => 'Feb', ...]``), the given array will be used. + +* ``'minYear'`` - The lowest value to use in the year select picker. + +* ``'maxYear'`` - The maximum value to use in the year select picker. + +* ``'orderYear'`` - The order of year values in the year select picker. + Possible values are ``'asc'`` and ``'desc'``. Defaults to ``'desc'``. + +.. _time-options: + +Options for Time-Related Controls +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These options are concerning the time-related methods - ``hour()``, +``minute()``, ``second()``, ``dateTime()`` and ``time()``: + +* ``'interval'`` - The interval in minutes between the values which are + displayed in the ``option`` elements of the minutes select picker. + Defaults to 1. + +* ``'round'`` - Set to ``up`` or ``down`` if you want to force rounding minutes + in either direction when the value doesn't fit neatly into an interval. + Defaults to ``null``. + +* ``timeFormat`` - Applies to ``dateTime()`` and ``time()``. The time format to + use in the select picker; either ``12`` or ``24``. When this option is set to + anything else than ``24`` the format will be automatically set to ``12`` and + the ``meridian`` select picker will be displayed automatically to the right of + the seconds select picker. Defaults to 24. + +* ``format`` - Applies to ``hour()``. The time format to use; either ``12`` or + ``24``. In case it's set to ``12`` the ``meridian`` select picker won't be + automatically displayed. It's up to you to either add it or provide means + to infer from the form context the right period of the day. Defaults to 24. + +* ``second`` - Applies to ``dateTime()`` and ``time()``. Set to ``true`` to + enable the seconds drop down. Defaults to ``false``. + +Creating DateTime Controls +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: dateTime($fieldName, $options = []) + +* ``$fieldName`` - A string that will be used as a prefix for the HTML ``name`` + attribute of the ``select`` elements. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, or specific datetime options (see above), + as well as any valid HTML attributes. + +Creates a set of ``select`` elements for date and time. + +To control the order of controls, and any elements/content between the controls you +can override the ``dateWidget`` template. By default the ``dateWidget`` template +is:: + + {{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}} + +Calling the method without additional options will generate, by default, +5 select pickers, for: year (4 digits), month (full English name), day (num), +hour (num), minutes (num). + +For example :: + + form->dateTime('registered') ?> + +Output: + +.. code-block:: html + + + + + + + +To create datetime controls with custom classes/attributes on a specific select +box, you can provide them as arrays of options for each component, within the +``$options`` argument. + +For example:: + + echo $this->Form->dateTime('released', [ + 'year' => [ + 'class' => 'year-classname', + ], + 'month' => [ + 'class' => 'month-class', + 'data-type' => 'month', + ], + ]); + +Which would create the following two select pickers: + +.. code-block:: html + + + + +Creating Date Controls +~~~~~~~~~~~~~~~~~~~~~~ +.. php:method:: date($fieldName, $options = []) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` elements. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, of the :ref:`datetime-options`, any applicable + :ref:`time-options`, as well as any valid HTML attributes. + +Creates, by default, three select pickers populated with values for: +year (4 digits), month (full English name) and day (numeric), respectively. + +You can further control the generated ``select`` elements by providing +additional options. + +For example:: + + // Assuming current year is 2017; this disables day picker, removes empty + // option on year picker, limits lowest year, adds HTML attributes on year, + // adds a string 'empty' option on month, changes month to numeric + Form->date('registered', [ + 'minYear' => 2018, + 'monthNames' => false, + 'empty' => [ + 'year' => false, + 'month' => 'Choose month...' + ], + 'day' => false, + 'year' => [ + 'class' => 'cool-years', + 'title' => 'Registration Year' + ] + ]); + ?> + +Output: + +.. code-block:: html + + + + +Creating Time Controls +~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: time($fieldName, $options = []) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` elements. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, of the :ref:`datetime-options`, any applicable + :ref:`time-options`, as well as any valid HTML attributes. + +Creates, by default, two ``select`` elements (``hour`` and ``minute``) populated +with values for 24 hours and 60 minutes, respectively. +Additionally, HTML attributes may be supplied in ``$options`` for each specific +component. If ``$options['empty']`` is ``false``, the select picker will not +include an empty default option. + +For example, to create a time range with minutes selectable in 15 minute +increments, and to apply classes to the select boxes, you could do the +following:: + + echo $this->Form->time('released', [ + 'interval' => 15, + 'hour' => [ + 'class' => 'foo-class', + ], + 'minute' => [ + 'class' => 'bar-class', + ], + ]); + +Which would create the following two select pickers: + +.. code-block:: html + + + + +Creating Year Controls +~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: year(string $fieldName, array $options = []) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` element. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, of the :ref:`datetime-options`, any applicable + :ref:`date-options`, as well as any valid HTML attributes. + +Creates a ``select`` element populated with the years from ``minYear`` +to ``maxYear`` (when these options are provided) or else with values starting +from -5 years to +5 years counted from today. Additionally, HTML attributes may +be supplied in ``$options``. +If ``$options['empty']`` is ``false``, the select picker will not include an +empty item in the list. + +For example, to create a year range from 2000 to the current year you +would do the following:: + + echo $this->Form->year('purchased', [ + 'minYear' => 2000, + 'maxYear' => date('Y') + ]); + +If it was 2009, you would get the following: + +.. code-block:: html + + + +Creating Month Controls +~~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: month(string $fieldName, array $attributes) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` element. +* ``$attributes`` - An optional array including any of the + :ref:`general-control-options`, of the :ref:`datetime-options`, any applicable + :ref:`date-options`, as well as any valid HTML attributes. + +Creates a ``select`` element populated with month names. + +For example:: + + echo $this->Form->month('mob'); + +Will output: + +.. code-block:: html + + + +You can pass in, your own array of months to be used by setting the +``'monthNames'`` attribute, or have months displayed as numbers by +passing ``false``. + +E.g. :: + + echo $this->Form->month('mob', ['monthNames' => false]); + +.. note:: + + The default months can be localized with CakePHP + :doc:`/core-libraries/internationalization-and-localization` features. + +Creating Day Controls +~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: day(string $fieldName, array $attributes) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` element. +* ``$attributes`` - An optional array including any of the + :ref:`general-control-options`, of the :ref:`datetime-options`, any applicable + :ref:`date-options`, as well as any valid HTML attributes. + +Creates a ``select`` element populated with the (numerical) days of the +month. + +To create an empty ``option`` element with a prompt text of your choosing +(e.g. the first option is 'Day'), you can supply the text in the ``'empty'`` +parameter. + +For example:: + + echo $this->Form->day('created', ['empty' => 'Day']); + +Will output: + +.. code-block:: html + + + +Creating Hour Controls +~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: hour(string $fieldName, array $attributes) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` element. +* ``$attributes`` - An optional array including any of the + :ref:`general-control-options`, of the :ref:`datetime-options`, any applicable + :ref:`time-options`, as well as any valid HTML attributes. + +Creates a ``select`` element populated with the hours of the day. + +You can create either 12 or 24 hour pickers using the ``'format'`` option:: + + echo $this->Form->hour('created', [ + 'format' => 12 + ]); + echo $this->Form->hour('created', [ + 'format' => 24 + ]); + +Creating Minute Controls +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: minute(string $fieldName, array $attributes) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` element. +* ``$attributes`` - An optional array including any of the + :ref:`general-control-options`, of the :ref:`datetime-options`, any applicable + :ref:`time-options`, as well as any valid HTML attributes. + +Creates a ``select`` element populated with values for the minutes of the hour. +You can create a select picker that only contains specific values by using the +``'interval'`` option. + +For example, if you wanted 10 minutes increments you would do the following:: + + // In your view template file + echo $this->Form->minute('arrival', [ + 'interval' => 10 + ]); + +This would output: + +.. code-block:: html + + + +Creating Meridian Controls +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. php:method:: meridian(string $fieldName, array $attributes) + +* ``$fieldName`` - A field name that will be used as a prefix for the HTML + ``name`` attribute of the ``select`` element. +* ``$attributes`` - An optional array including any of the + :ref:`general-control-options` as well as any valid HTML attributes. + +Creates a ``select`` element populated with 'am' and 'pm'. This is useful when +the hour format is set to ``12`` instead of ``24``, as it allows to specify the +period of the day to which the hour belongs. + +.. _create-label: + +Creating Labels +=============== + +.. php:method:: label(string $fieldName, string $text, array $options) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. +* ``$text`` - An optional string providing the label caption text. +* ``$options`` - Optional. Array containing any of the + :ref:`general-control-options` as well as any valid HTML attributes. + +Creates a ``label`` element. The argument ``$fieldName`` is used for generating +the HTML ``for`` attribute of the element; if ``$text`` is undefined, +``$fieldName`` will also be used to inflect the label's ``text`` attribute. + +E.g. :: + + echo $this->Form->label('User.name'); + echo $this->Form->label('User.name', 'Your username'); + +Output: + +.. code-block:: html + + + + +With the third parameter ``$options`` you can set the id or class:: + + echo $this->Form->label('User.name', null, ['id' => 'user-label']); + echo $this->Form->label('User.name', 'Your username', ['class' => 'highlight']); + +Output: + +.. code-block:: html + + + + +Displaying and Checking Errors +============================== + +FormHelper exposes a couple of methods that allow us to easily check for +field errors and when necessary display customized error messages. + +Displaying Errors +----------------- + +.. php:method:: error(string $fieldName, mixed $text, array $options) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. +* ``$text`` - Optional. A string or array providing the error message(s). If an + array, then it should be a hash of key names => messages. Defaults to + ``null``. +* ``$options`` - An optional array that can only contain a boolean with the key + ``'escape'``, which will define whether to HTML escape the + contents of the error message. Defaults to ``true``. + +Shows a validation error message, specified by ``$text``, for the given +field, in the event that a validation error has occurred. If ``$text`` is not +provided then the default validation error message for that field will be used. + +Uses the following template widgets:: + + 'error' => '
    {{content}}
    ' + 'errorList' => '
      {{content}}
    ' + 'errorItem' => '
  • {{text}}
  • ' + +The ``'errorList'`` and ``'errorItem'`` templates are used to format mutiple +error messages per field. + +Example:: + + // If in TicketsTable you have a 'notEmpty' validation rule: + public function validationDefault(Validator $validator) + { + $validator + ->requirePresence('ticket', 'create') + ->notEmpty('ticket'); + } + + // And inside Templates/Tickets/add.ctp you have: + echo $this->Form->text('ticket'); + + if ($this->Form->isFieldError('ticket')) { + echo $this->Form->error('ticket', 'Completely custom error message!'); + } + +If you would click the *Submit* button of your form without providing a value +for the *Ticket* field, your form would output: + +.. code-block:: html + + +
    Completely custom error message!
    + +.. note:: + + When using :php:meth:`~Cake\\View\\Helper\\FormHelper::control()`, errors are + rendered by default, so you don't need to use ``isFieldError()`` or call + ``error()`` manually. + +.. tip:: + + If you use a certain model field to generate multiple form fields via + ``control()``, and you want the same validation error message displayed for + each one, you will probably be better off defining a custom error message + inside the respective :ref:`validator rules`. + +.. TODO:: Add examples. + +Checking for Errors +------------------- + +.. php:method:: isFieldError(string $fieldName) + +* ``$fieldName`` - A field name in the form ``'Modelname.fieldname'``. + +Returns ``true`` if the supplied ``$fieldName`` has an active validation +error, otherwise returns ``false``. + +Example:: + + if ($this->Form->isFieldError('gender')) { + echo $this->Form->error('gender'); + } + +Creating Buttons and Submit Elements +==================================== + +Creating Submit Elements +------------------------ + +.. php:method:: submit(string $caption, array $options) + +* ``$caption`` - An optional string providing the button's text caption or a + path to an image. Defaults to ``'Submit'``. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, or of the specific submit options (see below) + as well as any valid HTML attributes. + +Creates an ``input`` element of ``submit`` type, with ``$caption`` as value. +If the supplied ``$caption`` is a URL pointing to an image (i.e. if the string +contains '://' or contains any of the extensions '.jpg, .jpe, .jpeg, .gif'), +an image submit button will be generated, using the specified image if it +exists. If the first character is '/' then the image path is relative to +*webroot*, else if the first character is not '/' then the image path is +relative to *webroot/img*. + +By default it will use the following widget templates:: + + 'inputSubmit' => '' + 'submitContainer' => '
    {{content}}
    ' + +**Options for Submit** + +* ``'type'`` - Set this option to ``'reset'`` in order to generate reset buttons. + It defaults to ``'submit'``. + +* ``'templateVars'`` - Set this array to provide additional template variables + for the input element and its container. + +* Any other provided attributes will be assigned to the ``input`` element. + +The following:: + + echo $this->Form->submit('Click me'); + +Will output: + +.. code-block:: html + +
    + +You can pass a relative or absolute URL of an image to the +caption parameter instead of the caption text:: + + echo $this->Form->submit('ok.png'); + +Will output: + +.. code-block:: html + +
    + +Submit inputs are useful when you only need basic text or images. If you need +more complex button content you should use ``button()``. + +Creating Button Elements +------------------------ + +.. php:method:: button(string $title, array $options = []) + +* ``$title`` - Mandatory string providing the button's text caption. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, or of the specific button options (see below) + as well as any valid HTML attributes. + +Creates an HTML button with the specified title and a default type +of ``'button'``. + +**Options for Button** + +* ``$options['type']`` - You can set this to one of the following three + possible values: + + #. ``'submit'`` - Similarly to the ``$this->Form->submit()`` method it will + create a submit button. However this won't generate a wrapping ``div`` + as ``submit()`` does. This is the default type. + #. ``'reset'`` - Creates a form reset button. + #. ``'button'`` - Creates a standard push button. + +* ``$options['escape']`` - Boolean. If set to ``true`` it will HTML encode + the value provided inside ``$title``. Defaults to ``false``. + +For example:: + + echo $this->Form->button('A Button'); + echo $this->Form->button('Another Button', ['type' => 'button']); + echo $this->Form->button('Reset the Form', ['type' => 'reset']); + echo $this->Form->button('Submit Form', ['type' => 'submit']); + +Will output: + +.. code-block:: html + + + + + + +Example of use of the ``'escape'`` option:: + + // Will render escaped HTML. + echo $this->Form->button('Submit Form', [ + 'type' => 'submit', + 'escape' => true + ]); + +Closing the Form +================ + +.. php:method:: end($secureAttributes = []) + +* ``$secureAttributes`` - Optional. Allows you to provide secure attributes + which will be passed as HTML attributes into the hidden input elements + generated for the SecurityComponent. + +The ``end()`` method closes and completes a form. Often, ``end()`` will only +output a closing form tag, but using ``end()`` is a good practice as it +enables FormHelper to insert the hidden form elements that +:php:class:`Cake\\Controller\\Component\\SecurityComponent` requires: + +.. code-block:: php + + Form->create(); ?> + + + + Form->end(); ?> + +If you need to add additional attributes to the generated hidden inputs +you can use the ``$secureAttributes`` argument. + +E.g. :: + + echo $this->Form->end(['data-type' => 'hidden']); + +Will output: + +.. code-block:: html + +
    + + +
    + +.. note:: + + If you are using + :php:class:`Cake\\Controller\\Component\\SecurityComponent` in your + application you should always end your forms with ``end()``. + +Creating Standalone Buttons and POST Links +========================================== + +Creating POST Buttons +--------------------- + +.. php:method:: postButton(string $title, mixed $url, array $options = []) + +* ``$title`` - Mandatory string providing the button's text caption. By default + not HTML encoded. +* ``$url`` - The URL of the form provided as a string or as array. +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, or of the specific options (see below) as well + as any valid HTML attributes. + +Creates a `` +
    + + + +
    + + +Since this method generates a ``form`` element, do not use this method in an +already opened form. Instead use +:php:meth:`Cake\\View\\Helper\\FormHelper::submit()` +or :php:meth:`Cake\\View\\Helper\\FormHelper::button()` to create buttons +inside opened forms. + +Creating POST Links +------------------- + +.. php:method:: postLink(string $title, mixed $url = null, array $options = []) + +* ``$title`` - Mandatory string providing the text to be wrapped in ```` + tags. +* ``$url`` - Optional. String or array which contains the URL + of the form (Cake-relative or external URL starting with ``http://``). +* ``$options`` - An optional array including any of the + :ref:`general-control-options`, or of the specific options (see below) as well + as any valid HTML attributes. + +Creates an HTML link, but accesses the URL using the method you specify +(defaults to POST). Requires JavaScript to be enabled in browser. + +**Options for POST Link** + +* ``'data'`` - Array with key/value to pass in hidden input. + +* ``'method'`` - Request method to use. E.g. set to ``'delete'`` + to simulate a HTTP/1.1 DELETE request. Defaults to ``'post'``. + +* ``'confirm'`` - The confirmation message to display on click. Defaults to + ``null``. + +* ``'block'`` - Set this option to ``true`` to append the form to view block + ``'postLink'`` or provide a custom block name. Defaults to ``null``. + +* Also, the ``postLink`` method will accept the options which are valid for + the ``link()`` method. + +This method creates a ``
    `` element. If you want to use this method +inside of an existing form, you must use the ``block`` option so that the +new form is being set to a :ref:`view block ` that can be +rendered outside of the main form. + +If all you are looking for is a button to submit your form, then you should +use :php:meth:`Cake\\View\\Helper\\FormHelper::button()` or +:php:meth:`Cake\\View\\Helper\\FormHelper::submit()` instead. + +.. note:: + + Be careful to not put a postLink inside an open form. Instead use the + ``block`` option to buffer the form into a :ref:`view block ` + +.. _customizing-templates: + +Customizing the Templates FormHelper Uses +========================================= + +Like many helpers in CakePHP, FormHelper uses string templates to format the +HTML it creates. While the default templates are intended to be a reasonable set +of defaults, you may need to customize the templates to suit your application. + +To change the templates when the helper is loaded you can set the ``'templates'`` +option when including the helper in your controller:: + + // In a View class + $this->loadHelper('Form', [ + 'templates' => 'app_form', + ]); + +This would load the tags found in **config/app_form.php**. This file should +contain an array of templates *indexed by name*:: + + // in config/app_form.php + return [ + 'inputContainer' => '
    {{content}}
    ', + ]; + +Any templates you define will replace the default ones included in the helper. +Templates that are not replaced, will continue to use the default values. + +You can also change the templates at runtime using the ``setTemplates()`` method:: + + $myTemplates = [ + 'inputContainer' => '
    {{content}}
    ', + ]; + $this->Form->setTemplates($myTemplates); + // Prior to 3.4 + $this->Form->templates($myTemplates); + +.. warning:: + + Template strings containing a percentage sign (``%``) need special attention; + you should prefix this character with another percentage so it looks like + ``%%``. The reason is that internally templates are compiled to be used with + ``sprintf()``. Example: ``'
    {{content}}
    '`` + +List of Templates +----------------- + +The list of default templates, their default format and the variables they +expect can be found in the +`FormHelper API documentation `_. + +Using Distinct Custom Control Containers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +In addition to these templates, the ``control()`` method will attempt to use +distinct templates for each control container. For example, when creating +a datetime control the ``datetimeContainer`` will be used if it is present. +If that container is missing the ``inputContainer`` template will be used. + +For example:: + + // Add custom radio wrapping HTML + $this->Form->setTemplates([ + 'radioContainer' => '
    {{content}}
    ' + ]); + + // Create a radio set with our custom wrapping div. + echo $this->Form->control('User.email_notifications', [ + 'options' => ['y', 'n'], + 'type' => 'radio' + ]); + +Using Distinct Custom Form Groups +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Similar to controlling containers, the ``control()`` method will also attempt to use +distinct templates for each form group. A form group is a combo of label and +control. For example, when creating a radio control the ``radioFormGroup`` will be +used if it is present. If that template is missing by default each set of ``label`` +& ``input`` is rendered using the default ``formGroup`` template. + +For example:: + + // Add custom radio form group + $this->Form->setTemplates([ + 'radioFormGroup' => '
    {{label}}{{input}}
    ' + ]); + +Adding Additional Template Variables to Templates +------------------------------------------------- + +You can add additional template placeholders in custom templates, and populate +those placeholders when generating controls. + +E.g. :: + + // Add a template with the help placeholder. + $this->Form->setTemplates([ + 'inputContainer' => '
    + {{content}} {{help}}
    ' + ]); + + // Generate an input and populate the help variable + echo $this->Form->control('password', [ + 'templateVars' => ['help' => 'At least 8 characters long.'] + ]); + +Output: + +.. code-block:: html + +
    + + + At least 8 characters long. +
    + +.. versionadded:: 3.1 + The templateVars option was added in 3.1.0 + +Moving Checkboxes & Radios Outside of a Label +--------------------------------------------- + +By default CakePHP nests checkboxes created via ``control()`` and radio buttons +created by both ``control()`` and ``radio()`` within label elements. +This helps make it easier to integrate popular CSS frameworks. If you need to +place checkbox/radio inputs outside of the label you can do so by modifying the +templates:: + + $this->Form->setTemplates([ + 'nestingLabel' => '{{hidden}}{{input}}{{text}}', + 'formGroup' => '{{input}}{{label}}', + ]); + +This will make radio buttons and checkboxes render outside of their labels. + +Generating Entire Forms +======================= + +Creating Multiple Controls +-------------------------- + +.. php:method:: controls(array $fields = [], $options = []) + +* ``$fields`` - An array of fields to generate. Allows setting + custom types, labels and other options for each specified field. +* ``$options`` - Optional. An array of options. Valid keys are: + + #. ``'fieldset'`` - Set this to ``false`` to disable the fieldset. + If empty, the fieldset will be enabled. Can also be an array of parameters + to be applied as HTML attributes to the ``fieldset`` tag. + #. ``legend`` - String used to customize the ``legend`` text. Set this to + ``false`` to disable the legend for the generated input set. + +Generates a set of controls for the given context wrapped in a +``fieldset``. You can specify the generated fields by including them:: + + echo $this->Form->controls([ + 'name', + 'email' + ]); + +You can customize the legend text using an option:: + + echo $this->Form->controls($fields, ['legend' => 'Update news post']); + +You can customize the generated controls by defining additional options in the +``$fields`` parameter:: + + echo $this->Form->controls([ + 'name' => ['label' => 'custom label'] + ]); + +When customizing, ``$fields``, you can use the ``$options`` parameter to +control the generated legend/fieldset. + +For example:: + + echo $this->Form->controls( + [ + 'name' => ['label' => 'custom label'] + ], + ['legend' => 'Update your post'] + ); + +If you disable the ``fieldset``, the ``legend`` will not print. + +Creating Controls for a Whole Entity +------------------------------------ + +.. php:method:: allControls(array $fields, $options = []) + +* ``$fields`` - Optional. An array of customizations for the fields that will + be generated. Allows setting custom types, labels and other options. +* ``$options`` - Optional. An array of options. Valid keys are: + + #. ``'fieldset'`` - Set this to ``false`` to disable the fieldset. + If empty, the fieldset will be enabled. Can also be an array of + parameters to be applied as HTMl attributes to the ``fieldset`` tag. + #. ``legend`` - String used to customize the ``legend`` text. Set this to + ``false`` to disable the legend for the generated control set. + +This method is closely related to ``controls()``, however the ``$fields`` argument +is defaulted to *all* fields in the current top-level entity. To exclude +specific fields from the generated controls, set them to ``false`` in the +``$fields`` parameter:: + + echo $this->Form->allControls(['password' => false]); + // Or prior to 3.4.0: + echo $this->Form->allInputs(['password' => false]); + +.. _associated-form-inputs: + +Creating Inputs for Associated Data +=================================== + +Creating forms for associated data is straightforward and is closely related to +the paths in your entity's data. Assuming the following table relations: + +* Authors HasOne Profiles +* Authors HasMany Articles +* Articles HasMany Comments +* Articles BelongsTo Authors +* Articles BelongsToMany Tags + +If we were editing an article with its associations loaded we could +create the following controls:: + + $this->Form->create($article); + + // Article controls. + echo $this->Form->control('title'); + + // Author controls (belongsTo) + echo $this->Form->control('author.id'); + echo $this->Form->control('author.first_name'); + echo $this->Form->control('author.last_name'); + + // Author profile (belongsTo + hasOne) + echo $this->Form->control('author.profile.id'); + echo $this->Form->control('author.profile.username'); + + // Tags controls (belongsToMany) + echo $this->Form->control('tags.0.id'); + echo $this->Form->control('tags.0.name'); + echo $this->Form->control('tags.1.id'); + echo $this->Form->control('tags.1.name'); + + // Multiple select element for belongsToMany + echo $this->Form->control('tags._ids', [ + 'type' => 'select', + 'multiple' => true, + 'options' => $tagList, + ]); + + // Inputs for the joint table (articles_tags) + echo $this->Form->control('tags.0._joinData.starred'); + echo $this->Form->control('tags.1._joinData.starred'); + + // Comments controls (hasMany) + echo $this->Form->control('comments.0.id'); + echo $this->Form->control('comments.0.comment'); + echo $this->Form->control('comments.1.id'); + echo $this->Form->control('comments.1.comment'); + +The above controls could then be marshalled into a completed entity graph using +the following code in your controller:: + + $article = $this->Articles->patchEntity($article, $this->request->getData(), [ + 'associated' => [ + 'Authors', + 'Authors.Profiles', + 'Tags', + 'Comments' + ] + ]); + +Adding Custom Widgets +===================== + +CakePHP makes it easy to add custom control widgets in your application, and use +them like any other control type. All of the core control types are implemented as +widgets, which means you can override any core widget with your own +implementation as well. + +Building a Widget Class +----------------------- + +Widget classes have a very simple required interface. They must implement the +:php:class:`Cake\\View\\Widget\\WidgetInterface`. This interface requires +the ``render(array $data)`` and ``secureFields(array $data)`` methods to be +implemented. The ``render()`` method expects an array of data to build the +widget and is expected to return a string of HTML for the widget. +The ``secureFields()`` method expects an array of data as well and is expected +to return an array containing the list of fields to secure for this widget. +If CakePHP is constructing your widget you can expect to +get a ``Cake\View\StringTemplate`` instance as the first argument, followed by +any dependencies you define. If we wanted to build an Autocomplete widget you +could do the following:: + + namespace App\View\Widget; + + use Cake\View\Form\ContextInterface; + use Cake\View\Widget\WidgetInterface; + + class AutocompleteWidget implements WidgetInterface + { + + protected $_templates; + + public function __construct($templates) + { + $this->_templates = $templates; + } + + public function render(array $data, ContextInterface $context) + { + $data += [ + 'name' => '', + ]; + return $this->_templates->format('autocomplete', [ + 'name' => $data['name'], + 'attrs' => $this->_templates->formatAttributes($data, ['name']) + ]); + } + + public function secureFields(array $data) + { + return [$data['name']]; + } + } + +Obviously, this is a very simple example, but it demonstrates how a custom +widget could be built. This widget would render the "autocomplete" string +template, such as:: + + $this->Form->setTemplates([ + 'autocomplete' => '' + ]); + +For more information on string templates, see :ref:`customizing-templates`. + +Using Widgets +------------- + +You can load custom widgets when loading FormHelper or by using the +``addWidget()`` method. When loading FormHelper, widgets are defined as +a setting:: + + // In View class + $this->loadHelper('Form', [ + 'widgets' => [ + 'autocomplete' => ['Autocomplete'] + ] + ]); + +If your widget requires other widgets, you can have FormHelper populate those +dependencies by declaring them:: + + $this->loadHelper('Form', [ + 'widgets' => [ + 'autocomplete' => [ + 'App\View\Widget\AutocompleteWidget', + 'text', + 'label' + ] + ] + ]); + +In the above example, the ``autocomplete`` widget would depend on the ``text`` and +``label`` widgets. If your widget needs access to the View, you should use the +``_view`` 'widget'. When the ``autocomplete`` widget is created, it will be passed +the widget objects that are related to the ``text`` and ``label`` names. To add +widgets using the ``addWidget()`` method would look like:: + + // Using a classname. + $this->Form->addWidget( + 'autocomplete', + ['Autocomplete', 'text', 'label'] + ); + + // Using an instance - requires you to resolve dependencies. + $autocomplete = new AutocompleteWidget( + $this->Form->getTemplater(), + $this->Form->widgetRegistry()->get('text'), + $this->Form->widgetRegistry()->get('label'), + ); + $this->Form->addWidget('autocomplete', $autocomplete); + +Once added/replaced, widgets can be used as the control 'type':: + + echo $this->Form->control('search', ['type' => 'autocomplete']); + +This will create the custom widget with a ``label`` and wrapping ``div`` just +like ``controls()`` always does. Alternatively, you can create just the control +widget using the magic method:: + + echo $this->Form->autocomplete('search', $options); + +Working with SecurityComponent +============================== + +:php:meth:`Cake\\Controller\\Component\\SecurityComponent` offers several +features that make your forms safer and more secure. By simply including the +``SecurityComponent`` in your controller, you'll automatically benefit from +form tampering-prevention features. + +As mentioned previously when using SecurityComponent, you should always close +your forms using :php:meth:`~Cake\\View\\Helper\\FormHelper::end()`. This will +ensure that the special ``_Token`` inputs are generated. + +.. php:method:: unlockField($name) + +* ``$name`` - Optional. The dot-separated name for the field. + +Unlocks a field making it exempt from the ``SecurityComponent`` field +hashing. This also allows the fields to be manipulated by JavaScript. +The ``$name`` parameter should be the entity property name for the field:: + + $this->Form->unlockField('id'); + +.. php:method:: secure(array $fields = [], array $secureAttributes = []) + +* ``$fields`` - Optional. An array containing the list of fields to use when + generating the hash. If not provided, then ``$this->fields`` will be used. +* ``$secureAttributes`` - Optional. An array of HTML attributes to be passed + into the generated hidden input elements. + +Generates a hidden ``input`` field with a security hash based on the fields used +in the form or an empty string when secured forms are not in use. +If ``$secureAttributes`` is set, these HTML attributes will be +merged into the hidden input tags generated for the SecurityComponent. This is +especially useful to set HTML5 attributes like ``'form'``. + +.. meta:: + :title lang=en: FormHelper + :description lang=en: The FormHelper focuses on creating forms quickly, in a way that will streamline validation, re-population and layout. + :keywords lang=en: form helper,cakephp form,form create,form input,form select,form file field,form label,form text,form password,form checkbox,form radio,form submit,form date time,form error,validate upload,unlock field,form security diff --git a/tl/views/helpers/html.rst b/tl/views/helpers/html.rst new file mode 100644 index 0000000000000000000000000000000000000000..b074e25193417a9f4b544806bb85631834581664 --- /dev/null +++ b/tl/views/helpers/html.rst @@ -0,0 +1,849 @@ +Html +#### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: HtmlHelper(View $view, array $config = []) + +The role of the HtmlHelper in CakePHP is to make HTML-related +options easier, faster, and more resilient to change. Using this +helper will enable your application to be more light on its feet, +and more flexible on where it is placed in relation to the root of +a domain. + +Many HtmlHelper methods include a ``$attributes`` parameter, +that allow you to tack on any extra attributes on your tags. Here +are a few examples of how to use the ``$attributes`` parameter: + +.. code-block:: html + + Desired attributes: + Array parameter: ['class' => 'someClass'] + + Desired attributes: + Array parameter: ['name' => 'foo', 'value' => 'bar'] + +Inserting Well-Formatted Elements +================================= + +The most important task the HtmlHelper accomplishes is creating +well formed markup. This section will cover some of the +methods of the HtmlHelper and how to use them. + +Creating Charset Tags +--------------------- + +.. php:method:: charset($charset=null) + +Used to create a meta tag specifying the document's character. The default value +is UTF-8. An example use:: + + echo $this->Html->charset(); + +Will output: + +.. code-block:: html + + + +Alternatively, :: + + echo $this->Html->charset('ISO-8859-1'); + +Will output: + +.. code-block:: html + + + +Linking to CSS Files +-------------------- + +.. php:method:: css(mixed $path, array $options = []) + +Creates a link(s) to a CSS style-sheet. If the ``block`` option is set to +``true``, the link tags are added to the ``css`` block which you can print +inside the head tag of the document. + +You can use the ``block`` option to control which block the link element +will be appended to. By default it will append to the ``css`` block. + +If key 'rel' in ``$options`` array is set to 'import' the stylesheet will be imported. + +This method of CSS inclusion assumes that the CSS file specified +resides inside the **webroot/css** directory if path doesn't start with a '/'. :: + + echo $this->Html->css('forms'); + +Will output: + +.. code-block:: html + + + +The first parameter can be an array to include multiple files. :: + + echo $this->Html->css(['forms', 'tables', 'menu']); + +Will output: + +.. code-block:: html + + + + + +You can include CSS files from any loaded plugin using +:term:`plugin syntax`. To include **plugins/DebugKit/webroot/css/toolbar.css** +you could use the following:: + + echo $this->Html->css('DebugKit.toolbar.css'); + +If you want to include a CSS file which shares a name with a loaded +plugin you can do the following. For example if you had a ``Blog`` plugin, +and also wanted to include **webroot/css/Blog.common.css**, you would:: + + echo $this->Html->css('Blog.common.css', ['plugin' => false]); + +Creating CSS Programatically +---------------------------- + +.. php:method:: style(array $data, boolean $oneline = true) + +Builds CSS style definitions based on the keys and values of the +array passed to the method. Especially handy if your CSS file is +dynamic. :: + + echo $this->Html->style([ + 'background' => '#633', + 'border-bottom' => '1px solid #000', + 'padding' => '10px' + ]); + +Will output:: + + background:#633; border-bottom:1px solid #000; padding:10px; + +Creating meta Tags +------------------ + +.. php:method:: meta(string|array $type, string $url = null, array $options = []) + +This method is handy for linking to external resources like RSS/Atom feeds +and favicons. Like css(), you can specify whether or not you'd like this tag +to appear inline or appended to the ``meta`` block by setting the 'block' +key in the $attributes parameter to ``true``, ie - ``['block' => true]``. + +If you set the "type" attribute using the $attributes parameter, +CakePHP contains a few shortcuts: + +======== ====================== + type translated value +======== ====================== +html text/html +rss application/rss+xml +atom application/atom+xml +icon image/x-icon +======== ====================== + +.. code-block:: php + + Html->meta( + 'favicon.ico', + '/favicon.ico', + ['type' => 'icon'] + ); + ?> + // Output (line breaks added) + + Html->meta( + 'Comments', + '/comments/index.rss', + ['type' => 'rss'] + ); + ?> + // Output (line breaks added) + + +This method can also be used to add the meta keywords and +descriptions. Example:: + + Html->meta( + 'keywords', + 'enter any meta keyword here' + ); + ?> + // Output + + + Html->meta( + 'description', + 'enter any meta description here' + ); + ?> + // Output + + +In addition to making predefined meta tags, you can create link elements:: + + Html->meta([ + 'link' => 'http://example.com/manifest', + 'rel' => 'manifest' + ]); + ?> + // Output + + +Any attributes provided to meta() when called this way will be added to the +generated link tag. + +Creating DOCTYPE +---------------- + +.. php:method:: docType(string $type = 'html5') + +Returns a (X)HTML DOCTYPE (document type declaration). Supply the document +type according to the following table: + ++--------------------------+----------------------------------+ +| type | translated value | ++==========================+==================================+ +| html4-strict | HTML 4.01 Strict | ++--------------------------+----------------------------------+ +| html4-trans | HTML 4.01 Transitional | ++--------------------------+----------------------------------+ +| html4-frame | HTML 4.01 Frameset | ++--------------------------+----------------------------------+ +| html5 (default) | HTML5 | ++--------------------------+----------------------------------+ +| xhtml-strict | XHTML 1.0 Strict | ++--------------------------+----------------------------------+ +| xhtml-trans | XHTML 1.0 Transitional | ++--------------------------+----------------------------------+ +| xhtml-frame | XHTML 1.0 Frameset | ++--------------------------+----------------------------------+ +| xhtml11 | XHTML 1.1 | ++--------------------------+----------------------------------+ + +:: + + echo $this->Html->docType(); + // Outputs: + + echo $this->Html->docType('html4-trans'); + // Outputs: + // + +Linking to Images +----------------- + +.. php:method:: image(string $path, array $options = []) + +Creates a formatted image tag. The path supplied should be relative +to **webroot/img/**. :: + + echo $this->Html->image('cake_logo.png', ['alt' => 'CakePHP']); + +Will output: + +.. code-block:: html + + CakePHP + +To create an image link specify the link destination using the +``url`` option in ``$attributes``. :: + + echo $this->Html->image("recipes/6.jpg", [ + "alt" => "Brownies", + 'url' => ['controller' => 'Recipes', 'action' => 'view', 6] + ]); + +Will output: + +.. code-block:: html + +
    + Brownies + + +If you are creating images in emails, or want absolute paths to images you +can use the ``fullBase`` option:: + + echo $this->Html->image("logo.png", ['fullBase' => true]); + +Will output: + +.. code-block:: html + + + +You can include image files from any loaded plugin using +:term:`plugin syntax`. To include **plugins/DebugKit/webroot/img/icon.png** +You could use the following:: + + echo $this->Html->image('DebugKit.icon.png'); + +If you want to include an image file which shares a name with a loaded +plugin you can do the following. For example if you had a ``Blog`` plugin, +and also wanted to include **webroot/img/Blog.icon.png**, you would:: + + echo $this->Html->image('Blog.icon.png', ['plugin' => false]); + +Creating Links +-------------- + +.. php:method:: link(string $title, mixed $url = null, array $options = []) + +General purpose method for creating HTML links. Use ``$options`` to +specify attributes for the element and whether or not the +``$title`` should be escaped. :: + + echo $this->Html->link( + 'Enter', + '/pages/home', + ['class' => 'button', 'target' => '_blank'] + ); + +Will output: + +.. code-block:: html + + Enter + +Use ``'_full'=>true`` option for absolute URLs:: + + echo $this->Html->link( + 'Dashboard', + ['controller' => 'Dashboards', 'action' => 'index', '_full' => true] + ); + +Will output: + +.. code-block:: html + + Dashboard + +Specify ``confirm`` key in options to display a JavaScript ``confirm()`` +dialog:: + + echo $this->Html->link( + 'Delete', + ['controller' => 'Recipes', 'action' => 'delete', 6], + ['confirm' => 'Are you sure you wish to delete this recipe?'] + ); + +Will output: + +.. code-block:: html + + + Delete + + +Query strings can also be created with ``link()``. :: + + echo $this->Html->link('View image', [ + 'controller' => 'Images', + 'action' => 'view', + 1, + '?' => ['height' => 400, 'width' => 500] + ]); + +Will output: + +.. code-block:: html + + View image + +HTML special characters in ``$title`` will be converted to HTML +entities. To disable this conversion, set the escape option to +``false`` in the ``$options`` array. :: + + echo $this->Html->link( + $this->Html->image("recipes/6.jpg", ["alt" => "Brownies"]), + "recipes/view/6", + ['escape' => false] + ); + +Will output: + +.. code-block:: html + + + Brownies + + +Setting ``escape`` to ``false`` will also disable escaping of attributes of the +link. You can use the option ``escapeTitle`` to disable just +escaping of title and not the attributes. :: + + echo $this->Html->link( + $this->Html->image('recipes/6.jpg', ['alt' => 'Brownies']), + 'recipes/view/6', + ['escapeTitle' => false, 'title' => 'hi "howdy"'] + ); + +Will output: + +.. code-block:: html + + + Brownies + + +Also check :php:meth:`Cake\\View\\Helper\\UrlHelper::build()` method +for more examples of different types of URLs. + +Linking to Videos and Audio Files +--------------------------------- + +.. php:method:: media(string|array $path, array $options) + +Options: + +- ``type`` Type of media element to generate, valid values are "audio" + or "video". If type is not provided media type is guessed based on + file's mime type. +- ``text`` Text to include inside the video tag +- ``pathPrefix`` Path prefix to use for relative URLs, defaults to + 'files/' +- ``fullBase`` If provided the src attribute will get a full address + including domain name + +Returns a formatted audio/video tag: + +.. code-block:: php + + Html->media('audio.mp3') ?> + + // Output + + + Html->media('video.mp4', [ + 'fullBase' => true, + 'text' => 'Fallback text' + ]) ?> + + // Output + + + Html->media( + ['video.mp4', ['src' => 'video.ogg', 'type' => "video/ogg; codecs='theora, vorbis'"]], + ['autoplay'] + ) ?> + + // Output + + +Linking to Javascript Files +--------------------------- + +.. php:method:: script(mixed $url, mixed $options) + +Include a script file(s), contained either locally or as a remote URL. + +By default, script tags are added to the document inline. If you override +this by setting ``$options['block']`` to ``true``, the script tags will instead +be added to the ``script`` block which you can print elsewhere in the document. +If you wish to override which block name is used, you can do so by setting +``$options['block']``. + +``$options['once']`` controls whether or +not you want to include this script once per request or more than +once. This defaults to ``true``. + +You can use $options to set additional properties to the +generated script tag. If an array of script tags is used, the +attributes will be applied to all of the generated script tags. + +This method of JavaScript file inclusion assumes that the +JavaScript file specified resides inside the **webroot/js** +directory:: + + echo $this->Html->script('scripts'); + +Will output: + +.. code-block:: html + + + +You can link to files with absolute paths as well to link files +that are not in **webroot/js**:: + + echo $this->Html->script('/otherdir/script_file'); + +You can also link to a remote URL:: + + echo $this->Html->script('http://code.jquery.com/jquery.min.js'); + +Will output: + +.. code-block:: html + + + +The first parameter can be an array to include multiple files. :: + + echo $this->Html->script(['jquery', 'wysiwyg', 'scripts']); + +Will output: + +.. code-block:: html + + + + + +You can append the script tag to a specific block using the ``block`` +option:: + + echo $this->Html->script('wysiwyg', ['block' => 'scriptBottom']); + +In your layout you can output all the script tags added to 'scriptBottom':: + + echo $this->fetch('scriptBottom'); + +You can include script files from any loaded plugin using +:term:`plugin syntax`. To include **plugins/DebugKit/webroot/js/toolbar.js** +You could use the following:: + + echo $this->Html->script('DebugKit.toolbar.js'); + +If you want to include a script file which shares a name with a loaded +plugin you can do the following. For example if you had a ``Blog`` plugin, +and also wanted to include **webroot/js/Blog.plugins.js**, you would:: + + echo $this->Html->script('Blog.plugins.js', ['plugin' => false]); + +Creating Inline Javascript Blocks +--------------------------------- + +.. php:method:: scriptBlock($code, $options = []) + +To generate Javascript blocks from PHP view code, you can use one of the script +block methods. Scripts can either be output in place, or buffered into a block:: + + // Define a script block all at once, with the defer attribute. + $this->Html->scriptBlock('alert("hi")', ['defer' => true]); + + // Buffer a script block to be output later. + $this->Html->scriptBlock('alert("hi")', ['block' => true]); + +.. php:method:: scriptStart($options = []) +.. php:method:: scriptEnd() + +You can use the ``scriptStart()`` method to create a capturing block that will +output into a ``' + ]); + +When loading files of templates, your file should look like:: + + '' + ]; + +.. warning:: + + Template strings containing a percentage sign (``%``) need special attention, + you should prefix this character with another percentage so it looks like + ``%%``. The reason is that internally templates are compiled to be used with + ``sprintf()``. Example: '
    {{content}}
    ' + +Creating Breadcrumb Trails with HtmlHelper +========================================== + +.. php:method:: addCrumb(string $name, string $link = null, mixed $options = null) +.. php:method:: getCrumbs(string $separator = '»', string $startText = false) +.. php:method:: getCrumbList(array $options = [], $startText = false) + +Many applications have breadcrumb trails to ease end user navigations. You can +create a breadcrumb trail in your app with some help from HtmlHelper. To make +bread crumbs, first the following in your layout +template:: + + echo $this->Html->getCrumbs(' > ', 'Home'); + +The ``$startText`` option can also accept an array. This gives more control +over the generated first link:: + + echo $this->Html->getCrumbs(' > ', [ + 'text' => $this->Html->image('home.png'), + 'url' => ['controller' => 'Pages', 'action' => 'display', 'home'], + 'escape' => false + ]); + +Any keys that are not ``text`` or ``url`` will be passed to +:php:meth:`~HtmlHelper::link()` as the ``$options`` parameter. + +Now, in your view you'll want to add the following to start the +breadcrumb trails on each of the pages:: + + $this->Html->addCrumb('Users', '/users'); + $this->Html->addCrumb('Add User', ['controller' => 'Users', 'action' => 'add']); + +This will add the output of "**Home > Users > Add User**" in your layout where +``getCrumbs`` was added. + +You can also fetch the crumbs formatted inside an HTML list:: + + echo $this->Html->getCrumbList(); + +As options you can use regular HTML parameter that fits in the ``
      `` +(Unordered List) such as ``class`` and for the specific options, you have: +``separator`` (will be between the ``
    • `` elements), ``firstClass`` and +``lastClass`` like:: + + echo $this->Html->getCrumbList( + [ + 'firstClass' => false, + 'lastClass' => 'active', + 'class' => 'breadcrumb' + ], + 'Home' + ); + +This method uses :php:meth:`Cake\\View\\Helper\\HtmlHelper::tag()` to generate +list and its elements. Works similar to +:php:meth:`~Cake\\View\\Helper\\HtmlHelper::getCrumbs()`, so it uses options +which every crumb was added with. You can use the ``$startText`` parameter to +provide the first breadcrumb link/text. This is useful when you always want to +include a root link. This option works the same as the ``$startText`` option for +:php:meth:`~Cake\\View\\Helper\\HtmlHelper::getCrumbs()`. + +.. meta:: + :title lang=en: HtmlHelper + :description lang=en: The role of the HtmlHelper in CakePHP is to make HTML-related options easier, faster, and more resilient to change. + :keywords lang=en: html helper,cakephp css,cakephp script,content type,html image,html link,html tag,script block,script start,html url,cakephp style,cakephp crumbs diff --git a/tl/views/helpers/number.rst b/tl/views/helpers/number.rst new file mode 100644 index 0000000000000000000000000000000000000000..df47fdeec36c5342bb3a0f33c1aab691e98d1527 --- /dev/null +++ b/tl/views/helpers/number.rst @@ -0,0 +1,25 @@ +Number +###### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: NumberHelper(View $view, array $config = []) + +The NumberHelper contains convenient methods that enable display +numbers in common formats in your views. These methods include ways +to format currency, percentages, data sizes, format numbers to +specific precisions and also to give you more flexibility with +formatting numbers. + +.. include:: /core-libraries/number.rst + :start-after: start-cakenumber + :end-before: end-cakenumber + +.. warning:: + + All symbols are UTF-8. + +.. meta:: + :title lang=en: NumberHelper + :description lang=en: The NumberHelper contains convenience methods that enable display numbers in common formats in your views. + :keywords lang=en: number helper,currency,number format,number precision,format file size,format numbers diff --git a/tl/views/helpers/paginator.rst b/tl/views/helpers/paginator.rst new file mode 100644 index 0000000000000000000000000000000000000000..874fa0e3e7d9b5e780b6f7b9817c7d39cbd66613 --- /dev/null +++ b/tl/views/helpers/paginator.rst @@ -0,0 +1,563 @@ +Paginator +######### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: PaginatorHelper(View $view, array $config = []) + +The PaginatorHelper is used to output pagination controls such as page numbers +and next/previous links. It works in tandem with +:php:class:`PaginatorComponent`. + +See also :doc:`/controllers/components/pagination` for information on how to +create paginated datasets and do paginated queries. + +.. _paginator-templates: + +PaginatorHelper Templates +========================= + +Internally PaginatorHelper uses a series of simple HTML templates to generate +markup. You can modify these templates to customize the HTML generated by the +PaginatorHelper. + +Templates use ``{{var}}`` style placeholders. It is important to not add any +spaces around the ``{{}}`` or the replacements will not work. + +Loading Templates from a File +----------------------------- + +When adding the PaginatorHelper in your controller, you can define the +'templates' setting to define a template file to load. This allows you to +customize multiple templates and keep your code DRY:: + + // In your AppView.php + public function initialize() + { + ... + $this->loadHelper('Paginator', ['templates' => 'paginator-templates']); + } + +This will load the file located at **config/paginator-templates.php**. See the +example below for how the file should look like. You can also load templates +from a plugin using :term:`plugin syntax`:: + + // In your AppView.php + public function initialize() + { + ... + $this->loadHelper('Paginator', ['templates' => 'MyPlugin.paginator-templates']); + } + +Whether your templates are in the primary application or a plugin, your +templates file should look something like:: + + return [ + 'number' => '{{text}}', + ]; + +Changing Templates at Run-time +------------------------------ + +.. php:method:: setTemplates($templates) + +This method allows you to change the templates used by PaginatorHelper at +runtime. This can be useful when you want to customize templates for a +particular method call:: + + // Read the current template value. + $result = $this->Paginator->getTemplates('number'); + // Prior to 3.4 + $result = $this->Paginator->templates('number'); + + // Change a template + $this->Paginator->setTemplates([ + 'number' => '{{text}}' + ]); + +.. warning:: + + Template strings containing a percentage sign (``%``) need special + attention, you should prefix this character with another percentage so it + looks like ``%%``. The reason is that internally templates are compiled to + be used with ``sprintf()``. + Example: '
      {{content}}
      ' + +Template Names +-------------- + +PaginatorHelper uses the following templates: + +- ``nextActive`` The active state for a link generated by next(). +- ``nextDisabled`` The disabled state for next(). +- ``prevActive`` The active state for a link generated by prev(). +- ``prevDisabled`` The disabled state for prev() +- ``counterRange`` The template counter() uses when format == range. +- ``counterPages`` The template counter() uses when format == pages. +- ``first`` The template used for a link generated by first(). +- ``last`` The template used for a link generated by last() +- ``number`` The template used for a link generated by numbers(). +- ``current`` The template used for the current page. +- ``ellipsis`` The template used for ellipses generated by numbers(). +- ``sort`` The template for a sort link with no direction. +- ``sortAsc`` The template for a sort link with an ascending direction. +- ``sortDesc`` The template for a sort link with a descending direction. + +Creating Sort Links +=================== + +.. php:method:: sort($key, $title = null, $options = []) + + :param string $key: The name of the column that the recordset should be sorted. + :param string $title: Title for the link. If $title is null, $key will be + used converted to "Title Case" format and used as the title. + :param array $options: Options for sorting link. + +Generates a sorting link. Sets querystring parameters for the sort and +direction. Links will default to sorting by asc. After the first click, links +generated with ``sort()`` will handle direction switching automatically. If the +resultset is sorted 'asc' by the specified key the returned link will sort by +'desc'. + +Accepted keys for ``$options``: + +* ``escape`` Whether you want the contents HTML entity encoded, defaults to + ``true``. +* ``model`` The model to use, defaults to :php:meth:`PaginatorHelper::defaultModel()`. +* ``direction`` The default direction to use when this link isn't active. +* ``lock`` Lock direction. Will only use the default direction then, defaults to ``false``. + +Assuming you are paginating some posts, and are on page one:: + + echo $this->Paginator->sort('user_id'); + +Output: + +.. code-block:: html + + User Id + +You can use the title parameter to create custom text for your link:: + + echo $this->Paginator->sort('user_id', 'User account'); + +Output: + +.. code-block:: html + + User account + +If you are using HTML like images in your links remember to set escaping off:: + + echo $this->Paginator->sort( + 'user_id', + 'User account', + ['escape' => false] + ); + +Output: + +.. code-block:: html + + User account + +The direction option can be used to set the default direction for a link. Once a +link is active, it will automatically switch directions like normal:: + + echo $this->Paginator->sort('user_id', null, ['direction' => 'desc']); + +Output: + +.. code-block:: html + + User Id + +The lock option can be used to lock sorting into the specified direction:: + + echo $this->Paginator->sort('user_id', null, ['direction' => 'asc', 'lock' => true]); + +.. php:method:: sortDir(string $model = null, mixed $options = []) + + Gets the current direction the recordset is sorted. + +.. php:method:: sortKey(string $model = null, mixed $options = []) + + Gets the current key by which the recordset is sorted. + +Creating Page Number Links +========================== + +.. php:method:: numbers($options = []) + +Returns a set of numbers for the paged result set. Uses a modulus to +decide how many numbers to show on each side of the current page By default +8 links on either side of the current page will be created if those pages exist. +Links will not be generated for pages that do not exist. The current page is +also not a link. + +Supported options are: + +* ``before`` Content to be inserted before the numbers. +* ``after`` Content to be inserted after the numbers. +* ``model`` Model to create numbers for, defaults to + :php:meth:`PaginatorHelper::defaultModel()`. +* ``modulus`` how many numbers to include on either side of the current page, + defaults to 8. +* ``first`` Whether you want first links generated, set to an integer to + define the number of 'first' links to generate. Defaults to ``false``. If a + string is set a link to the first page will be generated with the value as the + title:: + + echo $this->Paginator->numbers(['first' => 'First page']); + +* ``last`` Whether you want last links generated, set to an integer to define + the number of 'last' links to generate. Defaults to ``false``. Follows the same + logic as the ``first`` option. There is a + :php:meth:`~PaginatorHelper::last()` method to be used separately as well if + you wish. + +While this method allows a lot of customization for its output. It is +also ok to just call the method without any params. :: + + echo $this->Paginator->numbers(); + +Using the first and last options you can create links to the beginning +and end of the page set. The following would create a set of page links that +include links to the first 2 and last 2 pages in the paged results:: + + echo $this->Paginator->numbers(['first' => 2, 'last' => 2]); + +Creating Jump Links +=================== + +In addition to generating links that go directly to specific page numbers, +you'll often want links that go to the previous and next links, first and last +pages in the paged data set. + +.. php:method:: prev($title = '<< Previous', $options = []) + + :param string $title: Title for the link. + :param mixed $options: Options for pagination link. + + Generates a link to the previous page in a set of paged records. + + ``$options`` supports the following keys: + + * ``escape`` Whether you want the contents HTML entity encoded, + defaults to ``true``. + * ``model`` The model to use, defaults to :php:meth:`PaginatorHelper::defaultModel()`. + * ``disabledTitle`` The text to use when the link is disabled. Defaults to + the ``$title`` parameter. + + A simple example would be:: + + echo $this->Paginator->prev(' << ' . __('previous')); + + If you were currently on the second page of posts, you would get the following: + + .. code-block:: html + +
    • + + If there were no previous pages you would get: + + .. code-block:: html + + + + To change the templates used by this method see :ref:`paginator-templates`. + +.. php:method:: next($title = 'Next >>', $options = []) + + This method is identical to :php:meth:`~PaginatorHelper::prev()` with a few exceptions. It + creates links pointing to the next page instead of the previous one. It also + uses ``next`` as the rel attribute value instead of ``prev`` + +.. php:method:: first($first = '<< first', $options = []) + + Returns a first or set of numbers for the first pages. If a string is given, + then only a link to the first page with the provided text will be created:: + + echo $this->Paginator->first('< first'); + + The above creates a single link for the first page. Will output nothing if you + are on the first page. You can also use an integer to indicate how many first + paging links you want generated:: + + echo $this->Paginator->first(3); + + The above will create links for the first 3 pages, once you get to the third or + greater page. Prior to that nothing will be output. + + The options parameter accepts the following: + + - ``model`` The model to use defaults to :php:meth:`PaginatorHelper::defaultModel()` + - ``escape`` Whether or not the text should be escaped. Set to ``false`` if your + content contains HTML. + +.. php:method:: last($last = 'last >>', $options = []) + + This method works very much like the :php:meth:`~PaginatorHelper::first()` + method. It has a few differences though. It will not generate any links if you + are on the last page for a string values of ``$last``. For an integer value of + ``$last`` no links will be generated once the user is inside the range of last + pages. + +Creating Header Link Tags +========================= + +PaginatorHelper can be used to create pagination link tags in your page +```` elements:: + + // Create next/prev links for the current model. + echo $this->Paginator->meta(); + + // Create next/prev & first/last links for the current model. + echo $this->Paginator->meta(['first' => true, 'last' => true]); + +.. versionadded:: 3.4.0 + + The ``first`` and ``last`` options were added in 3.4.0 + +Checking the Pagination State +============================= + +.. php:method:: current(string $model = null) + + Gets the current page of the recordset for the given model:: + + // Our URL is: http://example.com/comments/view/page:3 + echo $this->Paginator->current('Comment'); + // Output is 3 + +.. php:method:: hasNext(string $model = null) + + Returns ``true`` if the given result set is not at the last page. + +.. php:method:: hasPrev(string $model = null) + + Returns ``true`` if the given result set is not at the first page. + +.. php:method:: hasPage(string $model = null, integer $page = 1) + + Returns ``true`` if the given result set has the page number given by ``$page``. + +.. php:method:: total(string $model = null) + + Returns the total number of pages for the provided model. + + .. versionadded:: 3.4.0 + +Creating a Page Counter +======================= + +.. php:method:: counter($options = []) + +Returns a counter string for the paged result set. Using a provided format +string and a number of options you can create localized and application +specific indicators of where a user is in the paged data set. + +There are a number of options for ``counter()``. The supported ones are: + +* ``format`` Format of the counter. Supported formats are 'range', 'pages' + and custom. Defaults to pages which would output like '1 of 10'. In the + custom mode the supplied string is parsed and tokens are replaced with + actual values. The available tokens are: + + - ``{{page}}`` - the current page displayed. + - ``{{pages}}`` - total number of pages. + - ``{{current}}`` - current number of records being shown. + - ``{{count}}`` - the total number of records in the result set. + - ``{{start}}`` - number of the first record being displayed. + - ``{{end}}`` - number of the last record being displayed. + - ``{{model}}`` - The pluralized human form of the model name. + If your model was 'RecipePage', ``{{model}}`` would be 'recipe pages'. + + You could also supply only a string to the counter method using the tokens + available. For example:: + + echo $this->Paginator->counter( + 'Page {{page}} of {{pages}}, showing {{current}} records out of + {{count}} total, starting on record {{start}}, ending on {{end}}' + ); + + Setting 'format' to range would output like '1 - 3 of 13':: + + echo $this->Paginator->counter([ + 'format' => 'range' + ]); + +* ``model`` The name of the model being paginated, defaults to + :php:meth:`PaginatorHelper::defaultModel()`. This is used in + conjunction with the custom string on 'format' option. + +Generating Pagination URLs +========================== + +.. php:method:: generateUrl(array $options = [], $model = null, $full = false) + +By default returns a full pagination URL string for use in non-standard contexts +(i.e. JavaScript). :: + + echo $this->Paginator->generateUrl(['sort' => 'title']); + +Creating a Limit Selectbox Control +================================== + +.. php:method:: limitControl(array $limits = [], $default = null, array $options = []) + +Create a dropdown control that changes the ``limit`` query parameter:: + + // Use the defaults. + echo $this->Paginator->limitControl(); + + // Define which limit options you want. + echo $this->Paginator->limitControl([25 => 25, 50 => 50]); + + // Custom limits and set the selected option + echo $this->Paginator->limitControl([25 => 25, 50 => 50], $user->perPage); + +The generated form and control will automatically submit on change. + +.. versionadded:: 3.5.0 + The ``limitControl()`` method was added in 3.5.0 + +Configuring Pagination Options +============================== + +.. php:method:: options($options = []) + +Sets all the options for the PaginatorHelper. Supported options are: + +* ``url`` The URL of the paginating action. 'url' has a few sub options as well: + + - ``sort`` The key that the records are sorted by. + - ``direction`` The direction of the sorting. Defaults to 'ASC'. + - ``page`` The page number to display. + + The above mentioned options can be used to force particular pages/directions. + You can also append additional URL content into all URLs generated in the + helper:: + + $this->Paginator->options([ + 'url' => [ + 'sort' => 'email', + 'direction' => 'desc', + 'page' => 6, + 'lang' => 'en' + ] + ]); + + The above adds the ``en`` route parameter to all links the helper will + generate. It will also create links with specific sort, direction and page + values. By default PaginatorHelper will merge in all of the current passed + arguments and query string parameters. + +* ``escape`` Defines if the title field for links should be HTML escaped. + Defaults to ``true``. + +* ``model`` The name of the model being paginated, defaults to + :php:meth:`PaginatorHelper::defaultModel()`. + +Example Usage +============= + +It's up to you to decide how to show records to the user, but most often this +will be done inside HTML tables. The examples below assume a tabular layout, but +the PaginatorHelper available in views doesn't always need to be restricted as +such. + +See the details on +`PaginatorHelper `_ in +the API. As mentioned, the PaginatorHelper also offers sorting features which +can be integrated into your table column headers: + +.. code-block:: php + + + + + + + + + + + + + +
      Paginator->sort('id', 'ID') ?>Paginator->sort('title', 'Title') ?>
      id ?> title) ?>
      + +The links output from the ``sort()`` method of the ``PaginatorHelper`` allow +users to click on table headers to toggle the sorting of the data by a given +field. + +It is also possible to sort a column based on associations: + +.. code-block:: php + + + + + + + + + + + + +
      Paginator->sort('title', 'Title') ?>Paginator->sort('Authors.name', 'Author') ?>
      title) ?> name) ?>
      + +The final ingredient to pagination display in views is the addition of page +navigation, also supplied by the PaginationHelper:: + + // Shows the page numbers + Paginator->numbers() ?> + + // Shows the next and previous links + Paginator->prev('« Previous') ?> + Paginator->next('Next »') ?> + + // Prints X of Y, where X is current page and Y is number of pages + Paginator->counter() ?> + +The wording output by the counter() method can also be customized using special +markers:: + + Paginator->counter([ + 'format' => 'Page {{page}} of {{pages}}, showing {{current}} records out of + {{count}} total, starting on record {{start}}, ending on {{end}}' + ]) ?> + +.. _paginator-helper-multiple: + +Paginating Multiple Results +=========================== + +If you are :ref:`paginating multiple queries ` +you'll need to set the ``model`` option when generating pagination related +elements. You can either use the ``model`` option on every method call you make +to ``PaginatorHelper``, or use ``options()`` to set the default model:: + + // Pass the model option + echo $this->Paginator->sort('title', ['model' => 'Articles']); + + // Set the default model. + $this->Paginator->options(['defaultModel' => 'Articles']); + echo $this->Paginator->sort('title'); + +By using the ``model`` option, ``PaginatorHelper`` will automatically use the +``scope`` defined in when the query was paginated. + +.. versionadded:: 3.3.0 + Multiple Pagination was added in 3.3.0 + +.. meta:: + :title lang=en: PaginatorHelper + :description lang=en: The PaginatorHelper is used to output pagination controls such as page numbers and next/previous links. + :keywords lang=en: paginator helper,pagination,sort,page number links,pagination in views,prev link,next link,last link,first link,page counter diff --git a/tl/views/helpers/rss.rst b/tl/views/helpers/rss.rst new file mode 100644 index 0000000000000000000000000000000000000000..f6cee5d20ff7d0d0faad519fdc968807094d9b9e --- /dev/null +++ b/tl/views/helpers/rss.rst @@ -0,0 +1,202 @@ +Rss +### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: RssHelper(View $view, array $config = []) + +The RssHelper makes generating XML for `RSS feeds `_ easy. + +.. deprecated:: 3.5.0 + RssHelper is deprecated as of 3.5.0, and will be removed in 4.0.0 + +Creating an RSS Feed with the RssHelper +======================================= + +This example assumes you have a Articles Controller, Articles Table and an +Article Entity already created and want to make an alternative view for RSS. + +Creating an XML/RSS version of ``articles/index`` is a snap with CakePHP. +After a few simple steps you can simply append the desired extension .rss to +``articles/index`` making your URL ``articles/index.rss``. Before we jump too +far ahead trying to get our webservice up and running we need to do a few +things. First extensions parsing needs to be activated, this is done in +**config/routes.php**:: + + Router::extensions('rss'); + +In the call above we've activated the .rss extension. When using +:php:meth:`Cake\\Routing\\Router::extensions()` you can pass a string or an +array of extensions as first argument. This will activate each +extension/content-type for use in your application. Now when the address +``articles/index.rss`` is requested you will get an XML version of +your ``articles/index``. However, first we need to edit the controller to +add in the rss-specific code. + +Controller Code +--------------- + +It is a good idea to add RequestHandler to your ArticlesController's +``initialize()`` method. This will allow a lot of automagic to occur:: + + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + +Before we can make an RSS version of our ``articles/index`` we need to get a few +things in order. It may be tempting to put the channel metadata in the +controller action and pass it to your view using the +:php:meth:`Cake\\Controller\\Controller::set()` method but this is +inappropriate. That information can also go in the view. That will come later +though, for now if you have a different set of logic for the data used to make +the RSS feed and the data for the HTML view you can use the +:php:meth:`Cake\\Controller\\Component\\RequestHandler::isRss()` method, +otherwise your controller can stay the same:: + + // Modify the Posts Controller action that corresponds to + // the action which deliver the rss feed, which is the + // Index action in our example. + + public function index() + { + if ($this->RequestHandler->isRss() ) { + $articles = $this->Articles + ->find() + ->limit(20) + ->order(['created' => 'desc']); + $this->set(compact('articles')); + } else { + // this is not an Rss request, so deliver + // data used by website's interface. + $this->paginate = [ + 'order' => ['created' => 'desc'], + 'limit' => 10 + ]; + $this->set('articles', $this->paginate($this->Articles)); + $this->set('_serialize', ['articles']); + } + } + +With all the View variables set we need to create an rss layout. + +Layout +------ + +An Rss layout is very simple, put the following contents in +**src/Template/Layout/rss/default.ctp**:: + + if (!isset($documentData)) { + $documentData = []; + } + if (!isset($channelData)) { + $channelData = []; + } + if (!isset($channelData['title'])) { + $channelData['title'] = $this->fetch('title'); + } + $channel = $this->Rss->channel([], $channelData, $this->fetch('content')); + echo $this->Rss->document($documentData, $channel); + +It doesn't look like much but thanks to the power in the ``RssHelper`` +it's doing a lot of lifting for us. We haven't set ``$documentData`` or +``$channelData`` in the controller, however in CakePHP your views +can pass variables back to the layout. Which is where our ``$channelData`` +array will come from setting all of the meta data for our feed. + +Next up is view file for my articles/index. Much like the layout file +we created, we need to create a **src/Template/Posts/rss/** directory and +create a new **index.ctp** inside that folder. The contents of the file +are below. + +View +---- + +Our view, located at **src/Template/Posts/rss/index.ctp**, begins by setting the +``$documentData`` and ``$channelData`` variables for the layout, these contain +all the metadata for our RSS feed. This is done by using the +:php:meth:`Cake\\View\\View::set()` method which is analogous to the +:php:meth:`Cake\\Controller\\Controller::set()` method. Here though we are +passing the channel's metadata back to the layout:: + + $this->set('channelData', [ + 'title' => __("Most Recent Posts"), + 'link' => $this->Url->build('/', true), + 'description' => __("Most recent posts."), + 'language' => 'en-us' + ]); + +The second part of the view generates the elements for the actual records of +the feed. This is accomplished by looping through the data that has been passed +to the view ($items) and using the :php:meth:`RssHelper::item()` method. The +other method you can use, :php:meth:`RssHelper::items()` which takes a callback +and an array of items for the feed. The callback method is usually called +``transformRss()``. There is one downfall to this method, which is that you +cannot use any of the other helper classes to prepare your data inside the +callback method because the scope inside the method does not include anything +that is not passed inside, thus not giving access to the TimeHelper or any +other helper that you may need. The :php:meth:`RssHelper::item()` transforms +the associative array into an element for each key value pair. + +.. note:: + + You will need to modify the $link variable as appropriate to + your application. You might also want to use a + :ref:`virtual property ` in your Entity. + +:: + + foreach ($articles as $article) { + $created = strtotime($article->created); + + $link = [ + 'controller' => 'Articles', + 'action' => 'view', + 'year' => date('Y', $created), + 'month' => date('m', $created), + 'day' => date('d', $created), + 'slug' => $article->slug + ]; + + // Remove & escape any HTML to make sure the feed content will validate. + $body = h(strip_tags($article->body)); + $body = $this->Text->truncate($body, 400, [ + 'ending' => '...', + 'exact' => true, + 'html' => true, + ]); + + echo $this->Rss->item([], [ + 'title' => $article->title, + 'link' => $link, + 'guid' => ['url' => $link, 'isPermaLink' => 'true'], + 'description' => $body, + 'pubDate' => $article->created + ]); + } + +You can see above that we can use the loop to prepare the data to be transformed +into XML elements. It is important to filter out any non-plain text characters +out of the description, especially if you are using a rich text editor for the +body of your blog. In the code above we used ``strip_tags()`` and +:php:func:`h()` to remove/escape any XML special characters from the content, +as they could cause validation errors. Once we have set up the data for the +feed, we can then use the :php:meth:`RssHelper::item()` method to create the XML +in RSS format. Once you have all this setup, you can test your RSS feed by going +to your site ``/posts/index.rss`` and you will see your new feed. It is always +important that you validate your RSS feed before making it live. This can be +done by visiting sites that validate the XML such as Feed Validator or the w3c +site at http://validator.w3.org/feed/. + +.. note:: + + You may need to set the value of 'debug' in your core configuration + to ``false`` to get a valid feed, because of the various debug + information added automagically under higher debug settings that + break XML syntax or feed validation rules. + +.. meta:: + :title lang=en: RssHelper + :description lang=en: The RssHelper makes generating XML for RSS feeds easy. + :keywords lang=en: rss helper,rss feed,isrss,rss item,channel data,document data,parse extensions,request handler diff --git a/tl/views/helpers/session.rst b/tl/views/helpers/session.rst new file mode 100644 index 0000000000000000000000000000000000000000..f1579f99aac2cb8213653afa8b5fed58209c473e --- /dev/null +++ b/tl/views/helpers/session.rst @@ -0,0 +1,50 @@ +Session +####### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: SessionHelper(View $view, array $config = []) + +.. deprecated:: 3.0.0 + The SessionHelper is deprecated in 3.x. Instead you should use either the + :doc:`FlashHelper ` or :ref:`access the + session via the request `. + +As a natural counterpart to the Session object, the Session +Helper replicates most of the object's functionality and makes it +available in your view. + +The major difference between the SessionHelper and the Session +object is that the helper does *not* have the ability to write +to the session. + +As with the session object, data is read by using +:term:`dot notation` array structures:: + + ['User' => [ + 'username' => 'super@example.com' + ]]; + +Given the previous array structure, the node would be accessed by +``User.username``, with the dot indicating the nested array. This +notation is used for all SessionHelper methods wherever a ``$key`` is +used. + +.. php:method:: read(string $key) + + :rtype: mixed + + Read from the Session. Returns a string or array depending on the + contents of the session. + +.. php:method:: check(string $key) + + :rtype: boolean + + Check to see whether a key is in the Session. Returns a boolean representing the + key's existence. + +.. meta:: + :title lang=en: SessionHelper + :description lang=en: The SessionHelper replicates most of the functionality and making it available in your view. + :keywords lang=en: session helper,flash messages,session flash,session read,session check diff --git a/tl/views/helpers/text.rst b/tl/views/helpers/text.rst new file mode 100644 index 0000000000000000000000000000000000000000..145c62c3c028ffd7e34abcb283c3655b09b80573 --- /dev/null +++ b/tl/views/helpers/text.rst @@ -0,0 +1,86 @@ +Text +#### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: TextHelper(View $view, array $config = []) + +The TextHelper contains methods to make text more usable and +friendly in your views. It aids in enabling links, formatting URLs, +creating excerpts of text around chosen words or phrases, +highlighting key words in blocks of text, and gracefully +truncating long stretches of text. + +Linking Email addresses +======================= + +.. php:method:: autoLinkEmails(string $text, array $options = []) + +Adds links to the well-formed email addresses in $text, according +to any options defined in ``$options`` (see +:php:meth:`HtmlHelper::link()`). :: + + $myText = 'For more information regarding our world-famous ' . + 'pastries and desserts, contact info@example.com'; + $linkedText = $this->Text->autoLinkEmails($myText); + +Output:: + + For more information regarding our world-famous pastries and desserts, + contact info@example.com + +This method automatically escapes its input. Use the ``escape`` +option to disable this if necessary. + +Linking URLs +============ + +.. php:method:: autoLinkUrls(string $text, array $options = []) + +Same as ``autoLinkEmails()``, only this method searches for +strings that start with https, http, ftp, or nntp and links them +appropriately. + +This method automatically escapes its input. Use the ``escape`` +option to disable this if necessary. + +Linking Both URLs and Email Addresses +===================================== + +.. php:method:: autoLink(string $text, array $options = []) + +Performs the functionality in both ``autoLinkUrls()`` and +``autoLinkEmails()`` on the supplied ``$text``. All URLs and emails +are linked appropriately given the supplied ``$options``. + +This method automatically escapes its input. Use the ``escape`` +option to disable this if necessary. + +Converting Text into Paragraphs +=============================== + +.. php:method:: autoParagraph(string $text) + +Adds proper

      around text where double-line returns are found, and
      where +single-line returns are found. :: + + $myText = 'For more information + regarding our world-famous pastries and desserts. + + contact info@example.com'; + $formattedText = $this->Text->autoParagraph($myText); + +Output:: + +

      For more information
      + regarding our world-famous pastries and desserts.

      +

      contact info@example.com

      + +.. include:: /core-libraries/text.rst + :start-after: start-text + :end-before: end-text + +.. meta:: + :title lang=en: TextHelper + :description lang=en: The TextHelper contains methods to make text more usable and friendly in your views. + :keywords lang=en: text helper,autoLinkEmails,autoLinkUrls,autoLink,excerpt,highlight,stripLinks,truncate,string text diff --git a/tl/views/helpers/time.rst b/tl/views/helpers/time.rst new file mode 100644 index 0000000000000000000000000000000000000000..c8b3f948a41d4c6317075725fb92fde42f78861b --- /dev/null +++ b/tl/views/helpers/time.rst @@ -0,0 +1,51 @@ +Time +#### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: TimeHelper(View $view, array $config = []) + +The TimeHelper allows for the quick processing of time related information. +The TimeHelper has two main tasks that it can perform: + +#. It can format time strings. +#. It can test time. + +Using the Helper +================ + +A common use of the TimeHelper is to offset the date and time to match a +user's time zone. Lets use a forum as an example. Your forum has many users who +may post messages at any time from any part of the world. An easy way to +manage the time is to save all dates and times as GMT+0 or UTC. Uncomment the +line ``date_default_timezone_set('UTC');`` in **config/bootstrap.php** to ensure +your application's time zone is set to GMT+0. + +Next add a time zone field to your users table and make the necessary +modifications to allow your users to set their time zone. Now that we know +the time zone of the logged in user we can correct the date and time on our +posts using the TimeHelper:: + + echo $this->Time->format( + $post->created, + \IntlDateFormatter::FULL, + null, + $user->time_zone + ); + // Will display 'Saturday, August 22, 2011 at 11:53:00 PM GMT' + // for a user in GMT+0. While displaying, + // 'Saturday, August 22, 2011 at 03:53 PM GMT-8:00' + // for a user in GMT-8 + +Most of TimeHelper's features are intended as backwards compatible interfaces +for applications that are upgrading from older versions of CakePHP. Because the +ORM returns :php:class:`Cake\\I18n\\Time` instances for every ``timestamp`` +and ``datetime`` column, you can use the methods there to do most tasks. +For example, to read about the accepted formatting strings take a look at the +`Cake\\I18n\\Time::i18nFormat() +`_ method. + +.. meta:: + :title lang=en: TimeHelper + :description lang=en: The TimeHelper will help you format time and test time. + :keywords lang=en: time helper,format time,timezone,unix epoch,time strings,time zone offset,utc,gmt diff --git a/tl/views/helpers/url.rst b/tl/views/helpers/url.rst new file mode 100644 index 0000000000000000000000000000000000000000..db6d45d43f55a1a44eb3744f03efc957de1618e0 --- /dev/null +++ b/tl/views/helpers/url.rst @@ -0,0 +1,130 @@ +Url +### + +.. php:namespace:: Cake\View\Helper + +.. php:class:: UrlHelper(View $view, array $config = []) + +The UrlHelper makes it easy for you to generate URL's from your other helpers. +It also gives you a single place to customize how URLs are generated by +overriding the core helper with an application one. See the +:ref:`aliasing-helpers` section for how to do this. + +Generating URLs +=============== + +.. php:method:: build(mixed $url = null, boolean|array $full = false) + +Returns a URL pointing to a combination of controller and action. +If ``$url`` is empty, it returns the ``REQUEST_URI``, otherwise it +generates the URL for the controller and action combo. If ``full`` is +``true``, the full base URL will be prepended to the result:: + + echo $this->Url->build([ + "controller" => "Posts", + "action" => "view", + "bar", + ]); + + // Output + /posts/view/bar + +Here are a few more usage examples: + +URL with extension:: + + echo $this->Url->build([ + "controller" => "Posts", + "action" => "list", + "_ext" => "rss", + ]); + + // Output + /posts/list.rss + +URL (starting with '/') with the full base URL prepended:: + + echo $this->Url->build('/posts', true); + + // Output + http://somedomain.com/posts + +URL with GET params and fragment anchor:: + + echo $this->Url->build([ + "controller" => "Posts", + "action" => "search", + "?" => ["foo" => "bar"], + "#" => "first", + ]); + + // Output + /posts/search?foo=bar#first + +The above example uses the ``?`` key which is useful when you want to be +explicit about the query string parameters you are using, or if you want a query +string parameter that shares a name with one of your route placeholders. + +URL for named route:: + + echo $this->Url->build(['_name' => 'product-page', 'slug' => 'i-m-slug']); + + // Assuming route is setup like: + // $router->connect( + // '/products/:slug', + // [ + // 'controller' => 'Products', + // 'action' => 'view', + // ], + // [ + // '_name' => 'product-page', + // ] + // ); + /products/i-m-slug + +The 2nd parameter allows you to define options controlling HTML escaping, and +whether or not the base path should be added:: + + $this->Url->build('/posts', [ + 'escape' => false, + 'fullBase' => true, + ]); + +URL with asset timestamp wrapped by a ````, here pre-loading +a font. Note: The file must exist and ``Configure::read('Asset.timestamp')`` +must return ``true`` or ``'force'`` for the timestamp to be appended:: + + echo $this->Html->meta([ + 'rel' => 'preload', + 'href' => $this->Url->assetUrl( + '/assets/fonts/yout-font-pack/your-font-name.woff2' + ), + 'as' => 'font', + ]); + +.. versionadded:: 3.3.5 + ``build()`` accepts an array as the 2nd argument as of 3.3.5 + +If you are generating URLs for CSS, Javascript or image files there are helper +methods for each of these asset types:: + + // Outputs /img/icon.png + $this->Url->image('icon.png'); + + // Outputs /js/app.js + $this->Url->script('app.js'); + + // Outputs /css/app.css + $this->Url->css('app.css'); + +.. versionadded:: 3.2.4 + The asset helper methods were added in 3.2.4. + +For further information check +`Router::url `_ +in the API. + +.. meta:: + :title lang=en: UrlHelper + :description lang=en: The role of the UrlHelper in CakePHP is to help build urls. + :keywords lang=en: url helper,url diff --git a/tl/views/json-and-xml-views.rst b/tl/views/json-and-xml-views.rst new file mode 100644 index 0000000000000000000000000000000000000000..6f63d680dda66c323e25d6f8fe1c4c0087e4fd24 --- /dev/null +++ b/tl/views/json-and-xml-views.rst @@ -0,0 +1,226 @@ +JSON and XML views +################## + +The ``JsonView`` and ``XmlView`` +let you create JSON and XML responses, and integrate with the +:php:class:`Cake\\Controller\\Component\\RequestHandlerComponent`. + +By enabling ``RequestHandlerComponent`` in your application, and enabling +support for the ``json`` and/or ``xml`` extensions, you can automatically +leverage the new view classes. ``JsonView`` and ``XmlView`` will be referred to +as data views for the rest of this page. + +There are two ways you can generate data views. The first is by using the +``_serialize`` key, and the second is by creating normal template files. + +Enabling Data Views in Your Application +======================================= + +Before you can use the data view classes, you'll first need to load the +:php:class:`Cake\\Controller\\Component\\RequestHandlerComponent` in your +controller:: + + public function initialize() + { + ... + $this->loadComponent('RequestHandler'); + } + +This can be done in your ``AppController`` and will enable automatic view class +switching on content types. You can also set the component up with the +``viewClassMap`` setting, to map types to your custom classes and/or map other +data types. + +You can optionally enable the json and/or xml extensions with +:ref:`file-extensions`. This will allow you to access the ``JSON``, ``XML`` or +any other special format views by using a custom URL ending with the name of the +response type as a file extension such as ``http://example.com/articles.json``. + +By default, when not enabling :ref:`file-extensions`, the request, the ``Accept`` +header is used for, selecting which type of format should be rendered to the +user. An example ``Accept`` format that is used to render ``JSON`` responses is +``application/json``. + +Using Data Views with the Serialize Key +======================================= + +The ``_serialize`` key is a special view variable that indicates which other +view variable(s) should be serialized when using a data view. This lets you skip +defining template files for your controller actions if you don't need to do any +custom formatting before your data is converted into json/xml. + +If you need to do any formatting or manipulation of your view variables before +generating the response, you should use template files. The value of +``_serialize`` can be either a string or an array of view variables to +serialize:: + + namespace App\Controller; + + class ArticlesController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function index() + { + // Set the view vars that have to be serialized. + $this->set('articles', $this->paginate()); + // Specify which view vars JsonView should serialize. + $this->set('_serialize', 'articles'); + } + } + +You can also define ``_serialize`` as an array of view variables to combine:: + + namespace App\Controller; + + class ArticlesController extends AppController + { + public function initialize() + { + parent::initialize(); + $this->loadComponent('RequestHandler'); + } + + public function index() + { + // Some code that created $articles and $comments + + // Set the view vars that have to be serialized. + $this->set(compact('articles', 'comments')); + + // Specify which view vars JsonView should serialize. + $this->set('_serialize', ['articles', 'comments']); + } + } + +Defining ``_serialize`` as an array has added the benefit of automatically +appending a top-level ```` element when using :php:class:`XmlView`. +If you use a string value for ``_serialize`` and XmlView, make sure that your +view variable has a single top-level element. Without a single top-level +element the Xml will fail to generate. + +Using a Data View with Template Files +===================================== + +You should use template files if you need to do some manipulation of your view +content before creating the final output. For example if we had articles, that had +a field containing generated HTML, we would probably want to omit that from a +JSON response. This is a situation where a view file would be useful:: + + // Controller code + class ArticlesController extends AppController + { + public function index() + { + $articles = $this->paginate('Articles'); + $this->set(compact('articles')); + } + } + + // View code - src/Template/Articles/json/index.ctp + foreach ($articles as &$article) { + unset($article->generated_html); + } + echo json_encode(compact('articles')); + +You can do more complex manipulations, or use helpers to do formatting as well. +The data view classes don't support layouts. They assume that the view file will +output the serialized content. + +.. note:: + As of 3.1.0 AppController, in the application skeleton automatically adds + ``'_serialize' => true`` to all XML/JSON requests. You will need to remove + this code from the beforeRender callback or set ``'_serialize' => false`` in + your controller's action if you want to use view files. + +Creating XML Views +================== + +.. php:class:: XmlView + +By default when using ``_serialize`` the XmlView will wrap your serialized +view variables with a ```` node. You can set a custom name for +this node using the ``_rootNode`` view variable. + +The XmlView class supports the ``_xmlOptions`` variable that allows you to +customize the options used to generate XML, e.g. ``tags`` vs ``attributes``. + +Creating JSON Views +=================== + +.. php:class:: JsonView + +The JsonView class supports the ``_jsonOptions`` variable that allows you to +customize the bit-mask used to generate JSON. See the +`json_encode `_ documentation for the valid +values of this option. + +For example, to serialize validation error output of CakePHP entities in a consistent form of JSON do:: + + // In your controller's action when saving failed + $this->set('errors', $articles->errors()); + $this->set('_jsonOptions', JSON_FORCE_OBJECT); + $this->set('_serialize', ['errors']); + +JSONP Responses +--------------- + +When using ``JsonView`` you can use the special view variable ``_jsonp`` to +enable returning a JSONP response. Setting it to ``true`` makes the view class +check if query string parameter named "callback" is set and if so wrap the json +response in the function name provided. If you want to use a custom query string +parameter name instead of "callback" set ``_jsonp`` to required name instead of +``true``. + +Example Usage +============= + +While the :doc:`RequestHandlerComponent +` can automatically set the view based +on the request content-type or extension, you could also handle view +mappings in your controller:: + + // src/Controller/VideosController.php + namespace App\Controller; + + use App\Controller\AppController; + use Cake\Network\Exception\NotFoundException; + + class VideosController extends AppController + { + public function export($format = '') + { + $format = strtolower($format); + + // Format to view mapping + $formats = [ + 'xml' => 'Xml', + 'json' => 'Json', + ]; + + // Error on unknown type + if (!isset($formats[$format])) { + throw new NotFoundException(__('Unknown format.')); + } + + // Set Out Format View + $this->viewBuilder()->className($formats[$format]); + + // Get data + $videos = $this->Videos->find('latest'); + + // Set Data View + $this->set(compact('videos')); + $this->set('_serialize', ['videos']); + + // Set Force Download + // Prior to 3.4.0 + // $this->response->download('report-' . date('YmdHis') . '.' . $format); + return $this->response->withDownload('report-' . date('YmdHis') . '.' . $format); + } + } + diff --git a/tl/views/themes.rst b/tl/views/themes.rst new file mode 100644 index 0000000000000000000000000000000000000000..0e4769fae8c6c2300044b265561ec5307da580a9 --- /dev/null +++ b/tl/views/themes.rst @@ -0,0 +1,78 @@ +Themes +###### + +Themes in CakePHP are simply plugins that focus on providing template files. +See the section on :ref:`plugin-create-your-own`. +You can take advantage of themes, making it easy to switch the look and feel of +your page quickly. In addition to template files, they can also provide helpers +and cells if your theming requires that. When using cells and helpers from your +theme, you will need to continue using the :term:`plugin syntax`. + +To use themes, set the theme name in your controller's action or +``beforeRender()`` callback:: + + class ExamplesController extends AppController + { + // For CakePHP before 3.1 + public $theme = 'Modern'; + + public function beforeRender(\Cake\Event\Event $event) + { + $this->viewBuilder()->setTheme('Modern'); + + // For CakePHP before 3.5 + $this->viewBuilder()->theme('Modern'); + } + } + +Theme template files need to be within a plugin with the same name. For example, +the above theme would be found in **plugins/Modern/src/Template**. +It's important to remember that CakePHP expects PascalCase plugin/theme names. Beyond +that, the folder structure within the **plugins/Modern/src/Template** folder is +exactly the same as **src/Template/**. + +For example, the view file for an edit action of a Posts controller would reside +at **plugins/Modern/src/Template/Posts/edit.ctp**. Layout files would reside in +**plugins/Modern/src/Template/Layout/**. You can provide customized templates +for plugins with a theme as well. If you had a plugin named 'Cms', that +contained a TagsController, the Modern theme could provide +**plugins/Modern/src/Template/Plugin/Cms/Tags/edit.ctp** to replace the edit +template in the plugin. + +If a view file can't be found in the theme, CakePHP will try to locate the view +file in the **src/Template/** folder. This way, you can create master template files +and simply override them on a case-by-case basis within your theme folder. + +If your theme also acts as a plugin, don't forget to ensure it is loaded in +**config/bootstrap.php**. For example:: + + /** + * Load our plugin theme residing in the folder /plugins/Modern + */ + Plugin::load('Modern'); + +Theme Assets +============ + +Because themes are standard CakePHP plugins, they can include any necessary +assets in their webroot directory. This allows for easy packaging and +distribution of themes. Whilst in development, requests for theme assets will be +handled by :php:class:`Cake\\Routing\\Dispatcher`. To improve performance for production +environments, it's recommended that you :ref:`symlink-assets`. + +All of CakePHP's built-in helpers are aware of themes and will create the +correct paths automatically. Like template files, if a file isn't in the theme +folder, it will default to the main webroot folder:: + + // When in a theme with the name of 'purple_cupcake' + $this->Html->css('main.css'); + + // creates a path like + /purple_cupcake/css/main.css + + // and links to + plugins/PurpleCupcake/webroot/css/main.css + +.. meta:: + :title lang=en: Themes + :keywords lang=en: production environments,theme folder,layout files,development requests,callback functions,folder structure,default view,dispatcher,symlink,case basis,layouts,assets,cakephp,themes,advantage