Skip to content

Commit

Permalink
Merge branch '2.3' into 2.5
Browse files Browse the repository at this point in the history
* 2.3: (33 commits)
  Fixing bad reference
  [#4743] parts -> part - subjective, but this sounds slightly better to me
  Update web_server_configuration.rst
  Update web_server_configuration.rst
  Update web_server_configuration.rst
  Add exception to console exception log
  [#4454] Re-wording section, but it may not actually be accurate
  More concrete explanation of validation groups
  Adding missing index/map documents
  [#4611] A few more tweaks and fixes
  Basically copying a section about upgrading other libraries down into the minor version section
  [#4611] Removing a few entries I meant to remove before
  [#4611] Making many tweaks thanks to guys like Javier, Wouter, Christian (xabbuh) and Stof
  Adding a guide about upgrading
  Fix typo and remove redundant sentence
  [Reference] Add default_locale config description
  Clarify tip for creating a new AppBundle
  Update forms.rst
  Rewroded some contents and fixed some lists of elements
  MAde some tweaks suggested by Wouter
  ...

Conflicts:
	book/http_cache.rst
  • Loading branch information
weaverryan committed Jan 3, 2015
2 parents 7be0dc6 + 0507225 commit 9f82c6f
Show file tree
Hide file tree
Showing 18 changed files with 523 additions and 174 deletions.
3 changes: 1 addition & 2 deletions best_practices/creating-the-project.rst
Expand Up @@ -152,8 +152,7 @@ that follows these best practices:
.. tip::

If you are using Symfony 2.6 or a newer version, the ``AppBundle`` bundle
is already generated for you. If you are using an older Symfony version,
If your Symfony installation doesn't come with a pre-generated ``AppBundle``,
you can generate it by hand executing this command:

.. code-block:: bash
Expand Down
4 changes: 2 additions & 2 deletions best_practices/forms.rst
Expand Up @@ -13,8 +13,8 @@ Building Forms
Define your forms as PHP classes.

The Form component allows you to build forms right inside your controller
code. Honestly, unless you need to reuse the form somewhere else, that's
totally fine. But for organization and reuse, we recommend that you define each
code. This is perfectly fine if you don't need to reuse the form somewhere else.
But for organization and reuse, we recommend that you define each
form in its own PHP class::

namespace AppBundle\Form;
Expand Down
1 change: 1 addition & 0 deletions best_practices/templates.rst
Expand Up @@ -153,6 +153,7 @@ name is irrelevant because you never use it in your own code):
app.twig.app_extension:
class: AppBundle\Twig\AppExtension
arguments: ["@markdown"]
public: false
tags:
- { name: twig.extension }
Expand Down
35 changes: 32 additions & 3 deletions book/forms.rst
Expand Up @@ -526,6 +526,7 @@ to an array callback::

use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
Expand All @@ -541,23 +542,51 @@ This will call the static method ``determineValidationGroups()`` on the
The Form object is passed as an argument to that method (see next example).
You can also define whole logic inline by using a ``Closure``::

use Acme\AcmeBundle\Entity\Client;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$data = $form->getData();
if (Entity\Client::TYPE_PERSON == $data->getType()) {
if (Client::TYPE_PERSON == $data->getType()) {
return array('person');
} else {
return array('company');
}

return array('company');
},
));
}

Using the ``validation_groups`` option overrides the default validation
group which is being used. If you want to validate the default constraints
of the entity as well you have to adjust the option as follows::

use Acme\AcmeBundle\Entity\Client;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

// ...
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => function(FormInterface $form) {
$data = $form->getData();
if (Client::TYPE_PERSON == $data->getType()) {
return array('Default', 'person');
}

return array('Default', 'company');
},
));
}

You can find more information about how the validation groups and the default constraints
work in the book section about :ref:`validation groups <book-validation-validation-groups>`.

.. index::
single: Forms; Validation groups based on clicked button

Expand Down
191 changes: 120 additions & 71 deletions book/http_cache.rst
Expand Up @@ -333,7 +333,14 @@ its creation more manageable::
// set a custom Cache-Control directive
$response->headers->addCacheControlDirective('must-revalidate', true);

Public vs private Responses
.. tip::

If you need to set cache headers for many different controller actions,
you might want to look into the FOSHttpCacheBundle_. It provides a way
to define cache headers based on the URL pattern and other request
properties.

Public vs Private Responses
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Both gateway and proxy caches are considered "shared" caches as the cached
Expand Down Expand Up @@ -399,9 +406,10 @@ header when none is set by the developer by following these rules:
``private`` directive automatically (except when ``s-maxage`` is set).

.. _http-expiration-validation:
.. _http-expiration-and-validation:

HTTP Expiration and Validation
------------------------------
HTTP Expiration, Validation and Invalidation
--------------------------------------------

The HTTP specification defines two caching models:

Expand All @@ -418,7 +426,9 @@ The HTTP specification defines two caching models:
header) to check if the page has changed since being cached.

The goal of both models is to never generate the same response twice by relying
on a cache to store and return "fresh" responses.
on a cache to store and return "fresh" responses. To achieve long caching times
but still provide updated content immediately, *cache invalidation* is
sometimes used.

.. sidebar:: Reading the HTTP Specification

Expand Down Expand Up @@ -772,7 +782,7 @@ at some interval (the expiration) to verify that the content is still valid.
annotations. See the `FrameworkExtraBundle documentation`_.

.. index::
pair: Cache; Configuration
pair: Cache; Configuration

More Response Methods
~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -800,8 +810,110 @@ Additionally, most cache-related HTTP headers can be set via the single
));

.. index::
single: Cache; ESI
single: ESI
single: Cache; Invalidation

.. _http-cache-invalidation:

Cache Invalidation
~~~~~~~~~~~~~~~~~~

"There are only two hard things in Computer Science: cache invalidation
and naming things." -- Phil Karlton

Once an URL is cached by a gateway cache, the cache will not ask the
application for that content anymore. This allows the cache to provide fast
responses and reduces the load on your application. However, you risk
delivering outdated content. A way out of this dilemma is to use long
cache lifetimes, but to actively notify the gateway cache when content
changes. Reverse proxies usually provide a channel to receive such
notifications, typically through special HTTP requests.

.. caution::

While cache invalidation is powerful, avoid it when possible. If you fail
to invalidate something, outdated caches will be served for a potentially
long time. Instead, use short cache lifetimes or use the validation model,
and adjust your controllers to perform efficient validation checks as
explained in :ref:`optimizing-cache-validation`.

Furthermore, since invalidation is a topic specific to each type of reverse
proxy, using this concept will tie you to a specific reverse proxy or need
additional efforts to support different proxies.

Sometimes, however, you need that extra performance you can get when
explicitly invalidating. For invalidation, your application needs to detect
when content changes and tell the cache to remove the URLs which contain
that data from its cache.

.. tip::

If you want to use cache invalidation, have a look at the
`FOSHttpCacheBundle`_. This bundle provides services to help with various
cache invalidation concepts, and also documents the configuration for the
a couple of common caching proxies.

If one content corresponds to one URL, the ``PURGE`` model works well.
You send a request to the cache proxy with the HTTP method ``PURGE`` (using
the word "PURGE" is a convention, technically this can be any string) instead
of ``GET`` and make the cache proxy detect this and remove the data from the
cache instead of going to Symfony to get a response.

Here is how you can configure the Symfony reverse proxy to support the
``PURGE`` HTTP method::

// app/AppCache.php

// ...
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class AppCache extends HttpCache
{
protected function invalidate(Request $request, $catch = false)
{
if ('PURGE' !== $request->getMethod()) {
return parent::invalidate($request, $catch);
}

if ('127.0.0.1' !== $request->getClientIp()) {
return new Response('Invalid HTTP method', Response::HTTP_BAD_REQUEST);
}

$response = new Response();
if ($this->getStore()->purge($request->getUri())) {
$response->setStatusCode(200, 'Purged');
} else {
$response->setStatusCode(200, 'Not found');
}

return $response;
}
}

.. caution::

You must protect the ``PURGE`` HTTP method somehow to avoid random people
purging your cached data.

**Purge** instructs the cache to drop a resource in *all its variants*
(according to the ``Vary`` header, see above). An alternative to purging is
**refreshing** a content. Refreshing means that the caching proxy is
instructed to discard its local cache and fetch the content again. This way,
the new content is already available in the cache. The drawback of refreshing
is that variants are not invalidated.

In many applications, the same content bit is used on various pages with
different URLs. More flexible concepts exist for those cases:

* **Banning** invalidates responses matching regular expressions on the
URL or other criteria;
* **Cache tagging** lets you add a tag for each content used in a response
so that you can invalidate all URLs containing a certain content.

.. index::
single: Cache; ESI
single: ESI

.. _edge-side-includes:

Expand Down Expand Up @@ -1048,70 +1160,6 @@ The ``render_esi`` helper supports two other useful options:
of ``continue`` indicating that, in the event of a failure, the gateway cache
will simply remove the ESI tag silently.

.. index::
single: Cache; Invalidation

.. _http-cache-invalidation:

Cache Invalidation
------------------

"There are only two hard things in Computer Science: cache invalidation
and naming things." -- Phil Karlton

You should never need to invalidate cached data because invalidation is already
taken into account natively in the HTTP cache models. If you use validation,
you never need to invalidate anything by definition; and if you use expiration
and need to invalidate a resource, it means that you set the expires date
too far away in the future.

.. note::

Since invalidation is a topic specific to each type of reverse proxy,
if you don't worry about invalidation, you can switch between reverse
proxies without changing anything in your application code.

Actually, all reverse proxies provide ways to purge cached data, but you
should avoid them as much as possible. The most standard way is to purge the
cache for a given URL by requesting it with the special ``PURGE`` HTTP method.

Here is how you can configure the Symfony reverse proxy to support the
``PURGE`` HTTP method::

// app/AppCache.php

// ...
use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

class AppCache extends HttpCache
{
protected function invalidate(Request $request, $catch = false)
{
if ('PURGE' !== $request->getMethod()) {
return parent::invalidate($request, $catch);
}

$response = new Response();
if ($this->getStore()->purge($request->getUri())) {
$response->setStatusCode(Response::HTTP_OK, 'Purged');
} else {
$response->setStatusCode(Response::HTTP_NOT_FOUND, 'Not purged');
}

return $response;
}
}

.. versionadded:: 2.4
Support for HTTP status code constants was introduced in Symfony 2.4.

.. caution::

You must protect the ``PURGE`` HTTP method somehow to avoid random people
purging your cached data.

Summary
-------

Expand Down Expand Up @@ -1139,3 +1187,4 @@ Learn more from the Cookbook
.. _`P6 - Caching: Browser and intermediary caches`: http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache
.. _`FrameworkExtraBundle documentation`: http://symfony.com/doc/current/bundles/SensioFrameworkExtraBundle/annotations/cache.html
.. _`ESI`: http://www.w3.org/TR/esi-lang
.. _`FOSHttpCacheBundle`: http://foshttpcachebundle.readthedocs.org/
3 changes: 1 addition & 2 deletions book/security.rst
Expand Up @@ -1141,8 +1141,7 @@ Next, you'll need to create a route for this URL (but not a controller):
return $collection;
And that's it! By sending a user to ``/logout`` (or whatever you configure
the ``path`` to be), Symfony will un-authenticate the current user. and
redirect them the homepage (the value defined by ``target``).
the ``path`` to be), Symfony will un-authenticate the current user.

Once the user has been logged out, they will be redirected to whatever path
is defined by the ``target`` parameter above (e.g. the ``homepage``).
Expand Down
30 changes: 29 additions & 1 deletion book/validation.rst
Expand Up @@ -817,11 +817,39 @@ With this configuration, there are three validation groups:
that belong to no other group.

``User``
Equivalent to all constraints of the ``User`` object in the ``Default`` group.
Equivalent to all constraints of the ``User`` object in the ``Default``
group. This is always the name of the class. The difference between this
and ``Default`` is explained below.

``registration``
Contains the constraints on the ``email`` and ``password`` fields only.

Constraints in the ``Default`` group of a class are the constraints that have either no
explicit group configured or that are configured to a group equal to the class name or
the string ``Default``.

.. caution::

When validating *just* the User object, there is no difference between the ``Default`` group
and the ``User`` group. But, there is a difference if ``User`` has embedded objects. For example,
imagine ``User`` has an ``address`` property that contains some ``Address`` object and that
you've added the :doc:`/reference/constraints/Valid` constraint to this property so that it's
validated when you validate the ``User`` object.

If you validate ``User`` using the ``Default`` group, then any constraints on the ``Address``
class that are in the ``Default`` group *will* be used. But, if you validate ``User`` using the
``User`` validation group, then only constraints on the ``Address`` class with the ``User``
group will be validated.

In other words, the ``Default`` group and the class name group (e.g. ``User``) are identical,
except when the class is embedded in another object that's actually the one being validated.

If you have inheritance (e.g. ``User extends BaseUser``) and you validate
with the class name of the subclass (i.e. ``User``), then all constraints
in the ``User`` and ``BaseUser`` will be validated. However, if you validate
using the base class (i.e. ``BaseUser``), then only the constraints in
the ``BaseUser`` group will be validated.

To tell the validator to use a specific group, pass one or more group names
as the third argument to the ``validate()`` method::

Expand Down

0 comments on commit 9f82c6f

Please sign in to comment.