diff --git a/.gitignore b/.gitignore deleted file mode 100644 index d8892ba..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -_static/logo-composer-transparent.png -_static/logo_silex.png -_static/twig-logo.png \ No newline at end of file diff --git a/.nojekyll b/.nojekyll deleted file mode 100644 index e69de29..0000000 diff --git a/CONTRIBUTING.html b/CONTRIBUTING.html deleted file mode 100644 index 1258959..0000000 --- a/CONTRIBUTING.html +++ /dev/null @@ -1,115 +0,0 @@ - - - - - - - Colaborando — Manual de Symfony2 en Español - - - - - - - - - - - - - - - - -
-
-
-
- -
-

Colaborando

-

¡Nos encantan los colaboradores! Para más información sobre cómo puedes contribuir a la documentación de Symfony, por favor lee [Colaborando en la documentación](<http://gitnacho.github.com/symfony-docs-es/contributing/documentation/overview.html>)

-
- - -
-
-
-
-
- Bifúrcame en GitHub - -
- -
- - -
-
- - -
- - - - - \ No newline at end of file diff --git a/_images/01-workflow.png b/_images/01-workflow.png deleted file mode 100644 index 44c32be..0000000 Binary files a/_images/01-workflow.png and /dev/null differ diff --git a/_images/02-kernel-request.png b/_images/02-kernel-request.png deleted file mode 100644 index f3ca215..0000000 Binary files a/_images/02-kernel-request.png and /dev/null differ diff --git a/_images/03-kernel-request-response.png b/_images/03-kernel-request-response.png deleted file mode 100644 index 817abec..0000000 Binary files a/_images/03-kernel-request-response.png and /dev/null differ diff --git a/_images/04-resolve-controller.png b/_images/04-resolve-controller.png deleted file mode 100644 index ae521aa..0000000 Binary files a/_images/04-resolve-controller.png and /dev/null differ diff --git a/_images/06-kernel-controller.png b/_images/06-kernel-controller.png deleted file mode 100644 index 5327e4b..0000000 Binary files a/_images/06-kernel-controller.png and /dev/null differ diff --git a/_images/07-controller-arguments.png b/_images/07-controller-arguments.png deleted file mode 100644 index a1c6ffc..0000000 Binary files a/_images/07-controller-arguments.png and /dev/null differ diff --git a/_images/08-call-controller.png b/_images/08-call-controller.png deleted file mode 100644 index bb74085..0000000 Binary files a/_images/08-call-controller.png and /dev/null differ diff --git a/_images/09-controller-returns-response.png b/_images/09-controller-returns-response.png deleted file mode 100644 index c155847..0000000 Binary files a/_images/09-controller-returns-response.png and /dev/null differ diff --git a/_images/10-kernel-view.png b/_images/10-kernel-view.png deleted file mode 100644 index f8f64f3..0000000 Binary files a/_images/10-kernel-view.png and /dev/null differ diff --git a/_images/11-kernel-exception.png b/_images/11-kernel-exception.png deleted file mode 100644 index 3bb0e39..0000000 Binary files a/_images/11-kernel-exception.png and /dev/null differ diff --git a/_images/DataTransformersTypes.png b/_images/DataTransformersTypes.png deleted file mode 100644 index 950acd3..0000000 Binary files a/_images/DataTransformersTypes.png and /dev/null differ diff --git a/_images/classdiagram.jpg b/_images/classdiagram.jpg deleted file mode 100644 index 8f0d017..0000000 Binary files a/_images/classdiagram.jpg and /dev/null differ diff --git a/_images/docs-pull-request-change-base.png b/_images/docs-pull-request-change-base.png deleted file mode 100644 index b0c36f4..0000000 Binary files a/_images/docs-pull-request-change-base.png and /dev/null differ diff --git a/_images/docs-pull-request.png b/_images/docs-pull-request.png deleted file mode 100644 index 7ec199e..0000000 Binary files a/_images/docs-pull-request.png and /dev/null differ diff --git a/_images/doctrine_image_1_es.png b/_images/doctrine_image_1_es.png deleted file mode 100644 index 530dcfe..0000000 Binary files a/_images/doctrine_image_1_es.png and /dev/null differ diff --git a/_images/doctrine_image_2_es.png b/_images/doctrine_image_2_es.png deleted file mode 100644 index 8ba8cfa..0000000 Binary files a/_images/doctrine_image_2_es.png and /dev/null differ diff --git a/_images/doctrine_image_3_es.png b/_images/doctrine_image_3_es.png deleted file mode 100644 index 912eda5..0000000 Binary files a/_images/doctrine_image_3_es.png and /dev/null differ diff --git a/_images/doctrine_web_debug_toolbar_es.png b/_images/doctrine_web_debug_toolbar_es.png deleted file mode 100644 index 5a5890b..0000000 Binary files a/_images/doctrine_web_debug_toolbar_es.png and /dev/null differ diff --git a/_images/form-simple.png b/_images/form-simple.png deleted file mode 100644 index e740dca..0000000 Binary files a/_images/form-simple.png and /dev/null differ diff --git a/_images/form-simple2.png b/_images/form-simple2.png deleted file mode 100644 index d50028f..0000000 Binary files a/_images/form-simple2.png and /dev/null differ diff --git a/_images/hola_nacho.png b/_images/hola_nacho.png deleted file mode 100644 index fa67ac8..0000000 Binary files a/_images/hola_nacho.png and /dev/null differ diff --git a/_images/http-xkcd-request_es.png b/_images/http-xkcd-request_es.png deleted file mode 100644 index 025d9c5..0000000 Binary files a/_images/http-xkcd-request_es.png and /dev/null differ diff --git a/_images/http-xkcd_es.png b/_images/http-xkcd_es.png deleted file mode 100644 index c724f6b..0000000 Binary files a/_images/http-xkcd_es.png and /dev/null differ diff --git a/_images/profiler_es.png b/_images/profiler_es.png deleted file mode 100644 index 41c3b0c..0000000 Binary files a/_images/profiler_es.png and /dev/null differ diff --git a/_images/progress.png b/_images/progress.png deleted file mode 100644 index c126bff..0000000 Binary files a/_images/progress.png and /dev/null differ diff --git a/_images/release-process.jpg b/_images/release-process.jpg deleted file mode 100644 index f3244c1..0000000 Binary files a/_images/release-process.jpg and /dev/null differ diff --git a/_images/request-flow_es.png b/_images/request-flow_es.png deleted file mode 100644 index 45167d4..0000000 Binary files a/_images/request-flow_es.png and /dev/null differ diff --git a/_images/request-response-flow.png b/_images/request-response-flow.png deleted file mode 100644 index 2ae1011..0000000 Binary files a/_images/request-response-flow.png and /dev/null differ diff --git a/_images/routing_auto_post_schema.png b/_images/routing_auto_post_schema.png deleted file mode 100644 index 73c0903..0000000 Binary files a/_images/routing_auto_post_schema.png and /dev/null differ diff --git a/_images/security_admin_role_access_es.png b/_images/security_admin_role_access_es.png deleted file mode 100644 index 4ce1edb..0000000 Binary files a/_images/security_admin_role_access_es.png and /dev/null differ diff --git a/_images/security_anonymous_user_access_es.png b/_images/security_anonymous_user_access_es.png deleted file mode 100644 index 8ed65cf..0000000 Binary files a/_images/security_anonymous_user_access_es.png and /dev/null differ diff --git a/_images/security_anonymous_user_denied_authorization_es.png b/_images/security_anonymous_user_denied_authorization_es.png deleted file mode 100644 index fa9ce8c..0000000 Binary files a/_images/security_anonymous_user_denied_authorization_es.png and /dev/null differ diff --git a/_images/security_authentication_authorization_es.png b/_images/security_authentication_authorization_es.png deleted file mode 100644 index 2d28333..0000000 Binary files a/_images/security_authentication_authorization_es.png and /dev/null differ diff --git a/_images/security_ryan_no_role_admin_access_es.png b/_images/security_ryan_no_role_admin_access_es.png deleted file mode 100644 index 3271ab6..0000000 Binary files a/_images/security_ryan_no_role_admin_access_es.png and /dev/null differ diff --git a/_images/serializer_workflow.png b/_images/serializer_workflow.png deleted file mode 100644 index 3e1944e..0000000 Binary files a/_images/serializer_workflow.png and /dev/null differ diff --git a/_images/sub-request.png b/_images/sub-request.png deleted file mode 100644 index 8609092..0000000 Binary files a/_images/sub-request.png and /dev/null differ diff --git a/_images/table.png b/_images/table.png deleted file mode 100644 index ba1e3ae..0000000 Binary files a/_images/table.png and /dev/null differ diff --git a/_images/web_debug_toolbar_es.png b/_images/web_debug_toolbar_es.png deleted file mode 100644 index 25974c6..0000000 Binary files a/_images/web_debug_toolbar_es.png and /dev/null differ diff --git a/_images/welcome.png b/_images/welcome.png deleted file mode 100644 index b5e9e57..0000000 Binary files a/_images/welcome.png and /dev/null differ diff --git a/_images/welcome_es.jpg b/_images/welcome_es.jpg deleted file mode 100644 index f709b6b..0000000 Binary files a/_images/welcome_es.jpg and /dev/null differ diff --git a/_sources/CONTRIBUTING.txt b/_sources/CONTRIBUTING.txt deleted file mode 100644 index c5ab2a2..0000000 --- a/_sources/CONTRIBUTING.txt +++ /dev/null @@ -1,4 +0,0 @@ -Colaborando ------------ - -¡Nos encantan los colaboradores! Para más información sobre cómo puedes contribuir a la documentación de *Symfony*, por favor lee [Colaborando en la documentación]() diff --git a/_sources/book/controller.txt b/_sources/book/controller.txt deleted file mode 100644 index 4517333..0000000 --- a/_sources/book/controller.txt +++ /dev/null @@ -1,633 +0,0 @@ -.. index:: - single: Controlador - -Controlador -=========== - -Un controlador es una función *PHP* que tú creas, misma que toma información desde la petición *HTTP* y construye una respuesta *HTTP* y la devuelve (como un objeto ``Respuesta`` de *Symfony2*). La respuesta podría ser una página *HTML*, un documento *XML*, un arreglo *JSON* serializado, una imagen, una redirección, un error 404 o cualquier otra cosa que se te ocurra. El controlador contiene toda la lógica arbitraria que *tu aplicación necesita* para reproducir el contenido de la página. - -Ve lo sencillo que es mirando un controlador *Symfony2* en acción. -El siguiente controlador reproducirá una página que simplemente imprime ``Hello world!``:: - - use Symfony\Component\HttpFoundation\Response; - - public function helloAction() - { - return new Response('Hello world!'); - } - -El objetivo de un controlador siempre es el mismo: crear y devolver un objeto ``Respuesta``. Al mismo tiempo, este puede leer la información de la petición, cargar un recurso de base de datos, enviar un correo electrónico, o fijar información en la sesión del usuario. -Pero en todos los casos, el controlador eventualmente devuelve el objeto ``Respuesta`` que será entregado al cliente. - -¡No hay magia y ningún otro requisito del cual preocuparse! Aquí tienes unos cuantos ejemplos comunes: - -* *Controlador A* prepara un objeto ``Respuesta`` que reproduce el contenido de la página principal del sitio. - -* *Controlador B* lee el parámetro ``slug`` de la petición para cargar una entrada del *blog* desde la base de datos y crear un objeto ``Respuesta`` exhibiendo ese *blog*. Si el ``slug`` no se puede encontrar en la base de datos, crea y devuelve un objeto ``Respuesta`` con un código de estado 404. - -* *Controlador C* procesa la información presentada en un formulario de contacto. Este lee la información del formulario desde la petición, guarda la información del contacto en la base de datos y envía mensajes de correo electrónico con la información de contacto al administrador del sitio *web*. Por último, crea un objeto ``Respuesta`` que redirige al navegador del cliente desde el formulario de contacto a la página de *«agradecimiento»*. - -.. index:: - single: Controlador; Ciclo de vida de la petición, controlador, respuesta - -Ciclo de vida de la petición, controlador, respuesta ----------------------------------------------------- - -Cada petición manejada por un proyecto *Symfony2* pasa por el mismo ciclo de vida básico. -La plataforma se encarga de las tareas repetitivas y, finalmente, ejecuta el controlador, que contiene el código personalizado de tu aplicación: - -#. Cada petición es manejada por un único archivo controlador frontal (por ejemplo, :file:`app.php` o :file:`app_dev.php`) el cual es responsable de arrancar la aplicación; - -#. El ``Enrutador`` lee la información de la petición (por ejemplo, la *URI*), encuentra una ruta que coincida con esa información, y lee el parámetro ``_controller`` de la ruta; - -#. El controlador de la ruta encontrada es ejecutado y el código dentro del controlador crea y devuelve un objeto ``Respuesta``; - -#. Las cabeceras *HTTP* y el contenido del objeto ``Respuesta`` se envían de vuelta al cliente. - -La creación de una página es tan fácil como crear un controlador (#3) y hacer una ruta que vincula una *URL* con ese controlador (#2). - -.. note:: - - A pesar de la similitud en el nombre, un «controlador frontal» es diferente a los «controladores» tratados en este capítulo. Un controlador frontal es un pequeño archivo *PHP* que vive en tu directorio web raíz y a través del cual se dirigen todas las peticiones. Una aplicación típica tendrá un controlador frontal en producción (por ejemplo, :file:`app.php`) y un controlador frontal para desarrollo (por ejemplo, :file:`app_dev.php`). Probablemente nunca necesites editar, ver o preocuparte por los controladores frontales en tu aplicación. - -.. index:: - single: Controlador; Ejemplo sencillo - -Un controlador sencillo ------------------------ - -Mientras que un controlador puede ser cualquier ejecutable *PHP* (una función, un método en un objeto o un ``Cierre``), en *Symfony2*, un controlador suele ser un único método dentro de un objeto controlador. Los controladores también se conocen como *acciones*. - -.. code-block:: php - :linenos: - - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; - - use Symfony\Component\HttpFoundation\Response; - - class HelloController - { - public function indexAction($name) - { - return new Response('Hello '.$name.'!'); - } - } - -.. tip:: - - Ten en cuenta que el *controlador* es el método ``indexAction``, que vive dentro de una *clase controlador* (``HelloController``). No te dejes confundir por la nomenclatura: una *clase controlador* simplemente es una conveniente forma de agrupar varios controladores/acciones. Generalmente, la clase controlador albergará varios controladores (por ejemplo, ``updateAction``, ``deleteAction``, etc.). - -Este controlador es bastante sencillo: - -* *línea 4*: *Symfony2* toma ventaja de la funcionalidad del espacio de nombres de *PHP 5.3* para el espacio de nombres de la clase del controlador completa. La palabra clave ``use`` importa la clase ``Respuesta``, la cual devolverá el controlador. - -* *línea 6*: El nombre de clase es la concatenación del nombre de la clase controlador (es decir ``Hello``) y la palabra ``Controller``. Esta es una convención que proporciona consistencia a los controladores y permite hacer referencia sólo a la primera parte del nombre (es decir, ``Hello``) en la configuración del enrutador. - -* *línea 8*: Cada acción en una clase controlador se sufija con ``Action`` y en la configuración de enrutado se refiere con el nombre de la acción (``index``). - En la siguiente sección, crearás una ruta que asigna una *URI* a esta acción. - Aprenderás cómo los marcadores de posición de la ruta (``{name}``) se convierten en argumentos para el método de acción (``$name``). - -* *línea 10*: el controlador crea y devuelve un objeto ``Respuesta``. - -.. index:: - single: Controlador; Rutas y controladores - -Asociando una *URL* a un controlador ------------------------------------- - -El nuevo controlador devuelve una página *HTML* simple. Para realmente ver esta página en tu navegador, necesitas crear una ruta, la cual corresponda a un patrón de *URL* específico para el controlador: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - hello: - path: /hello/{name} - defaults: { _controller: AcmeHelloBundle:Hello:index } - - .. code-block:: xml - - - - AcmeHelloBundle:Hello:index - - - .. code-block:: php - - // app/config/routing.php - $collection->add('hello', new Route('/hello/{name}', array( - '_controller' => 'AcmeHelloBundle:Hello:index', - ))); - -Yendo ahora a ``/hello/ryan`` se ejecuta el controlador ``HelloController::indexAction()`` y pasa ``ryan`` a la variable ``$name``. Crear una «página» significa simplemente que debes crear un método controlador y una ruta asociada. - -Observa la sintaxis utilizada para referirse al controlador: ``AcmeHelloBundle:Hello:index``. -*Symfony2* utiliza una flexible notación de cadena para referirse a diferentes controladores. -Esta es la sintaxis más común y le dice a *Symfony2* que busque una clase controlador llamada ``HelloController`` dentro de un paquete llamado ``AcmeHelloBundle``. Entonces ejecuta el método ``indexAction()``. - -Para más detalles sobre el formato de cadena utilizado para referirte a diferentes controladores, consulta el :ref:`controller-string-syntax`. - -.. note:: - - Este ejemplo coloca la configuración de enrutado directamente en el directorio ``app/config/``. Una mejor manera de organizar tus rutas es colocar cada ruta en el paquete al que pertenece. Para más información sobre este tema, consulta :ref:`routing-include-external-resources`. - -.. tip:: - - Puedes aprender mucho más sobre el sistema de enrutado en el :doc:`capítulo de enrutado `. - -.. index:: - single: Controlador; Argumentos del controlador - -.. _route-parameters-controller-arguments: - -Parámetros de ruta como argumentos para el controlador -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ya sabes que el parámetro ``_controller`` en ``AcmeHelloBundle:Hello:index`` se refiere al método ``HelloController::indexAction()`` que vive dentro del paquete ``AcmeHelloBundle``. Lo más interesante de esto son los argumentos que se pasan a este método:: - - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class HelloController extends Controller - { - public function indexAction($name) - { - // ... - } - } - -El controlador tiene un solo argumento, ``$name``, el cual corresponde al parámetro ``{name}`` de la ruta coincidente (``ryan`` en el ejemplo). De hecho, cuando ejecutas tu controlador, *Symfony2* empareja cada argumento del controlador con un parámetro de la ruta coincidente. Ve el siguiente ejemplo: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - hello: - path: /hello/{first_name}/{last_name} - defaults: { _controller: AcmeHelloBundle:Hello:index, color: green } - - .. code-block:: xml - - - - AcmeHelloBundle:Hello:index - green - - - .. code-block:: php - - // app/config/routing.php - $collection->add('hello', new Route('/hello/{first_name}/{last_name}', array( - '_controller' => 'AcmeHelloBundle:Hello:index', - 'color' => 'green', - ))); - -El controlador para esto puede tomar varios argumentos:: - - public function indexAction($first_name, $last_name, $color) - { - // ... - } - -Ten en cuenta que ambas variables marcadoras de posición (``{first_name}``, ``{last_name}``) así como la variable predeterminada ``color`` están disponibles como argumentos en el controlador. Cuando una ruta corresponde, las variables marcadoras de posición se combinan con ``defaults`` para hacer que un arreglo esté disponible para tu controlador. - -Asociar parámetros de ruta a los argumentos del controlador es fácil y flexible. Ten muy en cuenta las siguientes pautas mientras desarrollas. - -* **El orden de los argumentos del controlador no tiene importancia** - - *Symfony2* es capaz de igualar los nombres de los parámetros de la ruta con los nombres de las variables en la firma del método controlador. En otras palabras, se da cuenta de que el parámetro ``{last_name}`` coincide con el argumento ``$last_name``. - Los argumentos del controlador se pueden reorganizar completamente y aún así siguen funcionando perfectamente:: - - public function indexAction($last_name, $color, $first_name) - { - // ... - } - -* **Cada argumento requerido del controlador debe coincidir con un parámetro de enrutado** - - Lo siguiente lanzará una ``RuntimeException`` porque no hay ningún parámetro ``foo`` definido en la ruta:: - - public function indexAction($first_name, $last_name, $color, $foo) - { - // ... - } - - Sin embargo, hacer que el argumento sea opcional, es perfectamente legal. El siguiente ejemplo no lanzará una excepción:: - - public function indexAction($first_name, $last_name, $color, $foo = 'bar') - { - // ... - } - -* **No todos los parámetros de enrutado deben ser argumentos en tu controlador** - - Si por ejemplo, ``last_name`` no es tan importante para tu controlador, lo puedes omitir por completo:: - - public function indexAction($first_name, $color) - { - // ... - } - -.. tip:: - - Además, todas las rutas tienen un parámetro ``_route`` especial, el cual es igual al nombre de la ruta con la que fue emparejado (por ejemplo, ``hello``). Aunque no suele ser útil, igualmente está disponible como un argumento del controlador. - -.. _book-controller-request-argument: - -La ``Petición`` como argumento para el controlador -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para mayor comodidad, también puedes hacer que *Symfony* pase el objeto ``Petición`` como un argumento a tu controlador. Esto es conveniente especialmente cuando trabajas con formularios, por ejemplo:: - - use Symfony\Component\HttpFoundation\Request; - - public function updateAction(Request $request) - { - $form = $this->createForm(...); - - $form->bind($request); - // ... - } - -.. index:: - single: Controlador; Clase base controlador - -Creando páginas estáticas -------------------------- - -Puedes crear una página estática incluso sin crear un controlador (sólo se necesita una ruta -y la plantilla). - -¡Utilizalo! Ve :doc:`/cookbook/templating/render_without_controller`. - -La clase base del controlador ------------------------------ - -Para mayor comodidad, *Symfony2* viene con una clase ``Controller`` base, que te ayuda en algunas de las tareas más comunes del ``Controlador`` y proporciona acceso a cualquier recurso que tu clase controlador pueda necesitar. Al extender esta clase ``Controlador``, puedes tomar ventaja de varios métodos ayudantes. - -Agrega la instrucción ``use`` en lo alto de la clase ``Controlador`` y luego modifica ``HelloController`` para extenderla:: - - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Response; - - class HelloController extends Controller - { - public function indexAction($name) - { - return new Response('Hello '.$name.'!'); - } - } - -Esto, en realidad no cambia nada acerca de cómo funciona el controlador. En la siguiente sección, aprenderás acerca de los métodos ayudantes que la clase base del controlador pone a tu disposición. Estos métodos sólo son atajos para utilizar la funcionalidad del núcleo de *Symfony2* que está a nuestra disposición, usando o no la clase base ``Controller``. Una buena manera de ver la funcionalidad del núcleo en acción es buscar en la misma clase :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller`. - -.. tip:: - - Extender la clase base ``Controller`` en *Symfony* es opcional; esta contiene útiles atajos, pero no es obligatorio. También puedes extender la clase :class:`Symfony\\Component\\DependencyInjection\\ContainerAware`. El objeto contenedor del servicio será accesible a través de la propiedad ``container``. - -.. note:: - - Puedes definir tus :doc:`Controladores como Servicios `. - -.. index:: - single: Controlador; Tareas comunes - -Tareas comunes del controlador ------------------------------- - -A pesar de que un controlador puede hacer prácticamente cualquier cosa, la mayoría de los controladores se encargarán de las mismas tareas básicas una y otra vez. Estas tareas, tal como redirigir, procesar plantillas y acceder a servicios básicos, son muy fáciles de manejar en *Symfony2*. - -.. index:: - single: Controlador; Redirigiendo - -Redirigiendo -~~~~~~~~~~~~ - -Si deseas redirigir al usuario a otra página, utiliza el método ``redirect()``:: - - public function indexAction() - { - return $this->redirect($this->generateUrl('homepage')); - } - -El método ``generateUrl()`` es sólo una función auxiliar que genera la *URL* de una determinada ruta. Para más información, consulta el capítulo :doc:`Enrutando `. - -Por omisión, el método ``redirect()`` produce una redirección 302 (temporal). Para realizar una redirección 301 (permanente), modifica el segundo argumento:: - - public function indexAction() - { - return $this->redirect($this->generateUrl('homepage'), 301); - } - -.. tip:: - - El método ``redirect()`` simplemente es un atajo que crea un objeto ``Respuesta`` que se especializa en redirigir a los usuarios. Es equivalente a:: - - use Symfony\Component\HttpFoundation\RedirectResponse; - - return new RedirectResponse($this->generateUrl('homepage')); - -.. index:: - single: Controlador; Reenviando - -Reenviando -~~~~~~~~~~ - -Además, fácilmente puedes redirigir internamente hacia a otro controlador con el método ``forward()``. En lugar de redirigir el navegador del usuario, este hace una subpetición interna, y llama el controlador especificado. El método ``forward()`` regresa el objeto ``Respuesta``, el cual es devuelto desde el controlador:: - - public function indexAction($name) - { - $response = $this->forward('AcmeHelloBundle:Hello:fancy', array( - 'name' => $name, - 'color' => 'green', - )); - - // ... adicionalmente modifica la respuesta o la devuelve - // directamente - - return $response; - } - -Ten en cuenta que el método ``forward()`` utiliza la misma representación de cadena del controlador utilizada en la configuración de enrutado. En este caso, la clase controlador de destino será ``HelloController`` dentro de algún ``AcmeHelloBundle``. -El arreglo pasado al método se convierte en los argumentos del controlador resultante. -Esta misma interfaz se utiliza al incrustar controladores en las plantillas (consulta :ref:`templating-embedding-controller`). El método del controlador destino debe tener un aspecto como el siguiente:: - - public function fancyAction($name, $color) - { - // ... crea y devuelve un objeto Response - } - -Y al igual que al crear un controlador para una ruta, el orden de los argumentos para ``fancyAction`` no tiene la menor importancia. *Symfony2* empareja las claves nombre con el índice (por ejemplo, ``name``) con el argumento del método (por ejemplo, ``$name``). Si cambias el orden de los argumentos, *Symfony2* todavía pasará el valor correcto a cada variable. - -.. tip:: - - Al igual que otros métodos del ``Controller`` base, el método ``forward`` sólo es un atajo para la funcionalidad del núcleo de *Symfony2*. Puedes redirigir directamente por medio del servicio ``http_kernel``. Un reenvío devuelve un objeto ``Respuesta``:: - - $httpKernel = $this->container->get('http_kernel'); - $response = $httpKernel->forward( - 'AcmeHelloBundle:Hello:fancy', - array( - 'name' => $name, - 'color' => 'green', - ) - ); - -.. index:: - single: Controlador; Reproduciendo plantillas - -.. _controller-rendering-templates: - -Procesando plantillas -~~~~~~~~~~~~~~~~~~~~~ - -Aunque no es un requisito, la mayoría de los controladores en última instancia, reproducen una plantilla que es responsable de generar el código *HTML* (u otro formato) para el controlador. -El método ``renderView()`` procesa una plantilla y devuelve su contenido. Puedes usar el contenido de la plantilla para crear un objeto ``Respuesta``:: - - use Symfony\Component\HttpFoundation\Response; - - $content = $this->renderView( - 'AcmeHelloBundle:Hello:index.html.twig', - array('name' => $name) - ); - - return new Response($content); - -Incluso puedes hacerlo en un solo paso con el método ``render()``, el cual devuelve un objeto ``Respuesta`` con el contenido de la plantilla:: - - return $this->render( - 'AcmeHelloBundle:Hello:index.html.twig', - array('name' => $name) - ); - -En ambos casos, se reproducirá la plantilla ``Resources/views/Hello/index.html.twig`` dentro del ``AcmeHelloBundle``. - -El motor de plantillas de *Symfony* se explica con gran detalle en el capítulo :doc:`Plantillas `. - -.. tip:: - - Incluso puedes evitar la llamada al método ``render`` utilizando la anotación ``@Template``. Consulta la documentación del :doc:`FrameworkExtraBundle ` para más detalles. - -.. tip:: - - El método ``renderView`` es un atajo para usar el servicio de ``plantillas``. También puedes usar directamente el servicio de ``plantillas``:: - - $templating = $this->get('templating'); - $content = $templating->render( - 'AcmeHelloBundle:Hello:index.html.twig', - array('name' => $name) - ); - -.. note:: - - Es posible reproducir las plantillas en subdirectorios más profundos también, no obstante, ten cuidado para evitar caer en la trampa de hacer la estructura de directorios excesivamente elaborada:: - - $templating->render( - 'AcmeHelloBundle:Hello/Greetings:index.html.twig', - array('name' => $name) - ); - // reproduce index.html.twig ubicada en Resources/views/Hello/Greetings. - -.. index:: - single: Controlador; Accediendo a servicios - -Accediendo a otros servicios -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Al extender la clase base del controlador, puedes acceder a cualquier servicio de *Symfony2* a través del método ``get()``. Aquí hay varios servicios comunes que puedes necesitar:: - - $request = $this->getRequest(); - - $templating = $this->get('templating'); - - $router = $this->get('router'); - - $mailer = $this->get('mailer'); - -Hay un sinnúmero de servicios disponibles y te animamos a definir tus propios servicios. Para listar todos los servicios disponibles, utiliza la orden ``container:debug`` de la consola: - -.. code-block:: bash - - $ php app/console container:debug - -Para más información, consulta el capítulo :doc:`/book/service_container`. - -.. index:: - single: Controlador; Gestionando errores - single: Controlador; páginas 404 - -Gestionando errores y páginas 404 ---------------------------------- - -Cuando no se encuentra algo, debes jugar bien con el protocolo *HTTP* y devolver una respuesta 404. Para ello, debes lanzar un tipo de excepción especial. -Si estás extendiendo la clase base del controlador, haz lo siguiente:: - - public function indexAction() - { - // recupera el objeto desde la base de datos - $product = ...; - if (!$product) { - throw $this->createNotFoundException('The product does not exist'); - } - - return $this->render(...); - } - -El método ``createNotFoundException()`` crea un objeto ``NotFoundHttpException`` especial, que en última instancia, desencadena una respuesta *HTTP 404* en el interior de *Symfony*. - -Por supuesto, estás en libertad de lanzar cualquier clase de ``Excepción`` en tu controlador ---*Symfony2* automáticamente devolverá una respuesta *HTTP* con código 500. - -.. code-block:: php - - throw new \Exception('Something went wrong!'); - -En todos los casos, el usuario final ve una página de error estilizada y a los desarrolladores se les muestra una página de depuración de error completa (cuando visualizas la página en modo de depuración). -Puedes personalizar ambas páginas de error. Para más detalles, lee «:doc:`/cookbook/controller/error_pages`» en el recetario. - -.. index:: - single: Controlador; La sesión - single: Sesión - -Gestionando la sesión ---------------------- - -*Symfony2* proporciona un agradable objeto sesión que puedes utilizar para almacenar información sobre el usuario (ya sea una persona real usando un navegador, un robot o un servicio web) entre las peticiones. De manera predeterminada, *Symfony2* almacena los atributos de una :dfn:`cookie` usando las sesiones nativas de *PHP*. - -Almacenar y recuperar información de la sesión se puede conseguir fácilmente desde cualquier controlador:: - - $session = $this->getRequest()->getSession(); - - // guarda un atributo para reutilizarlo durante una - // posterior petición del usuario - $session->set('foo', 'bar'); - - // en otro controlador por otra petición - $foo = $session->get('foo'); - - // usa un valor predefinido si la clave no existe - $filters = $session->set('filters', array()); - -Estos atributos se mantendrán en el usuario por el resto de esa sesión. - -.. index:: - single: Sesión; Mensajes flash - -Mensajes flash -~~~~~~~~~~~~~~ - -También puedes almacenar pequeños mensajes que se pueden guardar en la sesión del usuario para exactamente una petición adicional. Esto es útil cuando procesas un formulario: -deseas redirigir y proporcionar un mensaje especial que aparezca en la *siguiente* petición. -Este tipo de mensajes se conocen como mensajes «flash». - -Por ejemplo, imagina que estás procesando el envío de un formulario:: - - public function updateAction() - { - $form = $this->createForm(...); - - $form->bind($this->getRequest()); - if ($form->isValid()) { - // hace algún tipo de procesamiento - - $this->get('session')->getFlashBag()->add('notice', 'Your changes were saved!'); - - return $this->redirect($this->generateUrl(...)); - } - - return $this->render(...); - } - -Después de procesar la petición, el controlador establece un mensaje flash ``notice`` y luego redirige al usuario. El nombre (``aviso``) no es significativo ---es lo que estás usando para identificar el tipo del mensaje. - -En la siguiente acción de la plantilla, podrías utilizar el siguiente código para reproducir el mensaje de ``aviso``: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% for flashMessage in app.session.flashbag.get('notice') %} -
- {{ flashMessage }} -
- {% endfor %} - - .. code-block:: html+php - - getFlashBag()->get('notice') as $message): ?> -
- $message
" ?> - - - -Por diseño, los mensajes flash están destinados a vivir por exactamente una petición (estos «desaparecen en un flash»). Están diseñados para utilizarlos a través de redirecciones exactamente como lo se hizo en este ejemplo. - -.. index:: - single: Controlador; Objeto Respuesta - -El objeto ``Respuesta`` ------------------------ - -El único requisito para un controlador es que devuelva un objeto ``Respuesta``. La clase :class:`Symfony\\Component\\HttpFoundation\\Response` es una abstracción *PHP* en torno a la respuesta *HTTP* ---el mensaje de texto, relleno con cabeceras *HTTP* y el contenido que se envía de vuelta al cliente:: - - use Symfony\Component\HttpFoundation\Response; - - // crea una simple respuesta con un código de estado 200 (el predeterminado) - $response = new Response('Hello '.$name, 200); - - // crea una respuesta JSON con código de estado 200 - $response = new Response(json_encode(array('name' => $name))); - $response->headers->set('Content-Type', 'application/json'); - -.. tip:: - - La propiedad ``headers`` es un objeto :class:`Symfony\\Component\\HttpFoundation\\HeaderBag` con varios métodos útiles para lectura y mutación de las cabeceras del objeto ``Respuesta``. Los nombres de las cabeceras están normalizados para que puedas usar ``Content-Type`` y este sea equivalente a ``content-type``, e incluso a ``content_type``. - -.. tip:: - - También hay clases especiales para hacer más fácil ciertas clases de respuestas: - - - Para JSON, está la clase :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`. - Ve :ref:`component-http-foundation-json-response`. - - Para archivos, está la clase :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`. - Ve :ref:`component-http-foundation-serving-files`. - -.. index:: - single: Controlador; Objeto Petición - -El objeto ``Petición`` ----------------------- - -Además de los valores de los marcadores de posición de enrutado, el controlador también tiene acceso al objeto ``Petición`` al extender la clase base ``Controlador``:: - - $request = $this->getRequest(); - - $request->isXmlHttpRequest(); // ¿es una petición Ajax? - - $request->getPreferredLanguage(array('en', 'fr')); - - $request->query->get('page'); // obtiene un parámetro $_GET - - $request->request->get('page'); // obtiene un parámetro $_POST - -Al igual que el objeto ``Respuesta``, las cabeceras de la petición se almacenan en un objeto ``HeaderBag`` y son fácilmente accesibles. - -Consideraciones finales ------------------------ - -Siempre que creas una página, en última instancia, tendrás que escribir algún código que contenga la lógica para esa página. En *Symfony*, a esto se le llama *controlador*, y es una función *PHP* que puede hacer cualquier cosa que necesites a fin de devolver el objeto ``Respuesta`` que se entregará al usuario final. - -Para facilitarte la vida, puedes optar por extender la clase base ``Controller``, la cual contiene atajos a métodos para muchas tareas de control comunes. Por ejemplo, puesto que no deseas poner el código *HTML* en tu controlador, puedes usar el método ``render()`` para reproducir y devolver el contenido desde una plantilla. - -En otros capítulos, verás cómo puedes usar el controlador para conservar y recuperar objetos desde una base de datos, procesar formularios presentados, manejar el almacenamiento en caché y mucho más. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/controller/error_pages` -* :doc:`/cookbook/controller/service` diff --git a/_sources/book/doctrine.txt b/_sources/book/doctrine.txt deleted file mode 100644 index 8d84e4b..0000000 --- a/_sources/book/doctrine.txt +++ /dev/null @@ -1,1277 +0,0 @@ -.. index:: - single: Doctrine - -Bases de datos y *Doctrine* -=========================== - -Seamos realistas, una de las tareas más comunes y desafiantes para cualquier aplicación involucra la persistencia y lectura de información hacia y desde una base de datos. Afortunadamente, *Symfony* viene integrado con `Doctrine`_, una biblioteca, cuyo único objetivo es dotarte de poderosas herramientas para facilitarte eso. En este capítulo, aprenderás la filosofía básica detrás de *Doctrine* y verás lo fácil que puede ser trabajar con una base de datos. - -.. note:: - - *Doctrine* está totalmente desconectado de *Symfony* y utilizarlo es opcional. - Este capítulo trata acerca del *ORM* de *Doctrine*, el cual te permite asociar objetos a una base de datos relacional (tal como *MySQL*, *PostgreSQL* o *Microsoft SQL*). Si prefieres utilizar las consultas de base de datos en bruto, es fácil y se explica en el artículo «:doc:`/cookbook/doctrine/dbal`» del recetario. - - También puedes persistir tus datos en `MongoDB`_ utilizando la biblioteca *ODM* de *Doctrine*. Para más información, lee la documentación en «:doc:`/bundles/DoctrineMongoDBBundle/index`». - -Un sencillo ejemplo: Un producto --------------------------------- - -La forma más fácil de entender cómo funciona *Doctrine* es verlo en acción. -En esta sección, configurarás tu base de datos, crearás un objeto ``Producto``, lo persistirás en la base de datos y lo recuperarás de nuevo. - -.. sidebar:: El código del ejemplo - - Si quieres seguir el ejemplo de este capítulo, crea el paquete ``AcmeStoreBundle`` ejecutando la orden: - - .. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/StoreBundle - -Configurando la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de comenzar realmente, tendrás que configurar tu información de conexión a la base de datos. Por convención, esta información se suele configurar en el archivo -:file:`app/config/parameters.yml`: - -.. code-block:: yaml - - # app/config/parameters.yml - parameters: - database_driver: pdo_mysql - database_host: localhost - database_name: proyecto_de_prueba - database_user: nombre_de_usuario - database_password: password - - # ... - -.. note:: - - Definir la configuración a través de :file:`parameters.yml` sólo es una convención. - Los parámetros definidos en este archivo son referidos en el archivo de configuración principal al configurar *Doctrine*: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - driver: "%database_driver%" - host: "%database_host%" - dbname: "%database_name%" - user: "%database_user%" - password: "%database_password%" - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $configuration->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => '%database_driver%', - 'host' => '%database_host%', - 'dbname' => '%database_name%', - 'user' => '%database_user%', - 'password' => '%database_password%', - ), - )); - - Al separar la información de la base de datos en un archivo independiente, puedes mantener fácilmente diferentes versiones del archivo en cada servidor. Además, fácilmente puedes almacenar la configuración de la base de datos (o cualquier otra información sensible) fuera de tu proyecto, posiblemente dentro de tu configuración de *Apache*, por ejemplo. Para más información, consulta :doc:`/cookbook/configuration/external_parameters`. - -Ahora que *Doctrine* conoce tu base de datos, posiblemente tenga que crear la base de datos para ti: - -.. code-block:: bash - - $ php app/console doctrine:database:create - -.. sidebar:: Configura la base de datos para que sea *UTF8* - - Una equivocación en la cual caen incluso los desarrolladores más experimentados cuándo empiezan un proyecto *Symfony2* es olvidar configurar el código de caracteres y cotejamiento en su base de datos, terminando con cotejamientos de tipo latin, los cuales están predefinidos en la mayoría de las bases de datos. - Incluso podrían recordar hacerlo al principio, pero olvidan que todo esto se hace después de ejecutar una orden relativamente común durante el desarrollo: - - .. code-block:: bash - - $ php app/console doctrine:database:drop --force - $ php app/console doctrine:database:create - - No hay manera de configurar estos valores predeterminados dentro de *Doctrine*, ya que trata de ser lo más agnóstica posible en términos de configuración del entorno. Una manera de resolver este problema consiste en configurar el nivel predeterminado del servidor. - - La configuración predefinida UTF8 para *MySQL* es tan simple como añadir unas cuantas líneas a tu archivo de configuración (generalmente ``my.cnf``): - - .. code-block:: yaml - - [mysqld] - collation-server = utf8_general_ci - character-set-server = utf8 - -.. note:: - - Si quieres usar ``SQLite`` como tu base de datos, debes especificar la ruta a donde se debería almacenar tu archivo de base de datos: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - driver: pdo_sqlite - path: "%kernel.root_dir%/sqlite.db" - charset: UTF8 - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => 'pdo_sqlite', - 'path' => '%kernel.root_dir%/sqlite.db', - 'charset' => 'UTF-8', - ), - )); - -Creando una clase Entidad -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Supongamos que estás construyendo una aplicación donde necesitas mostrar tus productos. -Sin siquiera pensar en *Doctrine* o en una base de datos, ya sabes que necesitas un objeto ``Producto`` para representar los productos. Crea esta clase en el directorio ``Entity`` de tu paquete ``AcmeStoreBundle``:: - - // src/Acme/StoreBundle/Entity/Product.php - namespace Acme\StoreBundle\Entity; - - class Product - { - protected $name; - - protected $price; - - protected $description; - } - -La clase ---a menudo llamada «entidad», es decir, *una clase básica que contiene datos*--- es simple y ayuda a cumplir con el requisito del negocio de productos que necesita tu aplicación. Sin embargo, esta clase no se puede guardar en una base de datos ---es sólo una clase *PHP* simple. - -.. tip:: - - Una vez que aprendas los conceptos detrás de *Doctrine*, puedes dejar que *Doctrine* cree clases de entidad simples por ti: - - .. code-block:: bash - - $ php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Product" --fields="name:string(255) price:float description:text" - -.. index:: - single: Doctrine; Agregando metadatos de asociación - -.. _book-doctrine-adding-mapping: - -Agregando información de asignación -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Doctrine* te permite trabajar con bases de datos de una manera mucho más interesante que solo recuperar filas de una tabla basada en columnas de un arreglo. En cambio, *Doctrine* te permite persistir *objetos* completos a la base de datos y recuperar objetos completos desde la base de datos. Esto funciona asociando una clase *PHP* a una tabla de la base de datos, y las propiedades de esa clase *PHP* a las columnas de la tabla: - -.. image:: /images/book/doctrine_image_1_es.png - :align: center - -Para que *Doctrine* sea capaz de hacer esto, sólo hay que crear «metadatos», o la configuración que le dice a *Doctrine* exactamente cómo debe *asociar* la clase ``Producto`` y sus propiedades a la base de datos. Estos metadatos se pueden especificar en una serie de diferentes formatos, incluyendo *YAML*, *XML* o directamente dentro de la clase ``Producto`` a través de anotaciones: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Acme/StoreBundle/Entity/Product.php - namespace Acme\StoreBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Entity - * @ORM\Table(name="product") - */ - class Product - { - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - protected $id; - - /** - * @ORM\Column(type="string", length=100) - */ - protected $name; - - /** - * @ORM\Column(type="decimal", scale=2) - */ - protected $price; - - /** - * @ORM\Column(type="text") - */ - protected $description; - } - - .. code-block:: yaml - - # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml - Acme\StoreBundle\Entity\Product: - type: entity - table: product - id: - id: - type: integer - generator: { strategy: AUTO } - fields: - name: - type: string - length: 100 - price: - type: decimal - scale: 2 - description: - type: text - - .. code-block:: xml - - - - - - - - - - - - - - -.. note:: - - Un paquete sólo puede aceptar un formato para definir metadatos. Por ejemplo, no es posible mezclar metadatos para la clase Entidad definidos en *YAML* con definidos en anotaciones *PHP*. - -.. tip:: - - El nombre de la tabla es opcional y si la omites, será determinada automáticamente basándose en el nombre de la clase entidad. - -*Doctrine* te permite elegir entre una amplia variedad de diferentes tipos de campo, cada uno con sus propias opciones. Para obtener información sobre los tipos de campo disponibles, consulta la sección :ref:`book-doctrine-field-types`. - -.. seealso:: - - También puedes consultar la `Documentación de asociación básica`_ de *Doctrine* para todos los detalles sobre la información de asignación. Si utilizas anotaciones, tendrás que prefijar todas las anotaciones con ``ORM\`` (por ejemplo, ``ORM\Column(..)``), lo cual no se muestra en la documentación de *Doctrine*. También tendrás que incluir la declaración ``use Doctrine\ORM\Mapping as ORM;`` la cual *importa* el prefijo ``ORM`` de las anotaciones. - -.. caution:: - - Ten cuidado de que tu nombre de clase y propiedades no estén asignados a un área protegida por palabras clave de *SQL* (tal como ``group`` o ``user``). Por ejemplo, si el nombre de clase de tu entidad es ``group``, entonces, de manera predeterminada, el nombre de la tabla será ``group``, lo cual provocará un error en algunos motores *SQL*. Consulta la `Documentación de palabras clave reservadas por SQL`_ para que sepas cómo escapar correctamente estos nombres. Alternativamente, si estás en libertad de elegir el esquema de tu base de datos, simplemente asigna un diferente nombre de tabla o columna. Ve las `Clases persistentes`_ y la `Asignación de propiedades`_ en la documentación de *Doctrine*. - -.. note:: - - Cuando utilizas otra biblioteca o programa (es decir, *Doxygen*) que utiliza anotaciones, debes colocar la anotación ``@IgnoreAnnotation`` en la clase para indicar que se deben ignorar las anotaciones *Symfony*. - - Por ejemplo, para evitar que la anotación ``@fn`` lance una excepción, añade lo siguiente:: - - /** - * @IgnoreAnnotation("fn") - */ - class Product - // ... - -Generando captadores y definidores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A pesar de que *Doctrine* ahora sabe cómo persistir en la base de datos un objeto ``Producto``, la clase en sí realmente no es útil todavía. Puesto que ``Producto`` es sólo una clase *PHP* regular, es necesario crear métodos captadores y definidores (por ejemplo, ``getName()``, ``setName()``) para poder acceder a sus propiedades (ya que las propiedades son ``protegidas``). Afortunadamente, *Doctrine* puede hacer esto por ti con la siguiente orden: - -.. code-block:: bash - - $ php app/console doctrine:generate:entities Acme/StoreBundle/Entity/Product - -Esta orden se asegura de que se generen todos los captadores y definidores para la clase ``Producto``. Esta es una orden segura --- la puedes ejecutar una y otra vez: sólo genera captadores y definidores que no existen (es decir, no sustituye métodos existentes). - -.. caution:: - - Ten en cuenta que el generador de entidades de *Doctrine* produce captadores/definidores sencillos. - Debes revisar las entidades generadas y ajustar a tus propias necesidades la lógica de los captadores/definidores. - -.. sidebar:: Más sobre ``doctrine:generate:entities`` - - con la orden ``doctrine:generate:entities`` puedes: - - * generar captadores y definidores; - - * generar clases repositorio configuradas con la anotación ``@ORM\Entity(repositoryClass="...")``, - - * generar el constructor adecuado para relaciones ``1:n`` y ``n:m``. - - La orden ``doctrine:generate:entities`` guarda una copia de seguridad del :file:`Producto.php` original llamada :file:`Producto.php~`. En algunos casos, la presencia de este archivo puede provocar un error «No se puede redeclarar la clase». Lo puedes quitar sin problemas. - - Ten en cuenta que no *necesitas* usar esta orden. *Doctrine* no se basa en la generación de código. Al igual que con las clases de *PHP* normales, sólo tienes que asegurarte de que sus propiedades protegidas/privadas tienen métodos captadores y definidores. - Puesto que cuando utilizas *Doctrine* es algo que tienes que hacer comúnmente, se creó esta orden. - -También puedes generar todas las entidades conocidas (es decir, cualquier clase *PHP* con información de asignación *Doctrine*) de un paquete o un espacio de nombres completo: - -.. code-block:: bash - - $ php app/console doctrine:generate:entities AcmeStoreBundle - $ php app/console doctrine:generate:entities Acme - -.. note:: - - A *Doctrine* no le importa si tus propiedades son ``protegidas`` o ``privadas``, o si una propiedad tiene o no una función captadora o definidora. - Aquí, los captadores y definidores se generan sólo porque los necesitarás para interactuar con tu objeto *PHP*. - -Creando tablas/esquema de la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora tienes una clase ``Producto`` utilizable con información de asignación de modo que *Doctrine* sabe exactamente cómo persistirla. Por supuesto, en tu base de datos aún no tienes la tabla ``producto`` correspondiente. Afortunadamente, *Doctrine* puede crear automáticamente todas las tablas de la base de datos necesarias para cada entidad conocida en tu aplicación. Para ello, ejecuta: - -.. code-block:: bash - - $ php app/console doctrine:schema:update --force - -.. tip:: - - En realidad, esta orden es increíblemente poderosa. Esta compara cómo se *debe* ver tu base de datos (en base a la información de asignación de tus entidades) con la forma en que *realmente* se ve, y genera las declaraciones *SQL* necesarias para *actualizar* la base de datos a su verdadera forma. En otras palabras, si agregas una nueva propiedad asignando metadatos a ``Producto`` y ejecutas esta tarea de nuevo, vas a generar la declaración ``alter table`` necesaria para añadir la nueva columna a la tabla ``Producto`` existente. - - Una forma aún mejor para tomar ventaja de esta funcionalidad es a través de las :doc:`migraciones `, las cuales te permiten generar estas instrucciones *SQL* y almacenarlas en las clases de la migración, mismas que puedes ejecutar sistemáticamente en tu servidor en producción con el fin de seguir la pista y migrar el esquema de la base de datos segura y fiablemente. - -Tu base de datos ahora cuenta con una tabla ``producto`` completamente operativa, con columnas que coinciden con los metadatos que has especificado. - -Persistiendo objetos a la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora que tienes asignada una entidad ``Producto`` y la tabla ``Producto`` correspondiente, estás listo para persistir los datos a la base de datos. Desde el interior de un controlador, esto es bastante fácil. Agrega el siguiente método al ``DefaultController`` del paquete: - -.. code-block:: php - :linenos: - - // src/Acme/StoreBundle/Controller/DefaultController.php - - // ... - use Acme\StoreBundle\Entity\Product; - use Symfony\Component\HttpFoundation\Response; - - public function createAction() - { - $product = new Product(); - $product->setName('A Foo Bar'); - $product->setPrice('19.99'); - $product->setDescription('Lorem ipsum dolor'); - - $em = $this->getDoctrine()->getManager(); - $em->persist($product); - $em->flush(); - - return new Response('Created product id '.$product->getId()); - } - -.. note:: - - Si estás siguiendo este ejemplo, tendrás que crear una ruta que apunte a esta acción para verla trabajar. - -Veamos detenidamente el ejemplo anterior: - -* **líneas 9-12** En esta sección, creas una instancia y trabajas con el objeto ``$product`` como con cualquier otro objeto *PHP* normal. - -* **línea 14** Esta línea consigue un objeto *gestor de entidades* de *Doctrine*, el cual es responsable de manejar el proceso de persistir y recuperar objetos hacia y desde la base de datos. - -* **línea 15** El método ``persist()`` dice a *Doctrine* que «maneje» el objeto ``$product``. Esto en realidad no provoca una consulta que se deba introducir en la base de datos (todavía). - -* **línea 16** Cuando se llama al método ``flush()``, *Doctrine* examina todos los objetos que está gestionando para ver si es necesario persistirlos en la base de datos. En este ejemplo, el objeto ``$product`` aún no se ha persistido, por lo tanto el gestor de la entidad ejecuta una consulta ``INSERT`` y crea una fila en la tabla ``producto``. - -.. note:: - - De hecho, ya que *Doctrine* es consciente de todas tus entidades gestionadas, cuando se llama al método ``flush()``, calcula el conjunto de cambios y ejecuta la(s) consulta(s) más eficiente(s) posible(s). Por ejemplo, si persistes un total de 100 objetos ``Producto`` y, posteriormente llamas a ``flush()``, *Doctrine* creará una *sola* declaración preparada y la volverá a utilizar en cada inserción. Este patrón se conoce como *Unidad de trabajo*, y se usa porque es rápido y eficiente. - -Al crear o actualizar objetos, el flujo de trabajo siempre es el mismo. En la siguiente sección, verás cómo *Doctrine* es lo suficientemente inteligente como para emitir automáticamente una consulta *UPDATE* si el registro ya existe en la base de datos. - -.. tip:: - - *Doctrine* proporciona una biblioteca que te permite cargar en tu proyecto mediante programación los datos de prueba (es decir, «datos accesorios»). Para más información, consulta :doc:`/bundles/DoctrineFixturesBundle/index`. - -Recuperando objetos desde la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Recuperar un objeto desde la base de datos es aún más fácil. Por ejemplo, supongamos que has configurado una ruta para mostrar un ``Producto`` específico en función del valor de su ``id``:: - - public function showAction($id) - { - $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') - ->find($id); - - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } - - // ... haz algo, como pasar el objeto $product a una plantilla - } - -.. tip:: - - Puedes conseguir el equivalente de este sin escribir ningún código utilizando el método abreviado ``@ParamConverter``. Consulta la documentación del :doc:`FrameworkExtraBundle ` para más detalles. - -Al consultar un determinado tipo de objeto, siempre utiliza lo que se conoce como «repositorio». Puedes pensar en un repositorio como una clase *PHP*, cuyo único trabajo consiste en ayudarte a buscar las entidades de una determinada clase. Puedes acceder al objeto ``repositorio`` de una clase ``entidad`` a través de:: - - $repository = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product'); - -.. note:: - - La cadena ``AcmeStoreBundle:Product`` es un método abreviado que puedes utilizar en cualquier lugar de *Doctrine* en lugar del nombre de clase completo de la entidad (es decir, ``Acme\StoreBundle\Entity\Product``). - Mientras que tu entidad viva bajo el espacio de nombres ``Entity`` de tu paquete, esto debe funcionar. - -Una vez que tengas tu repositorio, tienes acceso a todo tipo de útiles métodos:: - - // consulta por la clave principal (generalmente 'id') - $product = $repository->find($id); - - // nombres dinámicos de métodos para buscar un valor basad en columna - $product = $repository->findOneById($id); - $product = $repository->findOneByName('foo'); - - // recupera TODOS los productos - $products = $repository->findAll(); - - // busca un grupo de productos basándose en el valor de una columna arbitraria - $products = $repository->findByPrice(19.99); - -.. note:: - - Por supuesto, también puedes realizar consultas complejas, acerca de las cuales aprenderás más en la sección :ref:`book-doctrine-queries`. - -También puedes tomar ventaja de los útiles métodos ``findBy`` y ``findOneBy`` para recuperar objetos fácilmente basándote en varias condiciones:: - - // consulta por un producto que coincide en nombre y precio - $product = $repository->findOneBy(array('name' => 'foo', - 'price' => 19.99)); - - // consulta para todos los productos que emparejen el nombre, ordenados por precio - $products = $repository->findBy( - array('name' => 'foo'), - array('price' => 'ASC') - ); - -.. tip:: - - Cuando reproduces una página, puedes ver, en la esquina inferior derecha de la barra de herramientas de depuración web, cuántas consultas se realizaron. - - .. image:: /images/book/doctrine_web_debug_toolbar_es.png - :align: center - :scale: 75 - :width: 350 - - Si haces clic en el icono, se abrirá el generador de perfiles, mostrando las consultas exactas que se hicieron. - -Actualizando un objeto -~~~~~~~~~~~~~~~~~~~~~~ - -Una vez que hayas extraído un objeto de *Doctrine*, actualizarlo es relativamente fácil. Supongamos que tienes una ruta que asigna un identificador de producto a una acción de actualización de un controlador:: - - public function updateAction($id) - { - $em = $this->getDoctrine()->getManager(); - $product = $em->getRepository('AcmeStoreBundle:Product')->find($id); - - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } - - $product->setName('New product name!'); - $em->flush(); - - return $this->redirect($this->generateUrl('homepage')); - } - -La actualización de un objeto únicamente consta de tres pasos: - -#. Recuperar el objeto desde *Doctrine*; -#. Modificar el objeto; -#. Invocar a ``flush()`` en el gestor de la entidad; - -Ten en cuenta que no es necesario llamar a ``$em->persist($product)``. Recuerda que este método simplemente dice a *Doctrine* que procese o «vea» el objeto ``$product``. -En este caso, ya que recuperaste el objeto ``$product`` desde *Doctrine*, este ya está gestionado. - -Eliminando un objeto -~~~~~~~~~~~~~~~~~~~~ - -Eliminar un objeto es muy similar, pero requiere una llamada al método ``remove()`` del gestor de la entidad:: - - $em->remove($product); - $em->flush(); - -Como es de esperar, el método ``remove()`` notifica a *Doctrine* que deseas eliminar la entidad de la base de datos. La consulta *DELETE* real, sin embargo, no se ejecuta efectivamente hasta que se invoca al método ``flush()``. - -.. _`book-doctrine-queries`: - -Consultando por objetos ------------------------ - -Ya has visto cómo el objeto ``repositorio`` te permite ejecutar consultas básicas sin ningún trabajo:: - - $repository->find($id); - - $repository->findOneByName('Foo'); - -Por supuesto, *Doctrine* también te permite escribir consultas más complejas utilizando el lenguaje de consulta *Doctrine* (*DQL* por *Doctrine Query Language*). *DQL* es similar a *SQL*, excepto que debes imaginar que estás consultando por uno o más objetos de una clase entidad (por ejemplo, ``Producto``) en lugar de consultar por filas de una tabla (por ejemplo, ``producto``). - -Al consultar en *Doctrine*, tienes dos opciones: escribir consultas *Doctrine* puras o utilizar el generador de consultas de *Doctrine*. - -Consultando objetos con *DQL* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Imagina que deseas consultar los productos, pero sólo quieres devolver aquellos que cuestan más de ``19.99``, ordenados del más barato al más caro. Desde el interior de un controlador, haz lo siguiente:: - - $em = $this->getDoctrine()->getManager(); - $query = $em->createQuery( - 'SELECT p FROM AcmeStoreBundle:Product p WHERE p.price > :price ORDER BY p.price ASC' - )->setParameter('price', '19.99'); - - $products = $query->getResult(); - -Si te sientes cómodo con *SQL*, entonces debes sentir a *DQL* muy natural. La más grande diferencia es que necesitas pensar en términos de «objetos» en lugar de filas de una base de datos. Por esta razón, seleccionas *from* ``AcmeStoreBundle:Product`` y luego lo apodas ``p``. - -El método ``getResult()`` devuelve un arreglo de resultados. Si estás preguntando por un solo objeto, en su lugar puedes utilizar el método ``getSingleResult()``:: - - $product = $query->getSingleResult(); - -.. caution:: - - El método ``getSingleResult()`` lanza una excepción ``Doctrine\ORM\NoResultException`` si no se devuelven resultados y una ``Doctrine\ORM\NonUniqueResultException`` si se devuelve *más* de un resultado. Si utilizas este método, posiblemente tengas que envolverlo en un bloque ``try-catch`` y asegurarte de que sólo devuelve un resultado (si estás consultando sobre algo que sea viable podrías regresar más de un resultado):: - - $query = $em->createQuery('SELECT ...') - ->setMaxResults(1); - - try { - $product = $query->getSingleResult(); - } catch (\Doctrine\Orm\NoResultException $e) { - $product = null; - } - // ... - -La sintaxis *DQL* es increíblemente poderosa, permitiéndote unir entidades fácilmente (el tema de las :ref:`relaciones ` se describe más adelante), agrupación, etc. Para más información, consulta la documentación oficial de `Doctrine Query Language`_. - -.. sidebar:: Configurando parámetros - - Toma nota del método ``setParameter()``. Al trabajar con *Doctrine*, siempre es buena idea establecer cualquier valor externo como «marcador de posición», tal cómo se hizo en la consulta anterior: - - .. code-block:: text - - ... WHERE p.price > :price ... - - Entonces, puedes establecer el valor del marcador de posición ``price`` llamando al método ``setParameter()``:: - - ->setParameter('price', '19.99') - - Utilizar parámetros en lugar de colocar los valores directamente en la cadena de consulta, se hace para prevenir ataques de inyección *SQL* y *siempre* se debe hacer. - Si estás utilizando varios parámetros, puedes establecer simultáneamente sus valores usando el método ``setParameters()``:: - - ->setParameters(array( - 'price' => '19.99', - 'name' => 'Foo', - )) - -Usando el generador de consultas de *Doctrine* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En lugar de escribir las consultas directamente, también puedes usar el ``QueryBuilder`` de *Doctrine* para hacer el mismo trabajo con una agradable interfaz orientada a objetos. -Si usas un *IDE*, también puedes tomar ventaja del autocompletado a medida que escribes los nombres de método. Desde el interior de un controlador:: - - $repository = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product'); - - $query = $repository->createQueryBuilder('p') - ->where('p.price > :price') - ->setParameter('price', '19.99') - ->orderBy('p.price', 'ASC') - ->getQuery(); - - $products = $query->getResult(); - -El objeto ``QueryBuilder`` contiene todos los métodos necesarios para construir tu consulta. Al invocar al método ``getQuery()``, el generador de consultas devuelve un objeto ``Query`` normal, el cual es el mismo objeto que construiste directamente en la sección anterior. - -Para más información sobre el generador de consultas de *Doctrine*, consulta la documentación del `Generador de consultas`_ de *Doctrine*. - -Repositorio de clases personalizado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En las secciones anteriores, comenzamos a construir y utilizar consultas más complejas desde el interior de un controlador. Con el fin de aislar, probar y volver a usar estas consultas, es buena idea crear una clase ``repositorio`` personalizada para tu ``entidad`` y agregar métodos con tu lógica de consulta allí. - -Para ello, agrega el nombre de la clase del repositorio a la definición de asignación. - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Acme/StoreBundle/Entity/Product.php - namespace Acme\StoreBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository") - */ - class Product - { - //... - } - - .. code-block:: yaml - - # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml - Acme\StoreBundle\Entity\Product: - type: entity - repositoryClass: Acme\StoreBundle\Entity\ProductRepository - # ... - - .. code-block:: xml - - - - - - - - - - - -*Doctrine* puede generar la clase ``repositorio`` por ti ejecutando la misma orden usada anteriormente para generar los métodos captadores y definidores omitidos: - -.. code-block:: bash - - $ php app/console doctrine:generate:entities Acme - -A continuación, agrega un nuevo método --- ``findAllOrderedByName()`` --- a la clase repositorio recién generada. Este método debe consultar todas las entidades ``Producto``, ordenadas alfabéticamente. - -.. code-block:: php - - // src/Acme/StoreBundle/Entity/ProductRepository.php - namespace Acme\StoreBundle\Entity; - - use Doctrine\ORM\EntityRepository; - - class ProductRepository extends EntityRepository - { - public function findAllOrderedByName() - { - return $this->getEntityManager() - ->createQuery('SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC') - ->getResult(); - } - } - -.. tip:: - - Puedes acceder al gestor de la entidad a través de ``$this->getEntityManager()`` desde el interior del repositorio. - -Puedes utilizar este nuevo método al igual que los métodos de búsqueda predefinidos del repositorio:: - - $em = $this->getDoctrine()->getManager(); - $products = $em->getRepository('AcmeStoreBundle:Product') - ->findAllOrderedByName(); - -.. note:: - - Al utilizar una clase repositorio personalizada, todavía tienes acceso a los métodos de búsqueda predeterminados como ``find()`` y ``findAll()``. - -.. _`book-doctrine-relations`: - -Entidad relaciones/asociaciones -------------------------------- - -Supón que los productos en tu aplicación pertenecen exactamente a una ``«categoría»``. -En este caso, necesitarás un objeto ``Categoría`` y una manera de relacionar un objeto ``Producto`` a un objeto ``Categoría``. Empieza por crear la entidad ``Categoría``. -Ya sabes que tarde o temprano tendrás que persistir la clase a través de *Doctrine*, puedes dejar que *Doctrine* cree la clase por ti. - -.. code-block:: bash - - $ php app/console doctrine:generate:entity --entity="AcmeStoreBundle:Category" --fields="name:string(255)" - -Esta tarea genera la entidad ``Categoría`` para ti, con un campo ``id``, un campo ``name`` y las funciones captadoras y definidoras asociadas. - -Relación con la asignación de metadatos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para relacionar las entidades ``Categoría`` y ``Producto``, empieza por crear una propiedad ``productos`` en la clase ``Categoría``: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Acme/StoreBundle/Entity/Category.php - - // ... - use Doctrine\Common\Collections\ArrayCollection; - - class Category - { - // ... - - /** - * @ORM\OneToMany(targetEntity="Product", mappedBy="category") - */ - protected $products; - - public function __construct() - { - $this->products = new ArrayCollection(); - } - } - - .. code-block:: yaml - - # src/Acme/StoreBundle/Resources/config/doctrine/Category.orm.yml - Acme\StoreBundle\Entity\Category: - type: entity - # ... - oneToMany: - products: - targetEntity: Product - mappedBy: category - # no olvides iniciar la colección en el método __construct() de la entidad - - .. code-block:: xml - - - - - - - - - - - - -En primer lugar, ya que un objeto ``Categoría`` debe relacionar muchos objetos ``Producto``, agregamos una propiedad ``Productos`` para contener esos objetos ``Producto``. -Una vez más, esto no se hace porque lo necesite *Doctrine*, sino porque tiene sentido en la aplicación para que cada ``Categoría`` mantenga una gran variedad de objetos ``Producto``. - -.. note:: - - El código del método ``__construct()`` es importante porque *Doctrine* requiere que la propiedad ``$products`` sea un objeto ``ArrayCollection``. - Este objeto se ve y actúa casi *exactamente* como un arreglo, pero tiene cierta flexibilidad. Si esto te hace sentir incómodo, no te preocupes. Sólo imagina que es un ``arreglo`` y estarás bien. - -.. tip:: - - El valor de ``targetEntity`` en el decorador utilizado anteriormente puede hacer referencia a cualquier entidad con un espacio de nombres válido, no sólo a las entidades definidas en la misma clase. Para relacionarlo con una entidad definida en una clase o paquete diferente, escribe un espacio de nombres completo como la ``targetEntity``. - -Después, ya que cada clase ``Producto`` se puede relacionar exactamente a un objeto ``Categoría``, podrías desear agregar una propiedad ``$category`` a la clase ``Producto``: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Acme/StoreBundle/Entity/Product.php - - // ... - class Product - { - // ... - - /** - * @ORM\ManyToOne(targetEntity="Category", inversedBy="products") - * @ORM\JoinColumn(name="category_id", referencedColumnName="id") - */ - protected $category; - } - - .. code-block:: yaml - - # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml - Acme\StoreBundle\Entity\Product: - type: entity - # ... - manyToOne: - category: - targetEntity: Category - inversedBy: products - joinColumn: - name: category_id - referencedColumnName: id - - .. code-block:: xml - - - - - - - - - - - - -Por último, ahora que agregaste una nueva propiedad a ambas clases ``Categoría`` y ``Producto``, le dices a *Doctrine* que genere por ti los métodos captadores y definidores omitidos: - -.. code-block:: bash - - $ php app/console doctrine:generate:entities Acme - -No hagas caso de los metadatos de *Doctrine* por un momento. Ahora tienes dos clases ---``Categoría`` y ``Producto``--- con una relación natural de uno a muchos. La clase ``Categoría`` tiene un arreglo de objetos ``Producto`` y el objeto ``producto`` puede contener un objeto ``Categoría``. En otras palabras, construiste tus clases de una manera que tiene sentido para tus necesidades. El hecho de que los datos se tienen que persistir en una base de datos, siempre es secundario. - -Ahora, veamos los metadatos sobre la propiedad ``$category`` en la clase ``Producto``. Esta información le dice a *Doctrine* que la clase está relacionada con ``Categoría`` y que debe guardar el ``id`` del registro de la categoría en un campo ``category_id`` que vive en la tabla ``producto``. En otras palabras, el objeto ``Categoría`` relacionado se almacenará en la propiedad ``$category``, pero tras bambalinas, *Doctrine* deberá persistir esta relación almacenando el valor del ``id`` de la categoría en una columna ``category_id`` de la tabla ``producto``. - -.. image:: /images/book/doctrine_image_2_es.png - :align: center - -Los metadatos sobre la propiedad ``$products`` del objeto ``Categoría`` son menos importantes, y simplemente dicen a *Doctrine* que vea la propiedad ``Product.category`` para resolver cómo se asigna la relación. - -Antes de continuar, asegúrate de decirle a *Doctrine* que agregue la nueva tabla ``categoría``, la columna ``product.category_id`` y la nueva clave externa: - -.. code-block:: bash - - $ php app/console doctrine:schema:update --force - -.. note:: - - Esta tarea sólo la deberías utilizar durante el desarrollo. Para un más robusto método de actualización sistemática de tu base de datos en producción, lee sobre las :doc:`Migraciones de Doctrine `. - -Guardando entidades relacionadas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora, ¡puedes ver este nuevo código en acción! Imagina que estás dentro de un controlador:: - - // ... - - use Acme\StoreBundle\Entity\Category; - use Acme\StoreBundle\Entity\Product; - use Symfony\Component\HttpFoundation\Response; - - class DefaultController extends Controller - { - public function createProductAction() - { - $category = new Category(); - $category->setName('Main Products'); - - $product = new Product(); - $product->setName('Foo'); - $product->setPrice(19.99); - // relaciona este producto a la categoría - $product->setCategory($category); - - $em = $this->getDoctrine()->getManager(); - $em->persist($category); - $em->persist($product); - $em->flush(); - - return new Response( - 'Created product id: '.$product->getId().' and category id: '.$category->getId() - ); - } - } - -Ahora, se agrega una sola fila a las tablas ``categoría`` y ``producto``. -La columna ``product.category_id`` para el nuevo producto se ajusta a algún ``id`` de la nueva categoría. *Doctrine* gestiona la persistencia de esta relación para ti. - -Recuperando objetos relacionados -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando necesites recuperar objetos asociados, tu flujo de trabajo se ve justo como lo hacías antes. En primer lugar, buscas un objeto ``$product`` y luego accedes a su ``Categoría`` asociada:: - - public function showAction($id) - { - $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') - ->find($id); - - $categoryName = $product->getCategory()->getName(); - - // ... - } - -En este ejemplo, primero consultas por un objeto ``Producto`` basándote en el ``id`` del producto. Este emite una consulta *solo* para los datos del producto e hidrata al objeto ``$product`` con esos datos. Más tarde, cuando llames a ``$product->getCategory()->getName()``, *Doctrine* silenciosamente hace una segunda consulta para encontrar la ``Categoría`` que está relacionada con este ``Producto``. Entonces, prepara el objeto ``$category`` y te lo devuelve. - -.. image:: /images/book/doctrine_image_3_es.png - :align: center - -Lo importante es el hecho de que tienes fácil acceso a la categoría relacionada con el producto, pero, los datos de la categoría realmente no se recuperan hasta que pides la categoría (es decir, trata de «cargarlos de manera diferida»). - -También puedes consultar en la dirección contraria:: - - public function showProductAction($id) - { - $category = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Category') - ->find($id); - - $products = $category->getProducts(); - - // ... - } - -En este caso, ocurre lo mismo: primero consultas por un único objeto ``Categoría``, y luego *Doctrine* hace una segunda consulta para recuperar los objetos ``Producto`` relacionados, pero sólo una vez/si le preguntas por ellos (es decir, cuando invoques a ``->getProducts()``). -La variable ``$products`` es un arreglo de todos los objetos ``Producto`` relacionados con el objeto ``Categoría`` propuesto a través de sus valores ``category_id``. - -.. sidebar:: Relaciones y clases delegadas - - Esta «carga diferida» es posible porque, cuando sea necesario, *Doctrine* devuelve un objeto «delegado» en lugar del verdadero objeto. Veamos de nuevo el ejemplo anterior:: - - $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') - ->find($id); - - $category = $product->getCategory(); - - // imprime 'Proxies\AcmeStoreBundleEntityCategoryProxy' - echo get_class($category); - - Este objeto delegado extiende al verdadero objeto ``Categoría``, y se ve y actúa exactamente igual que él. La diferencia es que, al usar un objeto delegado, *Doctrine* puede retrasar la consulta de los datos reales de ``Categoría`` hasta que efectivamente se necesiten esos datos (por ejemplo, hasta que invoques a ``$category->getName()``). - - Las clases delegadas las genera *Doctrine* y se almacenan en el directorio :file:`cache`. - Y aunque probablemente nunca te des cuenta de que tu objeto ``$category`` en realidad es un objeto delegado, es importante tenerlo en cuenta. - - En la siguiente sección, al recuperar simultáneamente los datos del producto y la categoría (a través de una *unión*), *Doctrine* devolverá el *verdadero* objeto ``Categoría``, puesto que nada se tiene que cargar de forma diferida. - -Uniendo registros relacionados -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En los ejemplos anteriores, se realizaron dos consultas ---una para el objeto original (por ejemplo, una ``Categoría``)--- y otra para el/los objetos relacionados (por ejemplo, los objetos ``Producto``). - -.. tip:: - - Recuerda que puedes ver todas las consultas realizadas durante una petición a través de la barra de herramientas de depuración web. - -Por supuesto, si sabes por adelantado que necesitas tener acceso a los objetos, puedes evitar la segunda consulta emitiendo una unión en la consulta original. Agrega el siguiente método a la clase ``ProductRepository``:: - - // src/Acme/StoreBundle/Entity/ProductRepository.php - public function findOneByIdJoinedToCategory($id) - { - $query = $this->getEntityManager() - ->createQuery(' - SELECT p, c FROM AcmeStoreBundle:Product p - JOIN p.category c - WHERE p.id = :id' - )->setParameter('id', $id); - - try { - return $query->getSingleResult(); - } catch (\Doctrine\ORM\NoResultException $e) { - return null; - } - } - -Ahora, puedes utilizar este método en el controlador para consultar un objeto ``Producto`` y su correspondiente ``Categoría`` con una sola consulta:: - - public function showAction($id) - { - $product = $this->getDoctrine() - ->getRepository('AcmeStoreBundle:Product') - ->findOneByIdJoinedToCategory($id); - - $category = $product->getCategory(); - - // ... - } - -Más información sobre asociaciones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Esta sección ha sido una introducción a un tipo común de relación entre entidades, la relación uno a muchos. Para obtener detalles más avanzados y ejemplos de cómo utilizar otros tipos de relaciones (por ejemplo, ``uno a uno``, ``muchos a muchos``), consulta la sección `Asignando asociaciones`_ en la documentación de *Doctrine*. - -.. note:: - - Si estás utilizando anotaciones, tendrás que prefijar todas las anotaciones con ``ORM\`` (por ejemplo, ``ORM\OneToMany``), lo cual no se refleja en la documentación de *Doctrine*. También tendrás que incluir la declaración ``use Doctrine\ORM\Mapping as ORM;`` la cual *importa* el prefijo ``ORM`` de las anotaciones. - -Configurando ------------- - -*Doctrine* es altamente configurable, aunque probablemente nunca tendrás que preocuparte de la mayor parte de sus opciones. Para más información sobre la configuración de *Doctrine*, consulta la sección *Doctrine* del :doc:`Manual de referencia `. - -Ciclo de vida de las retrollamadas ----------------------------------- - -A veces, es necesario realizar una acción justo antes o después de insertar, actualizar o eliminar una entidad. Este tipo de acciones se conoce como «ciclo de vida» de las retrollamadas, ya que son métodos retrollamados que necesitas ejecutar durante las diferentes etapas del ciclo de vida de una entidad (por ejemplo, cuando la entidad es insertada, actualizada, eliminada, etc.) - -Si estás utilizando anotaciones para los metadatos, empieza por permitir el ciclo de vida de las retrollamadas. Esto no es necesario si estás usando *YAML* o *XML* para tu asignación: - -.. code-block:: php-annotations - - /** - * @ORM\Entity() - * @ORM\HasLifecycleCallbacks() - */ - class Product - { - // ... - } - -Ahora, puedes decir a *Doctrine* que ejecute un método en cualquiera de los eventos del ciclo de vida disponibles. Por ejemplo, supongamos que deseas establecer una columna de fecha ``created`` a la fecha actual, sólo cuando se persiste por primera vez la entidad (es decir, se inserta): - -.. configuration-block:: - - .. code-block:: php-annotations - - /** - * @ORM\PrePersist - */ - public function setCreatedValue() - { - $this->created = new \DateTime(); - } - - .. code-block:: yaml - - # src/Acme/StoreBundle/Resources/config/doctrine/Product.orm.yml - Acme\StoreBundle\Entity\Product: - type: entity - # ... - lifecycleCallbacks: - prePersist: [setCreatedValue] - - .. code-block:: xml - - - - - - - - - - - - - - -.. note:: - - En el ejemplo anterior se supone que has creado y asignado una propiedad ``created`` (no mostrada aquí). - -Ahora, justo antes de persistir la primer entidad, *Doctrine* automáticamente llamará a este método y establecerá el campo ``created`` a la fecha actual. - -Esto se puede repetir en cualquiera de los otros eventos del ciclo de vida, los cuales incluyen a: - -* ``preRemove`` -* ``postRemove`` -* ``prePersist`` -* ``postPersist`` -* ``preUpdate`` -* ``postUpdate`` -* ``postLoad`` -* ``loadClassMetadata`` - -Para más información sobre qué significan estos eventos y el ciclo de vida de las retrollamadas en general, consulta la sección `Ciclo de vida de los eventos`_ en la documentación de *Doctrine*. - -.. sidebar:: Ciclo de vida de retrollamada y escuchas de eventos - - Observa que el método ``setCreatedValue()`` no recibe argumentos. Este siempre es el caso para el ciclo de vida de las retrollamadas y es intencional: el ciclo de vida de las retrollamadas debe ser un método sencillo que se ocupe de transformar los datos internos de la entidad (por ejemplo, estableciendo un campo a creado/actualizado, generando un valor ficticio). - - Si necesitas hacer alguna tarea más pesada ---como llevar la bitácora de eventos o enviar un correo electrónico--- debes registrar una clase externa como un escucha o suscriptor de eventos y darle acceso a todos los recursos que necesites. Para más información, consulta :doc:`/cookbook/doctrine/event_listeners_subscribers`. - -Extensiones *Doctrine*: ``Timestampable``, ``Sluggable``, etc. --------------------------------------------------------------- - -*Doctrine* es bastante flexible, y dispone de una serie de extensiones de terceros que te permiten realizar fácilmente tareas repetitivas y comunes en tus entidades. -Estas incluyen cosas tales como ``Sluggable``, ``Timestampable``, ``Loggable``, ``Translatable`` y ``Tree``. - -Para más información sobre cómo encontrar y utilizar estas extensiones, ve el artículo sobre el uso de :doc:`extensiones comunes de Doctrine `. - -.. _book-doctrine-field-types: - -Referencia de tipos de campo *Doctrine* ---------------------------------------- - -*Doctrine* dispone de una gran cantidad de tipos de campo. Cada uno de estos asigna un tipo de dato *PHP* a un tipo de columna específica en cualquier base de datos que estés utilizando. Los siguientes tipos son compatibles con *Doctrine*: - -* **Cadenas** - - * ``string`` (usado para cadenas cortas) - * ``text`` (usado para cadenas grandes) - -* **Números** - - * ``integer`` - * ``smallint`` - * ``bigint`` - * ``decimal`` - * ``float`` - -* **Fechas y horas** (usa un objeto `DateTime`_ para estos campos en *PHP*) - - * ``date`` - * ``time`` - * ``datetime`` - -* **Otros tipos** - - * ``boolean`` - * ``object`` (serializado y almacenado en un campo ``CLOB``) - * ``array`` (serializado y almacenado en un campo ``CLOB``) - -Para más información, consulta la sección `Asignando tipos`_ en la documentación de *Doctrine*. - -Opciones de campo -~~~~~~~~~~~~~~~~~ - -Cada campo puede tener un conjunto de opciones aplicables. Las opciones disponibles incluyen ``type`` (el predeterminado es ``string``), ``name``, ``length``, ``unique`` y ``nullable``. Aquí tienes algunos ejemplos: - -.. configuration-block:: - - .. code-block:: php-annotations - - /** - * Un campo cadena con una longitud de 255 caracteres que no puede ser nulo - * (reflejando los valores predefinidos para 'type', 'length' - * y opciones *nullable*) - * - * @ORM\Column() - */ - protected $name; - - /** - * Un campo cadena de 150 caracteres de longitud que se persiste a una columna «email_address» y tiene un índice único. - * - * @ORM\Column(name="email_address", unique=true, length=150) - */ - protected $email; - - .. code-block:: yaml - - fields: - # Un campo cadena de longitud 255 que no puede ser null - # (reflejando los valores predefinidos para las opciones 'length' y 'nullable') - # el atributo type es necesario en las definiciones yaml - name: - type: string - - # Un campo cadena de longitud 150 que persiste a una columna 'email_address' - # y tiene un índice único. - email: - type: string - column: email_address - length: 150 - unique: true - - .. code-block:: xml - - # Un campo de tipo «string» de longitud 255 que no puede ser nulo - # (reflejando los valores predefinidos para las opciones «length» y «nullable») - # el atributo «type» es necesario en las definiciones yaml - name: - - - -.. note:: - - Hay algunas opciones más que no figuran en esta lista. Para más detalles, consulta la sección `Asignando propiedades`_ de la documentación de *Doctrine*. - -.. index:: - single: Doctrine; Ordenes de consola ORM - single: CLI; ORM de Doctrine - -Ordenes de consola ------------------- - -La integración del *ORM* de *Doctrine2* ofrece varias ordenes de consola bajo el espacio de nombres ``doctrine``. Para ver la lista de ordenes puedes ejecutar la consola sin ningún tipo de argumento: - -.. code-block:: bash - - $ php app/console - -Mostrará una lista con las ordenes disponibles, muchas de las cuales comienzan con el prefijo ``doctrine:``. Puedes encontrar más información sobre cualquiera de estas ordenes (o cualquier orden de *Symfony*) ejecutando la orden ``help``. Por ejemplo, para obtener detalles acerca de la tarea ``doctrine:database:create``, ejecuta: - -.. code-block:: bash - - $ php app/console help doctrine:database:create - -Algunas tareas notables o interesantes son: - -* ``doctrine:ensure-production-settings`` --- comprueba si el entorno actual está configurado de manera eficiente para producción. Esta siempre se debe ejecutar en el entorno ``prod``: - - .. code-block:: bash - - $ php app/console doctrine:ensure-production-settings --env=prod - -* ``doctrine:mapping:import`` --- permite a *Doctrine* llevar a cabo una introspección a una base de datos existente y crear información de asignación. Para más información, consulta :doc:`/cookbook/doctrine/reverse_engineering`. - -* ``doctrine:mapping:info`` --- te dice todas las entidades de las que *Doctrine* es consciente y si hay algún error básico con la asignación. - -* ``doctrine:query:dql`` y ``doctrine:query:sql`` --- te permiten ejecutar consultas *DQL* o *SQL* directamente desde la línea de ordenes. - -.. note:: - - Para poder cargar accesorios a tu base de datos, en su lugar, necesitas tener instalado el paquete ``DoctrineFixturesBundle``. Para aprender cómo hacerlo, lee el artículo «:doc:`/bundles/DoctrineFixturesBundle/index`» en la documentación. - -.. tip:: - - Esta página muestra cómo trabajar con *Doctrine* dentro de un controlador. Posiblemente también quieras trabajar con *Doctrine* en algún otro lugar en tu aplicación. El método :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::getDoctrine` del controlador regresa el servicio ``doctrine``, puedes trabajar con este de la misma manera que en cualquier otro lugar inyectándolo en tus propios servicios. Consulta :doc:`/book/service_container` para más sobre la creación de tus propios servicios. - -Resumen -------- - -Con *Doctrine*, puedes centrarte en tus objetos y la forma en que son útiles en tu aplicación y luego preocuparte por su persistencia en la base de datos. Esto se debe a que *Doctrine* te permite utilizar cualquier objeto *PHP* para almacenar los datos y se basa en la información de asignación de metadatos para asignar los datos de un objeto a una tabla particular de la base de datos. - -Y aunque *Doctrine* gira en torno a un concepto simple, es increíblemente poderoso, permitiéndote crear consultas complejas y suscribirte a los eventos que te permiten realizar diferentes acciones conforme los objetos recorren su ciclo de vida en la persistencia. - -Para más información acerca de *Doctrine*, ve la sección *Doctrine* del :doc:`Recetario `, que incluye los siguientes artículos: - -* :doc:`/bundles/DoctrineFixturesBundle/index` -* :doc:`/cookbook/doctrine/common_extensions` - -.. _`Doctrine`: http://www.doctrine-project.org/ -.. _`MongoDB`: http://www.mongodb.org/ -.. _`Documentación de asignación básica`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html -.. _`Generador de consultas`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/query-builder.html -.. _`Doctrine Query Language`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html -.. _`Asignando asociaciones`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html -.. _`DateTime`: http://www.php.net/manual/es/class.datetime.php -.. _`Asignando tipos`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#doctrine-mapping-types -.. _`Asignando propiedades`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping -.. _`Ciclo de vida de los eventos`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html#lifecycle-events -.. _`Documentación de palabras clave reservadas por SQL`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#quoting-reserved-words -.. _`Clases persistentes`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#persistent-classes -.. _`Asignación de propiedades`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/basic-mapping.html#property-mapping diff --git a/_sources/book/forms.txt b/_sources/book/forms.txt deleted file mode 100644 index c70abdc..0000000 --- a/_sources/book/forms.txt +++ /dev/null @@ -1,1312 +0,0 @@ -.. index:: - single: Formularios - -Formularios -=========== - -Utilizar formularios *HTML* es una de las más comunes ---y desafiantes--- tareas para un desarrollador web. *Symfony2* integra un componente ``Form`` que se ocupa de facilitarnos la utilización de formularios. En este capítulo, construirás un formulario complejo desde el principio, del cual, de paso, aprenderás las características más importantes de la biblioteca de formularios. - -.. note:: - - El componente ``Form`` de *Symfony* es una biblioteca independiente que puedes utilizar fuera de los proyectos *Symfony2*. Para más información, consulta el `Componente Form de Symfony2`_ en *Github*. - -.. index:: - single: Formularios; Creando un formulario sencillo - -Creando un formulario sencillo ------------------------------- - -Supón que estás construyendo una sencilla aplicación de tareas pendientes que necesita mostrar tus «pendientes». Debido a que tus usuarios tendrán que editar y crear tareas, tienes que crear un formulario. Pero antes de empezar, vamos a concentrarnos en la clase genérica ``Task`` que representa y almacena los datos para una sola tarea:: - - // src/Acme/TaskBundle/Entity/Task.php - namespace Acme\TaskBundle\Entity; - - class Task - { - protected $task; - - protected $dueDate; - - public function getTask() - { - return $this->task; - } - public function setTask($task) - { - $this->task = $task; - } - - public function getDueDate() - { - return $this->dueDate; - } - public function setDueDate(\DateTime $dueDate = null) - { - $this->dueDate = $dueDate; - } - } - -.. note:: - - Si estás codificando este ejemplo, primero crea el paquete ``AcmeTaskBundle`` ejecutando la siguiente orden (aceptando todas las opciones predeterminadas): - - .. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/TaskBundle - -Esta clase es un «antiguo objeto *PHP* sencillo», ya que, hasta ahora, no tiene nada que ver con *Symfony* o cualquier otra biblioteca. Es simplemente un objeto *PHP* normal que directamente resuelve un problema dentro de *tu* aplicación (es decir, la necesidad de representar una tarea pendiente en tu aplicación). Por supuesto, al final de este capítulo, serás capaz de enviar datos a una instancia de ``Task`` (a través de un formulario), validar sus datos, y persistirla en una base de datos. - -.. index:: - single: Formularios; Creando un formulario en un controlador - -Construyendo el formulario -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora que has creado una clase ``Task``, el siguiente paso es crear y reproducir el formulario *HTML* real. En *Symfony2*, esto se hace construyendo un objeto ``Form`` y luego pintándolo en una plantilla. Por ahora, esto se puede hacer en el interior de un controlador:: - - // src/Acme/TaskBundle/Controller/DefaultController.php - namespace Acme\TaskBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Acme\TaskBundle\Entity\Task; - use Symfony\Component\HttpFoundation\Request; - - class DefaultController extends Controller - { - public function newAction(Request $request) - { - // crea una task y le asigna algunos datos ficticios para este ejemplo - $task = new Task(); - $task->setTask('Write a blog post'); - $task->setDueDate(new \DateTime('tomorrow')); - - $form = $this->createFormBuilder($task) - ->add('task', 'text') - ->add('dueDate', 'date') - ->getForm(); - - return $this->render('AcmeTaskBundle:Default:new.html.twig', array( - 'form' => $form->createView(), - )); - } - } - -.. tip:: - - Este ejemplo muestra cómo crear el formulario directamente en el controlador. - Más adelante, en la sección «:ref:`book-form-creating-form-classes`», aprenderás cómo construir tu formulario en una clase independiente, lo cual es muy recomendable puesto que vuelve reutilizable tu formulario. - -La creación de un formulario requiere poco código relativamente, porque los objetos ``form`` de *Symfony2* se construyen con un «generador de formularios». El propósito del generador de formularios es permitirte escribir sencillas «recetas» de formulario, y hacer todo el trabajo pesado de la contrucción de un formulario. - -En este ejemplo, añadiste dos campos al formulario ---``task`` y ``dueDate``--- que corresponden a las propiedades ``task`` y ``dueDate`` de la clase ``Task``. -También asignaste a cada uno un «tipo» (por ejemplo, ``text``, ``date``), el cual entre otras cosas, determina qué etiqueta de formulario *HTML* se dibuja para ese campo. - -*Symfony2* viene con muchos tipos integrados que explicaremos en breve (consulta :ref:`book-forms-type-reference`). - -.. index:: - single: Formularios; Reproducción básica de plantillas - -Reproduciendo el formulario -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora que creaste el formulario, el siguiente paso es dibujarlo. Lo puedes hacer pasando un objeto ``view`` especial para formularios a tu plantilla (ten en cuenta la declaración ``$form->createView()`` en el controlador de arriba) y usando un conjunto de funciones ayudantes de formulario: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} -
- {{ form_widget(form) }} - - -
- - .. code-block:: html+php - - -
enctype($form) ?> > - widget($form) ?> - - -
- -.. image:: /images/book/form-simple.png - :align: center - -.. note:: - - Este ejemplo asume que has creado una ruta llamada ``task_new`` que apunta al controlador ``AcmeTaskBundle:Default:new`` creado anteriormente. - -¡Eso es todo! Al imprimir ``form_widget(form)``, se pinta cada campo en el formulario, junto con la etiqueta y un mensaje de error (si lo hay). Tan fácil como esto, aunque no es muy flexible (todavía). Por lo general, querrás reproducir individualmente cada campo del formulario para que puedas controlar la apariencia del formulario. Aprenderás cómo hacerlo en la sección «:ref:`form-rendering-template`». - -Antes de continuar, observa cómo el campo de entrada ``task`` reproducido tiene el valor de la propiedad ``task`` del objeto ``$task`` (es decir, «Escribe una entrada del *blog*»). -El primer trabajo de un formulario es: tomar datos de un objeto y traducirlos a un formato idóneo para reproducirlos en un formulario *HTML*. - -.. tip:: - - El sistema de formularios es lo suficientemente inteligente como para acceder al valor de la propiedad protegida ``task`` a través de los métodos ``getTask()`` y ``setTask()`` de la clase ``Task``. A menos que una propiedad sea pública, *debe* tener métodos «captadores» y «definidores» para que el componente ``Form`` pueda obtener y fijar datos en la propiedad. Para una propiedad booleana, puedes utilizar un método «isser» (por «es servicio», por ejemplo, ``isPublished()``) en lugar de un captador (por ejemplo, ``getPublished()`` o ``getReminder()``). - - .. versionadded:: 2.1 - La compatibilidad para los métodos «hasser» se añadió en *Symfony 2.1*. - -.. index:: - single: Formularios; Procesando el envío del formulario - -Procesando el envío del formulario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El segundo trabajo de un ``formulario`` es traducir los datos enviados por el usuario a las propiedades de un objeto. Para lograrlo, los datos presentados por el usuario deben estar vinculados al formulario. Añade la siguiente funcionalidad a tu controlador:: - - // ... - use Symfony\Component\HttpFoundation\Request; - - public function newAction(Request $request) - { - // sólo configura un objeto $task fresco (remueve los datos de prueba) - $task = new Task(); - - $form = $this->createFormBuilder($task) - ->add('task', 'text') - ->add('dueDate', 'date') - ->getForm(); - - if ($request->isMethod('POST')) { - $form->bind($request); - - if ($form->isValid()) { - // realiza alguna acción, tal como guardar la tarea en la base de datos - - return $this->redirect($this->generateUrl('task_success')); - } - } - - // ... - } - -.. versionadded:: 2.1 - El método ``bind`` se hizo más flexible en *Symfony 2.1*. Ahora acepta datos del cliente sin procesar (como antes) o un objeto ``Petición`` de *Symfony*. Este es preferible al método depreciado ``bindRequest``. - -Ahora, cuando se presente el formulario, el controlador vincula al formulario los datos presentados, los cuales se traducen en los nuevos datos de las propiedades ``task`` y ``dueDate`` del objeto ``$task``. Esto sucede a través del método ``bind()``. - -.. note:: - - Tan pronto como se llame a ``bind()``, los datos presentados se transfieren inmediatamente al objeto subyacente. Esto ocurre independientemente de si los datos subyacentes son válidos realmente o no. - -Este controlador sigue un patrón común para el manejo de formularios, y tiene tres posibles rutas: - -#. Inicialmente, cuando se carga el formulario en un navegador, el método de la petición es *GET*, lo cual significa simplemente que se debe crear y reproducir el formulario; - -#. Cuando el usuario envía el formulario (es decir, el método es *POST*), pero los datos presentados no son válidos (la validación se trata en la siguiente sección), el formulario es vinculado y, a continuación reproducido, esta vez mostrando todos los errores de validación; - -#. Cuando el usuario envía el formulario con datos válidos, el formulario es vinculado y en ese momento tienes la oportunidad de realizar algunas acciones usando el objeto ``$task`` (por ejemplo, persistirlo a la base de datos) antes de redirigir al usuario a otra página (por ejemplo, una página de «agradecimiento» o «éxito»). - -.. note:: - - Redirigir a un usuario después de un exitoso envío de formulario evita que el usuario pueda hacer clic en «actualizar» y volver a enviar los datos. - -.. index:: - single: Formularios; Validando - -Validando formularios ---------------------- - -En la sección anterior, aprendiste cómo se puede presentar un formulario con datos válidos o no válidos. En *Symfony2*, la validación se aplica al objeto subyacente (por ejemplo, ``Task``). En otras palabras, la cuestión no es si el «formulario» es válido, sino más bien si el objeto ``$task`` es válido después de aplicarle los datos enviados en el formulario. Invocar a ``$form->isValid()`` es un atajo que pregunta al objeto ``$task`` si tiene datos válidos o no. - -La validación se realiza añadiendo un conjunto de reglas (llamadas restricciones) a una clase. Para ver esto en acción, añade restricciones de validación para que el campo ``task`` no pueda estar vacío y el campo ``dueDate`` no pueda estar vacío y debe ser un objeto ``\DateTime`` válido. - -.. configuration-block:: - - .. code-block:: yaml - - # Acme/TaskBundle/Resources/config/validation.yml - Acme\TaskBundle\Entity\Task: - properties: - task: - - NotBlank: ~ - dueDate: - - NotBlank: ~ - - Type: \DateTime - - .. code-block:: php-annotations - - // Acme/TaskBundle/Entity/Task.php - use Symfony\Component\Validator\Constraints as Assert; - - class Task - { - /** - * @Assert\NotBlank() - */ - public $task; - - /** - * @Assert\NotBlank() - * @Assert\Type("\DateTime") - */ - protected $dueDate; - } - - .. code-block:: xml - - - - - - - - - \DateTime - - - - .. code-block:: php - - // Acme/TaskBundle/Entity/Task.php - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\NotBlank; - use Symfony\Component\Validator\Constraints\Type; - - class Task - { - // ... - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('task', new NotBlank()); - - $metadata->addPropertyConstraint('dueDate', new NotBlank()); - $metadata->addPropertyConstraint('dueDate', new Type('\DateTime')); - } - } - -¡Eso es todo! Si vuelves a enviar el formulario con datos no válidos, verás replicados los errores correspondientes en el formulario. - -.. _book-forms-html5-validation-disable: - -.. sidebar:: Validación HTML5 - - A partir de *HTML5*, muchos navegadores nativamente pueden imponer ciertas restricciones de validación en el lado del cliente. La validación más común se activa al reproducir un atributo ``required`` en los campos que son obligatorios. Para los navegadores compatibles con *HTML5*, esto se traducirá en un mensaje nativo del navegador que muestra si el usuario intenta enviar el formulario con ese campo en blanco. - - Los formularios generados sacan el máximo provecho de esta nueva característica añadiendo atributos *HTML* razonables que desencadenan la validación. La validación del lado del cliente, sin embargo, se puede desactivar añadiendo el atributo ``novalidate`` de la etiqueta ``form`` o ``formnovalidate`` a la etiqueta de envío. Esto es especialmente útil cuando deseas probar tus limitaciones en el lado del la validación del servidor, pero su navegador las previene, por ejemplo, la presentación de campos en blanco. - -La validación es una característica muy poderosa de *Symfony2* y tiene su propio :doc:`capítulo dedicado `. - -.. index:: - single: Formularios; Validando grupos - -.. _book-forms-validation-groups: - -Validando grupos -~~~~~~~~~~~~~~~~ - -.. tip:: - - Si no estás utilizando la :ref:`validación de grupos `, entonces puedes saltarte esta sección. - -Si tu objeto aprovecha la :ref:`validación de grupos `, tendrás que especificar la validación de grupos que utiliza tu formulario:: - - $form = $this->createFormBuilder($users, array( - 'validation_groups' => array('registration'), - ))->add(...); - -Si vas a crear :ref:`clases form ` (una buena práctica), entonces tendrás que agregar lo siguiente al método ``getDefaultOptions()``:: - - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'validation_groups' => array('registration') - )); - } - -En ambos casos, *sólo* se utilizará el grupo de validación ``registration`` para validar el objeto subyacente. - -Grupos basados en datos presentados -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - La posibilidad de especificar una retrollamada o Cierre en ``validation_groups`` es nueva en la versión 2.1 - -Si necesitas alguna lógica avanzada para determinar los grupos de validación (por ejemplo, basándote en datos presentados), puedes poner la opción ``validation_groups`` a un arreglo de retrollamadas, o a un ``Cierre``:: - - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'validation_groups' => array('Acme\\AcmeBundle\\Entity\\Client', 'determineValidationGroups'), - )); - } - -Esto llamará al método estático ``determineValidationGroups()`` en la clase ``Cliente`` después de vincular el formulario, pero antes de llevar a cabo la validación. -El objeto formulario se pasa como argumento al método (ve el siguiente ejemplo). -Además puedes definir tu lógica completa en línea usando un ``Cierre``:: - - 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()) { - return array('person'); - } else { - return array('company'); - } - }, - )); - } - -.. index:: - single: Formularios; Tipos de campo integrados - -.. _book-forms-type-reference: - -Tipos de campo integrados -------------------------- - -*Symfony* estándar viene con un gran grupo de tipos de campo que cubre todos los campos de formulario comunes y tipos de datos necesarios: - -.. include:: /reference/forms/types/map.rst.inc - -También puedes crear tus propios tipos de campo personalizados. Este tema se trata en el artículo «:doc:`/cookbook/form/create_custom_field_type`» del recetario. - -.. index:: - single: Formularios; Opciones del tipo de campo - -Opciones del tipo de campo -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada tipo de campo tiene una serie de opciones que puedes utilizar para configurarlo. -Por ejemplo, el campo ``dueDate`` se está traduciendo como 3 cajas de selección. Sin embargo, puedes configurar el :doc:`campo de fecha ` para que sea interpretado como un cuadro de texto (donde el usuario introduce la fecha como una cadena en el cuadro):: - - ->add('dueDate', 'date', array('widget' => 'single_text')) - -.. image:: /images/book/form-simple2.png - :align: center - -Cada tipo de campo tiene una diferente serie de opciones que le puedes pasar. -Muchas de ellas son específicas para el tipo de campo y puedes encontrar los detalles en la documentación de cada tipo. - -.. sidebar:: La opción ``required`` - - La opción más común es la opción ``required``, la cual puedes aplicar a cualquier campo. De manera predeterminada, la opción ``required`` está establecida en ``true``, lo cual significa que los navegadores preparados para *HTML5* aplicarán la validación en el cliente si el campo se deja en blanco. Si no deseas este comportamiento, establece la opción ``required`` en tu campo a ``false`` o :ref:`desactiva la validación de HTML5 `. - - También ten en cuenta que al establecer la opción ``required`` a ``true`` **no** resultará en aplicar la validación de lado del servidor. En otras palabras, si un usuario envía un valor en blanco para el campo (ya sea con un navegador antiguo o un servicio web, por ejemplo), será aceptado como un valor válido a menos que utilices la validación de restricción ``NotBlank`` o ``NotNull`` de *Symfony*. - - En otras palabras, la opción ``required`` es «agradable», pero ciertamente *siempre* se debe utilizar de lado del servidor. - -.. sidebar:: La opción ``label`` - - La etiqueta para el campo ``form`` se puede fijar usando la opción ``label``, la cual se puede aplicar a cualquier campo:: - - ->add('dueDate', 'date', array( - 'widget' => 'single_text', - 'label' => 'Due Date', - )) - - La etiqueta de un campo también se puede configurar al pintar la plantilla del formulario, ve más abajo. - -.. index:: - single: Formularios; Deduciendo el tipo de campo - -.. _book-forms-field-guessing: - -Deduciendo el tipo de campo ---------------------------- - -Ahora que has añadido metadatos de validación a la clase ``Task``, *Symfony* ya sabe un poco sobre tus campos. Si le permites, *Symfony* puede «deducir» el tipo de tu campo y configurarlo por ti. En este ejemplo, *Symfony* lo puede deducir a partir de las reglas de validación de ambos campos, ``task`` es un campo de texto normal y ``dueDate`` es un campo ``date``:: - - public function newAction() - { - $task = new Task(); - - $form = $this->createFormBuilder($task) - ->add('task') - ->add('dueDate', null, array('widget' => 'single_text')) - ->getForm(); - } - -El «adivino» se activa cuando omites el segundo argumento del método ``add()`` (o si le pasas ``null``). Si pasas un arreglo de opciones como tercer argumento (hecho por ``dueDate`` arriba), estas opciones se aplican al campo inferido. - -.. caution:: - - Si tu formulario utiliza una validación de grupo específica, el adivino del tipo de campo seguirá considerando *todas* las restricciones de validación cuando infiere el tipo de campo (incluyendo las restricciones que no son parte de la validación de grupo utilizada). - -.. index:: - single: Formularios; Deduciendo el tipo de campo - -Opciones para deducir el tipo de campo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Además de deducir el «tipo» de un campo, *Symfony* también puede tratar de inferir los valores correctos a partir de una serie de opciones del campo. - -.. tip:: - - Cuando estas opciones están configuradas, el campo se dibujará con los atributos *HTML* especiales proporcionados para la validación de *HTML5* en el cliente. Sin embargo, no genera el equivalente de las restricciones de lado del servidor (por ejemplo, ``Assert\Length``). - Y aunque tendrás que agregar manualmente la validación de lado del servidor, estas opciones del tipo de campo entonces se pueden deducir a partir de esa información. - -* ``required``: La opción ``required`` se puede deducir basándose en las reglas de validación (es decir, el campo es ``NotBlank`` o ``NotNull``) o los metadatos de *Doctrine* (es decir, el campo es ``nullable``). Esto es muy útil, ya que tu validación de lado del cliente se ajustará automáticamente a tus reglas de validación. - -* ``max_length``: Si el campo es una especie de campo de texto, entonces la opción ``max_length`` se puede inferir a partir de las restricciones de validación (si utilizas ``Length`` o ``Range``) o desde los metadatos de *Doctrine* (vía la longitud del campo). - -.. note:: - - Estas opciones de campo *sólo* se infieren si estás utilizando *Symfony* para deducir el tipo de campo (es decir, las omites por completo o pasas ``null`` como el segundo argumento de ``add()``). - -Si quieres cambiar uno de los valores inferidos, lo puedes redefinir pasando la opción en el arreglo de opciones del campo:: - - ->add('task', null, array('max_length' => 4)) - -.. index:: - single: Formularios; Reproduciendo en una plantilla - -.. _form-rendering-template: - -Reproduciendo un formulario en una plantilla --------------------------------------------- - -Hasta ahora, has visto cómo se puede reproducir todo el formulario con una sola línea de código. Por supuesto, generalmente necesitarás mucha más flexibilidad al reproducirlo: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} -
- {{ form_errors(form) }} - - {{ form_row(form.task) }} - {{ form_row(form.dueDate) }} - - {{ form_rest(form) }} - - -
- - .. code-block:: html+php - - -
enctype($form) ?>> - errors($form) ?> - - row($form['task']) ?> - row($form['dueDate']) ?> - - rest($form) ?> - - -
- -Échale un vistazo a cada parte: - -* ``form_enctype(form)`` --- Si por lo menos un campo es para carga de archivos, se reproduce el obligado ``enctype="multipart/form-data"``; - -* ``form_errors(form)`` --- Dibuja cualquier error global para todo el formulario (los errores específicos al campo se muestran junto a cada campo); - -* ``form_row(form.dueDate)`` --- Dibuja la etiqueta, cualquier error, y el elemento gráfico *HTML* del formulario para el campo en cuestión (por ejemplo, ``dueDate``), por omisión, en el interior de un elemento ``div``; - -* ``form_rest(form)`` --- Pinta todos los campos que aún no se han reproducido. - Por lo general es buena idea realizar una llamada a este ayudante en la parte inferior de cada formulario (en caso de haber olvidado sacar un campo o si no quieres preocuparte de reproducir manualmente los campos ocultos). Este ayudante también es útil para tomar ventaja de la :ref:`Protección CSRF ` automática. - -La mayor parte del trabajo la realiza el ayudante ``form_row``, el cual de manera predeterminada reproduce la etiqueta, los errores y el elemento gráfico *HTML* de cada campo del formulario dentro de una etiqueta ``div``. En la sección :ref:`form-theming`, aprenderás cómo puedes personalizar ``form_row`` en diferentes niveles. - -.. tip:: - - Puedes acceder a los datos reales de tu formulario vía ``form.vars.value``: - - .. configuration-block:: - - .. code-block:: jinja - - {{ form.vars.value.task }} - - .. code-block:: html+php - - get('value')->getTask() ?> - -.. index:: - single: Formularios; Reproduciendo cada campo a mano - -Reproduciendo cada campo a mano -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El ayudante ``form_row`` es magnífico porque rápidamente puedes reproducir cada campo del formulario (y también puedes personalizar el formato utilizado para la «fila»). Pero, puesto que la vida no siempre es tan simple, también puedes dibujar cada campo totalmente a mano. El producto final del siguiente fragmento es el mismo que cuando usas el ayudante ``form_row``: - -.. configuration-block:: - - .. code-block:: html+jinja - - {{ form_errors(form) }} - -
- {{ form_label(form.task) }} - {{ form_errors(form.task) }} - {{ form_widget(form.task) }} -
- -
- {{ form_label(form.dueDate) }} - {{ form_errors(form.dueDate) }} - {{ form_widget(form.dueDate) }} -
- - {{ form_rest(form) }} - - .. code-block:: html+php - - errors($form) ?> - -
- label($form['task']) ?> - errors($form['task']) ?> - widget($form['task']) ?> -
- -
- label($form['dueDate']) ?> - errors($form['dueDate']) ?> - widget($form['dueDate']) ?> -
- - rest($form) ?> - -Si la etiqueta generada automáticamente para un campo no es del todo correcta, la puedes especificar explícitamente: - -.. configuration-block:: - - .. code-block:: html+jinja - - {{ form_label(form.task, 'Task Description') }} - - .. code-block:: html+php - - label($form['task'], 'Task Description') ?> - -Algunos tipos de campo tienen opciones adicionales para su representación que puedes pasar al elemento gráfico. Estas opciones están documentadas con cada tipo, pero una opción común es ``attr``, la cual te permite modificar los atributos en el elemento del formulario. -Lo siguiente debería añadir la clase ``task_field`` al campo de entrada de texto reproducido: - -.. configuration-block:: - - .. code-block:: html+jinja - - {{ form_widget(form.task, { 'attr': {'class': 'task_field'} }) }} - - .. code-block:: html+php - - widget($form['task'], array( - 'attr' => array('class' => 'task_field'), - )) ?> - -Si necesitas dibujar campos de formulario «a mano», entonces puedes acceder a los valores individuales de los campos tal como el ``id`` ``nombre`` y ``etiqueta``. Por ejemplo, para conseguir el ``id``: - -.. configuration-block:: - - .. code-block:: html+jinja - - {{ form.task.vars.id }} - - .. code-block:: html+php - - get('id') ?> - -Para recuperar el valor utilizado para el atributo nombre del campo en el formulario necesitas utilizar -el valor ``full_name``: - -.. configuration-block:: - - .. code-block:: html+jinja - - {{ form.task.vars.full_name }} - - .. code-block:: html+php - - get('full_name') ?> - -Referencia de funciones de plantilla *Twig* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si estás utilizando *Twig*, hay disponible una referencia completa de las funciones de reproducción de formularios en el :doc:`Manual de referencia `. -Estúdiala para conocer todo acerca de los ayudantes y las opciones disponibles que puedes utilizar con cada uno. - -.. index:: - single: Formularios; Creando clases Form - -.. _book-form-creating-form-classes: - -Creando clases ``Form`` ------------------------ - -Como viste, puedes crear un formulario y utilizarlo directamente en un controlador. -Sin embargo, una mejor práctica es construir el formulario en una clase separada, independiente de las clases *PHP*, misma que puedes reutilizar en cualquier lugar de tu aplicación. Crea una nueva clase que albergará la lógica para la construcción del formulario de la tarea:: - - // src/Acme/TaskBundle/Form/Type/TaskType.php - namespace Acme\TaskBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - - class TaskType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('task'); - $builder->add('dueDate', null, array('widget' => 'single_text')); - } - - public function getName() - { - return 'task'; - } - } - -Esta nueva clase contiene todas las indicaciones necesarias para crear el formulario de la tarea (observa que el método ``getName()`` devolverá un identificador único para este «tipo» de formulario). La puedes utilizar para construir rápidamente un objeto formulario en el controlador:: - - // src/Acme/TaskBundle/Controller/DefaultController.php - - // agrega esta nueva declaración use en lo alto de la clase - use Acme\TaskBundle\Form\Type\TaskType; - - public function newAction() - { - $task = ...; - $form = $this->createForm(new TaskType(), $task); - - // ... - } - -Colocar la lógica del formulario en su propia clase significa que fácilmente puedes reutilizar el formulario en otra parte del proyecto. Esta es la mejor manera de crear formularios, pero la decisión en última instancia, depende de ti. - -.. _book-forms-data-class: - -.. sidebar:: Configurando el ``data_class`` - - Cada formulario tiene que conocer el nombre de la clase que contiene los datos subyacentes (por ejemplo, ``Acme\TaskBundle\Entity\Task``). Por lo general, esto sólo se deduce basándose en el objeto pasado como segundo argumento de ``createForm`` (es decir, ``$task``). Más tarde, cuando comiences a incorporar formularios, esto ya no será suficiente. Así que, si bien no siempre es necesario, generalmente es buena idea especificar directamente la opción ``data_class`` añadiendo lo siguiente al tipo de tu clase formulario:: - - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', - )); - } - -.. tip:: - - Al asignar formularios a objetos, se asignan todos los campos. Todos los campos del formulario que no existen en el objeto asignado provocarán que se lance una excepción. - - En los casos donde necesites más campos en el formulario (por ejemplo: para una casilla de verificación «Estoy de acuerdo con estos términos») que no se asociará al objeto subyacente, necesitas establecer la opción ``property_path`` a ``false``:: - - use Symfony\Component\Form\FormBuilderInterface; - - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('task'); - $builder->add('dueDate', null, array('mapped' => false)); - } - - Además, si hay algunos campos en el formulario que no se incluyen en los datos presentados, esos campos explícitamente se establecerán en ``null``. - - Los datos del campo se pueden acceder en un controlador con:: - - $form->get('dueDate')->getData(); - -.. index:: - pair: Formularios; Doctrine - -Formularios y *Doctrine* ------------------------- - -El objetivo de un formulario es traducir los datos de un objeto (por ejemplo, ``Task``) a un formulario *HTML* y luego traducir los datos enviados por el usuario al objeto original. Como tal, el tema de la persistencia del objeto ``Task`` a la base de datos es del todo ajeno al tema de los formularios. Pero, si has configurado la clase ``Task`` para persistirla a través de *Doctrine* (es decir, que le has añadido :ref:`metadatos de asignación `), entonces persistirla después de la presentación de un formulario se puede hacer cuando el formulario es válido:: - - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - $em->persist($task); - $em->flush(); - - return $this->redirect($this->generateUrl('task_success')); - } - -Si por alguna razón, no tienes acceso a tu objeto ``$task`` original, lo puedes recuperar desde el formulario:: - - $task = $form->getData(); - -Para más información, consulta el capítulo :doc:`ORM de Doctrine `. - -La clave es entender que cuando el formulario está vinculado, los datos presentados inmediatamente se transfieren al objeto subyacente. Si deseas conservar los datos, sólo tendrás que conservar el objeto en sí (el cual ya contiene los datos presentados). - -.. index:: - single: Formularios; Integrando formularios - -Integrando formularios ----------------------- - -A menudo, querrás crear un formulario que incluye campos de muchos objetos diferentes. Por ejemplo, un formulario de registro puede contener datos que pertenecen a un objeto ``User``, así como a muchos objetos ``Address``. Afortunadamente, esto es fácil y natural con el componente ``Form``. - -Integrando un solo objeto -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Supongamos que cada ``Task`` pertenece a un simple objeto ``Categoría``. Inicia, por supuesto, creando el objeto ``Categoría``:: - - // src/Acme/TaskBundle/Entity/Category.php - namespace Acme\TaskBundle\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Category - { - /** - * @Assert\NotBlank() - */ - public $name; - } - -A continuación, añade una nueva propiedad ``categoría`` a la clase ``Task``:: - - // ... - - class Task - { - // ... - - /** - * @Assert\Type(type="Acme\TaskBundle\Entity\Category") - */ - protected $category; - - // ... - - public function getCategory() - { - return $this->category; - } - - public function setCategory(Category $category = null) - { - $this->category = $category; - } - } - -Ahora que actualizaste tu aplicación para reflejar las nuevas necesidades, crea una clase formulario para que el usuario pueda modificar un objeto ``Categoría``:: - - // src/Acme/TaskBundle/Form/Type/CategoryType.php - namespace Acme\TaskBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class CategoryType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('name'); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Category', - )); - } - - public function getName() - { - return 'category'; - } - } - -El objetivo final es permitir que la ``Categoría`` de una ``Task`` sea modificada justo dentro del mismo formulario de la tarea. Para lograr esto, añade un campo ``categoría`` al objeto ``TaskType`` cuyo tipo es una instancia de la nueva clase ``CategoryType``: - -.. code-block:: php - - use Symfony\Component\Form\FormBuilderInterface; - - public function buildForm(FormBuilderInterface $builder, array $options) - { - // ... - - $builder->add('category', new CategoryType()); - } - -Los campos de ``CategoryType`` ahora se pueden reproducir junto a los de la clase ``TaskType``. Para activar la validación en ``CategoryType``, añade la opción ``cascade_validation`` como ``TaskType``:: - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', - 'cascade_validation' => true, - )); - } - -Reproduce los campos de ``Categoría`` de la misma manera que los campos de la ``Task`` original: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# ... #} - -

Category

-
- {{ form_row(form.category.name) }} -
- - {{ form_rest(form) }} - {# ... #} - - .. code-block:: html+php - - - -

Category

-
- row($form['category']['name']) ?> -
- - rest($form) ?> - - -Cuando el usuario envía el formulario, los datos presentados para los campos de ``Categoría`` se utilizan para construir una instancia de ``Categoría``, que entonces se establece en el campo ``categoría`` de la instancia de ``Task``. - -La instancia de ``Categoría`` es accesible naturalmente vía ``$task->getCategory()`` y la puedes persistir en la base de datos o utilizarla como necesites. - -Integrando una colección de formularios -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes integrar una colección de formularios en un solo formulario (imagina un formulario ``Categoría`` con muchos subformularios ``Producto``). Esto se consigue usando el tipo de campo ``collection``. - -Para más información consulta el artículo «:doc:`/cookbook/form/form_collections`» del recetario y la referencia del tipo de campo :doc:`collection`. - -.. index:: - single: Formularios; Tematizando - single: Formularios; Personalizando campos - -.. _form-theming: - -Tematizando formularios ------------------------ - -Puedes personalizar cómo se reproduce cada parte de un formulario. Eres libre de cambiar la forma en que se dibuja cada «fila» del formulario, cambiar el formato que sirve para reproducir errores, e incluso personalizar la forma en que se debe reproducir una etiqueta ``textarea``. Nada está fuera de límites, y puedes usar diferentes personalizaciones en diferentes lugares. - -*Symfony* utiliza plantillas para reproducir todas y cada una de las partes de un formulario, como las etiquetas ``label``, etiquetas ``input``, mensajes de error y todo lo demás. - -En *Twig*, cada «fragmento» del formulario está representado por un bloque *Twig*. Para personalizar alguna parte de cómo se reproduce un formulario, sólo hay que reemplazar el bloque adecuado. - -En *PHP*, cada «fragmento» del formulario se reproduce vía un archivo de plantilla individual. -Para personalizar cualquier parte de cómo se reproduce un formulario, sólo hay que reemplazar la plantilla existente creando una nueva. - -Para entender cómo funciona esto, vamos a personalizar el fragmento ``form_row`` añadiendo un atributo «class» al elemento ``div`` que envuelve cada fila. Para ello, crea un nuevo archivo de plantilla que almacenará el nuevo marcado: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/TaskBundle/Resources/views/Form/fields.html.twig #} - {% block form_row %} - {% spaceless %} -
- {{ form_label(form) }} - {{ form_errors(form) }} - {{ form_widget(form) }} -
- {% endspaceless %} - {% endblock form_row %} - - .. code-block:: html+php - - -
- label($form, $label) ?> - errors($form) ?> - widget($form, $parameters) ?> -
- -El fragmento ``field_row`` del formulario se usa cuando al dibujar la mayoría de los campos a través de la función ``form_row``. Para decir al componente ``Form`` que utilice tu nuevo fragmento ``field_row`` definido anteriormente, añade lo siguiente en la parte superior de la plantilla que dibuja el formulario: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} - {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' %} - - {% form_theme form 'AcmeTaskBundle:Form:fields.html.twig' - 'AcmeTaskBundle:Form:fields2.html.twig' %} - -
- - .. code-block:: html+php - - - setTheme($form, array('AcmeTaskBundle:Form')) ?> - - setTheme($form, array('AcmeTaskBundle:Form', 'AcmeTaskBundle:Form')) ?> - - - -La etiqueta ``form_theme`` (en *Twig*) «importa» los fragmentos definidos en la plantilla dada y los utiliza al reproducir el formulario. En otras palabras, cuando más adelante en esta plantilla se invoque la función ``form_row``, se utilizará el bloque ``field_row`` de tu tema personalizado (en lugar del bloque ``field_row`` predefinido suministrado con *Symfony*). - -Tu tema personalizado no tiene que sustituir todos los bloques. Cuando dibujes un bloque que no se reemplaza en tu tema personalizado, el motor de creación de temas caerá de nuevo en el tema global (definido a nivel del paquete). - -Si hay varios temas personalizados siempre se buscará en el orden listado antes de caer de nuevo al tema global. - -Para personalizar cualquier parte de un formulario, sólo tienes que reemplazar el fragmento apropiado. Saber exactamente qué bloque sustituir es el tema de la siguiente sección. - -.. versionadded:: 2.1 - Introduce una sintaxis alterna para el ``form_theme`` de *Twig*. Esta acepta cualquier expresión *Twig* válida (la diferencia más notable es el uso de un arreglo cuando utilizas múltiples temas). - - .. code-block:: html+jinja - - {# src/Acme/TaskBundle/Resources/views/Default/new.html.twig #} - - {% form_theme form with 'AcmeTaskBundle:Form:fields.html.twig' %} - - {% form_theme form with ['AcmeTaskBundle:Form:fields.html.twig', - 'AcmeTaskBundle:Form:fields2.html.twig'] %} - -Para una explicación más extensa, consulta :doc:`/cookbook/form/form_customization`. - -.. index:: - single: Formularios; Nombrando fragmentos de la plantilla - -.. _form-template-blocks: - -Nombrando fragmentos de formulario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En *Symfony*, cada parte de un formulario reproducido ---elementos *HTML* de formulario, errores, -etiquetas, etc.--- se definen en base a un tema, el cual es una colección de bloques en *Twig* y una colección de archivos de plantilla en *PHP*. - -En *Twig*, cada bloque necesario se define en un solo archivo de plantilla (`form_div_layout.html.twig`_) que vive dentro del `puente Twig`_. Dentro de este archivo, puedes ver todos los bloques necesarios para reproducir un formulario y cada tipo de campo predeterminado. - -En *PHP*, los fragmentos son archivos de plantilla individuales. De manera predeterminada se encuentran en el directorio ``Resources/views/Form`` del paquete de la plataforma (`ver en GitHub`_). - -El nombre de cada fragmento sigue el mismo patrón básico y se divide en dos partes, separadas por un solo carácter de guión bajo (``_``). Algunos ejemplos son: - -* ``form_row`` --- usado por ``form_row`` para reproducir la mayoría de los campos; -* ``textarea_widget`` --- usado por ``form_widget`` para dibujar un campo de tipo ``textarea``; -* ``form_errors`` --- usado por ``form_errors`` para dibujar los errores de un campo; - -Cada fragmento sigue el mismo patrón básico: ``type_part``. La porción ``type`` corresponde al *tipo* del campo que se está reproduciendo (por ejemplo, ``textarea``, ``checkbox``, ``date``, etc.), mientras que la porción ``part`` corresponde a *qué* se está reproduciendo (por ejemplo, ``label``, ``widget``, ``errors``, etc.). Por omisión, hay cuatro posibles *partes* de un formulario que puedes pintar: - -+-------------+--------------------------+---------------------------------------------------------+ -| ``label`` | (p. ej. ``form_label``) | dibuja la etiqueta de los campos | -+-------------+--------------------------+---------------------------------------------------------+ -| ``widget`` | (p. ej. ``form_widget``) | dibuja la representación *HTML* de los campos | -+-------------+--------------------------+---------------------------------------------------------+ -| ``errors`` | (p. ej. ``form_errors``) | dibuja los errores de los campos | -+-------------+--------------------------+---------------------------------------------------------+ -| ``row`` | (p. ej. ``form_row``) | dibuja el renglón completo de los campos (etiqueta, | -| | | elemento gráfico y errores) | -+-------------+--------------------------+---------------------------------------------------------+ - -.. note:: - - En realidad, hay otras 3 *partes* ---``rows``, ``rest`` y ``enctype``--- pero rara vez o quizá nunca te tengas que preocupar de cómo sustituirlas. - -Al conocer el tipo de campo (por ejemplo, ``textarea``) y cual parte deseas personalizar (por ejemplo, ``widget``), puedes construir el nombre del fragmento que se debe redefinir (por ejemplo, ``textarea_widget``). - -.. index:: - single: Formularios; Heredando fragmentos de plantilla - -Heredando fragmentos de plantilla -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En algunos casos, parece que falta el fragmento que deseas personalizar. -Por ejemplo, no hay fragmento ``textarea_errors`` en los temas predeterminados provistos con *Symfony*. Entonces, ¿cómo se reproducen los errores de un campo ``textarea``? - -La respuesta es: a través del fragmento ``field_errors``. Cuando *Symfony* pinta los errores del tipo ``textarea``, primero busca un fragmento ``textarea_errors`` antes de caer de nuevo al fragmento ``form_errors``. Cada tipo de campo tiene un tipo *padre* (el tipo primario del ``textarea`` es ``text``, y su padre es el ``form``), y *Symfony* utiliza el fragmento para el tipo del padre si no existe el fragmento base. - -Por lo tanto, para sustituir *sólo* los errores de los campos ``textarea``, copia el fragmento ``form_errors``, renómbralo como ``textarea_errors`` y personalízalo. Para sustituir la reproducción predeterminada para error de *todos* los campos, copia y personaliza el fragmento ``form_errors`` directamente. - -.. tip:: - - El tipo «padre» de cada tipo de campo está disponible en la :doc:`referencia del tipo form ` para cada tipo de campo. - -.. index:: - single: Formularios; Tematizado global - -Tematizando formularios globalmente -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En el ejemplo anterior, utilizaste el ayudante ``form_theme`` (en *Twig*) para «importar» fragmentos de formulario personalizados *sólo* para ese formulario. También puedes decirle a *Symfony* que importe formularios personalizados a través de tu proyecto. - -*Twig* -...... - -Para incluir automáticamente en *todas* las plantillas los bloques personalizados de la plantilla ``fields.html.twig`` creada anteriormente, modifica el archivo de configuración de tu aplicación: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - twig: - form: - resources: - - 'AcmeTaskBundle:Form:fields.html.twig' - # ... - - .. code-block:: xml - - - - - AcmeTaskBundle:Form:fields.html.twig - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('twig', array( - 'form' => array( - 'resources' => array( - 'AcmeTaskBundle:Form:fields.html.twig', - ), - ), - // ... - )); - -Ahora se utilizan todos los bloques dentro de la plantilla ``fields.html.twig`` a nivel global para definir el formulario producido. - -.. sidebar:: Personalizando toda la salida del formulario en un único archivo con *Twig* - - En *Twig*, también puedes personalizar el bloque correcto de un formulario dentro de la plantilla donde se necesita esa personalización: - - .. code-block:: html+jinja - - {% extends '::base.html.twig' %} - - {# importa '_self' como el tema del formulario #} - {% form_theme form _self %} - - {# hace la personalización del fragmento del formulario #} - {% block form_row %} - {# pinta la fila del campo personalizado #} - {% endblock form_row %} - - {% block content %} - {# ... #} - - {{ form_row(form.task) }} - {% endblock %} - - La etiqueta ``{% form_theme form_self %}`` permite personalizar bloques directamente dentro de la plantilla que utilizará las personalizaciones. Utiliza este método para crear rápidamente formularios personalizados que sólo son necesarios en una sola plantilla. - - .. caution:: - - La funcionalidad ``{% form_theme form _self %}`` *únicamente* trabajará si tu plantilla extiende a otra. Si no, debes hacer que ``form_theme`` apunte a una plantilla distinta. - -*PHP* -..... - -Para incluir automáticamente *todas* las plantillas personalizadas del directorio `Acme/TaskBundle/Resources/views/Form` creado anteriormente, modifica el archivo de configuración de tu aplicación: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - templating: - form: - resources: - - 'AcmeTaskBundle:Form' - # ... - - - .. code-block:: xml - - - - - - AcmeTaskBundle:Form - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'templating' => array( - 'form' => array( - 'resources' => array( - 'AcmeTaskBundle:Form', - ), - ), - ) - // ... - )); - -Cualquier fragmento dentro del directorio `Acme/TaskBundle/Resources/views/Form` ahora se utiliza globalmente para definir la salida del formulario. - -.. index:: - single: Formularios; Protección CSRF - -.. _forms-csrf: - -Protección *CSRF* ------------------ - -*CSRF* (Cross-site request forgery) ---o `Falsificación de petición en sitios cruzados`_--- es un método por el cual un usuario malintencionado intenta hacer que tus usuarios legítimos, sin saberlo, presenten datos que no tienen la intención de enviar. Afortunadamente, los ataques *CSRF* se pueden prevenir usando un elemento *CSRF* dentro de tus formularios. - -La buena nueva es que, por omisión, *Symfony* integra y valida elementos *CSRF* automáticamente. Esto significa que puedes aprovechar la protección *CSRF* sin hacer nada. De hecho, ¡cada formulario en este capítulo se ha aprovechado de la protección *CSRF*! - -La protección *CSRF* funciona añadiendo un campo oculto al formulario ---por omisión denominado ``_token``--- el cual contiene un valor que sólo tú y tu usuario conocen. Esto garantiza que el usuario ---y no alguna otra entidad--- es el que presenta dichos datos. -*Symfony* automáticamente valida la presencia y exactitud de este elemento. - -El campo ``_token`` es un campo oculto y será reproducido automáticamente si se incluye la función ``form_rest()`` de la plantilla, la cual garantiza que se presenten todos los campos producidos. - -El elemento *CSRF* se puede personalizar formulario por formulario. Por ejemplo:: - - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class TaskType extends AbstractType - { - // ... - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', - 'csrf_protection' => true, - 'csrf_field_name' => '_token', - // una clave única para ayudar generar la ficha secreta - 'intention' => 'task_item', - )); - } - - // ... - } - -Para desactivar la protección *CSRF*, fija la opción ``csrf_protection`` a ``false``. -Las personalizaciones también se pueden hacer a nivel global en tu proyecto. Para más información, consulta la sección :ref:`referencia de configuración de formularios `. - -.. note:: - - La opción ``intention`` es opcional pero mejora considerablemente la seguridad del elemento generado produciendo uno diferente para cada formulario. - -.. index:: - single: Formularios; Sin clase - -Usando un formulario sin clase ------------------------------- - -En la mayoría de los casos, un formulario está ligado a un objeto, y los campos del formulario obtienen y almacenan sus datos en las propiedades de ese objeto. Esto exactamente es lo que has visto hasta ahora en este capítulo con la clase ``Task``. - -Pero a veces, es posible que sólo desees utilizar un formulario sin una clase, y devolver un arreglo de los datos presentados. Esto realmente es muy fácil:: - - // asegúrate de importar el espacio de nombres Request antes de la clase - use Symfony\Component\HttpFoundation\Request; - // ... - - public function contactAction(Request $request) - { - $defaultData = array('message' => 'Type your message here'); - $form = $this->createFormBuilder($defaultData) - ->add('name', 'text') - ->add('email', 'email') - ->add('message', 'textarea') - ->getForm(); - - if ($request->isMethod('POST')) { - $form->bind($request); - - // data es un arreglo con claves 'name', 'email', y 'message' - $data = $form->getData(); - } - - // ... pinta el formulario - } - -Por omisión, un formulario en realidad asume que deseas trabajar con arreglos de datos, en lugar de con un objeto. Hay exactamente dos maneras en que puedes cambiar este comportamiento y en su lugar enlazar el formulario a un objeto: - -#. Pasa un objeto al crear el formulario (como primer argumento de ``createFormBuilder`` o segundo argumento de ``createForm``); - -#. Declara la opción ``data_class`` en tu formulario. - -Si *no* haces ninguna de estas, entonces el formulario devolverá los datos como un arreglo. En este ejemplo, debido a que ``$defaultData`` no es un objeto (y no se ha establecido la opción ``data_class``), en última instancia ``$form->getData()``, devuelve un arreglo. - -.. tip:: - - También puedes acceder a los valores *POST* (en este caso ``«name»``) directamente a través del objeto ``Petición``, de la siguiente manera:: - - $this->get('request')->request->get('name'); - - Ten en cuenta, sin embargo, que en la mayoría de los casos una mejor opción es utilizar el método ``getData()``, ya que devuelve los datos (generalmente un objeto), después de que la infraestructura del formulario los ha transformado. - -Añadiendo validación -~~~~~~~~~~~~~~~~~~~~ - -La única pieza faltante es la validación. Por lo general, cuando llamas a ``$form->isValid()``, el objeto es validado leyendo las restricciones que aplicaste a esa clase. Si tu formulario está vinculado a un objeto (es decir, estás utilizando la opción ``data_class`` -o pasando un objeto a tu formulario), este casi siempre es el enfoque que quieres usar. Ve :doc:`/book/validation` para más detalles. - -.. _form-option-constraints: - -Pero si no está vinculado a un objeto y en cambio recuperaste un simple arreglo de los datos presentados, ¿cómo puedes agregar restricciones a los datos de tu formulario? - -La respuesta es configurar las restricciones tú mismo, y anexarlas a los campos individuales. El enfoque general está cubierto un poco más en el :ref:`capítulo de validación `, pero aquí está un pequeño ejemplo: - -.. versionadded:: 2.1 - La opción ``constraints``, que acepta una única restricción o un arreglo de restricciones (antes de 2.1, la opción fue llamada ``validation_constraint``, y sólo acepta una única restricción) es nueva para *Symfony 2.1*. - -.. code-block:: php - - use Symfony\Component\Validator\Constraints\Length; - use Symfony\Component\Validator\Constraints\NotBlank; - - $builder - ->add('firstName', 'text', array( - 'constraints' => new Length(array('min' => 3)), - )) - ->add('lastName', 'text', array( - 'constraints' => array( - new NotBlank(), - new Length(array('min' => 3)), - ), - )) - ; - -.. tip:: - - Si utilizas grupos de validación, necesitas o bien hacer referencia al grupo ``Default`` al crear el formulario, o establecer el grupo correcto en la restricción que estás añadiendo. - -.. code-block:: php - - new NotBlank(array('groups' => array('create', 'update')) - - -Consideraciones finales ------------------------ - -Ahora ya conoces todos los bloques de construcción necesarios para elaborar formularios complejos y funcionales para tu aplicación. Cuando construyas formularios, ten en cuenta que el primer objetivo de un formulario es traducir los datos de un objeto (``Task``) a un formulario *HTML* para que el usuario pueda modificar esos datos. El segundo objetivo de un formulario es tomar los datos presentados por el usuario y volverlos a aplicar al objeto. - -Todavía hay mucho más que aprender sobre el poderoso mundo de los formularios, tal como la forma de :doc:`manejar archivos subidos con Doctrine ` o cómo crear un formulario donde puedes agregar dinámicamente una serie de subformularios (por ejemplo, una lista de tareas donde puedes seguir añadiendo más campos a través de *Javascript* antes de presentarlos). Consulta el recetario para estos temas. Además, asegúrate de apoyarte en la :doc:`referencia de tipos de campo `, que incluye ejemplos de cómo utilizar cada tipo de campo y sus opciones. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/doctrine/file_uploads` -* :doc:`Referencia del campo File ` -* :doc:`Creando tipos de campo personalizados ` -* :doc:`/cookbook/form/form_customization` -* :doc:`/cookbook/form/dynamic_form_modification` -* :doc:`/cookbook/form/data_transformers` - -.. _`Componente Form de Symfony2`: https://github.com/symfony/Form -.. _`DateTime`: http://www.php.net/manual/es/class.datetime.php -.. _`puente Twig`: https://github.com/symfony/symfony/tree/2.2/src/Symfony/Bridge/Twig -.. _`form_div_layout.html.twig`: https://github.com/symfony/symfony/blob/2.2/src/Symfony/Bridge/Twig/Resources/views/Form/form_div_layout.html.twig -.. _`Falsificación de petición en sitios cruzados`: http://es.wikipedia.org/wiki/Cross_Site_Request_Forgery -.. _`ver en GitHub`: https://github.com/symfony/symfony/tree/2.2/src/Symfony/Bundle/FrameworkBundle/Resources/views/Form diff --git a/_sources/book/from_flat_php_to_symfony2.txt b/_sources/book/from_flat_php_to_symfony2.txt deleted file mode 100644 index 9cdcc90..0000000 --- a/_sources/book/from_flat_php_to_symfony2.txt +++ /dev/null @@ -1,628 +0,0 @@ -*Symfony2* frente a *PHP* simple -================================ - -**¿Por qué Symfony2 es mejor que sólo abrir un archivo y escribir PHP simple?** - -Si nunca has usado una plataforma *PHP*, no estás familiarizado con la filosofía *MVC*, o simplemente te preguntas qué es todo ese *alboroto* en torno a *Symfony2*, este capítulo es para ti. En vez de *decirte* que *Symfony2* te permite desarrollar software más rápido y mejor que con *PHP* simple, debes verlo tú mismo. - -En este capítulo, vamos a escribir una aplicación sencilla en *PHP* simple, y luego la reconstruiremos para que esté mejor organizada. Podrás viajar a través del tiempo, viendo las decisiones de por qué el desarrollo web ha evolucionado en los últimos años hasta donde está ahora. - -Al final, verás cómo *Symfony2* te puede rescatar de las tareas cotidianas y te permite recuperar el control de tu código. - -Un sencillo *blog* en *PHP* simple ----------------------------------- - -En este capítulo, crearemos una simbólica aplicación de *blog* utilizando sólo *PHP* simple. -Para empezar, crea una página que muestre las entradas del *blog* que se han persistido en la base de datos. Escribirla en *PHP* simple es rápido y sucio: - -.. code-block:: html+php - - - - - - - List of Posts - - -

List of Posts

- - - - - - -Eso es fácil de escribir, se ejecuta rápido, y, cuando tu aplicación crece, imposible de mantener. Hay varios problemas que es necesario abordar: - -* **No hay comprobación de errores**: ¿Qué sucede si falla la conexión a la base de datos? - -* **Deficiente organización**: Si la aplicación crece, este único archivo cada vez será más difícil de mantener, hasta que finalmente sea imposible. ¿Dónde se debe colocar el código para manejar un formulario enviado? ¿Cómo se pueden validar los datos? ¿Dónde debe ir el código para enviar mensajes de correo electrónico? - -* **Es difícil reutilizar el código**: Ya que todo está en un archivo, no hay manera de volver a utilizar alguna parte de la aplicación en otras «páginas» del *blog*. - -.. note:: - - Otro problema no mencionado aquí es el hecho de que la base de datos está vinculada a *MySQL*. Aunque no se ha tratado aquí, *Symfony2* integra `Doctrine`_ plenamente, una biblioteca dedicada a la abstracción y asignación de bases de datos. - -Trabajaremos en la solución de estos y muchos problemas más. - -Aislando la presentación -~~~~~~~~~~~~~~~~~~~~~~~~ - -El código inmediatamente se puede beneficiar de la separación entre la «lógica» de la aplicación y el código que prepara la «presentación» *HTML*: - -.. code-block:: html+php - - - - - List of Posts - - -

List of Posts

- - - - -Por convención, el archivo que contiene toda la lógica de la aplicación ---:file:`index.php`--- se conoce como *«controlador»*. El término :term:`controlador` es una palabra que se escucha mucho, independientemente del lenguaje o plataforma que utilices. Simplemente se refiere a la zona de *tu código* que procesa la entrada del usuario y prepara la respuesta. - -En este caso, el controlador prepara los datos de la base de datos y, luego los incluye en una plantilla para presentarlos. Con el controlador aislado, fácilmente podríamos cambiar *sólo* el archivo de plantilla si es necesario procesar las entradas del *blog* en algún otro formato (por ejemplo, ``lista.json.php`` para el formato *JSON*). - -Aislando la lógica de la aplicación (el dominio) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Hasta ahora, la aplicación sólo contiene una página. Pero ¿qué pasa si una segunda página necesita utilizar la misma conexión a la base de datos, e incluso el mismo arreglo de entradas del *blog*? Reconstruye el código para que el comportamiento de las funciones básicas de acceso a datos de la aplicación esté aislado en un nuevo archivo llamado :file:`model.php`: - -.. code-block:: html+php - - - - - - <?php echo $title ?> - - - - - - -La plantilla (``templates/list.php``) ahora se puede simplificar para «extender» el diseño: - -.. code-block:: html+php - - - - -

List of Posts

- - - - - -Ahora se introdujo una metodología que te permite reutilizar el diseño. Desafortunadamente, para lograrlo, estás obligado a utilizar en la plantilla algunas desagradables funciones de *PHP* (``ob_start()``, ``ob_get_clean()``). *Symfony2* utiliza un componente ``Templating`` que te permite realizar esto limpia y fácilmente. En breve lo verás en acción. - -Agregando una página ``«show»`` al *blog* ------------------------------------------ - -La página ``«list»`` del *blog* se ha rediseñado para que el código esté mejor organizado y sea reutilizable. Para probarlo, añade una página ``«show»`` al *blog*, que muestre una entrada individual del *blog* identificada por un parámetro de consulta ``id``. - -Para empezar, crea una nueva función en el archivo :file:`model.php` que recupere un resultado individual del *blog* basándose en un identificador dado:: - - // model.php - function get_post_by_id($id) - { - $link = open_database_connection(); - - $id = intval($id); - $query = 'SELECT date, title, body FROM post WHERE id = '.$id; - $result = mysql_query($query); - $row = mysql_fetch_assoc($result); - - close_database_connection($link); - - return $row; - } - -A continuación, crea un nuevo archivo llamado :file:`show.php` ---el controlador para esta nueva página: - -.. code-block:: html+php - - - - -

- -
-
- -
- - - - -Ahora, es muy fácil crear la segunda página y sin duplicar código. Sin embargo, esta página introduce problemas aún más perniciosos que una plataforma puede resolver por ti. Por ejemplo, un parámetro ``id`` ilegal u omitido en la consulta hará que la página se bloquee. Sería mejor si esto reprodujera una página 404, pero sin embargo, en realidad esto no se puede hacer fácilmente. Peor aún, si olvidaras desinfectar el parámetro ``id`` por medio de la función ``intval()``, tu base de datos estaría en riesgo de un ataque de inyección *SQL*. - -Otro importante problema es que cada archivo de controlador individual debe incluir al archivo :file:`model.php`. ¿Qué pasaría si cada archivo de controlador de repente tuviera que incluir un archivo adicional o realizar alguna tarea global (por ejemplo, reforzar la seguridad)? -Tal como está ahora, el código tendría que incluir todos los archivos de los controladores. -Si olvidas incluir algo en un solo archivo, esperemos que no sea alguno relacionado con la seguridad... - -El «controlador frontal» al rescate ------------------------------------ - -Una mucho mejor solución es usar un :term:`controlador frontal`: un único archivo *PHP* a través del cual se procesen *todas* las peticiones. Con un controlador frontal, la *URI* de la aplicación cambia un poco, pero se vuelve más flexible: - -.. code-block:: text - - Sin controlador frontal - /index.php => (ejecuta index.php) la página lista de mensajes. - /show.php => (ejecuta show.php) la página muestra un mensaje particular. - - Con index.php como controlador frontal - /index.php => (ejecuta index.php) la página lista de mensajes. - /index.php/show => (ejecuta index.php) la página muestra un mensaje particular. - -.. tip:: - Puedes quitar la porción :file:`index.php` de la *URI* si utilizas las reglas de reescritura de *Apache* (o equivalentes). En ese caso, la *URI* resultante de la página show del *blog* simplemente sería ``/show``. - -Cuando se usa un controlador frontal, un solo archivo *PHP* (:file:`index.php` en este caso) procesa todas las peticiones. Para la página ``show`` del *blog*, ``/index.php/show`` realmente ejecuta el archivo :file:`index.php`, que ahora es el responsable de dirigir internamente las peticiones basándose en la *URI* completa. Como puedes ver, un controlador frontal es una herramienta muy poderosa. - -Creando el controlador frontal -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Estás a punto de dar un **gran** paso en la aplicación. Con un archivo manejando todas las peticiones, puedes centralizar cosas tales como el manejo de la seguridad, la carga de configuración y enrutado. En esta aplicación, :file:`index.php` ahora debe ser lo suficientemente inteligente como para reproducir la lista de entradas del *blog* *o* mostrar la página de una entrada particular basándose en la *URI* solicitada: - -.. code-block:: html+php - -

Page Not Found

'; - } - -Por organización, ambos controladores (antes :file:`index.php` y :file:`show.php`) son funciones *PHP* y cada una se ha movido a un archivo separado, :file:`controllers.php`: - -.. code-block:: php - - function list_action() - { - $posts = get_all_posts(); - require 'templates/list.php'; - } - - function show_action($id) - { - $post = get_post_by_id($id); - require 'templates/show.php'; - } - -Como controlador frontal, :file:`index.php` ha asumido un papel completamente nuevo, el cual incluye la carga de las bibliotecas del núcleo y encaminar la aplicación para invocar a uno de los dos controladores (las funciones ``list_action()`` y ``show_action()``). En realidad, el controlador frontal está empezando a verse y actuar como el mecanismo *Symfony2* para la manipulación y enrutado de peticiones. - -.. tip:: - - Otra ventaja del controlador frontal es la flexibilidad de las *URL*. Ten en cuenta que la *URL* a la página ``show`` del *blog* se puede cambiar de ``/show`` a ``/read`` cambiando el código solamente en una única ubicación. Antes, era necesario cambiar todo un archivo para cambiar el nombre. En *Symfony2*, incluso las *URL* son más flexibles. - -Por ahora, la aplicación ha evolucionado de un único archivo *PHP*, a una estructura organizada y permite la reutilización de código. Debes estar feliz, pero aún lejos de estar satisfecho. Por ejemplo, el sistema de «enrutado» es voluble, y no reconoce que la página ``list`` (``/index.php``) también debe ser accesible a través de ``/`` (si has agregado las reglas de reescritura de *Apache*). Además, en lugar de desarrollar el *blog*, una gran cantidad del tiempo se ha gastado trabajando en la «arquitectura» del código (por ejemplo, el enrutado, invocando controladores, plantillas, etc.) Se tendrá que gastar más tiempo para manejar el envío de formularios, validación de entradas, llevar la bitácora de sucesos y la seguridad. -¿Por qué tienes que reinventar soluciones a todos estos problemas rutinarios? - -Añadiendo un toque *Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Symfony2* al rescate. Antes de utilizar *Symfony2* realmente, necesitas descargarlo. Esto se puede hacer utilizando ``Composer``, el cual cuida de descargar la versión -correcta con todas sus dependencias y proporciona un cargador automático. Un cargador automático es una herramienta que permite empezar a utilizar clases *PHP* sin incluir explícitamente el archivo que contiene la clase. - -En tu directorio raíz crea un archivo de texto llamado :file:`composer.json` con el siguiente contenido: - -.. code-block:: json - - { - "require": { - "symfony/symfony": "2.2.*" - }, - "autoload": { - "files": ["model.php","controllers.php"] - } - } - -Luego, `descarga Composer`_ y entonces ejecuta la siguiente orden, la cual descargará *Symfony* a un directorio :file:`vendor/`: - -.. code-block:: bash - - $ php composer.phar install - -Junto con la descarga de tus dependencias, ``Composer`` genera un archivo :file:`vendor/autoload.php`, el cual cuida de cargar automáticamente todos los archivos en la platafomra *Symfony* así como los archivos mencionados en la sección ``autoload`` de tu :file:`composer.json`. - -La esencia de la filosofía *Symfony* es la idea de que el trabajo principal de una aplicación es interpretar cada petición y devolver una respuesta. Con este fin, *Symfony2* proporciona ambas clases :class:`Symfony\\Component\\HttpFoundation\\Request` y :class:`Symfony\\Component\\HttpFoundation\\Response`. Estas clases son representaciones orientadas a objetos de la petición *HTTP* que se está procesando y la respuesta *HTTP* que devolverá. Úsalas para mejorar el *blog*: - -.. code-block:: html+php - - getPathInfo(); - if ('/' == $uri) { - $response = list_action(); - } elseif ('/show' == $uri && $request->query->has('id')) { - $response = show_action($request->query->get('id')); - } else { - $html = '

Page Not Found

'; - $response = new Response($html, 404); - } - - // difunde las cabeceras y envía la respuesta - $response->send(); - -Los controladores son responsables de devolver un objeto ``Respuesta``. -Para facilitarnos esto, puedes agregar una nueva función ``render_template()``, la cual, por cierto, actúa un poco como el motor de plantillas de *Symfony2*: - -.. code-block:: php - - // controllers.php - use Symfony\Component\HttpFoundation\Response; - - function list_action() - { - $posts = get_all_posts(); - $html = render_template('templates/list.php', array('posts' => $posts)); - - return new Response($html); - } - - function show_action($id) - { - $post = get_post_by_id($id); - $html = render_template('templates/show.php', array('post' => $post)); - - return new Response($html); - } - - // función ayudante para reproducir plantillas - function render_template($path, array $args) - { - extract($args); - ob_start(); - require $path; - $html = ob_get_clean(); - - return $html; - } - -Al reunir una pequeña parte de *Symfony2*, la aplicación es más flexible y fiable. La ``Petición`` proporciona una manera confiable para acceder a información de la petición *HTTP*. Especialmente, el método ``getPathInfo()`` devuelve una *URI* limpia (siempre devolviendo ``/show`` y nunca ``/index.php/show``). -Por lo tanto, incluso si el usuario va a ``/index.php/show``, la aplicación es lo suficientemente inteligente para encaminar la petición hacia ``show_action()``. - -El objeto ``Respuesta`` proporciona flexibilidad al construir la respuesta *HTTP*, permitiendo que las cabeceras *HTTP* y el contenido se agreguen a través de una interfaz orientada a objetos. -Y aunque las respuestas en esta aplicación son simples, esta flexibilidad pagará dividendos en cuanto tu aplicación crezca. - -Aplicación de ejemplo en *Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El *blog* ha *avanzado*, pero todavía contiene una gran cantidad de código para una aplicación tan simple. De paso, también inventamos un sencillo sistema de enrutado y un método utilizando ``ob_start()`` y ``ob_get_clean()`` para procesar plantillas. Si por alguna razón, necesitas continuar la construcción de esta «plataforma» desde cero, por lo menos puedes usar los componentes independientes `Routing`_ y `Templating`_ de *Symfony*, que resuelven estos problemas. - -En lugar de volver a solucionar problemas comunes, puedes dejar que *Symfony2* se preocupe de ellos por ti. Aquí está la misma aplicación de ejemplo, ahora construida en *Symfony2*:: - - // src/Acme/BlogBundle/Controller/BlogController.php - namespace Acme\BlogBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class BlogController extends Controller - { - public function listAction() - { - $posts = $this->get('doctrine')->getManager() - ->createQuery('SELECT p FROM AcmeBlogBundle:Post p') - ->execute(); - - return $this->render( - 'AcmeBlogBundle:Blog:list.html.php', - array('posts' => $posts) - ); - } - - public function showAction($id) - { - $post = $this->get('doctrine') - ->getManager() - ->getRepository('AcmeBlogBundle:Post') - ->find($id) - ; - - if (!$post) { - // provoca que se muestre la página de error 404 - throw $this->createNotFoundException(); - } - - return $this->render( - 'AcmeBlogBundle:Blog:show.html.php', - array('post' => $post) - ); - } - } - -Los dos controladores siguen siendo ligeros. Cada uno utiliza la :doc:`biblioteca ORM de Doctrine ` para recuperar objetos de la base de datos y el componente ``Templating`` para reproducir una plantilla y devolver un objeto ``Respuesta``. La plantilla ``list`` ahora es un poco más simple: - -.. code-block:: html+php - - - extend('::layout.html.php') ?> - - set('title', 'List of Posts') ?> - -

List of Posts

- - -El diseño es casi idéntico: - -.. code-block:: html+php - - - - - - <?php echo $view['slots']->output( - 'title', - 'Default title' - ) ?> - - - output('_content') ?> - - - -.. note:: - - Te vamos a dejar como ejercicio la plantilla ``show``, porque debería ser trivial crearla basándote en la plantilla ``list``. - -Cuando arranca el motor *Symfony2* (conocido como el ``núcleo``), necesita un mapa para saber qué controladores ejecutar basándose en la información solicitada. -Un mapa de configuración de enrutado proporciona esta información en formato legible: - -.. code-block:: yaml - - # app/config/routing.yml - blog_list: - path: /blog - defaults: { _controller: AcmeBlogBundle:Blog:list } - - blog_show: - path: /blog/show/{id} - defaults: { _controller: AcmeBlogBundle:Blog:show } - -Ahora que *Symfony2* se encarga de todas las tareas rutinarias, el controlador frontal es muy simple. Y debido a que hace tan poco, nunca tienes que volver a tocarlo una vez creado (y si utilizas una distribución *Symfony2*, ¡ni siquiera tendrás que crearlo!):: - - // web/app.php - require_once __DIR__.'/../app/bootstrap.php'; - require_once __DIR__.'/../app/AppKernel.php'; - - use Symfony\Component\HttpFoundation\Request; - - $kernel = new AppKernel('prod', false); - $kernel->handle(Request::createFromGlobals())->send(); - -El único trabajo del controlador frontal es iniciar el motor de *Symfony2* (``núcleo``) y pasarle un objeto ``Petición`` para que lo manipule. El núcleo de *Symfony2* entonces utiliza el mapa de enrutado para determinar qué controlador invocar. Al igual que antes, el método controlador es el responsable de devolver el objeto ``Respuesta`` final. -Realmente no hay mucho más sobre él. - -Para conseguir una representación visual de cómo maneja *Symfony2* cada petición, consulta el :ref:`diagrama de flujo de la petición `. - -Qué más ofrece *Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~ - -En los siguientes capítulos, aprenderás más acerca de cómo funciona cada pieza de *Symfony* y la organización recomendada de un proyecto. Por ahora, vamos a ver cómo, migrar el *blog* de *PHP* simple a *Symfony2* nos ha mejorado la vida: - -* Tu aplicación cuenta con **código claro y organizado consistentemente** (aunque *Symfony* no te obliga a ello). Este promueve la **reutilización** y permite a los nuevos desarrolladores ser productivos en el proyecto con mayor rapidez. - -* 100% del código que escribes es para *tu* aplicación. **No necesitas desarrollar o mantener servicios públicos de bajo nivel** tal como la :ref:`carga automática ` de clases, el :doc:`enrutado ` o la reproducción de :doc:`controladores `; - -* *Symfony2* te proporciona **acceso a herramientas de código abierto** tal como *Doctrine*, plantillas, seguridad, formularios, validación y componentes de traducción (por nombrar algunos); - -* La aplicación ahora disfruta de **direcciones URL totalmente flexibles** gracias al componente ``Routing``; - -* La arquitectura centrada en *HTTP* de *Symfony2* te da acceso a poderosas herramientas, tal como la **memoria caché HTTP** impulsadas por la **caché HTTP interna de Symfony2** o herramientas más poderosas, tales como `Varnish`_. Esto se trata posteriormente en el capítulo «:doc:`todo sobre caché `». - -Y lo mejor de todo, utilizando *Symfony2*, ¡ahora tienes acceso a un conjunto de herramientas de **código abierto de alta calidad desarrolladas por la comunidad Symfony2**! -Puedes encontrar una buena colección de herramientas comunitarias de *Symfony2* en `KnpBundles.com`_. - -Mejores plantillas ------------------- - -Si decides utilizarlo, *Symfony2* de serie viene con un motor de plantillas llamado `Twig`_ el cual hace que las plantillas se escriban más rápido y sean más fáciles de leer. -Esto significa que, incluso, ¡la aplicación de ejemplo podría contener mucho menos código! Toma por ejemplo, la plantilla ``list`` escrita en *Twig*: - -.. code-block:: html+jinja - - {# src/Acme/BlogBundle/Resources/views/Blog/list.html.twig #} - {% extends "::layout.html.twig" %} - - {% block title %}List of Posts{% endblock %} - - {% block body %} -

List of Posts

- - {% endblock %} - -También es fácil escribir la plantilla ``layout.html.twig`` correspondiente: - -.. code-block:: html+jinja - - {# app/Resources/views/layout.html.twig #} - - - - {% block title %}Default title{% endblock %} - - - {% block body %}{% endblock %} - - - -*Twig* es compatible con *Symfony2*. Y si bien, las plantillas *PHP* siempre contarán con el apoyo de *Symfony2*, vamos a seguir explicando muchas de las ventajas de *Twig*. Para más información, consulta el capítulo :doc:`Plantillas `. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/templating/PHP` -* :doc:`/cookbook/controller/service` - -.. _`Doctrine`: http://www.doctrine-project.org -.. _`descarga Composer`: http://getcomposer.org/download/ -.. _`Routing`: https://github.com/symfony/Routing -.. _`Templating`: https://github.com/symfony/Templating -.. _`KnpBundles.com`: http://knpbundles.com/ -.. _`Twig`: http://gitnacho.github.com/symfony-docs-es/twig/index.html -.. _`Varnish`: https://www.varnish-cache.org/ -.. _`PHPUnit`: http://www.phpunit.de diff --git a/_sources/book/http_cache.txt b/_sources/book/http_cache.txt deleted file mode 100644 index 386a01b..0000000 --- a/_sources/book/http_cache.txt +++ /dev/null @@ -1,788 +0,0 @@ -.. index:: - single: Caché - -Caché *HTTP* -============ - -La naturaleza de las aplicaciones *web* ricas significa que son dinámicas. No importa qué tan eficiente sea tu aplicación, cada petición siempre contendrá más sobrecarga que simplemente servir un archivo estático. - -Y para la mayoría de las aplicaciones *Web*, está bien. *Symfony2* es tan rápido como el rayo, a menos que estés haciendo una muy complicada aplicación, cada petición se responderá rápidamente sin poner demasiada tensión a tu servidor. - -Pero cuando tu sitio crezca, la sobrecarga general se puede convertir en un problema. El procesamiento que se realiza normalmente en cada petición se debe hacer sólo una vez. Este exactamente es el objetivo que tiene que consumar la memoria caché. - -La memoria caché en hombros de gigantes ---------------------------------------- - -La manera más efectiva para mejorar el rendimiento de una aplicación es memorizar en caché la salida completa de una página y luego eludir por completo la aplicación en cada petición posterior. Por supuesto, esto no siempre es posible para los sitios web altamente dinámicos, ¿o no? En este capítulo, te mostraremos cómo funciona el sistema de caché *Symfony2* y por qué este es el mejor enfoque posible. - -El sistema de cache *Symfony2* es diferente porque se basa en la simplicidad y el poder de la caché *HTTP* tal como está definida en la :term:`especificación HTTP`. -En lugar de reinventar una metodología de memoria caché, *Symfony2* adopta la norma que define la comunicación básica en la Web. Una vez que comprendas los principios fundamentales de los modelos de caducidad y validación de la memoria caché *HTTP*, estarás listo para dominar el sistema de caché *Symfony2*. - -Para efectos de aprender cómo guardar en caché con *Symfony2*, abordaremos el tema en cuatro pasos: - -#. Una :ref:`pasarela de caché `, o delegado inverso (``«proxy»``), es una capa independiente situada frente a tu aplicación. La caché del delegado inverso responde a medida que son devueltas desde tu aplicación y contesta a peticiones con respuestas de la caché antes de que lleguen a tu aplicación. *Symfony2* proporciona su propio delegado inverso, pero puedes utilizar cualquier delegado inverso. - -#. Las cabeceras de :ref:`cache HTTP ` se utilizan para comunicarse con la pasarela de caché y cualquier otra caché entre tu aplicación y el cliente. *Symfony2* proporciona parámetros predeterminados y una potente interfaz para interactuar con las cabeceras de caché. - -#. La :ref:`caducidad y validación ` *HTTP* son los dos modelos utilizados para determinar si el contenido memorizado en caché es *fresco* (se puede reutilizar de la memoria caché) u *obsoleto* (lo debe regenerar la aplicación). - -#. :ref:`Inclusión del borde lateral ` (*Edge Side Includes -ESI*) permite que la caché *HTTP* utilice fragmentos de la página en caché (incluso fragmentos anidados) independientemente. - Con *ESI*, incluso puedes guardar en caché una página entera durante 60 minutos, pero una barra lateral integrada sólo por 5 minutos. - -Dado que la memoria caché *HTTP* no es exclusiva de *Symfony*, ya existen muchos artículos sobre el tema. Si eres novato en el tema de la memoria caché *HTTP*, te *recomendamos* el artículo de Ryan Tomayko `Things Caches Do`_. Otro recurso que aborda el tema es la `Guía de caché`_ de Mark Nottingham. - -.. index:: - single: Caché; Delegado - single: Caché; Delegado inverso - single: Caché; Pasarela - -.. _gateway-caches: - -Memoria caché con pasarela de caché ------------------------------------ - -Cuándo memorizar caché con *HTTP*, la *caché* está separada de tu aplicación por completo y se sitúa entre tu aplicación y el cliente haciendo la petición. - -El trabajo de la caché es aceptar las peticiones del cliente y pasarlas de nuevo a tu aplicación. La memoria caché también recibirá las respuestas devueltas por tu aplicación y las remitirá al cliente. La caché es el «geniecillo» de la comunicación petición-respuesta entre el cliente y tu aplicación. - -De paso, la memoria caché almacena cada respuesta que se considere «almacenable en caché» (consulta :ref:`http-cache-introduction`). Si de nuevo se solicita el mismo recurso, la memoria caché envía la respuesta memorizada en caché al cliente, eludiendo tu aplicación por completo. - -Este tipo de caché se conoce como pasarela de caché *HTTP* y existen muchas como `Varnish`_, `Squid en modo delegado inverso`_ y el delegado inverso de *Symfony2*. - -.. index:: - single: Caché; Tipos de - -Tipos de Caché -~~~~~~~~~~~~~~ - -Sin embargo, una pasarela de caché no es el único tipo de caché. De hecho, las cabeceras de caché *HTTP* enviadas por tu aplicación son consumidas e interpretadas por un máximo de tres diferentes tipos de caché: - -* *Caché de navegadores*: Cada navegador viene con su propia caché local, lo cual es realmente útil para cuando pulsas «atrás» o para imágenes y otros activos. - La caché del navegador es una caché *privada*, los recursos memorizados en caché no se comparten con nadie más; - -* *Delegados de caché*: Un delegado de caché *compartida* es aquel en el cual muchas personas pueden estar detrás de uno solo. Por lo general instalado por las grandes corporaciones y proveedores de Internet para reducir latencia y tráfico de red; - -* *Pasarela de caché*: Al igual que un delegado, también es una caché *compartida* pero en el lado del servidor. Instalada por los administradores de red, esta tiene sitios web más escalables, confiables y prácticos. - -.. tip:: - - Las pasarelas de caché a veces también se conocen como delegados inversos de caché, cachés alquiladas o incluso aceleradores *HTTP*. - -.. note:: - - La importancia de la caché *privada* frente a la *compartida* será más evidente a medida que hablemos de las respuestas en la memoria caché con contenido que es específico para un solo usuario (por ejemplo, información de cuenta). - -Cada respuesta de tu aplicación probablemente vaya a través de uno o los dos primeros tipos de caché. Estas cachés están fuera de tu control, pero siguen las instrucciones de la caché *HTTP* establecidas en la respuesta. - -.. index:: - single: Caché; Delegado inverso de Symfony2 - -.. _`symfony-gateway-cache`: - -Delegado inverso de *Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Symfony2* viene con un delegado inverso de caché (también conocido como pasarela de caché) escrito en *PHP*. Que al activarla, inmediatamente puede memorizar en caché respuestas de tu aplicación. La instalación es muy fácil. Cada nueva aplicación *Symfony2* viene con una caché preconfigurada en el núcleo (``AppCache``) que envuelve al predeterminado (``AppKernel``). El núcleo de la memoria caché *es* el delegado inverso. - -Para habilitar la memoria caché, modifica el código de un controlador frontal para utilizar la caché del núcleo:: - - // web/app.php - require_once __DIR__.'/../app/bootstrap.php.cache'; - require_once __DIR__.'/../app/AppKernel.php'; - require_once __DIR__.'/../app/AppCache.php'; - - use Symfony\Component\HttpFoundation\Request; - - $kernel = new AppKernel('prod', false); - $kernel->loadClassCache(); - // envuelve el AppKernel predeterminado con un AppCache - $kernel = new AppCache($kernel); - $request = Request::createFromGlobals(); - $response = $kernel->handle($request); - $response->send(); - $kernel->terminate($request, $response); - -La memoria caché del núcleo actúa de inmediato como un delegado inverso ---memorizando en caché las respuestas de tu aplicación y devolviéndolas al cliente---. - -.. tip:: - - La caché del núcleo tiene un método especial `getLog()`, el cual devuelve una cadena que representa lo que sucedió en la capa de la caché. En el entorno de desarrollo, se usa para depurar y validar la estrategia de caché:: - - error_log($kernel->getLog()); - -El objeto ``AppCache`` tiene una sensible configuración predeterminada, pero la puedes afinar por medio de un conjunto de opciones que puedes configurar sustituyendo el método :method:`Symfony\\Bundle\\FrameworkBundle\\HttpCache\\HttpCache::getOptions`:: - - // app/AppCache.php - use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache; - - class AppCache extends HttpCache - { - protected function getOptions() - { - return array( - 'debug' => false, - 'default_ttl' => 0, - 'private_headers' => array('Authorization', 'Cookie'), - 'allow_reload' => false, - 'allow_revalidate' => false, - 'stale_while_revalidate' => 2, - 'stale_if_error' => 60, - ); - } - } - -.. tip:: - - A menos que la sustituyas en ``getOptions()``, la opción ``debug`` se establecerá automáticamente al valor de depuración del ``AppKernel`` envuelto. - -Aquí está una lista de las principales opciones: - -* ``default_ttl``: El número de segundos que una entrada de caché se debe considerar nueva cuando no hay información fresca proporcionada explícitamente en una respuesta. Las cabeceras explícitas ``Cache-Control`` o ``Expires`` sustituyen este valor (predeterminado: ``0``); - -* ``private_headers``: Conjunto de cabeceras de la petición que desencadenan el comportamiento ``Cache-Control`` «privado» en las respuestas en que no son clasificadas explícitamente como ``públicas`` o ``privadas`` vía una directiva ``Cache-Control``. - (default: ``Authorization`` y :dfn:`cookie`); - -* ``allow_reload``: Especifica si el cliente puede forzar una recarga desde caché incluyendo una directiva ``Cache-Control`` «``no-cache``» en la petición. Selecciona ``true`` para cumplir con la RFC 2616 (por omisión: ``false``); - -* ``allow_revalidate``: Especifica si el cliente puede forzar una revalidación de caché incluyendo una directiva ``Cache-Control`` ``max-age = 0`` en la petición. Ponla en ``true`` para cumplir con la RFC 2616 (por omisión: false); - -* ``stale_while_revalidate``: Especifica el número de segundos predeterminado (la granularidad es el segundo puesto que la precisión de respuesta *TTL* es un segundo) durante el cual la memoria caché puede regresar inmediatamente una respuesta obsoleta mientras que revalida en segundo plano (por omisión: ``2``); este ajuste lo reemplaza ``stale-while-revalidate`` de la extensión *HTTP* ``Cache-Control`` (consulta la *RFC 5.861*); - -* ``stale_if_error``: Especifica el número de segundos predeterminado (la granularidad es el segundo) durante el cual la caché puede servir una respuesta obsoleta cuando se detecta un error (por omisión: ``60``). Este valor lo reemplaza ``stale-if-error`` de la extensión *HTTP* ``Cache-Control`` (consulta la RFC 5861). - -Si ``debug`` es ``true``, *Symfony2* automáticamente agrega una cabecera ``X-Symfony-Cache`` a la respuesta que contiene información útil acerca de aciertos y errores de caché. - -.. sidebar:: Cambiando de un delegado inverso a otro - - El delegado inverso de *Symfony2* es una gran herramienta a utilizar en el desarrollo de tu sitio web o al desplegar tu web en un servidor compartido donde no puedes instalar nada más allá que código *PHP*. Pero está escrito en *PHP*, y por lo tanto no puede ser tan rápido como un delegado escrito en *C*. Es por eso que recomendamos ---de ser posible--- usar *Varnish* o *Squid* en tus servidores de producción. La buena nueva es que el cambio de un servidor delegado a otro es fácil y transparente, sin modificar el código necesario en tu aplicación. Comienza fácilmente con el delegado inverso de *Symfony2* y actualiza a *Varnish* cuando aumente el tráfico. - - Para más información sobre el uso de *Varnish* con *Symfony2*, consulta el capítulo :doc:`Cómo usar Varnish ` en el recetario. - -.. note:: - - El rendimiento del delegado inverso de *Symfony2* es independiente de la complejidad de tu aplicación. Eso es porque el núcleo de tu aplicación sólo se inicia cuando la petición se debe remitir a ella. - -.. index:: - single: Caché; HTTP - -.. _http-cache-introduction: - -Introducción a la memoria caché *HTTP* --------------------------------------- - -Para aprovechar las ventajas de las capas de memoria caché disponibles, tu aplicación se debe poder comunicar con las respuestas que son memorizables y las reglas que rigen cuándo y cómo la caché será obsoleta. Esto se hace ajustando las cabeceras de caché *HTTP* en la respuesta. - -.. tip:: - - Ten en cuenta que *HTTP* no es más que el lenguaje (un lenguaje de texto simple) que los clientes web (navegadores, por ejemplo) y los servidores web utilizan para comunicarse entre sí. Cuando hablamos de la memoria caché *HTTP*, estamos hablando de la parte de ese lenguaje que permite a los clientes y servidores intercambiar información relacionada con la memoria caché. - -*HTTP* especifica cuatro cabeceras de caché para respuestas en las que estamos interesados aquí: - -* ``Cache-Control`` -* ``Expires`` -* ``ETag`` -* ``Last-Modified`` - -La cabecera más importante y versátil es la cabecera ``Cache-Control``, la cual en realidad es una colección de variada información de caché. - -.. note:: - - Cada una de las cabeceras se explica en detalle en la sección :ref:`http-expiration-validation`. - -.. index:: - single: Caché; Cabecera Cache-Control - single: Cabeceras HTTP; Cache-Control - -La cabecera ``Cache-Control`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La cabecera ``Cache-Control`` es la única que no contiene una, sino varias piezas de información sobre la memoria caché de una respuesta. Cada pieza de información está separada por una coma:: - - Cache-Control: private, max-age=0, must-revalidate - - Cache-Control: max-age=3600, must-revalidate - -*Symfony* proporciona una abstracción de la cabecera ``Cache-Control`` para hacer más manejable su creación:: - - // ... - - use Symfony\Component\HttpFoundation\Response; - - $response = new Response(); - - // marca la respuesta como pública o privada - $response->setPublic(); - $response->setPrivate(); - - // fija la edad máxima de privado o compartido - $response->setMaxAge(600); - $response->setSharedMaxAge(600); - - // fija una directiva Cache-Control personalizada - $response->headers->addCacheControlDirective('must-revalidate', true); - -Respuestas públicas frente a privadas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ambas, la pasarela de caché y el delegado de caché, son considerados como cachés «compartidas» debido a que el contenido memorizado en caché se comparte con más de un usuario. Si cada vez equivocadamente una memoria caché compartida almacena una respuesta específica al usuario, posteriormente la puede devolver a cualquier cantidad de usuarios diferentes. ¡Imagina si la información de tu cuenta se memoriza en caché y luego la regresa a todos los usuarios posteriores que soliciten la página de su cuenta! - -Para manejar esta situación, cada respuesta se puede fijar para que sea pública o privada: - -* *public*: Indica que la respuesta se puede memorizar en caché por ambas cachés privadas y compartidas; - -* *private*: Indica que la totalidad o parte del mensaje de la respuesta es para un solo usuario y no se debe memorizar en caché en una caché compartida. - -Por omisión, *Symfony* conservadoramente fija cada respuesta para que sea privada. Para aprovechar las ventajas de las cachés compartidas (como el delegado inverso de *Symfony2*), explícitamente deberás fijar la respuesta como pública. - -.. index:: - single: Caché; Métodos seguros - -Métodos seguros -~~~~~~~~~~~~~~~ - -La memoria caché *HTTP* sólo funciona para métodos *HTTP* «seguros» (como *GET* y *HEAD*). Estar seguro significa que nunca cambia de estado la aplicación en el servidor al servir la petición (por supuesto puedes registrar información, datos de la caché, etc.) -Esto tiene dos consecuencias muy razonables: - -* *Nunca* debes cambiar el estado de tu aplicación al responder a una petición *GET* o *HEAD*. Incluso si no utilizas una pasarela caché, la presencia del delegado de caché significa que alguna petición *GET* o *HEAD* puede o no llegar a tu servidor; - -* No esperes que haya métodos *PUT*, *POST* o *DELETE* en caché. Estos métodos están diseñados para utilizarse al mutar el estado de tu aplicación (por ejemplo, borrar una entrada de *blog*). La memoria caché debe impedir que determinadas peticiones toquen y muten tu aplicación. - -Reglas de caché y valores predeterminados -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*HTTP* 1.1 por omisión, permite memorizar en caché cualquier cosa a menos que haya una cabecera ``Cache-Control`` explícita. En la práctica, la mayoría de las cachés no hacen nada cuando las peticiones tienen una ``galleta``, una cabecera de autorización, utilizan un método no seguro (es decir, *PUT*, *POST*, *DELETE*), o cuando las respuestas tienen código de redirección de estado. - -*Symfony2* automáticamente establece una sensible y conservadora cabecera ``Cache-Control`` cuando esta no está definida por el desarrollador, siguiendo estas reglas: - -* Si no has definido cabecera caché (``Cache-Control``, ``Expires``, ``ETag`` o ``Last-Modified``), ``Cache-Control`` es establecida en ``no-cache``, lo cual significa que la respuesta no se guarda en caché; - -* Si ``Cache-Control`` está vacía (pero una de las otras cabeceras de caché está presente), su valor se establece en ``private, must-revalidate``; - -* Pero si por lo menos una directiva ``Cache-Control`` está establecida, y no se han añadido directivas ``public`` o ``private`` de forma explícita, *Symfony2* agrega la directiva ``private`` automáticamente (excepto cuando ``s-maxage`` está establecida). - -.. _http-expiration-validation: - -Caducidad y validación *HTTP* ------------------------------ - -La especificación *HTTP* define dos modelos de memoria caché: - -* Con el `modelo de caducidad`_, sólo tienes que especificar el tiempo en que la respuesta se debe considerar «fresca» incluyendo una cabecera ``Cache-Control`` y/o una ``Expires``. Las cachés que entienden de expiración no harán la misma petición hasta que la versión en caché alcance el límite de caducidad y se vuelva «obsoleta»; - -* Cuando las páginas realmente son dinámicas (es decir, su representación cambia con mucha frecuencia), a menudo es necesario el `modelo de validación`_. Con este modelo, la memoria caché memoriza la respuesta, pero, pregunta al servidor en cada petición si la respuesta memorizada sigue siendo válida. La aplicación utiliza un identificador de respuesta único (la cabecera ``Etag``) y/o una marca de tiempo (la cabecera ``Last-Modified``) para comprobar si la página ha cambiado desde su memorización en caché. - -El objetivo de ambos modelos es nunca generar la misma respuesta en dos ocasiones dependiendo de una caché para almacenar y devolver respuestas «``frescas``». - -.. sidebar:: Leyendo la especificación *HTTP* - - La especificación *HTTP* define un sencillo pero potente lenguaje con el cual clientes y servidores se pueden comunicar. Como desarrollador *web*, el modelo petición-respuesta de la especificación domina tu trabajo. Lamentablemente, el documento de la especificación real ---`RFC 2616`_--- puede ser difícil de leer. - - Hay un esfuerzo en curso (`HTTP Bis`_) para reescribir la RFC 2616. Este no describe una nueva versión de *HTTP*, sino sobre todo aclara la especificación *HTTP* original. La organización también se ha mejorado ya que la especificación se divide en siete partes; todo lo relacionado con la caché *HTTP* se puede encontrar en dos partes dedicadas (`P4 - Petición condicional`_ y `P6 - Caché: Navegador y caché intermedia`_). - - Como desarrollador web, estás invitado a leer la especificación. Su claridad y poder ---incluso más de diez años después de su creación--- tiene un valor incalculable. No te desanimes por la apariencia de la especificación ---su contenido es mucho más bello que la cubierta---. - -.. index:: - single: Caché; Caducidad HTTP - -Caducidad -~~~~~~~~~ - -El modelo de caducidad es el más eficiente y simple de los dos modelos de memoria caché y se debe utilizar siempre que sea posible. Cuando una respuesta se memoriza en caché con una caducidad, la caché memorizará la respuesta y la enviará directamente sin tocar a la aplicación hasta que esta caduque. - -El modelo de caducidad se puede lograr usando una de dos, casi idénticas, cabeceras *HTTP*: ``Expires`` o ``Cache-Control``. - -.. index:: - single: Caché; Cabecera Expires - single: Cabeceras HTTP; Expires - -Caducidad con la cabecera ``Expires`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -De acuerdo con la especificación *HTTP* «el campo de la cabecera ``Expires`` da la fecha/hora después de la cual se considera que la respuesta es vieja». La cabecera ``Expires`` se puede establecer con el método ``setExpires()`` de la ``Respuesta``. Esta necesita una instancia de ``DateTime`` como argumento:: - - $fecha = new DateTime(); - $date->modify('+600 seconds'); - - $response->setExpires($date); - -La cabecera *HTTP* resultante se ve de la siguiente manera: - -.. code-block:: text - - Expires: Thu, 01 Mar 2011 16:00:00 GMT - -.. note:: - - El método ``setExpires()`` automáticamente convierte la fecha a la zona horaria GMT como lo requiere la especificación. - -Ten en cuenta que en las versiones de *HTTP* anteriores a la 1.1 el servidor origen no estaba obligado a enviar la cabecera ``Date``. En consecuencia, la memoria caché (por ejemplo el navegador) podría -necesitar de contar en su reloj local para evaluar la cabecera ``Expires`` tomando el cálculo de la vida vulnerable para desviaciones del reloj. Otra limitación de la cabecera ``Expires`` es que la especificación establece que «Los servidores HTTP/1.1 no deben enviar fechas de más de un año en el futuro en ``Expires``». - -.. index:: - single: Caché; Cabecera Cache-Control - single: Cabeceras HTTP; Cache-Control - -Caducidad con la cabecera ``Cache-Control`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Debido a las limitaciones de la cabecera ``Expires``, la mayor parte del tiempo, debes usar la cabecera ``Cache-Control`` en su lugar. Recordemos que la cabecera ``Cache-Control`` se utiliza para especificar muchas directivas de caché diferentes. Para caducidad, hay dos directivas, ``max-age`` y ``s-maxage``. La primera la utilizan todas las cachés, mientras que la segunda sólo se tiene en cuenta por las cachés compartidas:: - - // Establece el número de segundos después de que la - // respuesta ya no se debe considerar fresca - $response->setMaxAge(600); - - // Lo mismo que la anterior pero sólo para cachés compartidas - $response->setSharedMaxAge(600); - -La cabecera ``Cache-Control`` debería tener el siguiente formato (esta puede tener directivas adicionales): - -.. code-block:: text - - Cache-Control: max-age=600, s-maxage=600 - -.. index:: - single: Caché; Validando - -Validando -~~~~~~~~~ - -Cuando un recurso se tiene que actualizar tan pronto como se realiza un cambio en los datos subyacentes, el modelo de caducidad se queda corto. Con el modelo de caducidad, no se pedirá a la aplicación que devuelva la respuesta actualizada hasta que la caché finalmente se convierta en obsoleta. - -El modelo de validación soluciona este problema. Bajo este modelo, la memoria caché sigue almacenando las respuestas. La diferencia es que, por cada petición, la caché pregunta a la aplicación cuando o no la respuesta memorizada sigue siendo válida. Si la caché todavía *es* válida, tu aplicación debe devolver un código de estado 304 y no el contenido. Esto le dice a la caché que está bien devolver la respuesta memorizada. - -Bajo este modelo, sobre todo ahorras ancho de banda ya que la representación no se envía dos veces al mismo cliente (en su lugar se envía una respuesta 304). Pero si diseñas cuidadosamente tu aplicación, es posible que puedas obtener los datos mínimos necesarios para enviar una respuesta 304 y ahorrar *CPU* también (más abajo puedes ver una implementación de ejemplo). - -.. tip:: - - El código de estado 304 significa «No Modificado». Es importante porque este código de estado *no* tiene el contenido real solicitado. - En cambio, la respuesta simplemente es un ligero conjunto de instrucciones que indican a la caché que se debe utilizar la versión almacenada. - -Al igual que con la caducidad, hay dos diferentes cabeceras *HTTP* que puedes utilizar para implementar el modelo de validación: ``Etag`` y ``Last-Modified``. - -.. index:: - single: Caché; Cabecera Etag - single: Cabeceras HTTP; Etag - -Validando con la cabecera ``ETag`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La cabecera ``ETag`` es una cabecera de cadena (llamada «entidad-etiqueta») que identifica unívocamente una representación del recurso destino. Este es generado completamente y establecido por tu aplicación de modo que puedes decir, por ejemplo, si el recurso memorizado ``/sobre`` está al día con el que tu aplicación iba a devolver. Una ``ETag`` es como una huella digital y se utiliza para comparar rápidamente si dos versiones diferentes de un recurso son equivalentes. Como las huellas digitales, cada ``ETag`` debe ser única en todas las representaciones de un mismo recurso. - -Para ver una sencilla implementación, genera la ``ETag`` como el ``md5`` del contenido:: - - public function indexAction() - { - $response = $this->render('MyBundle:Main:index.html.twig'); - $response->setETag(md5($response->getContent())); - $response->setPublic(); // verifica que la respuesta es púbica/susceptible de guardar en caché - $response->isNotModified($this->getRequest()); - - return $response; - } - -El método :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified` compara la ``ETag`` enviada en la ``Petición`` con la configurada en la ``Respuesta``. Si ambas coinciden, el método automáticamente establece el código de estado de la ``Respuesta`` a 304. - -Este algoritmo es bastante simple y muy genérico, pero es necesario crear la ``Respuesta`` completa antes de ser capaz de calcular la ``ETag``, lo cual es subóptimo. -En otras palabras, esta ahorra ancho de banda, pero no ciclos de la *CPU*. - -En la sección :ref:`optimizing-cache-validation`, te mostraré cómo puedes utilizar la validación de manera más inteligente para determinar la validez de una caché sin hacer tanto trabajo. - -.. tip:: - - *Symfony2* también apoya ``ETags`` débiles pasando ``true`` como segundo argumento del método :method:`Symfony\\Component\\HttpFoundation\\Response::setETag`. - -.. index:: - single: Caché; Cabecera Last-Modified - single: Cabeceras HTTP; Last-Modified - -Validando con la cabecera ``Last-Modified`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La cabecera ``Last-Modified`` es la segunda forma de validación. De acuerdo con la especificación *HTTP*, «El campo de la cabecera ``Last-Modified`` indica la fecha y hora en que el servidor origen considera que la representación fue modificada por última vez». En otras palabras, la aplicación decide si o no el contenido memorizado se ha actualizado en función de sí o no se ha actualizado desde que la respuesta entró en caché. - -Por ejemplo, puedes utilizar la última fecha de actualización de todos los objetos necesarios para calcular la representación del recurso como valor para el valor de la cabecera ``Last-Modified``:: - - public function showAction($articleSlug) - { - // ... - - $articleDate = new \DateTime($article->getUpdatedAt()); - $authorDate = new \DateTime($author->getUpdatedAt()); - - $date = $authorDate > $articleDate ? $authorDate : $articleDate; - - $response->setLastModified($date); - // Ajusta la respuesta como pública. De lo contrario será privada por omisión. - $response->setPublic(); - - if ($response->isNotModified($this->getRequest())) { - return $response; - } - - // ... hace más trabajo para poblar la respuesta con el contenido completo - - return $response; - } - -El método :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified` compara la cabecera ``If-Modified-Since`` enviada por la petición con la cabecera ``Last-Modified`` configurada en la respuesta. Si son equivalentes, la ``Respuesta`` establecerá un código de estado 304. - -.. note:: - - La cabecera ``If-Modified-Since`` de la petición es igual a la cabecera ``Last-Modified`` de la última respuesta enviada al cliente por ese recurso en particular. - Así es como se comunican el cliente y el servidor entre ellos y deciden si el recurso se ha actualizado desde que se memorizó. - -.. index:: - single: Caché; Get Condicional - single: HTTP; 304 - -.. _optimizing-cache-validation: - -Optimizando tu código con validación -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El objetivo principal de cualquier estrategia de memoria caché es aligerar la carga de la aplicación. -Dicho de otra manera, cuanto menos hagas en tu aplicación para devolver una respuesta 304, mejor. El método ``Response::isNotModified()`` hace exactamente eso al exponer un patrón simple y eficiente:: - - use Symfony\Component\HttpFoundation\Response; - - public function showAction($articleSlug) - { - // Obtiene la mínima información para calcular la ETag - // o el valor de Last-Modified (basado en la petición, - // los datos se recuperan de una base de datos o un par - // clave-valor guardado, por ejemplo) - $article = ...; - - // crea una respuesta con una cabecera ETag y/o Last-Modified - $response = new Response(); - $response->setETag($article->computeETag()); - $response->setLastModified($article->getPublishedAt()); - - // Ajusta la respuesta como pública. De lo contrario será privada por omisión. - $response->setPublic(); - - // verifica que la respuesta no se ha modificado para la petición dada - if ($response->isNotModified($this->getRequest())) { - // devuelve la instancia de la aplicación - return $response; - } else { - // aquí haz algo más - como recuperar más datos - $comments = ...; - - // o reproduce una plantilla con la $response que acabas de iniciar - return $this->render( - 'MyBundle:MyController:article.html.twig', - array('article' => $article, 'comments' => $comments), - $response - ); - } - } - -Cuando la ``Respuesta`` no es modificada, el ``isNotModified()`` automáticamente fija el código de estado de la respuesta a ``304``, remueve el contenido, y remueve algunas cabeceras que no deben estar presentes en respuestas ``304`` (consulta -:method:`Symfony\\Component\\HttpFoundation\\Response::setNotModified`). - -.. index:: - single: Caché; Vary - single: Cabeceras HTTP; Vary - -Variando la respuesta -~~~~~~~~~~~~~~~~~~~~~ - -Hasta ahora, asumiste que cada *URI* tiene exactamente una representación del recurso destino. De forma predeterminada, la caché *HTTP* se memoriza usando la *URI* del recurso como la clave de la caché. Si dos personas solicitan la misma *URI* de un recurso memorizable, la segunda persona recibirá la versión en caché. - -A veces esto no es suficiente y se necesita memorizar en caché diferentes versiones de la misma *URI* basándose en uno o más valores de las cabeceras de la petición. Por ejemplo, si comprimes las páginas cuando el cliente lo permite, cualquier *URI* tiene dos representaciones: -una cuando el cliente es compatible con la compresión, y otra cuando no. Esta determinación se hace por el valor de la cabecera ``Accept-Encoding`` de la petición. - -En este caso, necesitas que la memoria almacene una versión comprimida y otra sin comprimir de la respuesta para la *URI* particular y devolverlas basándose en el valor de la cabecera ``Accept-Encoding``. Esto se hace usando la cabecera ``Vary`` de la respuesta, la cual es una lista separada por comas de diferentes cabeceras cuyos valores lanzan una diferente representación de los recursos solicitados: - -.. code-block:: text - - Vary: Accept-Encoding, User-Agent - -.. tip:: - - Esta cabecera ``Vary`` particular debería memorizar diferentes versiones de cada recurso en base a la *URI* y el valor de las cabeceras ``Accept-Encoding`` y ``User-Agent`` de la petición. - -El objeto ``Respuesta`` ofrece una interfaz limpia para gestionar la cabecera ``Vary``:: - - // establece una cabecera vary - $response->setVary('Accept-Encoding'); - - // establece múltiples cabeceras vary - $response->setVary(array('Accept-Encoding', 'User-Agent')); - -El método ``setVary()`` toma un nombre de cabecera o un arreglo de nombres de cabecera de cual respuesta varía. - -Caducidad y validación -~~~~~~~~~~~~~~~~~~~~~~ - -Por supuesto, puedes utilizar tanto la caducidad como la validación de la misma ``Respuesta``. -La caducidad gana a la validación, te puedes beneficiar de lo mejor de ambos mundos. En otras palabras, utilizando tanto la caducidad como la validación, puedes instruir a la caché para que sirva el contenido memorizado, mientras que revisas de nuevo algún intervalo (de caducidad) para verificar que el contenido sigue siendo válido. - -.. index:: - pair: Caché; Configuración - -Más métodos de respuesta -~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase ``Respuesta`` proporciona muchos métodos más relacionados con la caché. Estos son los más útiles:: - - // Marca la respuesta como obsoleta - $response->expire(); - - // Fuerza a la respuesta a devolver una adecuada respuesta 304 sin contenido - $response->setNotModified(); - -Adicionalmente, puedes configurar muchas de las cabeceras *HTTP* relacionadas con la caché a través del único método :method:`Symfony\\Component\\HttpFoundation\\Response::setCache`:: - - // Establece la configuración de caché en una llamada - $response->setCache(array( - 'etag' => $etag, - 'last_modified' => $date, - 'max_age' => 10, - 's_maxage' => 10, - 'public' => true, - // 'private' => true, - )); - -.. index:: - single: Caché; ESI - single: ESI - -.. _edge-side-includes: - -Usando inclusión del borde lateral ----------------------------------- - -Las pasarelas de caché son una excelente forma de hacer que tu sitio web tenga un mejor desempeño. Pero tienen una limitación: sólo pueden memorizar páginas enteras. Si no puedes memorizar todas las páginas o si partes de una página tienen «más» elementos dinámicos, se te acabó la suerte. Afortunadamente, *Symfony2* ofrece una solución para estos casos, basada ​​en una tecnología llamada `ESI`_, o Inclusión de bordes laterales («``Edge Side Includes``»). Akamaï escribió esta especificación hace casi 10 años, y esta permite que partes específicas de una página tengan una estrategia de memorización diferente a la de la página principal. - -La especificación *ESI* describe las etiquetas que puedes incrustar en tus páginas para comunicarte con la pasarela de caché. *Symfony2* sólo implementa una etiqueta, ``include``, ya que es la única útil fuera del contexto de Akamaï: - -.. code-block:: html - - - - - - - - - - - - - -.. note:: - - Observa que en el ejemplo cada etiqueta *ESI* tiene una *URL* completamente cualificada. - Una etiqueta *ESI* representa un fragmento de página que se puede recuperar a través de la *URL*. - -Cuando se maneja una petición, la pasarela de caché obtiene toda la página de su caché o la pide a partir de la interfaz de administración de tu aplicación. Si la respuesta contiene una o más etiquetas *ESI*, estas se procesan de la misma manera. En otras palabras, la pasarela caché o bien, recupera el fragmento de página incluida en su caché o de nuevo pide el fragmento de página desde la interfaz de administración de tu aplicación. Cuando se han resuelto todas las etiquetas *ESI*, la pasarela caché une cada una en la página principal y envía el contenido final al cliente. - -Todo esto sucede de forma transparente a nivel de la pasarela caché (es decir, fuera de tu aplicación). Como verás, si decides tomar ventaja de las etiquetas *ESI*, *Symfony2* hace que el proceso de incluirlas sea casi sin esfuerzo. - -Usando *ESI* en *Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Primero, para usar *ESI*, asegúrate de activarlo en la configuración de tu aplicación: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - esi: { enabled: true } - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - // ... - 'esi' => array('enabled' => true), - )); - -Ahora, supongamos que tenemos una página que es relativamente estática, salvo por un teletipo de noticias en la parte inferior del contenido. Con *ESI*, puedes memorizar el teletipo de noticias independientemente del resto de la página. - -.. code-block:: php - - public function indexAction() - { - $response = $this->render('MyBundle:MyController:index.html.twig'); - // Pone el tiempo compartido máximo - el cual además marca la respuesta como pública - $response->setSharedMaxAge(600); - - return $response; - } - -En este ejemplo, la caché de la página completa tiene un tiempo de vida de diez minutos. -En seguida, incluirás el teletipo de noticias en la plantilla incorporando una acción. -Esto se hace a través del ayudante ``render`` (Consulta :ref:`templating-embedding-controller` para más detalles). - -Como el contenido integrado viene de otra página (o controlador en este caso), *Symfony2* utiliza el ayudante ``render`` estándar para configurar las etiquetas *ESI*: - -.. configuration-block:: - - .. code-block:: jinja - - {# puedes usar una referencia al controlador #} - {{ render_esi(controller('...:news', { 'max': 5 })) }} - - {# ... o una URL #} - {{ render_esi(url('latest_news', { 'max': 5 })) }} - - .. code-block:: html+php - - render( - new ControllerReference('...:news', array('max' => 5)), - array('renderer' => 'esi')) - ?> - - render( - $view['router']->generate('latest_news', array('max' => 5), true), - array('renderer' => 'esi'), - ) ?> - -Al utilizar el ``esi`` reproducido (vía la función ``render_esi`` de *Twig*), le dices a *Symfony2* que la acción se debería reproducir como una etiqueta *ESI*. Te podrías estar preguntando por qué querrías utilizar un ayudante en vez de sólo escribir la etiqueta *ESI* directamente. Esto se debe a que al utilizar un ayudante haces que tu aplicación trabaje incluso si no tienes instalada ninguna pasarela de caché. - -Al utilizar la función ``render`` predefinida (o configurar la estrategia a ``inline``), *Symfony2* fusiona el contenido de la página incluida con la principal antes de enviar la respuesta al cliente. Pero si utilizas la estrategia ``esi`` (es decir, llamas a ``render_esi``), *y* si *Symfony2* detecta que está hablando con una pasarela de caché que cuenta con soporte *ESI*, genera una etiqueta que incluye *ESI*. Pero si no hay ninguna pasarela de caché o si no cuenta con soporte *ESI*, *Symfony2* sólo fusionará el contenido de la página incluida dentro de la principal tal como lo haría si hubieras utilizado ``render``. - -.. note:: - - *Symfony2* detecta si una pasarela caché admite *ESI* a través de otra especificación Akamaï que fuera de la caja es compatible con el delegado inverso de *Symfony2*. - -La acción integrada ahora puede especificar sus propias reglas de caché, totalmente independientes de la página principal. - -.. code-block:: php - - public function newsAction($max) - { - // ... - - $response->setSharedMaxAge(60); - } - -Con *ESI*, la caché de la página completa será válida durante 600 segundos, pero la caché del componente de noticias sólo dura 60 segundos. - -Cuándo utilizas una referencia al controlador, la etiqueta *ESI* se debe referir a la acción incorporada como una *URL* accesible a modo de que la pasarela de caché la pueda recuperar independientemente del resto de la página. *Symfony2* cuida de generar una *URL* única para cualquier referencia de controlador y es capaz de enrutarla correctamente gracias a un escucha que debes habilitar en tu configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - fragments: { path: /_fragment } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - // ... - 'fragments' => array('path' => '/_fragment'), - )); - -Una gran ventaja de estrategia *ESI* es que puedes hacer tu aplicación tan dinámica como sea necesario y al mismo tiempo, tocar la aplicación lo menos posible. - -.. tip:: - - El escucha sólo responde a direcciones *IP* locales o a delegados inversos confiables. - -.. note:: - - Una vez que comiences a usar *ESI*, recuerda usar siempre la directiva ``s-maxage`` en lugar de ``max-age``. Como el navegador nunca recibe recursos agregados, no es consciente del subcomponente, y por lo tanto obedecerá la directiva ``max-age`` y memorizará la página completa. Y no quieres eso. - -El ayudante ``render_esi`` apoya otras dos útiles opciones: - -* ``alt``: utilizada como el atributo ``alt`` en la etiqueta *ESI*, el cual te permite especificar una *URL* alternativa para utilizarla si no se puede encontrar ``src``; - -* ``ignore_errors``: si la fijas a ``true``, se agrega un atributo ``onerror`` a la *ESI* con un valor de ``continue`` indicando que, en caso de una falla, la pasarela caché simplemente debe eliminar la etiqueta *ESI* silenciosamente. - -.. index:: - single: Caché; Invalidando - -.. _http-cache-invalidation: - -Invalidando la caché --------------------- - - «Sólo hay dos cosas difíciles en Ciencias de la Computación: Invalidación de caché y nombrar cosas» --Phil Karlton - -Nunca debería ser necesario invalidar los datos memorizados en caché porque la invalidación ya se tiene en cuenta de forma nativa en los modelos de caché *HTTP*. Si utilizas la validación, por definición, no será necesario invalidar ninguna cosa; y si utilizas la caducidad y necesitas invalidar un recurso, significa que estableciste la fecha de caducidad muy adelante en el futuro. - -.. note:: - - Debido a que la invalidación es un tema específico de cada tipo de delegado inverso, si no te preocupa la invalidación, puedes cambiar entre los delegados inversos sin cambiar nada en el código de tu aplicación. - -En realidad, todos los delegados inversas proporcionan una manera de purgar datos almacenados en caché, pero lo debes evitar tanto como sea posible. La forma más habitual es purgar la caché de una *URL* dada solicitándola con el método especial ``PURGE`` de *HTTP*. - -Aquí está cómo puedes configurar la caché del delegado inverso de *Symfony2* para apoyar el método ``PURGE`` de *HTTP*:: - - // 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(404, 'Not purged'); - } else { - $response->setStatusCode(200, 'Purged'); - } - - return $response; - } - } - -.. caution:: - - De alguna manera, debes proteger el método ``PURGE`` de *HTTP* para evitar que alguien aleatoriamente purgue los datos memorizados. - -Resumen -------- - -*Symfony2* fue diseñado para seguir las reglas probadas de la carretera: *HTTP*. El almacenamiento en caché no es una excepción. Dominar el sistema caché de *Symfony2* significa familiarizarse con los modelos de caché *HTTP* y usarlos eficientemente. Esto significa que, en lugar de confiar sólo en la documentación de *Symfony2* y ejemplos de código, tienes acceso a un mundo de conocimientos relacionados con la memorización en caché *HTTP* y la pasarela caché, tal como *Varnish*. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/cache/varnish` - -.. _`Things Caches Do`: http://tomayko.com/writings/things-caches-do -.. _`Guía de caché`: http://www.mnot.net/cache_docs/ -.. _`Varnish`: https://www.varnish-cache.org/ -.. _`Squid en modo delegado inverso`: http://wiki.squid-cache.org/SquidFaq/ReverseProxy -.. _`modelo de caducidad`: http://tools.ietf.org/html/rfc2616#section-13.2 -.. _`modelo de validación`: http://tools.ietf.org/html/rfc2616#section-13.3 -.. _`RFC 2616`: http://tools.ietf.org/html/rfc2616 -.. _`HTTP Bis`: http://tools.ietf.org/wg/httpbis/ -.. _`P4 - Petición condicional`: http://tools.ietf.org/html/draft-ietf-httpbis-p4-conditional-12 -.. _`P6 - Caché: Navegador y caché intermedia`: http://tools.ietf.org/html/draft-ietf-httpbis-p6-cache-12 -.. _`ESI`: http://www.w3.org/TR/esi-lang diff --git a/_sources/book/http_fundamentals.txt b/_sources/book/http_fundamentals.txt deleted file mode 100644 index cf3b783..0000000 --- a/_sources/book/http_fundamentals.txt +++ /dev/null @@ -1,401 +0,0 @@ -.. index:: - single: Fundamentos de Symfony2 - -*Symfony2* y fundamentos *HTTP* -=============================== - -¡Enhorabuena! Al aprender acerca de *Symfony2*, vas bien en tu camino para llegar a ser un más *productivo*, bien *enfocado* y *popular* desarrollador web (en realidad, en la última parte, estás por tu cuenta). *Symfony2* está diseñado para volver a lo básico: las herramientas de desarrollo que te permiten desarrollar más rápido y construir aplicaciones más robustas, mientras que permanece fuera de tu camino. *Symfony* está basado en las mejores ideas de muchas tecnologías: las herramientas y conceptos que estás a punto de aprender representan el esfuerzo de miles de personas, durante muchos años. En otras palabras, no estás aprendiendo «*Symfony*», estás aprendiendo los fundamentos de la *web*, buenas prácticas de desarrollo, y cómo utilizar muchas nuevas y asombrosas bibliotecas *PHP*, dentro o independientemente de *Symfony2*. Por lo tanto, ¡prepárate! - -Fiel a la filosofía *Symfony2*, este capítulo comienza explicando el concepto fundamental común para el desarrollo *web*: *HTTP*. Independientemente de tus antecedentes o lenguaje de programación preferido, este capítulo es una **lectura obligada** para todo mundo. - -*HTTP* es Simple ----------------- - -*HTTP* («*HyperText Transfer Protocol*» para los apasionados y, en Español *Protocolo de transferencia hipertexto*) es un lenguaje de texto que permite a dos máquinas comunicarse entre sí. ¡Eso es todo! Por ejemplo, al comprobar las últimas noticias acerca de cómica `xkcd`_, la siguiente conversación (aproximadamente) se lleva a cabo: - -.. image:: /images/http-xkcd_es.png - :align: center - -Y aunque el lenguaje real utilizado es un poco más formal, sigue siendo bastante simple. -*HTTP* es el término utilizado para describir este lenguaje simple basado en texto. Y no importa cómo desarrolles en la web, el objetivo de tu servidor *siempre* es entender las peticiones de texto simple, y devolver respuestas en texto simple. - -*Symfony2* está construido basado en torno a esa realidad. Ya sea que te des cuenta o no, *HTTP* es algo que usas todos los días. Con *Symfony2*, aprenderás a dominarlo. - -.. index:: - single: HTTP; El paradigma petición/respuesta - -Paso 1: El cliente envía una petición -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Todas las conversaciones en la web comienzan con una *petición*. La petición es un mensaje de texto creado por un cliente (por ejemplo un navegador, una aplicación para el *iPhone*, etc.) en un formato especial conocido como *HTTP*. El cliente envía la petición a un servidor, y luego espera la respuesta. - -Echa un vistazo a la primera parte de la interacción (la petición) entre un navegador y el servidor web `xkcd`: - -.. image:: /images/http-xkcd-request_es.png - :align: center - -Hablando en *HTTP*, esta petición *HTTP* en realidad se vería algo parecida a esto: - -.. code-block:: text - - GET / HTTP/1.1 - Host: xkcd.com - Accept: text/html - User-Agent: Mozilla/5.0 (Macintosh) - -Este sencillo mensaje comunica *todo* lo necesario sobre qué recursos exactamente solicita el cliente. La primera línea de una petición *HTTP* es la más importante y contiene dos cosas: la *URI* y el método *HTTP*. - -La *URI* (por ejemplo, ``/``, ``/contact``, etc.) es la dirección o ubicación que identifica unívocamente al recurso que el cliente quiere. El método *HTTP* (por ejemplo, *GET*) define lo que quieres *hacer* con el recurso. Los métodos *HTTP* son los *verbos* de la petición y definen las pocas formas más comunes en que puedes actuar sobre el recurso: - -+----------+---------------------------------------+ -| *GET* | Recupera el recurso desde el servidor | -+----------+---------------------------------------+ -| *POST* | Crea un recurso en el servidor | -+----------+---------------------------------------+ -| *PUT* | Actualiza el recurso en el servidor | -+----------+---------------------------------------+ -| *DELETE* | Elimina el recurso del servidor | -+----------+---------------------------------------+ - -Con esto en mente, te puedes imaginar que una petición *HTTP* podría ser similar a eliminar una entrada de *blog* específica, por ejemplo: - -.. code-block:: text - - DELETE /blog/15 HTTP/1.1 - -.. note:: - - En realidad, hay nueve métodos *HTTP* definidos por la especificación *HTTP*, pero muchos de ellos no se utilizan o apoyan ampliamente. En realidad, muchos navegadores modernos no apoyan los métodos *PUT* y *DELETE*. - -Además de la primera línea, una petición *HTTP* invariablemente contiene otras líneas de información conocidas como cabeceras de petición. Las cabeceras pueden suministrar una amplia gama de información como el servidor (o ``host``) solicitado, los formatos de respuesta que acepta el cliente (``Accept``) y la aplicación que utiliza el cliente para realizar la petición (``User-Agent``). Existen muchas otras cabeceras y se pueden encontrar en el artículo `Lista de campos de las cabeceras HTTP`_ en la Wikipedia. - -Paso 2: El servidor devuelve una respuesta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Una vez que un servidor ha recibido la petición, sabe exactamente qué recursos necesita el cliente (a través de la *URI*) y lo que el cliente quiere hacer con ese recurso (a través del método). Por ejemplo, en el caso de una petición *GET*, el servidor prepara el recurso y lo devuelve en una respuesta *HTTP*. Considera la respuesta del servidor web, xkcd: - -.. image:: /images/http-xkcd_es.png - :align: center - -Traducida a *HTTP*, la respuesta enviada de vuelta al navegador se verá algo similar a esto: - -.. code-block:: text - - HTTP/1.1 200 OK - Date: Sat, 02 Apr 2011 21:05:05 GMT - Server: lighttpd/1.4.19 - Content-Type: text/html - - - - - -La respuesta *HTTP* contiene el recurso solicitado (contenido *HTML* en este caso), así como otra información acerca de la respuesta. La primera línea es especialmente importante y contiene el código de estado *HTTP* (200 en este caso) de la respuesta. El código de estado comunica el resultado global de la petición devuelta al cliente. ¿Tuvo éxito la petición? ¿Hubo algún error? Existen diferentes códigos de estado que indican éxito, un error o qué más se necesita hacer con el cliente (por ejemplo, redirigirlo a otra página). La lista completa se puede encontrar en el artículo `Lista de códigos de estado HTTP`_ en la Wikipedia. - -Al igual que la petición, una respuesta *HTTP* contiene datos adicionales conocidos como cabeceras *HTTP*. Por ejemplo, una importante cabecera de la respuesta *HTTP* es ``Content-Type``. El cuerpo del mismo recurso se puede devolver en varios formatos diferentes, incluyendo *HTML*, *XML* o *JSON* y la cabecera ``Content-Type`` utiliza Internet Media Types como ``text/html`` para decirle al cliente cual formato se ha devuelto. Puedes encontrar una lista completa en el artículo `Lista de tipos de medios comunes`_ en la Wikipedia. - -Existen muchas otras cabeceras, algunas de las cuales son muy poderosas. Por ejemplo, puedes usar ciertas cabeceras para crear un poderoso sistema de memoria caché. - -Peticiones, respuestas y desarrollo Web -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Esta conversación petición-respuesta es el proceso fundamental que impulsa toda la comunicación en la web. Y tan importante y poderoso como es este proceso, inevitablemente es simple. - -El hecho más importante es el siguiente: independientemente del lenguaje que utilices, el tipo de aplicación que construyas (*web*, móvil, *API* *JSON*), o la filosofía de desarrollo que sigas, el objetivo final de una aplicación **siempre** es entender cada petición y crear y devolver la respuesta adecuada. - -*Symfony* está diseñado para adaptarse a esta realidad. - -.. tip:: - - Para más información acerca de la especificación *HTTP*, lee la referencia original `HTTP 1.1 RFC`_ o `HTTP Bis`_, el cual es un esfuerzo activo para aclarar la especificación original. Una gran herramienta para comprobar tanto la petición como las cabeceras de la respuesta mientras navegas es la extensión `Cabeceras HTTP en vivo`_ (*Live HTTP Headers*) para Firefox. - -.. index:: - single: Fundamentos de Symfony2; Peticiones y respuestas - -Peticiones y respuestas en *PHP* --------------------------------- - -Entonces ¿cómo interactúas con la «petición» y creas una «respuesta» utilizando *PHP*? En realidad, *PHP* te abstrae un poco de todo el proceso:: - - $uri = $_SERVER['REQUEST_URI']; - $foo = $_GET['foo']; - - header('Content-type: text/html'); - echo 'La URI solicitada es: '.$uri; - echo 'El valor del parámetro "foo" es: '.$foo; - -Por extraño que parezca, esta pequeña aplicación, de hecho, está tomando información de la petición *HTTP* y la utiliza para crear una respuesta *HTTP*. En lugar de analizar el mensaje *HTTP* de la petición, *PHP* prepara variables superglobales tales como ``$_SERVER`` y ``$_GET`` que contienen toda la información de la petición. Del mismo modo, en lugar de devolver la respuesta *HTTP* con formato de texto, puedes usar la función ``header()`` para crear las cabeceras de la respuesta y simplemente imprimir el contenido real que será la porción que contiene el mensaje de la respuesta. *PHP* creará una verdadera respuesta *HTTP* y la devolverá al cliente: - -.. code-block:: text - - HTTP/1.1 200 OK - Date: Sat, 03 Apr 2011 02:14:33 GMT - Server: Apache/2.2.17 (Unix) - Content-Type: text/html - - La URI solicitada es: /testing?foo=symfony - El valor del parámetro "foo" es: symfony - -Peticiones y respuestas en *Symfony* ------------------------------------- - -*Symfony* ofrece una alternativa al enfoque de *PHP* a través de dos clases que te permiten interactuar con la petición *HTTP* y la respuesta de una manera más fácil. -La clase :class:`Symfony\\Component\\HttpFoundation\\Request` es una sencilla representación orientada a objetos del mensaje de la petición *HTTP*. Con ella, tienes toda la información a tu alcance:: - - use Symfony\Component\HttpFoundation\Request; - - $request = Request::createFromGlobals(); - - // la URI solicitada (p. ej. /sobre) menos algunos parámetros de la consulta - $request->getPathInfo(); - - // recupera las variables GET y POST respectivamente - $request->query->get('foo'); - $request->request->get('bar', 'default value if bar does not exist'); - - // recupera las variables de SERVER - $request->server->get('HTTP_HOST'); - - // recupera una instancia del archivo subido identificado por foo - $request->files->get('foo'); - - // recupera un valor de COOKIE - $request->cookies->get('PHPSESSID'); - - // recupera una cabecera HTTP de la petición, normalizada, con índices en minúscula - $request->headers->get('host'); - $request->headers->get('content_type'); - - $request->getMethod(); // GET, POST, PUT, DELETE, HEAD - $request->getLanguages(); // un arreglo de idiomas aceptados por el cliente - -Como bono adicional, en el fondo la clase ``Petición`` hace un montón de trabajo del cual nunca tendrás que preocuparte. Por ejemplo, el método ``isSecure()`` comprueba *tres* diferentes valores en *PHP* que pueden indicar si el usuario está conectado a través de una conexión segura (es decir, ``https``). - -.. sidebar:: ``ParameterBags`` y atributos de la petición - - Como vimos anteriormente, las variables ``$_GET`` y ``$_POST`` son accesibles a través de las propiedades ``query`` y ``request``, respectivamente. Cada uno de estos objetos es un objeto de la :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`, la cual cuenta con métodos cómo: - :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::get`, - :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::has`, - :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::all` entre otros. - De hecho, todas las propiedades públicas utilizadas en el ejemplo anterior son un ejemplo del ``ParameterBag``. - - .. _book-fundamentals-attributes: - - La clase ``Petición`` también tiene una propiedad pública ``attributes``, que tiene datos especiales relacionados en cómo funciona internamente la aplicación. Para la plataforma *Symfony2*, ``attibutes`` mantiene los valores devueltos por la ruta buscada, tal como ``_controller``, ``id`` (por lo tanto si tienes un comodín ``{id}``), e incluso el nombre de la ruta buscada (``_route``). La propiedad ``attributes`` existe enteramente para ser un lugar donde se pueda preparar y almacenar información del contexto específico de la petición. - - -*Symfony* también proporciona una clase ``Respuesta``: una simple representación *PHP* de un mensaje de respuesta *HTTP*. Esto permite que tu aplicación utilice una interfaz orientada a objetos para construir la respuesta que será devuelta al cliente:: - - use Symfony\Component\HttpFoundation\Response; - $response = new Response(); - - $response->setContent('

Hello world!

'); - $response->setStatusCode(200); - $response->headers->set('Content-Type', 'text/html'); - - // imprime las cabeceras HTTP seguidas por el contenido - $response->send(); - -Si *Symfony* no ofreciera nada más, ya tendrías un conjunto de herramientas para acceder fácilmente a la información de la petición y una interfaz orientada a objetos para crear la respuesta. Incluso, a medida que aprendas muchas de las poderosas características de *Symfony*, nunca olvides que el objetivo de tu aplicación es *interpretar una petición y crear la respuesta adecuada basada en la lógica de tu aplicación*. - -.. tip:: - - Las clases ``Respuesta`` y ``Petición`` forman parte de un componente independiente incluido en *Symfony* llamado ``HttpFoundation``. Este componente se puede utilizar completamente independiente de *Symfony* y también proporciona clases para manejar sesiones y subir archivos. - -El viaje desde la petición hasta la respuesta ---------------------------------------------- - -Al igual que el mismo *HTTP*, los objetos ``Petición`` y ``Respuesta`` son bastante simples. -La parte difícil de la construcción de una aplicación es escribir lo que viene en el medio. -En otras palabras, el verdadero trabajo viene al escribir el código que interpreta la información de la petición y crea la respuesta. - -Tu aplicación probablemente hace muchas cosas, como enviar correo electrónico, manejar los formularios presentados, guardar cosas en una base de datos, reproducir las páginas *HTML* y proteger el contenido con seguridad. ¿Cómo puedes manejar todo esto y todavía mantener tu código organizado y fácil de mantener? - -*Symfony* fue creado para resolver estos problemas para que no tengas que hacerlo personalmente. - -El controlador frontal -~~~~~~~~~~~~~~~~~~~~~~ - -Tradicionalmente, las aplicaciones eran construidas de modo que cada «página» de un sitio tenía su propio archivo físico: - -.. code-block:: text - - index.php - contacto.php - blog.php - -Hay varios problemas con este enfoque, incluyendo la falta de flexibilidad de las *URL* (¿qué pasa si quieres cambiar :file:`blog.php` a :file:`noticias.php` sin romper todos tus enlaces?) y el hecho de que cada archivo *debe* incluir manualmente un conjunto de archivos básicos para la seguridad, conexiones a base de datos y que el «aspecto» del sitio pueda permanecer constante. - -Una mucho mejor solución es usar un :term:`controlador frontal`: un solo archivo *PHP* que se encargue de todas las peticiones que llegan a tu aplicación. Por ejemplo: - -+-----------------------------+----------------------------+ -| :file:`/index.php` | ejecuta :file:`index.php` | -+-----------------------------+----------------------------+ -| :file:`/index.php/contact` | ejecuta :file:`index.php` | -+-----------------------------+----------------------------+ -| :file:`/index.php/blog` | ejecuta :file:`index.php` | -+-----------------------------+----------------------------+ - -.. tip:: - - Usando ``mod_rewrite`` de *Apache* (o equivalente con otros servidores web), las *URL* se pueden limpiar fácilmente hasta ser sólo ``/``, ``/contact`` y ``/blog``. - -Ahora, cada petición se maneja de la misma manera exactamente. En lugar de *URL* individuales ejecutando diferentes archivos *PHP*, el controlador frontal *siempre* se ejecuta, y el enrutado de diferentes *URL* a diferentes partes de tu aplicación se realiza internamente. Esto resuelve los problemas del enfoque original. -Casi todas las aplicaciones *web* modernas lo hacen ---incluyendo aplicaciones como *WordPress*. - -Mantente organizado -~~~~~~~~~~~~~~~~~~~ - -Dentro de tu controlador frontal, tienes que averiguar qué código se debe ejecutar y cual contenido se debe regresar. Para resolver esto, necesitas comprobar la *URI* entrante y ejecutar diferentes partes de tu código dependiendo de ese valor. Esto se puede poner feo rápidamente:: - - // index.php - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\Response; - $request = Request::createFromGlobals(); - $path = $request->getPathInfo(); // La ruta URI solicitada - - if (in_array($path, array('', '/'))) { - $response = new Response('Welcome to the homepage.'); - } elseif ($path == '/contact') { - $response = new Response('Contact us'); - } else { - $response = new Response('Page not found.', 404); - } - $response->send(); - -La solución a este problema puede ser difícil. Afortunadamente esto es *exactamente* para lo que *Symfony* está diseñado. - -El flujo de las aplicaciones *Symfony* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando dejas que *Symfony* controle cada petición, la vida es mucho más fácil. *Symfony* sigue el mismo patrón simple en cada petición: - -.. _request-flow-figure: - -.. figure:: /images/request-flow_es.png - :align: center - :alt: Flujo de la petición en Symfony2 - - Las peticiones entrantes son interpretadas por el enrutador y pasadas a las funciones controladoras que regresan objetos ``Respuesta``. - -Cada «página» de tu sitio está definida en un archivo de configuración de enrutado que asigna las diferentes *URL* a diferentes funciones *PHP*. El trabajo de cada función *PHP* conocida como :term:`controlador`, es utilizar la información de la petición ---junto con muchas otras herramientas que *Symfony* pone a tu disposición--- para crear y devolver un objeto ``Respuesta``. En otras palabras, el controlador es donde *está tu* código: ahí es dónde se interpreta la petición y crea una respuesta. - -¡Así de fácil! Repasemos: - -* Cada petición ejecuta un archivo controlador frontal; - -* El sistema de enrutado determina cual función *PHP* se debe ejecutar en base a la información de la petición y la configuración de enrutado que creaste; - -* La función *PHP* correcta se ejecuta, donde tu código crea y devuelve el objeto ``Respuesta`` adecuado. - -Una petición *Symfony* en acción -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Sin bucear demasiado en los detalles, veamos este proceso en acción. Supongamos que deseas agregar una página ``/contact`` a tu aplicación *Symfony*. En primer lugar, empezamos agregando una entrada ``/contact`` a tu archivo de configuración de enrutado: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - contact: - path: /contact - defaults: { _controller: AcmeDemoBundle:Main:contact } - - .. code-block:: xml - - - AcmeBlogBundle:Main:contact - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('contact', new Route('/contact', array( - '_controller' => 'AcmeBlogBundle:Main:contact', - ))); - - return $collection; - -.. note:: - - Este ejemplo utiliza :doc:`YAML ` para definir la configuración de enrutado. La configuración de enrutado también se puede escribir en otros formatos, tal como *XML* o *PHP*. - -Cuando alguien visita la página ``/contact``, esta ruta coincide, y se ejecuta el controlador especificado. Como veremos en el capítulo :doc:`Enrutando `, la cadena ``AcmeDemoBundle:Main:contact`` es una sintaxis corta que apunta hacia el método *PHP* ``contactAction`` dentro de una clase llamada ``MainController``:: - - // src/Acme/DemoBundle/Controller/MainController.php - use Symfony\Component\HttpFoundation\Response; - - class MainController - { - public function contactAction() - { - return new Response('

Contact us!

'); - } - } - -En este muy simple ejemplo, el controlador sencillamente crea un objeto :class:`Symfony\\Component\\HttpFoundation\\Response` con el código *HTML* ``«

Contact us!

»``. En el capítulo :doc:`Controlador `, aprenderás cómo un controlador puede reproducir plantillas, permitiendo que tu código de «presentación» (es decir, algo que en realidad escribe *HTML*) viva en un archivo de plantilla separado. Esto libera al controlador de preocuparse sólo de las cosas difíciles: la interacción con la base de datos, la manipulación de los datos presentados o el envío de mensajes de correo electrónico. - -*Symfony2*: Construye tu aplicación, no tus herramientas. ---------------------------------------------------------- - -Ahora sabemos que el objetivo de cualquier aplicación es interpretar cada petición entrante y crear una respuesta adecuada. Cuando una aplicación crece, es más difícil mantener organizado tu código y que a la vez sea fácil darle mantenimiento. Invariablemente, las mismas tareas complejas siguen viniendo una y otra vez: la persistencia de cosas a la base de datos, procesamiento y reutilización de plantillas, manejo de formularios presentados, envío de mensajes de correo electrónico, validación de entradas del usuario y administración de la seguridad. - -La buena nueva es que ninguno de estos problemas es único. *Symfony* proporciona una plataforma completa, con herramientas que te permiten construir tu aplicación, no tus herramientas. Con *Symfony2*, nada se te impone: eres libre de usar la plataforma *Symfony* completa, o simplemente una pieza de *Symfony* por sí misma. - -.. index:: - single: Componentes de Symfony2 - -Herramientas independientes: *Componentes* de *Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Entonces, ¿qué *es Symfony2*? En primer lugar, *Symfony2* es una colección de más de veinte bibliotecas independientes que se pueden utilizar dentro de *cualquier* proyecto *PHP*. Estas bibliotecas, llamadas *componentes de Symfony2*, contienen algo útil para casi cualquier situación, independientemente de cómo desarrolles tu proyecto. Para nombrar algunas: - -* :doc:`HttpFoundation` --- Contiene las clases ``Petición`` y ``Respuesta``, así como otras clases para manejar sesiones y cargar archivos; - -* :doc:`Enrutando ` --- Potente y rápido sistema de enrutamiento que te permite asociar una *URI* específica (por ejemplo ``/contacto``) a cierta información acerca de cómo se debe manejar esa petición (por ejemplo, ejecutando el método ``contactoAction()``); - -* `Form`_ --- Una completa y flexible plataforma para crear formularios y procesar los datos presentados en ellos; - -* `Validator`_ Un sistema para crear reglas sobre datos y entonces, cuando el usuario presenta los datos comprobar si son válidos o no siguiendo esas reglas; - -* :doc:`ClassLoader` Una biblioteca de carga automática que permite utilizar clases *PHP* sin necesidad de ``require`` los archivos que contienen esas clases manualmente; - -* :doc:`Plantillas ` Un juego de herramientas para reproducir plantillas, manejar la herencia de plantillas (es decir, una plantilla es decorada con un diseño) y realizar otras tareas comunes de las plantillas; - -* `Security`_ --- Una poderosa biblioteca para manejar todo tipo de seguridad dentro de una aplicación; - -* `Translation`_ Una plataforma para traducir cadenas en tu aplicación. - -Todos y cada uno de estos componentes se desacoplan y se pueden utilizar en *cualquier* proyecto *PHP*, independientemente de si utilizas la plataforma *Symfony2*. -Cada parte está hecha para utilizarla si es conveniente y sustituirse cuando sea necesario. - -La solución completa: La *plataforma Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Entonces, ¿qué *es* la *plataforma* *Symfony2*? La *plataforma* *Symfony2* es una biblioteca *PHP* que realiza dos distintas tareas: - -#. Proporciona una selección de componentes (es decir, los componentes de *Symfony2*) y bibliotecas de terceros (por ejemplo, `SwiftMailer`_ para enviar mensajes de correo electrónico); - -#. Proporciona configuración sensible y el «pegamento» que une la biblioteca con todas estas piezas. - -El objetivo de la plataforma es integrar muchas herramientas independientes con el fin de proporcionar una experiencia coherente al desarrollador. Incluso la propia plataforma es un paquete *Symfony2* (es decir, un complemento) que puedes configurar o sustituir completamente. - -*Symfony2* proporciona un potente conjunto de herramientas para desarrollar aplicaciones web rápidamente sin imponerse en tu aplicación. Los usuarios normales rápidamente pueden comenzar el desarrollo usando una distribución *Symfony2*, que proporciona un esqueleto del proyecto con parámetros predeterminados. Para los usuarios más avanzados, el cielo es el límite. - -.. _`xkcd`: http://xkcd.com/ -.. _`HTTP 1.1 RFC`: http://www.w3.org/Protocols/rfc2616/rfc2616.html -.. _`HTTP Bis`: http://datatracker.ietf.org/wg/httpbis/ -.. _`Cabeceras HTTP en vivo`: https://addons.mozilla.org/en-US/firefox/addon/live-http-headers/ -.. _`Lista de códigos de estado HTTP`: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes -.. _`Lista de campos de las cabeceras HTTP`: http://en.wikipedia.org/wiki/List_of_HTTP_header_fields -.. _`Lista de tipos de medios comunes`: http://en.wikipedia.org/wiki/Internet_media_type#List_of_common_media_types -.. _`Form`: https://github.com/symfony/Form -.. _`Validator`: https://github.com/symfony/Validator -.. _`Security`: https://github.com/symfony/Security -.. _`Translation`: https://github.com/symfony/Translation -.. _`Swiftmailer`: http://swiftmailer.org/ diff --git a/_sources/book/index.txt b/_sources/book/index.txt deleted file mode 100644 index 0e7abfe..0000000 --- a/_sources/book/index.txt +++ /dev/null @@ -1,27 +0,0 @@ -Libro -===== - -.. toctree:: - :hidden: - - http_fundamentals - from_flat_php_to_symfony2 - installation - page_creation - controller - routing - templating - doctrine - propel - testing - validation - forms - security - http_cache - translation - service_container - performance - internals - stable_api - -.. include:: /book/map.rst.inc diff --git a/_sources/book/installation.txt b/_sources/book/installation.txt deleted file mode 100644 index a29c6d9..0000000 --- a/_sources/book/installation.txt +++ /dev/null @@ -1,266 +0,0 @@ -.. index:: - single: Instalando - -Instalando y configurando *Symfony* -=================================== - -El objetivo de este capítulo es empezar a trabajar con una aplicación funcionando incorporada en lo alto de *Symfony*. Afortunadamente, *Symfony* dispone de «distribuciones», que son proyectos *Symfony* funcionales desde el «arranque», los cuales puedes descargar y comenzar a desarrollar inmediatamente. - -.. tip:: - - Si estás buscando instrucciones sobre la mejor manera de crear un nuevo proyecto y guardarlo vía el control de código fuente, consulta `Usando control de código fuente`_. - -Instalando una distribución de *Symfony2* ------------------------------------------ - -.. tip:: - - En primer lugar, comprueba que tienes instalado y configurado un servidor web (como *Apache*) con *PHP 5.3.8* o superior. Para más información sobre los requisitos de *Symfony2*, consulta los :doc:`requisitos en la referencia `. - -Los paquetes de las «distribuciones» de *Symfony2*, son aplicaciones totalmente funcionales que incluyen las bibliotecas del núcleo de *Symfony2*, una selección de útiles paquetes, una sensible estructura de directorios y alguna configuración predeterminada. Al descargar una distribución *Symfony2*, estás descargando la estructura de una aplicación operativa que puedes utilizar inmediatamente para comenzar a desarrollar tu aplicación. - -Empieza por visitar la página de descarga de *Symfony2* en `http://symfony.com/download`_. -En esta página, puedes encontrar la *edición estándar de Symfony*, que es la distribución principal de *Symfony2*. Hay 2 maneras en las que puedes iniciar tu proyecto: - -Opción 1) ``Composer`` -~~~~~~~~~~~~~~~~~~~~~~ - -`Composer`_ es una biblioteca de administración de dependencias para *PHP*, la cual puedes utilizar para descargar la *Edición estándar de Symfony2*. - -Comienza `descargando Composer`_ en algún lugar de tu ordenador local. Si tienes instalado :command:`curl`, es tan fácil como: - -.. code-block:: bash - - % curl -s https://getcomposer.org/installer | php - -.. note:: - - Si tu ordenador no está listo para utilizar ``Composer``, verás algunas recomendaciones cuándo ejecutes esta orden. Sigue las recomendaciones hasta lograr que ``Composer`` trabaje correctamente. - -``Composer`` es un archivo *PHAR* ejecutable, el cual puedes utilizar para descargar la distribución estándar: - -.. code-block:: bash - - php composer.phar create-project symfony/framework-standard-edition /ruta/al/directorio/web-raiz/Symfony 2.2.0 - -.. tip:: - - Para una versión exacta, reemplaza `2.2.0` con la más reciente versión de *Symfony* (p. ej. 2.2.1). Para detalles, ve la `Página de instalación de Symfony`_ - -.. tip:: - - Para descargar más rápido los archivos de proveedores, añade la opción ``--prefer-dist`` al final de cualquier orden de *Composer*. - -Esta orden puede tardar varios minutos en su ejecución puesto que composer descarga la Distribución estándar junto con todas las bibliotecas de proveedores que necesita. Cuando termine, tendrás un directorio parecido a este: - -.. code-block:: text - - ruta/al/directorio/web-raiz/ <- tu directorio raíz del servidor web - Symfony/ <- el archivo extraído - app/ - cache/ - config/ - logs/ - src/ - ... - vendor/ - ... - web/ - app.php - ... - -Opción 2) Descarga un archivo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -También puedes descargar un archivo de la Edición estándar. En este caso, necesitas hacer dos elecciones: - -* Descargar o bien un archivo ``.tgz`` o ``.zip`` --- ambos son equivalentes, descarga aquel con el que te sientas más cómodo---; - -* Descargar la distribución con o sin ``vendors``. Si estás planeando utilizar más bibliotecas o paquetes de terceros y gestionarlas vía ``Composer``, probablemente tienes que descarga la versión «sin ``vendors``». - -Descarga uno de los archivos en algún lugar bajo el directorio raíz de tu servidor web local y descomprímelo. Desde una línea de ordenes de *UNIX*, esto se puede hacer con una de las siguientes ordenes (sustituye ``###`` con el nombre del archivo real): - -.. code-block:: bash - - # para un archivo .tgz - $ tar zxvf Symfony_Standard_Vendors_2.2.###.tgz - - # para un archivo .zip - $ unzip Symfony_Standard_Vendors_2.2.###.zip - -Si descargaste el archivo «sin ``vendors``», definitivamente necesitarás leer la siguiente sección. - -.. note:: - - Fácilmente puedes sustituir la estructura de directorios predefinida. Consulta :doc:`/cookbook/configuration/override_dir_structure` para más información. - -.. _installation-updating-vendors: - -Actualizando ``vendors`` -~~~~~~~~~~~~~~~~~~~~~~~~ - -Al llegar a este punto, has descargado un proyecto *Symfony* completamente operativo en el cual puedes comenzar a desarrollar tu propia aplicación. Un proyecto *Symfony* depende de una serie de bibliotecas externas. Estas se han descargado al directorio ``vendor/`` de tu proyecto vía una biblioteca llamada `Composer`_. - -Dependiendo de cómo descargaste *Symfony*, ahora posiblemente o no necesites actualizar tus proveedores. Pero, actualizar tus proveedores siempre es seguro, y garantiza que tienes todas las bibliotecas de proveedores necesarias. - -Paso 1: Consigue `Composer`_ (El nuevo gran sistema de empacado *PHP*) - -.. code-block:: bash - - curl -s http://getcomposer.org/installer | php - -Asegúrate de descargar :file:`composer.phar` en el mismo directorio dónde se encuentra el archivo :file:`composer.json` (este, por omisión, es el directorio raíz de tu proyecto *Symfony*). - -Paso 2: Instala las bibliotecas de terceros - -.. code-block:: bash - - $ php composer.phar install - -Esta orden descarga todas las bibliotecas de terceros necesarias ---incluyendo al mismo *Symfony*--- en el directorio :file:`vendor/`. - -.. note:: - - Si no tienes instalado ``curl``, simplemente puedes descargar manualmente el archivo ``instalador`` de http://getcomposer.org/installer. Coloca ese archivo en tu proyecto y luego ejecuta: - - .. code-block:: bash - - php installer - php composer.phar install - -.. tip:: - - Cuando ejecutes ``php composer.phar install`` o ``php composer.phar update``, composer ejecutará las ordenes postinstalación/postactualización para limpiar la caché e instalar los activos. De manera predeterminada, los activos se copiarán a tu directorio ``web``. - - En vez de copiar tus activos *Symfony*, puedes crear enlaces simbólicos si tu sistema operativo los apoya. Para crear enlaces simbólicos, añade una entrada en el nodo ``extra`` de tu archivo :file:`composer.json`` con la clave ``symfony-assets-install`` y el valor ``symlink``: - - - .. code-block:: json - - "extra": { - "symfony-app-dir": "app", - "symfony-web-dir": "web", - "symfony-assets-install": "symlink" - } - - Al suministrar ``relative`` en lugar de ``symlink`` a ``symfony-assets-install``, la orden generará enlaces simbólicos relativos. - -Instalando y configurando -~~~~~~~~~~~~~~~~~~~~~~~~~ - -En este punto, todas las bibliotecas de terceros necesarias ahora viven en el directorio :file:`vendor/`. También tienes una instalación predeterminada de la aplicación en :file:`app/` y algunos ejemplos de código dentro de :file:`src/`. - -*Symfony2* viene con una interfaz visual para probar la configuración del servidor, muy útil para ayudarte a solucionar problemas relacionados con la configuración de tu servidor web y *PHP* para utilizar *Symfony*. Usa la siguiente *URL* para examinar tu configuración: - -.. code-block:: text - - http://localhost/config.php - -Si hay algún problema, corrígelo antes de continuar. - -.. sidebar:: Configurando permisos - - Un problema común es que ambos directorios ``app/cache`` y ``app/logs`` deben tener permiso de escritura, tanto para el servidor web cómo para la línea de ordenes del usuario. En un sistema *UNIX*, si el usuario del servidor *web* es diferente de tu usuario de la línea de ordenes, puedes ejecutar las siguientes ordenes una sola vez en el proyecto para garantizar que los permisos se configuran correctamente. - - **Ten en cuenta que no todos los servidores web corren como el usuario** ``www-data`` como en los ejemplos de abajo. En su lugar, revisa qué usuario está corriendo *tu* servidor *web* y úsalo en lugar de ``www-data``. - - En un sistema *UNIX*, esto se puede hacer con una de las siguientes órdenes: - - .. code-block:: bash - - $ ps aux | grep httpd - - o - - .. code-block:: bash - - $ ps aux | grep apache - - **1. Usando ACL en un sistema que admite chmod +a** - - Muchos sistemas te permiten utilizar la orden chmod +a. Intenta esta orden primero, y si se produce un error --- intenta el siguiente método: Asegurate de cambiar ``www-data`` por el usuario de tu servidor *web* en la primer orden ``chmod``: - - .. code-block:: bash - - $ rm -rf app/cache/* - $ rm -rf app/logs/* - - $ sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs - $ sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs - - **2. Usando ACL en un sistema que no es compatible con chmod +a** - - Algunos sistemas, no son compatibles con ``chmod +a``, pero son compatibles con otra utilidad llamada ``setfacl``. Posiblemente tengas que habilitar la `compatibilidad con ACL`_ en tu partición e instalar :dfn:`setfacl` antes de usarlo (como es el caso de *Ubuntu*), de esta manera: - - .. code-block:: bash - - $ sudo setfacl -R -m u:www-data:rwX -m u:`whoami`:rwX app/cache app/logs - $ sudo setfacl -dR -m u:www-data:rwx -m u:`whoami`:rwx app/cache app/logs - - **3. Sin usar ACL** - - Si no tienes acceso para modificar los directorios *ACL*, tendrás que cambiar la ``umask`` para que los directorios ``cache/`` y ``logs/`` se puedan escribir por el grupo o por cualquiera (dependiendo de si el usuario del servidor web y el usuario de la línea de ordenes están en el mismo grupo o no). Para ello, pon la siguiente línea al comienzo de los archivos ``app/console``, ``web/app.php`` y ``web/app_dev.php``:: - - umask(0002); // Esto permitirá que los permisos sean 0775 - - // o - - umask(0000); // Esto permitirá que los permisos sean 0777 - - Ten en cuenta que el uso de *ACL* se recomienda cuando tienes acceso a ellos en el servidor porque cambiar la ``umask`` no es seguro en subprocesos. - -Cuando todo esté listo, haz clic en el enlace «Visita la página de Bienvenida» para ver tu primer aplicación «real» en *Symfony2*: - -.. code-block:: text - - http://localhost/app_dev.php/ - -¡*Symfony2* debería darte la bienvenida y felicitarte por tu arduo trabajo hasta el momento! - -.. image:: /images/quick_tour/welcome.png - -.. tip:: - - Para conseguir bonitas y breves *URL* deberías apuntar el documento raíz de tu servidor *web* o servidor virtual al directorio ``Symfony/web/``. Aunque esto no es necesario para el desarrollo, es recomendable al momento de desplegar tu aplicación en producción cuando todo el sistema y archivos de configuración se vuelven inaccesibles a los clientes. Para información sobre cómo configurar el directorio raíz de tu servidor web, lee :doc:`/cookbook/configuration/web_server_configuration` o consulta la documentación oficial de tu servidor web: - `Apache`_ | `Nginx`_ . - -Empezando a desarrollar ------------------------ - -Ahora que tienes una aplicación *Symfony2* completamente operativa, ¡puedes empezar a desarrollarla! Tu distribución puede contener algún código de ejemplo --- revisa el archivo ``README.md`` incluido con la distribución (abrelo como archivo de texto) para ver qué código de ejemplo incluye tu distribución. - -Si es tu primer contacto con *Symfony*, alcánzanos en «:doc:`page_creation`», donde aprenderás a crear páginas, cambiar la configuración, y todo lo que necesitarás en tu nueva aplicación. - -Además, asegúrate de revisar el :doc:`Recetario `, el cual contiene una amplia variedad de artículos que solucionan problemas específicos con *Symfony*. - -.. note:: - - Si quieres eliminar el código de ejemplo de tu distribución, dale un vistazo a este artículo en el recetario: «:doc:`/cookbook/bundles/remove`» - -Usando control de código fuente -------------------------------- - -Si estás utilizando un sistema de control de versiones como ``Git`` o ``Subversion``, puedes configurar tu sistema de control de versiones y empezar a confirmar cambios al proyecto normalmente. La *edición estándar de Symfony* **es** el punto de partida para tu nuevo proyecto. - -Para instrucciones específicas sobre la mejor manera de configurar el proyecto para almacenarlo en *git*, consulta :doc:`/cookbook/workflow/new_project_git`. - -Ignorando el directorio :file:`vendor/` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si has descargado el archivo *sin proveedores*, puedes omitir todo el directorio :file:`vendor/` y no confirmarlo al control de versiones. Con ``Git``, esto se logra creando un archivo :file:`.gitignore` y añadiendo lo siguiente: - -.. code-block:: text - - /vendor/ - -Ahora, el directorio de proveedores no será confirmado al control de versiones. Esto está muy bien (en realidad, ¡es genial!) porque cuando alguien más clone o coteje el proyecto, él/ella simplemente puede ejecutar el archivo ``php composer.phar install`` para descargar todas las bibliotecas de proveedores necesarias. - -.. _`compatibilidad con ACL`: https://help.ubuntu.com/community/FilePermissionsACLs -.. _`http://symfony.com/download`: http://symfony.com/download -.. _`Git`: http://git-scm.com/ -.. _`GitHub Bootcamp`: http://help.github.com/set-up-git-redirect -.. _`Composer`: http://getcomposer.org/ -.. _`descargando Composer`: http://getcomposer.org/download/ -.. _`Apache`: http://httpd.apache.org/docs/current/mod/core.html#documentroot -.. _`Nginx`: http://wiki.nginx.org/Symfony -.. _`Página de instalación de Symfony`: http://symfony.com/download diff --git a/_sources/book/internals.txt b/_sources/book/internals.txt deleted file mode 100644 index 6aee06f..0000000 --- a/_sources/book/internals.txt +++ /dev/null @@ -1,543 +0,0 @@ -.. index:: - single: Funcionamiento interno - -Funcionamiento interno -====================== - -Parece que quieres entender cómo funciona y cómo extender *Symfony2*. -¡Eso me hace muy feliz! Esta sección es una explicación en profundidad de *Symfony2* desde dentro. - -.. note:: - - Necesitas leer esta sección sólo si quieres entender cómo funciona *Symfony2* detrás de la escena, o si deseas extender *Symfony2*. - -Descripción ------------ - -El código *Symfony2* está hecho de varias capas independientes. Cada capa está construida en lo alto de la anterior. - -.. tip:: - - La carga automática no la gestiona la plataforma directamente; Esto se hace utilizando el cargador automático de ``Composer`` (:file:`vendor/autoload.php`), el cual está incluido en el archivo :file:`src/autoload.php`. - -Componente ``HttpFoundation`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Al nivel más profundo está el componente :namespace:`Symfony\\Component\\HttpFoundation`. ``HttpFoundation`` proporciona los principales objetos necesarios para hacer frente a *HTTP*. -Es una abstracción orientada a objetos de algunas funciones y variables nativas de *PHP*: - -* La clase :class:`Symfony\\Component\\HttpFoundation\\Request` resume las principales variables globales de *PHP*, tales como ``$_GET``, ``$_POST``, ``$_COOKIE``, ``$_FILES`` y ``$_SERVER``; - -* La clase :class:`Symfony\\Component\\HttpFoundation\\Response` abstrae algunas funciones *PHP* como ``header()``, ``setcookie()`` y ``echo``; - -* La clase :class:`Symfony\\Component\\HttpFoundation\\Session` y la interfaz :class:`Symfony\\Component\\HttpFoundation\\SessionStorage\\SessionStorageInterface`, abstraen la gestión de sesiones y las funciones ``session_*()``. - -Componente ``HttpKernel`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -En lo alto de ``HttpFoundation`` está el componente :namespace:`Symfony\\Component\\HttpKernel`. ``HttpKernel`` se encarga de la parte dinámica de *HTTP*; es una fina capa en la parte superior de las clases ``Petición`` y ``Respuesta`` para estandarizar la forma en que se manejan las peticiones. Este, también proporciona puntos de extensión y herramientas que lo convierten en el punto de partida ideal para crear una plataforma *Web* sin demasiado trabajo. - -Además, opcionalmente añade configurabilidad y extensibilidad, gracias al componente de inyección de dependencias y un potente sistema de complementos (paquetes). - -.. seealso:: - - Lee más sobre la :doc:`Inyección de dependencias ` y los :doc:`Paquetes `. - -Paquete ``FrameworkBundle`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El paquete :namespace:`Symfony\\Bundle\\FrameworkBundle` es el paquete que une los principales componentes y bibliotecas para hacer una plataforma *MVC* ligera y rápida. Este viene con una sensible configuración predeterminada y convenios para suavizar la curva de aprendizaje. - -.. index:: - single: Funcionamiento interno; Kernel - -El núcleo ---------- - -La clase :class:`Symfony\\Component\\HttpKernel\\HttpKernel` es la clase central de *Symfony2* y es responsable de procesar las peticiones del cliente. Su objetivo principal es «convertir» un objeto :class:`Symfony\\Component\\HttpFoundation\\Request` a un objeto :class:`Symfony\\Component\\HttpFoundation\\Response`. - -Cada ``núcleo`` de *Symfony2* implementa :class:`Symfony\\Component\\HttpKernel\\HttpKernelInterface`:: - - function handle(Request $request, $type = self::MASTER_REQUEST, $catch = true) - -.. index:: - single: Funcionamiento interno; Resolutor de controlador - -Controladores -~~~~~~~~~~~~~ - -Para convertir una ``Petición`` a una ``Respuesta``, el ``núcleo`` cuenta con un «``Controlador``». Un controlador puede ser cualquier *PHP* ejecutable válido. - -El ``núcleo`` delega la selección de cual controlador se debe ejecutar a una implementación de :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`:: - - public function getController(Request $request); - - public function getArguments(Request $request, $controller); - -El método :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController` devuelve el controlador (un *PHP* ejecutable) asociado a la petición dada. La implementación predeterminada de (:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`) busca un atributo ``_controller`` en la petición que representa el nombre del controlador (una cadena ``«class::method»``, cómo ``Bundle\BlogBundle\PostController:indexAction``). - -.. tip:: - - La implementación predeterminada utiliza la clase :class:`Symfony\\Bundle\\FrameworkBundle\\EventListener\\RouterListener` para definir el atributo ``_controller`` de la petición (consulta el :ref:`kernel-core-request`). - -El método :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments` devuelve un arreglo de argumentos para pasarlo al Controlador ejecutable. La implementación predeterminada automáticamente resuelve los argumentos del método, basándose en los atributos de la Petición. - -.. sidebar:: Emparejando los argumentos del método ``Controlador`` desde los atributos de la ``Petición`` - - Por cada argumento del método, *Symfony2* trata de obtener el valor de un atributo de la ``Petición`` con el mismo nombre. Si no se proporciona, el valor predeterminado es el argumento utilizado de estar definido:: - - // Symfony2 debe buscar un atributo 'id' (obligatorio) - // y un 'admin' (opcional) - public function showAction($id, $admin = true) - { - // ... - } - -.. index:: - single: Funcionamiento interno; Procesando la petición - -Procesando peticiones -~~~~~~~~~~~~~~~~~~~~~ - -El método :method:`Symfony\\Component\\HttpKernel\\HttpKernel::handle` toma una ``Petición`` y *siempre* devuelve una ``Respuesta``. Para convertir la ``Petición``, ``handle()`` confía en el mecanismo de resolución y una cadena ordenada de notificaciones de evento (consulta la siguiente sección para más información acerca de cada evento): - -#. Antes de hacer cualquier otra cosa, difunde el evento ``kernel.request`` ---si alguno de los escuchas devuelve una ``Respuesta``, salta directamente al paso 8; - -#. El mecanismo de resolución es llamado para determinar el controlador a ejecutar; - -#. Los escuchas del evento ``kernel.controller`` ahora pueden manipular el controlador ejecutable como quieras (cambiarlo, envolverlo, ...); - -#. El núcleo verifica que el controlador en realidad es un *PHP* ejecutable válido; - -#. Se llama al mecanismo de resolución para determinar los argumentos a pasar al controlador; - -#. El ``núcleo`` llama al controlador; - -#. Si el controlador no devuelve una ``Respuesta``, los escuchas del evento ``kernel.view`` pueden convertir en ``Respuesta`` el valor devuelto por el Controlador; - -#. Los escuchas del evento ``kernel.response`` pueden manipular la ``Respuesta`` (contenido y cabeceras); - -#. Devuelve la respuesta. - -Si se produce una Excepción durante el procesamiento, difunde la ``kernel.exception`` y se da la oportunidad a los escuchas de convertir la excepción en una ``Respuesta``. Si esto funciona, se difunde el evento ``kernel.response``; si no, se vuelve a lanzar la excepción. - -Si no deseas que se capturen las Excepciones (para peticiones incrustadas, por ejemplo), desactiva el evento ``kernel.exception`` pasando ``false`` como tercer argumento del método ``handle()``. - -.. index:: - single: Funcionamiento interno; Peticiones internas - -Funcionamiento interno de las peticiones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En cualquier momento durante el manejo de una petición (la 'maestra' uno), puede manejar una subpetición. Puedes pasar el tipo de petición al método ``handle()`` (su segundo argumento): - -* ``HttpKernelInterface::MASTER_REQUEST``; -* ``HttpKernelInterface::SUB_REQUEST``. - -El tipo se pasa a todos los eventos y los escuchas pueden actuar en consecuencia (algún procesamiento sólo debe ocurrir en la petición maestra). - -.. index:: - pair: Kernel; Evento - -Eventos -~~~~~~~ - -Cada evento lanzado por el ``núcleo`` es una subclase de :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`. Esto significa que cada evento tiene acceso a la misma información básica: - -* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequestType` --- devuelve el *tipo* de la petición (``HttpKernelInterface::MASTER_REQUEST`` o ``HttpKernelInterface::SUB_REQUEST``); - -* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getKernel` --- devuelve el núcleo que está procesando la petición; - -* :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequest` --- devuelve la ``Petición`` procesada actualmente. - -``getRequestType()`` -.................... - -El método ``getRequestType()`` permite a los escuchas conocer el tipo de la petición. Por ejemplo, si un escucha sólo debe estar atento a las peticiones maestras, agrega el siguiente código al principio de tu método escucha:: - - use Symfony\Component\HttpKernel\HttpKernelInterface; - - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { - // regresa inmediatamente - return; - } - -.. tip:: - - Si todavía no estás familiarizado con el *Despachador de eventos* de *Symfony2*, primero lee la sección del :doc:`Componente despachador de eventos `. - -.. index:: - single: Evento; kernel.request - -.. _kernel-core-request: - -Evento ``kernel.request`` -......................... - -*Clase del evento*: :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent` - -El objetivo de este evento es devolver inmediatamente un objeto ``Respuesta`` o variables de configuración para poder invocar un controlador después del evento. Cualquier escucha puede devolver un objeto ``Respuesta`` a través del método ``setResponse()`` en el evento. En este caso, todos los otros escuchas no serán llamados. - -Este evento lo utiliza el ``FrameworkBundle`` para llenar el atributo ``_controller`` de la ``Petición``, a través de :class:`Symfony\\Bundle\\FrameworkBundle\\EventListener\\RouterListener`. ``RequestListener`` usa un objeto :class:`Symfony\\Component\\Routing\\RouterInterface` para emparejar la ``Petición`` y determinar el nombre del controlador (guardado en el atributo ``_controller`` de la ``Petición``). - -.. index:: - single: Evento; kernel.controller - -Evento ``kernel.controller`` -............................ - -*Clase del evento*: :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent` - -Este evento no lo utiliza el ``FrameworkBundle``, pero puede ser un punto de entrada para modificar el controlador que se debe ejecutar:: - - use Symfony\Component\HttpKernel\Event\FilterControllerEvent; - - public function onKernelController(FilterControllerEvent $event) - { - $controller = $event->getController(); - // ... - - // el controlador se puede cambiar a cualquier PHP ejecutable - $event->setController($controller); - } - -.. index:: - single: Evento; kernel.view - -Evento ``kernel.view`` -...................... - -*Clase del evento*: :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent` - -Este evento no lo utiliza el ``FrameworkBundle``, pero lo puedes usar para implementar un subsistema de vistas. Este evento se llama *sólo* si el controlador *no* devuelve un objeto ``Respuesta``. El propósito del evento es permitir que algún otro valor de retorno se convierta en una ``Respuesta``. - -El valor devuelto por el controlador es accesible a través del método ``getControllerResult``:: - - use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent; - use Symfony\Component\HttpFoundation\Response; - - public function onKernelView(GetResponseForControllerResultEvent $event) - { - $val = $event->getControllerResult(); - $response = new Response(); - - // ... de alguna manera modifica la Respuesta desde el valor de retorno - - $event->setResponse($response); - } - -.. index:: - single: Evento; kernel.response - -Evento ``kernel.response`` -.......................... - -*Clase del evento*: :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent` - -El propósito de este evento es permitir que otros sistemas modifiquen o sustituyan el objeto ``Respuesta`` después de su creación:: - - public function onKernelResponse(FilterResponseEvent $event) - { - $response = $event->getResponse(); - - // ... modifica el objeto Respuesta - } - -El ``FrameworkBundle`` registra varios escuchas: - -* :class:`Symfony\\Component\\HttpKernel\\EventListener\\ProfilerListener`: - recoge los datos de la petición actual; - -* :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener`: - inyecta la barra de herramientas de depuración web; - -* :class:`Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener`: fija el ``Content-Type`` de la respuesta basándose en el formato de la petición; - -* :class:`Symfony\\Component\\HttpKernel\\EventListener\\EsiListener`: agrega una cabecera *HTTP* ``Surrogate-Control`` cuando es necesario analizar etiquetas *ESI* en la respuesta. - -.. index:: - single: Evento; kernel.exception - -.. _kernel-kernel.exception: - -Evento ``kernel.exception`` -........................... - -*Clase del evento*: :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent` - -``FrameworkBundle`` registra un :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener` el cual remite la ``Petición`` a un determinado controlador (el valor del parámetro ``exception_listener.controller`` ---debe estar en notación ``clase::método``---). - -Un escucha de este evento puede crear y configurar un objeto ``Respuesta``, crear y establecer un nuevo objeto ``Excepción``, o simplemente no hacer nada:: - - use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent; - use Symfony\Component\HttpFoundation\Response; - - public function onKernelException(GetResponseForExceptionEvent $event) - { - $exception = $event->getException(); - $response = new Response(); - // configura el objeto respuesta basándose en la excepción capturada - $event->setResponse($response); - - // alternativamente puedes establecer una nueva excepción - // $exception = new \Exception('Some special exception'); - // $event->setException($exception); - } - -.. note:: - - Debido a que *Symfony* se asegura de que el código de estado de la ``Respuesta`` se ajusta al más adecuado dependiendo de la excepción, establecer el estado de la respuesta no funcionará. Si quieres reescribir el código de estado (no deberías, sin una buena razón), configurar la cabecera ``X-Status-Code``:: - - return new Response('Error', 404 /* ignored */, array('X-Status-Code' => 200)); - -.. index:: - single: Despachador de evento - -El despachador de eventos -------------------------- - -El despachador de eventos es un componente independiente y es el responsable de mucha de la lógica y flujo subyacente detrás de una petición *Symfony*. Para más información consulta la documentación del :doc:`Componente despachador de eventos `. - -.. index:: - single: Generador de perfiles - -.. _internals-profiler: - -Generador de perfiles ---------------------- - -Cuando se activa, el generador de perfiles de *Symfony2* recoge información útil sobre cada petición presentada a tu aplicación y la almacena para su posterior análisis. Utiliza el generador de perfiles en el entorno de desarrollo para que te ayude a depurar tu código y mejorar el rendimiento; úsalo en el entorno de producción para explorar problemas después del hecho. - -Rara vez tienes que lidiar con el generador de perfiles directamente puesto que *Symfony2* proporciona herramientas de visualización como la barra de herramientas de depuración web y el generador de perfiles web. Si utilizas la *edición estándar de Symfony2*, el generador de perfiles, la barra de herramientas de depuración web, y el generador de perfiles web, ya están configurados con ajustes razonables. - -.. note:: - - El generador de perfiles recopila información para todas las peticiones (peticiones simples, redirecciones, excepciones, peticiones *Ajax*, peticiones *ESI*; y para todos los métodos *HTTP* y todos los formatos). Esto significa que para una única *URL*, puedes tener varios perfiles de datos asociados (un par petición/respuesta externa). - -.. index:: - single: Generador de perfiles: Visualizando - -Visualizando perfiles de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Usando la barra de depuración web -................................. - -En el entorno de desarrollo, la barra de depuración web está disponible en la parte inferior de todas las páginas. Esta muestra un buen resumen de los datos perfilados que te da acceso instantáneo a una gran cantidad de información útil cuando algo no funciona como esperabas. - -Si el resumen presentado por las herramientas de la barra de depuración web no es suficiente, haz clic en el enlace simbólico (una cadena compuesta de 13 caracteres aleatorios) para acceder al generador de perfiles web. - -.. note:: - - Si no se puede hacer clic en el enlace, significa que las rutas del generador de perfiles no están registradas (más adelante hay información de configuración). - -Analizando datos del perfil con el generador de perfiles web -............................................................ - -El generador de perfiles web es una herramienta de visualización para perfilar datos que puedes utilizar en desarrollo para depurar tu código y mejorar el rendimiento; pero también lo puedes utilizar para explorar problemas que ocurren en producción. Este expone toda la información recogida por el generador de perfiles en una interfaz web. - -.. index:: - single: Generador de perfiles: Usando el servicio generador de perfiles - -Accediendo a información del generador de perfiles -.................................................. - -No es necesario utilizar el visualizador predeterminado para acceder a la información de perfiles. Pero ¿cómo se puede recuperar información del perfil de una petición específica después del hecho? Cuando el generador de perfiles almacena datos sobre una ``Petición``, también le asocia una ficha; esta ficha está disponible en la cabecera *HTTP* ``X-Debug-Token`` de la Respuesta:: - - $profile = $container->get('profiler')->loadProfileFromResponse($response); - - $profile = $container->get('profiler')->loadProfile($token); - -.. tip:: - - Cuando el generador de perfiles está habilitado pero no la barra de herramientas de depuración *web*, o cuando desees obtener la ficha de una petición *Ajax*, utiliza una herramienta como *Firebug* para obtener el valor de la cabecera *HTTP* ``X-Debug-Token``. - -Usa el método :method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::find` para acceder a elementos basándose en algún criterio:: - - // consigue las 10 últimas fichas - $tokens = $container->get('profiler')->find('', '', 10); - - // consigue las 10 últimas fichas de todas las URL que contienen /admin/ - $tokens = $container->get('profiler')->find('', '/admin/', 10); - - // consigue las 10 últimas fichas de peticiones locales - $tokens = $container->get('profiler')->find('127.0.0.1', '', 10); - -Si deseas manipular los datos del perfil en una máquina diferente a la que generó la información, utiliza los métodos :method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::export` e :method:`Symfony\\Component\\HttpKernel\\Profiler\\Profiler::import`:: - - // en la máquina en producción - $profile = $container->get('profiler')->loadProfile($token); - $data = $profiler->export($profile); - - // en la máquina de desarrollo - $profiler->import($data); - -.. index:: - single: Generador de perfiles: Visualizando - -Configurando -............ - -La configuración predeterminada de *Symfony2* viene con ajustes razonables para el generador de perfiles, la barra de herramientas de depuración web, y el generador de perfiles web. Aquí está por ejemplo la configuración para el entorno de desarrollo: - -.. configuration-block:: - - .. code-block:: yaml - - # carga el generador de perfiles - framework: - profiler: { only_exceptions: false } - - # activa el generador de perfiles web - web_profiler: - toolbar: true - intercept_redirects: true - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // carga el generador de perfiles - $container->loadFromExtension('framework', array( - 'profiler' => array('only-exceptions' => false), - )); - - // activa el generador de perfiles web - $container->loadFromExtension('web_profiler', array( - 'toolbar' => true, - 'intercept-redirects' => true, - 'verbose' => true, - )); - -Cuando ``only-exceptions`` se establece a ``true``, el generador de perfiles sólo recoge datos cuando tu aplicación lanza una excepción. - -Cuando ``intercept-redirects`` está establecido en ``true``, el generador de perfiles web intercepta las redirecciones y te da la oportunidad de analizar los datos recogidos antes de seguir la redirección. - -Si activas el generador de perfiles web, también es necesario montar las rutas de los perfiles: - -.. configuration-block:: - - .. code-block:: yaml - - _profiler: - resource: @WebProfilerBundle/Resources/config/routing/profiler.xml - prefix: /_profiler - - .. code-block:: xml - - - - .. code-block:: php - - $collection->addCollection($loader->import("@WebProfilerBundle/Resources/config/routing/profiler.xml"), '/_profiler'); - -Dado que el generador de perfiles añade algo de sobrecarga, posiblemente desees activarlo sólo bajo ciertas circunstancias en el entorno de producción. La configuración ``only-exceptions`` limita al generador de perfiles a 500 páginas, ¿pero si quieres obtener información cuando la *IP* cliente proviene de una dirección específica, o para una porción limitada del sitio web? Puedes utilizar una emparejadora de petición: - -.. configuration-block:: - - .. code-block:: yaml - - # activa el generador de perfiles sólo para peticiones entrantes de la red 192.168.0.0 - framework: - profiler: - matcher: { ip: 192.168.0.0/24 } - - # activa el generador de perfiles sólo para las URL /admin - framework: - profiler: - matcher: { path: "^/admin/" } - - # combina reglas - framework: - profiler: - matcher: { ip: 192.168.0.0/24, path: "^/admin/" } - - # usa una instancia emparejadora personalizada definida en el servicio 'custom_matcher' - framework: - profiler: - matcher: { service: custom_matcher } - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - // activa el generador de perfiles sólo para peticiones entrantes de la red 192.168.0.0 - $container->loadFromExtension('framework', array( - 'profiler' => array( - 'matcher' => array('ip' => '192.168.0.0/24'), - ), - )); - - // activa el generador de perfiles sólo para las URL /admin - $container->loadFromExtension('framework', array( - 'profiler' => array( - 'matcher' => array('path' => '^/admin/'), - ), - )); - - // combina reglas - $container->loadFromExtension('framework', array( - 'profiler' => array( - 'matcher' => array('ip' => '192.168.0.0/24', 'path' => '^/admin/'), - ), - )); - - # usa una instancia emparejadora personalizada definida en el servicio 'custom_matcher' - $container->loadFromExtension('framework', array( - 'profiler' => array( - 'matcher' => array('service' => 'custom_matcher'), - ), - )); - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/testing/profiling` -* :doc:`/cookbook/profiler/data_collector` -* :doc:`/cookbook/event_dispatcher/class_extension` -* :doc:`/cookbook/event_dispatcher/method_behavior` - -.. _`componente de Inyección de dependencias de Symfony2`: https://github.com/symfony/DependencyInjection diff --git a/_sources/book/page_creation.txt b/_sources/book/page_creation.txt deleted file mode 100644 index 3e9f911..0000000 --- a/_sources/book/page_creation.txt +++ /dev/null @@ -1,789 +0,0 @@ -.. index:: - single: Creando páginas - -Creando páginas en *Symfony2* -============================= - -Crear una nueva página en *Symfony2* es un sencillo proceso de dos pasos: - -* *Crea una ruta*: Una ruta define la *URL* de tu página (por ejemplo ``/sobre``) y especifica un controlador (el cual es una función *PHP*) que *Symfony2* debe ejecutar cuando la *URL* de una petición entrante coincida con el patrón de la ruta; - -* *Crea un controlador*: Un controlador es una función *PHP* que toma la petición entrante y la transforma en el objeto ``Respuesta`` de *Symfony2* que es devuelto al usuario. - -Nos encanta este enfoque simple porque coincide con la forma en que funciona la *Web*. -Cada interacción en la *Web* se inicia con una petición *HTTP*. El trabajo de la aplicación simplemente es interpretar la petición y devolver la respuesta *HTTP* adecuada. - -*Symfony2* sigue esta filosofía y te proporciona las herramientas y convenios para mantener organizada tu aplicación a medida que crece en usuarios y complejidad. - -.. index:: - single: Creando páginas; Ejemplo - -La página «¡Hola *Symfony*!» ----------------------------- - -Vamos a empezar construyendo un subproducto de la clásica aplicación «¡Hola Mundo!». Cuando hayamos terminado, el usuario podrá recibir un saludo personal (por ejemplo, «Hola *Symfony*») al ir a la siguiente *URL*: - -.. code-block:: text - - http://localhost/app_dev.php/hello/Symfony - -En realidad, serás capaz de sustituir ``Symfony`` con cualquier otro nombre al cual darle la bienvenida. Para crear la página, sigue el simple proceso de dos pasos. - -.. note:: - - La guía asume que ya has descargado *Symfony2* y configurado tu servidor web. En la *URL* anterior se supone que ``localhost`` apunta al directorio ``web``, de tu nuevo proyecto *Symfony2*. Para información más detallada sobre este proceso, consulta la documentación del servidor *web* que estás usando. - Aquí están las páginas de la documentación pertinente para algunos servidores *web* que podrías estar utilizando: - - * Para el servidor *HTTP* *Apache*, consulta la documentación de `Apache sobre DirectoryIndex`_. Para *Nginx*, consulta la documentación de `ubicación HttpCoreModule de Nginx`_. - -Antes de empezar: Crea el paquete -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de empezar, tendrás que crear un ``bundle`` (*paquete* en adelante). En *Symfony2*, un :term:`paquete` es como un complemento (o plugin, para los puristas), salvo que todo el código de tu aplicación debe vivir dentro de un paquete. - -Un paquete no es más que un directorio que alberga todo lo relacionado con una función específica, incluyendo clases *PHP*, configuración, e incluso hojas de estilo y archivos de *Javascript* (consulta :ref:`page-creation-bundles`). - -Para crear un paquete llamado ``AcmeHelloBundle`` (el paquete de ejemplo que vamos a construir en este capítulo), ejecuta la siguiente orden y sigue las instrucciones en pantalla (usa todas las opciones predeterminadas): - -.. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/HelloBundle --format=yml - -Detrás del escenario, se crea un directorio para el paquete en :file:`src/Acme/HelloBundle`. -Además, automáticamente agrega una línea al archivo ``app/AppKernel.php`` para registrar el paquete en el núcleo:: - - // app/AppKernel.php - public function registerBundles() - { - $bundles = array( - ..., - new Acme\HelloBundle\AcmeHelloBundle(), - ); - // ... - - return $bundles; - } - -Ahora que ya está configurado el paquete, puedes comenzar a construir tu aplicación dentro del paquete. - -Paso 1: Creando la ruta -~~~~~~~~~~~~~~~~~~~~~~~~ - -De manera predeterminada, el archivo de configuración de enrutado en una aplicación *Symfony2* se encuentra en :file:`app/config/routing.yml`. Al igual que toda la configuración en *Symfony2*, fuera de la caja también puedes optar por utilizar *XML* o *PHP* para configurar tus rutas. - -Si te fijas en el archivo de enrutado principal, verás que *Symfony* ya ha agregado una entrada al generar el ``AcmeHelloBundle``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - acme_hello: - resource: "@AcmeHelloBundle/Resources/config/routing.yml" - prefix: / - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->addCollection( - $loader->import('@AcmeHelloBundle/Resources/config/routing.php'), - '/', - ); - - return $collection; - -Esta entrada es bastante básica: le dice a *Symfony* que cargue la configuración de enrutado del archivo ``Resources/config/routing.yml`` que reside en el interior del ``AcmeHelloBundle``. -Esto significa que colocas la configuración de enrutado directamente en ``app/config/routing.yml`` u organizas tus rutas a través de tu aplicación, y las importas desde ahí. - -Ahora que el archivo :file:`routing.yml` es importado desde el paquete, añade la nueva ruta que define la *URL* de la página que estás a punto de crear: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/routing.yml - hello: - path: /hello/{name} - defaults: { _controller: AcmeHelloBundle:Hello:index } - - .. code-block:: xml - - - - - - - - AcmeHelloBundle:Hello:index - - - - .. code-block:: php - - // src/Acme/HelloBundle/Resources/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('hello', new Route('/hello/{name}', array( - '_controller' => 'AcmeHelloBundle:Hello:index', - ))); - - return $collection; - -La ruta se compone de dos piezas básicas: ``path``, que es la *URL* con la que esta ruta debe coincidir, y un arreglo ``defaults``, que especifica el controlador que se debe ejecutar. La sintaxis del marcador de posición (``{name}``) en la ruta es un comodín. Significa que ``/hello/Ryan``, ``/hello/Fabien`` o cualquier otra *URI* similar coincidirá con esta ruta. El parámetro marcador de posición ``{name}`` también se pasará al controlador, de manera que podamos utilizar su valor para saludar personalmente al usuario. - -.. note:: - - El sistema de enrutado tiene muchas más características para crear estructuras *URI* flexibles y potentes en tu aplicación. Para más detalles, consulta el capítulo :doc:`Enrutando `. - -Paso 2: Creando el controlador -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando una *URL* como ``/hello/Ryan`` es manejada por la aplicación, la ruta ``hello`` corresponde con el controlador ``AcmeHelloBundle:Hello:index`` el cual es ejecutado por la plataforma. El segundo paso del proceso de creación de páginas es precisamente la creación de ese controlador. - -El controlador --- ``AcmeHelloBundle:Hello:index`` es el nombre *lógico* del controlador, el cual se asigna al método ``indexAction`` de una clase *PHP* llamada ``Acme\HelloBundle\Controller\HelloController``. Empieza creando este archivo dentro de tu ``AcmeHelloBundle``:: - - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; - - class HelloController - { - } - -En realidad, el controlador no es más que un método *PHP* que tú creas y *Symfony* ejecuta. Aquí es donde el código utiliza la información de la petición para construir y preparar el recurso solicitado. Salvo en algunos casos avanzados, el producto final de un controlador siempre es el mismo: un objeto ``Respuesta`` de *Symfony2*. - -Crea el método ``indexAction`` que *Symfony* ejecutará cuando concuerde la ruta ``hello``:: - - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; - - use Symfony\Component\HttpFoundation\Response; - - class HelloController - { - public function indexAction($name) - { - return new Response('Hello '.$name.'!'); - } - } - -El controlador es simple: este crea un nuevo objeto ``Respuesta``, cuyo primer argumento es el contenido que se debe utilizar para la respuesta (una pequeña página *HTML* en este ejemplo). - -¡Enhorabuena! Después de crear solamente una ruta y un controlador ¡ya tienes una página completamente operativa! Si todo lo has configurado correctamente, la aplicación debe darte la bienvenida: - -.. code-block:: text - - http://localhost/app_dev.php/hello/Ryan - -.. tip:: - - También puedes ver tu aplicación en el :ref:`entorno ` «prod» visitando: - - .. code-block:: text - - http://localhost/app.php/hello/Ryan - - Si se produce un error, probablemente sea porque necesitas vaciar la caché ejecutando: - - .. code-block:: bash - - $ php app/console cache:clear --env=prod --no-debug - -Un opcional, pero frecuente, tercer paso en el proceso es crear una plantilla. - -.. note:: - - Los controladores son el punto de entrada principal a tu código y un ingrediente clave en la creación de páginas. Puedes encontrar mucho más información en el capítulo :doc:`Controlador `. - -Paso 3 opcional: Creando la plantilla -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Las plantillas te permiten mover toda la presentación (por ejemplo, el código *HTML*) a un archivo separado y reutilizar diferentes partes del diseño de la página. En vez de escribir el código *HTML* dentro del controlador, en su lugar reproduce una plantilla: - -.. code-block:: php - :linenos: - - // src/Acme/HelloBundle/Controller/HelloController.php - namespace Acme\HelloBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class HelloController extends Controller - { - public function indexAction($name) - { - return $this->render( - 'AcmeHelloBundle:Hello:index.html.twig', - array('name' => $name) - ); - - // dibuja una plantilla PHP en su lugar - // return $this->render( - // 'AcmeHelloBundle:Hello:index.html.php', - // array('name' => $name) - // ); - } - } - -.. note:: - - Para poder usar el método :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::render`, tu controlador debe extender la clase :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` que añade atajos para las tareas comunes en los controladores. Esto se hace en el ejemplo anterior añadiendo la declaración ``use`` en la línea 4 y luego extendiendo el ``Controlador`` en la línea 6. - -El método ``render()`` crea un objeto ``Respuesta`` poblado con el contenido propuesto, y reproduce la plantilla. Como cualquier otro controlador, en última instancia vas a devolver ese objeto ``Respuesta``. - -Ten en cuenta que hay dos ejemplos diferentes para procesar la plantilla. -De forma predeterminada, *Symfony2* admite dos diferentes lenguajes de plantillas: las clásicas plantillas *PHP* y las breves pero poderosas plantillas `Twig`_. No te espantes ---eres libre de optar por una o, incluso, ambas en el mismo proyecto. - -El controlador procesa la plantilla ``AcmeHelloBundle:Hello:index.html.twig``, utilizando la siguiente convención de nomenclatura: - - **NombrePaquete**:**NombreControlador**:**NombrePlantilla** - -Este es el nombre *lógico* de la plantilla, el cual se asigna a una ubicación física usando la siguiente convención. - - **/ruta/a/NombrePaquete**/Resources/views/**NombreControlador**/**NombrePlantilla** - -En este caso, ``AcmeHelloBundle`` es el nombre del paquete, ``Hello`` es el controlador e ``index.html.twig`` la plantilla: - -.. configuration-block:: - - .. code-block:: jinja - :linenos: - - {# src/Acme/HelloBundle/Resources/views/Hello/index.html.twig #} - {% extends '::base.html.twig' %} - - {% block body %} - Hello {{ name }}! - {% endblock %} - - .. code-block:: html+php - - - extend('::base.html.php') ?> - - Hello escape($name) ?>! - -Veamos la situación a través de la plantilla *Twig* línea por línea: - -* *línea 2*: La etiqueta ``extends`` define una plantilla padre. La plantilla define explícitamente un archivo con el diseño dentro del cual será colocada. - -* *línea 4*: La etiqueta ``block`` dice que todo el interior se debe colocar dentro de un bloque llamado ``body``. Como verás, es responsabilidad de la plantilla padre (``base.html.twig``) reproducir, en última instancia, el bloque llamado ``body``. - -La plantilla padre, ``::base.html.twig``, omite ambas porciones de su nombre tanto **NombrePaquete** como **NombreControlador** (de ahí los dobles dos puntos (``::``) al principio). Esto significa que la plantilla vive fuera de cualquier paquete, en el directorio ``app``: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# app/Resources/views/base.html.twig #} - - - - - {% block title %}Welcome!{% endblock %} - {% block stylesheets %}{% endblock %} - - - - {% block body %}{% endblock %} - {% block javascripts %}{% endblock %} - - - - .. code-block:: html+php - - - - - - - <?php $view['slots']->output('title', 'Welcome!') ?> - output('stylesheets') ?> - - - - output('_content') ?> - output('javascripts') ?> - - - -El archivo de la plantilla base define el diseño *HTML* y reproduce el bloque ``body`` que definiste en la plantilla ``index.html.twig``. Además reproduce el bloque ``title``, el cual puedes optar por definir en la plantilla ``index.html.twig``. -Dado que no has definido el bloque ``title`` en la plantilla derivada, el valor predeterminado es «``Welcome!``». - -Las plantillas son una poderosa manera de reproducir y organizar el contenido de tu página. Una plantilla puede reproducir cualquier cosa, desde el marcado *HTML*, al código *CSS*, o cualquier otra cosa que el controlador posiblemente tenga que devolver. - -En el ciclo de vida del manejo de una petición, el motor de plantillas simplemente es una herramienta opcional. Recuerda que el objetivo de cada controlador es devolver un objeto ``Respuesta``. Las plantillas son una poderosa, pero opcional, herramienta para crear el contenido de ese objeto ``Respuesta``. - -.. index:: - single: Estructura del directorio - -La estructura de directorios ----------------------------- - -Después de unas cortas secciones, ya entiendes la filosofía detrás de la creación y procesamiento de páginas en *Symfony2*. También has comenzado a ver cómo están estructurados y organizados los proyectos *Symfony2*. Al final de esta sección, sabrás dónde encontrar y colocar diferentes tipos de archivos y por qué. - -Aunque totalmente flexible, por omisión, cada :term:`aplicación` *Symfony* tiene la misma estructura de directorios básica y recomendada: - -* ``app/``: Este directorio contiene la configuración de la aplicación; - -* :file:`src/`: Todo el código *PHP* del proyecto se almacena en este directorio; - -* :file:`vendor/`: Por convención aquí se coloca cualquier biblioteca de terceros; - -* ``web/``: Este es el directorio *web* raíz y contiene todos los archivos de acceso público; - -.. _the-web-directory: - -El directorio *web* -~~~~~~~~~~~~~~~~~~~ - -El directorio raíz del servidor *web*, es el hogar de todos los archivos públicos y estáticos tales como imágenes, hojas de estilo y archivos *JavaScript*. También es el lugar donde vive cada :term:`controlador frontal`:: - - // web/app.php - require_once __DIR__.'/../app/bootstrap.php.cache'; - require_once __DIR__.'/../app/AppKernel.php'; - - use Symfony\Component\HttpFoundation\Request; - - $kernel = new AppKernel('prod', false); - $kernel->loadClassCache(); - $kernel->handle(Request::createFromGlobals())->send(); - -El archivo del controlador frontal (:file:`app.php` en este ejemplo) es el archivo *PHP* que realmente se ejecuta cuando utilizas una aplicación *Symfony2* y su trabajo consiste en utilizar una clase del núcleo, ``AppKernel``, para arrancar la aplicación. - -.. tip:: - - Tener un controlador frontal significa que se utilizan diferentes y más flexibles *URL* que en una aplicación *PHP* típica. Cuando usamos un controlador frontal, las *URL* se formatean de la siguiente manera: - - .. code-block:: text - - http://localhost/app.php/hello/Ryan - - El controlador frontal, :file:`app.php`, se ejecuta y la *URL* «interna»: ``/hello/Ryan`` es encaminada internamente con la configuración de enrutado. - Al utilizar las reglas ``mod_rewrite`` de *Apache*, puedes forzar la ejecución del archivo :file:`app.php` sin necesidad de especificarlo en la *URL*: - - .. code-block:: text - - http://localhost/hello/Ryan - -Aunque los controladores frontales son esenciales en el manejo de cada petición, rara vez los tendrás que modificar o incluso pensar en ellos. Los vamos a mencionar brevemente de nuevo en la sección de `Entornos`_. - -El directorio de la aplicación (``app``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Como vimos en el controlador frontal, la clase ``AppKernel`` es el punto de entrada principal de la aplicación y es la responsable de toda la configuración. Como tal, se almacena en el directorio ``app/``. - -Esta clase debe implementar dos métodos que definen todo lo que *Symfony* necesita saber acerca de tu aplicación. Ni siquiera tienes que preocuparte de estos métodos durante el arranque ---*Symfony* los llena por ti con parámetros predeterminados. - -* ``registerBundles()``: Devuelve un arreglo con todos los paquetes necesarios para ejecutar la aplicación (consulta :ref:`page-creation-bundles`); - -* ``registerContainerConfiguration()``: Carga el archivo de configuración de recursos principal de la aplicación (consulta la sección `Configurando la aplicación`_); - -En el desarrollo del día a día, generalmente vas a utilizar el directorio ``app/`` para modificar la configuración y los archivos de enrutado en el directorio ``app/config/`` (consulta la sección `Configurando la aplicación`_). Este también contiene el directorio caché de la aplicación (``app/cache``), un directorio de registro (``app/logs``) y un directorio para archivos de recursos a nivel de la aplicación, tal como plantillas (``app/Resources``). -Aprenderás más sobre cada uno de estos directorios en capítulos posteriores. - -.. _autoloading-introduction-sidebar: - -.. sidebar:: Carga automática - - Al arrancar *Symfony*, un archivo especial ---:file:`app/autoload.php`--- es| incluido. Este archivo es creado por ``Composer`` y cargará automáticamente todos los archivos que vivan en el directorio :file:`src/` de tu aplicación así como todas las bibliotecas de terceros mencionadas en el archivo :file:`Composer.json`. - - Gracias al cargador automático, nunca tendrás que preocuparte de usar declaraciones ``include`` o ``require``. En cambio, ``Composer`` utiliza el espacio de nombres de una clase para determinar su ubicación e incluir automáticamente el archivo en el instante en que necesitas una clase. - - El cargador automático ya está configurado para buscar cualquiera de tus clases *PHP* en el directorio :file:`src/`. Para que trabaje la carga automática, el nombre de la clase y la ruta del archivo deben seguir el mismo patrón: - - .. code-block:: text - - Class Name: - Acme\HelloBundle\Controller\HelloController - Path: - src/Acme/HelloBundle/Controller/HelloController.php - -El directorio fuente (``src``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En pocas palabras, el directorio :file:`src/` contiene todo el código real (código *PHP*, plantillas, archivos de configuración, estilo, etc.) que impulsa a *tu* aplicación. -De hecho, cuando desarrollas, la gran mayoría de tu trabajo se llevará a cabo dentro de uno o más paquetes creados en este directorio. - -Pero, ¿qué es exactamente un :term:`paquete`? - -.. _page-creation-bundles: - -El sistema de paquetes ----------------------- - -Un paquete es similar a un complemento en otro software, pero aún mejor. La diferencia clave es que en *Symfony2* **todo** es un paquete, incluyendo tanto la funcionalidad básica de la plataforma como el código escrito para tu aplicación. -Los paquetes son ciudadanos de primera clase en *Symfony2*. Esto te proporciona la flexibilidad para utilizar las características preconstruidas envasadas en `paquetes de terceros`_ o para distribuir tus propios paquetes. Además, facilita la selección y elección de las características por habilitar en tu aplicación y optimizarlas en la forma que desees. - -.. note:: - - Si bien, aquí vamos a cubrir lo básico, hay un capítulo dedicado completamente al tema de los :doc:`paquetes `. - -Un paquete simplemente es un conjunto estructurado de archivos en un directorio que implementa una sola característica. Puedes crear un ``BlogBundle``, un ``ForoBundle`` o un paquete para gestionar usuarios (muchos de ellos ya existen como paquetes de código abierto). Cada directorio contiene todo lo relacionado con esa característica, incluyendo archivos *PHP*, plantillas, hojas de estilo, archivos *Javascript*, pruebas y cualquier otra cosa necesaria. -Cada aspecto de una característica existe en un paquete y cada característica vive en un paquete. - -Una aplicación se compone de paquetes tal como está definido en el método ``registerBundles()`` de la clase ``AppKernel``:: - - // app/AppKernel.php - public function registerBundles() - { - $bundles = array( - new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), - new Symfony\Bundle\SecurityBundle\SecurityBundle(), - new Symfony\Bundle\TwigBundle\TwigBundle(), - new Symfony\Bundle\MonologBundle\MonologBundle(), - new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), - new Symfony\Bundle\DoctrineBundle\DoctrineBundle(), - new Symfony\Bundle\AsseticBundle\AsseticBundle(), - new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), - new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(), - ); - - if (in_array($this->getEnvironment(), array('dev', 'test'))) { - $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); - $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); - $bundles[] = new Sensio\Bundle\DistributionBundle\SensioDistributionBundle(); - $bundles[] = new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(); - } - - return $bundles; - } - -Con el método ``registerBundles()``, tienes el control total sobre cuales paquetes utiliza tu aplicación (incluyendo los paquetes del núcleo de *Symfony*). - -.. tip:: - - Un paquete puede vivir en *cualquier lugar* siempre y cuando *Symfony2* lo pueda cargar automáticamente (vía el autocargador configurado en ``app/autoload.php``). - - -Creando un paquete -~~~~~~~~~~~~~~~~~~ - -La *edición estándar de Symfony* viene con una práctica tarea que crea un paquete totalmente operativo para ti. Por supuesto, la creación manual de un paquete también es muy fácil. - -Para mostrarte lo sencillo que es el sistema de paquetes, vamos a crear y activar un nuevo paquete llamado ``AcmeTestBundle``. - -.. tip:: - - La parte ``Acme`` es sólo un nombre ficticio que debes sustituir por un «proveedor» que represente tu nombre u organización (por ejemplo, ``ABCTestBundle`` por alguna empresa llamada ``ABC``). - -En primer lugar, crea un directorio :file:`src/Acme/TestBundle/` y añade un nuevo archivo llamado :file:`AcmeTestBundle.php`:: - - // src/Acme/TestBundle/AcmeTestBundle.php - namespace Acme\TestBundle; - - use Symfony\Component\HttpKernel\Bundle\Bundle; - - class AcmeTestBundle extends Bundle - { - } - -.. tip:: - - El nombre ``AcmeTestBundle`` sigue las :ref:`convenciones de nomenclatura de paquetes ` estándar. - También puedes optar por acortar el nombre del paquete simplemente a ``TestBundle`` al nombrar esta clase ``TestBundle`` (y el nombre del archivo :file:`TestBundle.php`). - -Esta clase vacía es la única pieza que necesitamos para crear nuestro nuevo paquete. Aunque comúnmente está vacía, esta clase es poderosa y se puede utilizar para personalizar el comportamiento del paquete. - -Ahora que creaste tu paquete, tienes que activarlo a través de la clase ``AppKernel``:: - - // app/AppKernel.php - public function registerBundles() - { - $bundles = array( - ..., - // registra tus paquetes - new Acme\TestBundle\AcmeTestBundle(), - ); - // ... - - return $bundles; - } - -Y si bien ``AcmeTestBundle`` aún no hace nada, está listo para utilizarlo. - -Y aunque esto es bastante fácil, *Symfony* también proporciona una interfaz de línea de ordenes para generar una estructura de paquete básica: - -.. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/TestBundle - -Esto genera el esqueleto del paquete con un controlador básico, la plantilla y recursos de enrutado que se pueden personalizar. Aprenderás más sobre la línea de ordenes de las herramientas de *Symfony2* más tarde. - -.. tip:: - - Cuando quieras crear un nuevo paquete o uses un paquete de terceros, siempre asegúrate de habilitar el paquete en ``registerBundles()``. Cuando usas la orden ``generate:bundle``, hace esto para ti. - -Estructura de directorios de un paquete -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La estructura de directorios de un paquete es simple y flexible. De forma predeterminada, el sistema de paquetes sigue una serie de convenciones que ayudan a mantener el código consistente entre todos los paquetes *Symfony2*. Echa un vistazo a ``AcmeHelloBundle``, ya que contiene algunos de los elementos más comunes de un paquete: - -* :file:`Controller/` Contiene los controladores del paquete (por ejemplo, :file:`HelloController.php`); - -* ``DependencyInjection/`` mantiene ciertas extensiones para las clases de inyección de dependencias, qué configuración puede importar el servicio, registra uno o más pases del compilador (este directorio no es necesario); - -* :file:`Resources/config/` Contiene la configuración, incluyendo la configuración de enrutado (por ejemplo, :file:`routing.yml`); - -* ``Resources/views/`` Contiene las plantillas organizadas por nombre de controlador (por ejemplo, ``Hello/index.html.twig``); - -* ``Resources/public/`` Contiene recursos *web* (imágenes, hojas de estilo, etc.) y es copiado o enlazado simbólicamente al directorio ``web/`` del proyecto vía la orden de consola ``assets:install``; - -* ``Tests/`` Tiene todas las pruebas para el paquete. - -Un paquete puede ser tan pequeño o tan grande como la característica que implementa. Este contiene sólo los archivos que necesita y nada más. - -A medida que avances en el libro, aprenderás cómo persistir objetos a una base de datos, crear y validar formularios, crear traducciones para tu aplicación, escribir pruebas y mucho más. Cada uno de estos tiene su propio lugar y rol dentro del paquete. - -Configurando la aplicación --------------------------- - -La aplicación consiste de una colección de paquetes que representan todas las características y capacidades de tu aplicación. Cada paquete se puede personalizar a través de archivos de configuración escritos en *YAML*, *XML* o *PHP*. De forma predeterminada, el archivo de configuración principal vive en el directorio :file:`app/config/` y se llama :file:`config.yml`, :file:`config.xml` o :file:`config.php` en función del formato que prefieras: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - imports: - - { resource: parameters.yml } - - { resource: security.yml } - - framework: - secret: "%secret%" - router: { resource: "%kernel.root_dir%/config/routing.yml" } - # ... - - # Twig Configuration - twig: - debug: "%kernel.debug%" - strict_variables: "%kernel.debug%" - - # ... - - .. code-block:: xml - - - - - - - - - - - - - - - - - - .. code-block:: php - - $this->import('parameters.yml'); - $this->import('security.yml'); - - $container->loadFromExtension('framework', array( - 'secret' => '%secret%', - 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'), - // ... - ), - )); - - // Configuración Twig - $container->loadFromExtension('twig', array( - 'debug' => '%kernel.debug%', - 'strict_variables' => '%kernel.debug%', - )); - - // ... - -.. note:: - - Aprenderás exactamente cómo cargar cada archivo/formato en la siguiente sección, `Entornos`_. - -Cada entrada de nivel superior como ``framework`` o ``twig`` define la configuración de un paquete específico. Por ejemplo, la clave ``framework`` define la configuración para el núcleo de *Symfony* ``FrameworkBundle`` e incluye la configuración de enrutado, plantillas, y otros sistemas del núcleo. - -Por ahora, no te preocupes por las opciones de configuración específicas de cada sección. -El archivo de configuración viene con parámetros predeterminados. A medida que leas y explores más cada parte de *Symfony2*, aprenderás sobre las opciones de configuración específicas de cada característica. - -.. sidebar:: Formatos de configuración - - A lo largo de los capítulos, todos los ejemplos de configuración muestran los tres formatos (*YAML*, *XML* y *PHP*). Cada uno tiene sus propias ventajas y desventajas. Tú eliges cual utilizar: - - * *YAML*: Sencillo, limpio y legible (aprende más sobre *yaml* en «:doc:`/components/yaml/yaml_format`»); - - * *XML*: Más poderoso que *YAML* a veces y es compatible con el autocompletado del *IDE*; - - * *PHP*: Muy potente, pero menos fácil de leer que los formatos de configuración estándar. - -Volcando la configuración predefinida -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - La orden ``config:dump-reference`` se añadió en *Symfony 2.1* - -Puedes volcar a la consola la configuración predefinida en *YAML* de un paquete usando la orden ``config:dump-reference``. He aquí un ejemplo del volcado de la configuración predefinida del ``FrameworkBundle``: - -.. code-block:: text - - app/console config:dump-reference FrameworkBundle - -También puedes usar el alias de la extensión (la ``clave`` en el archivo de configuración): - -.. code-block:: text - - app/console config:dump-reference framework - -.. note:: - - Revisa el artículo del recetario: :doc:`Cómo exponer la configuración semántica de un paquete ` para información sobre cómo añadir configuración a tus propios paquetes. - -.. index:: - single: Entornos; Introducción - -.. _environments-summary: - -Entornos --------- - -Una aplicación puede funcionar en diversos entornos. Los diferentes entornos comparten el mismo código *PHP* (aparte del controlador frontal), pero usan diferente configuración. Por ejemplo, un entorno de desarrollo ``dev`` registrará las advertencias y errores, mientras que un entorno de producción ``prod`` sólo registra los errores. Algunos archivos se vuelven a generar en cada petición en el entorno ``dev`` (para mayor comodidad de los desarrolladores), pero se memorizan en caché en el entorno ``prod``. Todos los entornos viven juntos en la misma máquina y ejecutan la misma aplicación. - -Un proyecto *Symfony2* generalmente comienza con tres entornos (``dev``, ``test`` y ``prod``), aunque la creación de nuevos entornos es fácil. Puedes ver tu aplicación en diferentes entornos con sólo cambiar el controlador frontal en tu navegador. Para ver la aplicación en el entorno ``dev``, accede a la aplicación a través del controlador frontal de desarrollo: - -.. code-block:: text - - http://localhost/app_dev.php/hello/Ryan - -Si deseas ver cómo se comportará tu aplicación en el entorno de producción, en su lugar, llama al controlador frontal ``prod``: - -.. code-block:: text - - http://localhost/app.php/hello/Ryan - -Puesto que el entorno ``prod`` está optimizado para velocidad; la configuración, el enrutado y las plantillas *Twig* se compilan en clases *PHP* simples y se guardan en caché. -Cuando veas los cambios en el entorno ``prod``, tendrás que borrar estos archivos memorizados en caché y así permitir su reconstrucción: - -.. code-block:: bash - - $ php app/console cache:clear --env=prod --no-debug - -.. note:: - - Si abres el archivo ``web/app.php``, encontrarás que está configurado explícitamente para usar el entorno ``prod``:: - - $kernel = new AppKernel('prod', false); - - Puedes crear un nuevo controlador frontal para un nuevo entorno copiando el archivo y cambiando ``prod`` por algún otro valor. - -.. note:: - - El entorno ``test`` se utiliza cuando se ejecutan pruebas automáticas y no se puede acceder directamente a través del navegador. Consulta el capítulo :doc:`Probando ` para más detalles. - -.. index:: - single: Entornos; Configuración - -Configurando entornos -~~~~~~~~~~~~~~~~~~~~~ - -La clase ``AppKernel`` es responsable de cargar realmente el archivo de configuración de tu elección:: - - // app/AppKernel.php - public function registerContainerConfiguration(LoaderInterface $loader) - { - $loader->load( - __DIR__.'/config/config_'.$this->getEnvironment().'.yml' - ); - } - -Ya sabes que la extensión :file:`.yml` se puede cambiar a :file:`.xml` o :file:`.php` si prefieres usar *XML* o *PHP* para escribir tu configuración. -Además, observa que cada entorno carga su propio archivo de configuración. Considera el archivo de configuración para el entorno ``dev``. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - imports: - - { resource: config.yml } - - framework: - router: { resource: "%kernel.root_dir%/config/routing_dev.yml" } - profiler: { only_exceptions: false } - - # ... - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $loader->import('config.php'); - - $container->loadFromExtension('framework', array( - 'router' => array('resource' => '%kernel.root_dir%/config/routing_dev.php'), - 'profiler' => array('only-exceptions' => false), - )); - - // ... - -La clave ``imports`` es similar a una declaración ``include`` *PHP* y garantiza que en primer lugar se carga el archivo de configuración principal (:file:`config.yml`). El resto del archivo de configuración predeterminado aumenta el registro en la bitácora de eventos y otros ajustes conducentes a un entorno de desarrollo. - -Ambos entornos ``prod`` y ``test`` siguen el mismo modelo: cada uno importa el archivo de configuración básico y luego modifica sus valores de configuración para adaptarlos a las necesidades específicas del entorno. Esto es sólo una convención, pero te permite reutilizar la mayor parte de tu configuración y personalizar sólo piezas puntuales entre entornos. - -Resumen -------- - -¡Enhorabuena! Ahora has visto todos los aspectos fundamentales de *Symfony2* y afortunadamente descubriste lo fácil y flexible que puede ser. Y si bien aún *hay muchas* características por venir, asegúrate de tener en cuenta los siguientes puntos básicos: - -* La creación de una página es un proceso de tres pasos que involucran una **ruta**, un **controlador** y (opcionalmente) una **plantilla**; - -* Cada proyecto sólo contiene unos cuantos directorios principales: :file:`web/` (recursos web y controladores frontales), :file:`app/` (configuración), :file:`src/` (tus paquetes), y :file:`vendor/` (código de terceros) (también hay un directorio :file:`bin/` que se utiliza para ayudarte a actualizar las bibliotecas de proveedores); - -* Cada característica en *Symfony2* (incluyendo el núcleo de la plataforma *Symfony2*) está organizada en un *paquete*, el cual es un conjunto estructurado de archivos para esa característica; - -* La **configuración** de cada paquete vive en el directorio ``Resources/config`` del paquete y se puede especificar en *YAML*, *XML* o *PHP*; - -* La **configuración global de la aplicación** vive en el directorio ``app/config/``; - -* Cada **entorno** es accesible a través de un diferente controlador frontal (por ejemplo, :file:`app.php` y :file:`app_dev.php`) el cual carga un archivo de configuración diferente. - -A partir de aquí, cada capítulo te dará a conocer más y más potentes herramientas y conceptos avanzados. Cuanto más sepas sobre *Symfony2*, tanto más apreciarás la flexibilidad de su arquitectura y el poder que te proporciona para desarrollar aplicaciones rápidamente. - -.. _`Twig`: http://gitnacho.github.com/symfony-docs-es/twig/index.html -.. _`paquetes de terceros`: http://knpbundles.com -.. _`edición estándar de Symfony`: http://symfony.com/download -.. _`Apache sobre DirectoryIndex`: http://httpd.apache.org/docs/2.0/mod/mod_dir.html -.. _`ubicación HttpCoreModule de Nginx`: http://wiki.nginx.org/HttpCoreModule#location diff --git a/_sources/book/performance.txt b/_sources/book/performance.txt deleted file mode 100644 index 4d0a8cd..0000000 --- a/_sources/book/performance.txt +++ /dev/null @@ -1,102 +0,0 @@ -.. index:: - single: Pruebas - -Rendimiento -=========== - -*Symfony2* es rápido, desde que lo sacas de la caja. Por supuesto, si realmente necesitas velocidad, hay muchas maneras en las cuales puedes hacer que *Symfony* sea aún más rápido. En este capítulo, podrás explorar muchas de las formas más comunes y potentes para hacer que tu aplicación *Symfony* sea aún más rápida. - -.. index:: - single: Rendimiento; Caché de código de bytes - -Utilizando una caché de código de bytes (p. ej. *APC*) ------------------------------------------------------- - -Una de las mejores (y más fáciles) cosas que debes hacer para mejorar el rendimiento es utilizar una «caché de código de bytes». La idea de una caché de código de bytes es eliminar la necesidad de constantemente tener que volver a compilar el código fuente *PHP*. Hay disponible una serie de `cachés de código de bytes`_, algunas de las cuales son de código abierto. Probablemente, la caché de código de bytes más utilizada sea `APC`_ - -Usar una caché de código de bytes realmente no tiene ningún inconveniente, y *Symfony2* se ha diseñado para desempeñarse muy bien en este tipo de entorno. - -Optimización adicional -~~~~~~~~~~~~~~~~~~~~~~ - -La caché de código de bytes, por lo general, comprueba los cambios de los archivos fuente. Esto garantiza que si cambias un archivo fuente, el código de bytes se vuelve a compilar automáticamente. -Esto es muy conveniente, pero, obviamente, implica una sobrecarga. - -Por esta razón, algunas cachés de código de bytes ofrecen una opción para desactivar esa comprobación. -Obviamente, cuando desactivas esta comprobación, será responsabilidad del administrador del servidor asegurarse de que la caché se borra cada vez que cambia un archivo fuente. De lo contrario, no se verán los cambios realizados. - -Por ejemplo, para desactivar estos controles en *APC*, sólo tienes que añadir la opción ``apc.stat=0`` en tu archivo de configuración :file:`php.ini`. - -.. index:: - single: Rendimiento; Autocargador - -Usa la funcionalidad de asociación de clases de ``Composer`` ------------------------------------------------------------- - -De manera predeterminada, la *edición estándar de Symfony2* utiliza el autocargador en el archivo `autoload.php`_. Este autocargador es fácil de usar, ya que automáticamente encontrará cualquier nueva clase que hayas colocado en los directorios registrados. - -Desafortunadamente, esto tiene un costo, puesto que el cargador itera en todos los espacios de nombres configurados para encontrar un archivo, haciendo llamadas a ``file_exists`` hasta que finalmente encuentra el archivo que estás buscando. - -La solución más sencilla es decirle a ``Composer`` que construya un «mapa de clases» (es decir, un gran arreglo con la ubicación de todas las clases). Esto se puede hacer desde la línea de ordenes, y se podría convertir en parte de tu proceso de despliegue: - -.. code-block:: bash - - php composer.phar dump-autoload --optimize - -Internamente, esto construye el gran arreglo con el mapa de clases en ``vendor/composer/autoload_namespaces.php``. - -Memorizando en caché el autocargador con *APC* ----------------------------------------------- - -Otra solución es memorizar en caché la ubicación de cada clase después de localizarla -por primera vez. *Symfony* viene con una clase ---:class:`Symfony\\Component\\ClassLoader\\ApcClassLoader`--- que hace eso exactamente. Para usarla, sólo adapta tu archivo del controlador frontal. -Si estás utilizando la distribución estándar, este código ya debe estar disponible como comentarios en este archivo:: - - // app.php - // ... - - $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; - - // Usa APC para mejorar el rendimiento de la carga automática - // Cambia 'sf2' por el prefijo que desees a fin de evitar - // conflictos de clave con otra aplicación. - /* - $loader = new ApcClassLoader('sf2', $loader); - $loader->register(true); - */ - - // ... - -.. note:: - - Al utilizar el cargador automático *APC*, si agregas nuevas clases, las encontrará automáticamente y todo funcionará igual que antes (es decir, no hay razón para «limpiar» la caché). Sin embargo, si cambias la ubicación de un determinado espacio de nombres o prefijo, tendrás que limpiar tu caché *APC*. De lo contrario, el cargador aún buscará en la ubicación anterior todas las clases dentro de ese espacio de nombres. - -.. index:: - single: Rendimiento; Archivos de arranque - -Utilizando archivos de arranque -------------------------------- - -Para garantizar una óptima flexibilidad y reutilización de código, las aplicaciones de *Symfony2* aprovechan una variedad de clases y componentes de terceros. Pero cargar todas estas clases desde archivos separados en cada petición puede dar lugar a alguna sobrecarga. Para reducir esta sobrecarga, la *edición estándar de Symfony2* proporciona un guión para generar lo que se conoce como `archivo de arranque`_, el cual contiene la definición de múltiples clases en un solo archivo. Al incluir este archivo (el cual contiene una copia de muchas de las clases del núcleo), *Symfony* ya no tiene que incluir algunos de los archivos de código fuente que contienen las clases. Esto reducirá bastante la E/S del disco. - -Si estás utilizando la *edición estándar de Symfony2*, entonces probablemente ya estás utilizando el archivo de arranque. Para estar seguro, abre el controlador frontal (por lo general :file:`app.php`) y asegúrate de que existe la siguiente línea:: - - require_once __DIR__.'/../app/bootstrap.php.cache'; - -Ten en cuenta que hay dos desventajas cuando utilizas un archivo de arranque: - -* El archivo se tiene que regenerar cada vez que cambia alguna de las fuentes original (es decir, cuando actualizas el código fuente de *Symfony2* o las bibliotecas de proveedores); - -* En la depuración, será necesario colocar puntos de interrupción dentro del archivo de arranque. - -Si estás utilizando la *edición estándar de Symfony2*, los archivos de arranque se reconstruyen automáticamente después de actualizar las bibliotecas de proveedores a través de la orden ``php composer.phar install``. - -Archivos de arranque y caché de código de bytes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Incluso cuando utilizas código de bytes en caché, el rendimiento mejorará cuando utilices un archivo de arranque ya que habrá menos archivos en los cuales supervisar cambios. Por supuesto, si esta función está desactivada en la caché del código de bytes (por ejemplo, ``apc.stat = 0`` en *APC*), no existe una razón para utilizar un archivo de arranque. - -.. _`cachés de código de bytes`: http://en.wikipedia.org/wiki/List_of_PHP_accelerators -.. _`APC`: http://www.php.net/manual/es/book.apc.php -.. _`autoload.php`: https://github.com/symfony/symfony-standard/blob/master/app/autoload.php -.. _`archivo de arranque`: https://github.com/sensio/SensioDistributionBundle/blob/master/Composer/ScriptHandler.php diff --git a/_sources/book/propel.txt b/_sources/book/propel.txt deleted file mode 100644 index 9378732..0000000 --- a/_sources/book/propel.txt +++ /dev/null @@ -1,408 +0,0 @@ -.. index:: - single: Propel - -Bases de datos y *Propel* -========================= - -Seamos realistas, una de las tareas más comunes y desafiantes para cualquier aplicación involucra la persistencia y lectura de información hacia y desde una base de datos. *Symfony2* no viene integrado con ningún *ORM*, pero integrar *Propel* es relativamente fácil. -Para instalar *Propel*, lee `Trabajando Con Symfony2`_ en la documentación de *Propel*. - -Un sencillo ejemplo: Un producto --------------------------------- - -En esta sección, configurarás tu base de datos, crearás un objeto ``Producto``, lo persistirás en la base de datos y lo recuperarás de nuevo. - -.. sidebar:: El código del ejemplo - - Si quieres seguir el ejemplo de este capítulo, crea el paquete ``AcmeStoreBundle`` ejecutando la orden: - - .. code-block:: bash - - $ php app/console generate:bundle --namespace=Acme/StoreBundle - -Configurando la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de poder comenzar realmente, tendrás que establecer tu información para la conexión a la base de datos. Por convención, esta información se suele configurar en el archivo -:file:`app/config/parameters.yml`: - -.. code-block:: yaml - - # app/config/parameters.yml - parameters: - database_driver: mysql - database_host: localhost - database_name: proyecto_de_prueba - database_user: nombre_de_usuario - database_password: password - database_charset: UTF8 - -.. note:: - - Definir la configuración a través de :file:`parameters.yml` sólo es una convención. Los parámetros definidos en este archivo son referidos en el archivo de configuración principal al ajustar *Propel*: - -Estos parámetros definidos en ``parameters.yml`` ahora se pueden incluir en el archivo de configuración (:file:`config.yml`): - -.. code-block:: yaml - - propel: - dbal: - driver: "%database_driver%" - user: "%database_user%" - password: "%database_password%" - dsn: "%database_driver%:host=%database_host%;dbname=%database_name%;charset=%database_charset%" - -Ahora que *Propel* está consciente de tu base de datos, posiblemente tenga que crear la base de datos para ti: - -.. code-block:: bash - - $ php app/console propel:database:create - -.. note:: - - En este ejemplo, tienes configurada una conexión, denominada ``default``. Si quieres configurar más de una conexión, consulta la `sección de configuración del PropelBundle`_. - -Creando una clase ``Modelo`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En el mundo de *Propel*, las clases ``ActiveRecord`` son conocidas como **modelos** debido a que las clases generadas por *Propel* contienen alguna lógica del negocio. - -.. note:: - - Para la gente que usa *Symfony2* with *Doctrine2*, los **modelos** son equivalentes a **entidades**. - -Supongamos que estás construyendo una aplicación donde necesitas mostrar tus productos. -Primero, crea un archivo :file:`schema.xml` en el directorio ``Resources/config`` de tu paquete ``AcmeStoreBundle``: - -.. code-block:: xml - - - - - - - - -
-
- -Construyendo el modelo -~~~~~~~~~~~~~~~~~~~~~~ - -Después de crear tu archivo ``schema.xml``, genera tu modelo ejecutando: - -.. code-block:: bash - - $ php app/console propel:model:build - -Esto genera todas las clases del modelo en el directorio ``Model/`` del paquete ``AcmeStoreBundle`` para que desarrolles rápidamente tu aplicación. - -Creando tablas/esquema de la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora tienes una clase ``Producto`` utilizable con todo lo que necesitas para persistirla. Por supuesto, en tu base de datos aún no tienes la tabla ``producto`` correspondiente. Afortunadamente, *Propel* puede crear automáticamente todas las tablas de la base de datos necesarias para cada modelo conocido en tu aplicación. Para ello, ejecuta: - -.. code-block:: bash - - $ php app/console propel:sql:build - $ php app/console propel:sql:insert --force - -Tu base de datos ahora cuenta con una tabla ``producto`` completamente operativa, con columnas que coinciden con el esquema que has especificado. - -.. tip:: - - Puedes combinar las tres últimas ordenes ejecutando la siguiente orden: ``php app/console propel:build --insert-sql``. - -Persistiendo objetos a la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora que tienes un objeto ``Producto`` y la tabla ``producto`` correspondiente, estás listo para persistir la información a la base de datos. Desde el interior de un controlador, esto es bastante fácil. Agrega el siguiente método al ``DefaultController`` del paquete:: - - // src/Acme/StoreBundle/Controller/DefaultController.php - - // ... - use Acme\StoreBundle\Model\Product; - use Symfony\Component\HttpFoundation\Response; - - public function createAction() - { - $product = new Product(); - $product->setName('A Foo Bar'); - $product->setPrice(19.99); - $product->setDescription('Lorem ipsum dolor'); - - $product->save(); - - return new Response('Created product id '.$product->getId()); - } - -En esta pieza de código, creas y trabajas con una instancia del objeto ``$product``. -Al invocar al método ``save()``, la persistes a la base de datos. No tienes que usar otros servicios, el objeto sabe cómo persistirse a sí mismo. - -.. note:: - - Si estás siguiendo este ejemplo, necesitas crear una :doc:`ruta ` que apunte a este método para verlo en acción. - -Recuperando objetos desde la base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Recuperar un objeto desde la base de datos es aún más fácil. Por ejemplo, supongamos que has configurado una ruta para mostrar un ``Producto`` específico en función del valor de su ``id``:: - - // ... - use Acme\StoreBundle\Model\ProductQuery; - - public function showAction($id) - { - $product = ProductQuery::create() - ->findPk($id); - - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } - - // ... haz algo, como pasar el objeto $product a una plantilla - } - -Actualizando un objeto -~~~~~~~~~~~~~~~~~~~~~~ - -Una vez que hayas recuperado un objeto de *Propel*, actualizarlo es relativamente fácil. Supongamos que tienes una ruta que asigna un identificador de producto a una acción de actualización en un controlador:: - - // ... - use Acme\StoreBundle\Model\ProductQuery; - - public function updateAction($id) - { - $product = ProductQuery::create() - ->findPk($id); - - if (!$product) { - throw $this->createNotFoundException( - 'No product found for id '.$id - ); - } - - $product->setName('New product name!'); - $product->save(); - - return $this->redirect($this->generateUrl('homepage')); - } - -La actualización de un objeto únicamente consta de tres pasos: - -#. recuperar el objeto desde Propel (línea 6 - 13); -#. modificar el objeto (línea 15); -#. guardarlo (línea 16). - -Eliminando un objeto -~~~~~~~~~~~~~~~~~~~~ - -Eliminar un objeto es muy similar, pero requiere una llamada al método ``delete()`` del objeto:: - - $product->delete(); - -Consultando por objetos ------------------------ - -*Propel* provee clases ``Query`` generadas para ejecutar ambas consultas, básicas y complejas sin mayor esfuerzo:: - - \Acme\StoreBundle\Model\ProductQuery::create()->findPk($id); - - \Acme\StoreBundle\Model\ProductQuery::create() - ->filterByName('Foo') - ->findOne(); - -Imagina que deseas consultar los productos, pero sólo quieres devolver aquellos que cuestan más de ``19.99``, ordenados del más barato al más caro. Desde el interior de un controlador, haz lo siguiente:: - - $products = \Acme\StoreBundle\Model\ProductQuery::create() - ->filterByPrice(array('min' => 19.99)) - ->orderByPrice() - ->find(); - -En una línea, recuperas tus productos en una potente manera orientada a objetos. No necesitas gastar tu tiempo con *SQL* o ninguna otra cosa, *Symfony2* ofrece programación completamente orientada a objetos y *Propel* respeta la misma filosofía proveyendo una impresionante capa de abstracción. - -Si quieres reutilizar algunas consultas, puedes añadir tus propios métodos a la clase ``ProductQuery``:: - - // src/Acme/StoreBundle/Model/ProductQuery.php - class ProductQuery extends BaseProductQuery - { - public function filterByExpensivePrice() - { - return $this - ->filterByPrice(array('min' => 1000)); - } - } - -Pero, ten en cuenta que *Propel* genera una serie de métodos por ti y puedes escribir un sencillo ``findAllOrderedByName()`` sin ningún esfuerzo:: - - \Acme\StoreBundle\Model\ProductQuery::create() - ->orderByName() - ->find(); - -Relaciones/Asociaciones ------------------------ - -Supón que los productos en tu aplicación pertenecen exactamente a una «categoría». En este caso, necesitarás un objeto ``Categoría`` y una manera de relacionar un objeto ``Producto`` a un objeto ``Categoría``. - -Comienza agregando la definición de ``categoría`` en tu archivo :file:`schema.xml`: - -.. code-block:: xml - - - - - - - - - - - - -
- - - - -
-
- -Crea las clases: - -.. code-block:: bash - - $ php app/console propel:model:build - -Asumiendo que tienes productos en tu base de datos, no los querrás perder. Gracias a las migraciones, *Propel* es capaz de actualizar tu base de datos sin perder la información existente. - -.. code-block:: bash - - $ php app/console propel:migration:generate-diff - $ php app/console propel:migration:migrate - -Tu base de datos se ha actualizado, puedes continuar escribiendo tu aplicación. - -Guardando objetos relacionados -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora, veamos el código en acción. Imagina que estás dentro de un controlador:: - - // ... - use Acme\StoreBundle\Model\Category; - use Acme\StoreBundle\Model\Product; - use Symfony\Component\HttpFoundation\Response; - - class DefaultController extends Controller - { - public function createProductAction() - { - $category = new Category(); - $category->setName('Main Products'); - - $product = new Product(); - $product->setName('Foo'); - $product->setPrice(19.99); - // relaciona este producto a la categoría - $product->setCategory($category); - - // guarda todo - $product->save(); - - return new Response( - 'Created product id: '.$product->getId().' and category id: '.$category->getId() - ); - } - } - -Ahora, se agrega una sola fila en ambas tablas ``categoría`` y ``producto``. La columna ``product.category_id`` para el nuevo producto se ajusta al ``id`` de la nueva categoría. *Propel* maneja la persistencia de las relaciones por ti. - -Recuperando objetos relacionados -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando necesites recuperar objetos asociados, tu flujo de trabajo se ve justo como lo hacías antes. En primer lugar, buscas un objeto ``$product`` y luego accedes a su ``Categoría`` asociada:: - - // ... - use Acme\StoreBundle\Model\ProductQuery; - - public function showAction($id) - { - $product = ProductQuery::create() - ->joinWithCategory() - ->findPk($id); - - $categoryName = $product->getCategory()->getName(); - - // ... - } - -Ten en cuenta que en el ejemplo anterior, únicamente hicimos una consulta. - -Más información sobre asociaciones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Encontrarás más información sobre las relaciones leyendo el capítulo dedicado a las -`relaciones`_. - -Ciclo de vida de las retrollamadas ----------------------------------- - -A veces, es necesario realizar una acción justo antes o después de insertar, actualizar o eliminar un objeto. Este tipo de acciones se conoce como «ciclo de vida» de las retrollamadas o :dfn:`hooks` (en adelante «ganchos»), ya que son métodos retrollamados que necesitas ejecutar durante las diferentes etapas del ciclo de vida de un objeto (por ejemplo, cuando insertas, actualizas, eliminas, etc., un objeto) - -Para añadir un gancho, solo tenemos que añadir un método a la clase del objeto:: - - // src/Acme/StoreBundle/Model/Product.php - - // ... - class Product extends BaseProduct - { - public function preInsert(\PropelPDO $con = null) - { - // hace algo antes de insertar el objeto - } - } - -*Propel* ofrece los siguientes ganchos: - -* ``preInsert()`` código ejecutado antes de insertar un nuevo objeto -* ``postInsert()`` código ejecutado después de insertar un nuevo objeto -* ``preUpdate()`` código ejecutado antes de actualizar un objeto existente -* ``postUpdate()`` código ejecutado después de actualizar un objeto existente -* ``preSave()`` código ejecutado antes de guardar un objeto (nuevo o existente) -* ``postSave()`` código ejecutado después de guardar un objeto (nuevo o existente) -* ``preDelete()`` código ejecutado antes de borrar un objeto -* ``postDelete()`` código ejecutado después de borrar un objeto - - -Comportamientos ---------------- - -Todo los comportamientos empacados en *Propel* trabajan con *Symfony2*. Para conseguir más información sobre cómo utiliza *Propel* los comportamientos, consulta la sección de `referencia de comportamientos`_. - -Ordenes -------- - -Debes leer la sección dedicada a las `Ordenes de Propel en Symfony2`_. - -.. _`Trabajando con Symfony2`: http://propelorm.org/cookbook/symfony2/working-with-symfony2.html#installation -.. _`sección de configuración del PropelBundle`: http://propelorm.org/cookbook/symfony2/working-with-symfony2.html#configuration -.. _`relaciones`: http://propelorm.org/documentation/04-relationships.html -.. _`referencia de comportamientos`: http://propelorm.org/documentation/#behaviors_reference -.. _`Ordenes de Propel en Symfony2`: http://propelorm.org/cookbook/symfony2/working-with-symfony2#the_commands diff --git a/_sources/book/routing.txt b/_sources/book/routing.txt deleted file mode 100644 index 1cb2cfe..0000000 --- a/_sources/book/routing.txt +++ /dev/null @@ -1,1089 +0,0 @@ -.. index:: - single: Routing - -Enrutando -========= - -Las *URL* bonitas absolutamente son una necesidad para cualquier aplicación web seria. Esto significa dejar atrás las *URL* feas como ``index.php?article_id=57`` en favor de algo así como ``/leer/intro-a-symfony``. - -Tener tal flexibilidad es más importante aún. ¿Qué pasa si necesitas cambiar la *URL* de una página de ``/blog`` a ``/noticias``? ¿Cuántos enlaces necesitas cazar y actualizar para hacer el cambio? Si estás utilizando el enrutador de *Symfony*, el cambio es sencillo. - -El enrutador de *Symfony2* te permite definir *URL* creativas que se asignan a diferentes áreas de la aplicación. Al final de este capítulo, serás capaz de: - -* Crear rutas complejas asignadas a controladores -* Generar *URL* que contienen plantillas y controladores -* Cargar recursos de enrutado desde paquetes (o de cualquier otro lugar) -* Depurar tus rutas - -.. index:: - single: Enrutando; Fundamentos - -Enrutador en acción -------------------- - -Una ``ruta`` es un mapa desde la trayectoria de una *URL* hasta un controlador. Por ejemplo, supongamos que deseas adaptar cualquier *URL* como ``/blog/mi-post`` o ``/blog/todo-sobre-symfony`` y enviarla a un controlador que puede buscar y reproducir esta entrada del *blog*. -La ruta es simple: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - blog_show: - path: /blog/{slug} - defaults: { _controller: AcmeBlogBundle:Blog:show } - - .. code-block:: xml - - - - - - - AcmeBlogBundle:Blog:show - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog_show', new Route('/blog/{slug}', array( - '_controller' => 'AcmeBlogBundle:Blog:show', - ))); - - return $collection; - -.. versionadded:: 2.2 - La opción ``path`` es nueva en *Symfony* 2.2, en versiones anteriores se utilizaba ``pattern``. - -La ruta definida por ``blog_show`` actúa como ``/blog/*`` donde al comodín se le da el nombre de ``slug``. Para la *URL* ``/blog/my-blog-post``, la variable ``slug`` obtiene un valor de ``my-blog-post``, que está disponible para usarlo en el controlador (sigue leyendo). - -El parámetro ``_controller`` es una clave especial que le dice a *Symfony* qué controlador se debe ejecutar cuando una *URL* coincida con esta ruta. La cadena ``_controller`` se conoce como el :ref:`nombre lógico `. Esta sigue un patrón que apunta a una clase y método *PHP* específicos:: - - // src/Acme/BlogBundle/Controller/BlogController.php - namespace Acme\BlogBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class BlogController extends Controller - { - public function showAction($slug) - { - // usa la variable $slug para consultar la base de datos - $blog = ...; - - return $this->render('AcmeBlogBundle:Blog:show.html.twig', array( - 'blog' => $blog, - )); - } - } - -¡Enhorabuena! Acabas de crear tu primera ruta y la conectaste a un controlador. Ahora, cuando visites ``/blog/my-post``, el controlador ``showAction`` será ejecutado y la variable ``$slug`` será igual a ``my-post``. - -Este es el objetivo del enrutador de *Symfony2*: asignar la *URL* de una petición a un controlador. De paso, aprenderás todo tipo de trucos que incluso facilitan la asignación de *URL* complejas. - -.. index:: - single: Enrutando; Bajo el capó - -Enrutando: Bajo el capó ------------------------- - - -Cuando se hace una petición a tu aplicación, esta contiene una dirección al «recurso» exacto que solicitó el cliente. Esta dirección se conoce como *URL* (o *URI*), y podría ser ``/contact``, ``/blog/read-me``, o cualquier otra cosa. Tomemos la siguiente petición *HTTP*, por ejemplo: - -.. code-block:: text - - GET /blog/my-blog-post - -El objetivo del sistema de enrutado de *Symfony2* es analizar esta *URL* y determinar qué controlador se debe ejecutar. Todo el proceso es el siguiente: - -#. La petición es manejada por el controlador frontal de *Symfony2* (por ejemplo, :file:`app.php`); - -#. El núcleo de *Symfony2* (es decir, el Kernel) pregunta al enrutador que examine la petición; - -#. El enrutador busca la *URL* entrante para emparejarla con una ruta específica y devuelve información sobre la ruta, incluyendo el controlador que se debe ejecutar; - -#. El núcleo de *Symfony2* ejecuta el controlador, que en última instancia, devuelve un objeto ``Respuesta``. - -.. figure:: /images/request-flow_es.png - :align: center - :alt: Flujo de la petición en Symfony2 - - La capa del enrutador es una herramienta que traduce la *URL* entrante a un controlador específico a ejecutar. - -.. index:: - single: Enrutando; Creando rutas - -Creando rutas -------------- - -*Symfony* carga todas las rutas de tu aplicación desde un archivo de configuración de enrutado. El archivo usualmente es ``app/config/routing.yml``, pero lo puedes configurar para que sea cualquier otro (incluyendo un archivo *XML* o *PHP*) vía el archivo de configuración de la aplicación: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - router: { resource: "%kernel.root_dir%/config/routing.yml" } - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - // ... - 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'), - )); - -.. tip:: - - A pesar de que todas las rutas se cargan desde un solo archivo, es práctica común incluir recursos de enrutado adicionales. Para ello, sólo indica en el archivo de configuración de enrutado principal cuáles archivos externos se tendrían que incluir. - Consulta la sección :ref:`routing-include-external-resources` para más información. - -Configuración básica de rutas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Definir una ruta es fácil, y una aplicación típica tendrá un montón de rutas. -Una ruta básica consta de dos partes: el ``path`` a emparejar y un arreglo ``defaults``: - -.. configuration-block:: - - .. code-block:: yaml - - _welcome: - path: / - defaults: { _controller: AcmeDemoBundle:Main:homepage } - - .. code-block:: xml - - - - - - - AcmeDemoBundle:Main:homepage - - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('_welcome', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Main:homepage', - ))); - - return $collection; - -Esta ruta coincide con la página de inicio (``/``) y la asigna al controlador de la página principal ``AcmeDemoBundle:Main:homepage``. *Symfony2* convierte la cadena ``_controller`` en una función *PHP* real y la ejecuta. Este proceso será explicado en breve en la sección :ref:`controller-string-syntax`. - -.. index:: - single: Enrutando; Marcadores de posición - -Enrutando con marcadores de posición -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Por supuesto, el sistema de enrutado es compatible con rutas mucho más interesantes. Muchas rutas contienen uno o más «comodines» llamados marcadores de posición: - -.. configuration-block:: - - .. code-block:: yaml - - blog_show: - path: /blog/{slug} - defaults: { _controller: AcmeBlogBundle:Blog:show } - - .. code-block:: xml - - - - - - - AcmeBlogBundle:Blog:show - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog_show', new Route('/blog/{slug}', array( - '_controller' => 'AcmeBlogBundle:Blog:show', - ))); - - return $collection; - -La ruta emparejará con cualquier cosa que se parezca a ``/blog/*``. Aún mejor, el valor coincide con el marcador de posición ``{slug}`` que estará disponible dentro de tu controlador. En otras palabras, si la *URL* es ``/blog/hello-world``, una variable ``$slug``, con un valor de ``hello-world``, estará disponible en el controlador. -Esta se puede usar, por ejemplo, para cargar la entrada en el ``blog`` coincidente con esa cadena. - -La ruta *no* debe, sin embargo, emparejar con un ``/blog`` sencillo. Eso es porque, por omisión, todos los marcadores de posición son obligatorios. Esto se puede cambiar agregando un valor marcador de posición al arreglo ``defaults``. - -Marcadores de posición obligatorios y opcionales -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para hacer las cosas más emocionantes, añade una nueva ruta que muestre una lista de todas las entradas del *'blog'* para la petición imaginaria *'blog'*: - -.. configuration-block:: - - .. code-block:: yaml - - blog: - path: /blog - defaults: { _controller: AcmeBlogBundle:Blog:index } - - .. code-block:: xml - - - - - - - AcmeBlogBundle:Blog:index - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog', new Route('/blog', array( - '_controller' => 'AcmeBlogBundle:Blog:index', - ))); - - return $collection; - -Hasta el momento, esta ruta es tan simple como es posible --- no contiene marcadores de posición y sólo coincidirá con la *URL* exacta ``/blog``. ¿Pero si necesitamos que esta ruta sea compatible con paginación, donde ``/blog/2`` muestra la segunda página de las entradas del *blog*? Actualiza la ruta para que tenga un nuevo marcador de posición ``{page}``: - -.. configuration-block:: - - .. code-block:: yaml - - blog: - path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index } - - .. code-block:: xml - - - - - - - AcmeBlogBundle:Blog:index - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', - ))); - - return $collection; - -Al igual que el marcador de posición ``{slug}`` anterior, el valor coincidente con ``{page}`` estará disponible dentro de tu controlador. Puedes utilizar su valor para determinar cual conjunto de entradas del *blog* muestra determinada página. - -¡Pero espera! Puesto que los marcadores de posición de forma predeterminada son obligatorios, esta ruta ya no coincidirá con ``/blog`` simplemente. En su lugar, para ver la página 1 del *blog*, ¡habrá la necesidad de utilizar la *URL* ``/blog/1``! Debido a que esa no es la manera en que se comporta una aplicación web rica, debes modificar la ruta para que el parámetro ``{page}`` sea opcional. -Esto se consigue incluyéndolo en la colección ``defaults``: - -.. configuration-block:: - - .. code-block:: yaml - - blog: - path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } - - .. code-block:: xml - - - - - - - AcmeBlogBundle:Blog:index - 1 - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', - 'page' => 1, - ))); - - return $collection; - -Agregando ``page`` a la clave ``defaults``, el marcador de posición ``{page}`` ya no es necesario. La *URL* ``/blog`` coincidirá con esta ruta y el valor del parámetro ``page`` se fijará en ``1``. La *URL* ``/blog/2`` también coincide, dando al parámetro ``page`` un valor de ``2``. Perfecto. - -+---------+--------------+ -| /blog | {page} = 1 | -+---------+--------------+ -| /blog/1 | {page} = 1 | -+---------+--------------+ -| /blog/2 | {page} = 2 | -+---------+--------------+ - -.. tip:: - - Las rutas con parámetros opcionales al final no coincidirán con peticiones con una barra inclinada final (es decir, ``/blog/`` no coincidirá, en cambio ``/blog`` concordará). - -.. index:: - single: Enrutando; Requisitos - -Agregando requisitos -~~~~~~~~~~~~~~~~~~~~ - -Echa un vistazo a las rutas que hemos creado hasta ahora: - -.. configuration-block:: - - .. code-block:: yaml - - blog: - path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } - - blog_show: - path: /blog/{slug} - defaults: { _controller: AcmeBlogBundle:Blog:show } - - .. code-block:: xml - - - - - - - AcmeBlogBundle:Blog:index - 1 - - - - AcmeBlogBundle:Blog:show - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', - 'page' => 1, - ))); - - $collection->add('blog_show', new Route('/blog/{show}', array( - '_controller' => 'AcmeBlogBundle:Blog:show', - ))); - - return $collection; - -¿Puedes ver el problema? Ten en cuenta que ambas rutas tienen patrones que coinciden con las *URL* que se parezcan a ``/blog/*``. El enrutador de *Symfony* siempre elegirá la **primera** ruta coincidente que encuentre. En otras palabras, la ruta ``blog_show`` *nunca* corresponderá. En cambio, una *URL* como ``/blog/my-blog-post`` coincidirá con la primera ruta (``blog``) y devolverá un valor sin sentido de ``my-blog-post`` para el parámetro ``{page}``. - -+---------------------------+-------+------------------------------+ -| *URL* | ruta | parámetros | -+===========================+=======+==============================+ -| /blog/2 | blog | {page} = 2 | -+---------------------------+-------+------------------------------+ -| /blog/mi-entrada-del-blog | blog | {page} = mi-entrada-del-blog | -+---------------------------+-------+------------------------------+ - -La respuesta al problema es añadir *requisitos* a la ruta. Las rutas en este ejemplo funcionarán perfectamente si el patrón ``/blog/{page}`` *sólo* concuerda con una *URL* dónde la parte ``{page}`` sea un número entero. Afortunadamente, se puede agregar fácilmente una expresión regular de requisitos para cada parámetro. Por ejemplo: - -.. configuration-block:: - - .. code-block:: yaml - - blog: - path: /blog/{page} - defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 } - requirements: - page: \d+ - - .. code-block:: xml - - - - - - - AcmeBlogBundle:Blog:index - 1 - \d+ - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('blog', new Route('/blog/{page}', array( - '_controller' => 'AcmeBlogBundle:Blog:index', - 'page' => 1, - ), array( - 'page' => '\d+', - ))); - - return $collection; - -El requisito ``\d+`` es una expresión regular diciendo que el valor del parámetro ``{page}`` debe ser un dígito (es decir, un número). La ruta ``blog`` todavía coincide con una *URL* como ``/blog/2`` (porque 2 es un número), pero ya no concuerda con una *URL* como ``/blog/my-blog-pos`` (porque ``my-blog-post`` *no* es un número). - -Como resultado, una *URL* como ``/blog/my-blog-post`` ahora coincide correctamente con la ruta ``blog_show``. - -+---------------------------+-----------+-------------------------------+ -| *URL* | ruta | parámetros | -+===========================+===========+===============================+ -| /blog/2 | blog | {page} = 2 | -+---------------------------+-----------+-------------------------------+ -| /blog/mi-entrada-del-blog | blog_show | {ficha} = mi-entrada-del-blog | -+---------------------------+-----------+-------------------------------+ - -.. sidebar:: Las primeras rutas siempre ganan - - ¿Qué significa todo eso de que el orden de las rutas es muy importante? - Si la ruta ``blog_show`` se coloca por encima de la ruta ``blog``, la *URL* ``/blog/2`` coincidiría con ``blog_show`` en lugar de ``blog`` ya que el parámetro ``{slug}`` de ``blog_show`` no tiene ningún requisito. Usando el orden adecuado y requisitos claros, puedes lograr casi cualquier cosa. - -Puesto que el parámetro ``requirements`` consiste de expresiones regulares, la complejidad y flexibilidad de cada requisito es totalmente tuya. Supongamos que la página principal de tu aplicación está disponible en dos diferentes idiomas, basándose en la *URL*: - -.. configuration-block:: - - .. code-block:: yaml - - homepage: - path: /{_locale} - defaults: { _controller: AcmeDemoBundle:Main:homepage, _locale: en } - requirements: - _locale: en|fr - - .. code-block:: xml - - - - - - - AcmeDemoBundle:Main:homepage - en - en|fr - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('homepage', new Route('/{culture}', array( - '_controller' => 'AcmeDemoBundle:Main:homepage', - 'culture' => 'en', - ), array( - 'culture' => 'en|fr', - ))); - - return $collection; - -Para las peticiones entrantes, la porción ``{_locale}`` de la dirección se compara con la expresión regular ``(en|es)``. - -+-----+-------------------------------+ -| / | {_locale} = es | -+-----+-------------------------------+ -| /en | {_locale} = en | -+-----+-------------------------------+ -| /es | {_locale} = es | -+-----+-------------------------------+ -| /fr | *no coincidirá con esta ruta* | -+-----+-------------------------------+ - -.. index:: - single: Enrutando; Requisitos de método - -Agregando requisitos de método *HTTP* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Además de la *URL*, también puedes coincidir con el *método* de la petición entrante (es decir, *GET*, *HEAD*, *POST*, *PUT*, *DELETE*). Supongamos que tienes un formulario de contacto con dos controladores ---uno para mostrar el formulario (en una petición *GET*) y uno para procesar el formulario una vez presentado (en una petición *POST*). Esto se puede lograr con la siguiente configuración de ruta: - -.. configuration-block:: - - .. code-block:: yaml - - contact: - path: /contact - defaults: { _controller: AcmeDemoBundle:Main:contact } - methods: [GET] - - contact_process: - path: /contact - defaults: { _controller: AcmeDemoBundle:Main:contactProcess } - methods: [POST] - - .. code-block:: xml - - - - - - - AcmeDemoBundle:Main:contact - - - - AcmeDemoBundle:Main:contactProcess - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('contact', new Route('/contact', array( - '_controller' => 'AcmeDemoBundle:Main:contact', - ), array(), array(), '', array(), array('GET'))); - - $collection->add('contact_process', new Route('/contact', array( - '_controller' => 'AcmeDemoBundle:Main:contactProcess', - ), array(), array(), '', array(), array('POST'))); - - return $collection; - -.. versionadded:: 2.2 - La opción ``methods`` se añadió en *Symfony* 2.2. Usa el requisito ``_method`` en versiones anteriores. - -A pesar del hecho de que estas dos rutas tienen patrones idénticos (``/contact``), la primera ruta sólo coincidirá con las peticiones ``GET`` y la segunda sólo coincidirá con las peticiones ``POST``. Esto significa que puedes mostrar y enviar el formulario a través de la misma *URL*, mientras usas controladores distintos para las dos acciones. - -.. note:: - - Si no especifícas ningún ``método``, la ruta emparejará con *todos* los métodos. - -Añadiendo un servidor -~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.2 - El soporte necesario para emparejar con el servidor se añadió en *Symfony* 2.2 - -También puedes emparejar con el *host* *HTTP* de la petición entrante. Para más información, consulta el :doc:`/components/routing/hostname_pattern` en la documentación del componente *Routing*. - -.. index:: - single: Enrutando; Ejemplo avanzado - single: Enrutando; parámetro _format - -.. _advanced-routing-example: - -Ejemplo de enrutado avanzado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En este punto, tienes todo lo necesario para crear una poderosa estructura de enrutado *Symfony*. El siguiente es un ejemplo de cuán flexible puede ser el sistema de enrutado: - -.. configuration-block:: - - .. code-block:: yaml - - article_show: - path: /articles/{_locale}/{year}/{title}.{_format} - defaults: { _controller: AcmeDemoBundle:Article:show, _format: html } - requirements: - _locale: en|fr - _format: html|rss - year: \d+ - - .. code-block:: xml - - - - - - - AcmeDemoBundle:Article:show - html - en|fr - html|rss - \d+ - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('homepage', new Route('/articles/{culture}/{year}/{title}.{_format}', array( - '_controller' => 'AcmeDemoBundle:Article:show', - '_format' => 'html', - ), array( - 'culture' => 'en|fr', - '_format' => 'html|rss', - 'year' => '\d+', - ))); - - return $collection; - -Como hemos visto, esta ruta sólo coincide si la porción ``{_locale}`` de la *URL* es o bien «``en``» o «``fr``» y si ``{year}`` es un número. Esta ruta también muestra cómo puedes utilizar un punto entre los marcadores de posición en lugar de una barra inclinada. Las *URL* que coinciden con esta ruta se podrían ver como las siguientes: - -* ``/articles/en/2010/my-post`` -* ``/articles/fr/2010/my-post.rss`` -* ``/articles/en/2013/my-latest-post.html`` - -.. _book-routing-format-param: - -.. sidebar:: El parámetro especial de enrutado ``_format`` - - Este ejemplo también resalta el parámetro especial de enrutado ``_format``. - Cuando se utiliza este parámetro, el valor coincidente se convierte en el «formato de la petición» del objeto ``Petición``. En última instancia, el formato de la petición se usa para cosas tales como establecer el ``Content-Type`` de la respuesta (por ejemplo, un formato de petición ``json`` se traduce en un ``Content-Type`` de ``application/json``). - Este también se puede usar en el controlador para reproducir una plantilla diferente por cada valor de ``_format``. El parámetro ``_format`` es una forma muy poderosa para reproducir el mismo contenido en distintos formatos. - -.. note:: - - A veces quieres hacer configurables globalmente ciertas partes de tus rutas. - *Symfony 2.1* te proporciona una manera de hacer esto aprovechando los parámetros del contenedor de servicios. Lee más sobre esto en «:doc:`/cookbook/routing/service_container_parameters`». - -Parámetros de enrutado especiales -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Como has visto, cada parámetro de enrutado o valor predeterminado finalmente está disponible como un argumento en el método controlador. Adicionalmente, hay tres parámetros que son especiales: cada uno añade una única pieza de funcionalidad a tu aplicación: - -* ``_controller``: Como hemos visto, este parámetro se utiliza para determinar qué controlador se ejecuta cuando la ruta concuerda; - -* ``_format``: Se utiliza para establecer el formato de la petición (:ref:`Leer más `); - -* ``_locale``: Se utiliza para establecer la configuración regional en la petición (:ref:`Leer más `); - -.. tip:: - - Si utilizas el parámetro ``_locale`` en una ruta, ese valor también se almacenará en la sesión para las subsecuentes peticiones lo cual evita guardar la misma región. - -.. index:: - single: Enrutando; Controladores - single: Controlador; Cadena denominando el formato - -.. _controller-string-syntax: - -Patrón de nomenclatura para controladores ------------------------------------------ - -Cada ruta debe tener un parámetro ``_controller``, el cual determina qué controlador se debe ejecutar cuando dicha ruta concuerde. Este parámetro utiliza un patrón de cadena simple llamado el *nombre lógico del controlador*, que *Symfony* asigna a un método y clase *PHP* específico. El patrón consta de tres partes, cada una separada por dos puntos: - - **paquete**:**controlador**:**acción** - -Por ejemplo, un valor ``_controller`` de ``AcmeBlogBundle:Blog:show`` significa: - -+----------------+----------------------+-----------------+ -| Paquete | Clase de controlador | Nombre método | -+================+======================+=================+ -| AcmeBlogBundle | BlogController | showAction | -+----------------+----------------------+-----------------+ - -El controlador podría tener este aspecto:: - - // src/Acme/BlogBundle/Controller/BlogController.php - namespace Acme\BlogBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class BlogController extends Controller - { - public function showAction($slug) - { - // ... - } - } - -Ten en cuenta que *Symfony* añade la cadena ``Controller`` al nombre de la clase (``Blog`` -=> ``BlogController``) y ``Action`` al nombre del método (``show`` => ``showAction``). - -También podrías referirte a este controlador utilizando su nombre de clase y método completamente cualificado: ``Acme\BlogBundle\Controller\BlogController::showAction``. -Pero si sigues algunas simples convenciones, el nombre lógico es más conciso y permite mayor flexibilidad. - -.. note:: - - Además de utilizar el nombre lógico o el nombre de clase completamente cualificado, *Symfony* es compatible con una tercera forma de referirse a un controlador. Este método utiliza un solo separador de dos puntos (por ejemplo, ``service_name:indexAction``) y hace referencia al controlador como un servicio (consulta :doc:`/cookbook/controller/service`). - -Parámetros de ruta y argumentos del controlador ------------------------------------------------ - -Los parámetros de ruta (por ejemplo, ``{slug}``) son especialmente importantes porque cada uno se pone a disposición como argumento para el método controlador:: - - public function showAction($slug) - { - // ... - } - -En realidad, toda la colección ``defaults`` se combina con los valores del parámetro para formar un solo arreglo. Cada clave de ese arreglo está disponible como un argumento en el controlador. - -En otras palabras, por cada argumento de tu método controlador, *Symfony* busca un parámetro de ruta con ese nombre y asigna su valor a ese argumento. -En el ejemplo avanzado anterior, podrías utilizar cualquier combinación (en cualquier orden) de las siguientes variables como argumentos para el método ``showAction()``: - -* ``$_locale`` -* ``$year`` -* ``$title`` -* ``$_format`` -* ``$_controller`` - -Dado que los marcadores de posición y los valores de la colección ``defaults`` se combinan, incluso la variable ``$_controller`` está disponible. Para una explicación más detallada, consulta :ref:`route-parameters-controller-arguments`. - -.. tip:: - - También puedes utilizar una variable especial ``$_route``, que se fija al nombre de la ruta que concordó. - -.. index:: - single: Enrutando; Importando recursos de enrutado - -.. _routing-include-external-resources: - -Incluyendo recursos de enrutado externos ----------------------------------------- - -Todas las rutas se cargan a través de un único archivo de configuración ---usualmente ``app/config/routing.yml`` (consulta `Creando rutas`_ más arriba). Por lo general, sin embargo, deseas cargar rutas para otros lugares, como un archivo de enrutado que vive dentro de un paquete. Esto se puede hacer «importando» ese archivo: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - acme_hello: - resource: "@AcmeHelloBundle/Resources/config/routing.yml" - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - - $collection = new RouteCollection(); - $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php")); - - return $collection; - -.. note:: - - Cuando importas recursos desde *YAML*, la clave (por ejemplo, ``acme_hello``) no tiene sentido. - Sólo asegúrate de que es única para que no haya otras líneas que reemplazar. - -La clave ``resource`` carga el recurso de la ruta dada. En este ejemplo, el recurso es la ruta completa a un archivo, donde la sintaxis contextual del atajo ``@AcmeHelloBundle`` se resuelve en la ruta a ese paquete. El archivo importado podría tener este aspecto: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/routing.yml - acme_hello: - path: /hello/{name} - defaults: { _controller: AcmeHelloBundle:Hello:index } - - .. code-block:: xml - - - - - - - - AcmeHelloBundle:Hello:index - - - - .. code-block:: php - - // src/Acme/HelloBundle/Resources/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('acme_hello', new Route('/hello/{name}', array( - '_controller' => 'AcmeHelloBundle:Hello:index', - ))); - - return $collection; - -Las rutas de este archivo se analizan y cargan en la misma forma que el archivo de enrutado principal. - -Prefijando rutas importadas -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -También puedes optar por proporcionar un «prefijo» para las rutas importadas. Por ejemplo, supongamos que deseas que la ruta ``acme_hello`` tenga una ruta final de ``/admin/hello/{name}`` en lugar de simplemente ``/hello/{name}``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - acme_hello: - resource: "@AcmeHelloBundle/Resources/config/routing.yml" - prefix: /admin - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - - $collection = new RouteCollection(); - $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), '/admin'); - - return $collection; - -La cadena ``/admin`` ahora será prefijada a la trayectoria de cada ruta cargada del nuevo recurso de enrutado. - -.. tip:: - - Además puedes definir rutas usando anotaciones. Consulta la documentación del :doc:`FrameworkExtraBundle ` para ver cómo hacerlo. - -Añadiendo un servidor a la expresión regular de las rutas importadas --------------------------------------------------------------------- - -.. versionadded:: 2.2 - El soporte necesario para emparejar con el servidor se añadió en *Symfony* 2.2 - -Puedes poner el servidor en la expresión regular de las rutas importadas. Para más información, ve :ref:`component-routing-host-imported`. - -.. index:: - single: Enrutando; Depurando - -Visualizando y depurando rutas ------------------------------- - -Si bien agregar y personalizar rutas, es útil para poder visualizar y obtener información detallada sobre tus rutas. Una buena manera de ver todas las rutas en tu aplicación es a través de la orden de consola ``router:debug``. Ejecuta la siguiente orden desde la raíz de tu proyecto. - -.. code-block:: bash - - $ php app/console router:debug - -Esta orden imprimirá una útil lista de *todas* las rutas configuradas en tu aplicación: - -.. code-block:: text - - homepage ANY / - contact GET /contact - contact_process POST /contact - article_show ANY /articles/{_locale}/{year}/{title}.{_format} - blog ANY /blog/{page} - blog_show ANY /blog/{slug} - -También puedes obtener información muy específica de una sola ruta incluyendo el nombre de la ruta después de la orden: - -.. code-block:: bash - - $ php app/console router:debug article_show - -Asímismo, si deseas probar acuál ruta coincide con una trayectoria dada, puedes usar la orden de consola ``router:match``: - -.. code-block:: bash - - $ php app/console router:match /blog/my-latest-post - -Esta orden imprimirá la ruta de la *URL* coincidente. - -.. code-block:: text - - Route "blog_show" matches - -.. index:: - single: Enrutando; Generando URL - -Generando *URL* ---------------- - -El sistema de enrutado también se debe utilizar para generar *URL*. En realidad, el enrutado es un sistema bidireccional: asignando la *URL* a un controlador+parámetros y la ruta+parámetros a una *URL*. Los métodos :method:`Symfony\\Component\\Routing\\Router::match` y :method:`Symfony\\Component\\Routing\\Router::generate` de este sistema bidireccional. Tomando la ruta ``blog_show`` del ejemplo anterior:: - - $params = $this->get('router')->match('/blog/my-blog-post'); - // array( - // 'slug' => 'my-blog-post', - // '_controller' => 'AcmeBlogBundle:Blog:show', - // ) - - $uri = $this->get('router')->generate('blog_show', array('slug' => 'my-blog-post')); - // /blog/my-blog-post - -Para generar una *URL*, debes especificar el nombre de la ruta (por ejemplo, ``blog_show``) y cualquier comodín (por ejemplo, ``slug = my-blog-post``) usado en el patrón de esa ruta. Con esta información, fácilmente puedes generar cualquier *URL*:: - - class MainController extends Controller - { - public function showAction($slug) - { - // ... - - $url = $this->generateUrl( - 'blog_show', - array('slug' => 'my-blog-post') - ); - } - } - -.. note:: - - En los controladores que extienden la clase base :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller` de *Symfony*, puedes utilizar el método :method:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller::generateUrl`, el cual llama al método :method:`Symfony\\Component\\Routing\\Router::generate` del servicio enrutador. - -En una sección posterior, aprenderás cómo generar *URL* desde el interior de tus plantillas. - -.. tip:: - - Si la interfaz de tu aplicación utiliza peticiones *AJAX*, posiblemente desees poder generar las direcciones *URL* en *JavaScript* basándote en tu configuración de enrutado. - Usando el `FOSJsRoutingBundle`_, puedes hacer eso exactamente: - - .. code-block:: javascript - - var url = Routing.generate( - 'blog_show', - {"slug": 'my-blog-post'} - ); - - Para más información, consulta la documentación del paquete. - -.. index:: - single: Enrutando; URL absolutas - -Generando *URL* absolutas -~~~~~~~~~~~~~~~~~~~~~~~~~ - -De forma predeterminada, el enrutador va a generar *URL* relativas (por ejemplo ``/blog``). Para generar una *URL* absoluta, sólo tienes que pasar ``true`` como tercer argumento del método ``generate()``:: - - $router->generate('blog_show', array('slug' => 'my-blog-post'), true); - // http://www.example.com/blog/my-blog-post - -.. note:: - - El servidor utilizado al generar una *URL* absoluta es el anfitrión del objeto ``Petición`` actual. Este se detercta automática basándose en la información del servidor suministrada por *PHP*. Al generar direcciones *URL* absolutas para archivos ejecutables desde la línea de ordenes, debes configurar manualmente el servidor que desees en el objeto ``RequestContext``:: - - $router->getContext()->setHost('www.example.com'); - -.. index:: - single: Enrutando; Generando URL en plantillas - -Generando *URL* con cadena de consulta -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El método ``generate`` toma un arreglo de valores comodín para generar la *URI*. -Pero si pasas adicionales, se añadirán a la *URI* como cadena de consulta:: - - $router->generate('blog', array('page' => 2, 'category' => 'Symfony')); - // /blog/2?category=Symfony - -Generando *URL* desde una plantilla -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El lugar más común para generar una *URL* es dentro de una plantilla cuando creas enlaces entre las páginas de tu aplicación. Esto se hace igual que antes, pero utilizando una función ayudante de plantilla: - -.. configuration-block:: - - .. code-block:: html+jinja - - - Read this blog post. - - - .. code-block:: html+php - - - Read this blog post. - - -También puedes generar *URL* absolutas. - -.. configuration-block:: - - .. code-block:: html+jinja - - - Read this blog post. - - - .. code-block:: html+php - - - Read this blog post. - - -Resumen -------- - -El enrutado es un sistema para asignar la dirección de las peticiones entrantes a la función controladora que se debe llamar para procesar la petición. Este permite especificar ambas *URL* bonitas y mantiene la funcionalidad de tu aplicación disociada de las *URL*. El enrutado es un mecanismo de dos vías, lo cual significa que también lo debes usar para generar tus direcciones *URL*. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/routing/scheme` - -.. _`FOSJsRoutingBundle`: https://github.com/FriendsOfSymfony/FOSJsRoutingBundle diff --git a/_sources/book/security.txt b/_sources/book/security.txt deleted file mode 100644 index 70e5f93..0000000 --- a/_sources/book/security.txt +++ /dev/null @@ -1,1722 +0,0 @@ -.. index:: - single: Seguridad - -Seguridad -========= - -La seguridad es un proceso de dos etapas, cuyo objetivo es evitar que un usuario acceda a un recurso al cual no debería tener acceso. - -En el primer paso del proceso, el sistema de seguridad identifica quién es el usuario obligándolo a presentar algún tipo de identificación. Esto se llama **autenticación**, y significa que el sistema está tratando de determinar quién eres. - -Una vez que el sistema sabe quien eres, el siguiente paso es determinar si deberías tener acceso a un determinado recurso. Esta parte del proceso se llama **autorización**, y significa que el sistema está comprobando si tienes suficientes privilegios para realizar una determinada acción. - -.. image:: /images/book/security_authentication_authorization_es.png - :align: center - -Puesto que la mejor manera de aprender es viendo un ejemplo, empieza asegurando tu aplicación con autenticación *HTTP* básica. - -.. note:: - - El `componente Security de Symfony`_ está disponible como una biblioteca *PHP* independiente para usarla en cualquier proyecto *PHP*. - -Ejemplo básico: Autenticación *HTTP* ------------------------------------- - -Puedes ajustar el componente de seguridad a través de la configuración de tu aplicación. -De hecho, la mayoría de las opciones de seguridad estándar son sólo cuestión de usar los ajustes correctos. La siguiente configuración le dice a *Symfony* que proteja cualquier *URL* coincidente con ``/admin/*`` y pida al usuario sus credenciales mediante autenticación *HTTP* básica (es decir, el cuadro de dialogo a la vieja escuela: nombre de usuario/contraseña): - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - secured_area: - pattern: ^/ - anonymous: ~ - http_basic: - realm: "Secured Demo Area" - - access_control: - - { path: ^/admin, roles: ROLE_ADMIN } - - providers: - in_memory: - memory: - users: - ryan: { password: ryanpass, roles: 'ROLE_USER' } - admin: { password: kitten, roles: 'ROLE_ADMIN' } - - encoders: - Symfony\Component\Security\Core\User\User: plaintext - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - 'pattern' => '^/', - 'anonymous' => array(), - 'http_basic' => array( - 'realm' => 'Secured Demo Area', - ), - ), - ), - 'access_control' => array( - array('path' => '^/admin', 'role' => 'ROLE_ADMIN'), - ), - 'providers' => array( - 'in_memory' => array( - 'memory' => array( - 'users' => array( - 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'), - 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'), - ), - ), - ), - ), - 'encoders' => array( - 'Symfony\Component\Security\Core\User\User' => 'plaintext', - ), - )); - -.. tip:: - - Una distribución estándar de *Symfony* separa la configuración de seguridad en un archivo independiente (por ejemplo, ``app/config/security.yml``). Si no tienes un archivo de seguridad autónomo, puedes poner la configuración directamente en el archivo de configuración principal (por ejemplo, :file:`app/config/config.yml`). - -El resultado final de esta configuración es un sistema de seguridad totalmente operativo que tiene el siguiente aspecto: - -* Hay dos usuarios en el sistema (``ryan`` y ``admin``); -* Los usuarios se autentican a través de la autenticación *HTTP* básica del sistema; -* Cualquier *URL* que coincida con ``/admin/*`` está protegida, y sólo el usuario ``admin`` puede acceder a ellas; -* Todas las *URL* que *no* coincidan con ``/admin/*`` son accesibles para todos los usuarios (y nunca se pide al usuario que se registre). - -Veamos brevemente cómo funciona la seguridad y cómo entra en juego cada parte de la configuración. - -Cómo funciona la seguridad: autenticación y autorización --------------------------------------------------------- - -El sistema de seguridad de *Symfony* trabaja identificando a un usuario (es decir, la autenticación) y comprobando si ese usuario debe tener acceso a una *URL* o recurso específico. - -.. _book-security-firewalls: - -Cortafuegos (autenticación) -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando un usuario hace una petición a una *URL* que está protegida por un cortafuegos, se activa el sistema de seguridad. El trabajo del cortafuegos es determinar si el usuario necesita estar autenticado, y si lo hace, enviar una respuesta al usuario para iniciar el proceso de autenticación. - -Un cortafuegos se activa cuando la *URL* de una petición entrante concuerda con el ``patrón`` de la expresión regular configurada en el valor ``config`` del cortafuegos. En este ejemplo el ``patrón`` (``^/``) concordará con *cada* petición entrante. El hecho de que el cortafuegos esté activado *no* significa, sin embargo, que el nombre de usuario de autenticación *HTTP* y el cuadro de diálogo de la contraseña se muestre en cada *URL*. Por ejemplo, cualquier usuario puede acceder a ``/foo`` sin que se le pida se autentique. - -.. image:: /images/book/security_anonymous_user_access_es.png - :align: center - -Esto funciona en primer lugar porque el cortafuegos permite *usuarios anónimos* a través del parámetro de configuración ``anonymous``. En otras palabras, el cortafuegos no requiere que el usuario se autentique plenamente de inmediato. Y puesto que no hay ``rol`` especial necesario para acceder a ``/foo`` (bajo la sección ``access_control``), la petición se puede llevar a cabo sin solicitar al usuario se autentique. - -Si eliminas la clave ``anonymous``, el cortafuegos *siempre* hará que un usuario se autentique inmediatamente. - -Control de acceso (autorización) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si un usuario solicita ``/admin/foo``, sin embargo, el proceso se comporta de manera diferente. -Esto se debe a la sección de configuración ``access_control`` la cual dice que cualquier *URL* coincidente con el patrón de la expresión regular ``^/admin`` (es decir, ``/admin`` o cualquier cosa coincidente con ``/admin/*``) requiere el rol ``ROLE_ADMIN``. Los roles son la base para la mayor parte de la autorización: el usuario puede acceder a ``/admin/foo`` sólo si cuenta con el rol ``ROLE_ADMIN``. - -.. image:: /images/book/security_anonymous_user_denied_authorization_es.png - :align: center - -Como antes, cuando el usuario hace la petición originalmente, el cortafuegos no solicita ningún tipo de identificación. Sin embargo, tan pronto como la capa de control de acceso niega el acceso a los usuarios (ya que el usuario anónimo no tiene el rol ``ROLE_ADMIN``), el servidor de seguridad entra en acción e inicia el proceso de autenticación). -El proceso de autenticación depende del mecanismo de autenticación que utilices. Por ejemplo, si estás utilizando el método de autenticación con formulario de acceso, el usuario será redirigido a la página de inicio de sesión. Si estás utilizando autenticación *HTTP*, se enviará al usuario una respuesta *HTTP 401* para que el usuario vea el cuadro de diálogo de nombre de usuario y contraseña. - -Ahora el usuario de nuevo tiene la posibilidad de presentar sus credenciales a la aplicación. -Si las credenciales son válidas, se puede intentar de nuevo la petición original. - -.. image:: /images/book/security_ryan_no_role_admin_access_es.png - :align: center - -En este ejemplo, el usuario ``ryan`` se autentica correctamente con el cortafuegos. -Pero como ``ryan`` no cuenta con el rol ``ROLE_ADMIN``, se le sigue negando el acceso a ``/admin/foo``. En última instancia, esto significa que el usuario debe ver algún tipo de mensaje indicándole que se le ha denegado el acceso. - -.. tip:: - - Cuando *Symfony* niega el acceso al usuario, él verá una pantalla de error y recibe un código de estado *HTTP 403* (``Prohibido``). Puedes personalizar la pantalla de error, acceso denegado, siguiendo las instrucciones de las :ref:`Páginas de error ` en el artículo para personalizar la página de error 403 del recetario. - -Por último, si el usuario ``admin`` solicita ``/admin/foo``, se lleva a cabo un proceso similar, excepto que ahora, después de haberse autenticado, la capa de control de acceso le permitirá pasar a través de la petición: - -.. image:: /images/book/security_admin_role_access_es.png - :align: center - -El flujo de la petición cuando un usuario solicita un recurso protegido es sencillo, pero increíblemente flexible. Como verás más adelante, la autenticación se puede realizar de varias maneras, incluyendo a través de un formulario de acceso, certificados X.509 o la autenticación del usuario a través de *Twitter*. Independientemente del método de autenticación, el flujo de la petición siempre es el mismo: - -#. Un usuario accede a un recurso protegido; -#. La aplicación redirige al usuario al formulario de acceso; -#. El usuario presenta sus credenciales (por ejemplo nombre de usuario/contraseña); -#. El cortafuegos autentica al usuario; -#. El nuevo usuario autenticado intenta de nuevo la petición original. - -.. note:: - - El proceso *exacto* realmente depende un poco en el mecanismo de autenticación utilizado. Por ejemplo, cuando utilizas el formulario de acceso, el usuario presenta sus credenciales a una *URL* que procesa el formulario (por ejemplo ``/login_check``) y luego es redirigido a la dirección solicitada originalmente (por ejemplo ``/admin/foo``). - Pero con la autenticación *HTTP*, el usuario envía sus credenciales directamente a la *URL* original (por ejemplo ``/admin/foo``) y luego la página se devuelve al usuario en la misma petición (es decir, sin redirección). - - Este tipo de idiosincrasia no debería causar ningún problema, pero es bueno tenerla en cuenta. - -.. tip:: - - También aprenderás más adelante cómo puedes proteger *cualquier cosa* en *Symfony2*, incluidos controladores específicos, objetos, e incluso métodos *PHP*. - -.. _book-security-form-login: - -Usando un formulario de acceso tradicional ------------------------------------------- - -.. tip:: - - En esta sección, aprenderás cómo crear un formulario de acceso básico que continúa usando los usuarios definidos en el código del archivo ``security.yml``. - - Para cargar usuarios desde la base de datos, por favor consulta :doc:`/cookbook/security/entity_provider`. - Al leer este artículo y esta sección, puedes crear un sistema de formularios de acceso completo que carga usuarios desde la base de datos. - -Hasta ahora, hemos visto cómo cubrir tu aplicación bajo un cortafuegos y proteger el acceso a determinadas zonas con roles. Al usar la autenticación *HTTP*, puedes aprovechar sin esfuerzo, el cuadro de diálogo nativo nombre de usuario/contraseña que ofrecen todos los navegadores. Sin embargo, fuera de la caja, *Symfony* es compatible con múltiples mecanismos de autenticación. Para información detallada sobre todos ellos, consulta la :doc:`Referencia para afinar el sistema de seguridad `. - -En esta sección, vamos a mejorar este proceso permitiendo la autenticación del usuario a través de un formulario de acceso *HTML* tradicional. - -En primer lugar, activa el formulario de acceso en el cortafuegos: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - secured_area: - pattern: ^/ - anonymous: ~ - form_login: - login_path: login - check_path: login_check - - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - 'pattern' => '^/', - 'anonymous' => array(), - 'form_login' => array( - 'login_path' => 'login', - 'check_path' => 'login_check', - ), - ), - ), - )); - -.. tip:: - - Si no necesitas personalizar tus valores ``login_path`` o ``check_path`` (los valores utilizados aquí son los valores predeterminados), puedes acortar tu configuración: - - .. configuration-block:: - - .. code-block:: yaml - - form_login: ~ - - .. code-block:: xml - - - - .. code-block:: php - - 'form_login' => array(), - -Ahora, cuando el sistema de seguridad inicia el proceso de autenticación, redirige al usuario al formulario de acceso (predeterminado a ``/login``). La implementación visual de este formulario de acceso es tu trabajo. Primero, crea las dos rutas que utilizarás en la configuración de seguridad: La ruta ``login`` mostrará el formulario de inicio de sesión (es decir ``/login``) y la ruta ``login_check`` procesará el formulario enviado (es decir ``/login_check``): - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - login: - pattern: /login - defaults: { _controller: AcmeSecurityBundle:Security:login } - login_check: - pattern: /login_check - - .. code-block:: xml - - - - - - - - AcmeSecurityBundle:Security:login - - - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('login', new Route('/login', array( - '_controller' => 'AcmeDemoBundle:Security:login', - ))); - $collection->add('login_check', new Route('/login_check', array())); - - return $collection; - -.. note:: - - *No* necesitas implementar un controlador para la *URL* ``/login_check`` ya que el cortafuegos automáticamente captura y procesa cualquier formulario enviado a esta *URL*. - -.. versionadded:: 2.1 - A partir de *Symfony* 2.1, *debes* tener configuradas las rutas para tus claves ``login_path``, ``check_path`` y ``logout``. Estas claves pueden ser nombres de ruta (tal como muestra este ejemplo) o las *URL* que tienen rutas configuradas para ello. - -Ten en cuenta que el nombre de la ruta ``login`` coincide con el valor ``login_path`` configurado, a donde el sistema de seguridad redirigirá a los usuarios que necesiten ingresar. - -A continuación, crea el controlador que mostrará el formulario de acceso:: - - // src/Acme/SecurityBundle/Controller/SecurityController.php; - namespace Acme\SecurityBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\Security\Core\SecurityContext; - - class SecurityController extends Controller - { - public function loginAction() - { - $request = $this->getRequest(); - $session = $request->getSession(); - - // obtiene el error de inicio de sesión si lo hay - if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { - $error = $request->attributes->get( - SecurityContext::AUTHENTICATION_ERROR - ); - } else { - $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); - $session->remove(SecurityContext::AUTHENTICATION_ERROR); - } - - return $this->render( - 'AcmeSecurityBundle:Security:login.html.twig', - array( - // último nombre de usuario ingresado - 'last_username' => $session->get(SecurityContext::LAST_USERNAME), - 'error' => $error, - ) - ); - } - } - -No dejes que este controlador te confunda. Como veremos en un momento, cuando el usuario envía el formulario, el sistema de seguridad automáticamente se encarga de procesar la recepción del formulario por ti. Si el usuario ha presentado un nombre de usuario o contraseña no válidos, este controlador lee el error del formulario enviado desde el sistema de seguridad de modo que se pueda mostrar al usuario. - -En otras palabras, tu trabajo es mostrar el formulario al usuario y los errores de ingreso que puedan haber ocurrido, pero, el propio sistema de seguridad se encarga de verificar el nombre de usuario y contraseña y la autenticación del usuario. - -Por último, crea la plantilla correspondiente: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/SecurityBundle/Resources/views/Security/login.html.twig #} - {% if error %} -
{{ error.message }}
- {% endif %} - - - - - - - - - {# - Si deseas controlar la URL a la que rediriges al - usuario en caso de éxito (más detalles abajo) - - #} - - - - - .. code-block:: html+php - - - -
getMessage() ?>
- - -
- - - - - - - - - -
- -.. tip:: - - La variable ``error`` pasada a la plantilla es una instancia de :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`. - Esta puede contener más información ---o incluso información confidencial--- sobre el fallo de autenticación, ¡por lo tanto utilízala prudentemente! - -El formulario tiene muy pocos requisitos. En primer lugar, presentando el formulario a ``/login_check`` (a ​​través de la ruta ``login_check``), el sistema de seguridad debe interceptar el envío del formulario y procesarlo automáticamente. En segundo lugar, el sistema de seguridad espera que los campos presentados se llamen ``_username`` y ``_password`` (estos nombres de campo se pueden :ref:`configurar `). - -¡Y eso es todo! Cuando envías el formulario, el sistema de seguridad automáticamente comprobará las credenciales del usuario y, o bien autenticará al usuario o lo enviará al formulario de acceso donde se puede mostrar el error. - -Revisemos todo el proceso: - -#. El usuario intenta acceder a un recurso que está protegido; -#. El cortafuegos inicia el proceso de autenticación redirigiendo al usuario al formulario de acceso (``/login``); -#. La página ``/login`` reproduce el formulario de acceso a través de la ruta y el controlador creado en este ejemplo; -#. El usuario envía el formulario de acceso a ``/login_check``; -#. El sistema de seguridad intercepta la petición, comprueba las credenciales presentadas por el usuario, autentica al usuario si todo está correcto, y si no, envía al usuario de nuevo al formulario de acceso. - -Por omisión, si las credenciales presentadas son correctas, el usuario será redirigido a la página solicitada originalmente (por ejemplo ``/admin/foo``). Si originalmente el usuario fue directo a la página de inicio de sesión, será redirigido a la página principal. -Esto puede ser altamente personalizado, lo cual te permite, por ejemplo, redirigir al usuario a una *URL* específica. - -Para más detalles sobre esto y cómo personalizar el proceso de entrada en general, consulta :doc:`/cookbook/security/form_login`. - -.. _book-security-common-pitfalls: - -.. sidebar:: Evitando errores comunes - - Cuando prepares tu formulario de acceso, ten cuidado con unas cuantas trampas muy comunes. - - **1. Crea las rutas correctas** - - En primer lugar, asegúrate de que has definido correctamente las rutas ``/login`` y ``/login_check`` y que corresponden a los valores configurados de ``login_path`` y ``check_path``. Una mala configuración aquí puede significar que seas redirigido a una página de error 404 en lugar de a la página de acceso, o que al presentar el formulario de acceso no haga nada (sólo verás el formulario de acceso una y otra vez). - - **2. Asegúrate de que la página de inicio de sesión no esté protegida** - - Además, asegúrate de que la página de acceso *no* requiere ningún rol para verla. Por ejemplo, la siguiente configuración ---la cual requiere el rol ``ROLE_ADMIN`` para todas las *URL* (incluyendo la *URL* ``/login``)---, provocará un bucle de redirección: - - .. configuration-block:: - - .. code-block:: yaml - - access_control: - - { path: ^/, roles: ROLE_ADMIN } - - .. code-block:: xml - - - - - - .. code-block:: php - - 'access_control' => array( - array('path' => '^/', 'role' => 'ROLE_ADMIN'), - ), - - Quitar el control de acceso en la *URL* ``/login`` soluciona el problema: - - .. configuration-block:: - - .. code-block:: yaml - - access_control: - - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } - - { path: ^/, roles: ROLE_ADMIN } - - .. code-block:: xml - - - - - - - .. code-block:: php - - 'access_control' => array( - array('path' => '^/login', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY'), - array('path' => '^/', 'role' => 'ROLE_ADMIN'), - ), - - Además, si el cortafuegos *no* permite usuarios anónimos, necesitas crear un cortafuegos especial que permita usuarios anónimos en la página de acceso: - - .. configuration-block:: - - .. code-block:: yaml - - firewalls: - login_firewall: - pattern: ^/login$ - anonymous: ~ - secured_area: - pattern: ^/ - form_login: ~ - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - 'firewalls' => array( - 'login_firewall' => array( - 'pattern' => '^/login$', - 'anonymous' => array(), - ), - 'secured_area' => array( - 'pattern' => '^/', - 'form_login' => array(), - ), - ), - - **3. Asegúrate de que** ``/login_check`` **está detrás de un cortafuegos** - - A continuación, asegúrate de que tu ruta *URL* ``check_path`` (por ejemplo ``/login_check``) está detrás del cortafuegos que estás utilizando para tu formulario de acceso (en este ejemplo, el único cortafuegos coincide con *todas* las *URL*, incluyendo ``/login_check``). Si ``/login_check`` no coincide con ningún cortafuegos, recibirás una excepción ``No se puede encontrar el controlador para la ruta «/login_check»``. - - **4. Múltiples cortafuegos no comparten el contexto de seguridad** - - Si estás utilizando múltiples cortafuegos y autenticas contra un cortafuegos, *no* serás autenticado automáticamente contra ningún otro cortafuegos. - Diferentes cortafuegos, son como diferentes sistemas de seguridad. Para hacerlo debes especificar explícitamente el mismo :ref:`reference-security-firewall-context` para diferentes cortafuegos. Pero normalmente la mayoría de las aplicaciones, tener un cortafuegos principal es suficiente. - -Autorizando ------------ - -El primer paso en la seguridad siempre es la autenticación: el proceso de verificar quién es el usuario. Con *Symfony*, la autenticación se puede hacer de cualquier manera ---a través de un formulario de acceso, autenticación básica *HTTP*, e incluso a través de *Facebook*. - -Una vez que el usuario se ha autenticado, comienza la autorización. La autorización proporciona una forma estándar y potente para decidir si un usuario puede acceder a algún recurso (una *URL*, un modelo de objetos, una llamada a un método, ...). Esto funciona asignando roles específicos a cada usuario y, a continuación, requiriendo diferentes roles para diferentes recursos. - -El proceso de autorización tiene dos lados diferentes: - -#. El usuario tiene un conjunto de roles específico; -#. Un recurso requiere un rol específico a fin de tener acceso. - -En esta sección, nos centraremos en cómo proteger diferentes recursos (por ejemplo, *URL*, llamadas a métodos, etc.) con diferentes roles. Más tarde, aprenderás más sobre cómo crear y asignar roles a los usuarios. - -Protegiendo patrones de *URL* específicas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La forma más básica para proteger parte de tu aplicación es asegurar un patrón de *URL* completo. Ya lo viste en el primer ejemplo de este capítulo, cómo algo que coincide con el patrón de la expresión regular ``^/admin`` requiere el rol ``ROLE_ADMIN``. - -.. caution:: - - Entender exactamente cómo trabaja ``access_control`` es **muy** importante para garantizar que tu aplicación está protegida correctamente. Ve más adelante :ref:`security-book-access-control-explanation` para información detallada. - -Puedes definir tantos patrones *URL* como necesites ---cada uno es una expresión regular---. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - { path: ^/admin/users, roles: ROLE_SUPER_ADMIN } - - { path: ^/admin, roles: ROLE_ADMIN } - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - // ... - 'access_control' => array( - array('path' => '^/admin/users', 'role' => 'ROLE_SUPER_ADMIN'), - array('path' => '^/admin', 'role' => 'ROLE_ADMIN'), - ), - )); - -.. tip:: - - Al prefijar la ruta con ``^`` te aseguras que sólo coinciden las *URL* que *comienzan* con ese patrón. Por ejemplo, una ruta de simplemente ``/admin`` (sin el ``^``) correctamente coincidirá con ``/admin/foo`` pero también coincide con la *URL* ``/foo/admin``. - -.. _security-book-access-control-explanation: - -Entendiendo cómo trabaja ``access_control`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Por cada petición entrante, *Symfony2* comprueba cada opción ``access_control`` para encontrar *una* que concuerde con la petición actual. Tan pronto como encuentra una entrada ``access_control`` coincidente, se detiene ---únicamente si el **primer** ``access_control`` concordante se usa para forzar el acceso---. - -Cada ``access_control`` tiene varias opciones que configuran dos diferentes cosas: (a) :ref:`la petición entrante emparejada debe tener esta entrada de control de acceso ` y (b) :ref:`una vez emparejada, debe tener algún tipo de restricción de acceso aplicable `: - -.. _security-book-access-control-matching-options: - -**(a) Emparejando Opciones** - -*Symfony2* crea una instancia de :class:`Symfony\\Component\\HttpFoundation\\RequestMatcher` para cada entrada ``access_control``, la cual determina si o no se debería usar un determinado control de acceso en esa petición. Las siguientes opciones de ``access_control`` se utilizan para emparejar: - -* ``path`` -* ``ip`` -* ``host`` -* ``methods`` - -Toma las siguientes entradas de ``access_control`` como ejemplo: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - { path: ^/admin, roles: ROLE_USER_IP, ip: 127.0.0.1 } - - { path: ^/admin, roles: ROLE_USER_HOST, host: symfony.com } - - { path: ^/admin, roles: ROLE_USER_METHOD, methods: [POST, PUT] } - - { path: ^/admin, roles: ROLE_USER } - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - 'access_control' => array( - array('path' => '^/admin', 'role' => 'ROLE_USER_IP', 'ip' => '127.0.0.1'), - array('path' => '^/admin', 'role' => 'ROLE_USER_HOST', 'host' => 'symfony.com'), - array('path' => '^/admin', 'role' => 'ROLE_USER_METHOD', 'method' => 'POST, PUT'), - array('path' => '^/admin', 'role' => 'ROLE_USER'), - ), - -Para cada petición entrante, *Symfony* debe decidir cuál ``access_control`` utilizar basándose en la *URI*, la dirección *IP* del cliente, el nombre del servidor entrante, y el método de la petición. Recuerda, se usa la primera regla que coincida, y si para una entrada no se especifican ``ip``, ``host`` o ``method``, ese ``access_control`` emparejará con cualquier ``ip``, ``host`` o ``method``: - -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| **URI** | **IP** | **HOST** | **METHOD** | ``access_control`` | ¿Porqué? | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| ``/admin/user`` | 127.0.0.1 | example.com | GET | regla #1 (``ROLE_USER_IP``) | La *URI* empareja con ``path`` y la *IP* con ``ip``. | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| ``/admin/user`` | 127.0.0.1 | symfony.com | GET | regla #1 (``ROLE_USER_IP``) | ``path`` e ``ip`` todavía concuerdan. Esta además debería | -| | | | | | emparejar con la entrada ``ROLE_USER_HOST``, pero *sólo* si se | -| | | | | | usa el **primer** ``access_control`` coincidente. | -| | | | | | | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | symfony.com | GET | regla #2 (``ROLE_USER_HOST``) | La ``ip`` no concuerda con la primera regla, por lo tanto se | -| | | | | | usa la segunda regla (la cual concuerda). | -| | | | | | | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | symfony.com | POST | regla #2 (``ROLE_USER_HOST``) | La segunda regla todavía concuerda. Esta además debería | -| | | | | | emparejar con la tercera regla (``ROLE_USER_METHOD``), pero | -| | | | | | solo si se usa la **primer** ``access_control`` coincidente. | -| | | | | | | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | example.com | POST | regla #3 (``ROLE_USER_METHOD``)| La ``ip`` y ``host`` no concuerdan con las primeras dos | -| | | | | | entradas, pero la tercera ---``ROLE_USER_METHOD``--- concuerda | -| | | | | | y se usa. | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| ``/admin/user`` | 168.0.0.1 | example.com | GET | regla #4 (``ROLE_USER``) | La ``ip``, ``host`` y ``method`` evitan que las primeras tres | -| | | | | | entradas concuerden. Pero debido a que la *URI* concuerda con | -| | | | | | el patrón ``path`` de la entrada ``ROLE_USER``, esta se usa. | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ -| ``/foo`` | 127.0.0.1 | symfony.com | POST | no hay entradas concordantes | Esta no concuerda con ninguna regla ``access_control``, debido | -| | | | | | a que la *URI* no concuerda con los valores de ``path``. | -| | | | | | | -+-----------------+-------------+-------------+------------+--------------------------------+-----------------------------------------------------------------+ - -.. _security-book-access-control-enforcement-options: - -**(b) Forzando el acceso** - -Una vez que *Symfony2* ha decidido cuál entrada ``access_control`` concuerda (si la hay), entonces *aplica* las restricciones de acceso basándose en las opciones ``roles`` y ``requires_channel``: - -* ``role`` Si el usuario no tiene determinado rol o roles, entonces el acceso es denegado (internamente, se lanza una :class:`Symfony\\Component\\Security\\Core\\Exception\\AccessDeniedException`); - -* ``requires_channel`` Si el canal de la petición entrante (p. ej. ``http``) no concuerda con este valor (p. ej. ``https``), el usuario será redirigido (p. ej. redirigido de ``http`` a ``https``, o viceversa). - -.. tip:: - - Si el acceso es denegado, el sistema intentará autenticar al usuario si aún no lo está (p. ej. redirigiendo al usuario a la página de inicio de sesión). Si el usuario ya inició sesión, se mostrará la página del error 403 «acceso denegado». Consulta :doc:`/cookbook/controller/error_pages` para más información. - -.. _book-security-securing-ip: - -Protegiendo por *IP* -~~~~~~~~~~~~~~~~~~~~ - -En algunas situaciones puede surgir la necesidad de restringir el acceso a una determinada ruta basándose en la *IP*. Esto es importante particularmente en el caso de la :ref:`Inclusión del borde lateral ` (*ESI*), por ejemplo. Cuándo *ESI* está habilitada, es recomendable proteger el acceso a direcciones *URL* *ESI*. De hecho, algunas *ESI* pueden contener algo de contenido privado tal como la información del usuario actual. Para impedir cualquier acceso directo a estos recursos desde un navegador *web* (deduciendo el patrón *ESI* de la *URL*), la ruta *ESI* se **debe** asegurar para que únicamente sea visible desde la caché de un delegado inverso de confianza. - -Aquí tienes un ejemplo de cómo podrías proteger todas las rutas *ESI* que empiezan con un determinado prefijo, ``/esi``, para que no se accedan desde el exterior: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - { path: ^/esi, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 } - - { path: ^/esi, roles: ROLE_NO_ACCESS } - - .. code-block:: xml - - - - - - - .. code-block:: php - - 'access_control' => array( - array('path' => '^/esi', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'ip' => '127.0.0.1'), - array('path' => '^/esi', 'role' => 'ROLE_NO_ACCESS'), - ), - -Así es como trabaja cuándo la ruta es ``/esi/algo`` proveniente de la *IP* ``10.0.0.1``: - -* La primera regla del control de acceso es ignorada debido a que ``path`` concuerda pero la ``ip`` no; - -* La segunda regla de control de acceso se activa (la única restricción sigue siendo ``path`` y concuerda): Puesto que el usuario no puede tener el rol ``ROLE_NO_ACCESS`` cuando no está definido, el acceso es denegado (el rol ``ROLE_NO_ACCESS`` puede ser cualquier cosa que no empareje con un rol existente, solo sirve como un truco para negar el acceso siempre). - -Ahora, si la misma petición proviene de ``127.0.0.1``: - -* Ahora, se activa la primera regla del control de acceso porque ambas ``path`` e ``ip`` concuerdan: El acceso es permitido debido a que el usuario siempre tiene el rol ``IS_AUTHENTICATED_ANONYMOUSLY``. - -* La segunda regla de acceso no se examina debido a que la primera regla concordó. - -.. _book-security-securing-channel: - -Protegiendo por canal -~~~~~~~~~~~~~~~~~~~~~ - -También puedes requerir que un usuario acceda a una *URL* vía *SSL*; Sólo utiliza el argumento ``requires_channel`` en cualquier entrada del ``access_control``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - access_control: - - { path: ^/cart/checkout, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https } - - .. code-block:: xml - - - - - - .. code-block:: php - - 'access_control' => array( - array('path' => '^/cart/checkout', 'role' => 'IS_AUTHENTICATED_ANONYMOUSLY', 'requires_channel' => 'https'), - ), - -.. _book-security-securing-controller: - -Protegiendo un controlador -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Proteger tu aplicación basándote en los patrones *URL* es fácil, pero, en algunos casos, puede no estar suficientemente bien ajustado. Cuando sea necesario, fácilmente puedes forzar la autorización desde un controlador:: - - // ... - use Symfony\Component\Security\Core\Exception\AccessDeniedException; - - public function helloAction($name) - { - if (false === $this->get('security.context')->isGranted('ROLE_ADMIN')) { - throw new AccessDeniedException(); - } - - // ... - } - -.. _book-security-securing-controller-annotations: - -También puedes optar por instalar y utilizar el opcional ``JMSSecurityExtraBundle``, con el cual puedes asegurar tu controlador usando anotaciones:: - - // ... - use JMS\SecurityExtraBundle\Annotation\Secure; - - /** - * @Secure(roles="ROLE_ADMIN") - */ - public function helloAction($name) - { - // ... - } - -Para más información, consulta la documentación de `JMSSecurityExtraBundle`_. Si estás usando la distribución estándar de *Symfony*, este paquete está disponible de forma predeterminada. -Si no es así, lo puedes descargar e instalar. - -Protegiendo otros servicios -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -De hecho, en *Symfony* puedes proteger cualquier cosa utilizando una estrategia similar a la observada en la sección anterior. Por ejemplo, supongamos que tienes un servicio (es decir, una clase *PHP*), cuyo trabajo consiste en enviar mensajes de correo electrónico de un usuario a otro. -Puedes restringir el uso de esta clase ---no importa dónde se esté utilizando--- a los usuarios que tienen un rol específico. - -Para más información sobre cómo utilizar el componente de seguridad para proteger diferentes servicios y métodos en tu aplicación, consulta :doc:`/cookbook/security/securing_services`. - -Listas de control de acceso (*ACL*): Protegiendo objetos individuales de base de datos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Imagina que estás diseñando un sistema de *blog* donde los usuarios pueden comentar tus entradas. Ahora, deseas que un usuario pueda editar sus propios comentarios, pero no los de otros usuarios. Además, como usuario ``admin``, quieres tener la posibilidad de editar *todos* los comentarios. - -El componente de seguridad viene con un sistema opcional de lista de control de acceso (*ACL*) que puedes utilizar cuando sea necesario para controlar el acceso a instancias individuales de un objeto en el sistema. *Sin* *ACL*, puedes proteger tu sistema para que sólo determinados usuarios puedan editar los comentarios del *blog* en general. Pero *con* *ACL*, puedes restringir o permitir el acceso en base a comentario por comentario. - -Para más información, consulta el artículo del recetario: :doc:`/cookbook/security/acl`. - -Usuarios --------- - -En las secciones anteriores, aprendiste cómo puedes proteger diferentes recursos que requieren un conjunto de *roles* para un recurso. En esta sección vamos a explorar el otro lado de la autorización: los usuarios. - -¿De dónde provienen los usuarios? (*Proveedores de usuarios*) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Durante la autenticación, el usuario envía un conjunto de credenciales (por lo general un nombre de usuario y contraseña). El trabajo del sistema de autenticación es concordar esas credenciales contra una piscina de usuarios. Entonces, ¿de dónde viene esta lista de usuarios? - -En *Symfony2*, los usuarios pueden venir de cualquier parte ---un archivo de configuración, una tabla de base de datos, un servicio web, o cualquier otra cosa que se te ocurra. Todo lo que proporcione uno o más usuarios al sistema de autenticación se conoce como «proveedor de usuario». -*Symfony2* de serie viene con los dos proveedores de usuario más comunes: uno que carga los usuarios de un archivo de configuración y otro que carga usuarios de una tabla de la base de datos. - -Especificando usuarios en un archivo de configuración -..................................................... - -La forma más fácil para especificar usuarios es directamente en un archivo de configuración. -De hecho, ya lo has visto en algunos ejemplos de este capítulo. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - providers: - default_provider: - memory: - users: - ryan: { password: ryanpass, roles: 'ROLE_USER' } - admin: { password: kitten, roles: 'ROLE_ADMIN' } - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - // ... - 'providers' => array( - 'default_provider' => array( - 'memory' => array( - 'users' => array( - 'ryan' => array('password' => 'ryanpass', 'roles' => 'ROLE_USER'), - 'admin' => array('password' => 'kitten', 'roles' => 'ROLE_ADMIN'), - ), - ), - ), - ), - )); - -Este proveedor de usuario se denomina proveedor de usuario «en memoria», ya que los usuarios no se almacenan en alguna parte de una base de datos. El objeto usuario en realidad lo proporciona *Symfony* (:class:`Symfony\\Component\\Security\\Core\\User\\User`). - -.. tip:: - Cualquier proveedor de usuario puede cargar usuarios directamente desde la configuración especificando el parámetro de configuración ``users`` y la lista de usuarios debajo de él. - -.. caution:: - - Si tu nombre de usuario es completamente numérico (por ejemplo, ``77``) o contiene un guión (por ejemplo, ``user-name``), debes utilizar la sintaxis alterna al especificar usuarios en *YAML*: - - .. code-block:: yaml - - users: - - { name: 77, password: pass, roles: 'ROLE_USER' } - - { name: user-name, password: pass, roles: 'ROLE_USER' } - -Para sitios pequeños, este método es rápido y fácil de configurar. Para sistemas más complejos, querrás cargar usuarios desde la base de datos. - -.. _book-security-user-entity: - -Cargando usuarios de la base de datos -..................................... - -Si deseas cargar tus usuarios a través del *ORM* de *Doctrine*, lo puedes hacer creando una clase ``User`` y configurando el proveedor ``entity``. - -.. tip:: - - Hay disponible un paquete de código abierto de alta calidad, el cual te permite almacenar tus usuarios a través del *ORM* u *ODM* de *Doctrine*. Lee más acerca del `FOSUserBundle`_ en *GitHub*. - -Con este enfoque, primero crea tu propia clase ``User``, la cual se almacenará en la base de datos. - -.. code-block:: php - - // src/Acme/UserBundle/Entity/User.php - namespace Acme\UserBundle\Entity; - - use Symfony\Component\Security\Core\User\UserInterface; - use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Entity - */ - class User implements UserInterface - { - /** - * @ORM\Column(type="string", length=255) - */ - protected $username; - - // ... - } - -En cuanto al sistema de seguridad se refiere, el único requisito para tu clase Usuario personalizada es que implemente la interfaz :class:`Symfony\\Component\\Security\\Core\\User\\UserInterface`. Esto significa que el concepto de un «usuario» puede ser cualquier cosa, siempre y cuando implemente esta interfaz. - -.. versionadded:: 2.1 - En *Symfony* 2.1, se removió el método ``equals`` de la ``UserInterface``. - Si necesitas sustituir la implementación predeterminada de la lógica de comparación, implementa la nueva interfaz :class:`Symfony\\Component\\Security\\Core\\User\\EquatableInterface`. - -.. note:: - - El objeto ``User`` se debe serializar y guardar en la sesión entre peticiones, por lo tanto se recomienda que `implementes la interfaz \Serializable`_ en tu objeto que representa al usuario. Esto es especialmente importante si tu clase ``User`` tiene una clase padre con propiedades privadas. - -A continuación, configura una ``entidad`` proveedora de usuario, y apúntala a tu clase ``User``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - providers: - main: - entity: { class: Acme\UserBundle\Entity\User, property: username } - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'providers' => array( - 'main' => array( - 'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'), - ), - ), - )); - -Con la introducción de este nuevo proveedor, el sistema de autenticación intenta cargar un objeto ``User`` de la base de datos utilizando el campo ``username`` de esa clase. - -.. note:: - Este ejemplo sólo intenta mostrar la idea básica detrás del proveedor ``entity``. Para ver un ejemplo completo funcionando, consulta :doc:`/cookbook/security/entity_provider`. - -Para más información sobre cómo crear tu propio proveedor personalizado (por ejemplo, si necesitas cargar usuarios a través de un servicio *Web*), consulta :doc:`/cookbook/security/custom_provider`. - -.. _book-security-encoding-user-password: - -Codificando la contraseña del usuario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Hasta ahora, por simplicidad, todos los ejemplos tienen las contraseñas de los usuarios almacenadas en texto plano (si los usuarios se almacenan en un archivo de configuración o en alguna base de datos). Por supuesto, en una aplicación real, por razones de seguridad, desearás codificar las contraseñas de los usuarios. Esto se logra fácilmente asignando la clase Usuario a una de las varias integradas en ``encoders``. Por ejemplo, para almacenar los usuarios en memoria, pero ocultar sus contraseñas a través de ``sha1``, haz lo siguiente: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - providers: - in_memory: - memory: - users: - ryan: { password: bb87a29949f3a1ee0559f8a57357487151281386, roles: 'ROLE_USER' } - admin: { password: 74913f5cd5f61ec0bcfdb775414c2fb3d161b620, roles: 'ROLE_ADMIN' } - - encoders: - Symfony\Component\Security\Core\User\User: - algorithm: sha1 - iterations: 1 - encode_as_base64: false - - .. code-block:: xml - - - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - // ... - 'providers' => array( - 'in_memory' => array( - 'memory' => array( - 'users' => array( - 'ryan' => array('password' => 'bb87a29949f3a1ee0559f8a57357487151281386', 'roles' => 'ROLE_USER'), - 'admin' => array('password' => '74913f5cd5f61ec0bcfdb775414c2fb3d161b620', 'roles' => 'ROLE_ADMIN'), - ), - ), - ), - ), - 'encoders' => array( - 'Symfony\Component\Security\Core\User\User' => array( - 'algorithm' => 'sha1', - 'iterations' => 1, - 'encode_as_base64' => false, - ), - ), - )); - -Al establecer las ``iterations`` a ``1`` y ``encode_as_base64`` en ``false``, la contraseña simplemente se corre una vez a través del algoritmo ``sha1`` y sin ninguna codificación adicional. Ahora puedes calcular el ``hash`` de la contraseña mediante programación (por ejemplo, ``hash('sha1', 'ryanpass')``) o a través de alguna herramienta en línea como `functions-online.com`_ - -Si vas a crear tus usuarios dinámicamente (y almacenarlos en una base de datos), puedes utilizar algoritmos ``hash`` aún más difíciles y, luego confiar en un objeto codificador de clave real para ayudarte a codificar las contraseñas. Por ejemplo, supongamos que tu objeto usuario es ``Acme\UserBundle\Entity\User`` (como en el ejemplo anterior). Primero, configura el codificador para ese usuario: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - # ... - - encoders: - Acme\UserBundle\Entity\User: sha512 - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - // ... - 'encoders' => array( - 'Acme\UserBundle\Entity\User' => 'sha512', - ), - )); - -En este caso, estás utilizando el algoritmo ``SHA512`` fuerte. Además, puesto que hemos especificado simplemente el algoritmo (``sha512``) como una cadena, el sistema de manera predeterminada revuelve tu contraseña 5000 veces en una fila y luego la codifica como ``base64``. En otras palabras, la contraseña ha sido fuertemente ofuscada por lo tanto la contraseña revuelta no se puede decodificar (es decir, no se puede determinar la contraseña desde la contraseña ofuscada). - -.. versionadded:: 2.2 - A partir de *Symfony* 2.2 también puedes utilizar los codificadores de contraseña :ref:`PBKDF2 ` y :ref:`BCrypt `. - -Determinando la contraseña codificada -..................................... - -Si tienes algún formulario de registro para los usuarios, necesitas poder determinar el algoritmo de codificación utilizado en la contraseña, para que lo puedas usar en tu usuario. No importa qué algoritmo configures para tu objeto usuario, desde un controlador siempre puedes determinar el algoritmo de codificación de la contraseña de la siguiente manera:: - - $factory = $this->get('security.encoder_factory'); - $user = new Acme\UserBundle\Entity\User(); - - $encoder = $factory->getEncoder($user); - $password = $encoder->encodePassword('ryanpass', $user->getSalt()); - $user->setPassword($password); - -Recuperando el objeto usuario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Después de la autenticación, el objeto ``Usuario`` del usuario actual se puede acceder a través del servicio ``security.context``. Desde el interior de un controlador, este se verá así:: - - public function indexAction() - { - $user = $this->get('security.context')->getToken()->getUser(); - } - -En un controlador existe un atajo para esto: - -.. code-block:: php - - public function indexAction() - { - $user = $this->getUser(); - } - - -.. note:: - - Los usuarios anónimos técnicamente están autenticados, lo cual significa que el método ``isAuthenticated()`` de un objeto usuario anónimo devolverá ``true``. Para comprobar si el usuario está autenticado realmente, verifica el rol ``IS_AUTHENTICATED_FULLY``. - -En una plantilla *Twig* puedes acceder a este objeto a través de la clave ``app.user``, la cual llama al método :method:`GlobalVariables::getUser()`: - -.. configuration-block:: - - .. code-block:: html+jinja - -

Username: {{ app.user.username }}

- - .. code-block:: html+php - -

Username: getUser()->getUsername() ?>

- - -Usando múltiples proveedores de usuario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada mecanismo de autenticación (por ejemplo, la autenticación *HTTP*, formulario de acceso, etc.) utiliza exactamente un proveedor de usuario, y de forma predeterminada utilizará el primer proveedor de usuario declarado. Pero, si deseas especificar unos cuantos usuarios a través de la configuración y el resto de los usuarios en la base de datos? Esto es posible creando un nuevo proveedor que encadene los dos: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - providers: - chain_provider: - chain: - providers: [in_memory, user_db] - in_memory: - memory: - users: - foo: { password: test } - user_db: - entity: { class: Acme\UserBundle\Entity\User, property: username } - - .. code-block:: xml - - - - - - in_memory - user_db - - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'providers' => array( - 'chain_provider' => array( - 'chain' => array( - 'providers' => array('in_memory', 'user_db'), - ), - ), - 'in_memory' => array( - 'memory' => array( - 'users' => array( - 'foo' => array('password' => 'test'), - ), - ), - ), - 'user_db' => array( - 'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'), - ), - ), - )); - -Ahora, todos los mecanismos de autenticación utilizan el ``chain_provider``, puesto que es el primero especificado. El ``chain_provider``, a su vez, intenta cargar el usuario, tanto el proveedor ``in_memory`` cómo ``USER_DB``. - -.. tip:: - - Si no tienes razones para separar a tus usuarios ``in_memory`` de tus usuarios ``user_db``, lo puedes hacer aún más fácil combinando las dos fuentes en un único proveedor: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - providers: - main_provider: - memory: - users: - foo: { password: test } - entity: - class: Acme\UserBundle\Entity\User, - property: username - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'providers' => array( - 'main_provider' => array( - 'memory' => array( - 'users' => array( - 'foo' => array('password' => 'test'), - ), - ), - 'entity' => array('class' => 'Acme\UserBundle\Entity\User', 'property' => 'username'), - ), - ), - )); - -También puedes configurar el cortafuegos o mecanismos de autenticación individuales para utilizar un proveedor específico. Una vez más, a menos que explícitamente especifiques un proveedor, siempre se utiliza el primer proveedor: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - secured_area: - # ... - provider: user_db - http_basic: - realm: "Secured Demo Area" - provider: in_memory - form_login: ~ - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - // ... - 'provider' => 'user_db', - 'http_basic' => array( - // ... - 'provider' => 'in_memory', - ), - 'form_login' => array(), - ), - ), - )); - -En este ejemplo, si un usuario intenta acceder a través de autenticación *HTTP*, el sistema de autenticación debe utilizar el proveedor de usuario ``in_memory``. Pero si el usuario intenta acceder a través del formulario de acceso, utilizará el proveedor ``USER_DB`` (ya que es el valor predeterminado para el servidor de seguridad en su conjunto). - -Para más información acerca de los proveedores de usuario y la configuración del cortafuegos, consulta la :doc:`/reference/configuration/security`. - -Roles ------ - -La idea de un «rol» es clave para el proceso de autorización. Cada usuario tiene asignado un conjunto de roles y cada recurso requiere uno o más roles. Si el usuario tiene los roles necesarios, se le concede acceso. En caso contrario se deniega el acceso. - -Los roles son bastante simples, y básicamente son cadenas que puedes inventar y utilizar cuando sea necesario (aunque los roles son objetos internos). Por ejemplo, si necesitas comenzar a limitar el acceso a la sección ``admin`` del *blog* de tu sitio *web*, puedes proteger esa sección con un rol llamado ``ROLE_BLOG_ADMIN``. Este rol no necesita estar definido en ningún lugar ---puedes comenzar a usarlo. - -.. note:: - - Todos los roles **deben** comenzar con el prefijo ``ROLE_`` el cual será gestionado por *Symfony2*. Si defines tus propios roles con una clase ``Role`` dedicada (más avanzada), no utilices el prefijo ``ROLE_``. - -Roles jerárquicos -~~~~~~~~~~~~~~~~~ - -En lugar de asociar muchos roles a los usuarios, puedes definir reglas de herencia creando una jerarquía de roles: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - role_hierarchy: - ROLE_ADMIN: ROLE_USER - ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] - - .. code-block:: xml - - - - ROLE_USER - ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'role_hierarchy' => array( - 'ROLE_ADMIN' => 'ROLE_USER', - 'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_ALLOWED_TO_SWITCH'), - ), - )); - -En la configuración anterior, los usuarios con rol ``ROLE_ADMIN`` también tendrán el rol de ``ROLE_USER``. El rol ``ROLE_SUPER_ADMIN`` tiene ``ROLE_ADMIN``, ``ROLE_ALLOWED_TO_SWITCH`` y ``ROLE_USER`` (heredado de ``ROLE_ADMIN``). - -Cerrando sesión ---------------- - -Por lo general, también quieres que tus usuarios puedan salir. Afortunadamente, el cortafuegos puede manejar esto automáticamente cuando activas el parámetro de configuración ``logout``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - secured_area: - # ... - logout: - path: /logout - target: / - # ... - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'secured_area' => array( - // ... - 'logout' => array('path' => 'logout', 'target' => '/'), - ), - ), - // ... - )); - -Una vez lo hayas configurado en tu cortafuegos, enviar a un usuario a ``/logout`` (o cualquiera que sea tu ``path`` configurada), desautenticará al usuario actual. El usuario será enviado a la página de inicio (el valor definido por el parámetro ``target``). Ambos parámetros ``path`` y ``target`` por omisión se configuran a lo que esté especificado aquí. En otras palabras, a menos que necesites personalizarlos, los puedes omitir por completo y abreviar tu configuración: - -.. configuration-block:: - - .. code-block:: yaml - - logout: ~ - - .. code-block:: xml - - - - .. code-block:: php - - 'logout' => array(), - -Ten en cuenta que *no* es necesario implementar un controlador para la *URL* ``/logout`` porque el cortafuegos se encarga de todo. Sin embargo, posiblemente necesites crear una ruta para que la puedas utilizar para generar la *URL*: - -.. caution:: - - A partir de *Symfony 2.1* **debes** tener una ruta que corresponda a la ruta para cerrar la sesión. Sin esta ruta, el cierre de sesión no trabajará. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - logout: - path: /logout - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('logout', new Route('/logout', array())); - - return $collection; - -Una vez que el usuario ha cerrado la sesión, será redirigido a cualquier ruta definida por el parámetro ``target`` anterior (por ejemplo, la página ``principal``). Para más información sobre cómo configurar el cierre de sesión, consulta la :doc:`Referencia para afinar el sistema de seguridad `. - -.. _book-security-template: - -Controlando el acceso en plantillas ------------------------------------ - -Si dentro de una plantilla deseas comprobar si el usuario actual tiene un rol, utiliza la función ayudante incorporada: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% if is_granted('ROLE_ADMIN') %} - Delete - {% endif %} - - .. code-block:: html+php - - isGranted('ROLE_ADMIN')): ?> - Delete - - -.. note:: - - Si utilizas esta función y *no* estás en una *URL* donde haya un cortafuegos activo, se lanzará una excepción. Una vez más, casi siempre es buena idea tener un cortafuegos principal que cubra todas las *URL* (como hemos mostrado en este capítulo). - -Controlando el acceso en controladores --------------------------------------- - -Si deseas comprobar en tu controlador si el usuario actual tiene un rol, utiliza el método :method:`Symfony\\Component\\Security\\Core\\SecurityContext::isGranted` del contexto de seguridad:: - - public function indexAction() - { - // a los usuarios 'admin' les muestra diferente contenido - if ($this->get('security.context')->isGranted('ROLE_ADMIN')) { - // ... aquí carga el contenido 'admin' - } - - // ... aquí carga otro contenido regular - } - -.. note:: - - Debe haber un cortafuegos activo o al llamar al método ``isGranted`` se producirá una excepción. Ve la nota anterior acerca de las plantillas para más detalles. - -Suplantando a un usuario ------------------------- - -A veces, es útil poder cambiar de un usuario a otro sin tener que iniciar sesión de nuevo (por ejemplo, cuando depuras o tratas de entender un error que un usuario ve y que no se puede reproducir). Esto se puede hacer fácilmente activando el escucha ``switch_user`` del cortafuegos: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - main: - # ... - switch_user: true - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'main'=> array( - // ... - 'switch_user' => true - ), - ), - )); - -Para cambiar a otro usuario, sólo tienes que añadir una cadena de consulta con el parámetro ``_switch_user`` y el nombre de usuario como el valor de la dirección actual: - -.. code-block:: text - - http://ejemplo.com/somewhere?_switch_user=thomas - -Para volver al usuario original, utiliza el nombre de usuario especial ``_exit``: - -.. code-block:: text - - http://ejemplo.com/somewhere?_switch_user=_exit - -Durante la suplantación, el usuario está provisto con una función de rol especial -llamada ``ROLE_PREVIOUS_ADMIN``. En una plantilla, por ejemplo, este rol se puede usar para mostrar un enlace para salir de la suplantación: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% if is_granted('ROLE_PREVIOUS_ADMIN') %} - Exit impersonation - {% endif %} - - .. code-block:: html+php - - isGranted('ROLE_PREVIOUS_ADMIN')): ?> - - Exit impersonation - - - -Por supuesto, esta función se debe poner a disposición de un pequeño grupo de usuarios. -De forma predeterminada, el acceso está restringido a usuarios que tienen el rol ``ROLE_ALLOWED_TO_SWITCH``. El nombre de esta función se puede modificar a través de la configuración ``role``. Para mayor seguridad, también puedes cambiar el nombre del parámetro de consulta a través de la configuración ``parameter``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - main: - # ... - switch_user: { role: ROLE_ADMIN, parameter: _want_to_be_this_user } - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'main'=> array( - // ... - 'switch_user' => array('role' => 'ROLE_ADMIN', 'parameter' => '_want_to_be_this_user'), - ), - ), - )); - -Autenticación apátrida ----------------------- - -De forma predeterminada, *Symfony2* confía en una :dfn:`cookie` (la Sesión) para persistir el contexto de seguridad del usuario. Pero si utilizas certificados o autenticación *HTTP*, por ejemplo, la persistencia no es necesaria ya que están disponibles las credenciales para cada petición. En ese caso, y si no es necesario almacenar cualquier otra cosa entre peticiones, puedes activar la autenticación apátrida (lo cual significa que *Symfony2* jamás creará una :dfn:`cookie`): - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/security.yml - security: - firewalls: - main: - http_basic: ~ - stateless: true - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/security.php - $container->loadFromExtension('security', array( - 'firewalls' => array( - 'main' => array('http_basic' => array(), 'stateless' => true), - ), - )); - -.. note:: - - Si utilizas un formulario de acceso, *Symfony2* creará una :dfn:`cookie`, incluso si estableces ``stateless`` a ``true``. - -Utilerías ---------- - -.. versionadded:: 2.2 - Las clases ``StringUtils`` y ``SecureRandom`` se añadieron en *Symfony 2.2* - -El componente de seguridad de *Symfony* viene con una colección de agradables utilidades relacionadas con la seguridad. Estas utilidades las usa *Symfony*, pero también las deberías utilizar si quieres solucionar los problemas a que están destinadas. - -Comparando cadenas -~~~~~~~~~~~~~~~~~~ - -El tiempo que tome comparar dos cadenas depende de sus diferencias. Este lo puede utilizar un atacante cuándo las dos cadenas representan una contraseña por ejemplo; Este se conoce como `Ataque temporizado`_. - -Internamente, al comparar dos contraseñas, *Symfony* utiliza un algoritmo de tiempo -constante; Puedes utilizar la misma estrategia en tu propio código gracias a la clase :class:`Symfony\\Component\\Security\\Core\\Util\\StringUtils`:: - - use Symfony\Component\Security\Core\Util\StringUtils; - - // ¿es igual la contraseña1 a la contraseña2? - $bool = StringUtils::equals($password1, $password2); - -Generando aleatoriamente un número seguro -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Siempre que necesites generar un número aleatorio seguro, te -animamos a utilizar la clase :class:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom` de *Symfony*:: - - use Symfony\Component\Security\Core\Util\SecureRandom; - - $generator = new SecureRandom(); - $random = $generator->nextBytes(10); - -El método :method:`Symfony\\Component\\Security\\Core\\Util\\SecureRandom::nextBytes` -regresa una cadena aleatoria compuesta del número de caracteres pasados como argumento (10 en el ejemplo anterior). - -La clase *SecureRandom* trabaja mejor cuándo está instalado *OpenSSL* pero cuándo no -está disponible, recae en un algoritmo interno, el cual necesita un archivo de semilla -para trabajar correctamente. Sólo suministra un nombre de archivo para habilitarlo:: - - $generator = new SecureRandom('/alguna/ruta/para/guardar/la/semilla.txt'); - $random = $generator->nextBytes(10); - -.. note:: - - También puedes acceder a una instancia aleatoria segura directamente desde el contenedor de inyección de dependencias de *Symfony*; Su nombre es ``security.secure_random``. - -Palabras finales ----------------- - -La seguridad puede ser un tema profundo y complejo para resolverlo correctamente en tu aplicación. -Afortunadamente, el componente de seguridad de *Symfony* sigue un modelo de seguridad bien probado en torno a la *autenticación* y *autorización*. La autenticación, siempre sucede en primer lugar, está a cargo de un cortafuegos, cuyo trabajo es determinar la identidad del usuario a través de varios métodos diferentes (por ejemplo, la autenticación *HTTP*, formulario de acceso, etc.) En el recetario, encontrarás ejemplos de otros métodos para manejar la autenticación, incluyendo la manera de implementar una funcionalidad «recuérdame» por medio de :dfn:`cookie`. - -Una vez que un usuario se autentica, la capa de autorización puede determinar si el usuario debe tener acceso a un recurso específico. Por lo general, los *roles* se aplican a *URL*, clases o métodos y si el usuario actual no tiene ese rol, se le niega el acceso. La capa de autorización, sin embargo, es mucho más profunda, y sigue un sistema de «voto» para que varias partes puedan determinar si el usuario actual debe tener acceso a un determinado recurso. -Para saber más sobre este y otros temas busca en el recetario. - -Aprende más en el recetario ---------------------------- - -* :doc:`Forzando HTTP/HTTPS ` -* :doc:`Agregando usuarios a la lista negra por dirección IP con un votante personalizado ` -* :doc:`Listas de control de acceso (ACL) ` -* :doc:`/cookbook/security/remember_me` - -.. _`componente Security de Symfony`: https://github.com/symfony/Security -.. _`JMSSecurityExtraBundle`: http://gitnacho.github.com/symfony-docs-es/bundles/JMSSecurityExtraBundle/index.html -.. _`FOSUserBundle`: https://github.com/FriendsOfSymfony/FOSUserBundle -.. _`implementes la interfaz \Serializable`: http://www.php.net/manual/es/class.serializable.php -.. _`functions-online.com`: http://www.functions-online.com/sha1.html -.. _`Ataque temporizado`: http://en.wikipedia.org/wiki/Timing_attack diff --git a/_sources/book/service_container.txt b/_sources/book/service_container.txt deleted file mode 100644 index b4d43c6..0000000 --- a/_sources/book/service_container.txt +++ /dev/null @@ -1,750 +0,0 @@ -.. index:: - single: Contenedor de Servicios - single: Inyección de dependencias; Contenedor - -Contenedor de servicios -======================= - -Una aplicación *PHP* moderna está llena de objetos. Un objeto te puede facilitar la entrega de mensajes de correo electrónico, mientras que otro te puede permitir mantener información en una base de datos. En tu aplicación, puedes crear un objeto que gestione tu inventario de productos, y otro objeto que procese los datos de una *API* de terceros. El punto es que una aplicación moderna hace muchas cosas y está organizada en muchos objetos que se encargan de cada tarea. - -Este capítulo es sobre un objeto *PHP* especial en *Symfony2* que te ayuda a crear instancias, organizar y recuperar los muchos objetos de tu aplicación. -Este objeto, llamado contenedor de servicios, te permitirá estandarizar y centralizar la forma en que se construyen los objetos en tu aplicación. El contenedor te facilita la vida, es superveloz, y enfatiza una arquitectura que promueve el código reutilizable y disociado. Y debido a que todas las clases de *Symfony2* básicas usan el contenedor, aprenderás cómo ampliar, configurar y utilizar cualquier objeto en *Symfony2*. En gran parte, el contenedor de servicios es el mayor contribuyente a la velocidad y extensibilidad de *Symfony2*. - -Por último, configurar y usar el contenedor de servicios es fácil. Al final de este capítulo, te sentirás cómodo creando tus propios objetos y personalizando objetos de cualquier paquete de terceros a través del contenedor. Empezarás a escribir código más reutilizable, comprobable y disociado, simplemente porque el contenedor de servicios facilita la escritura de buen código. - -.. tip:: - - Si quieres conocer un poco más después de leer este capítulo, revisa la :doc:`Documentación del componente de inyección de dependencias `. - -.. index:: - single: Contenedor de servicios; ¿Qué es un servicio? - -¿Qué es un servicio? --------------------- - -En pocas palabras, un :term:`Servicio` es cualquier objeto *PHP* que realiza algún tipo de tarea «global». Es un nombre genérico que se utiliza a propósito en informática para describir un objeto creado para un propósito específico (por ejemplo, la entrega de mensajes de correo electrónico). Cada servicio se utiliza en toda tu aplicación cada vez que necesites la funcionalidad específica que proporciona. No tienes que hacer nada especial para hacer un servicio: simplemente escribe una clase *PHP* con algo de código que realice una tarea específica. ¡Felicidades, acabas de crear un servicio! - -.. note:: - - Por regla general, un objeto *PHP* es un servicio si se utiliza a nivel global en tu aplicación. Utilizamos un solo servicio ``Mailer`` a nivel global para enviar mensajes de correo electrónico mientras que muchos objetos ``Mensaje`` que este entrega **no** son servicios. Del mismo modo, un objeto ``Producto`` **no** es un servicio, pero un objeto que persiste objetos ``Producto`` a una base de datos **es** un servicio. - -Entonces, ¿cuál es la ventaja? La ventaja de pensar en «servicios» es que comienzas a pensar en la separación de cada parte de la funcionalidad de tu aplicación como una serie de servicios. Puesto que cada servicio se limita a un trabajo, puedes acceder fácilmente a cada servicio y usar su funcionalidad siempre que la necesites. Cada servicio también se puede probar y configurar muy fácilmente, ya que está separado de la otra funcionalidad de tu aplicación. Esta idea se llama `arquitectura orientada a servicios`_ y no es única de *Symfony2* e incluso de *PHP*. Estructurando tu aplicación en torno a un conjunto de clases *Servicio* independientes es una bien conocida y confiable práctica mejor orientada a objetos. Estas habilidades son clave para ser un buen desarrollador en casi cualquier lenguaje. - -.. index:: - single: Contenedor de servicios; ¿Qué es un contenedor de servicios? - -¿Qué es un contenedor de servicios? ------------------------ - -Un :term:`Contenedor de servicios` (o *contenedor de inyección de dependencias*) simplemente es un objeto *PHP* que gestiona la creación de instancias de servicios (es decir, objetos). - -Por ejemplo, supongamos que tienes una simple clase *PHP* que envía mensajes de correo electrónico. -Sin un contenedor de servicios, debes crear manualmente el objeto cada vez que lo necesites:: - - use Acme\HelloBundle\Mailer; - - $mailer = new Mailer('sendmail'); - $mailer->send('ryan@foobar.net', ...); - -Esto es bastante fácil. La clase imaginaria ``Mailer`` te permite configurar el método utilizado para entregar los mensajes de correo electrónico (por ejemplo, ``sendmail``, ``smtp``, etc.). -Pero, ¿si quisieras utilizar el servicio *mailer* en algún otro lugar? Desde luego, no quieres repetir la configuración del *mailer* cada vez que necesites utilizar el objeto ``Mailer``. Qué pasa si necesitas cambiar el ``transporte`` de ``sendmail`` a ``smtp`` en toda tu aplicación? Tendrías que cazar cada sitio en que creas un servicio ``Mailer`` y cambiarlo. - -.. index:: - single: Contenedor de servicios; Configurando servicios - -Creando/configurando servicios en el contenedor ------------------------------------------------ - -Una mejor respuesta es dejar que el contenedor de servicios cree el objeto ``Mailer`` para ti. Para que esto funcione, debes *enseñar* al contenedor cómo crear el servicio ``Mailer``. Esto se hace a través de configuración, la cual se puede especificar en *YAML*, *XML* o *PHP*: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - services: - my_mailer: - class: Acme\HelloBundle\Mailer - arguments: [sendmail] - - .. code-block:: xml - - - - - sendmail - - - - .. code-block:: php - - // app/config/config.php - use Symfony\Component\DependencyInjection\Definition; - - $container->setDefinition('my_mailer', new Definition( - 'Acme\HelloBundle\Mailer', - array('sendmail') - )); - -.. note:: - - Cuando se inicia, por omisión *Symfony2* construye el contenedor de servicios usando la configuración de (:file:`app/config/config.yml`). El archivo exacto que se carga es dictado por el método ``AppKernel::registerContainerConfiguration()``, el cual carga un archivo de configuración específico al entorno (por ejemplo, :file:`config_dev.yml` para el entorno ``dev`` o :file:`config_prod.yml` para ``prod``). - -Una instancia del objeto ``Acme\HelloBundle\Mailer`` ahora está disponible a través del contenedor de servicios. El contenedor está disponible en cualquier controlador tradicional de *Symfony2*, donde puedes acceder al servicio del contenedor a través del método ``get()``:: - - class HelloController extends Controller - { - // ... - - public function sendEmailAction() - { - // ... - $mailer = $this->get('my_mailer'); - $mailer->send('ryan@foobar.net', ...); - } - } - -Cuando solicites el servicio ``my_mailer`` desde el contenedor, el contenedor construye el objeto y te lo devuelve. Esta es otra de las principales ventajas de utilizar el contenedor de servicios. Es decir, un servicio *nunca* se construye hasta que es necesario. Si defines un servicio y no lo utilizas en una petición, el servicio no se crea. Esto ahorra memoria y aumenta la velocidad de tu aplicación. -Esto también significa que la sanción en rendimiento por definir muchos servicios es mínima o inexistente. Los servicios que nunca se usan nunca se construyen. - -Como bono adicional, el servicio ``Mailer`` se crea sólo una vez y esa misma instancia se vuelve a utilizar cada vez que solicites el servicio. Este es el comportamiento que ---casi siempre--- necesitarás (el cual es más flexible y potente), pero, más adelante aprenderás cómo puedes configurar un servicio que tiene varias instancias en el artículo «:doc:`/cookbook/service_container/scopes`» del recetario. - -.. _book-service-container-parameters: - -Parámetros del servicio ------------------------ - -La creación de nuevos servicios (es decir, objetos) a través del contenedor es bastante sencilla. Los parámetros provocan que al definir los servicios estén más organizados y sean más flexibles: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - parameters: - my_mailer.class: Acme\HelloBundle\Mailer - my_mailer.transport: sendmail - - services: - my_mailer: - class: "%my_mailer.class%" - arguments: ["%my_mailer.transport%"] - - .. code-block:: xml - - - - Acme\HelloBundle\Mailer - sendmail - - - - - %my_mailer.transport% - - - - .. code-block:: php - - // app/config/config.php - use Symfony\Component\DependencyInjection\Definition; - - $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer'); - $container->setParameter('my_mailer.transport', 'sendmail'); - - $container->setDefinition('my_mailer', new Definition( - '%my_mailer.class%', - array('%my_mailer.transport%') - )); - -El resultado final es exactamente el mismo que antes ---la diferencia sólo está en *cómo* defines el servicio---. Al rodear las cadenas ``my_mailer.class`` y ``my_mailer.transport`` entre signos de porcentaje (``%``), el contenedor sabe que tiene que buscar los parámetros con esos nombres. Cuando se construye el contenedor, este busca el valor de cada parámetro y lo utiliza en la definición del servicio. - -.. versionadded:: 2.1 - Escapar el carácter ``@`` en los valores de parámetros *YAML* es nuevo en *Symfony 2.1.9* y *Symfony 2.2.1*. - -.. note:: - - Si quieres utilizar una cadena que comience con un signo de ``@`` como valor del parámetro (es decir, una muy segura contraseña de correo) en un archivo *yaml*, necesitas escaparlo añadiendo otra ``@`` (Esto sólo aplica al formato *YAML*): - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - # Esto será analizado como la cadena "@clavesegura" - mailer_password: "@@clavesegura" - -.. note:: - - El signo de porcentaje en un parámetro o argumento, como parte de la cadena, se debe escapar con otro signo de porcentaje: - - .. code-block:: xml - - http://symfony.com/?foo=%%s&bar=%%d - -.. caution:: - - Puedes recibir una :class:`Symfony\\Component\\DependencyInjection\\Exception\\ScopeWideningInjectionException` al pasar el servicio ``request`` como argumento. Para entender este mejor problema y aprender cómo solucionarlo, consulta el artículo :doc:`/cookbook/service_container/scopes` en el recetario. - -El propósito de los parámetros es alimentar información a los servicios. Por supuesto, no hay nada malo en definir el servicio sin utilizar ningún parámetro. -Los parámetros, sin embargo, tienen varias ventajas: - -* Separan y organizan todo el servicio en «opciones» bajo una sola clave ``parameters``; - -* Los valores del parámetro se pueden utilizar en la definición de múltiples servicios; - -* Cuando creas un servicio en un paquete (mostrado en breve), el uso de parámetros facilita la personalización del servicio en tu aplicación. - -La opción de usar o no parámetros depende de ti. Los paquetes de terceros de alta calidad *siempre* usan parámetros, ya que producen servicios más configurables almacenados en el contenedor. Para los servicios de tu aplicación, sin embargo, posiblemente no necesites la flexibilidad de los parámetros. - -Arreglo de parámetros -~~~~~~~~~~~~~~~~~~~~~ - -Los parámetros también pueden contener arreglos como valores. Ve :ref:`component-di-parameters-array`. - -Importando la configuración de recursos desde otros contenedores ----------------------------------------------------------------- - -.. tip:: - - En esta sección, nos referiremos a los archivos de configuración de servicios como *recursos*. - Esto es para resaltar el hecho de que, si bien la mayoría de la configuración de recursos debe estar en archivos (por ejemplo, *YAML*, *XML*, *PHP*), *Symfony2* es tan flexible que la configuración se puede cargar desde cualquier lugar (por ejemplo, una base de datos e incluso a través de un servicio web externo). - -El contenedor de servicios se construye usando un recurso de configuración simple (:file:`app/config/config.yml` por omisión). Toda la configuración de otros servicios (incluido el núcleo de *Symfony2* y la configuración de paquetes de terceros) se debe importar desde el interior de este archivo de una u otra manera. Esto proporciona absoluta flexibilidad sobre los servicios en tu aplicación. - -La configuración externa de servicios se puede importar de dos diferentes maneras. La primera --- y el método más común--- es vía la directiva ``imports``. Más tarde, veremos el segundo método, el cual es el método más flexible y preferido -para importar la configuración de servicios desde paquetes de terceros. - -.. index:: - single: Contenedor de servicios; Imports - -.. _service-container-imports-directive: - -Importando configuración con ``imports`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Hasta ahora, has puesto tu definición del contenedor del servicios ``my_mailer`` directamente en el archivo de configuración de la aplicación (por ejemplo, :file:`app/config/config.yml`). Por supuesto, debido a que la clase ``Mailer`` vive dentro de ``AcmeHelloBundle``, también tiene más sentido poner la definición del contenedor de ``my_mailer`` en el paquete. - -En primer lugar, mueve la definición del contenedor de ``my_mailer`` a un nuevo archivo contenedor de recursos dentro de ``AcmeHelloBundle``. Si los directorios ``Resources`` y ``Resources/config`` no existen, créalos. - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - my_mailer.class: Acme\HelloBundle\Mailer - my_mailer.transport: sendmail - - services: - my_mailer: - class: "%my_mailer.class%" - arguments: ["%my_mailer.transport%"] - - .. code-block:: xml - - - - Acme\HelloBundle\Mailer - sendmail - - - - - %my_mailer.transport% - - - - .. code-block:: php - - // src/Acme/HelloBundle/Resources/config/services.php - use Symfony\Component\DependencyInjection\Definition; - - $container->setParameter('my_mailer.class', 'Acme\HelloBundle\Mailer'); - $container->setParameter('my_mailer.transport', 'sendmail'); - - $container->setDefinition('my_mailer', new Definition( - '%my_mailer.class%', - array('%my_mailer.transport%') - )); - -La propia definición no ha cambiado, sólo su ubicación. Por supuesto, el contenedor de servicios no sabe sobre el nuevo archivo de recursos. Afortunadamente, es fácil importar el archivo de recursos utilizando la clave ``imports`` en la configuración de la aplicación. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - imports: - - { resource: "@AcmeHelloBundle/Resources/config/services.yml" } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $this->import('@AcmeHelloBundle/Resources/config/services.php'); - -La directiva ``imports`` permite a tu aplicación incluir recursos de configuración del contenedor de servicios desde cualquier otro lugar (comúnmente desde paquetes). -La ubicación de ``resources``, para archivos, es la ruta absoluta al archivo de recursos. La sintaxis especial ``@AcmeHello`` resuelve la ruta al directorio del paquete ``AcmeHelloBundle``. Esto te ayuda a especificar la ruta a los recursos sin tener que preocuparte más adelante de si se mueve el ``AcmeHelloBundle`` a un directorio diferente. - -.. index:: - single: Contenedor de servicios; Configuración de extensión - -.. _service-container-extension-configuration: - -Importando configuración vía el contenedor de extensiones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando desarrollas en *Symfony2*, comúnmente debes usar la directiva ``imports`` para importar la configuración del contenedor desde los paquetes que has creado específicamente para tu aplicación. La configuración del contenedor de paquetes de terceros, incluyendo los servicios básicos de *Symfony2*, normalmente se carga con cualquier otro método que sea más flexible y fácil de configurar en tu aplicación. - -Así es como funciona. Internamente, cada paquete define sus servicios de manera muy parecida a lo que has visto hasta ahora. Es decir, un paquete utiliza uno o más archivos de configuración de recursos (por lo general *XML*) para especificar los parámetros y servicios para ese paquete. Sin embargo, en lugar de importar cada uno de estos recursos directamente desde la configuración de tu aplicación utilizando la directiva ``imports``, sólo tienes que invocar una *extensión contenedora de servicios* dentro del paquete, la cual hace el trabajo por ti. Una extensión del contenedor de servicios es una clase *PHP* creada por el autor del paquete para lograr dos cosas: - -* Importar todos los recursos del contenedor de servicios necesarios para configurar los servicios del paquete; - -* Permitir una configuración semántica y directa para poder ajustar el paquete sin interactuar con los parámetros planos de configuración del paquete contenedor del servicio. - -En otras palabras, una extensión del contenedor de servicios configura los servicios para un paquete en tu nombre. Y como veremos en un momento, la extensión proporciona una interfaz sensible y de alto nivel para configurar el paquete. - -Tomemos el ``FrameworkBundle`` ---el núcleo de la plataforma *Symfony2*--- como ejemplo. La presencia del siguiente código en la configuración de tu aplicación invoca a la extensión ``FrameworkBundle`` en el interior del contenedor de servicios: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - secret: xxxxxxxxxx - form: true - csrf_protection: true - router: { resource: "%kernel.root_dir%/config/routing.yml" } - # ... - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'secret' => 'xxxxxxxxxx', - 'form' => array(), - 'csrf-protection' => array(), - 'router' => array('resource' => '%kernel.root_dir%/config/routing.php'), - - // ... - )); - -Cuando se analiza la configuración, el contenedor busca una extensión que pueda manejar la directiva de configuración ``framework``. La extensión en cuestión, que vive en el ``FrameworkBundle``, es invocada y cargada la configuración del servicio para el ``FrameworkBundle``. Si quitas por completo la clave ``framework`` del archivo de configuración de tu aplicación, no se cargarán los servicios básicos de *Symfony2*. El punto es que tú tienes el control: la plataforma *Symfony2* no contiene ningún tipo de magia ni realiza alguna acción en que tú no tengas el control. - -Por supuesto, puedes hacer mucho más sencilla la «activación» de la extensión ``FrameworkBundle`` en el contenedor de servicios. Cada extensión te permite personalizar fácilmente el paquete, sin tener que preocuparte acerca de cómo se definen los servicios internos. - -En este caso, la extensión te permite personalizar la configuración de ``error_handler``, ``csrf_protection``, ``router`` y mucho más. Internamente, el ``FrameworkBundle`` utiliza las opciones especificadas aquí para definir y configurar los servicios específicos del mismo. El paquete se encarga de crear todos los ``parámetros`` y ``servicios`` necesarios para el contenedor de servicios, mientras permite que la mayor parte de la configuración se pueda personalizar fácilmente. Como bono adicional, la mayoría de las extensiones del contenedor de servicios también son lo suficientemente inteligentes como para realizar la validación ---notificándote opciones omitidas o datos de tipo incorrecto. - -Al instalar o configurar un paquete, consulta la documentación del paquete sobre cómo se deben instalar y configurar sus servicios. Las opciones disponibles para los paquetes básicos se pueden encontrar dentro de la :doc:`Guía de Referencia `. - -.. note:: - - Nativamente, el contenedor de servicios sólo reconoce las directivas ``parameters``, ``services`` e ``imports``. Cualquier otra directiva es manejada por una extensión del contenedor de servicios. - -Si quieres exponer la configuración fácil en tus propios paquetes, lee -«:doc:`/cookbook/bundles/extension`» del recetario. - -.. index:: - single: Contenedor de servicios; Refiriendo servicios - -Refiriendo (inyectando) servicios ---------------------------------- - -Hasta el momento, nuestro servicio original ``my_mailer`` es simple: sólo toma un argumento en su constructor, el cual es fácilmente configurable. Como verás, el poder real del contenedor se lleva a cabo cuando es necesario crear un servicio que depende de uno o varios otros servicios en el contenedor. - -Por ejemplo, supongamos que tienes un nuevo servicio, ``NewsletterManager``, que ayuda a gestionar la preparación y entrega de un mensaje de correo electrónico a una colección de direcciones. Por supuesto el servicio ``my_mailer`` ciertamente ya es bueno en la entrega de mensajes de correo electrónico, así que lo usaremos dentro de ``NewsletterManager`` para manejar la entrega real de los mensajes. Se pretende que esta clase pudiera ser algo como esto:: - - // src/Acme/HelloBundle/Newsletter/NewsletterManager.php - namespace Acme\HelloBundle\Newsletter; - - use Acme\HelloBundle\Mailer; - - class NewsletterManager - { - protected $mailer; - - public function __construct(Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -Sin utilizar el contenedor de servicios, podemos crear un nuevo ``NewsletterManager`` muy fácilmente desde el interior de un controlador:: - - use Acme\HelloBundle\Newsletter\NewsletterManager; - - // ... - - public function sendNewsletterAction() - { - $mailer = $this->get('my_mailer'); - $newsletter = new NewsletterManager($mailer); - // ... - } - -Este enfoque está bien, pero, ¿si más adelante decides que la clase ``NewsletterManager`` necesita un segundo o tercer argumento constructor? ¿Y si decides reconstruir tu código y cambiar el nombre de la clase? En ambos casos, habría que encontrar todos los lugares donde se crea una instancia de ``NewsletterManager`` y modificarla. Por supuesto, el contenedor de servicios te da una opción mucho más atractiva: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager - - services: - my_mailer: - # ... - newsletter_manager: - class: %newsletter_manager.class% - arguments: ["@my_mailer"] - - .. code-block:: xml - - - - - Acme\HelloBundle\Newsletter\NewsletterManager - - - - - - - - - - - - .. code-block:: php - - // src/Acme/HelloBundle/Resources/config/services.php - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter( - 'newsletter_manager.class', - 'Acme\HelloBundle\Newsletter\NewsletterManager' - ); - - $container->setDefinition('my_mailer', ...); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', - array(new Reference('my_mailer')) - )); - -En *YAML*, la sintaxis especial ``@my_mailer`` le dice al contenedor que busque un servicio llamado ``my_mailer`` y pase ese objeto al constructor de ``NewsletterManager``. En este caso, sin embargo, el servicio especificado ``my_mailer`` debe existir. Si no es así, lanzará una excepción. Puedes marcar tus dependencias como opcionales --- explicaremos esto en la siguiente sección. - -La utilización de referencias es una herramienta muy poderosa que te permite crear clases de servicios independientes con dependencias bien definidas. En este ejemplo, el servicio ``newsletter_manager`` necesita del servicio ``my_mailer`` para poder funcionar. Al definir esta dependencia en el contenedor de servicios, el contenedor se encarga de todo el trabajo de crear instancias de objetos. - -Dependencias opcionales: Inyección del definidor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Inyectar dependencias en el constructor de esta manera es una excelente manera de asegurarte que la dependencia está disponible para usarla. Si tienes dependencias opcionales para una clase, entonces, la «inyección del definidor» puede ser una mejor opción. Esto significa inyectar la dependencia usando una llamada a método en lugar de a través del constructor. La clase se vería así:: - - namespace Acme\HelloBundle\Newsletter; - - use Acme\HelloBundle\Mailer; - - class NewsletterManager - { - protected $mailer; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -La inyección de la dependencia por medio del método definidor sólo necesita un cambio de sintaxis: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - newsletter_manager.class: Acme\HelloBundle\Newsletter\NewsletterManager - - services: - my_mailer: - # ... - newsletter_manager: - class: %newsletter_manager.class% - calls: - - [setMailer, ["@my_mailer"]] - - .. code-block:: xml - - - - - Acme\HelloBundle\Newsletter\NewsletterManager - - - - - - - - - - - - - - .. code-block:: php - - // src/Acme/HelloBundle/Resources/config/services.php - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter( - 'newsletter_manager.class', - 'Acme\HelloBundle\Newsletter\NewsletterManager' - ); - - $container->setDefinition('my_mailer', ...); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%' - ))->addMethodCall('setMailer', array( - new Reference('my_mailer'), - )); - -.. note:: - - Los enfoques presentados en esta sección se llaman «inyección de constructor» e «inyección de definidor». El contenedor de servicios de *Symfony2* también es compatible con la «inyección de propiedad». - -Haciendo que las referencias sean opcionales --------------------------------------------- - -A veces, uno de tus servicios puede tener una dependencia opcional, lo cual significa que la dependencia no es necesaria para que el servicio funcione correctamente. En el ejemplo anterior, el servicio ``my_mailer`` *debe* existir, si no, será lanzada una excepción. Al modificar la definición del servicio ``newsletter_manager``, puedes hacer opcional esta referencia. Entonces, el contenedor será inyectado si es que existe y no hace nada si no: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - - services: - newsletter_manager: - class: %newsletter_manager.class% - arguments: ["@?my_mailer"] - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // src/Acme/HelloBundle/Resources/config/services.php - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - use Symfony\Component\DependencyInjection\ContainerInterface; - - // ... - $container->setParameter( - 'newsletter_manager.class', - 'Acme\HelloBundle\Newsletter\NewsletterManager' - ); - - $container->setDefinition('my_mailer', ...); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', - array( - new Reference( - 'my_mailer', - ContainerInterface::IGNORE_ON_INVALID_REFERENCE - ) - ) - )); - -En *YAML*, la sintaxis especial ``@?`` le dice al contenedor de servicios que la dependencia es opcional. Por supuesto, también debes escribir ``NewsletterManager`` para que permita una dependencia opcional:: - - public function __construct(Mailer $mailer = null) - { - // ... - } - -El núcleo de *Symfony* y servicios en paquetes de terceros ----------------------------------------------------------- - -Puesto que *Symfony2* y todos los paquetes de terceros configuran y recuperan sus servicios a través del contenedor, puedes acceder fácilmente a ellos e incluso utilizarlos en tus propios servicios. Para mantener las cosas simples, de manera predeterminada *Symfony2* no requiere que los controladores se definan como servicios. Además *Symfony2* inyecta el contenedor de servicios completo en el controlador. Por ejemplo, para manejar el almacenamiento de información sobre la sesión de un usuario, *Symfony2* proporciona un servicio ``sesión``, al cual se puede acceder dentro de un controlador estándar de la siguiente manera:: - - public function indexAction($bar) - { - $session = $this->get('session'); - $session->set('foo', $bar); - - // ... - } - -En *Symfony2*, constantemente vas a utilizar los servicios prestados por el núcleo de *Symfony* o paquetes de terceros para realizar tareas como la reproducción de plantillas (``templating``), el envío de mensajes de correo electrónico (``mailer``), o para acceder a información sobre la petición. - -Puedes dar un paso más allá usando estos servicios dentro de los servicios que has creado para tu aplicación. Empieza modificando el ``NewsletterManager`` para usar el gestor de correo real de *Symfony2*, el servicio ``mailer`` (en vez del pretendido ``my_mailer``). -También vamos a pasar el servicio del motor de plantillas al ``NewsletterManager`` para que puedas generar el contenido del correo electrónico a través de una plantilla:: - - namespace Acme\HelloBundle\Newsletter; - - use Symfony\Component\Templating\EngineInterface; - - class NewsletterManager - { - protected $mailer; - - protected $templating; - - public function __construct(\Swift_Mailer $mailer, EngineInterface $templating) - { - $this->mailer = $mailer; - $this->templating = $templating; - } - - // ... - } - -Configurar el contenedor de servicios es fácil: - -.. configuration-block:: - - .. code-block:: yaml - - services: - newsletter_manager: - class: %newsletter_manager.class% - arguments: ["@mailer", "@templating"] - - .. code-block:: xml - - - - - - - .. code-block:: php - - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', - array( - new Reference('mailer'), - new Reference('templating'), - ) - )); - -El servicio ``newsletter_manager`` ahora tiene acceso a los servicios del núcleo ``mailer`` y ``templating``. Esta es una forma común de crear servicios específicos para tu aplicación que aprovechan el poder de los distintos servicios en la plataforma. - -.. tip:: - - Asegúrate que la clave ``swiftmailer`` aparece en la configuración de tu aplicación. Como se mencionó en :ref:`service-container-extension-configuration`, la clave ``SwiftMailer`` invoca a la extensión de servicio desde el ``SwiftmailerBundle``, el cual registra el servicio ``mailer``. - -.. _book-service-container-tags: - -Etiquetas ---------- - -De la misma manera que en la *Web* una entrada de *blog* se puede etiquetar con cosas tales como *«Symfony»* o *«PHP»*, los servicios configurados en el contenedor también se pueden etiquetar. En el contenedor de servicios, una etiqueta implica que el servicio está destinado a usarse para un propósito específico. Tomemos el siguiente ejemplo: - -.. configuration-block:: - - .. code-block:: yaml - - services: - foo.twig.extension: - class: Acme\HelloBundle\Extension\FooExtension - tags: - - { name: twig.extension } - - .. code-block:: xml - - - - - - .. code-block:: php - - $definition = new Definition('Acme\HelloBundle\Extension\FooExtension'); - $definition->addTag('twig.extension'); - $container->setDefinition('foo.twig.extension', $definition); - -La etiqueta ``twig.extension`` es una etiqueta especial que ``TwigBundle`` usa -durante la configuración. Al dar al servicio esta etiqueta ``twig.extension``, el paquete sabe que el servicio ``foo.twig.extension`` se debe registrar como una extensión *Twig* con *Twig*. En otras palabras, *Twig* encuentra todos los servicios con la etiqueta ``twig.extension`` y automáticamente los registra como extensiones. - -Las etiquetas, entonces, son una manera de decirle a *Symfony2* u otros paquetes de terceros que el paquete se debe registrar o utilizar de alguna forma especial. - -La siguiente es una lista de etiquetas disponibles con los paquetes del núcleo de *Symfony2*. -Cada una de ellas tiene un efecto diferente en tu servicio y muchas etiquetas requieren argumentos adicionales (más allá de sólo el parámetro ``name``). - -Para una lista de todas las etiquetas disponibles en el núcleo de la plataforma *Symfony*, revisa las :doc:`/reference/dic_tags`. - -Depurando servicios -------------------- - -Puedes descubrir los servicios que están registrados con el contenedor utilizando la consola. Para mostrar todos los servicios y la clase de cada servicio, ejecuta: - -.. code-block:: bash - - $ php app/console container:debug - -De manera predefinida únicamente se muestran los servicios públicos, pero también puedes ver servicios privados: - -.. code-block:: bash - - $ php app/console container:debug --show-private - -Puedes conseguir información más detallada sobre un servicio particular especificando su id: - -.. code-block:: bash - - $ php app/console container:debug my_mailer - -Aprende más ------------ - -* :doc:`/components/dependency_injection/parameters` -* :doc:`/components/dependency_injection/compilation` -* :doc:`/components/dependency_injection/definitions` -* :doc:`/components/dependency_injection/factories` -* :doc:`/components/dependency_injection/parentservices` -* :doc:`/components/dependency_injection/tags` -* :doc:`/cookbook/controller/service` -* :doc:`/cookbook/service_container/scopes` -* :doc:`/cookbook/service_container/compiler_passes` -* :doc:`/components/dependency_injection/advanced` - -.. _`arquitectura orientada a servicios`: http://wikipedia.org/wiki/Service-oriented_architecture diff --git a/_sources/book/stable_api.txt b/_sources/book/stable_api.txt deleted file mode 100644 index 3110a9a..0000000 --- a/_sources/book/stable_api.txt +++ /dev/null @@ -1,40 +0,0 @@ -.. index:: - single: API estable - -*API* estable de *Symfony2* -=========================== - -La *API* estable de *Symfony2* es un subconjunto de todos los métodos públicos de *Symfony2* (componentes y paquetes básicos) que comparten las siguientes propiedades: - -* El espacio de nombres y nombre de la clase no van a cambiar; -* El nombre del método no va a cambiar; -* La firma del método (el tipo de los argumentos y del valor de retorno) no va a cambiar; -* La semántica de lo que hace el método no va a cambiar. - -Sin embargo, la imprementación en sí misma puede cambiar. El único caso válido para un cambio en la *API* estable es con el fin de corregir algún problema de seguridad. - -La *API* estable se basa en una lista blanca, marcada con ``@api``. Por lo tanto, todo lo no etiquetado explícitamente no es parte de la *API* estable. - -.. tip:: - - Cualquier paquete de terceros también deberá publicar su propia *API* estable. - -A partir de *Symfony 2.0*, los siguientes componentes tienen una *API* etiquetada pública: - -* BrowserKit -* ClassLoader -* Console -* CssSelector -* DependencyInjection -* DomCrawler -* EventDispatcher -* Finder -* HttpFoundation -* HttpKernel -* Locale -* Process -* Routing -* Templating -* Translation -* Validator -* Yaml diff --git a/_sources/book/templating.txt b/_sources/book/templating.txt deleted file mode 100644 index 83a5d0a..0000000 --- a/_sources/book/templating.txt +++ /dev/null @@ -1,1274 +0,0 @@ -.. index:: - single: Plantillas - -Creando y usando plantillas -=========================== - -Como sabes, el :doc:`Controlador ` es responsable de manejar cada petición entrante en una aplicación *Symfony2*. En realidad, el controlador delega la mayor parte del trabajo pesado ​​a otros lugares para que el código se pueda probar y volver a utilizar. Cuando un controlador necesita generar *HTML*, *CSS* o cualquier otro contenido, que maneje el trabajo fuera del motor de plantillas. -En este capítulo, aprenderás cómo escribir potentes plantillas que puedes utilizar para devolver contenido al usuario, rellenar el cuerpo de correo electrónico y mucho más. Aprenderás métodos abreviados, formas inteligentes para extender las plantillas y cómo reutilizar código de plantilla. - -.. note:: - - El tema de cómo reproducir plantillas se aborda en la página :ref:`controlador ` del libro. - - -.. index:: - single: Plantillas; ¿Qué es una plantilla? - -Plantillas ----------- - -Una plantilla simplemente es un archivo de texto que puede generar cualquier formato basado en texto (*HTML*, *XML*, *CSV*, *LaTeX*...). El tipo de plantilla más familiar es una plantilla *PHP* --- un archivo de texto interpretado por *PHP* que contiene una mezcla de texto y código *PHP*: - -.. code-block:: html+php - - - - - Welcome to Symfony! - - -

- - - - - -.. index:: Twig; Introducción - -Pero *Symfony2* contiene un lenguaje de plantillas aún más potente llamado `Twig`_. -*Twig* te permite escribir plantillas concisas y fáciles de leer que son más amigables para los diseñadores web y, de varias maneras, más poderosas que las plantillas *PHP*: - -.. code-block:: html+jinja - - - - - Welcome to Symfony! - - -

{{ page_title }}

- - - - - -*Twig* define dos tipos de sintaxis especial: - -* ``{{ ... }}``: «Dice algo»: imprime una variable o el resultado de una expresión a la plantilla; - -* ``{% ... %}``: «Hace algo»: una **etiqueta** que controla la lógica de la plantilla; se utiliza para declaraciones ``if`` y ejecutar bucles ``for``, por ejemplo. - -.. note:: - - Hay una tercer sintaxis utilizada para crear comentarios: ``{# esto es un comentario #}``. - Esta sintaxis se puede utilizar en múltiples líneas como la sintaxis ``/* comentario */`` equivalente de *PHP*. - -*Twig* también contiene **filtros**, los cuales modifican el contenido antes de reproducirlo. -El siguiente fragmento convierte a mayúsculas la variable ``title`` antes de reproducirla: - -.. code-block:: jinja - - {{ title|upper }} - -*Twig* viene con una larga lista de `etiquetas`_ y `filtros`_ que están disponibles de forma predeterminada. Incluso puedes `agregar tus propias extensiones`_ a *Twig*, según sea necesario. - -.. tip:: - - Registrar una extensión *Twig* es tan fácil como crear un nuevo servicio y etiquetarlo con :ref:`twig.extension `. - -Como verás a través de la documentación, *Twig* también es compatible con funciones y fácilmente puedes añadir nuevas. Por ejemplo, la siguiente función, utiliza una etiqueta ``for`` estándar y la función ``cycle`` para imprimir diez etiquetas ``div``, alternando entre clases ``par`` e ``impar``: - -.. code-block:: html+jinja - - {% for i in 0..10 %} -
- -
- {% endfor %} - -A lo largo de este capítulo, mostraremos las plantillas de ejemplo en ambos formatos *Twig* y *PHP*. - -.. tip:: - - Si *decides* no utilizar *Twig* y lo desactivas, tendrás que implementar tu propio controlador de excepciones a través del evento ``kernel.exception``. - -.. sidebar:: ¿Porqué *Twig*? - - Las plantillas *Twig* están destinadas a ser simples y no procesar etiquetas *PHP*. Esto es por diseño: el sistema de plantillas *Twig* está destinado a expresar la presentación, no la lógica del programa. Cuanto más utilices *Twig*, más apreciarás y te beneficiarás de esta distinción. Y, por supuesto, todos los diseñadores web las amarán. - - *Twig* también puede hacer cosas que *PHP* no puede, como controlar el espacio en blanco, cuenta con un recinto de seguridad, escape de salida automático y contextual e incluye funciones personalizadas y filtros que sólo afectan a las plantillas. *Twig* contiene pequeñas características que facilitan la escritura de plantillas y estas son más concisas. Tomemos el siguiente ejemplo, el cual combina un bucle con una declaración ``if`` lógica: - - .. code-block:: html+jinja - -
    - {% for user in users if user.active %} -
  • {{ user.username }}
  • - {% else %} -
  • No users found
  • - {% endfor %} -
- -.. index:: - pair: Twig; Caché - -Guardando en caché plantillas *Twig* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Twig* es rápido. Cada plantilla *Twig* se compila hasta una clase *PHP* nativa que se reproduce en tiempo de ejecución. Las clases compiladas se encuentran en el directorio ``app/cache/{entorno}/twig`` (donde ``{entorno}`` es el entorno, tal como ``dev`` o ``prod``) y, en algunos casos, pueden ser útiles mientras depuras. Consulta la sección :ref:`environments-summary` para más información sobre los entornos. - -Cuando está habilitado el modo ``debug`` (comúnmente en el entorno ``dev``) al realizar cambios a una plantilla *Twig*, esta se vuelve a compilar automáticamente. Esto significa que durante el desarrollo, felizmente, puedes realizar cambios en una plantilla *Twig* e inmediatamente ver las modificaciones sin tener que preocuparte de limpiar ninguna caché. - -Cuando el modo ``debug`` está desactivado (comúnmente en el entorno ``prod``), sin embargo, debes borrar el directorio de caché para regenerar las plantillas. Recuerda hacer esto al desplegar tu aplicación. - -.. index:: - single: Plantillas; Herencia - -Plantillas, herencia y diseño ------------------------------ - -A menudo, las plantillas en un proyecto comparten elementos comunes, como el encabezado, pie de página, barra lateral o más. En *Symfony2*, nos gusta pensar en este problema de forma diferente: una plantilla se puede decorar con otra. Esto funciona exactamente igual que las clases *PHP*: la herencia de plantillas nos permite crear un «diseño» de plantilla base que contiene todos los elementos comunes de tu sitio definidos como **bloques** (piensa en «clases *PHP* con métodos base»). Una plantilla hija puede extender el diseño base y reemplazar cualquiera de sus bloques (piensa en las «subclases *PHP* que sustituyen determinados métodos de su clase padre»). - -En primer lugar, crea un archivo con tu diseño base: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# app/Resources/views/base.html.twig #} - - - - - {% block title %}Test Application{% endblock %} - - - - -
- {% block body %}{% endblock %} -
- - - - .. code-block:: html+php - - - - - - - <?php $view['slots']->output('title', 'Test Application') ?> - - - - -
- output('body') ?> -
- - - -.. note:: - - Aunque la explicación sobre la herencia de plantillas será en términos de *Twig*, la filosofía es la misma entre plantillas *Twig* y *PHP*. - -Esta plantilla define el esqueleto del documento *HTML* base de una simple página de dos columnas. En este ejemplo, se definen tres áreas ``{% block %}`` (``title``, ``sidebar`` y ``body``). Una plantilla hija puede sustituir cada uno de los bloques o dejarlos con su implementación predeterminada. Esta plantilla también se podría reproducir directamente. En este caso, los bloques ``title``, ``sidebar`` y ``body`` simplemente mantienen los valores predeterminados usados en esta plantilla. - -Una plantilla hija podría tener este aspecto: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} - {% extends '::base.html.twig' %} - - {% block title %}My cool blog posts{% endblock %} - - {% block body %} - {% for entry in blog_entries %} -

{{ entry.title }}

-

{{ entry.body }}

- {% endfor %} - {% endblock %} - - .. code-block:: html+php - - - extend('::base.html.php') ?> - - set('title', 'My cool blog posts') ?> - - start('body') ?> - -

getTitle() ?>

-

getBody() ?>

- - stop() ?> - -.. note:: - - La plantilla padre se identifica mediante una sintaxis de cadena especial (``::base.html.twig``) la cual indica que la plantilla vive en el directorio ``app/Resources/views`` del proyecto. Esta convención de nomenclatura se explica completamente en :ref:`template-naming-locations`. - -La clave para la herencia de plantillas es la etiqueta ``{% extends %}``. Esta le indica al motor de plantillas que primero evalúe la plantilla base, la cual establece el diseño y define varios bloques. Luego reproduce la plantilla hija, en ese momento, los bloques ``title`` y ``body`` del padre son reemplazados por los de la hija. Dependiendo del valor de ``blog_entries``, el resultado sería algo como esto: - -.. code-block:: html - - - - - - My cool blog posts - - - - -
-

My first post

-

The body of the first post.

- -

Another post

-

The body of the second post.

-
- - - -Ten en cuenta que como en la plantilla hija no has definido un bloque ``sidebar``, en su lugar, se utiliza el valor de la plantilla padre. Una plantilla padre, de forma predeterminada, siempre utiliza una etiqueta ``{% block %}`` para el contenido. - -Puedes utilizar tantos niveles de herencia como quieras. En la siguiente sección, explicaremos un modelo común de tres niveles de herencia junto con la forma en que se organizan las plantillas dentro de un proyecto *Symfony2*. - -Cuando trabajes con la herencia de plantillas, ten en cuenta los siguientes consejos: - -* Si utilizas ``{% extends %}`` en una plantilla, esta debe ser la primer etiqueta en esa plantilla; - -* Mientras más etiquetas ``{% block %}`` tengas en tu plantilla base, mejor. - Recuerda, las plantillas hijas no tienen que definir todos los bloques de los padres, por lo tanto crea tantos bloques en tus plantillas base como desees y dale a cada uno un razonable valor predeterminado. Mientras más bloques tengan tus plantillas base, más flexible será tu diseño; - -* Si te encuentras duplicando contenido en una serie de plantillas, muy probablemente significa que debes mover el contenido a un ``{% block %}`` en una plantilla padre. - En algunos casos, una mejor solución podría ser mover el contenido a una nueva plantilla e incluirla con ``include`` (consulta :ref:`incluyendo-plantillas`); - -* Si necesitas conseguir el contenido de un bloque desde la plantilla padre, puedes usar la función ``{{ parent() }}``. Esta es útil si deseas añadir algo al contenido de un bloque padre en vez de reemplazarlo por completo: - - .. code-block:: html+jinja - - {% block sidebar %} -

Table of Contents

- - {# ... #} - - {{ parent() }} - {% endblock %} - -.. index:: - single: Plantillas; Convenciones de nomenclatura - single: Plantillas; Ubicación de archivos - -.. _template-naming-locations: - -Nomenclatura y ubicación de plantillas --------------------------------------- - -.. versionadded:: 2.2 - El soporte necesario para rutas en el espacio de nombres se añadió en 2.2, permitiendo nombres de plantilla tal como ``@AcmeDemo/layout.html.twig``. Consulta :doc:`/cookbook/templating/namespaced_paths` para más detalles. - -De forma predeterminada, las plantillas pueden vivir en dos diferentes lugares: - -* ``app/Resources/views/``: El directorio de las ``vistas`` de la aplicación puede contener todas las plantillas base de la aplicación (es decir, los diseños de tu aplicación), así como plantillas que sustituyen a plantillas de paquetes (consulta :ref:`overriding-bundle-templates`); - -* ``ruta/al/paquete/Resources/views/`` Cada paquete contiene sus plantillas en su directorio (y subdirectorios) ``Resources/views``. La mayoría de las plantillas viven dentro de un paquete. - -*Symfony2* utiliza una sintaxis de cadena **paquete**:**controlador**:**plantilla** para las plantillas. Esto permite diferentes tipos de plantilla, dónde cada una vive en un lugar específico: - -* ``AcmeBlogBundle:Blog:index.html.twig``: Esta sintaxis se utiliza para especificar una plantilla para una página específica. Las tres partes de la cadena, cada una separada por dos puntos (``:``), significan lo siguiente: - - * ``AcmeBlogBundle``: (*paquete*) la plantilla vive dentro de :dfn:`AcmeBlogBundle` (por ejemplo, :file:`src/Acme/BlogBundle`); - - * ``Blog``: (*controlador*) indica que la plantilla vive dentro del subdirectorio ``Blog`` de ``Resources/views``; - - * ``index.html.twig``: (*plantilla*) el nombre real del archivo es ``index.html.twig``. - - Suponiendo que :dfn:`AcmeBlogBundle` vive en :file:`src/Acme/BlogBundle`, la ruta final para el diseño debería ser :file:`src/Acme/BlogBundle/Resources/views/Blog/index.html.twig`. - -* ``AcmeBlogBundle::layout.html.twig``: Esta sintaxis se refiere a una plantilla base que es específica para ``AcmeBlogBundle``. Puesto que falta la porción central, «controlador» (por ejemplo, ``Blog``), la plantilla vive en ``Resources/views/layout.html.twig`` dentro de ``AcmeBlogBundle``. - -* ``::base.html.twig``: Esta sintaxis se refiere a una plantilla o diseño base de la aplicación. Observa que la cadena comienza con dobles dos puntos (``::``), lo cual significa que faltan ambas porciones *paquete* y *controlador*. Esto quiere decir que la plantilla no se encuentra en ningún paquete, sino en el directorio raíz de la aplicación ``app/Resources/views/``. - -En la sección :ref:`overriding-bundle-templates`, encontrarás cómo puedes sustituir cada plantilla que vive dentro de ``AcmeBlogBundle``, por ejemplo, colocando una plantilla del mismo nombre en el directorio ``app/Resources/AcmeBlog/views/``. Esto nos da el poder para sustituir plantillas de cualquier paquete de terceros. - -.. tip:: - - Esperemos que la sintaxis de nomenclatura de plantilla te resulte familiar ---es la misma convención de nomenclatura utilizada para referirse al :ref:`controller-string-syntax`. - -Sufijo de plantilla -~~~~~~~~~~~~~~~~~~~ - -El formato **paquete**:**controlador**:**plantilla** de cada plantilla, especifica *dónde* se encuentra el archivo de plantilla. Cada nombre de plantilla también cuenta con dos extensiones que especifican el *formato* y *motor* de esa plantilla. - -* **AcmeBlogBundle:Blog:index.html.twig** --- formato *HTML*, motor *Twig* - -* **AcmeBlogBundle:Blog:index.html.php** --- formato *HTML*, motor *PHP* - -* **AcmeBlogBundle:Blog:index.css.twig** --- formato *CSS*, motor *Twig* - -De forma predeterminada, cualquier plantilla *Symfony2* se puede escribir en *Twig* o *PHP*, y la última parte de la extensión (por ejemplo :file:`.twig` o :file:`.php`) especifica cuál de los dos *motores* se debe utilizar. La primera parte de la extensión, (por ejemplo :file:`.html`, :file:`.css`, etc.) es el formato final que la plantilla debe generar. A diferencia del motor, el cual determina cómo procesa *Symfony2* la plantilla, esta simplemente es una táctica de organización utilizada en caso de que el mismo recurso se tenga que reproducir como *HTML* (``index.html.twig``), *XML* (``index.xml.twig``), o cualquier otro formato. Para más información, lee la sección :ref:`template-formats`. - -.. note:: - - Los «motores» disponibles se pueden configurar e incluso agregar nuevos motores. - Consulta :ref:`Configuración de plantillas ` para más detalles. - -.. index:: - single: Plantillas; Etiquetas y ayudantes - single: Plantillas; Ayudantes - -Etiquetas y ayudantes ---------------------- - -Ya entendiste los conceptos básicos de las plantillas, cómo son denominadas y cómo utilizar la herencia en plantillas. Las partes más difíciles ya quedaron atrás. En esta sección, aprenderás acerca de un amplio grupo de herramientas disponibles para ayudarte a realizar las tareas de plantilla más comunes, como la inclusión de otras plantillas, enlazar páginas e incluir imágenes. - -*Symfony2* viene con varias etiquetas *Twig* especializadas y funciones que facilitan la labor del diseñador de la plantilla. En *PHP*, el sistema de plantillas extensible ofrece un sistema de *ayudantes* que proporciona funciones útiles en el contexto de la plantilla. - -Ya hemos visto algunas etiquetas integradas en *Twig* (``{% block %}`` y ``{% extends %}``), así como un ejemplo de un ayudante *PHP* (consulta ``$view['slot']``). Aquí aprenderás un poco más. - -.. index:: - single: Plantillas; Incluyendo otras plantillas - -.. _incluyendo-plantillas: - -Incluyendo otras plantillas -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A menudo querrás incluir la misma plantilla o fragmento de código en varias páginas diferentes. Por ejemplo, en una aplicación con «artículos de noticias», el código de la plantilla que muestra un artículo se puede utilizar en la página de detalles del artículo, en una página que muestra los artículos más populares, o en una lista de los artículos más recientes. - -Cuando necesitas volver a utilizar un trozo de código *PHP*, normalmente mueves el código a una nueva clase o función *PHP*. Lo mismo es cierto para las plantillas. Al mover el código de la plantilla a su propia plantilla, este se puede incluir en cualquier otra plantilla. En primer lugar, crea la plantilla que tendrás que volver a usar. - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/ArticleBundle/Resources/views/Article/articleDetails.html.twig #} -

{{ article.title }}

- - -

- {{ article.body }} -

- - .. code-block:: html+php - - -

getTitle() ?>

- - -

- getBody() ?> -

- -Incluir esta plantilla en cualquier otra plantilla es sencillo: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/ArticleBundle/Resources/views/Article/list.html.twig #} - {% extends 'AcmeArticleBundle::layout.html.twig' %} - - {% block body %} -

Recent Articles

- - {% for article in articles %} - {{ include('AcmeArticleBundle:Article:articleDetails.html.twig', {'article': article}) }} - {% endfor %} - {% endblock %} - - .. code-block:: html+php - - - extend('AcmeArticleBundle::layout.html.php') ?> - - start('body') ?> -

Recent Articles

- - - render( - 'AcmeArticleBundle:Article:articleDetails.html.php', - array('article' => $article) - ) ?> - - stop() ?> - -La plantilla se incluye utilizando la función ``{{ include() }}``. Observa que el nombre de la plantilla sigue la misma convención típica. La plantilla ``articleDetails.html.twig`` utiliza una variable ``article``. Esta es proporcionada por la plantilla ``list.html.twig`` utilizando la orden ``with``. - -.. tip:: - - ``{'article': article}`` es la sintaxis de asignación estándar de *Twig* (es decir, un arreglo con claves nombradas). Si tuviéramos que pasar varios elementos, se vería así: ``{'foo': foo, 'bar': bar}``. - -.. versionadded:: 2.2 - La función ``include()`` es una nueva característica de *Twig* que está disponible en *Symfony 2.2*. Anteriormente se utilizaba la etiqueta ``{% include %}``. - -.. index:: - single: Plantillas; Integrando acción - -.. _templating-embedding-controller: - -Integrando controladores -~~~~~~~~~~~~~~~~~~~~~~~~ - -En algunos casos, es necesario hacer algo más que incluir una simple plantilla. Supongamos que en tu diseño tienes una barra lateral, la cual contiene los tres artículos más recientes. -Recuperar los tres artículos puede incluir consultar la base de datos o realizar otra pesada lógica ​​que no se puede hacer desde dentro de una plantilla. - -La solución es simplemente insertar el resultado de un controlador en tu plantilla entera. En primer lugar, crea un controlador que reproduzca una cierta cantidad de artículos recientes:: - - // src/Acme/ArticleBundle/Controller/ArticleController.php - class ArticleController extends Controller - { - public function recentArticlesAction($max = 3) - { - // hace una llamada a la base de datos u otra lógica - // para obtener los "$max" artículos más recientes - $articles = ...; - - return $this->render( - 'AcmeArticleBundle:Article:recentList.html.twig', - array('articles' => $articles) - ); - } - } - -La plantilla ``recentList`` es perfectamente clara: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} - {% for article in articles %} - - {{ article.title }} - - {% endfor %} - - .. code-block:: html+php - - - - - getTitle() ?> - - - -.. note:: - - Ten en cuenta que la *URL* del artículo está directamente en el código en este ejemplo - (p. ej. ``/article/*slug*``). Esta es una mala práctica. En la siguiente sección, aprenderás cómo hacer esto correctamente. - -Para incluir el controlador, tendrás que referirte a él utilizando la sintaxis de cadena estándar para controladores (es decir, **paquete**:**controlador**:**acción**): - -.. configuration-block:: - - .. code-block:: html+jinja - - {# app/Resources/views/base.html.twig #} - - {# ... #} - - - .. code-block:: html+php - - - - - - -Cada vez que te encuentres necesitando una variable o una pieza de información a la que una plantilla no tiene acceso, considera reproducir un controlador. -Los controladores se ejecutan rápidamente y promueven la buena organización y reutilización de código. - -Contenido asíncrono con ``hinclude.js`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - La compatibilidad con :file:`hinclude.js` se añadió en *Symfony 2.1* - -Los controladores se pueden incorporar asincrónicamente usando la biblioteca *JavaScript* `hinclude.js`_. -Debido a que el contenido integrado proviene de otra página (o controlador para el caso), *Symfony2* utiliza el ayudante ``render`` estándar para configurar las etiquetas ``hinclude``: - -.. configuration-block:: - - .. code-block:: jinja - - {% render url('...') with {}, {'standalone': 'js'} %} - - .. code-block:: php - - render( - new ControllerReference('...'), - array('renderer' => 'hinclude') - ) ?> - - render( - $view['router']->generate('...'), - array('renderer' => 'hinclude') - ) ?> - -.. note:: - - Para que trabaje, debes incluir `hinclude.js`_ en tu página. - -.. note:: - - Al utilizar un controlador en vez de una *URL*, debes habilitar la configuración de ``fragmentos`` de *Symfony*: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - fragments: { path: /_fragment } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - // ... - 'fragments' => array('path' => '/_fragment'), - )); - -Puedes especificar globalmente el contenido predeterminado (al cargar o si *JavaScript* está desactivado) en la configuración de tu aplicación: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - templating: - hinclude_default_template: AcmeDemoBundle::hinclude.html.twig - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - // ... - 'templating' => array( - 'hinclude_default_template' => array('AcmeDemoBundle::hinclude.html.twig'), - ), - )); - -.. versionadded:: 2.2 - Las plantillas ``default`` para la función ``render`` se añadieron en *Symfony 2.2* - -Puedes definir plantillas ``default`` para la función ``render`` (lo cuál sustituye cualquier plantilla ``default`` global que esté definida): - -.. configuration-block:: - - .. code-block:: jinja - - {{ render_hinclude(controller('...'), {'default': 'AcmeDemoBundle:Default:content.html.twig'}) }} - - .. code-block:: php - - render( - new ControllerReference('...'), - array( - 'renderer' => 'hinclude', - 'default' => 'AcmeDemoBundle:Default:content.html.twig', - ) - ) ?> - -O puedes especificar además, una cadena que se mostrará como el contenido predefinido: - -.. configuration-block:: - - .. code-block:: jinja - - {{ render_hinclude(controller('...'), {'default': 'Loading...'}) }} - - .. code-block:: php - - render( - new ControllerReference('...'), - array( - 'renderer' => 'hinclude', - 'default' => 'Loading...', - ) - ) ?> - -.. index:: - single: Plantillas; Enlazando páginas - -.. _book-templating-pages: - -Enlazando páginas -~~~~~~~~~~~~~~~~~ - -La creación de enlaces a otras páginas en tu aplicación es uno de los trabajos más comunes de una plantilla. En lugar de codificar las *URL* en las plantillas, utiliza la función ``path`` de *Twig* (o el ayudante ``router`` en *PHP*) para generar *URL* basadas en la configuración de enrutado. Más tarde, si deseas modificar la *URL* de una página en particular, todo lo que tienes que hacer es cambiar la configuración de enrutado; las plantillas automáticamente generarán la nueva *URL*. - -En primer lugar, crea el enlace a la página ``«_welcome»``, la cual es accesible a través de la siguiente configuración de enrutado: - -.. configuration-block:: - - .. code-block:: yaml - - _welcome: - path: / - defaults: { _controller: AcmeDemoBundle:Welcome:index } - - .. code-block:: xml - - - AcmeDemoBundle:Welcome:index - - - .. code-block:: php - - $collection = new RouteCollection(); - $collection->add('_welcome', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Welcome:index', - ))); - - return $collection; - -Para enlazar a la página, sólo tienes que utilizar la función ``path`` de *Twig* y referir la ruta: - -.. configuration-block:: - - .. code-block:: html+jinja - - Home - - .. code-block:: html+php - - Home - -Como era de esperar, esto genera la *URL* ``/``. Ahora para una ruta más complicada: - -.. configuration-block:: - - .. code-block:: yaml - - article_show: - path: /article/{slug} - defaults: { _controller: AcmeArticleBundle:Article:show } - - .. code-block:: xml - - - AcmeArticleBundle:Article:show - - - .. code-block:: php - - $collection = new RouteCollection(); - $collection->add('article_show', new Route('/article/{slug}', array( - '_controller' => 'AcmeArticleBundle:Article:show', - ))); - - return $collection; - -En este caso, es necesario especificar el nombre de la ruta (``article_show``) y un valor para el parámetro ``{slug}``. Usando esta ruta, revisemos la plantilla ``recentList`` de la sección anterior y enlacemos los artículos correctamente: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} - {% for article in articles %} - - {{ article.title }} - - {% endfor %} - - .. code-block:: html+php - - - - - getTitle() ?> - - - -.. tip:: - - También puedes generar una *URL* absoluta utilizando la función ``url`` de *Twig*: - - .. code-block:: html+jinja - - Home - - Lo mismo se puede hacer en plantillas *PHP* pasando un tercer argumento al método ``generate()``: - - .. code-block:: html+php - - Home - -.. index:: - single: Plantillas; Enlazando activos - -.. _book-templating-assets: - -Enlazando activos -~~~~~~~~~~~~~~~~~ - -Las plantillas también se refieren comúnmente a imágenes, *JavaScript*, hojas de estilo y otros activos. Por supuesto, puedes codificar la ruta de estos activos (por ejemplo ``/images/logo.png``), pero *Symfony2* ofrece una opción más dinámica a través de la función ``asset`` de *Twig*: - -.. configuration-block:: - - .. code-block:: html+jinja - - Symfony! - - - - .. code-block:: html+php - - Symfony! - - - -El propósito principal de la función ``asset`` es hacer más portátil tu aplicación. -Si tu aplicación vive en la raíz de tu servidor (por ejemplo, http://ejemplo.com), entonces las rutas reproducidas deben ser ``/images/logo.png``. Pero si tu aplicación vive en un subdirectorio (por ejemplo, http://ejemplo.com/mi_aplic), cada ruta de activo debe reproducir el subdirectorio (por ejemplo ``/mi_aplic/images/logo.png``). La función ``asset`` se encarga de esto determinando cómo se está utilizando tu aplicación y generando las rutas correctas en consecuencia. - -Además, si utilizas la función ``asset``, *Symfony* automáticamente puede añadir una cadena de consulta a tu activo, con el fin de garantizar que los activos estáticos actualizados no se almacenen en caché al desplegar tu aplicación. Por ejemplo, ``/images/logo.png`` podría ser ``/images/logo.png?v2``. Para más información, consulta la opción de configuración :ref:`ref-framework-assets-version`. - -.. index:: - single: Plantillas; Incluyendo hojas de estilo y JavaScript - single: Hojas de estilo; Incluyendo hojas de estilo - single: JavaScript; Incluyendo archivos JavaScript - -Incluyendo hojas de estilo y *JavaScript* en *Twig* ---------------------------------------------------- - -Ningún sitio estaría completo sin incluir archivos de *JavaScript* y hojas de estilo. -En *Symfony*, la inclusión de estos activos se maneja elegantemente, aprovechando la herencia de plantillas de *Symfony*. - -.. tip:: - - Esta sección te enseñará la filosofía detrás de la inclusión de activos como hojas de estilo y *Javascript* en *Symfony*. *Symfony* también empaca otra biblioteca, llamada ``Assetic``, la cual sigue esta filosofía, pero te permite hacer cosas mucho más interesantes con esos activos. Para más información sobre el uso de ``Assetic`` consulta :doc:`/cookbook/assetic/asset_management`. - - -Comienza agregando dos bloques a la plantilla base que mantendrá tus activos: -uno llamado ``stylesheet`` dentro de la etiqueta ``head`` y otro llamado ``javascript`` justo por encima de la etiqueta de cierre ``body``. Estos bloques deben contener todas las hojas de estilo y archivos *Javascript* que necesitas en tu sitio: - -.. code-block:: html+jinja - - {# app/Resources/views/base.html.twig #} - - - {# ... #} - - {% block stylesheets %} - - {% endblock %} - - - {# ... #} - - {% block javascripts %} - - {% endblock %} - - - -¡Eso es bastante fácil! Pero ¿y si es necesario incluir una hoja de estilo extra o archivos *Javascript* desde una plantilla hija? Por ejemplo, supongamos que tienes una página de contacto y necesitas incluir una hoja de estilo :file:`contact.css` *sólo* en esa página. Desde dentro de la plantilla de la página de contacto, haz lo siguiente: - -.. code-block:: html+jinja - - {# src/Acme/DemoBundle/Resources/views/Contact/contact.html.twig #} - {% extends '::base.html.twig' %} - - {% block stylesheets %} - {{ parent() }} - - - {% endblock %} - - {# ... #} - -En la plantilla hija, sólo tienes que reemplazar el bloque ``stylesheets`` y poner tu nueva etiqueta de hoja de estilo dentro de ese bloque. Por supuesto, debido a que la quieres añadir al contenido del bloque padre (y no *cambiarla* en realidad), debes usar la función ``parent()`` de *Twig* para incluir todo, desde el bloque ``stylesheets`` de la plantilla base. - -Además, puedes incluir activos ubicados en el directorio ``Resources/public`` de tus paquetes. -Deberás ejecutar la orden ``php app/console assets:install destino [--symlink]``, la cual mueve (o enlaza simbólicamente) tus archivos a la ubicación correcta. (destino por omisión es «web»). - -.. code-block:: html+jinja - - - -El resultado final es una página que incluye ambas hojas de estilo :file:`main.css` y :file:`contact.css`. - -Variables de plantilla globales -------------------------------- - -En cada petición, *Symfony2* debe configurar una variable de plantilla global ``app`` en ambos motores de plantilla predefinidos *Twig* y *PHP*. La variable ``app`` es una instancia de :class:`Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables` que automáticamente te proporciona acceso a algunas variables específicas de la aplicación: - -* ``app.security`` - El contexto de seguridad. -* ``app.user`` - El objeto ``Usuario`` actual. -* ``app.request`` - El objeto ``Petición``. -* ``app.session`` - El objeto ``Sesión``. -* ``app.environment`` - El entorno actual («dev», «prod», etc.) -* ``app.debug`` - ``True`` si está en modo de depuración. ``False`` en caso contrario. - -.. configuration-block:: - - .. code-block:: html+jinja - -

Username: {{ app.user.username }}

- {% if app.debug %} -

Request method: {{ app.request.method }}

-

Application Environment: {{ app.environment }}

- {% endif %} - - .. code-block:: html+php - -

Username: getUser()->getUsername() ?>

- getDebug()): ?> -

Request method: getRequest()->getMethod() ?>

-

Application Environment: getEnvironment() ?>

- - -.. tip:: - - Puedes agregar tus propias variables de plantilla globales. Ve el ejemplo de :doc:`variables globales` en el recetario. - -.. index:: - single: Plantillas; El servicio plantillas (templating) - -Configurando y usando el servicio ``plantilla`` ------------------------------------------------ - -El corazón del sistema de plantillas en *Symfony2* es el ``motor`` de plantillas. -Este objeto especial es el encargado de reproducir las plantillas y devolver su contenido. Cuando reproduces una plantilla en un controlador, por ejemplo, en realidad estás usando el motor del servicio de plantillas. Por ejemplo:: - - return $this->render('AcmeArticleBundle:Article:index.html.twig'); - -es equivalente a:: - - use Symfony\Component\HttpFoundation\Response; - - $engine = $this->container->get('templating'); - $content = $engine->render('AcmeArticleBundle:Article:index.html.twig'); - - return $response = new Response($content); - -.. _template-configuration: - -El motor de plantillas (o «servicio») está configurado para funcionar automáticamente al interior de *Symfony2*. Por supuesto, puedes configurar más en el archivo de configuración de la aplicación: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - # ... - templating: { engines: ['twig'] } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - // ... - - 'templating' => array( - 'engines' => array('twig'), - ), - )); - -Disponemos de muchas opciones de configuración y están cubiertas en el :doc:`Apéndice Configurando `. - -.. note:: - - En el motor de ``twig`` es obligatorio el uso del ``webprofiler`` (así como muchos otros paquetes de terceros). - -.. index:: - single: Plantilla; Sustituyendo plantillas - -.. _overriding-bundle-templates: - -Sustituyendo plantillas del paquete ------------------------------------ - -La comunidad de *Symfony2* se enorgullece de crear y mantener paquetes de alta calidad (consulta `KnpBundles.com`_) para ver la gran cantidad de diferentes características. -Una vez que utilizas un paquete de terceros, probablemente necesites redefinir y personalizar una o más de sus plantillas. - -Supongamos que hemos incluido el paquete imaginario :dfn:`AcmeBlogBundle` de código abierto en el proyecto (por ejemplo, en el directorio :file:`src/Acme/BlogBundle`). Y si bien estás muy contento con todo, deseas sustituir la página «lista» del *blog* para personalizar el marcado específicamente para tu aplicación. Al excavar en el controlador del ``Blog`` de ``AcmeBlogBundle``, encuentras lo siguiente:: - - public function indexAction() - { - // alguna lógica para recuperar artículos - $blogs = ...; - - $this->render( - 'AcmeBlogBundle:Blog:index.html.twig', - array('blogs' => $blogs) - ); - } - -Al reproducir ``AcmeBlogBundle:Blog:index.html.twig``, en realidad *Symfony2* busca la plantilla en dos diferentes lugares: - -#. ``app/Resources/AcmeBlogBundle/views/Blog/index.html.twig`` -#. :file:`src/Acme/BlogBundle/Resources/views/Blog/index.html.twig` - -Para sustituir la plantilla del paquete, sólo tienes que copiar la plantilla ``index.html.twig`` del paquete a ``app/Resources/AcmeBlogBundle/views/Blog/index.html.twig`` (el directorio ``app/Resources/AcmeBlogBundle`` no existe, por lo tanto tendrás que crearlo). Ahora eres libre de personalizar la plantilla para tu aplicación. - -.. caution:: - - Si agregas una plantilla en una nueva ubicación, *posiblemente* tengas que vaciar la caché (con ``php app/consola cache:clear``), incluso si estás en modo de depuración. - -Esta lógica también aplica a las plantillas base del paquete. Supongamos también que cada plantilla en ``AcmeBlogBundle`` hereda de una plantilla base llamada ``AcmeBlogBundle::layout.html.twig``. Al igual que antes, *Symfony2* buscará la plantilla en los dos siguientes lugares: - -#. ``app/Resources/AcmeBlogBundle/views/layout.html.twig`` -#. :file:`src/Acme/BlogBundle/Resources/views/layout.html.twig` - -Una vez más, para sustituir la plantilla, sólo tienes que copiarla desde el paquete a ``app/Resources/AcmeBlogBundle/views/layout.html.twig``. Ahora estás en libertad de personalizar esta copia como mejor te parezca. - -Si retrocedes un paso, verás que *Symfony2* siempre empieza a buscar una plantilla en el directorio ``app/Resources/{NOMBRE_PAQUETE}/views/``. Si la plantilla no existe allí, continúa buscando dentro del directorio ``Resources/views`` del propio paquete. Esto significa que todas las plantillas del paquete se pueden sustituir colocándolas en el subdirectorio ``app/Resources`` correcto. - -.. note:: - - También puedes reemplazar las plantillas de un paquete usando la herencia de paquetes. Para más información, consulta :doc:`/cookbook/bundles/inheritance`. - -.. _templating-overriding-core-templates: - -.. index:: - single: Plantilla; Excepción al sustituir plantillas - -Sustituyendo plantillas del núcleo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Puesto que la plataforma *Symfony2* en sí misma sólo es un paquete, las plantillas del núcleo se pueden sustituir de la misma manera. Por ejemplo, el núcleo de ``TwigBundle`` contiene una serie de diferentes plantillas para «excepción» y «error» que puedes sustituir copiando cada una del directorio ``Resources/views/Exception`` del ``TwigBundle`` a... ¡adivinaste! el directorio ``app/Resources/TwigBundle/views/Exception``. - -.. index:: - single: Plantillas; Patrón de herencia de tres niveles - -Herencia de tres niveles ------------------------- - -Una manera común de usar la herencia es utilizar un enfoque de tres niveles. Este método funciona a la perfección con los tres diferentes tipos de plantilla que acabamos de cubrir: - -* Crea un archivo ``app/Resources/views/base.html.twig`` que contenga el diseño principal para tu aplicación (como en el ejemplo anterior). Internamente, esta plantilla se llama ``::base.html.twig``; - -* Crea una plantilla para cada «sección» de tu sitio. Por ejemplo, ``AcmeBlogBundle``, tendría una plantilla llamada ``AcmeBlogBundle::layout.html.twig`` que sólo contiene los elementos específicos de la sección *blog*; - - .. code-block:: html+jinja - - {# src/Acme/BlogBundle/Resources/views/layout.html.twig #} - {% extends '::base.html.twig' %} - - {% block body %} -

Blog Application

- - {% block content %}{% endblock %} - {% endblock %} - -* Crea plantillas individuales para cada página y haz que cada una extienda la plantilla de la sección adecuada. Por ejemplo, la página «index» se llama algo parecido a ``AcmeBlogBundle:Blog:index.html.twig`` y enumera las entradas del *blog* real. - - .. code-block:: html+jinja - - {# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #} - {% extends 'AcmeBlogBundle::layout.html.twig' %} - - {% block content %} - {% for entry in blog_entries %} -

{{ entry.title }}

-

{{ entry.body }}

- {% endfor %} - {% endblock %} - -Ten en cuenta que esta plantilla extiende la plantilla de la sección --- (``AcmeBlogBundle::layout.html.twig``), que a su vez, extiende el diseño base de la aplicación (``::base.html.twig``). -Este es el modelo común de la herencia de tres niveles. - -Cuando construyas tu aplicación, podrás optar por este método o, simplemente, hacer que cada plantilla de página extienda directamente la plantilla base de tu aplicación (por ejemplo, ``{% extends '::base.html.twig' %}``). El modelo de plantillas de tres niveles es un método de las buenas prácticas utilizadas por los paquetes de proveedores a fin de que la plantilla base de un paquete se pueda sustituir fácilmente para extender correctamente el diseño base de tu aplicación. - -.. index:: - single: Plantillas; Mecanismo de escape - - -Mecanismo de escape -------------------- - -Cuando generas *HTML* a partir de una plantilla, siempre existe el riesgo de que una variable de plantilla pueda producir *HTML* involuntario o código peligroso de lado del cliente. El resultado es que el contenido dinámico puede romper el código *HTML* de la página resultante o permitir a un usuario malicioso realizar un ataque de `Explotación de vulnerabilidades del sistema`_ (*Cross Site Scripting XSS*). Considera este ejemplo clásico: - -.. configuration-block:: - - .. code-block:: html+jinja - - Hello {{ name }} - - .. code-block:: html+php - - Hello - -Imagina que el usuario introduce el siguiente código como su nombre: - -.. code-block:: text - - - -Sin ningún tipo de mecanismo de escape, la plantilla resultante provocaría que aparezca un cuadro de alerta *JavaScript*: - -.. code-block:: html - - Hello - -Y aunque esto parece inofensivo, si un usuario puede llegar hasta aquí, ese mismo usuario también será capaz de escribir código *JavaScript* malicioso que subrepticiamente realice acciones dentro de la zona segura de un usuario legítimo. - -La respuesta al problema es el mecanismo de escape. Con el mecanismo de escape, reproduces la misma plantilla sin causar daño alguno, y literalmente, imprimes en pantalla la etiqueta ``script``: - -.. code-block:: html - - Hello <script>alert('helloe')</script> - -*Twig* y los sistemas de plantillas *PHP* abordan el problema de diferentes maneras. -Si estás utilizando *Twig*, el mecanismo de escape por omisión está activado y tu aplicación está protegida. -En *PHP*, el mecanismo de escape no es automático, lo cual significa que, de ser necesario, necesitas escapar todo manualmente. - -Mecanismo de escape en *Twig* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si estás utilizando las plantillas de *Twig*, entonces el mecanismo de escape está activado por omisión. Esto significa que estás protegido fuera de la caja de las consecuencias no intencionales del código presentado por los usuarios. De forma predeterminada, el mecanismo de escape asume que el contenido se escapó para salida *HTML*. - -En algunos casos, tendrás que desactivar el mecanismo de escape cuando estás reproduciendo una variable de confianza y marcado que no se debe escapar. -Supongamos que los usuarios administrativos están autorizados para escribir artículos que contengan código *HTML*. De forma predeterminada, *Twig* debe escapar el cuerpo del artículo. - -Para reproducirlo normalmente, agrega el filtro ``raw``: - -.. code-block:: jinja - - {{ article.body|raw }} - -También puedes desactivar el mecanismo de escape dentro de una área ``{% block %}`` o para una plantilla completa. Para más información, consulta la documentación de *Twig* sobre el `Mecanismo de escape`_. - -Mecanismo de escape en *PHP* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El mecanismo de escape no es automático cuando utilizas plantillas *PHP*. Esto significa que a menos que escapes una variable expresamente, no estás protegido. Para utilizar el mecanismo de escape, usa el método especial de la vista ``escape()``: - -.. code-block:: html+php - - Hello escape($name) ?> - -De forma predeterminada, el método ``escape()`` asume que la variable se está reproduciendo en un contexto *HTML* (y por tanto la variable se escapa para que sea *HTML* seguro). -El segundo argumento te permite cambiar el contexto. Por ejemplo, para mostrar algo en una cadena *JavaScript*, utiliza el contexto ``js``: - -.. code-block:: html+php - - var myMsg = 'Hello escape($name, 'js') ?>'; - -.. index:: - single: Plantillas; Formatos - -.. _template-formats: - -Depurando ---------- - -.. versionadded:: 2.0.9 - Esta característica está disponible desde *Twig* ``1.5.x``, que se adoptó por primera vez en *Symfony* 2.0.9. - -Cuando utilizas *PHP*, puedes usar ``var_dump()`` si necesitas encontrar rápidamente el valor de una variable proporcionada. Esto es útil, por ejemplo, dentro de tu controlador. -Lo mismo puedes lograr cuando utilizas *Twig* usando la extensión de depuración (``debug``). Necesitas activarla en la configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - services: - acme_hello.twig.extension.debug: - class: Twig_Extension_Debug - tags: - - { name: 'twig.extension' } - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/config.php - use Symfony\Component\DependencyInjection\Definition; - - $definition = new Definition('Twig_Extension_Debug'); - $definition->addTag('twig.extension'); - $container->setDefinition('acme_hello.twig.extension.debug', $definition); - -Puedes descargar los parámetros de plantilla utilizando la función ``dump``: - -.. code-block:: html+jinja - - {# src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig #} - {{ dump(articles) }} - - {% for article in articles %} - - {{ article.title }} - - {% endfor %} - - -Las variables serán descargadas si configuras a *Twig* (en :file:`config.yml`) con ``debug`` a ``true``. De manera predeterminada, esto significa que las variables serán descargadas en el entorno ``dev``, pero no el entorno ``prod``. - -Comprobando la sintaxis ------------------------ - -.. versionadded:: 2.1 - La orden ``twig:lint`` fue añadida en *Symfony 2.1* - -Puedes localizar errores de sintaxis en plantillas *Twig* utilizando la orden de consola ``twig:lint``: - -.. code-block:: bash - - # Puedes buscar por nombre de archivo: - $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views/Article/recentList.html.twig - - # o por directorio: - $ php app/console twig:lint src/Acme/ArticleBundle/Resources/views - - # o usando el nombre del paquete: - $ php app/console twig:lint @AcmeArticleBundle - -Formato de plantillas ---------------------- - -Las plantillas son una manera genérica para reproducir contenido en *cualquier* formato. Y, aunque en la mayoría de los casos debes utilizar plantillas para reproducir contenido *HTML*, una plantilla fácilmente puede generar *JavaScript*, *CSS*, *XML* o cualquier otro formato que puedas soñar. - -Por ejemplo, el mismo «recurso» a menudo se reproduce en varios formatos diferentes. -Para reproducir una página índice de artículos en formato *XML*, basta con incluir el formato en el nombre de la plantilla: - -* *nombre de plantilla XML*: ``AcmeArticleBundle:Article:index.xml.twig`` -* *nombre del archivo de plantilla XML*: ``index.xml.twig`` - -Ciertamente, esto no es más que una convención de nomenclatura y la plantilla realmente no se reproduce de manera diferente en función de ese formato. - -En muchos casos, posiblemente quieras permitir que un solo controlador reproduzca múltiples formatos basándose en el «formato de la petición». Por esa razón, un patrón común es hacer lo siguiente:: - - public function indexAction() - { - $format = $this->getRequest()->getRequestFormat(); - - return $this->render('AcmeBlogBundle:Blog:index.'.$format.'.twig'); - } - -El ``getRequestFormat`` en el objeto ``Petición`` por omisión es *HTML*, pero lo puedes devolver en cualquier otro formato basándote en el formato solicitado por el usuario. -El formato de la petición muy frecuentemente es gestionado por el enrutador, donde puedes configurar una ruta para que ``/contact`` establezca el formato ``html`` de la petición, mientras que ``/contact.xml`` establezca al formato ``xml``. Para más información, consulta el :ref:`ejemplo avanzado en el capítulo de Enrutado `. - -Para crear enlaces que incluyan el parámetro de formato, agrega una clave ``_format`` en el parámetro ``hash``: - -.. configuration-block:: - - .. code-block:: html+jinja - - - PDF Version - - - .. code-block:: html+php - - - PDF Version - - -Consideraciones finales ------------------------ - -El motor de plantillas de *Symfony* es una poderosa herramienta que puedes utilizar cada vez que necesites generar contenido de presentación en *HTML*, *XML* o cualquier otro formato. -Y aunque las plantillas son una manera común de generar contenido en un controlador, su uso no es obligatorio. El objeto ``Respuesta`` devuelto por un controlador se puede crear usando o sin usar una plantilla:: - - // crea un objeto Respuesta donde el contenido reproduce la plantilla - $response = $this->render('AcmeArticleBundle:Article:index.html.twig'); - - // crea un objeto Respuesta cuyo contenido es texto simple - $response = new Response('response content'); - -El motor de plantillas de *Symfony* es muy flexible y de manera predeterminada disponemos de dos diferentes reproductores de plantilla: las tradicionales plantillas *PHP* y las elegantes y potentes plantillas *Twig*. Ambas apoyan una jerarquía de plantillas y vienen empacadas con un rico conjunto de funciones auxiliares capaces de realizar las tareas más comunes. - -En general, el tema de las plantillas se debe pensar como una poderosa herramienta que está a tu disposición. En algunos casos, posiblemente no necesites reproducir una plantilla, y en *Symfony2*, eso está absolutamente bien. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/templating/PHP` -* :doc:`/cookbook/controller/error_pages` -* :doc:`/cookbook/templating/twig_extension` - -.. _`Twig`: http://gitnacho.github.com/symfony-docs-es/twig/index.html -.. _`KnpBundles.com`: http://knpbundles.com -.. _`Explotación de vulnerabilidades del sistema`: http://es.wikipedia.org/wiki/Cross-site_scripting -.. _`Mecanismo de escape`: http://gitnacho.github.com/Twig/api.html#extension-escaper -.. _`etiquetas`: http://gitnacho.github.com/Twig/tags/index.html -.. _`filtros`: http://gitnacho.github.com/Twig/filters/index.html -.. _`agregar tus propias extensiones`: http://gitnacho.github.com/Twig/advanced.html#creando-una-extension -.. _`hinclude.js`: http://mnot.github.com/hinclude/ diff --git a/_sources/book/testing.txt b/_sources/book/testing.txt deleted file mode 100644 index 5b1dc71..0000000 --- a/_sources/book/testing.txt +++ /dev/null @@ -1,701 +0,0 @@ -.. index:: - single: Pruebas - -Probando -======== - -Cada vez que escribes una nueva línea de código, potencialmente, añades nuevos errores también. -Para construir mejores y más confiables aplicaciones, debes probar tu código usando ambas pruebas, unitarias y funcionales. - -La plataforma de pruebas *PHPUnit* ----------------------------------- - -*Symfony2* integra una biblioteca independiente ---llamada *PHPUnit*--- para proporcionarte una rica plataforma de pruebas. Esta parte no cubre *PHPUnit* en sí mismo, puesto que la biblioteca cuenta con su propia y excelente `documentación`_. - -.. note:: - - *Symfony2* trabaja con *PHPUnit* 3.5.11 o posterior, aunque se necesita la versión 3.6.4 para probar el código del núcleo de Symfony. - -Cada prueba ---si se trata de una prueba unitaria o una prueba funcional--- es una clase *PHP* que debe vivir en el subdirectorio ``Tests/`` de tus paquetes. Si sigues esta regla, entonces puedes ejecutar todas las pruebas de tu aplicación con la siguiente orden: - -.. code-block:: bash - - # especifica la configuración del directorio en la línea de ordenes - $ phpunit -c app/ - -La opción ``-c`` le dice a *PHPUnit* que busque el archivo de configuración en el directorio ``app/``. Si tienes curiosidad sobre qué significan las opciones de *PHPUnit*, dale un vistazo al archivo ``app/phpunit.xml.dist``. - -.. tip:: - - La cobertura de código se puede generar con la opción ``--coverage-html``. - -.. index:: - single: Pruebas; Pruebas unitarias - -Pruebas unitarias ------------------ - -Una prueba unitaria normalmente es una prueba contra una clase *PHP* específica. Si deseas probar el comportamiento de tu aplicación en conjunto, ve la sección sobre las `Pruebas funcionales`_. - -Escribir pruebas unitarias en *Symfony2* no es diferente a escribir pruebas unitarias *PHPUnit* normales. Supongamos, por ejemplo, que tienes una clase *increíblemente* simple llamada ``Calculator`` en el directorio ``Utility/`` de tu paquete:: - - // src/Acme/DemoBundle/Utility/Calculator.php - namespace Acme\DemoBundle\Utility; - - class Calculator - { - public function add($a, $b) - { - return $a + $b; - } - } - -Para probarla, crea un archivo ``CalculatorTest`` en el directorio ``Tests/Utility`` de tu paquete:: - - // src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php - namespace Acme\DemoBundle\Tests\Utility; - - use Acme\DemoBundle\Utility\Calculator; - - class CalculatorTest extends \PHPUnit_Framework_TestCase - { - public function testAdd() - { - $calc = new Calculator(); - $result = $calc->add(30, 12); - - // ¡acierta que nuestra calculadora suma dos números correctamente! - $this->assertEquals(42, $result); - } - } - -.. note:: - - Por convención, el subdirectorio ``Tests/`` debería replicar al directorio de tu paquete. Por lo tanto, si estás probando una clase en el directorio ``Utility/`` de tu paquete, pon tus pruebas en el directorio ``Tests/Utility``. - -Al igual que en tu aplicación real ---el archivo ``bootstrap.php.cache``--- automáticamente activa el autocargador (como si lo hubieras configurado por omisión en el archivo ``phpunit.xml.dist``). - -Correr las pruebas de un determinado archivo o directorio también es muy fácil: - -.. code-block:: bash - - # ejecuta todas las pruebas en el directorio 'Utility' - $ phpunit -c app src/Acme/DemoBundle/Tests/Utility/ - - # corre las pruebas para la clase Calculator - $ phpunit -c app src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php - - # corre todas las pruebas del paquete entero - $ phpunit -c app src/Acme/DemoBundle/ - -.. index:: - single: Pruebas; Pruebas funcionales - -Pruebas funcionales -------------------- - -Las pruebas funcionales verifican la integración de las diferentes capas de una aplicación (desde el enrutado hasta la vista). Ellas no son diferentes de las pruebas unitarias en cuanto a *PHPUnit* se refiere, pero tienen un flujo de trabajo muy específico: - -* Envían una petición; -* Prueban la respuesta; -* Hacen clic en un enlace o envían un formulario; -* Prueban la respuesta; -* Enjuagan y repiten. - -Tu primera prueba funcional -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Las pruebas funcionales son simples archivos *PHP* que suelen vivir en el directorio ``Tests/Controller`` de tu paquete. Si deseas probar las páginas a cargo de tu clase ``DemoController``, empieza creando un nuevo archivo :file:`DemoControllerTest.php` que extiende una clase ``WebTestCase`` especial. - -Por ejemplo, la *edición estándar de Symfony2* proporciona una sencilla prueba funcional para ``DemoController`` (`DemoControllerTest`_) que dice lo siguiente:: - - // src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php - namespace Acme\DemoBundle\Tests\Controller; - - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - - class DemoControllerTest extends WebTestCase - { - public function testIndex() - { - $client = static::createClient(); - - $crawler = $client->request('GET', '/demo/hello/Fabien'); - - $this->assertGreaterThan( - 0, - $crawler->filter('html:contains("Hello Fabien")')->count() - ); - } - } - -.. tip:: - - Para ejecutar tus pruebas funcionales, la clase ``WebTestCase`` arranca el núcleo de tu aplicación. En la mayoría de los casos, esto sucede automáticamente. - Sin embargo, si tu núcleo se encuentra en un directorio no estándar, deberás modificar tu archivo :file:`phpunit.xml.dist` para ajustar la variable de entorno ``KERNEL_DIR`` al directorio de tu núcleo: - - .. code-block:: xml - - - - - - - - - -El método ``createClient()`` devuelve un cliente, el cual es como un navegador que debes usar para explorar tu sitio:: - - $crawler = $client->request('GET', '/demo/hello/Fabien'); - -El método ``request()`` (consulta :ref:`más sobre el método request `) devuelve un objeto :class:`Symfony\\Component\\DomCrawler\\Crawler` que puedes utilizar para seleccionar elementos en la respuesta, hacer clic en enlaces, y enviar formularios. - -.. tip:: - - El ``Crawler`` únicamente trabaja cuando la respuesta es *XML* o un documento *HTML*. - Para conseguir el contenido crudo de la respuesta, llama a ``$client->getResponse()->getContent()``. - -Haz clic en un enlace seleccionándolo primero con el ``Crawler`` utilizando una expresión ``XPath`` o un selector *CSS*, luego utiliza el cliente para hacer clic en él. Por ejemplo, el siguiente código buscará todos los enlaces con el texto ``Greet``, a continuación, selecciona el segundo, y en última instancia, hace clic en él:: - - $link = $crawler->filter('a:contains("Greet")')->eq(1)->link(); - - $crawler = $client->click($link); - -El envío de un formulario es muy similar; selecciona un botón del formulario, opcionalmente sustituye algunos valores del formulario, y ​​envía el formulario correspondiente:: - - $form = $crawler->selectButton('submit')->form(); - - // sustituye algunos valores - $form['name'] = 'Lucas'; - $form['form_name[subject]'] = 'Hey there!'; - - // envía el formulario - $crawler = $client->submit($form); - -.. tip:: - - El formulario también puede manejar archivos subidos y contiene métodos para llenar los diferentes tipos de campos del formulario (por ejemplo, ``select()`` y ``tick()``). Para más detalles, consulta la sección `Formularios`_ más adelante. - -Ahora que puedes navegar fácilmente a través de una aplicación, utiliza las aserciones para probar que en realidad hace lo que se espera. Utiliza el ``Crawler`` para hacer aserciones sobre el *DOM*:: - - // Afirma que la respuesta concuerda con un determinado selector CSS. - $this->assertGreaterThan(0, $crawler->filter('h1')->count()); - -O bien, prueba contra el contenido de la respuesta directamente si lo que deseas es acertar que el contenido contiene algún texto, o si la respuesta no es un documento *XML* o *HTML*:: - - $this->assertRegExp( - '/Hello Fabien/', - $client->getResponse()->getContent() - ); - -.. _book-testing-request-method-sidebar: - -.. sidebar:: Más sobre el método ``request()``: - - La firma completa del método ``request()`` es la siguiente:: - - request( - $method, - $uri, - array $parameters = array(), - array $files = array(), - array $server = array(), - $content = null, - $changeHistory = true - ) - - El arreglo ``server`` son los valores crudos que esperarías encontrar normalmente en la superglobal `$_SERVER`_ de *PHP*. Por ejemplo, para establecer las cabeceras *HTTP* ``Content-Type``, ``Referer`` y ``X-Requested-With``, deberías pasar lo siguiente (no olvides el prefijo ``HTTP_`` para las cabeceras que no son estándar):: - - $client->request( - 'GET', - '/demo/hello/Fabien', - array(), - array(), - array( - 'CONTENT_TYPE' => 'application/json', - 'HTTP_REFERER' => '/foo/bar', - 'HTTP_X-Requested-With' => 'XMLHttpRequest', - ) - ); - -.. index:: - single: Pruebas; Aserciones - -.. sidebar:: Útiles aserciones - - Para empezar más rápido, aquí tienes una lista de las aserciones más comunes y útiles:: - - // Acierta que cuando menos hay una etiqueta h2 - // con la clase «subtitle» - $this->assertGreaterThan( - 0, - $crawler->filter('h2.subtitle')->count() - ); - - // Acierta que hay exactamente 4 etiquetas h2 en la página - $this->assertCount(4, $crawler->filter('h2')); - - // Acierta que la cabecera 'Content-Type' es 'application/json' - $this->assertTrue( - $client->getResponse()->headers->contains( - 'Content-Type', - 'application/json' - ) - ); - - // Acierta que el contenido de la respuesta concuerda con una expresión regular. - $this->assertRegExp('/foo/', $client->getResponse()->getContent()); - - // Acierta que el código de estado de la respuesta es 2xx - $this->assertTrue($client->getResponse()->isSuccessful()); - // Acierta que el código de estado de la respuesta es 404 - $this->assertTrue($client->getResponse()->isNotFound()); - // Acierta un código de estado 200 específico - $this->assertEquals( - 200, - $client->getResponse()->getStatusCode() - ); - - // Acierta que la respuesta es una redirección a '/demo/contact' - $this->assertTrue( - $client->getResponse()->isRedirect('/demo/contact') - ); - // o simplemente comprueba que la respuesta es una redirección a cualquier URL - $this->assertTrue($client->getResponse()->isRedirect()); - -.. index:: - single: Pruebas; Cliente - -Trabajando con el Cliente de pruebas ------------------------------------- - -El Cliente de pruebas simula un cliente *HTTP* tal como un navegador y hace peticiones a tu aplicación *Symfony2*:: - - $crawler = $client->request('GET', '/hello/Fabien'); - -El método ``request()`` toma el método *HTTP* y una *URL* como argumentos y devuelve una instancia de ``Crawler``. - -Utiliza el rastreador para encontrar elementos del *DOM* en la respuesta. Puedes utilizar estos elementos para hacer clic en los enlaces y presentar formularios:: - - $link = $crawler->selectLink('Go elsewhere...')->link(); - $crawler = $client->click($link); - - $form = $crawler->selectButton('validate')->form(); - $crawler = $client->submit($form, array('name' => 'Fabien')); - -Ambos métodos ``click()`` y ``submit()`` devuelven un objeto ``Crawler``. -Estos métodos son la mejor manera para navegar por tu aplicación permitiéndole se preocupe de un montón de detalles por ti, tal como detectar el método *HTTP* de un formulario y proporcionándote una buena *API* para cargar archivos. - -.. tip:: - - Aprenderás más sobre los objetos ``Link`` y ``Form`` más adelante en la sección :ref:`Crawler `. - -También puedes usar el método ``request`` para simular el envío de formularios directamente o realizar peticiones más complejas:: - - // envía un formulario directamente (¡Pero es más fácil usando el 'Crawler'!) - $client->request('POST', '/submit', array('name' => 'Fabien')); - - // Envía una simple cadena JSON en el cuerpo de la petición - $client->request( - 'POST', - '/submit', - array(), - array(), - array('CONTENT_TYPE' => 'application/json'), - '{"name":"Fabien"}' - ); - - // envía un formulario con un campo para subir un archivo - use Symfony\Component\HttpFoundation\File\UploadedFile; - - $photo = new UploadedFile( - '/ruta/a/foto.jpg', - 'foto.jpg', - 'image/jpeg', - 123 - ); - $client->request( - 'POST', - '/submit', - array('name' => 'Fabien'), - array('photo' => $photo) - ); - - // Realiza una petición DELETE, y pasa las cabeceras HTTP - $client->request( - 'DELETE', - '/post/12', - array(), - array(), - array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word') - ); - -Por último pero no menos importante, puedes hacer que cada petición se ejecute en su propio proceso *PHP* para evitar efectos secundarios cuando se trabaja con varios clientes en el mismo archivo:: - - $client->insulate(); - -Navegando -~~~~~~~~~ - -El cliente es compatible con muchas operaciones que se pueden hacer en un navegador real:: - - $client->back(); - $client->forward(); - $client->reload(); - - // Limpia todas las cookies y el historial - $client->restart(); - -Accediendo a objetos internos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si utilizas el cliente para probar tu aplicación, posiblemente quieras acceder a los objetos internos del cliente:: - - $history = $client->getHistory(); - $cookieJar = $client->getCookieJar(); - -También puedes obtener los objetos relacionados con la última petición:: - - $request = $client->getRequest(); - $response = $client->getResponse(); - $crawler = $client->getCrawler(); - -Si tus peticiones no son aisladas, también puedes acceder al ``Contenedor`` y al ``núcleo``:: - - $container = $client->getContainer(); - $kernel = $client->getKernel(); - -Accediendo al contenedor -~~~~~~~~~~~~~~~~~~~~~~~~ - -Es altamente recomendable que una prueba funcional sólo pruebe la respuesta. Sin embargo, bajo ciertas circunstancias muy raras, posiblemente desees acceder a algunos objetos internos para escribir aserciones. En tales casos, puedes acceder al contenedor de inyección de dependencias:: - - $container = $client->getContainer(); - -Ten en cuenta que esto no tiene efecto si aíslas el cliente o si utilizas una capa *HTTP*. Para listar todos los servicios disponibles en tu aplicación, utiliza la orden ``container:debug`` de la consola. - -.. tip:: - - Si la información que necesitas comprobar está disponible desde el generador de perfiles, úsala en su lugar. - -Accediendo a los datos del perfil -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En cada petición, puedes habilitar el perfilador de *Symfony* para recolectar datos sobre el -manejo interno de esa petición. Por ejemplo, podrías usar el perfilador para verificar que una determinada página ejecuta menos de un cierto número de consultas a la base de datos al cargarla. - -Para obtener el generador de perfiles de la última petición, haz lo siguiente:: - - // habilita el perfilador para la próxima petición - $client->enableProfiler(); - - $crawler = $client->request('GET', '/profiler'); - - // consigue el perfil - $profile = $client->getProfile(); - -Para detalles específicos en el uso del generador de perfiles en una prueba, consulta el artículo :doc:`/cookbook/testing/profiling` en el recetario. - -Redirigiendo -~~~~~~~~~~~~ - -Cuando una petición devuelve una respuesta de redirección, el cliente no la sigue automáticamente. Puedes examinar la ``Respuesta`` y después forzar la redirección con el método ``followRedirect()``:: - - $crawler = $client->followRedirect(); - -Si quieres que el cliente siga todos los cambios de dirección automáticamente, lo puedes forzar con el método ``followRedirects()``:: - - $client->followRedirects(); - -.. index:: - single: Pruebas; Crawler - -.. _book-testing-crawler: - -El ``Crawler`` --------------- - -Cada vez que hagas una petición con el cliente devolverá una instancia del ``Crawler``. -Este nos permite recorrer documentos *HTML*, seleccionar nodos, encontrar enlaces y formularios. - -Recorriendo -~~~~~~~~~~~ - -Al igual que *jQuery*, el ``Crawler`` tiene métodos para recorrer el *DOM* de un documento *HTML*/*XML*: Por ejemplo, el siguiente fragmento encuentra todos los elementos ``input[type=submit]``, selecciona el último en la página, y luego selecciona el elemento padre inmediato:: - - $newCrawler = $crawler->filter('input[type=submit]') - ->last() - ->parents() - ->first() - ; - -Disponemos de muchos otros métodos: - -+------------------------+----------------------------------------------------+ -| Método | Descripción | -+========================+====================================================+ -| ``filter('h1.title')`` | Nodos que coinciden con el selector *CSS* | -+------------------------+----------------------------------------------------+ -| ``filterXpath('h1')`` | Nodos que coinciden con la expresión *XPath* | -+------------------------+----------------------------------------------------+ -| ``eq(1)`` | Nodo para el índice especificado | -+------------------------+----------------------------------------------------+ -| ``first()`` | Primer nodo | -+------------------------+----------------------------------------------------+ -| ``last()`` | Último nodo | -+------------------------+----------------------------------------------------+ -| ``siblings()`` | Hermanos | -+------------------------+----------------------------------------------------+ -| ``nextAll()`` | Todos los hermanos siguientes | -+------------------------+----------------------------------------------------+ -| ``previousAll()`` | Todos los hermanos precedentes | -+------------------------+----------------------------------------------------+ -| ``parents()`` | Devuelve los nodos padre | -+------------------------+----------------------------------------------------+ -| ``children()`` | Devuelve los nodos hijo | -+------------------------+----------------------------------------------------+ -| ``reduce($lambda)`` | Nodos para los cuales el ejecutable no devuelve | -| | ``false`` | -+------------------------+----------------------------------------------------+ - -Debido a que cada uno de estos métodos devuelve una nueva instancia del ``Crawler``, puedes reducir tu selección de nodos encadenando las llamadas al método:: - - $crawler - ->filter('h1') - ->reduce(function ($node, $i) - { - if (!$node->getAttribute('class')) { - return false; - } - }) - ->first(); - -.. tip:: - - Usa la función ``count()`` para obtener el número de nodos almacenados en un ``Crawler``: - ``count($crawler)`` - -Extrayendo información -~~~~~~~~~~~~~~~~~~~~~~ - -El ``Crawler`` puede extraer información de los nodos:: - - // Devuelve el valor del atributo del primer nodo - $crawler->attr('class'); - - // Devuelve el valor del nodo para el primer nodo - $crawler->text(); - - // Extrae un arreglo de atributos de todos los nodos - // (_text devuelve el valor del nodo) - // devuelve un arreglo de cada elemento en 'crawler', - // cada cual con su valor y href - $info = $crawler->extract(array('_text', 'href')); - - // Ejecuta una función anónima por cada nodo y - // devuelve un arreglo de resultados - $data = $crawler->each(function ($node, $i) - { - return $node->attr('href'); - }); - -Enlaces -~~~~~~~ - -Para seleccionar enlaces, puedes usar los métodos de recorrido anteriores o el conveniente atajo ``selectLink()``:: - - $crawler->selectLink('Click here'); - -Este selecciona todos los enlaces que contienen el texto dado, o hace clic en las imágenes en que el atributo ``alt`` contiene el texto dado. Al igual que los otros métodos de filtrado, devuelve otro objeto ``Crawler``. - -Una vez seleccionado un enlace, tienes acceso al objeto especial ``Link``, el cual tiene útiles métodos específicos para enlaces (tal como ``getMethod()`` y -``getUri()``). Para hacer clic en el enlace, usa el método ``click()`` del cliente suministrando un objeto ``Link``:: - - $link = $crawler->selectLink('Click here')->link(); - - $client->click($link); - -Formularios -~~~~~~~~~~~ - -Al igual que con cualquier otro enlace, seleccionas el formulario con el método ``selectButton()``:: - - $buttonCrawlerNode = $crawler->selectButton('submit'); - -.. note:: - - Ten en cuenta que seleccionamos botones del formulario y no el formulario porque un formulario puede tener varios botones; si utilizas la *API* para recorrerlos, ten en cuenta que debes buscar un botón. - -El método ``selectButton()`` puede seleccionar etiquetas ``button`` y etiquetas ``input`` de envío. Este usa diferentes partes de los botones para encontrarlos: - -* El valor del atributo ``value``; - -* El valor del atributo ``id`` o ``alt`` de imágenes; - -* El valor del atributo ``id`` o ``name`` de las etiquetas ``button``. - -Una vez que tienes un ``Crawler`` que representa un botón, invoca al método ``form()`` para obtener la instancia del ``Formulario`` del nodo del formulario que envuelve al botón:: - - $form = $buttonCrawlerNode->form(); - -Cuando llamas al método ``form()``, también puedes pasar un arreglo de valores de campo que sustituyan los valores predeterminados:: - - $form = $buttonCrawlerNode->form(array( - 'name' => 'Fabien', - 'my_form[subject]' => 'Symfony rocks!', - )); - -Y si quieres simular un método *HTTP* específico del formulario, pásalo como segundo argumento:: - - $form = $buttonCrawlerNode->form(array(), 'DELETE'); - -El cliente puede enviar instancias de ``Form``:: - - $client->submit($form); - -Los valores del campo también se pueden pasar como segundo argumento del método ``submit()``:: - - $client->submit($form, array( - 'name' => 'Fabien', - 'my_form[subject]' => 'Symfony rocks!', - )); - -Para situaciones más complejas, utiliza la instancia de ``Form`` como un arreglo para establecer el valor de cada campo individualmente:: - - // Cambia el valor de un campo - $form['name'] = 'Fabien'; - $form['my_form[subject]'] = 'Symfony rocks!'; - -También hay una buena *API* para manipular los valores de los campos de acuerdo a su tipo:: - - // selecciona una opción o un botón de radio - $form['country']->select('France'); - - // marca una casilla de verificación (checkbox) - $form['like_symfony']->tick(); - - // carga un archivo - $form['photo']->upload('/ruta/a/lucas.jpg'); - -.. tip:: - - Puedes conseguir los valores que se enviarán llamando al método ``getValues()`` del objeto ``Form``. Los archivos subidos están disponibles en un arreglo separado devuelto por ``getFiles()``. Los métodos ``getPhpValues()`` y ``getPhpFiles()`` también devuelven los valores enviados, pero en formato *PHP* (este convierte las claves en notación con paréntesis cuadrados ---por ejemplo, ``my_form[subject]``--- a arreglos *PHP*). - -.. index:: - pair: Pruebas; Configuración - -Probando la configuración -------------------------- - -El cliente utilizado por las pruebas funcionales crea un núcleo que se ejecuta en un entorno de ``prueba`` especial. Debido a que *Symfony* carga el ``app/config/config_test.yml`` -en el entorno ``test``, puedes ajustar cualquiera de las opciones de tu aplicación específicamente para pruebas. - -Por ejemplo, por omisión, el ``swiftmailer`` está configurado para que en el entorno ``test`` *no* se entregue realmente el correo electrónico. Lo puedes ver bajo la opción de configuración ``swiftmailer``. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_test.yml - - # ... - swiftmailer: - disable_delivery: true - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config_test.php - - // ... - $container->loadFromExtension('swiftmailer', array( - 'disable_delivery' => true, - )); - -Además, puedes usar un entorno completamente diferente, o redefinir el modo de depuración predeterminado (``true``) pasando cada opción al método ``createClient()``:: - - $client = static::createClient(array( - 'environment' => 'my_test_env', - 'debug' => false, - )); - -Si tu aplicación se comporta de acuerdo a algunas cabeceras *HTTP*, pásalas como segundo argumento de ``createClient()``:: - - $client = static::createClient(array(), array( - 'HTTP_HOST' => 'en.example.com', - 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', - )); - -También puedes reemplazar cabeceras *HTTP* en base a la petición:: - - $client->request('GET', '/', array(), array(), array( - 'HTTP_HOST' => 'en.example.com', - 'HTTP_USER_AGENT' => 'MySuperBrowser/1.0', - )); - -.. tip:: - - El cliente de prueba está disponible como un servicio en el contenedor del entorno ``test`` (o cuando está habilitada la opción :ref:`framework.test `). Esto significa que ---de ser necesario--- puedes redefinir el servicio completamente. - -.. index:: - pair: PHPUnit; Configuración - -Configuración de *PHPUnit* -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada aplicación tiene su propia configuración de *PHPUnit*, almacenada en el archivo ``phpunit.xml.dist``. Puedes editar este archivo para cambiar los valores predeterminados o crear un archivo :file:`phpunit.xml` para modificar la configuración de tu máquina local. - -.. tip:: - - Guarda el archivo :file:`phpunit.xml.dist` en tu repositorio de código, e ignora el archivo :file:`phpunit.xml`. - -De forma predeterminada, la orden ``PHPUnit`` sólo ejecuta las pruebas almacenadas en los paquetes «estándar» (las pruebas estándar están en el directorio :file:`src/*/Bundle/Tests` o :file:`src/*/Bundle/*Bundle/Tests`), pero fácilmente puedes añadir más directorios. Por ejemplo, la siguiente configuración añade las pruebas de los paquetes de terceros que has instalado: - -.. code-block:: xml - - - - - ../src/*/*Bundle/Tests - ../src/Acme/Bundle/*Bundle/Tests - - - -Para incluir otros directorios en la cobertura de código, también edita la sección ````: - -.. code-block:: xml - - - - - ../src - - ../src/*/*Bundle/Resources - ../src/*/*Bundle/Tests - ../src/Acme/Bundle/*Bundle/Resources - ../src/Acme/Bundle/*Bundle/Tests - - - - -Aprende más ------------ - -* :doc:`/components/dom_crawler` -* :doc:`/components/css_selector` -* :doc:`/cookbook/testing/http_authentication` -* :doc:`/cookbook/testing/insulating_clients` -* :doc:`/cookbook/testing/profiling` -* :doc:`/cookbook/testing/bootstrap` - - -.. _`DemoControllerTest`: https://github.com/symfony/symfony-standard/blob/master/src/Acme/DemoBundle/Tests/Controller/DemoControllerTest.php -.. _`$_SERVER`: http://www.php.net/manual/es/reserved.variables.server.php -.. _`documentación`: http://www.phpunit.de/manual/3.5/en/ diff --git a/_sources/book/translation.txt b/_sources/book/translation.txt deleted file mode 100644 index 0a53856..0000000 --- a/_sources/book/translation.txt +++ /dev/null @@ -1,842 +0,0 @@ -.. index:: - single: Traducciones - -Traduciendo -=========== - -El término «internacionalización» (frecuentemente abreviado como `i18n`_) se refiere al proceso de abstraer cadenas y otros elementos específicos de la configuración regional de tu aplicación a una capa donde se puedan traducir y convertir basándose en la configuración regional del usuario (es decir, idioma y país). Para el texto, esto significa envolver cada uno con una función capaz de traducir el texto (o «mensaje») al idioma del usuario:: - - // el texto *siempre* se imprime en Inglés - echo 'Hello World'; - - // el texto se puede traducir al idioma del - // usuario final o predeterminado en Inglés - echo $translator->trans('Hello World'); - -.. note:: - - El término *locale* se refiere más o menos al lenguaje y país del usuario. Este puede ser cualquier cadena que tu aplicación utilice para manejar las traducciones y otras diferencias de formato (por ejemplo, el formato de moneda). Recomendamos el código de *idioma* `ISO639-1`_, un guión bajo (``_``), luego el código de *país* `ISO3166 Alpha-2`_ (p. ej. ``fr_FR`` para Francés/Francia). - -En este capítulo, aprenderas cómo preparar una aplicación para apoyar varias configuraciones regionales, y luego, cómo crear traducciones para múltiples regiones. En general, el proceso tiene varios pasos comunes: - -#. Habilitar y configurar el componente ``Translation`` de *Symfony*; - -#. Abstraer cadenas (es decir, «mensajes») envolviéndolas en llamadas al ``Traductor``; - -#. Crear recursos de traducción para cada configuración regional compatible, la cual traduce cada mensaje en la aplicación; - -#. Determinar, establecer y administrar la configuración regional del usuario para la petición y, opcionalmente, la sesión entera. - -.. index:: - single: Traducciones; Configuración - -Configurando ------------- - -La traducción está a cargo de un :term:`servicio` ``Traductor`` que utiliza la configuración regional del usuario para buscar y devolver mensajes traducidos. Antes de usarlo, habilita el ``Traductor`` en tu configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - translator: { fallback: en } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'translator' => array('fallback' => 'en'), - )); - -La opción ``fallback`` define la configuración regional de reserva cuando la traducción no existe en la configuración regional del usuario. - -.. tip:: - - Cuando la traducción no existe para una configuración regional, el traductor primero intenta encontrar la traducción para el idioma («``es``» si la región es «``es_MX``» por ejemplo). Si esto también falla, busca una traducción utilizando la configuración regional de reserva. - -La región utilizada en las traducciones es la almacenada en la petición. Esta, normalmente se establece en el atributo ``_locale`` de tus rutas (ve :ref:`book-translation-locale-url`). - -.. index:: - single: Traducciones; Traducción básica - -Traducción básica ------------------ - -La traducción de texto se hace a través del servicio ``traductor`` (:class:`Symfony\\Component\\Translation\\Translator`). Para traducir un bloque de texto (llamado un *mensaje*), utiliza el método :method:`Symfony\\Component\\Translation\\Translator::trans`. Supongamos, por ejemplo, que estás traduciendo un simple mensaje desde el interior de un controlador:: - - // ... - use Symfony\Component\HttpFoundation\Response; - - public function indexAction() - { - $t = $this->get('translator')->trans('Symfony2 is great'); - - return new Response($t); - } - -Al ejecutar este código, *Symfony2* tratará de traducir el mensaje ``«Symfony2 is great»``, basándose en la variable ``locale`` del usuario. Para que esto funcione, tenemos que decirle a *Symfony2* la manera de traducir el mensaje a través de un «recurso de traducción», el cual es una colección de mensajes traducidos para una determinada configuración regional. -Este «diccionario» de traducciones se puede crear en varios formatos diferentes, *XLIFF* es el formato recomendado: - -.. configuration-block:: - - .. code-block:: xml - - - - - - - - Symfony2 is great - J'aime Symfony2 - - - - - - .. code-block:: php - - // messages.fr.php - return array( - 'Symfony2 is great' => 'J\'aime Symfony2', - ); - - .. code-block:: yaml - - # messages.fr.yml - Symfony2 is great: J'aime Symfony2 - -Ahora, si el idioma de la configuración regional del usuario es el Francés (por ejemplo, ``fr_FR`` o ``fr_BE``), el mensaje será traducido a ``J'aime Symfony2``. - -El proceso de traducción -~~~~~~~~~~~~~~~~~~~~~~~~ - -Para empezar a traducir el mensaje, *Symfony2* utiliza un sencillo proceso: - -* Determina la ``región`` del usuario actual, la cual está almacenada en la petición (o almacenada como ``locale`` en la sesión del usuario); - -* Carga un catálogo de mensajes traducidos desde los recursos de traducción definidos para la configuración de ``locale`` (por ejemplo, ``fr_FR``). Los mensajes de la configuración regional de reserva también se cargan y se agregan al catálogo si no existen ya. El resultado final es un gran «diccionario» de traducciones. Consulta `Catálogos de mensajes`_ para más detalles; - -* Si se encuentra el mensaje en el catálogo, devuelve la traducción. En caso contrario, el traductor devuelve el mensaje original. - -Cuando se usa el método ``trans()``, *Symfony2* busca la cadena exacta dentro del catálogo de mensajes apropiado y la devuelve (si existe). - -.. index:: - single: Traducciones; Marcadores de posición en mensajes - -Marcadores de posición en mensajes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A veces, debes traducir un mensaje que contiene una variable:: - - // ... - use Symfony\Component\HttpFoundation\Response; - - public function indexAction($name) - { - $t = $this->get('translator')->trans('Hello '.$name); - - return new Response($t); - } - -Sin embargo, la creación de una traducción de esta cadena es imposible, ya que el traductor tratará de buscar el mensaje exacto, incluyendo las porciones variables (por ejemplo, ``«Hello Ryan»`` o ``«Hello Fabien»``). En lugar de escribir una traducción de cada posible iteración de la variable ``$name``, podemos reemplazar la variable con un «marcador de posición»:: - - // ... - use Symfony\Component\HttpFoundation\Response; - - public function indexAction($name) - { - $t = $this->get('translator')->trans( - 'Hello %name%', - array('%name%' => $name) - ); - - return new Response($t); - } - -*Symfony2* ahora busca una traducción del mensaje en bruto (``Hello %name%``) y *después* reemplaza los marcadores de posición con sus valores. La creación de una traducción se hace igual que antes: - -.. configuration-block:: - - .. code-block:: xml - - - - - - - - Hello %name% - Bonjour %name% - - - - - - .. code-block:: php - - // messages.fr.php - return array( - 'Hello %name%' => 'Bonjour %name%', - ); - - .. code-block:: yaml - - # messages.fr.yml - 'Hello %name%': Bonjour %name% - -.. note:: - - Los marcadores de posición pueden tomar cualquier forma, el mensaje completo se reconstruye usando la `función strtr`_ de *PHP*. Sin embargo, se requiere la notación ``%var%`` cuando se traduce en plantillas *Twig*, y en general es un convenio razonable a seguir. - -Como ya viste, la creación de una traducción es un proceso de dos pasos: - -#. Abstraer el mensaje que se necesita traducir procesándolo con el ``Traductor``. - -#. Crear una traducción del mensaje para cada región que elijas apoyar. - -El segundo paso se realiza creando catálogos de mensajes que definen las traducciones para cualquier número de regiones diferentes. - -.. index:: - single: Traducciones; Catálogos de mensajes - -Catálogos de mensajes ---------------------- - -Cuando se traduce un mensaje, *Symfony2* compila un catálogo de mensajes para la configuración regional del usuario y busca en ese una traducción del mensaje. Un catálogo de mensajes es como un diccionario de traducciones para una configuración regional específica. Por ejemplo, el catálogo de la configuración regional ``fr_FR`` podría contener la siguiente traducción: - -.. code-block:: text - - Symfony2 is Great => J'aime Symfony2 - -Es responsabilidad del desarrollador (o traductor) de una aplicación internacionalizada crear estas traducciones. Las traducciones son almacenadas en el sistema de archivos y descubiertas por *Symfony*, gracias a algunos convenios. - -.. tip:: - - Cada vez que creas un *nuevo* recurso de traducción (o instalas un paquete que incluye un recurso de traducción), para que *Symfony* pueda descubrir el nuevo recurso de traducción, asegúrate de borrar la caché con la siguiente orden: - - .. code-block:: bash - - $ php app/console cache:clear - -.. index:: - single: Traducciones; Ubicación de recursos de traducción - -Ubicación de traducción y convenciones de nomenclatura -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Symfony2* busca archivos de mensajes (es decir, traducciones) en los siguientes lugares: - -* el ``/Resources/translations``; - -* el ``/Resources//translations``; - -* el directorio ``Resources/translations/`` del paquete. - -Las ubicaciones están enumeradas basándose en su prioridad. Por lo tanto puedes -redefinir la traducción de los mensajes de un paquete en cualquiera de los 2 directorios superiores. - -El mecanismo de sustitución trabaja a nivel de claves: sólo es necesario enumerar las claves remplazadas en un archivo de mensajes de alta prioridad. Cuando una clave no se encuentra en un archivo de mensajes, el traductor automáticamente vuelve a los archivos de mensajes de reserva de menor prioridad. - -El nombre del archivo de las traducciones también es importante ya que *Symfony2* utiliza una convención para determinar los detalles sobre las traducciones. Cada archivo de mensaje se tiene que denominar de acuerdo a la siguiente ruta: ``dominio.región.cargador``: - -* **dominio**: Una forma opcional para organizar los mensajes en grupos (por ejemplo, ``admin``, ``navigation`` o el valor predeterminado ``messages``) --- consulta `Usando el dominio de los mensajes`_; - -* **región**: La región para la cual son las traducciones (por ejemplo, «\ ``en_GB``\ », «``en``\ », etc.); - -* **cargador**: ¿Cómo debe cargar y analizar el archivo *Symfony2* (por ejemplo, ``XLIFF``, ``php`` o ``yml``). - -El cargador puede ser el nombre de cualquier gestor registrado. De manera predeterminada, *Symfony* incluye los siguientes cargadores: - -* ``xliff``: archivo *XLIFF*; -* ``php``: archivo *PHP*; -* ``yml``: archivo *YAML*. - -La elección del cargador a utilizar es totalmente tuya y es una cuestión de gusto. - -.. note:: - - También puedes almacenar las traducciones en una base de datos, o cualquier otro almacenamiento, proporcionando una clase personalizada que implemente la interfaz :class:`Symfony\\Component\\Translation\\Loader\\LoaderInterface`. - -.. index:: - single: Traducciones; Creando recursos de traducción - -Creando traducciones -~~~~~~~~~~~~~~~~~~~~ - -El acto de crear archivos de traducción es una parte importante de la «localización» (a menudo abreviado `L10n`_). Los archivos de traducción constan de una serie de pares Identificador-traducción para un determinado dominio y configuración regional. - La fuente es el identificador para la traducción individual, y puede ser el mensaje en la región principal (por ejemplo ``«Symfony is great»``) de tu aplicación o un identificador único (por ejemplo, ``«symfony2.great»`` ---ve la barra lateral más abajo---): - -.. configuration-block:: - - .. code-block:: xml - - - - - - - - Symfony2 is great - J'aime Symfony2 - - - symfony2.great - J'aime Symfony2 - - - - - - .. code-block:: php - - // src/Acme/DemoBundle/Resources/translations/messages.fr.php - return array( - 'Symfony2 is great' => 'J\'aime Symfony2', - 'symfony2.great' => 'J\'aime Symfony2', - ); - - .. code-block:: yaml - - # src/Acme/DemoBundle/Resources/translations/messages.fr.yml - Symfony2 is great: J'aime Symfony2 - symfony2.great: J'aime Symfony2 - -*Symfony2* descubrirá estos archivos y los utilizará cuando traduce o bien ``«Symfony2 is great»`` o ``«symfony2.great»`` en un idioma regional de Francés (por ejemplo, ``fr_FR`` o ``fr_BE``). - -.. sidebar:: Usar mensajes reales o palabras clave - - Este ejemplo ilustra las dos diferentes filosofías cuando creas mensajes a traducir:: - - $t = $translator->trans('Symfony2 is great'); - - $t = $translator->trans('symfony2.great'); - - En el primer método, los mensajes están escritos en el idioma de la región predeterminada (Inglés en este caso). Ese mensaje se utiliza entonces como el «id» al crear traducciones. - - En el segundo método, los mensajes en realidad son «palabras clave» que transmiten la idea del mensaje. Entonces, la palabra clave del mensaje se utiliza como el «id» para las traducciones. En este caso, la traducción se debe hacer para la región predeterminada (es decir, para traducir ``symfony2.great`` a ``Symfony2 is great``). - - El segundo método es útil porque la clave del mensaje no se tendrá que cambiar en cada archivo de la traducción si decides que el mensaje en realidad debería decir «Ciertamente Symfony2 es magnífico» en la región predeterminada. - - La elección del método a utilizar es totalmente tuya, pero a menudo se recomienda el formato de «palabra clave». - - Además, es compatible con archivos anidados en formato ``php`` y ``yaml`` para evitar repetir siempre lo mismo si utilizas palabras clave en lugar de texto real para tus identificadores: - - .. configuration-block:: - - .. code-block:: yaml - - symfony2: - is: - great: Symfony2 is great - amazing: Symfony2 is amazing - has: - bundles: Symfony2 has bundles - user: - login: Login - - .. code-block:: php - - return array( - 'symfony2' => array( - 'is' => array( - 'great' => 'Symfony2 is great', - 'amazing' => 'Symfony2 is amazing', - ), - 'has' => array( - 'bundles' => 'Symfony2 has bundles', - ), - ), - 'user' => array( - 'login' => 'Login', - ), - ); - - Los niveles múltiples se acoplan en pares de id/traducción añadiendo un punto (.) entre cada nivel, por lo tanto los ejemplos anteriores son equivalentes a los siguientes: - - .. configuration-block:: - - .. code-block:: yaml - - symfony2.is.great: Symfony2 is great - symfony2.is.amazing: Symfony2 is amazing - symfony2.has.bundles: Symfony2 has bundles - user.login: Login - - .. code-block:: php - - return array( - 'symfony2.is.great' => 'Symfony2 is great', - 'symfony2.is.amazing' => 'Symfony2 is amazing', - 'symfony2.has.bundles' => 'Symfony2 has bundles', - 'user.login' => 'Login', - ); - -.. index:: - single: Traducciones; Dominio de los mensajes - -Usando el dominio de los mensajes ---------------------------------- - -Como ya viste, los archivos de mensajes se organizan en las diferentes regiones en que se traducirán. Los archivos de mensajes también se pueden organizar en «dominios». -Al crear archivos de mensajes, el dominio es la primera porción del nombre de archivo. -El dominio predeterminado es ``messages``. Por ejemplo, supongamos que, por organización, las traducciones se dividieron en tres diferentes dominios: ``messages``, ``admin`` -y ``navigation``. La traducción española debe tener los siguientes archivos de mensaje: - -* ``messages.es.xliff`` -* ``admin.es.xliff`` -* ``navigation.es.xliff`` - -Al traducir las cadenas que no están en el dominio predeterminado (``messages``), debes especificar el dominio como tercer argumento de ``trans()``:: - - $this->get('translator')->trans('Symfony2 is great', array(), 'admin'); - -*Symfony2* ahora buscará el mensaje en el dominio ``admin`` de la configuración regional del usuario. - -.. index:: - single: Traducciones; Región del usuario - -Manejando la configuración regional del usuario ------------------------------------------------ - -La configuración regional del usuario actual se almacena en la petición y se puede acceder a través del objeto ``Petición``:: - - // accede al objeto request en un controlador estándar - $request = $this->getRequest(); - - $locale = $request->getLocale(); - - $request->setLocale('en_US'); - -.. index:: - single: Traducciones; Región y reserva predeterminada - -También es posible almacenar la región en la sesión en lugar de en base a cada petición. Si lo haces, cada subsecuente petición tendrá esa región. - -.. code-block:: php - - $this->get('session')->set('_locale', 'en_US'); - -En la sección :ref:`book-translation-locale-url`, más adelante, puedes ver cómo configurar la región a través del enrutado. - -Configuración regional predeterminada y reserva -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si la configuración regional no se ha establecido explícitamente en la sesión, el parámetro de configuración ``fallback_locale`` será utilizado por el ``Traductor``. El valor predeterminado del parámetro es ``«en»`` (consulta la sección `Configurando`_). - -Alternativamente, puedes garantizar que hay una ``región`` establecida en cada petición definiendo una opción ``default_locale`` en la clave ``framework``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - default_locale: en - - .. code-block:: xml - - - - en - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'default_locale' => 'en', - )); - -.. versionadded:: 2.1 - El parámetro ``default_locale`` originalmente se definía bajo la clave ``session``, sin embargo, se movió en la versión 2.1. Esto se debe a que la región ahora se establece en la petición en lugar de en la sesión. - -.. _book-translation-locale-url: - -``Locale`` y la *URL* -~~~~~~~~~~~~~~~~~~~~~ - -Dado que la configuración regional del usuario se almacena en la sesión, puede ser tentador utilizar la misma *URL* para mostrar un recurso en muchos idiomas diferentes en función de la región del usuario. Por ejemplo, ``http://www.ejemplo.com/contact`` podría mostrar el contenido en Inglés para un usuario y en Francés para otro. Por desgracia, esto viola una norma fundamental de la *Web*: que una *URL* particular devuelve el mismo recurso, independientemente del usuario. Para acabar de enturbiar el problema, ¿cual sería la versión del contenido indexado por los motores de búsqueda? - -Una mejor política es incluir la configuración regional en la *URL*. Esto es totalmente compatible con el sistema de enrutado mediante el parámetro especial ``_locale``: - -.. configuration-block:: - - .. code-block:: yaml - - contact: - path: /{_locale}/contact - defaults: { _controller: AcmeDemoBundle:Contact:index, _locale: en } - requirements: - _locale: en|fr|de - - .. code-block:: xml - - - AcmeDemoBundle:Contact:index - en - en|fr|de - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('contact', new Route('/{_locale}/contact', array( - '_controller' => 'AcmeDemoBundle:Contact:index', - '_locale' => 'en', - ), array( - '_locale' => 'en|fr|de', - ))); - - return $collection; - -Cuando utilizas el parámetro especial ``_locale`` en una ruta, la configuración regional emparejada *automáticamente se establece en la sesión del usuario*. En otras palabras, si un usuario visita la *URI* ``/es/contact``, la región ``«es»`` se ajustará automáticamente según la configuración regional de la sesión del usuario. - -Ahora puedes utilizar la configuración regional del usuario para crear rutas hacia otras páginas traducidas en tu aplicación. - -.. index:: - single: Traducciones; Pluralización - -Pluralizando ------------- - -La pluralización de mensajes es un tema difícil puesto que las reglas pueden ser bastante complejas. Por ejemplo, aquí tienes la representación matemática de las reglas de pluralización de Rusia:: - - (($number % 10 == 1) && ($number % 100 != 11)) - ? 0 - : ((($number % 10 >= 2) - && ($number % 10 <= 4) - && (($number % 100 < 10) - || ($number % 100 >= 20))) - ? 1 - : 2 - ); - -Como puedes ver, en Ruso, puedes tener tres formas diferentes del plural, dando a cada una un índice de 0, 1 o 2. Para todas las formas, el plural es diferente, por lo que la traducción también es diferente. - -Cuando una traducción tiene diferentes formas debido a la pluralización, puedes proporcionar todas las formas como una cadena separada por una línea vertical (``|``):: - - 'Hay una manzana|Hay %count% manzanas' - -Para traducir mensajes pluralizados, utiliza el método :method:`Symfony\\Component\\Translation\\Translator::transChoice`:: - - $t = $this->get('translator')->transChoice( - 'There is one apple|There are %count% apples', - 10, - array('%count%' => 10) - ); - -El segundo argumento (``10`` en este ejemplo), es el *número* de objetos descrito y se utiliza para determinar cual traducción usar y también para rellenar el marcador de posición ``%count%``. - -En base al número dado, el traductor elige la forma plural adecuada. -En Inglés, la mayoría de las palabras tienen una forma singular cuando hay exactamente un objeto y una forma plural para todos los otros números (0, 2, 3...). Así pues, si ``count`` es ``1``, el traductor utilizará la primera cadena (``Hay una manzana``) como la traducción. De lo contrario, utilizará ``Hay %count% manzanas``. - -Aquí está la traducción al Francés:: - - 'Il y a %count% pomme|Il y a %count% pommes' - -Incluso si la cadena tiene una apariencia similar (se compone de dos subcadenas separadas por una línea vertical), las reglas francesas son diferentes: la primera forma (no plural) se utiliza cuando ``count`` es ``0`` o ``1``. Por lo tanto, el traductor utilizará automáticamente la primera cadena (``Il y a %count% pomme``) cuando ``count`` es ``0`` o ``1``. - -Cada región tiene su propio conjunto de reglas, con algunas que tienen hasta seis formas diferentes de plural con reglas complejas detrás de las cuales los números asignan a tal forma plural. -Las reglas son bastante simples para Inglés y Francés, pero para el Ruso, puedes querer una pista para saber qué regla coincide con qué cadena. Para ayudar a los traductores, puedes «etiquetar» cada cadena:: - - 'one: There is one apple|some: There are %count% apples' - - 'none_or_one: Il y a %count% pomme|some: Il y a %count% pommes' - -Las etiquetas realmente son pistas sólo para los traductores y no afectan a la lógica utilizada para determinar qué forma plural usar. Las etiquetas pueden ser cualquier cadena descriptiva que termine con dos puntos (``:``). Las etiquetas además no necesitan ser las mismas en el mensaje original cómo en la traducción. - -.. tip:: - - Como las etiquetas son opcionales, el traductor no las utiliza (el traductor únicamente obtendrá una cadena basada en su posición en la cadena). - -Intervalo explícito de pluralización -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La forma más fácil de pluralizar un mensaje es dejar que *Symfony2* utilice su lógica interna para elegir qué cadena se utiliza en base a un número dado. A veces, tendrás más control o quieres una traducción diferente para casos específicos (por ``0``, o cuando el número es negativo, por ejemplo). Para estos casos, puedes utilizar intervalos matemáticos explícitos:: - - '{0} There are no apples|{1} There is one apple|]1,19] There are %count% apples|[20,Inf] There are many apples' - -Los intervalos siguen la notación `ISO 31-11`_. La cadena anterior especifica cuatro intervalos diferentes: exactamente ``0``, exactamente ``1``, ``2-19`` y ``20`` y superior. - -También puedes mezclar reglas matemáticas explícitas y estándar. En este caso, si la cuenta no corresponde con un intervalo específico, las reglas estándar entran en vigor después de remover las reglas explícitas:: - - '{0} There are no apples|[20,Inf] There are many apples|There is one apple|a_few: There are %count% apples' - -Por ejemplo, para ``1 apple``, la regla estándar ``There is one apple`` será utilizada. Para ``2-19 apples``, la segunda regla estándar ``There are %count% -apples`` será seleccionada. - -Un :class:`Symfony\\Component\\Translation\\Interval` puede representar un conjunto finito de números:: - - {1,2,3,4} - -O números entre otros dos números:: - - [1, +Inf[ - ]-1,2[ - -El delimitador izquierdo puede ser ``[`` (inclusive) o ``]`` (exclusivo). El delimitador derecho puede ser ``[`` (exclusivo) o ``]`` (inclusive). Más allá de los números, puedes usar ``-Inf`` y ``+Inf`` para el infinito. - -.. index:: - single: Traducciones; En plantillas - -Traducciones en plantillas --------------------------- - -La mayoría de las veces, la traducción ocurre en las plantillas. *Symfony2* proporciona apoyo nativo para ambas plantillas *Twig* y *PHP*. - -.. _book-translation-tags: - -Plantillas *Twig* -~~~~~~~~~~~~~~~~~ - -*Symfony2* proporciona etiquetas *Twig* especializadas (``trans`` y ``transchoice``) para ayudar con la traducción de los mensajes de *bloques de texto estático*: - -.. code-block:: jinja - - {% trans %}Hello %name%{% endtrans %} - - {% transchoice count %} - {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples - {% endtranschoice %} - -La etiqueta ``transchoice`` obtiene automáticamente la variable ``%count%`` a partir del contexto actual y la pasa al traductor. Este mecanismo sólo funciona cuando se utiliza un marcador de posición después del patrón ``%var%``. - -.. tip:: - - Si necesitas utilizar el carácter de porcentaje (``%``) en una cadena, lo tienes que escapar duplicando el siguiente: ``{% trans %}Porcentaje: %percent%%%{% endtrans %}`` - -También puedes especificar el dominio del mensaje y pasar algunas variables adicionales: - -.. code-block:: jinja - - {% trans with {'%name%': 'Fabien'} from "app" %}Hello %name%{% endtrans %} - - {% trans with {'%name%': 'Fabien'} from "app" into "fr" %}Hello %name%{% endtrans %} - - {% transchoice count with {'%name%': 'Fabien'} from "app" %} - {0} %name%, there are no apples|{1} %name%, there is one apple|]1,Inf] %name%, there are %count% apples - {% endtranschoice %} - -.. _book-translation-filters: - -Los filtros ``trans`` y ``transchoice`` se pueden utilizar para traducir *texto variable* y expresiones complejas: - -.. code-block:: jinja - - {{ message|trans }} - - {{ message|transchoice(5) }} - - {{ message|trans({'%name%': 'Fabien'}, "app") }} - - {{ message|transchoice(5, {'%name%': 'Fabien'}, 'app') }} - -.. tip:: - - Usar etiquetas de traducción o filtros tiene el mismo efecto, pero con una sutil diferencia: la salida escapada automáticamente sólo se aplica a las traducciones usando un filtro. En otras palabras, si necesitas garantizar que tu traducción *no* se escape en la salida, debes aplicar el filtro ``raw`` después del filtro de traducción: - - .. code-block:: jinja - - {# el texto traducido entre etiquetas nunca se escapa #} - {% trans %} -

foo

- {% endtrans %} - - {% set message = '

foo

' %} - - {# Las cadenas y variables traducidas vía un filtro se escapan por omisión #} - {{ message|trans|raw }} - {{ '

bar

'|trans|raw }} - -.. tip:: - - Puedes establecer el ámbito de traducción para una plantilla *Twig* entera con una sola etiqueta: - - .. code-block:: jinja - - {% trans_default_domain "app" %} - - Ten en cuenta que esto sólo influye en la plantilla actual, no en las plantillas «incluidas» (con el fin de evitar efectos secundarios). - -.. versionadded:: 2.1 - La etiqueta ``trans_default_domain`` es nueva en *Symfony2.1* - -Plantillas *PHP* -~~~~~~~~~~~~~~~~ - -El servicio traductor es accesible en plantillas *PHP* a través del ayudante ``translator``: - -.. code-block:: html+php - - trans('Symfony2 is great') ?> - - transChoice( - '{0} There is no apples|{1} There is one apple|]1,Inf[ There are %count% apples', - 10, - array('%count%' => 10) - ) ?> - -Forzando la configuración regional del traductor ------------------------------------------------- - -Al traducir un mensaje, *Symfony2* utiliza la configuración regional de la petición o la configuración regional de ``reserva`` si es necesario. También puedes especificar manualmente la configuración regional utilizada para la traducción:: - - $this->get('translator')->trans( - 'Symfony2 is great', - array(), - 'messages', - 'fr_FR' - ); - - $this->get('translator')->transChoice( - '{0} There are no apples|{1} There is one apple|]1,Inf[ There are %count% apples', - 10, - array('%count%' => 10), - 'messages', - 'fr_FR' - ); - -Traduciendo contenido de base de datos --------------------------------------- - -La traducción del contenido de la base de datos la debe manejar *Doctrine* a través de la `Extensión Translatable`_. Para más información, consulta la documentación de la biblioteca. - -.. _book-translation-constraint-messages: - -Traduciendo mensajes de restricción ------------------------------------ - -La mejor manera de entender la traducción de las restricciones es verla en acción. Para empezar, supongamos que creaste un sencillo objeto *PHP* el cual en algún lugar tiene que utilizar tu aplicación:: - - // src/Acme/BlogBundle/Entity/Author.php - namespace Acme\BlogBundle\Entity; - - class Author - { - public $name; - } - -Añade las restricciones con cualquiera de los métodos admitidos. Configura la opción de mensaje al texto fuente traducido. Por ejemplo, para garantizar que la propiedad ``$name`` no esté vacía, agrega lo siguiente: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - properties: - name: - - NotBlank: { message: "author.name.not_blank" } - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/Author.php - use Symfony\Component\Validator\Constraints as Assert; - - class Autor - { - /** - * @Assert\NotBlank(message = "author.name.not_blank") - */ - public $name; - } - - .. code-block:: xml - - - - - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\NotBlank; - - class Autor - { - public $name; - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('name', new NotBlank(array( - 'message' => 'author.name.not_blank', - ))); - } - } - -Crea un archivo de traducción bajo el catálogo ``validators`` para los mensajes de restricción, por lo general en el directorio ``Resources/translations/`` del paquete. Consulta `Catálogos de mensajes`_ para más detalles; - -.. configuration-block:: - - .. code-block:: xml - - - - - - - - author.name.not_blank - Por favor, ingresa un nombre de autor. - - - - - - .. code-block:: php - - // validators.en.php - return array( - 'author.name.not_blank' => 'Por favor, ingresa un nombre de autor.', - ); - - .. code-block:: yaml - - # validators.en.yml - author.name.not_blank: Por favor, ingresa un nombre de autor.. - -Resumen -------- - -Con el componente ``Translation`` de *Symfony2*, la creación de una aplicación internacionalizada ya no tiene que ser un proceso doloroso y se reduce a sólo algunos pasos básicos: - -* Abstrae los mensajes en tu aplicación envolviendo cada uno en el método :method:`Symfony\\Component\\Translation\\Translator::trans` o :method:`Symfony\\Component\\Translation\\Translator::transChoice`; - -* Traduce cada mensaje en varias configuraciones regionales creando archivos de mensajes traducidos. *Symfony2* descubre y procesa cada archivo porque su nombre sigue una convención específica; - -* Gestiona la región del usuario, la cual está almacenada en la petición, pero también se puede poner en la sesión del usuario. - -.. _`i18n`: http://es.wikipedia.org/wiki/Internacionalizaci%C3%B3n_y_localizaci%C3%B3n -.. _`L10n`: http://es.wikipedia.org/wiki/Internacionalizaci%C3%B3n_y_localizaci%C3%B3n -.. _`función strtr`: http://www.php.net/manual/es/function.strtr.php -.. _`ISO 31-11`: http://en.wikipedia.org/wiki/Interval_(mathematics)#Notations_for_intervals -.. _`Extensión Translatable`: https://github.com/l3pp4rd/DoctrineExtensions -.. _`ISO3166 Alpha-2`: http://en.wikipedia.org/wiki/ISO_3166-1#Current_codes -.. _`ISO639-1`: http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes diff --git a/_sources/book/validation.txt b/_sources/book/validation.txt deleted file mode 100644 index 18e0012..0000000 --- a/_sources/book/validation.txt +++ /dev/null @@ -1,773 +0,0 @@ -.. index:: - single: Validando - -Validando -========= - -La validación es una tarea muy común en aplicaciones *web*. Los datos introducidos en formularios se tienen que validar. Los datos también se deben validar antes de escribirlos en una base de datos o pasarlos a un servicio *web*. - -*Symfony2* viene con un componente `Validator`_ que facilita y transparenta esta tarea. Este componente está basado en la `especificación de validación Bean JSR303`_. - -.. index:: - single: Validación; Fundamentos - -Fundamentos de la validación ----------------------------- - -La mejor manera de entender la validación es verla en acción. Para empezar, supongamos que creaste un objeto *PHP* plano el cual en algún lugar tiene que utilizar tu aplicación:: - - // src/Acme/BlogBundle/Entity/Author.php - namespace Acme\BlogBundle\Entity; - - class Author - { - public $name; - } - -Hasta ahora, esto es sólo una clase ordinaria que sirve a algún propósito dentro de tu aplicación. El objetivo de la validación es decir si o no los datos de un objeto son válidos. Para que esto funcione, debes configurar una lista de reglas (llamada :ref:`constraints ---en adelante: restricciones--- `) que el objeto debe seguir para ser válido. Estas reglas se pueden especificar a través de una serie de diferentes formatos (*YAML*, *XML*, anotaciones o *PHP*). - -Por ejemplo, para garantizar que la propiedad ``$name`` no esté vacía, agrega lo siguiente: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - properties: - name: - - NotBlank: ~ - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Constraints as Assert; - - class Autor - { - /** - * @Assert\NotBlank() - */ - public $name; - } - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\NotBlank; - - class Autor - { - public $name; - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('name', new NotBlank()); - } - } - -.. tip:: - - Las propiedades protegidas y privadas también se pueden validar, así como los métodos «get» (consulta la sección :ref:`validator-constraint-targets`). - -.. index:: - single: Validación; Usando el validador - -Usando el servicio ``validador`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A continuación, para validar realmente un objeto ``Author``, utiliza el método ``validate`` del servicio ``validador`` (clase :class:`Symfony\\Component\\Validator\\Validator`). -El trabajo del ``validador`` es fácil: lee las restricciones (es decir, las reglas) de una clase y comprueba si los datos en el objeto satisfacen esas restricciones. Si la validación falla, devuelve un arreglo de errores. Toma este sencillo ejemplo desde el interior de un controlador:: - - // ... - use Symfony\Component\HttpFoundation\Response; - use Acme\BlogBundle\Entity\Author; - - public function indexAction() - { - $author = new Author(); - // ... hace algo con el objeto $author - - $validator = $this->get('validator'); - $errors = $validator->validate($author); - - if (count($errors) > 0) { - return new Response(print_r($errors, true)); - } else { - return new Response('The author is valid! Yes!'); - } - } - -Si la propiedad ``$name`` está vacía, verás el siguiente mensaje de error: - -.. code-block:: text - - Acme\BlogBundle\Author.name: - This value should not be blank - -Si insertas un valor en la propiedad ``name``, aparecerá el satisfactorio mensaje de éxito. - -.. tip:: - - La mayor parte del tiempo, no interactúas directamente con el servicio ``validador`` o necesitas preocuparte por imprimir los errores. La mayoría de las veces, vas a utilizar la validación indirectamente al manejar los datos de formularios presentados. Para más información, consulta la sección :ref:`book-validation-forms`. - -También puedes pasar la colección de errores a una plantilla. - -.. code-block:: php - - if (count($errors) > 0) { - return $this->render('AcmeBlogBundle:Author:validate.html.twig', array( - 'errors' => $errors, - )); - } else { - // ... - } - -Dentro de la plantilla, puedes sacar la lista de errores exactamente como la necesites: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/BlogBundle/Resources/views/Autor/validate.html.twig #} -

The author has the following errors

-
    - {% for error in errors %} -
  • {{ error.message }}
  • - {% endfor %} -
- - .. code-block:: html+php - - -

The author has the following errors

-
    - -
  • getMessage() ?>
  • - -
- -.. note:: - - Cada error de validación (conocido cómo «violación de restricción»), está representado por un objeto :class:`Symfony\\Component\\Validator\\ConstraintViolation`. - -.. index:: - single: Validación; Validando con formularios - -.. _book-validation-forms: - -Validación y formularios -~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes utilizar el servicio ``validator`` en cualquier momento para validar cualquier objeto. -En realidad, sin embargo, por lo general al trabajar con formularios vas a trabajar con el ``validador`` indirectamente. La biblioteca de formularios de *Symfony* utiliza internamente el servicio ``validador`` para validar el objeto subyacente después de que los valores se han presentado y vinculado. Las violaciones de restricción en el objeto se convierten en objetos ``FieldError`` los cuales puedes mostrar fácilmente en tu formulario. El flujo de trabajo típico en la presentación del formulario se parece al siguiente visto desde el interior de un controlador:: - - // ... - use Acme\BlogBundle\Entity\Author; - use Acme\BlogBundle\Form\AuthorType; - use Symfony\Component\HttpFoundation\Request; - - public function updateAction(Request $request) - { - $author = new Author(); - $form = $this->createForm(new AuthorType(), $author); - - if ($request->isMethod('POST')) { - $form->bind($request); - - if ($form->isValid()) { - // validación superada, haz algo con el objeto $author - - return $this->redirect($this->generateUrl(...)); - } - } - - return $this->render('BlogBundle:Author:form.html.twig', array( - 'form' => $form->createView(), - )); - } - -.. note:: - - Este ejemplo utiliza un formulario de la clase ``AutorType``, el cual no mostramos aquí. - -Para más información, consulta el capítulo :doc:`Formularios `. - -.. index:: - pair: Validación; Configuración - -.. _book-validation-configuration: - -Configurando ------------- - -El validador de *Symfony2* está activado por omisión, pero debes habilitar explícitamente las anotaciones si estás utilizando el método de anotación para especificar tus restricciones: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - validation: { enable_annotations: true } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('framework', array( - 'validation' => array( - 'enable_annotations' => true, - ), - )); - -.. index:: - single: Validación; Restricciones - -.. _validation-constraints: - -Restricciones -------------- - -El ``validador`` está diseñado para validar objetos contra *restricciones* (es decir, reglas). A fin de validar un objeto, basta con asignar una o más restricciones a tu clase y luego pasarla al servicio ``validador``. - -Detrás del escenario, una restricción simplemente es un objeto *PHP* que hace una declaración asertiva. En la vida real, una restricción podría ser: «El pastel no se debe quemar». -En *Symfony2*, las restricciones son similares: son aserciones de que una condición es verdadera. Dado un valor, una restricción te dirá si o no el valor se adhiere a las reglas de tu restricción. - -Restricciones compatibles -~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Symfony2* viene con un gran número de las más comunes restricciones necesarias. - -.. include:: /reference/constraints/map.rst.inc - -También puedes crear tus propias restricciones personalizadas. Este tema se trata en el artículo «:doc:`/cookbook/validation/custom_constraint`» del recetario. - -.. index:: - single: Validación; Configurando restricciones - -.. _book-validation-constraint-configuration: - -Configurando restricciones -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Algunas restricciones, como :doc:`NotBlank`, son simples, mientras que otras, como la restricción :doc:`Choice`, tienen varias opciones de configuración disponibles. Supongamos que la clase ``Autor`` tiene otra propiedad, ``género`` que se puede configurar como «masculino» o «femenino»: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - properties: - gender: - - Choice: { choices: [male, female], message: Choose a valid gender. } - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/Author.php - use Symfony\Component\Validator\Constraints as Assert; - - class Autor - { - /** - * @Assert\Choice( - * choices = { "male", "female" }, - * message = "Choose a valid gender." - * ) - */ - public $gender; - } - - .. code-block:: xml - - - - - - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\Choice; - - class Autor - { - public $gender; - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('gender', new Choice(array( - 'choices' => array('male', 'female'), - 'message' => 'Choose a valid gender.', - ))); - } - } - -.. _validation-default-option: - -Las opciones de una restricción siempre se pueden pasar como un arreglo. Algunas restricciones, sin embargo, también te permiten pasar el valor de una opción *«predeterminada»*, en lugar del arreglo. En el caso de la restricción ``Choice``, las ``opciones`` se pueden especificar de esta manera: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - properties: - gender: - - Choice: [male, female] - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Constraints as Assert; - - class Autor - { - /** - * @Assert\Choice({"male", "female"}) - */ - protected $gender; - } - - .. code-block:: xml - - - - - - - - - male - female - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\Choice; - - class Autor - { - protected $gender; - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint( - 'gender', - new Choice(array('male', 'female')) - ); - } - } - -Esto, simplemente está destinado a hacer que la configuración de las opciones más comunes de una restricción sea más breve y rápida. - -Si alguna vez no estás seguro de cómo especificar una opción, o bien consulta la documentación de la *API* por la restricción o juega a lo seguro pasando siempre las opciones en un arreglo (el primer método se muestra más arriba). - -Traduciendo mensajes de restricción ------------------------------------ - -Para más información sobre la traducción de los mensajes de restricción, consulta :ref:`book-translation-constraint-messages`. - -.. index:: - single: Validación; Objetivos de restricción - -.. _validator-constraint-targets: - -Objetivos de restricción ------------------------- - -Las restricciones se pueden aplicar a una propiedad de clase (por ejemplo, ``name``) o a un método captador público (por ejemplo ``getFullName``). El primero es el más común y fácil de usar, pero el segundo te permite especificar reglas de validación más complejas. - -.. index:: - single: Validación; Restricción de propiedades - -.. _validation-property-target: - -Propiedades -~~~~~~~~~~~ - -La validación de propiedades de clase es la técnica de validación más básica. *Symfony2* te permite validar propiedades privadas, protegidas o públicas. El siguiente listado muestra cómo configurar la propiedad ``$firstName`` de una clase ``Author`` para que por lo menos tenga 3 caracteres. - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - properties: - firstName: - - NotBlank: ~ - - Length: - min: 3 - - .. code-block:: php-annotations - - // Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Constraints as Assert; - - class Autor - { - /** - * @Assert\NotBlank() - * @Assert\Length(min = "3") - */ - private $firstName; - } - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\NotBlank; - use Symfony\Component\Validator\Constraints\Length; - - class Autor - { - private $firstName; - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('firstName', new NotBlank()); - $metadata->addPropertyConstraint( - 'firstName', - new Length(array("min" => 3))); - } - } - -.. index:: - single: Validación; Restricción de captadores - -Captadores -~~~~~~~~~~ - -Las restricciones también se pueden aplicar al valor devuelto por un método. *Symfony2* te permite agregar una restricción a cualquier método público cuyo nombre comience con ``get`` o ``is``. En esta guía, ambos métodos de este tipo son conocidos como «captadores» o ``getters``. - -La ventaja de esta técnica es que te permite validar el objeto de forma dinámica. Por ejemplo, supongamos que quieres asegurarte de que un campo de contraseña no coincide con el nombre del usuario (por razones de seguridad). Puedes hacerlo creando un método ``isPasswordLegal``, a continuación, acertar que este método debe devolver ``true``: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - getters: - passwordLegal: - - "True": { message: "The password cannot match your first name" } - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Constraints as Assert; - - class Autor - { - /** - * @Assert\True(message = "The password cannot match your first name") - */ - public function isPasswordLegal() - { - // devuelve 'true' o 'false' - } - } - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\True; - - class Autor - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addGetterConstraint('passwordLegal', new True(array( - 'message' => 'The password cannot match your first name', - ))); - } - } - -Ahora, crea el método ``isPasswordLegal()`` e incluye la lógica que necesites:: - - public function isPasswordLegal() - { - return ($this->firstName != $this->password); - } - -.. note:: - - El ojo perspicaz se habrá dado cuenta de que el prefijo del captador (``get`` o ``is``) se omite en la asignación. Esto te permite mover la restricción a una propiedad con el mismo nombre más adelante (o viceversa) sin cambiar la lógica de validación. - -.. _validation-class-target: - -Clases -~~~~~~ - -Algunas restricciones se aplican a toda la clase que se va a validar. Por ejemplo, la restricción :doc:`Retrollamada ` es una restricción que se aplica a la clase en sí misma: Cuando se valide esa clase, los métodos especificados por esta restricción se ejecutarán simplemente para que cada uno pueda proporcionar una validación más personalizada. - -.. _book-validation-validation-groups: - -Validando grupos ----------------- - -Hasta ahora, hemos sido capaces de agregar restricciones a una clase y consultar si o no esa clase pasa todas las restricciones definidas. En algunos casos, sin embargo, tendrás que validar un objeto contra únicamente *algunas* restricciones de esa clase. Para ello, puedes organizar cada restricción en uno o más «grupos de validación», y luego aplicar la validación contra un solo grupo de restricciones. - -Por ejemplo, supongamos que tienes una clase ``Usuario``, la cual se usa más adelante tanto cuando un usuario se registra como cuando un usuario actualiza su información de contacto: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\User: - properties: - email: - - Email: { groups: [registration] } - password: - - NotBlank: { groups: [registration] } - - Length: { min: 7, groups: [registration] } - city: - - Length: - min: 2 - - .. code-block:: php-annotations - - // src/Acme/BlogBundle/Entity/User.php - namespace Acme\BlogBundle\Entity; - - use Symfony\Component\Security\Core\User\UserInterface; - use Symfony\Component\Validator\Constraints as Assert; - - class User implements UserInterface - { - /** - * @Assert\Email(groups={"registration"}) - */ - private $email; - - /** - * @Assert\NotBlank(groups={"registration"}) - * @Assert\Length(min=7, groups={"registration"}) - */ - private $password; - - /** - * @Assert\Length(min = "2") - */ - private $city; - } - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/User.php - namespace Acme\BlogBundle\Entity; - - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints\Email; - use Symfony\Component\Validator\Constraints\NotBlank; - use Symfony\Component\Validator\Constraints\Length; - - class User - { - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('email', new Email(array( - 'groups' => array('registration'), - ))); - - $metadata->addPropertyConstraint('password', new NotBlank(array( - 'groups' => array('registration'), - ))); - $metadata->addPropertyConstraint('password', new Length(array( - 'min' => 7, - 'groups' => array('registration') - ))); - - $metadata->addPropertyConstraint( - 'city', - Length(array("min" => 3))); - } - } - -Con esta configuración, hay dos grupos de validación: - -* contiene las restricciones no asignadas a algún otro grupo; - -* contiene restricciones sólo en los campos de ``email`` y ``password``. - -Para decir al validador que use un grupo específico, pasa uno o más nombres de grupo como segundo argumento al método ``validate()``:: - - $errors = $validator->validate($author, array('registration')); - -Por supuesto, por lo general vas a trabajar con la validación indirectamente a través de la biblioteca de formularios. Para obtener información sobre cómo utilizar la validación de grupos dentro de los formularios, consulta :ref:`book-forms-validation-groups`. - -.. index:: - single: Validación; Validando valores crudos - -.. _book-validation-raw-values: - -Validando valores y arreglos ----------------------------- - -Hasta ahora, hemos visto cómo puedes validar objetos completos. Pero a veces, sólo deseas validar un único valor ---como verificar que una cadena es una dirección de correo electrónico válida---. Esto realmente es muy fácil de hacer. Desde el interior de un controlador, se ve así:: - - use Symfony\Component\Validator\Constraints\Email; - // ... - - public function addEmailAction($email) - { - $emailConstraint = new Email(); - // puedes fijar todas las "opciones" de restricción de esta manera - $emailConstraint->message = 'Invalid email address'; - - // usa el validador para validar el valor - $errorList = $this->get('validator')->validateValue( - $email, - $emailConstraint - ); - - if (count($errorList) == 0) { - // esta ES una dirección de correo válida, haz algo - } else { - // esta *no* es una dirección de correo electrónico válida - $errorMessage = $errorList[0]->getMessage(); - - // ... haz algo con el error - } - - // ... - } - -Al llamar a ``validateValue`` en el validador, puedes pasar un valor en bruto y el objeto restricción contra el cual deseas validar el valor. Una lista completa de restricciones disponibles ---así como el nombre de clase completo para cada restricción--- está disponible en la sección :doc:`referencia de restricciones `. - -El método ``validateValue`` devuelve un objeto :class:`Symfony\\Component\\Validator\\ConstraintViolationList`, que actúa como un arreglo de errores. Cada error de la colección es un objeto :class:`Symfony\\Component\\Validator\\ConstraintViolation`, que contiene el mensaje de error en su método ``getMessage``. - -Consideraciones finales ------------------------ - -El ``validador`` de *Symfony2* es una herramienta poderosa que puedes aprovechar para garantizar que los datos de cualquier objeto son «válidos». El poder detrás de la validación radica en las «restricciones», las cuales son reglas que se pueden aplicar a propiedades o métodos captadores de tu objeto. Y mientras más utilices la plataforma de validación indirectamente cuando uses formularios, recordarás que puedes utilizarla en cualquier lugar para validar cualquier objeto. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/validation/custom_constraint` - -.. _`Validator`: https://github.com/symfony/Validator -.. _`especificación de validación Bean JSR303`: http://jcp.org/en/jsr/detail?id=303 diff --git a/_sources/bundles/DoctrineFixturesBundle/index.txt b/_sources/bundles/DoctrineFixturesBundle/index.txt deleted file mode 100644 index 9799cd1..0000000 --- a/_sources/bundles/DoctrineFixturesBundle/index.txt +++ /dev/null @@ -1,308 +0,0 @@ -``DoctrineFixturesBundle`` -========================== - -Los accesorios se utilizan para cargar en una base de datos un juego de datos controlado. Puedes utilizar estos datos para pruebas o podrían ser los datos iniciales necesarios para ejecutar la aplicación sin problemas. *Symfony2* no tiene integrada forma alguna de administrar accesorios, pero *Doctrine2* cuenta con una biblioteca para ayudarte a escribir accesorios para el :doc:`ORM ` u :doc:`ODM ` de *Doctrine*. - -Instalando y configurando -------------------------- - -Los accesorios de *Doctrine* para *Symfony* se mantienen en el `DoctrineMigrationsBundle`_. -El paquete utiliza la biblioteca externa de `Datos accesorios de Doctrine`_. - -Sigue estos pasos para instalar el paquete y la biblioteca en la edición estándar de *Symfony*. Añade lo siguiente a tu archivo :file:`composer.json`: - -.. code-block:: json - - { - "require": { - "doctrine/doctrine-fixtures-bundle": "dev-master" - } - } - -Actualiza las bibliotecas de proveedores: - -.. code-block:: bash - - $ php composer.phar update - -Si todo va bien, el ``DoctrineFixturesBundle`` ahora se puede encontrar en ``vendor/doctrine/doctrine-fixtures-bundle``. - -.. note:: - - ``DoctrineFixturesBundle`` instala la biblioteca de `Datos accesorios de Doctrine`_. La biblioteca se puede encontrar en ``vendor/doctrine/data-fixtures``. - -Por último, registra el paquete ``DoctrineFixturesBundle`` en ``app/AppKernel.php``. - -.. code-block:: php - - // ... - public function registerBundles() - { - $bundles = array( - // ... - new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), - // ... - ); - // ... - } - -Escribiendo accesorios sencillos --------------------------------- - -Los accesorios de *Doctrine2* son clases *PHP* que pueden crear y persistir objetos a la base de datos. Al igual que todas las clases en *Symfony2*, los accesorios deben vivir dentro de uno de los paquetes de tu aplicación. - -Para un paquete situado en ``src/Acme/HelloBundle``, las clases accesorio deben vivir dentro de ``src/Acme/HelloBundle/DataFixtures/ORM`` o ``src/Acme/HelloBundle/DataFixtures/MongoDB``, para *ORM* y *ODM* respectivamente, esta guía asume que estás utilizando el *ORM* --- pero, los accesorios se pueden agregar con la misma facilidad si estás utilizando *ODM*. - -Imagina que tienes una clase ``User``, y te gustaría cargar un nuevo ``Usuario``: - -.. code-block:: php - - // src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php - - namespace Acme\HelloBundle\DataFixtures\ORM; - - use Doctrine\Common\DataFixtures\FixtureInterface; - use Doctrine\Common\Persistence\ObjectManager; - use Acme\HelloBundle\Entity\User; - - class LoadUserData implements FixtureInterface - { - /** - * {@inheritDoc} - */ - public function load(ObjectManager $manager) - { - $userAdmin = new User(); - $userAdmin->setUsername('admin'); - $userAdmin->setPassword('test'); - - $manager->persist($userAdmin); - $manager->flush(); - } - } - -En *Doctrine2*, los accesorios sólo son objetos en los que cargas datos interactuando con tus entidades como lo haces normalmente. Esto te permite crear el accesorio exacto que necesitas para tu aplicación. - -La limitación más importante es que no puedes compartir objetos entre accesorios. -Más adelante, veremos la manera de superar esta limitación. - -Ejecutando accesorios ---------------------- - -Una vez que has escrito tus accesorios, los puedes cargar a través de la línea de ordenes usando la orden ``doctrine:fixtures:load`` - -.. code-block:: bash - - php app/console doctrine:fixtures:load - -Si estás utilizando *ODM*, en su lugar usa la orden ``doctrine:mongodb:fixtures:load``: - -.. code-block:: bash - - php app/console doctrine:mongodb:fixtures:load - -La tarea verá dentro del directorio ``DataFixtures/ORM`` (o ``DataFixtures/MongoDB`` para *ODM*) de cada paquete y ejecutará cada clase que implemente la ``FixtureInterface``. - -Ambas ordenes vienen con unas cuantas opciones: - -* ``--fixtures=/ruta/al/accesorio`` --- Usa esta opción para especificar manualmente el directorio de donde se deben cargar las clases accesorio; - -* ``--append`` --- Utiliza esta opción para añadir datos en lugar de eliminarlos antes de cargarlos (borrar primero es el comportamiento predeterminado); - -* ``--em=manager_name`` --- Especifica manualmente el gestor de la entidad a utilizar para cargar los datos. - -.. note:: - - Si utilizas la tarea ``doctrine:mongodb:fixtures:load``, reemplaza la opción ``--em=`` con ``--dm=`` para especificar manualmente el gestor de documentos. - -Un ejemplo de uso completo podría tener este aspecto: - -.. code-block:: bash - - php app/console doctrine:fixtures:load --fixtures=/ruta/al/accesorio1 --fixtures=/ruta/al/accesorio2 --append --em=gestor_foo - -Compartiendo objetos entre accesorios -------------------------------------- - -Escribir un accesorio básico es sencillo. Pero, ¿si tienes varias clases de accesorios y quieres poder referirte a los datos cargados en otras clases accesorio? -Por ejemplo, ¿qué pasa si cargas un objeto ``Usuario`` en un accesorio, y luego quieres mencionar una referencia a un accesorio diferente con el fin de asignar dicho usuario a un grupo particular? - -La biblioteca de accesorios de *Doctrine* maneja esto fácilmente permitiéndote especificar el orden en que se cargan los accesorios. - -.. code-block:: php - - // src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php - namespace Acme\HelloBundle\DataFixtures\ORM; - - use Doctrine\Common\DataFixtures\AbstractFixture; - use Doctrine\Common\DataFixtures\OrderedFixtureInterface; - use Doctrine\Common\Persistence\ObjectManager; - use Acme\HelloBundle\Entity\User; - - class LoadUserData extends AbstractFixture implements OrderedFixtureInterface - { - /** - * {@inheritDoc} - */ - public function load(ObjectManager $manager) - { - $userAdmin = new User(); - $userAdmin->setUsername('admin'); - $userAdmin->setPassword('test'); - - $manager->persist($userAdmin); - $manager->flush(); - - $this->addReference('admin-user', $userAdmin); - } - - /** - * {@inheritDoc} - */ - public function getOrder() - { - return 1; // el orden en el cual serán cargados los accesorios - } - } - -La clase accesorio ahora implementa la ``OrderedFixtureInterface``, la cual dice a *Doctrine* que deseas controlar el orden de tus accesorios. Crea otra clase accesorio y haz que se cargue después de ``LoadUserData`` devolviendo un orden de 2: - -.. code-block:: php - - // src/Acme/HelloBundle/DataFixtures/ORM/LoadGroupData.php - - namespace Acme\HelloBundle\DataFixtures\ORM; - - use Doctrine\Common\DataFixtures\AbstractFixture; - use Doctrine\Common\DataFixtures\OrderedFixtureInterface; - use Doctrine\Common\Persistence\ObjectManager; - use Acme\HelloBundle\Entity\Group; - - class LoadGroupData extends AbstractFixture implements OrderedFixtureInterface - { - /** - * {@inheritDoc} - */ - public function load(ObjectManager $manager) - { - $groupAdmin = new Group(); - $groupAdmin->setGroupName('admin'); - - $manager->persist($groupAdmin); - $manager->flush(); - - $this->addReference('admin-group', $groupAdmin); - } - - /** - * {@inheritDoc} - */ - public function getOrder() - { - return 2; // el orden en el cual serán cargados los accesorios - } - } - -Ambas clases accesorio extienden ``AbstractFixture``, lo cual te permite crear objetos y luego ponerlos como referencias para que se puedan utilizar posteriormente en otros accesorios. Por ejemplo, más tarde puedes referirte a los objetos ``$userAdmin`` y ``$groupAdmin`` a través de las referencias ``admin-user`` y ``admin-group``: - -.. code-block:: php - - // src/Acme/HelloBundle/DataFixtures/ORM/LoadUserGroupData.php - - namespace Acme\HelloBundle\DataFixtures\ORM; - - use Doctrine\Common\DataFixtures\AbstractFixture; - use Doctrine\Common\DataFixtures\OrderedFixtureInterface; - use Doctrine\Common\Persistence\ObjectManager; - use Acme\HelloBundle\Entity\UserGroup; - - class LoadUserGroupData extends AbstractFixture implements OrderedFixtureInterface - { - /** - * {@inheritDoc} - */ - public function load(ObjectManager $manager) - { - $userGroupAdmin = new UserGroup(); - $userGroupAdmin->setUser($this->getReference('admin-user')); - $userGroupAdmin->setGroup($this->getReference('admin-group')); - - $manager->persist($userGroupAdmin); - $manager->flush(); - } - - /** - * {@inheritDoc} - */ - public function getOrder() - { - return 3; - } - } - -Los accesorios ahora se ejecutan en el orden ascendente del valor devuelto por ``getOrder()``. Cualquier objeto que se establece con el método ``setReference()`` se puede acceder a través de ``getReference()`` en las clases accesorio que tienen un orden superior. - -Los accesorios te permiten crear cualquier tipo de dato que necesites a través de la interfaz normal de *PHP* para crear y persistir objetos. Al controlar el orden de los accesorios y establecer referencias, puedes manejar casi todo por medio de accesorios. - -Usando el contenedor en los accesorios --------------------------------------- - -En algunos casos necesitarás acceder a algunos servicios para cargar los accesorios. -*Symfony2* hace esto realmente fácil. El contenedor se inyectará en todas las clases accesorio que implementen la :class:`Symfony\\Component\\DependencyInjection\\ContainerAwareInterface`. - -Vamos a rescribir el primer accesorio para codificar la contraseña antes de almacenarla en la base de datos (una muy buena práctica). Esto utilizará el generador de codificadores para codificar la contraseña, garantizando que está codificada en la misma forma que la utiliza el componente de seguridad al efectuar la verificación: - -.. code-block:: php - - // src/Acme/HelloBundle/DataFixtures/ORM/LoadUserData.php - - namespace Acme\HelloBundle\DataFixtures\ORM; - - use Doctrine\Common\DataFixtures\FixtureInterface; - use Symfony\Component\DependencyInjection\ContainerAwareInterface; - use Symfony\Component\DependencyInjection\ContainerInterface; - use Acme\HelloBundle\Entity\User; - - class LoadUserData implements FixtureInterface, ContainerAwareInterface - { - /** - * @var ContainerInterface - */ - private $container; - - /** - * {@inheritDoc} - */ - public function setContainer(ContainerInterface $container = null) - { - $this->container = $container; - } - - /** - * {@inheritDoc} - */ - public function load(ObjectManager $manager) - { - $user = new User(); - $user->setUsername('admin'); - $user->setSalt(md5(uniqid())); - - $encoder = $this->container - ->get('security.encoder_factory') - ->getEncoder($user) - ; - $user->setPassword($encoder->encodePassword('secret', $user->getSalt())); - - $manager->persist($user); - $manager->flush(); - } - } - -Como puedes ver, todo lo que necesitas hacer es agregar :class:`Symfony\\Component\\DependencyInjection\\ContainerAwareInterface` a la clase y luego crear un nuevo método :method:`Symfony\\Component\\DependencyInjection\\ContainerInterface::setContainer` que implemente esa interfaz. Antes de ejecutar el accesorio, *Symfony* automáticamente llamará al método :method:`Symfony\\Component\\DependencyInjection\\ContainerInterface::setContainer`. Siempre y cuando guardes el contenedor como una propiedad en la clase (como se muestra arriba), puedes acceder a él en el método ``load()``. - -.. note:: - - Si te da flojera implementar el método necesario :method:`Symfony\\Component\\DependencyInjection\\ContainerInterface::setContainer`, entonces, puedes extender tu clase con :class:`Symfony\\Component\\DependencyInjection\\ContainerAware`. - -.. _DoctrineFixturesBundle: https://github.com/doctrine/DoctrineFixturesBundle -.. _`Datos accesorios de Doctrine`: https://github.com/doctrine/data-fixtures diff --git a/_sources/bundles/DoctrineMigrationsBundle/index.txt b/_sources/bundles/DoctrineMigrationsBundle/index.txt deleted file mode 100644 index 11fbec7..0000000 --- a/_sources/bundles/DoctrineMigrationsBundle/index.txt +++ /dev/null @@ -1,241 +0,0 @@ -``DoctrineMigrationsBundle`` -============================ - -La funcionalidad de migración de base de datos, es una extensión de la capa de abstracción de bases de datos y te ofrece la posibilidad de desplegar programáticamente nuevas versiones del esquema de la base de datos de forma segura y estandarizada. - -.. tip:: - - Puedes leer más sobre las migraciones de base de datos de *Doctrine* en la `documentación`_ del proyecto. - -Instalando ----------- - -Las migraciones de *Doctrine* para *Symfony* se mantienen en el `DoctrineMigrationsBundle`_. -El paquete usa la biblioteca `Migraciones de Base de datos de Doctrine`_. - -Sigue estos pasos para instalar el paquete y la biblioteca en la edición estándar de *Symfony*. Añade lo siguiente a tu archivo :file:`composer.json`: - -.. code-block:: json - - { - "require": { - "doctrine/doctrine-migrations-bundle": "dev-master" - } - } - -Actualiza las bibliotecas de proveedores: - -.. code-block:: bash - - $ php composer.phar update - -Si todo funciona, ahora puedes encontrar la biblioteca ``DoctrineMigrationsBundle`` en ``vendor/doctrine/doctrine-migrations-bundle``. - -.. note:: - - ``DoctrineMigrationsBundle`` instala la biblioteca `Migraciones de Base de datos de Doctrine`_. Puedes encontrar la biblioteca en ``vendor/doctrine/migrations``. - -Por último, asegúrate de activar el paquete en ``AppKernel.php`` incluyendo lo siguiente: - -.. code-block:: php - - // app/AppKernel.php - public function registerBundles() - { - $bundles = array( - //... - new Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle(), - ); - } - -Usando ------- - -Toda la funcionalidad de las migraciones se encuentra en unas cuantas ordenes de consola: - -.. code-block:: bash - - doctrine:migrations - :diff Genera una migración comparando tu base de datos actual - a tu información asignada. - :execute Ejecuta manualmente una sola versión de migración hacia - arriba o abajo. - :generate Genera una clase de migración en blanco. - :migrate Ejecuta una migración a una determinada versión o, a la - última versión disponible. - :status Ve el estado de un conjunto de migraciones. - :version Añade y elimina versiones de migración manualmente desde - la tabla de versiones. - -Empieza consiguiendo la situación de las migraciones en tu aplicación ejecutando la orden ``status``: - -.. code-block:: bash - - php app/console doctrine:migrations:status - - == Configuration - - >> Name: Application Migrations - >> Configuration Source: manually configured - >> Version Table Name: migration_versions - >> Migrations Namespace: Application\Migrations - >> Migrations Directory: /ruta/a/proyecto/app/DoctrineMigrations - >> Current Version: 0 - >> Latest Version: 0 - >> Executed Migrations: 0 - >> Available Migrations: 0 - >> New Migrations: 0 - -Ahora, podemos empezar a trabajar con las migraciones generando una nueva clase de migración en blanco. Más adelante, aprenderás cómo puedes generar migraciones automáticamente con *Doctrine*. - -.. code-block:: bash - - php app/console doctrine:migrations:generate - Nueva clase migración generada para "/ruta/a/tu/proyecto/app/DoctrineMigrations/Version20100621140655.php" - -Echa un vistazo a la clase migración recién generada y verás algo como lo siguiente:: - - namespace Application\Migrations; - - use Doctrine\DBAL\Migrations\AbstractMigration, - Doctrine\DBAL\Schema\Schema; - - class Version20100621140655 extends AbstractMigration - { - public function up(Schema $schema) - { - - } - - public function down(Schema $schema) - { - - } - } - -Si ahora ejecutas la orden ``status`` te mostrará que tienes una nueva migración por ejecutar: - -.. code-block:: bash - - php app/console doctrine:migrations:status - - == Configuration - - >> Name: Application Migrations - >> Configuration Source: manually configured - >> Version Table Name: migration_versions - >> Migrations Namespace: Application\Migrations - >> Migrations Directory: /ruta/a/proyecto/app/DoctrineMigrations - >> Current Version: 0 - >> Latest Version: 2010-06-21 14:06:55 (20100621140655) - >> Executed Migrations: 0 - >> Available Migrations: 1 - >> New Migrations: 1 - - == Versiones de migración - - >> 2011-06-21 14:06:55 (20110621140655) no migrada - -Ahora puedes agregar algo de código de migración a los métodos ``up()`` y ``down()``, y finalmente cuando estés listo migrar: - -.. code-block:: bash - - php app/console doctrine:migrations:migrate - -Para más información sobre cómo escribir migraciones en sí mismas (es decir, la manera de rellenar los métodos ``up()`` y ``down()``), consulta la `documentación`_ oficial de las Migraciones de *Doctrine*. - -Ejecutando migraciones al desplegar tu aplicación -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Por supuesto, el objetivo final al escribir migraciones es poder utilizarlas para actualizar de manera fiable la estructura de tu base de datos cuando despliegues tu aplicación. -Al ejecutar las migraciones localmente (o en un servidor de pruebas), puedes asegurarte de que las migraciones trabajan según lo previsto. - -Cuando finalmente despliegues tu aplicación, sólo tienes que recordar ejecutar la orden ``doctrine:migrations:migrate``. Internamente, *Doctrine* crea una tabla ``migration_versions`` dentro de la base de datos y allí lleva a cabo el seguimiento de las migraciones que se han ejecutado. Por lo tanto, no importa cuantas migraciones hayas creado y ejecutado localmente, cuando se ejecuta la orden durante el despliegue, *Doctrine* sabrá exactamente qué migraciones no se han ejecutado todavía mirando la tabla ``migration_versions`` de tu base de datos en producción. Independientemente de qué servidor esté activado, siempre puedes ejecutar esta orden de forma segura para realizar sólo las migraciones que todavía no se han llevado a cabo en *esa* base de datos particular. - -Generando migraciones automáticamente -------------------------------------- - -En realidad, no deberías tener que escribir migraciones manualmente, puesto que la biblioteca de migraciones puede generar las clases de la migración automáticamente comparando tu información asignada a *Doctrine* (es decir, cómo se *debe* ver tu base de datos) con la estructura de la base de datos actual. - -Por ejemplo, supongamos que creas una nueva entidad ``Usuario`` y agregas información asignándola al *ORM* de *Doctrine*: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Acme/HelloBundle/Entity/User.php - namespace Acme\HelloBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - - /** - * @ORM\Entity - * @ORM\Table(name="hello_user") - */ - class User - { - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - protected $id; - - /** - * @ORM\Column(type="string", length="255") - */ - protected $name; - } - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/doctrine/User.orm.yml - Acme\HelloBundle\Entity\User: - type: entity - table: hello_user - id: - id: - type: integer - generator: - strategy: AUTO - fields: - name: - type: string - length: 255 - - .. code-block:: xml - - - - - - - - - - - - - -Con esta información, *Doctrine* ya está listo para ayudarte a persistir tu nuevo objeto ``Usuario`` hacia y desde la tabla ``hello_user``. Por supuesto, ¡esta tabla no existe aún! Genera una nueva migración para esta tabla automáticamente ejecutando la siguiente orden: - -.. code-block:: bash - - php app/console doctrine:migrations:diff - -Deberás ver un mensaje informando que se ha generado una nueva clase migración basada en las diferencias del esquema. Si abres ese archivo, encontrarás que tiene el código *SQL* necesario para crear la tabla ``hello_user``. A continuación, ejecuta la migración para agregar la tabla a tu base de datos: - -.. code-block:: bash - - php app/console doctrine:migrations:migrate - -La moraleja de la historia es la siguiente: después de cada cambio que realices en tu información de asignación a *Doctrine*, ejecuta la orden ``doctrine:migrations:diff`` para generar automáticamente las clases de la migración. - -Si lo haces desde el principio de tu proyecto (es decir, de modo que incluso las primeras tablas fueran cargadas a través de una clase migración), siempre podrás crear una base de datos actualizada y ejecutar las migraciones a fin de tener tu esquema de base de datos totalmente actualizado. De hecho, este es un flujo de trabajo fácil y confiable para tu proyecto. - -.. _`documentación`: http://docs.doctrine-project.org/projects/doctrine-migrations/en/latest/index.html -.. _`DoctrineMigrationsBundle`: https://github.com/doctrine/DoctrineMigrationsBundle -.. _`Migraciones de Base de datos de Doctrine`: https://github.com/doctrine/migrations diff --git a/_sources/bundles/DoctrineMongoDBBundle/config.txt b/_sources/bundles/DoctrineMongoDBBundle/config.txt deleted file mode 100644 index aaa0dc8..0000000 --- a/_sources/bundles/DoctrineMongoDBBundle/config.txt +++ /dev/null @@ -1,295 +0,0 @@ -Configurando ``DoctrineMongoDBBundle`` -====================================== - -Configuración de ejemplo ------------------------- - -.. code-block:: yaml - - # app/config/config.yml - doctrine_mongodb: - connections: - default: - server: mongodb://localhost:27017 - options: {} - default_database: hello_%kernel.environment% - document_managers: - default: - mappings: - AcmeDemoBundle: ~ - filters: - filter-name: - class: Class\Example\Filter\ODM\ExampleFilter - enabled: true - metadata_cache_driver: array # array, apc, xcache, memcache - -Si deseas utilizar ``memcache`` para memorizar tus metadatos, es necesario configurar la instancia ``Memcache``; por ejemplo, puedes hacer lo siguiente: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_mongodb: - default_database: hello_%kernel.environment% - connections: - default: - server: mongodb://localhost:27017 - options: {} - document_managers: - default: - mappings: - AcmeDemoBundle: ~ - metadata_cache_driver: - type: memcache - class: Doctrine\Common\Cache\MemcacheCache - host: localhost - port: 11211 - instance_class: Memcache - - .. code-block:: xml - - - - - - - - - - Doctrine\Common\Cache\MemcacheCache - localhost - 11211 - Memcache - - - - - - - - - -Configurando la asignación -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La definición explícita de todos los documentos asignados es la única configuración necesaria para *ODM* y hay varias opciones de configuración que puedes controlar. Existen las siguientes opciones de configuración para una asignación: - -- ``type`` Uno de ``annotations``, ``xml``, ``yml``, ``php`` o ``staticphp``. - Esta especifica cual tipo de metadatos usa el tipo de tu asignación. - -- ``dir`` Ruta a los archivos de entidad o asignación (dependiendo del controlador). Si esta ruta es relativa, se supone que es relativa a la raíz del paquete. Esto sólo funciona si el nombre de tu asignación es un nombre de paquete. Si deseas utilizar esta opción para especificar rutas absolutas debes prefijar la ruta con los parámetros del núcleo existentes en el *DIC* (por ejemplo ``%kernel.root_dir%``). - -- ``prefix`` Un prefijo de espacio de nombres común que comparten todos los documentos de esta asignación. Este prefijo no debe entrar en conflicto con otros prefijos definidos por otras asignaciones, de otra manera *Doctrine* no podrá encontrar algunos de tus documentos. Esta opción, por omisión, es el espacio de nombres del paquete + ``Document``, por ejemplo, para un paquete de la aplicación llamada ``AcmeHelloBundle``, el prefijo sería ``Acme\HelloBundle\Document``. - -- ``alias`` *Doctrine* ofrece una forma simple para rebautizar el espacio de nombres de los documentos, los nombres más cortos se utilizan en las consultas o para acceder al repositorio. - -* ``is_bundle`` Esta opción es un valor derivado de ``dir`` y por omisión se establece en ``true`` si ``dir`` es relativo provisto por un ``file_exists()`` verifica que devuelve ``false``. Este es ``false`` si al comprobar la existencia devuelve ``true``. En este caso has especificado una ruta absoluta y es más probable que los archivos de metadatos estén en un directorio fuera del paquete. - -Para evitar tener que configurar un montón de información para tus asignaciones, debes seguir los siguientes convenios: - -1. Pon todos tus documentos en un directorio ``Document/`` dentro de tu paquete. Por ejemplo ``Acme/HelloBundle/Document/``. - -2. Si estás usando asignación ``xml``, ``php`` o ``yml`` coloca todos tus archivos de configuración en el directorio ``Resources/config/doctrine/`` con el sufijo ``mongodb.xml``, ``mongodb.yml`` o ``mongodb.php`` respectivamente. - -3. Asume anotaciones si es un ``Document/`` pero no se encuentra el directorio ``Resources/config/doctrine/``. - -La siguiente configuración muestra un montón de ejemplos de asignación: - -.. code-block:: yaml - - doctrine_mongodb: - document_managers: - default: - mappings: - MyBundle1: ~ - MyBundle2: yml - MyBundle3: { type: annotation, dir: Documents/ } - MyBundle4: { type: xml, dir: Resources/config/doctrine/mapping } - MyBundle5: - type: yml - dir: my-bundle-mappings-dir - alias: BundleAlias - doctrine_extensions: - type: xml - dir: %kernel.root_dir%/../src/vendor/DoctrineExtensions/lib/DoctrineExtensions/Documents - prefix: DoctrineExtensions\Documents\ - alias: DExt - -Filtros -~~~~~~~ - -Fácilmente puedes añadir filtros a un gestor de documentos utilizando la siguiente sintaxis: - -.. code-block:: yaml - - doctrine_mongodb: - document_managers: - default: - filters: - filter-one: - class: Class\ExampleOne\Filter\ODM\ExampleFilter - enabled: true - filter-two: - class: Class\ExampleTwo\Filter\ODM\ExampleFilter - enabled: false - -Los filtros se usan para añadir condiciones al ``queryBuilder`` sin tener en cuenta dónde se generó la consulta. - -Múltiples conexiones -~~~~~~~~~~~~~~~~~~~~ - -Si necesitas múltiples conexiones y gestores de documentos puedes utilizar la siguiente sintaxis: - -.. configuration-block - - .. code-block:: yaml - - doctrine_mongodb: - default_database: hello_%kernel.environment% - default_connection: conn2 - default_document_manager: dm2 - metadata_cache_driver: apc - connections: - conn1: - server: mongodb://localhost:27017 - conn2: - server: mongodb://localhost:27017 - document_managers: - dm1: - connection: conn1 - metadata_cache_driver: xcache - mappings: - AcmeDemoBundle: ~ - dm2: - connection: conn2 - mappings: - AcmeHelloBundle: ~ - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - -Ahora puedes recuperar los servicios configurados conectando servicios:: - - $conn1 = $container->get('doctrine_mongodb.odm.conn1_connection'); - $conn2 = $container->get('doctrine_mongodb.odm.conn2_connection'); - -Y también puedes recuperar los gestores de servicios de documentos configurados que utilizan la conexión de servicios anterior:: - - $dm1 = $container->get('doctrine_mongodb.odm.dm1_document_manager'); - $dm2 = $container->get('doctrine_mongodb.odm.dm2_document_manager'); - -Conectando un grupo de servidores ``mongodb`` en 1 conexión -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Es posible conectarse a varios servidores ``mongodb`` en una conexión si utilizas un conjunto de réplicas haciendo una lista de todos los servidores dentro de la cadena de conexión como una lista separada por comas. - -.. configuration-block:: - - .. code-block:: yaml - - doctrine_mongodb: - # ... - connections: - default: - server: 'mongodb://mongodb-01:27017,mongodb-02:27017,mongodb-03:27017' - -Dónde ``mongodb-01``, ``mongodb-02`` y ``mongodb-03`` son los nombres de las máquinas anfitrionas. También puedes utilizar direcciones *IP*, si lo prefiere. - -Volviendo a intentar conexiones y sonsultas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*MongoDB* de *Doctrine* automáticamente es compatible con reintentos de conexión y consultas despues de encontrar una excepción, lo cual es útil cuando tratas con situaciones como conjunto de réplicas para la recuperación de fallos. Esto alivia mucha de la necesidad de capturar las excepciones del controlador *MongoDB* de *PHP* en la aplicación y volver a intentar esas operaciones manualmente. - -Puedes especificar el número de veces en que se reintentarán las conexiones y consultas a través de las opciones ``retry_connect`` y ``retry_query`` en la configuración del gestor de documentos. -Estas opciones de manerea predeterminada están en cero, lo cual significa que ninguna operación se volverá a intentar. - -Configuración predeterminada completa -------------------------------------- - -.. configuration-block:: - - .. code-block:: yaml - - doctrine_mongodb: - document_managers: - - # Prototype - id: - connection: ~ - database: ~ - logging: true - auto_mapping: false - retry_connect: 0 - retry_query: 0 - metadata_cache_driver: - type: ~ - class: ~ - host: ~ - port: ~ - instance_class: ~ - mappings: - - # Prototipo - name: [] - mapping: true - type: ~ - dir: ~ - prefix: ~ - alias: ~ - is_bundle: ~ - connections: - - # Prototype - id: - server: ~ - options: - connect: ~ - persist: ~ - timeout: ~ - replicaSet: ~ - username: ~ - password: ~ - proxy_namespace: MongoDBODMProxies - proxy_dir: %kernel.cache_dir%/doctrine/odm/mongodb/Proxies - auto_generate_proxy_classes: false - hydrator_namespace: Hydrators - hydrator_dir: %kernel.cache_dir%/doctrine/odm/mongodb/Hydrators - auto_generate_hydrator_classes: false - default_document_manager: ~ - default_connection: ~ - default_database: default diff --git a/_sources/bundles/DoctrineMongoDBBundle/form.txt b/_sources/bundles/DoctrineMongoDBBundle/form.txt deleted file mode 100644 index 63bb70a..0000000 --- a/_sources/bundles/DoctrineMongoDBBundle/form.txt +++ /dev/null @@ -1,248 +0,0 @@ -Cómo implementar un sencillo formulario de inscripción con *MongoDB* -==================================================================== - -Algunos formularios tienen campos adicionales cuyos valores no es necesario almacenar en la base de datos. En este ejemplo, crearás un formulario de registro con algunos campos adicionales, además, incluirás en el formulario un campo «términos aceptados» (como casilla de verificación), el cual en realidad no se almacena en la información de la cuenta. Vamos a utilizar *MongoDB* para almacenar los datos. - -El modelo de Usuario simple ---------------------------- - -Por lo tanto, en esta guía comenzarás con el modelo para un documento ``Usuario``:: - - // src/Acme/AccountBundle/Document/User.php - namespace Acme\AccountBundle\Document; - - use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB; - use Symfony\Component\Validator\Constraints as Assert; - use Doctrine\Bundle\MongoDBBundle\Validator\Constraints\Unique as MongoDBUnique; - - /** - * @MongoDB\Document(collection="users") - * @MongoDBUnique(fields="email") - */ - class User - { - /** - * @MongoDB\Id - */ - protected $id; - - /** - * @MongoDB\Field(type="string") - * @Assert\NotBlank() - * @Assert\Email() - */ - protected $email; - - /** - * @MongoDB\Field(type="string") - * @Assert\NotBlank() - */ - protected $password; - - public function getId() - { - return $this->id; - } - - public function getEmail() - { - return $this->email; - } - - public function setEmail($email) - { - $this->email = $email; - } - - public function getPassword() - { - return $this->password; - } - - // encriptado estúpidamente simple (¡por favor no copies esto!) - public function setPassword($password) - { - $this->password = sha1($password); - } - } - -Este documento ``Usuario`` contiene tres campos y dos de ellos (correo y contraseña) se deben mostrar en el formulario. La propiedad correo debe ser única en la base de datos, por lo tanto añadimos esta validación en lo alto de la clase. - -.. note:: - - Si deseas integrar este Usuario en el sistema de seguridad, es necesario implementar la :ref:`Interfaz de usuario ` del componente de Seguridad. - -Creando un formulario para el modelo ------------------------------------- - -A continuación, crea el formulario para el modelo ``Usuario``:: - - // src/Acme/AccountBundle/Form/Type/UserType.php - namespace Acme\AccountBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\Extension\Core\Type\RepeatedType; - use Symfony\Component\Form\FormBuilder; - - class UserType extends AbstractType - { - public function buildForm(FormBuilder $builder, array $options) - { - $builder->add('email', 'email'); - $builder->add('password', 'repeated', array( - 'first_name' => 'password', - 'second_name' => 'confirm', - 'type' => 'password' - )); - } - - public function getDefaultOptions(array $options) - { - return array('data_class' => 'Acme\AccountBundle\Document\User'); - } - - public function getName() - { - return 'user'; - } - } - -Acabamos de añadir dos campos: correo y contraseña (repetido para confirmar la contraseña introducida). La opción ``data_class`` le indica al formulario el nombre de la clase de los datos (es decir, el documento ``Usuario``). - -.. tip:: - - Para explorar más cosas sobre el componente Formulario, lee esta :doc:`documentación `. - -Incorporando el formulario Usuario en un formulario de inscripción ------------------------------------------------------------------- - -El formulario que vamos a usar para la página de registro no es el mismo que el formulario utilizado para simplemente modificar al ``Usuario`` (es decir, ``UserType``). El formulario de registro contiene más campos como «acepto las condiciones», cuyo valor no se almacenará en la base de datos. - -En otras palabras, creas un segundo formulario de inscripción, el cual incorpora el formulario ``Usuario`` y añades el campo extra necesario. Empecemos creando una clase simple que representa la «inscripción»:: - - // src/Acme/AccountBundle/Form/Model/Registration.php - namespace Acme\AccountBundle\Form\Model; - - use Symfony\Component\Validator\Constraints as Assert; - - use Acme\AccountBundle\Document\User; - - class Registration - { - /** - * @Assert\Type(type="Acme\AccountBundle\Document\User") - */ - protected $user; - - /** - * @Assert\NotBlank() - * @Assert\True() - */ - protected $termsAccepted; - - public function setUser(User $user) - { - $this->usuario = $usuario; - } - - public function getUser() - { - return $this->user; - } - - public function getTermsAccepted() - { - return $this->termsAccepted; - } - - public function setTermsAccepted($termsAccepted) - { - $this->termsAccepted = (boolean)$termsAccepted; - } - } - -A continuación, crea el formulario para el modelo ``Registro``:: - - // src/Acme/AccountBundle/Form/Type/RegistrationType.php - namespace Acme\AccountBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\Extension\Core\Type\RepeatedType; - use Symfony\Component\Form\FormBuilder; - - class RegistrationType extends AbstractType - { - public function buildForm(FormBuilder $builder, array $options) - { - $builder->add('user', new UserType()); - $builder->add('terms', 'checkbox', array('property_path' => 'termsAccepted')); - } - - public function getName() - { - return 'registration'; - } - } - -No necesitas utilizar métodos especiales para integrar el ``UserType`` en el formulario. -Un formulario es un campo, también --- por lo tanto lo puedes añadir como cualquier otro campo, con la expectativa de que la propiedad ``Usuario`` correspondiente mantendrá una instancia de la clase ``UserType``. - -Manejando el envío del formulario ---------------------------------- - -A continuación, necesitas un controlador para manejar el formulario. Comienza creando un controlador simple para mostrar el formulario de inscripción:: - - // src/Acme/AccountBundle/Controller/AccountController.php - namespace Acme\AccountBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Response; - - use Acme\AccountBundle\Form\Type\RegistrationType; - use Acme\AccountBundle\Form\Model\Registration; - - class AccountController extends Controller - { - public function registerAction() - { - $form = $this->createForm(new RegistrationType(), new Registration()); - - return $this->render('AcmeAccountBundle:Account:register.html.twig', array('form' => $form->createView())); - } - } - -y su plantilla: - -.. code-block:: html+jinja - - {# src/Acme/AccountBundle/Resources/views/Account/register.html.twig #} - -
- {{ form_widget(form) }} - - -
- -Finalmente, crea el controlador que maneja el envío del formulario. Esto realiza la validación y guarda los datos en *MongoDB*:: - - public function createAction() - { - $dm = $this->get('doctrine_mongodb')->getManager(); - - $form = $this->createForm(new RegistrationType(), new Registration()); - - $form->bindRequest($this->getRequest()); - - if ($form->isValid()) { - $registration = $form->getData(); - - $dm->persist($registration->getUser()); - $dm->flush(); - - return $this->redirect(...); - } - - return $this->render('AcmeAccountBundle:Account:register.html.twig', array('form' => $form->createView())); - } - -¡Eso es todo! Tu formulario ahora valida, y te permite guardar el objeto ``Usuario`` a *MongoDB*. diff --git a/_sources/bundles/DoctrineMongoDBBundle/index.txt b/_sources/bundles/DoctrineMongoDBBundle/index.txt deleted file mode 100644 index 520defe..0000000 --- a/_sources/bundles/DoctrineMongoDBBundle/index.txt +++ /dev/null @@ -1,631 +0,0 @@ -``DoctrineMongoDBBundle`` -========================= - -.. toctree:: - :hidden: - - config - form - -El asignador de objeto a documento `MongoDB`_ (*ODM* por Object Document Mapper) es muy similar al *ORM* de *Doctrine2* en su filosofía y funcionamiento. En otras palabras, similar al :doc:`ORM de Doctrine2 `, con el *ODM* de *Doctrine*, sólo tratas con objetos *PHP* simples, los cuales luego se persisten de forma transparente hacia y desde *MongoDB*. - -.. tip:: - - Puedes leer más acerca del *ODM* de *Doctrine MongoDB* en la `documentación`_ del proyecto. - -Está disponible el paquete que integra el *ODM MongoDB de Doctrine* en *Symfony*, por lo tanto es fácil configurarlo y usarlo. - -.. note:: - - Este capítulo lo debes de sentir muy parecido al capítulo :doc:`ORM de Doctrine2 `, que habla de cómo puedes utilizar el *ORM* de *Doctrine* para guardar los datos en bases de datos relacionales (por ejemplo, *MySQL*). Esto es a propósito --- si persistes en una base de datos relacional por medio del *ORM* o a través del *ODM MongoDB*, las filosofías son muy parecidas. - -Instalando ----------- - -Para utilizar el *ODM MongoDB*, necesitarás dos bibliotecas proporcionadas por *Doctrine* y un paquete que las integra en *Symfony*. - -Instalando el paquete con ``Composer`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para instalar ``DoctrineMongoDBBundle`` con ``Composer`` sólo tienes que añadir lo siguiente a tu archivo :file:`composer.json`:: - - { - "require": { - "doctrine/mongodb-odm-bundle": "3.0.*" - }, - "minimum-stability": "dev" - } - -La definición de ``minimum-stability`` es necesaria hasta que una versión no beta del paquete esté disponible. Dependiendo de las necesidades de tu proyecto, puedes utilizar valores distintos a ``dev``, lo cual se explica en la `documentación del esquema`_ *Composer*. - -Ahora puedes instalar las nuevas dependencias ejecutando la orden ``update`` de *Composer* desde el directorio donde se encuentra el archivo ``composer.json``: - -.. code-block:: bash - - php composer.phar update - -Ahora, ``Composer`` automáticamente descargará todos los archivos necesarios y los instalará por ti. - -Registrando las anotaciones y el paquete -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Luego, registra la biblioteca de anotaciones añadiendo lo siguiente al cargador automático (bajo la línea ``AnnotationRegistry::registerLoader`` existente):: - - // app/autoload.php - use Doctrine\ODM\MongoDB\Mapping\Driver\AnnotationDriver; - AnnotationDriver::registerAnnotationClasses(); - -Todo lo que resta por hacer es actualizar tu archivo :file:`AppKernel.php`, y registrar el nuevo paquete:: - - // app/AppKernel.php - public function registerBundles() - { - $bundles = array( - // ... - new Doctrine\Bundle\MongoDBBundle\DoctrineMongoDBBundle(), - ); - - // ... - } - -¡Enhorabuena! Estás listo para empezar a trabajar. - -Configurando ------------- - -Para empezar, necesitarás una estructura básica que configure el gestor de documentos. La forma más fácil es habilitar el ``auto_mapping``, el cual activará al *ODM MongoDB* a través de tu aplicación: - -.. code-block:: yaml - - # app/config/config.yml - doctrine_mongodb: - connections: - default: - server: mongodb://localhost:27017 - options: {} - default_database: test_database - document_managers: - default: - auto_mapping: true - -.. note:: - - Por supuesto, también te tienes que asegurar de que el servidor *MongoDB* se ejecute en segundo plano. Para más información, consulta la `Guía de inicio rápido`_ de *MongoDB*. - -Un sencillo ejemplo: Un producto --------------------------------- - -La mejor manera de entender el *ODM* de *Doctrine MongoDB* es verlo en acción. -En esta sección, recorreremos cada paso necesario para empezar a persistir documentos hacia y desde *MongoDB*. - -.. sidebar:: El código del ejemplo - - Si quieres seguir el ejemplo de este capítulo, crea el paquete ``AcmeStoreBundle`` ejecutando la orden: - - .. code-block:: bash - - php app/console generate:bundle --namespace=Acme/StoreBundle - -Creando una clase Documento -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Supongamos que estás construyendo una aplicación donde necesitas mostrar tus productos. -Sin siquiera pensar en *Doctrine* o *MongoDB*, ya sabes que necesitas un objeto ``Producto`` para representar los productos. Crea esta clase en el directorio ``Document`` de tu ``AcmeStoreBundle``:: - - // src/Acme/StoreBundle/Document/Product.php - namespace Acme\StoreBundle\Document; - - class Product - { - protected $name; - - protected $price; - } - -La clase --- a menudo llamada «documento», es decir, *una clase básica que contiene los datos* --- es simple y ayuda a cumplir con el requisito del negocio de que tu aplicación necesita productos. Esta clase, todavía no se puede persistir a *Doctrine MongoDB* --- es sólo una clase *PHP* simple. - -Agregando información de asignación -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Doctrine* te permite trabajar con *MongoDB* de una manera mucho más interesante que solo recuperar datos de un lado a otro como una matriz. En cambio, *Doctrine* te permite persistir *objetos* completos a *MongoDB* y recuperar objetos enteros desde *MongoDB*. Esto funciona asignando una clase *PHP* y sus propiedades a las entradas de una colección *MongoDB*. - -Para que *Doctrine* sea capaz de hacer esto, sólo tienes que crear «metadatos», o la configuración que le dice a *Doctrine* exactamente cómo se deben *asignar* a *MongoDB* la clase ``Producto`` y sus propiedades. Estos metadatos se pueden especificar en una variedad de formatos diferentes, incluyendo *YAML*, *XML* o directamente dentro de la clase ``Producto`` a través de anotaciones: - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Acme/StoreBundle/Document/Product.php - namespace Acme\StoreBundle\Document; - - use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB; - - /** - * @MongoDB\Document - */ - class Product - { - /** - * @MongoDB\Id - */ - protected $id; - - /** - * @MongoDB\String - */ - protected $name; - - /** - * @MongoDB\Float - */ - protected $price; - } - - .. code-block:: yaml - - # src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.yml - Acme\StoreBundle\Document\Product: - fields: - id: - id: true - name: - type: string - price: - type: float - - .. code-block:: xml - - - - - - - - - - - -*Doctrine* te permite elegir entre una amplia variedad de tipos de campo diferentes, cada uno con sus propias opciones. Para más información sobre los tipos de campo disponibles, consulta la sección :ref:`cb-mongodb-field-types`. - -.. seealso:: - - También puedes consultar la `Documentación de asignación básica`_ de *Doctrine* para todos los detalles sobre la información de asignación. Si utilizas anotaciones, tendrás que prefijar todas tus anotaciones con ``MongoDB\`` (por ejemplo, ``MongoDB\Cadena``), lo cual no se muestra en la documentación de *Doctrine*. También tendrás que incluir la declaración ``use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB;``, la cual *importa* el prefijo ``MongoDB`` para las anotaciones. - -Generando captadores y definidores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A pesar de que *Doctrine* ya sabe cómo persistir un objeto ``Producto`` a *MongoDB*, la clase en sí en realidad todavía no es útil. Puesto que ``Producto`` es sólo una clase *PHP* regular, es necesario crear métodos captadores y definidores (por ejemplo, ``getNombre()``, ``setNombre()``) para poder acceder a sus propiedades (ya que las propiedades son ``protegidas``). Afortunadamente, *Doctrine* puede hacer esto por ti con la siguiente orden: - -.. code-block:: bash - - php app/console doctrine:mongodb:generate:documents AcmeStoreBundle - -Esta orden se asegura de que se generen todos los captadores y definidores para la clase ``Producto``. Esta es una orden segura --- la puedes ejecutar una y otra vez: sólo genera captadores y definidores que no existen (es decir, no sustituye métodos existentes). - -.. note:: - - A *Doctrine* no le importa si tus propiedades son ``protegidas`` o ``privadas``, o si una propiedad tiene o no una función captadora o definidora. - Aquí, los captadores y definidores se generan sólo porque los necesitarás para interactuar con tu objeto *PHP*. - -Persistiendo objetos a *MongoDB* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora que tienes asignado un documento ``Producto`` completo, con métodos captadores y definidores, estás listo para persistir los datos a *MongoDB*. Desde el interior de un controlador, esto es bastante fácil. Agrega el siguiente método al ``DefaultController`` del paquete: - -.. code-block:: php - :linenos: - - // src/Acme/StoreBundle/Controller/DefaultController.php - use Acme\StoreBundle\Document\Product; - use Symfony\Component\HttpFoundation\Response; - // ... - - public function createAction() - { - $product = new Product(); - $product->setName('A Foo Bar'); - $product->setPrice('19.99'); - - $dm = $this->get('doctrine_mongodb')->getManager(); - $dm->persist($product); - $dm->flush(); - - return new Response('Created product id '.$product->getId()); - } - -.. note:: - - Si estás siguiendo este ejemplo, tendrás que crear una ruta que apunte a esta acción para verla trabajar. - -Vamos a recorrer este ejemplo: - -* **Líneas 8-10** En esta sección, creas una instancia y trabajas con el objeto ``$product`` como cualquier otro objeto *PHP* normal; - -* **Línea 12** Esta línea recupera el objeto *gestor de documentos*, el cual es responsable de manejar el proceso de persistir y recuperar objetos hacia y desde *MongoDB*; - -* **Línea 13** El método ``persist()`` dice a *Doctrine* que «procese» el objeto ``$product``. Esto en realidad no resulta en una consulta que se deba hacer a *MongoDB* (todavía). - -* **Línea 14** Cuando se llama al método ``flush()``, *Doctrine* mira todos los objetos que está gestionando para ver si es necesario persistirlos a *MongoDB*. En este ejemplo, el objeto ``$product`` aún no se ha persistido, por lo que el gestor de documentos hace una consulta a *MongoDB*, la cual añade una nueva entrada. - -.. note:: - - De hecho, ya que *Doctrine* está consciente de todos los objetos gestionados, cuando llamas al método ``flush()``, se calcula un conjunto de cambios y ejecuta la operación más eficiente posible. - -Al crear o actualizar objetos, el flujo de trabajo siempre es el mismo. En la siguiente sección, verás cómo *Doctrine* es lo suficientemente inteligente como para actualizar las entradas, si ya existen en *MongoDB*. - -.. tip:: - - *Doctrine* proporciona una biblioteca que te permite cargar en tu proyecto mediante programación los datos de prueba (es decir, «datos accesorios»). Para más información, consulta :doc:`/bundles/DoctrineFixturesBundle/index`. - -Recuperando objetos desde *MongoDB* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Recuperar un objeto de *MongoDB* incluso es más fácil. Por ejemplo, supongamos que has configurado una ruta para mostrar un ``Producto`` específico en función del valor de su ``id``:: - - public function showAction($id) - { - $product = $this->get('doctrine_mongodb') - ->getRepository('AcmeStoreBundle:Product') - ->find($id); - - if (!$product) { - throw $this->createNotFoundException('No product found for id '.$id); - } - - // haz algo, como pasar el objeto $product a una plantilla - } - -Al consultar por un determinado tipo de objeto, siempre utilizas lo que se conoce como «repositorio». Puedes pensar en un repositorio como una clase *PHP*, cuyo único trabajo consiste en ayudarte a buscar los objetos de una determinada clase. Puedes acceder al objeto repositorio de una clase documento vía:: - - $repository = $this->get('doctrine_mongodb') - ->getManager() - ->getRepository('AcmeStoreBundle:Product'); - -.. note:: - - La cadena ``AcmeStoreBundle:Product`` es un método abreviado que puedes utilizar en cualquier lugar de *Doctrine* en lugar del nombre de clase completo de la entidad (es decir, ``Acme\StoreBundle\Entity\Product``). - Mientras tu documento viva en el espacio de nombres ``Document`` de tu paquete, esto va a funcionar. - -Una vez que tengas tu repositorio, tienes acceso a todo tipo de útiles métodos:: - - // consulta por la clave principal (generalmente "id") - $product = $repository->find($id); - - // nombres de método dinámicos para búsquedas basadas en - // el valor de una columna - $product = $repository->findOneById($id); - $product = $repository->findOneByName('foo'); - - // recupera TODOS los productos - $products = $repository->findAll(); - - // busca un grupo de productos basándose en el - // valor de una columna arbitraria - $products = $repository->findByPrice(19.99); - -.. note:: - - Por supuesto, también puedes realizar consultas complejas, acerca de las cuales aprenderás más en la sección :ref:`book-doctrine-queries`. - -También puedes tomar ventaja de los útiles métodos ``findBy`` y ``findOneBy`` para recuperar objetos fácilmente basándote en varias condiciones:: - - // consulta por un producto que coincide en nombre y precio - $product = $repository->findOneBy(array('name' => 'foo', - 'price' => 19.99)); - - // consulta por todos los productos que coinciden - // con el nombre, y los ordena por precio - $product = $repository->findBy( - array('name' => 'foo'), - array('price', 'ASC') - ); - -Actualizando un objeto -~~~~~~~~~~~~~~~~~~~~~~ - -Una vez que hayas extraído un objeto de *Doctrine*, actualizarlo es relativamente fácil. Supongamos que tienes una ruta que asigna un identificador de producto a una acción de actualización de un controlador:: - - public function updateAction($id) - { - $dm = $this->get('doctrine_mongodb')->getManager(); - $product = $dm->getRepository('AcmeStoreBundle:Product')->find($id); - - if (!$product) { - throw $this->createNotFoundException('No product found for id '.$id); - } - - $product->setName('New product name!'); - $dm->flush(); - - return $this->redirect($this->generateUrl('homepage')); - } - -La actualización de un objeto únicamente consiste de tres pasos: - -1. Recuperar el objeto desde *Doctrine*; -2. Modificar el objeto; -3. Llamar a ``flush()`` en el gestor del documento; - -Ten en cuenta que ``$dm->persist($product)`` no es necesario. Recuerda que este método simplemente dice a *Doctrine* que procese o «vea» el objeto ``$product``. -En este caso, ya que recuperaste el objeto ``$product`` desde *Doctrine*, este ya está gestionado. - -Eliminando un objeto -~~~~~~~~~~~~~~~~~~~~ - -La eliminación de un objeto es muy similar, pero requiere una llamada al método ``remove()`` del gestor de documentos:: - - $dm->remove($product); - $dm->flush(); - -Como es de esperar, el método ``remove()`` notifica a *Doctrine* que deseas eliminar el documento propuesto de *MongoDB*. La operación real de eliminar sin embargo, no se ejecuta efectivamente hasta que invocas al método ``flush()``. - -Consultando por objetos ------------------------ - -Como vimos anteriormente, la clase ``repositorio`` integrada te permite consultar por uno o varios objetos basándote en una serie de diferentes parámetros. Cuando esto es suficiente, esta es la forma más sencilla de consultar documentos. Por supuesto, también puedes crear consultas más complejas. - -Usando el generador de consultas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El *ODM* de *Doctrine* viene con un objeto «Generador» de consultas, el cual te permite construir una consulta para exactamente los documentos que deseas devolver. Si usas un *IDE*, también puedes tomar ventaja del autocompletado a medida que escribes los nombres de métodos. -Desde el interior de un controlador:: - - $products = $this->get('doctrine_mongodb') - ->getManager() - ->createQueryBuilder('AcmeStoreBundle:Product') - ->field('name')->equals('foo') - ->limit(10) - ->sort('price', 'ASC') - ->getQuery() - ->execute() - -En este caso, devuelve 10 productos con el nombre «foo», ordenados de menor a mayor precio. - -El objeto ``QueryBuilder`` contiene todos los métodos necesarios para construir tu consulta. Para más información sobre el generador de consultas de *Doctrine*, consulta la documentación del `Generador de consultas`_ de *Doctrine*. Para una lista de las condiciones disponibles que puedes colocar en la consulta, ve la documentación específica a los `Operadores condicionales`_. - -Repositorio de clases personalizado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En la sección anterior, comenzaste a construir y utilizar consultas más complejas desde el interior de un controlador. A fin de aislar, probar y reutilizar esas consultas, es buena idea crear una clase repositorio personalizada para tu documento y allí agregar métodos con la lógica de la consulta. - -Para ello, agrega el nombre de la clase del repositorio a la definición de asignación. - -.. configuration-block:: - - .. code-block:: php-annotations - - // src/Acme/StoreBundle/Document/Product.php - namespace Acme\StoreBundle\Document; - - use Doctrine\ODM\MongoDB\Mapping\Annotations as MongoDB; - - /** - * @MongoDB\Document(repositoryClass="Acme\StoreBundle\Repository\ProductRepository") - */ - class Product - { - //... - } - - .. code-block:: yaml - - # src/Acme/StoreBundle/Resources/config/doctrine/Product.mongodb.yml - Acme\StoreBundle\Document\Product: - repositoryClass: Acme\StoreBundle\Repository\ProductRepository - # ... - - .. code-block:: xml - - - - - - - - - - - -*Doctrine* puede generar la clase repositorio para ti ejecutando: - -.. code-block:: bash - - php app/console doctrine:mongodb:generate:repositories AcmeStoreBundle - -A continuación, agrega un nuevo método --- ``findAllOrderedByName()`` --- a la clase repositorio recién generada. Este método deberá consultar por todos los documentos ``Producto``, ordenados alfabéticamente. - -.. code-block:: php - - // src/Acme/StoreBundle/Repository/ProductRepository.php - namespace Acme\StoreBundle\Repository; - - use Doctrine\ODM\MongoDB\DocumentRepository; - - class ProductRepository extends DocumentRepository - { - public function findAllOrderedByName() - { - return $this->createQueryBuilder() - ->sort('name', 'ASC') - ->getQuery() - ->execute(); - } - } - -Puedes utilizar este nuevo método al igual que los métodos de búsqueda predeterminados del repositorio:: - - $products = $this->get('doctrine_mongodb') - ->getManager() - ->getRepository('AcmeStoreBundle:Product') - ->findAllOrderedByName(); - - -.. note:: - - Al utilizar una clase repositorio personalizada, todavía tienes acceso a los métodos de búsqueda predeterminados como ``find()`` y ``findAll()``. - -Extensiones *Doctrine*: ``Timestampable``, ``Sluggable``, etc. --------------------------------------------------------------- - -*Doctrine* es bastante flexible, y dispone de una serie de extensiones de terceros que te permiten realizar fácilmente tareas repetitivas y comunes en tus entidades. -Estas incluyen cosas tales como ``Sluggable``, ``Timestampable``, ``registrable``, ``traducible`` y ``Tree``. - -Para más información sobre cómo encontrar y utilizar estas extensiones, ve el artículo sobre el uso de :doc:`extensiones comunes de Doctrine `. - -.. _cb-mongodb-field-types: - -Referencia de tipos de campo *Doctrine* ---------------------------------------- - -*Doctrine* dispone de una gran cantidad de tipos de campo. Cada uno de estos asigna un tipo de dato *PHP* a un determinado `tipo de MongoDB`_. Los siguientes son sólo *algunos* de los tipos admitidos por *Doctrine*: - -* ``string`` -* ``int`` -* ``float`` -* ``date`` -* ``timestamp`` -* ``boolean`` -* ``file`` - -Para más información, consulta la sección `Asignando tipos`_ en la documentación de *Doctrine*. - -.. index:: - single: Doctrine; Ordenes de consola ODM - single: CLI; ODM de Doctrine - -Ordenes de consola ------------------- - -La integración *ODM* de *Doctrine2* ofrece varias ordenes de consola en el espacio de nombres ``doctrine:mongodb``. Para ver la lista de ordenes puedes ejecutar la consola sin ningún tipo de argumento: - -.. code-block:: bash - - php app/console - -Mostrará una lista con las ordenes disponibles, muchas de las cuales comienzan con el prefijo ``doctrine:mongodb``. Puedes encontrar más información sobre cualquiera de estas ordenes (o cualquier orden de *Symfony*) ejecutando la orden ``help``. -Por ejemplo, para obtener detalles acerca de la tarea ``doctrine:mongodb:query``, ejecuta: - -.. code-block:: bash - - php app/console help doctrine:mongodb:query - -.. note:: - - Para poder cargar accesorios en *MongoDB*, necesitas tener instalado el paquete ``DoctrineFixturesBundle``. Para ver cómo hacerlo, lee el artículo «:doc:`/bundles/DoctrineFixturesBundle/index`» de la documentación. - -.. index:: - single: Configuración; ODM MongoDB de Doctrine - single: Doctrine; Configurando el ODM MongoDB - -Configurando ------------- - -Para información más detallada sobre las opciones de configuración disponibles cuando utilizas el *ODM* de *Doctrine*, consulta la sección :doc:`Referencia MongoDB `. - -Registrando escuchas y suscriptores de eventos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -*Doctrine* te permite registrar escuchas y suscriptores que recibirán una notificación cuando se produzcan diferentes eventos al interior del *ODM Doctrine*. Para más información, consulta la sección `Documentación de eventos`_ de *Doctrine*. - -.. tip:: - - Además de los eventos ``ODM``, también puedes escuchar los eventos de bajo nivel de `` MongoDB``, que encontrarás definidos en la clase :class:`Doctrine\MongoDB\Events`. - -.. note:: - - Cada conexión de *Doctrine* tiene su propio gestor de eventos, el cual se comparte con los gestores de documentos vinculados a esa conexión. Los escuchas y suscriptores pueden estar registrados con todos gestores de eventos o sólo uno (usando el nombre de la conexión). - -En *Symfony*, puedes registrar un escucha o suscriptor creando un :term:`servicio` y, a continuación :ref:`marcarlo ` con una etiqueta específica. - -* **escucha de evento**: Usa la etiqueta ``doctrine_mongodb.odm.event_listener`` para registrar un escucha. El atributo ``event`` es necesario y debe indicar el evento a escuchar. De forma predeterminada, los escuchas serán registrados con los gestores de eventos para todas las conexiones. Para restringir un escucha a una única conexión, especifica su nombre en la etiqueta del atributo ``connection``. - - El atributo ``priority``, el cual de manera predefinida es ``0`` si se omite, se puede utilizar para controlar el orden en que se registran los escuchas. Al igual que el :ref:`despachador de eventos ` de *Symfony2*, un mayor número dará lugar a que el escucha se ejecute primero y los escuchas con la misma prioridad se ejecutan en el orden en que fueron registrados en el gestor de eventos. - - Por último, el atributo ``lazy``, que de manera predeterminada es `false`` si se omite, se puede utilizar para solicitar que gestor del evento cargue al escucha de manera diferida cuando se distribuya el evento. - - .. configuration-block:: - - .. code-block:: yaml - - services: - my_doctrine_listener: - class: Acme\HelloBundle\Listener\MyDoctrineListener - # ... - tags: - - { name: doctrine_mongodb.odm.event_listener, event: postPersist } - - .. code-block:: xml - - - - - . - - .. code-block:: php - - $definition = new Definition('Acme\HelloBundle\Listener\MyDoctrineListener'); - // ... - $definition->addTag('doctrine_mongodb.odm.event_listener', array( - 'event' => 'postPersist', - )); - $container->setDefinition('my_doctrine_listener', $definition); - -* **suscriptor de evento**: Usa la etiqueta ``doctrine_mongodb.odm.event_subscriber`` para registrar un suscriptor. Los suscriptores son responsables de implementar al ``Doctrine\Common\EventSubscriber`` y suplir un método que devuelva los eventos que escuchará. Por esta razón, esta etiqueta no tiene el atributo ``event``; - No obstante, dispone de los atributos ``connection``, ``priority`` y ``lazy``. - -.. note:: - - A diferencia de los escuchas de eventos de *Symfony2*, el gestor de eventos de *Doctrine* espera que cada escucha y suscriptor tenga un nombre de método correspondiente al/los evento(s) observado(s). Por esta razón, las etiquetas antes mencionadas no tienen atributo ``method``. - -Integrando el ``SecurityBundle`` --------------------------------- - -Hay disponible un proveedor de usuarios para tus proyectos *MongoDB*, trabaja exactamente igual que el proveedor de entidades descrito en el :doc:`recetario ` - -.. configuration-block:: - - .. code-block:: yaml - - security: - providers: - my_mongo_provider: - mongodb: {class: Acme\DemoBundle\Document\User, property: username} - - .. code-block:: xml - - - - - - - - -Resumen -------- - -Con *Doctrine*, te puedes enfocar en los objetos y la forma en que son útiles en tu aplicación y en segundo lugar preocuparte de su persistencia a través de *MongoDB*. Esto se debe a que *Doctrine* te permite utilizar cualquier objeto *PHP* para almacenar los datos y confía en la información de asignación de metadatos para asignar los datos de un objeto a una colección *MongoDB*. - -Y aunque *Doctrine* gira en torno a un concepto simple, es increíblemente potente, permitiéndote crear consultas complejas y suscribirte a los eventos que te permiten realizar diferentes acciones conforme los objetos recorren su ciclo de vida en la persistencia. - -Aprende más en el recetario ---------------------------- - -* :doc:`/bundles/DoctrineMongoDBBundle/form` - -.. _`MongoDB`: http://www.mongodb.org/ -.. _`documentación`: http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/ -.. _`documentación del esquema`: http://getcomposer.org/doc/04-schema.md#minimum-stability -.. _`Guía de inicio rápido`: http://www.mongodb.org/display/DOCS/Quickstart -.. _`Documentación de asignación básica`: http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/reference/basic-mapping.html -.. _`tipo de MongoDB`: http://us.php.net/manual/en/mongo.types.php -.. _`Asignando tipos`: http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/basic-mapping.html#doctrine-mapping-types -.. _`Generador de consultas`: http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/reference/query-builder-api.html -.. _`Operadores condicionales`: http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/reference/query-builder-api.html#conditional-operators -.. _`Documentación de eventos`: http://www.doctrine-project.org/docs/mongodb_odm/1.0/en/reference/events.html diff --git a/_sources/bundles/JMSAopBundle/index.txt b/_sources/bundles/JMSAopBundle/index.txt deleted file mode 100644 index 3aa7010..0000000 --- a/_sources/bundles/JMSAopBundle/index.txt +++ /dev/null @@ -1,250 +0,0 @@ -**************** -``JMSAopBundle`` -**************** - -Este paquete añade capacidades `AOP `_ (``Aspect-oriented programming`` o Programación orientada a aspectos) a *Symfony2*. - -Si todavía no has oído hablar de *AOP*, básicamente te permite separar un asunto omnipresente (por ejemplo, la comprobación de seguridad) en una clase específica, y no tener que repetir ese código en todos los lugares donde se necesite. - -En otras palabras, te permite ejecutar código personalizado antes y después de invocar ciertos métodos en tu capa de servicios o controladores. También puedes optar por omitir la invocación del método original, o simplemente lanzar excepciones. - -Instalando ----------- - -Consigue una copia del código: - -.. code-block:: bash - - - git submodule add https://github.com/schmittjoh/JMSAopBundle.git src/JMS/AopBundle - -Finalmente, registra el paquete en tu núcleo: - -.. code-block:: php - - // en AppKernel::registerBundles() - $bundles = array( - // ... - new JMS\AopBundle\JMSAopBundle(), - // ... - ); - -Este paquete además necesita la biblioteca ``CG`` para generar código: - -.. code-block:: bash - - git submodule add https://github.com/schmittjoh/cg-library.git vendor/cg-library - -Asegúrate de registrar los espacios de nombres en tu cargador automático:: - - // app/autoload.php - $loader->registerNamespaces(array( - // ... - 'JMS' => __DIR__.'/../vendor/bundles', - 'CG' => __DIR__.'/../vendor/cg-library/src', - // ... - )); - - -Configurando ------------- - -.. code-block:: yaml - - jms_aop: - cache_dir: %kernel.cache_dir%/jms_aop - - -Usando ------- - -A fin de ejecutar código personalizado, necesitas dos clases. En primer lugar, necesitas un así llamado :dfn:`punto de corte` (``pointcut``). El propósito de esta clase es tomar la decisión de si cierto interceptor debe capturar una llamada a un método. Esta decisión se tiene que hacer estáticamente sólo en base de la firma del método en sí. - -La segunda clase es el interceptor. Esta clase se llama en lugar del método original. Esta contiene el código personalizado que quieres ejecutar. En este punto, tienes acceso al objeto en el cual es invocado el método, y todos los argumentos suministrados a ese método. - -Ejemplos --------- - -1. Registro cronológico -~~~~~~~~~~~~~~~~~~~~~~~ - -En este ejemplo, implementaremos el registro cronológico de todos los métodos que contienen ``«delete»`` - -Punto de corte -^^^^^^^^^^^^^^ - -:: - - name, 'delete'); - } - } - -:: - - # services.yml - services: - my_logging_pointcut: - class: LoggingPointcut - tags: - - { name: jms_aop.pointcut, interceptor: logging_interceptor } - - -Interceptor de registro -^^^^^^^^^^^^^^^^^^^^^^^ - -:: - - context = $context; - $this->logger = $logger; - } - - public function intercept(MethodInvocation $invocation) - { - $user = $this->context->getToken()->getUsername(); - $this->logger->info(sprintf('User "%s" invoked method "%s".', $user, $invocation->reflection->name)); - - // se asegura de proceder con la invocación, de lo contrario - // el método original nunca se llamará - return $invocation->proceed(); - } - } - -:: - - # services.yml - services: - logging_interceptor: - class: LoggingInterceptor - arguments: [@security.context, @logger] - - -2. Gestionando transacciones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En este ejemplo, añadimos una anotación ``@Transactional``, y automáticamente ajustamos todos los métodos donde se declara esta anotación en una transacción. - -Punto de corte -^^^^^^^^^^^^^^ - -:: - - use Doctrine\Common\Annotations\Reader; - use JMS\AopBundle\Aop\PointcutInterface; - use JMS\DiExtraBundle\Annotation as DI; - - /** - * @DI\Service - * @DI\Tag("jms_aop.pointcut", attributes = {"interceptor" = "aop.transactional_interceptor"}) - * - * @author Johannes M. Schmitt - */ - class TransactionalPointcut implements PointcutInterface - { - private $reader; - - /** - * @DI\InjectParams({ - * "reader" = @DI\Inject("annotation_reader"), - * }) - * @param Reader $reader - */ - public function __construct(Reader $reader) - { - $this->reader = $reader; - } - - public function matchesClass(\ReflectionClass $class) - { - return true; - } - - public function matchesMethod(\ReflectionMethod $method) - { - return null !== $this->reader->getMethodAnnotation($method, 'Annotation\Transactional'); - } - } - -Interceptor -^^^^^^^^^^^ - -:: - - use Symfony\Component\HttpKernel\Log\LoggerInterface; - use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - use CG\Proxy\MethodInvocation; - use CG\Proxy\MethodInterceptorInterface; - use Doctrine\ORM\EntityManager; - use JMS\DiExtraBundle\Annotation as DI; - - /** - * @DI\Service("aop.transactional_interceptor") - * - * @author Johannes M. Schmitt - */ - class TransactionalInterceptor implements MethodInterceptorInterface - { - private $em; - private $logger; - - /** - * @DI\InjectParams - * @param EntityManager $em - */ - public function __construct(EntityManager $em, LoggerInterface $logger) - { - $this->em = $em; - $this->logger = $logger; - } - - public function intercept(MethodInvocation $invocation) - { - $this->logger->info('Beginning transaction for method "'.$invocation.'")'); - $this->em->getConnection()->beginTransaction(); - try { - $rs = $invocation->proceed(); - - $this->logger->info(sprintf('Comitting transaction for method "%s" (method invocation successful)', $invocation)); - $this->em->getConnection()->commit(); - - return $rs; - } catch (\Exception $ex) { - if ($ex instanceof NotFoundHttpException) { - $this->logger->info(sprintf('Committing transaction for method "%s" (exception thrown, but no rollback)', $invocation)); - $this->em->getConnection()->commit(); - } else { - $this->logger->info(sprintf('Rolling back transaction for method "%s" (exception thrown)', $invocation)); - $this->em->getConnection()->rollBack(); - } - - throw $ex; - } - } - } diff --git a/_sources/bundles/JMSDiExtraBundle/annotations.txt b/_sources/bundles/JMSDiExtraBundle/annotations.txt deleted file mode 100644 index e9adcc2..0000000 --- a/_sources/bundles/JMSDiExtraBundle/annotations.txt +++ /dev/null @@ -1,233 +0,0 @@ -Anotaciones ------------ - -``@Inject`` -~~~~~~~~~~~ - -Esta marca una propiedad o parámetro para inyección: - -.. code-block:: php - - formFactory->create('my_form'); - -.. note :: - - ``@FormType`` implica ``@Service`` si no se definió explícitamente. - -``@DoctrineListener`` o ``@DoctrineMongoDBListener`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Automáticamente, registra la clase dada como un escucha para el *ORM* de *Doctrine* o el *ODM* del *MongoDB* de *Doctrine*: - -.. code-block:: php - - ` proporcionadas en tus controladores que no son servicios; Ningún otro directorio es escaneado. - -Sin embargo, si también quisieras utilizar anotaciones para configurar tus servicios normales, puedes configurar más ubicaciones como se muestra más adelante. - -Configurando ubicaciones ------------------------- - -Si quisieras configurar los servicios en un paquete propio vía anotaciones, o tienes algunos servicios externos a cualquier estructura de paquete tal como en tu directorio :file:`src/`, lo puedes hacer usando las siguientes opciones de configuración, de modo que el paquete las elegirá, y añadirá a tu contenedor de inyección de la dependencias: - -.. configuration-block:: - - .. code-block:: yaml - - jms_di_extra: - locations: - all_bundles: false - bundles: [FooBundle, AcmeBlogBundle] - directories: ["%kernel.root_dir%/../src"] - - .. code-block:: xml - - - - FooBundle - AcmeBlogBundle - - %kernel.root_dir%/../src - - - -.. tip :: - - Para optimo rendimiento de desarrollo (en producción no hay ninguna diferencia de cualquier manera), es recomendable configurar explícitamente los directorios que se tendrían que escanear buscando clases de servicio, y no confiar en la opción de configuración ``all_bundles``. - -Inyectando el controlador automáticamente ------------------------------------------ - -Este paquete te permite configurar la inyección de ciertas propiedades y métodos -de controladores automáticamente. Esto es más útil para servicios necesarios comúnmente, los cuales a partir de entonces no se necesita anotarlos explícitamente. - -.. configuration-block:: - - .. code-block:: yaml - - jms_di_extra: - automatic_controller_injections: - properties: - request: "@request" - router: "@router" - - method_calls: - setRouter: ["@router"] - - .. code-block:: xml - - - - @request - @router - - @router - - - -Si tu controlador tiene alguna de las propiedades o métodos anteriores, entonces nunca más tendrás que añadir una anotacion ``@Inject``, sino que automáticamente se inyectará el servicio que hayas configurado. No obstante, si declaras una anotación ``@Inject``, esta automáticamente sustituye cualquier cosa que hayas configurado en la sección anterior. diff --git a/_sources/bundles/JMSDiExtraBundle/doctrine.txt b/_sources/bundles/JMSDiExtraBundle/doctrine.txt deleted file mode 100644 index a754797..0000000 --- a/_sources/bundles/JMSDiExtraBundle/doctrine.txt +++ /dev/null @@ -1,52 +0,0 @@ -Integrando con *Doctrine* -========================= - -.. versionadded:: 1.1 - Se agregó la integración con *Doctrine*. - -Configuración -------------- - -La integración con *Doctrine* está habilitada de manera predeterminada. Sin embargo, fácilmente puedes la puedes desactivar en tu configuración: - -.. configuration-block:: - - .. code-block:: yaml - - jms_di_extra: - doctrine_integration: false - - .. code-block:: xml - - - - -Inyectando dependencias en repositorios ----------------------------------------- - -Si has habilitado integración de *Doctrine*, ahora puedes inyectar dependencias en repositorios utilizando anotaciones: - -.. code-block:: php - - use JMS\DiExtraBundle\Annotation as DI; - - class MyRepository extends EntityRepository - { - private $uuidGenerator; - - /** - * @DI\InjectParams({ - * "uuidGenerator" = @DI\Inject("my_uuid_generator"), - * }) - */ - public function setUuidGenerator(UUidGenerator $uuidGenerator) - { - $this->uuidGenerator = $uuidGenerator; - } - - // ... - } - -.. note :: - - Si no quieres usar anotaciones, también puedes implementar la ``Symfony\Componente\DependencyInjection\ContainerAwareInterface`` en tus repositorios para recibir el contenedor del servicio entero. \ No newline at end of file diff --git a/_sources/bundles/JMSDiExtraBundle/index.txt b/_sources/bundles/JMSDiExtraBundle/index.txt deleted file mode 100644 index defc5f2..0000000 --- a/_sources/bundles/JMSDiExtraBundle/index.txt +++ /dev/null @@ -1,41 +0,0 @@ -``JMSDiExtraBundle`` -==================== - -Introducción ------------- - -``JMSDiExtraBundle`` añade más potentes características a la inyección de dependencias en *Symfony2*: - -- Configuración de la inyección de dependencias vía anotaciones -- Convención de inyección de dependencias basada en controladores -- Capacidades orientadas a la programación de aspectos para controladores - -Documentación -------------- - -.. toctree:: - :hidden: - - installation - configuration - usage - doctrine - annotations - -- :doc:`Instalando ` -- :doc:`Configurando ` -- :doc:`Usando ` -- :doc:`Integrando con Doctrine ` -- :doc:`Anotaciones ` -- :doc:`Colecciones de servicios diferidos ` - -Licencia --------- - -El código se libera bajo la amigable `licencia empresarial de Apache2`_. - -La documentación está sujeta a la `Licencia de atribución no comercial sin derivaciones 3.0 recíproca`_. - -.. _`licencia empresarial de Apache2`: http://www.apache.org/licenses/LICENSE-2.0.html -.. _`Licencia de atribución no comercial sin derivaciones 3.0 recíproca`: http://creativecommons.org/licenses/by-nc-nd/3.0/ - diff --git a/_sources/bundles/JMSDiExtraBundle/installation.txt b/_sources/bundles/JMSDiExtraBundle/installation.txt deleted file mode 100644 index 89b3f2e..0000000 --- a/_sources/bundles/JMSDiExtraBundle/installation.txt +++ /dev/null @@ -1,43 +0,0 @@ -Instalando -========== - -.. note :: - - Esta versión ya no es compatible con *Symfony 2.0.x*. Por favor utiliza una versión más antigua de este paquete si todavía estás usando las series 2.0. - -``JMSDiExtraBundle`` se puede instalar convenientemente vía ``Composer``. Sólo añada lo siguiente a tu archivo :file:`composer.json`: - -.. code-block:: js - - // composer.json - { - // ... - require: { - // ... - "jms/di-extra-bundle": "dev-master" - } - } - -.. note :: - - Por favor sustituye ``dev-master`` en el fragmento anterior con la rama estable más reciente, por ejemplo ``1.0.*``. Además comprueba las etiquetas en *Github* para ver cuales versiones están disponibles. - -Luego, puedes instalar las nuevas dependencias ejecutando la orden ``upgrade`` desde el directorio donde está tu archivo :file:`composer.json`: - -.. code-block:: bash - - $ php composer.phar update - -Ahora, ``Composer`` automáticamente descargará todos los archivos necesarios y los instalará para ti. Todo lo que falta por hacer es actualizar tu archivo :file:`AppKernel.php`` y registrar el nuevo paquete: - -.. code-block:: php - - voters = $voters; - } - - public function isGranted(...) - { - foreach ($this->voters as $voter) { - // hace algo, potencialmente regresa anticipadamente. - } - } - } - -Supón que etiquetas tus votantes en el ``CID`` para permitir que otros paquetes registren servicios. Luego, puedes utilizar el ``LazyServiceSequencePass`` proporcionado el cuál cuidará de recoger los servicios etiquetados, y generará la definición para la secuencia:: - - use JMS\DiExtraBundle\Compiler\LazyServiceSequencePass; - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\HttpKernel\Bundle\Bundle; - - class MyBundle extends Bundle - { - public function build(ContainerBuilder $container) - { - $container->addCompilerPass(new LazyServiceSequencePass('voter', - function(ContainerBuilder $container, Definition $seqDef) { - // Añade la definición de la secuencia como argumento para la clase MyService. - $container->getDefinition('my_service')->addArgument($seqDef); - } - )); - } - } - - - -Mapa de servicios diferidos ---------------------------- - -Por ejemplo, si tienes un mapa de formatos a codificadores correspondientes. Muy probablemente, durante una petición, sólo necesites cargar el codificador para un formato específico. Usando el mapa, la carga de ese codificador se retrasará hasta que sabes qué formato necesitas, y no cargas los codificadores (y sus dependencias) para otros formatos. - -Empieza escribiendo el servicio que consume el mapa:: - - use JMS\DiExtraBundle\Annotation as DI; - use PhpCollection\Map; - - /** @DI\Service("my_service") */ - class MyService - { - private $encoders; - - public function __construct(Map $encoders) - { - $this->encoders = $encoders; - } - - public function useEncoder($format) - { - // El codificador se carga aquí. - $encoder = $this->encoders->get('json')->get(); - } - } - -Luego, añade algunos de los que etiquetaste para permitir que los añadiran otros paquetes también:: - - /** @DI\Service @DI\Tag("encoder", attributes = {"format": "json"}) */ - class JsonEncoder { } - - /** @DI\Service @DI\Tag("encoder", attributes = {"format": "xml"}) */ - class XmlEncoder { } - - -Finalmente, sólo necesitas añadir la definición del mapa de ``ID`` que fue construida por el ``LazyServiceMapPass`` proporcionado como argumento de tu servicio ``my_service``:: - - use JMS\DiExtraBundle\Compiler\LazyServiceMapPass; - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\HttpKernel\Bundle\Bundle; - - class MyBundle extends Bundle - { - public function build(ContainerBuilder $container) - { - $container->addCompilerPass(new LazyServiceMapPass('encoder', 'format', - function(ContainerBuilder $container, Definition $mapDef) { - // Añade la definición del mapa como argumento para la clase MyService. - $container->getDefinition('my_service')->addArgument($mapDef); - } - )); - } - } - - diff --git a/_sources/bundles/JMSDiExtraBundle/usage.txt b/_sources/bundles/JMSDiExtraBundle/usage.txt deleted file mode 100644 index d12d20e..0000000 --- a/_sources/bundles/JMSDiExtraBundle/usage.txt +++ /dev/null @@ -1,97 +0,0 @@ -Usándola -======== - -Clases no controladoras ------------------------ - -Las clases no controladoras están configuradas, y gestionadas por *Symfony DIC* justo como cualquier otro servicio que configuras utilizando *YAML*, *XML* o *PHP*. La única diferencia es que lo puedes hacer a través de anotaciones, lo cual es mucho más conveniente. - -Puedes utilizar estas anotaciones en servicios (para ejemplos, ve más adelante): -``@Service``, ``@Inject``, ``@InjectParams``, ``@Observe``, ``@Tag`` - -Ten en cuenta que no puedes usar la anotación ``@Inject`` en propiedades privadas o protegidas. -Del mismo modo, la anotación ``@InjectParams`` no funciona en métodos protegidos o privados. - -Controladores -------------- - -Los controladores son un tipo de clase especial que también son tratadas especialmente por este paquete. La diferencia más notable es que no es necesario definir estas clases como servicios. Sí, no hay servicios, pero no te preocupes que todavía puedes utilizar todas las características de la ``DIC``, e incluso algunas más. - -Inyectando en el constructor/definidor -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: php - - em = $em; - $this->session = $session; - } - // ... algunas acciones - } - -.. note :: - - La inyección en el constructor no es posible cuándo una definición del padre también define un constructor qué está configurado para inyección. - -Inyectando propiedades -~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: php - - getMailer(); - } - } - - /** @DI\LookupMethod("mailer") */ - protected function getMailer() { /* aquí el cuerpo está vacío */ } - } - -Puedes utilizar este tipo de inyección si tienes una dependencia que no siempre se necesita en el controlador, y que es costoso iniciarla, como el ``mailer`` en el ejemplo anterior. diff --git a/_sources/bundles/JMSSecurityExtraBundle/annotations.txt b/_sources/bundles/JMSSecurityExtraBundle/annotations.txt deleted file mode 100644 index 37b4628..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/annotations.txt +++ /dev/null @@ -1,155 +0,0 @@ -Anotaciones ------------ - -``@PreAuthorize`` -~~~~~~~~~~~~~~~~~ - -Esta anotación te permite definir una expresión (ve el párrafo del lenguaje de expresiones) que se ejecutó anteriormente a la invocación de un método: - -.. code-block:: php - - -.. code-block:: php - - myPrivateService->aMethodOnlyToBeInvokedThroughASpecificChannel(); - } - } - -``@SatisfiesParentSecurityPolicy`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Esto lo debes definir en un método que sustituya al método que tiene metadatos de seguridad. -Está ahí para asegurarse de que estás consciente de que la seguridad del método reemplazado no se puede hacer valer más, y que tienes que copiar todas las anotaciones si deseas mantenerlas. diff --git a/_sources/bundles/JMSSecurityExtraBundle/configuration.txt b/_sources/bundles/JMSSecurityExtraBundle/configuration.txt deleted file mode 100644 index cd0d544..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/configuration.txt +++ /dev/null @@ -1,59 +0,0 @@ -Configurando ------------- - -La siguiente es la configuración predefinida: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - jms_security_extra: - # Cuando quieras asegurar todos los servicios (true), o únicamente - # proteger servicios específicos (false); ve más adelante - secure_all_services: false - - # Al activar esta opción se agrega un atributo especial - # "ROLE_IDDQD" adicional. - # especial "IS_IDDQD". Cualquiera con este atributo efectivamente - # evadirá todas las comprobaciones de seguridad. - enable_iddqd_attribute: false - - # Habilita el lenguaje de expresiones - expressions: false - - # Te permite desactivar algunos, o todos los votantes - voters: - disable_authenticated: false - disable_role: false - disable_acl: false - - # Te permite especificar reglas de control de acceso para métodos - # específicos, tal como los controladores de acción - method_access_control: { } - - util: - secure_random: - connection: # el nombre de la conexión a doctrine - table_name: seed_table - seed_provider: # id del servicio de tu propia - # implementación proveedora de semilla - - .. code-block:: xml - - - - - - - - - \ No newline at end of file diff --git a/_sources/bundles/JMSSecurityExtraBundle/cookbook/creating_your_own_expression_function.txt b/_sources/bundles/JMSSecurityExtraBundle/cookbook/creating_your_own_expression_function.txt deleted file mode 100644 index 64b084f..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/cookbook/creating_your_own_expression_function.txt +++ /dev/null @@ -1,48 +0,0 @@ -Creando tus propieas funciones de expresión -=========================================== - -.. versionadded:: 1.3 - ``@DI\SecurityFunction`` fue añadida en 1.3. - -Incluso aunque las expresiones integradas ya te permiten hacer mucho, hay casos donde querrás añadir una función de expresión personalizada. - -Supón que quieres comprobar que un usuario tiene una determinada *IP*, lo podrías hacer usando esta expresión ``container.get("request").getIp() == "127.0.0.1"``. -sin embargo, si duplicas esto en varias acciones, se vuelve en mucho que escribir. Además, ¿qué pasa si quieres añadir otra *IP*? Tendrías que editar todos los sitios donde utilices esta expresión. Por lo tanto, en vez de la expresión anterior, añades otra función ``isLocalUser()``, misma que puedes utilizar en tus expresiones. - -.. code-block:: php - - -.. code-block:: php - - container = $container; - } - - /** @DI\SecurityFunction("isLocalUser") */ - public function isLocalUser() - { - return $this->container->get('request')->getIp() === '127.0.0.1'; - } - } - -.. note:: - - Si estás utilizando anotaciones para configurar el servicio (``@DI\Service``) asegurate de que la ruta a tu archivo es escaneado por el ``JMSDiExtraBundle``. Para más información, por favor, ve `configurando JMSDiExtraBundle `_. diff --git a/_sources/bundles/JMSSecurityExtraBundle/expressions.txt b/_sources/bundles/JMSSecurityExtraBundle/expressions.txt deleted file mode 100644 index 693d91e..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/expressions.txt +++ /dev/null @@ -1,119 +0,0 @@ -Lenguaje de autorización basado en expresiones -############################################## - -Introducción ------------- - -El lenguaje de expresiones es una muy potente alternativa para los atributos simples de los votantes del sistema de seguridad. Te permiten tomar decisiones complejas para controlar el acceso, y debido a que está compiladas hasta *PHP* crudo, son mucho más rápidas que los votantes integrados. Estas además se cargan de manera diferida por naturaleza, así que también ahorrarás algunos recursos, por ejemplo, no tener que iniciar el sistema *ACL* entero en cada petición. - -Uso ---- - -Uso programado -~~~~~~~~~~~~~~ - -Puedes ejecutar expresiones programadas utilizando el método ``isGranted`` del ``SecurityContext``. Algunos ejemplos: - -.. code-block:: php - - -.. code-block:: php - - isGranted(array(new Expression('hasRole("A")'))); - $securityContext->isGranted(array(new Expression('hasRole("A") or (hasRole("B") and hasRole("C"))'))); - $securityContext->isGranted(array(new Expression('hasPermission(object, "VIEW")'), $object)); - $securityContext->isGranted(array(new Expression('token.getUsername() == "Johannes"'))); - -Usando *Twig* -~~~~~~~~~~~~~ - -Puedes comprobar expresiones desde plantillas *Twig* utilizando la función ``is_expr_granted``. Algunos ejemplos: - -.. code-block:: jinja - - is_expr_granted("hasRole('FOO')") - is_expr_granted("hasPermission(object, 'VIEW')", object) - -Usando el control de acceso -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -También puedes usar expresiones en ``access_control``: - -.. configuration-block:: - - .. code-block:: yaml - - security: - access_control: - - { path: ^/foo, access: "hasRole('FOO') and hasRole('BAR')" } - - .. code-block:: xml - - - - - -Usándolo en base a anotaciones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Consulta la anotación ``@PreAuthorize`` en la `Referencia`_. Por favor, recuerda activar las expresiones en tu configuración, de lo contrario obtendrás una excepción al comprobar el acceso. - -Referencia ----------- - -+-----------------------------------+--------------------------------------------------------+ -| Expresión | Descripción | -+===================================+========================================================+ -| hasRole('ROLE') | comprueba si el segmento tiene un determinado rol. | -+-----------------------------------+--------------------------------------------------------+ -| hasAnyRole('ROLE1', 'ROLE2', ...) | Comprueba si el segmento tiene alguno de los roles | -| | proporcionados. | -+-----------------------------------+--------------------------------------------------------+ -| isAnonymous() | Comprueba si el segmento es anónimo. | -+-----------------------------------+--------------------------------------------------------+ -| isRememberMe() | Comprueba si el segmento es del tipo "Recuérdame". | -+-----------------------------------+--------------------------------------------------------+ -| isFullyAuthenticated() | Comprueba si el segmento está completamente | -| | autenticado. | -+-----------------------------------+--------------------------------------------------------+ -| isAuthenticated() | Comprueba si el segmento no es anónimo. | -+-----------------------------------+--------------------------------------------------------+ -| hasPermission(var, 'PERMISSION') | Comprueba si el segmento tiene los permisos | -| | proporcionados para un objeto dado (necesita el | -| | sistema *ACL*). | -+-----------------------------------+--------------------------------------------------------+ -| token | Variable que permite el acceso al segmento que se | -| | encuentra actualmente en el contexto de seguridad. | -+-----------------------------------+--------------------------------------------------------+ -| user | Variable que permite el acceso al usuario que está | -| | actualmente en el contexto de seguridad. | -+-----------------------------------+--------------------------------------------------------+ -| object | Variable que hace referencia al objeto para el cual se | -| | solicita acceso. | -+-----------------------------------+--------------------------------------------------------+ -| #*paramName* | Cualquier identificador prefijado con # se refiere a | -| | un parámetro del mismo nombre pasado al método donde | -| | se usa la expresión | -+-----------------------------------+--------------------------------------------------------+ -| and / && | El operador binario «and» | -+-----------------------------------+--------------------------------------------------------+ -| or / || | El operador binario «or» | -+-----------------------------------+--------------------------------------------------------+ -| == | El operador binario «is equal» | -+-----------------------------------+--------------------------------------------------------+ -| not / ! | Operador de negación | -+-----------------------------------+--------------------------------------------------------+ - -Recursos adicionales --------------------- - -.. toctree :: - :hidden: - - cookbook/creating_your_own_expression_function - -- :doc:`Creando tus propieas funciones de expresión ` diff --git a/_sources/bundles/JMSSecurityExtraBundle/index.txt b/_sources/bundles/JMSSecurityExtraBundle/index.txt deleted file mode 100644 index 0fd0258..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/index.txt +++ /dev/null @@ -1,45 +0,0 @@ -``JMSSecurityExtraBundle`` -========================== - -Resumen -------- - -Este paquete mejora el componente ``Security`` de *Symfony2* añadiendo nuevas características. - -Características: - -- Potente lenguaje de autorización basado en expresiones -- Método de seguridad para autorización -- Configuración de autorización vía anotaciones -- Generador de números aleatorios seguros - -Documentación -------------- - -.. toctree:: - :hidden: - - installation - expressions - configuration - method_security_authorization - annotations - -- :doc:`installation` -- :doc:`configuration` -- :doc:`expressions` -- :doc:`method_security_authorization` -- :doc:`random_number_generator` -- :doc:`annotations` - -Licencia --------- - -El código está liberado bajo la amigable `Licencia de Apache2`_. - -La documentación está sujeta a la `Attribution-NonCommercial-NoDerivs 3.0 Unported -license`_. - -.. _`Licencia de Apache2`: http://www.apache.org/licenses/LICENSE-2.0.html -.. _`Attribution-NonCommercial-NoDerivs 3.0 Unported license`: http://creativecommons.org/licenses/by-nc-nd/3.0/ - diff --git a/_sources/bundles/JMSSecurityExtraBundle/installation.txt b/_sources/bundles/JMSSecurityExtraBundle/installation.txt deleted file mode 100644 index 179820f..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/installation.txt +++ /dev/null @@ -1,123 +0,0 @@ -Instalando ----------- - -1. Usando ``Composer`` (recomendado) ------------------------------------- - -Para instalar ``JMSSecurityExtraBundle`` con ``Composer`` sólo añade lo siguiente al tu archivo ``composer.json``: - -.. code-block:: js - - // composer.json - { - // ... - require: { - // ... - "jms/security-extra-bundle": "dev-master" - } - } - -.. note:: - - Por favor, sustituye ``dev-master`` en el fragmento anterior con la última rama estable, por ejemplo ``1.0.*``. - -Luego, puedes instalar las nuevas dependencias ejecutando la orden ``update`` de ``Composer`` desde el directorio donde tienes tu archivo ``composer.json``: - -.. code-block:: bash - - $ php composer.phar update jms/security-extra-bundle - -Ahora, ``Composer`` automáticamente descargará todos los archivos necesarios y los instalará -por ti. Todo lo que resta por hacer es actualizar el archivo ``AppKernel.php`` y registrar el nuevo paquete: - -.. code-block:: php - - -.. code-block:: php - - registerNamespaces(array( - // ... - // ... - 'JMS' => __DIR__.'/../vendor/bundles', - 'Metadata' => __DIR__.'/../vendor/metadata/src', - 'CG' => __DIR__.'/../vendor/cg-library/src', - // ... - )); - -Ahora usa el programa ``vendors`` para clonar en tu proyecto los repositorios recientemente añadidos: - -.. code-block:: bash - - php bin/vendors install diff --git a/_sources/bundles/JMSSecurityExtraBundle/method_security_authorization.txt b/_sources/bundles/JMSSecurityExtraBundle/method_security_authorization.txt deleted file mode 100644 index 8c7b5dd..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/method_security_authorization.txt +++ /dev/null @@ -1,55 +0,0 @@ -Método de seguridad para autorización -------------------------------------- - -Los métodos de seguridad permiten controlar el acceso con más detalle del que tiene para ofrecer *Symfony2*. - -Generalmente puedes salvaguardar todos los métodos públicos, protegidos que no son estáticos, y no son finales. Los métodos privados no se pueden asegurar. También puedes añadir metadatos para métodos abstractos, o interfaces que entonces serán aplicados automáticamente a sus implementaciones concretas. - -Control de acceso vía configuración ``DI`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes especificar **expresiones** para el control de acceso en la configuración ``DI``: - -.. code-block:: yaml - - # config.yml - jms_security_extra: - method_access_control: - ':loginAction$': 'isAnonymous()' - 'AcmeFooBundle:.*:deleteAction': 'hasRole("ROLE_ADMIN")' - '^MyNamespace\MyService::foo$': 'hasPermission(#user, "VIEW")' - -El patrón es una expresión regular que distingue entre mayúsculas y minúsculas emparejada contra dos notaciones. -Se utiliza la primer coincidencia. - -En primer lugar, tu patrón se compara con la notación de controladores que no son servicios. -Obviamente, esto sólo se hace si tu clase en realidad es un controlador, por ejemplo, ``AcmeFooBundle:Add:new`` para un controlador llamado ``AddController`` y un método denominado ``NewAction`` en un subespacio de nombres ``Controller`` de un paquete llamado ``AcmeFooBundle``. - -Por último, tu patrón se empareja contra la concatenación del nombre de clase, y el nombre del método que está invocando, por ejemplo, ``Mi\Completamente\Cualificado\NombreClase::miNombreMetodo``. - -.. Note:: - - Si quisieras asegurar controladores que no son servicios, en su lugar deberías usar el ``JMSDiExtraBundle``. - -Control de acceso vía anotaciones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si quisieras asegurar un servicio con ``anotaciones``, necesitas habilitar la configuración de ``annotation`` para ese servicio: - -.. configuration-block:: - - .. code-block:: yaml - - services: - foo: - class: Bar - tags: [ { name: "security.secure_service" } ] - - .. code-block:: xml - - - - - - -En caso que quieras configurar todos los servicios vía ``anotaciones``, también puedes poner ``secure_all_services`` a ``true``. Entonces, no necesitas añadir una etiqueta a cada servicio. diff --git a/_sources/bundles/JMSSecurityExtraBundle/random_number_generator.txt b/_sources/bundles/JMSSecurityExtraBundle/random_number_generator.txt deleted file mode 100644 index 04a34f1..0000000 --- a/_sources/bundles/JMSSecurityExtraBundle/random_number_generator.txt +++ /dev/null @@ -1,60 +0,0 @@ -Generador de números aleatorios seguros ---------------------------------------- - -.. versionadded:: 1.2 - Se añadió el generador de números aleatorios seguros. - -Introducción ------------- - -En casi todas las aplicaciones, necesitas generar números aleatorios que un posible atacante no pueda adivinar. Desafortunadamente, *PHP* no proporciona la capacidad para hacerlo de forma consistente en todas las plataformas. - -Este paquete viene con varias implementaciones de proveedores de semillas, y se elige el mejor proveedor posible, dependiendo de tu configuración de *PHP*. - -Configurando ------------- - -Puedes activar el servicio ``«security.secure_random»`` con la siguiente configuración: - -.. configuration-block:: - - .. code-block:: yaml - - jms_security_extra: - util: - secure_random: ~ - - .. code-block:: xml - - - - - - - -También asegúrate de ejecutar ``php app/console doctrine:scheme:update``, o crea una migración equivalente para importar la tabla ``seed``. - -Usándolo --------- - -El generador se activa con el ``id`` de servicio ``security.secure_random``. - -.. code-block:: php - - container->get('security.secure_random'); - $bytes = $generator->nextBytes(16); // número aleatorio de 128-bit - -``$bytes`` en el ejemplo anterior contiene datos binarios. Luego puedes convertir estos datos a un formato que puedas imprimir utilizando una de estas funciones: - -.. code-block:: php - - -.. code-block:: php - - setExpires()`` -``@Cache(smaxage="15")`` ``$response->setSharedMaxAge()`` -``@Cache(maxage="15")`` ``$response->setMaxAge()`` -``@Cache(vary=["Cookie"])`` ```$response->setVary()`` -================================== =================================== - -.. note:: - - El atributo ``expires`` toma cualquier fecha válida entendida por la función ``strtotime()`` de *PHP*. diff --git a/_sources/bundles/SensioFrameworkExtraBundle/annotations/converters.txt b/_sources/bundles/SensioFrameworkExtraBundle/annotations/converters.txt deleted file mode 100644 index 44bf41f..0000000 --- a/_sources/bundles/SensioFrameworkExtraBundle/annotations/converters.txt +++ /dev/null @@ -1,202 +0,0 @@ -``@ParamConverter`` -=================== - -Usando ------- - -La anotación ``@ParamConverter`` llama a ``converters`` para convertir parámetros de la petición a objetos. Estos objetos se almacenan como atributos de la petición y por lo tanto se pueden inyectar en los argumentos del método controlador:: - - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; - - /** - * @Route("/blog/{id}") - * @ParamConverter("post", class="SensioBlogBundle:Post") - */ - public function showAction(Post $post) - { - } - -Suceden varias cosas bajo el capó: - -* El convertidor intenta obtener un objeto ``SensioBlogBundle:Post`` desde los atributos de la petición (los atributos de la petición provienen de los marcadores de posición de la ruta -- aquí ``id``); - -* Si no se encontró algún objeto ``Post``, se genera una ``respuesta`` ``404``; - -* Si se encuentra un objeto ``Post``, se define un nuevo atributo ``post`` para la ``petición`` (accesible a través de ``$request->attributes->get('post')``); - -* Al igual que cualquier otro atributo de la petición, este se inyecta automáticamente en el controlador cuando está presente en la firma del método. - -Si utilizas el tipo como el del ejemplo anterior, incluso puedes omitir la anotación ``@ParamConverter`` por completo: - -.. code-block:: php - - // automático con firma de método - public function showAction(Post $post) - { - } - -Para detectar qué convertidor está corrido en un parámetro se ejecuta el siguiente proceso : - -* Si se eligió un convertidor explícitamente con ``@ParamConverter(converter="nombre")`` se elige el convertidor con el nombre dado. -* De lo contrario se itera sobre todos los parámetros de convertidores registrados por prioridad. - El método ``supports()`` es invocado para comprobar si un parámetro convertidor puede convertir la petición en el parámetro requerido. Si regresa ``true`` el parámetro ``converter`` es invocado. - -Convertidores integrados ------------------------- - -El paquete tiene dos convertidor integrados, uno es el convertidor *Doctrine* y el otro``DateTime``. - -Convertidor *Doctrine* -~~~~~~~~~~~~~~~~~~~~~~ - -Nombre de convertidor: ``doctrine.orm`` - -El convertidor de *Doctrine* intenta convertir los atributos de la petición a entidades -de recuperadas de la base de datos por *Doctrine*. Hay dos distintos posibles enfoques: - -- Recuperar el objeto por clave primaria -- Recuperar el objeto por uno o varios campos que contienen valores únicos en la base de datos. - -El siguiente algoritmo determina qué operación realizar. - -- Si en la ruta está presente un argumento ``{id}``, busca el objeto por clave primaria. -- Si se configura una opción ``'id'`` y el argumento ruta concuerda, busca el objeto por clave primaria. -- Si no aplican las reglas anteriores, intenta encontrar una entidad emparejando los argumentos de la ruta con los campos de la entidad. Puedes controlar este proceso configurando el argumento ``exclude`` o asignando un atributo al campo llamado ``mapping``. - -De manera predeterminada, el convertidor de *Doctrine* utiliza el valor *predeterminado* del gestor de la entidad. Lo puedes configurar con la opción ``entity_manager``: - -.. code-block:: php - - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; - - /** - * @Route("/blog/{id}") - * @ParamConverter("post", class="SensioBlogBundle:Post", options={"entity_manager" = "foo"}) - */ - public function showAction(Post $post) - { - } - -Si el marcador de posición no tiene el mismo nombre como clave primaria, pasa la opción ``id``:: - - /** - * @Route("/blog/{post_id}") - * @ParamConverter("post", class="SensioBlogBundle:Post", options={"id" = "post_id"}) - */ - public function showAction(Post $post) - { - } - -Esto también te permite tener múltiples convertidores en una acción:: - - /** - * @Route("/blog/{id}/comments/{comment_id}") - * @ParamConverter("comment", class="SensioBlogBundle:Comment", options={"id" = "comment_id"}) - */ - public function showAction(Post $post, Comment $comment) - { - } - -En el ejemplo anterior, el argumento ``post`` es manejado automáticamente, pero el comentario está configurado con la anotación debido a que ambos no pueden seguir la convención predefinida. - -Si quieres emparejar una entidad usando múltiples campos usa ``mapping``:: - - /** - * @Route("/blog/{date}/{slug}/comments/{comment_slug}") - * @ParamConverter("post", options={"mapping": {"date": "date", "slug": "slug"}}) - * @ParamConverter("comment", options={"mapping": {"comment_slug": "slug"}}) - */ - public function showAction(Post $post, Comment $comment) - { - } - -Si estás emparejando una entidad que utiliza varios campos, pero quieres excluir un -argumento de la ruta para que forme parte de los criterios:: - - /** - * @Route("/blog/{date}/{slug}") - * @ParamConverter("post", options={"exclude": ["date"]}) - */ - public function showAction(Post $post, \DateTime $date) - { - } - -Si quieres especificar el método ``repository`` a utilizar para encontrar la entidad (por ejemplo, para añadir una unión a la consulta), puedes añadir la opción ``repository_method``:: - - /** - * @Route("/blog/{id}") - * @ParamConverter("post", class="SensioBlogBundle:Post", options={"repository_method" = "findWithJoins"}) - */ - public function showAction(Post $post) - { - } - -Convertidor ``DateTime`` -~~~~~~~~~~~~~~~~~~~~~~~~ - -Nombre de convertidor: ``datetime`` - -El convertidor ``datetime`` convierte cualquier ruta o atributo de petición a una instancia de ``datetime``:: - - /** - * @Route("/blog/archive/{start}/{end}") - */ - public function archiveAction(\DateTime $start, \DateTime $end) - { - } - -Por omisión puede analizar cualquier formato de fecha aceptado por el constructor de ``DateTime``. Puedes ser más estricto con la entrada dada a través de las opciones:: - - /** - * @Route("/blog/archive/{start}/{end}") - * @ParamConverter("start", options={"format": "Y-m-d"}) - * @ParamConverter("end", options={"format": "Y-m-d"}) - */ - public function archiveAction(\DateTime $start, \DateTime $end) - { - } - -Creando un convertidor ----------------------- - -Todos los convertidores deben implementar la :class:`Sensio\\Bundle\\FrameworkExtraBundle\\Request\\ParamConverter\\ParamConverterInterface`:: - - namespace Sensio\Bundle\FrameworkExtraBundle\Request\ParamConverter; - - use Sensio\Bundle\FrameworkExtraBundle\Configuration\ConfigurationInterface; - use Symfony\Component\HttpFoundation\Request; - - interface ParamConverterInterface - { - function apply(Request $request, ConfigurationInterface $configuration); - - function supports(ConfigurationInterface $configuration); - } - -El método ``supports()`` debe devolver ``true`` cuando sea capaz de convertir la configuración dada (una instancia de ``ParamConverter``). - -La instancia de ``ParamConverter`` tiene tres piezas de información sobre la anotación: - -* ``name``: El atributo ``name``; -* ``class``: El atributo nombre de clase (puede ser cualquier cadena que represente el nombre de la clase); -* ``options``: Un arreglo de opciones - -El método ``apply()`` se llama cuando una configuración es compatible. Basándonos en los atributos de la petición, debemos establecer un atributo llamado ``$configuration->getName()``, que almacene un objeto de la clase ``$configuration->getClass()``. - -Para registrar tu servicio convertidor tienes que añadir una etiqueta a tu servicio: - -.. configuration-block:: - - .. code-block:: xml - - - - - -Puedes registrar un convertidor por prioridad, por nombre (atributo ``«converter»``) o ambos. Si no especificas una prioridad o nombre el convertidor será añadido a la pila de convertidores con una prioridad de `0`. Para desactiva explícitamente la inscripción por prioridad tienes que ajustar la ``priority="false"`` en la definición de tu etiqueta. - -.. tip:: - - Utiliza la clase ``DoctrineParamConverter`` como plantilla para tus propios convertidores. diff --git a/_sources/bundles/SensioFrameworkExtraBundle/annotations/routing.txt b/_sources/bundles/SensioFrameworkExtraBundle/annotations/routing.txt deleted file mode 100644 index cf51bcf..0000000 --- a/_sources/bundles/SensioFrameworkExtraBundle/annotations/routing.txt +++ /dev/null @@ -1,163 +0,0 @@ -``@Route`` y ``@Method`` -======================== - -Usando ------- - -La anotación ``@Route`` asigna un patrón de ruta a un controlador:: - - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - - class PostController extends Controller - { - /** - * @Route("/") - */ - public function indexAction() - { - // ... - } - } - -La acción ``index`` del controlador ``Post`` ya está asignada a la dirección ``/``. Esto es equivalente a la siguiente configuración *YAML*: - -.. code-block:: yaml - - blog_home: - pattern: / - defaults: { _controller: SensioBlogBundle:Post:index } - -Al igual que cualquier patrón de ruta, puedes definir marcadores de posición, requisitos y valores predeterminados:: - - /** - * @Route("/{id}", requirements={"id" = "\d+"}, defaults={"id" = 1}) - */ - public function showAction($id) - { - } - -También puedes definir el valor predefinido para un marcador de posición con el valor predeterminado de *PHP*:: - - /** - * @Route("/{id}", requirements={"id" = "\d+"}) - */ - public function showAction($id = 1) - { - } - -También puedes combinar más de una *URL* definiendo una anotación ``@Route`` adicional:: - - /** - * @Route("/", defaults={"id" = 1}) - * @Route("/{id}") - */ - public function showAction($id) - { - } - -Activando ---------- - -Las rutas se deben importar para estar activas como cualquier otro recurso de enrutado (observa el tipo ``annotation``): - -.. code-block:: yaml - - # app/config/routing.yml - - # importa las rutas desde una clase controlador - post: - resource: "@SensioBlogBundle/Controller/PostController.php" - type: annotation - -También puedes importar un directorio completo: - -.. code-block:: yaml - - # importa rutas desde el directorio de controladores - blog: - resource: "@SensioBlogBundle/Controller" - type: annotation - -Como para cualquier otro recurso, puedes «montar» las rutas bajo un determinado prefijo: - -.. code-block:: yaml - - post: - resource: "@SensioBlogBundle/Controller/PostController.php" - prefix: /blog - type: annotation - -Nombre de ruta --------------- - -A una ruta definida con la anotación ``@Route`` se le asigna un nombre predeterminado, el cual está compuesto por el nombre del paquete, el nombre del controlador y el nombre de la acción. En el caso del ejemplo anterior sería -``sensio_blog_post_index``; - -Puedes utilizar el atributo ``name`` para reemplazar este nombre de ruta predeterminado:: - - /** - * @Route("/", name="blog_home") - */ - public function indexAction() - { - // ... - } - -Prefijo de ruta ---------------- - -Una anotación ``@Route`` en una clase controlador define un prefijo para todas las rutas de acción:: - - /** - * @Route("/blog") - */ - class PostController extends Controller - { - /** - * @Route("/{id}") - */ - public function showAction($id) - { - } - } - -La acción ``show`` ahora se asigna al patrón ``/blog/{id}``. - -Método de la ruta ------------------ - -Hay un atajo en la anotación ``@Method`` para especificar el método *HTTP* permitido para la ruta. Para usarlo, importa el espacio de nombres de la anotación ``Method``:: - - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; - - /** - * @Route("/blog") - */ - class PostController extends Controller - { - /** - * @Route("/edit/{id}") - * @Method({"GET", "POST"}) - */ - public function editAction($id) - { - } - } - -La acción ``editar`` ahora es asignada al patrón ``/blog/editar/{id}`` si el método *HTTP* utilizado es *GET* o *POST*. - -La anotación ``@Method`` sólo se toma en cuenta cuando una acción se anota con ``@Route``. - -Controlador como servicio -------------------------- - -También puedes utilizar la anotación ``@Route`` en una clase de controlador para asignar la clase controlador a un servicio para que el resolutor de controlador cree una instancia del controlador obteniendo el ``ID`` del contenedor en lugar de llamar a ``new PostController()`` en sí mismo:: - - /** - * @Route(service="my_post_controller_service") - */ - class PostController extends Controller - { - // ... - } diff --git a/_sources/bundles/SensioFrameworkExtraBundle/annotations/view.txt b/_sources/bundles/SensioFrameworkExtraBundle/annotations/view.txt deleted file mode 100644 index 4728207..0000000 --- a/_sources/bundles/SensioFrameworkExtraBundle/annotations/view.txt +++ /dev/null @@ -1,78 +0,0 @@ -``@Template`` -============= - -Usando ------- - -La anotación ``@Template`` asocia un controlador con un nombre de plantilla:: - - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; - - /** - * @Template("SensioBlogBundle:Post:show.html.twig") - */ - public function showAction($id) - { - // consigue el Post - $post = ...; - - return array('post' => $post); - } - -Cuando utilizas la anotación ``@Template``, el controlador debe regresar un arreglo de parámetros para pasarlo a la vista en lugar de un objeto ``Respuesta``. - -.. tip:: - Si la acción devuelve un objeto ``Respuesta``, la anotación ``@Template`` simplemente se omite. - -Si la plantilla se nombra después del controlador y los nombres de acción, como en el caso del ejemplo anterior, puedes omitir incluso el valor de la anotación:: - - /** - * @Template - */ - public function showAction($id) - { - // consigue el Post - $post = ...; - - return array('post' => $post); - } - -.. note:: - - Si estás utilizando *PHP* como tu sistema de plantillas, necesitas hacerlo explícito:: - - /** - * @Template(engine="php") - */ - public function showAction($id) - { - // ... - } - -Y si los únicos parámetros para pasar a la plantilla son los argumentos del método, puedes utilizar el atributo ``vars`` en lugar de devolver un arreglo. Esto es muy útil en combinación con la :doc:`anotación @ParamConverter `:: - - /** - * @ParamConverter("post", class="SensioBlogBundle:Post") - * @Template("SensioBlogBundle:Post:show.html.twig", vars={"post"}) - */ - public function showAction(Post $post) - { - } - -que, gracias a las convenciones, es equivalente a la siguiente configuración:: - - /** - * @Template(vars={"post"}) - */ - public function showAction(Post $post) - { - } - -Puedes hacer que sea aún más conciso puesto que todos los argumentos del método se pasan automáticamente a la plantilla si el método devuelve ``null`` y no se define el atributo ``vars``:: - - /** - * @Template - */ - public function showAction(Post $post) - { - } diff --git a/_sources/bundles/SensioFrameworkExtraBundle/index.txt b/_sources/bundles/SensioFrameworkExtraBundle/index.txt deleted file mode 100644 index 404c6e5..0000000 --- a/_sources/bundles/SensioFrameworkExtraBundle/index.txt +++ /dev/null @@ -1,140 +0,0 @@ -``SensioFrameworkExtraBundle`` -============================== - -El ``FrameworkBundle`` predeterminado de *Symfony2* implementa una plataforma *MVC*, básica pero robusta y flexible. `SensioFrameworkExtraBundle`_ la extiende añadiendo agradables convenciones y anotaciones. Esto permite controladores más concisos. - -Instalando ----------- - -`Descarga`_ el paquete y ponlo bajo el espacio de nombres ``Sensio\Bundle\``. -Luego, como con cualquier otro paquete, inclúyelo en la clase de tu núcleo: - -.. code-block:: php - - public function registerBundles() - { - $bundles = array( - ... - - new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(), - ); - - ... - } - -Configurando ------------- - -Todas las características proporcionadas por el paquete están habilitadas por omisión, cuando registras el paquete en la clase de tu núcleo. - -La configuración predeterminada es la siguiente: - -.. configuration-block:: - - .. code-block:: yaml - - sensio_framework_extra: - router: { annotations: true } - request: { converters: true } - view: { annotations: true } - cache: { annotations: true } - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // Carga el generador de perfiles - $container->loadFromExtension('sensio_framework_extra', array( - 'router' => array('annotations' => true), - 'request' => array('converters' => true), - 'view' => array('annotations' => true), - 'cache' => array('annotations' => true), - )); - -Puedes desactivar algunas anotaciones y convenciones definiendo uno o más valores en ``false``. - -Anotaciones para controladores ------------------------------- - -Las anotaciones son una buena manera de configurar controladores fácilmente, desde las rutas hasta la configuración de la caché. - -Incluso si las anotaciones no son una característica natural de *PHP*, aún tienen varias ventajas sobre los métodos de configuración clásicos de *Symfony2*: - -* El código y la configuración están en el mismo lugar (la clase controlador) -* Fáciles de aprender y usar; -* Concisas para escribirlas; -* Adelgazan tu controlador (puesto que su única responsabilidad es conseguir los datos del modelo). - -.. tip:: - - Si utilizas las clases ``view``, las anotaciones son una buena manera de evitar la creación de clases ``view`` para casos simples y comunes. - -Las siguientes anotaciones están definidas por el paquete: - -.. toctree:: - :maxdepth: 1 - - annotations/routing - annotations/converters - annotations/view - annotations/cache - -Este ejemplo muestra en acción todas las anotaciones disponibles:: - - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method; - - /** - * @Route("/blog") - * @Cache(expires="tomorrow") - */ - class AnnotController extends Controller - { - /** - * @Route("/") - * @Template - */ - public function indexAction() - { - $posts = ...; - - return array('posts' => $posts); - } - - /** - * @Route("/{id}") - * @Method("GET") - * @ParamConverter("post", class="SensioBlogBundle:Post") - * @Template("SensioBlogBundle:Annot:post.html.twig", vars={"post"}) - * @Cache(smaxage="15") - */ - public function showAction(Post $post) - { - } - } - -En la medida en que el método ``showAction`` siga algunas convenciones, puedes omitir algunas anotaciones:: - - /** - * @Route("/{id}") - * @Cache(smaxage="15") - */ - public function showAction(Post $post) - { - } - -Es necesario importar las rutas para activarlas como cualquier otro recurso de enrutado, ve :doc:`Activando anotaciones de enrutado ` para detalles. - -.. _`SensioFrameworkExtraBundle`: https://github.com/sensio/SensioFrameworkExtraBundle -.. _`Descarga`: http://github.com/sensio/SensioFrameworkExtraBundle diff --git a/_sources/bundles/SensioGeneratorBundle/commands/generate_bundle.txt b/_sources/bundles/SensioGeneratorBundle/commands/generate_bundle.txt deleted file mode 100644 index ac92f52..0000000 --- a/_sources/bundles/SensioGeneratorBundle/commands/generate_bundle.txt +++ /dev/null @@ -1,53 +0,0 @@ -Generando la estructura para un nuevo paquete -============================================= - -Usando ------- - -El ``generate:bundle`` genera una estructura para un nuevo paquete y automáticamente lo activa en la aplicación. - -De manera predeterminada, la orden se ejecuta en modo interactivo y hace preguntas para determinar el nombre del paquete, ubicación, formato y estructura de configuración predeterminada: - -.. code-block:: bash - - php app/console generate:bundle - -Para desactivar el modo interactivo, utiliza la opción ``--no-interaction`` pero no olvides suministrar todas las opciones necesarias: - -.. code-block:: bash - - php app/console generate:bundle --namespace=Acme/Bundle/BlogBundle --no-interaction - -Opciones disponibles --------------------- - -* ``--namespace``: El espacio de nombres a crear para el paquete. El espacio de nombres debe comenzar con un nombre de «proveedor», tal como el nombre de tu empresa, el nombre de tu proyecto, o el nombre de tu cliente, seguido por uno o más subespacios de nombres para una categoría opcional, el cual debe terminar con el nombre del paquete en sí mismo (debe tener ``Bundle`` como sufijo): - - .. code-block:: bash - - php app/console generate:bundle --namespace=Acme/Bundle/BlogBundle - -* ``--bundle-name``: El nombre opcional del paquete. Esta debe ser una cadena que termine con el sufijo ``Bundle``: - - .. code-block:: bash - - php app/console generate:bundle --bundle-name=AcmeBlogBundle - -* ``--dir``: El directorio en el cual guardar el paquete. Por convención, la orden detecta y utiliza el directorio ``src/`` de tu aplicación: - - .. code-block:: bash - - php app/console generate:bundle --dir=/var/www/miproyecto/src - -* ``--format``: (**annotation**) [valores: ``yml``, ``xml``, ``php`` o ``annotation``] - Determina el formato de enrutado a usar para los archivos de configuración generados. De manera predeterminada, la orden utiliza el formato ``annotation``. Al elegir el formato ``annotation`` se espera que el paquete ``SensioFrameworkExtraBundle`` ya esté instalado: - - .. code-block:: bash - - php app/console generate:bundle --format=annotation - -* ``--structure``: (**no**) [valores: ``yes``|``no``] Cuando o no generar una estructura de directorios completa incluyendo directorios públicos vacíos para documentación, activos *web* y diccionarios de traducción: - - .. code-block:: bash - - php app/console generate:bundle --structure=yes diff --git a/_sources/bundles/SensioGeneratorBundle/commands/generate_controller.txt b/_sources/bundles/SensioGeneratorBundle/commands/generate_controller.txt deleted file mode 100644 index 91c9385..0000000 --- a/_sources/bundles/SensioGeneratorBundle/commands/generate_controller.txt +++ /dev/null @@ -1,50 +0,0 @@ -Generando un nuevo controlador -============================== - -Usando ------- - -La orden ``generate:controller`` genera un nuevo controlador que incluye acciones, pruebas, plantillas y enrutado. - -De manera predeterminada, la orden se ejecuta en modo interactivo y hace preguntas para determinar el nombre del paquete, ubicación, formato y estructura de configuración predeterminada: - -.. code-block:: bash - - $ php app/console generate:controller - -La orden se puede ejecutar en modo no interactivo usando la opción ``--non-interaction`` sin olvidar todas las opciones necesarias: - -.. code-block:: bash - - $ php app/console generate:controller --no-interaction --controller=AcmeBlogBundle:Post - -Opciones disponibles --------------------- - -* ``--controller``: El nombre del controlador dado como notación abreviada conteniendo el nombre del proveedor en el cual está ubicado el controlador y el nombre del paquete. Por ejemplo: ``AcmeBlogBundle:Post`` (crea el ``PostController`` en el paquete ``AcmeBlogBundle``): - - .. code-block:: bash - - $ php app/console generate:controller --controller=AcmeBlogBundle:Post - -* ``--actions``: La lista de acciones a generar en la clase controladora. Esta tiene un formato como ``%nombreaccion%:%ruta%:%plantilla%`` (dónde ``:%plantilla%`` es opcional: - - .. code-block:: bash - - $ php app/console generate:controller --actions="showPostAction:/article/{id} getListAction:/_list-posts/{max}:AcmeBlogBundle:Post:list_posts.html.twig" - - # o - $ php app/console generate:controller --actions=showPostAction:/article/{id} --actions=getListAction:/_list-posts/{max}:AcmeBlogBundle:Post:list_posts.html.twig - -* ``--route-format``: (**annotation**) [valores: yml, xml, php o annotation] - Esta opción determina el formato que utilizará el enrutado. De manera predeterminada, la orden utiliza el formato ``annotation``: - - .. code-block:: bash - - $ php app/console generate:controller --route-format=annotation - -* ``--template-format``: (**twig**) [valores: twigo php] Esta opción determina el formato que utilizarán las plantillas. De manera predeterminada, la orden utiliza el formato ``twig``: - - .. code-block:: bash - - $ php app/console generate:controller --template-format=twig diff --git a/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_crud.txt b/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_crud.txt deleted file mode 100644 index 963e156..0000000 --- a/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_crud.txt +++ /dev/null @@ -1,53 +0,0 @@ -Generando un controlador ``CRUD`` basado en una entidad de *Doctrine* -===================================================================== - -Usando ------- - -La orden ``generate:doctrine:crud`` genera un controlador básico para una determinada entidad ubicada en un determinado paquete. Este controlador te permite realizar cinco operaciones básicas en un modelo. - -* Listar todos los registros -* Mostrar un determinado registro identificado por su clave primaria -* Crear un nuevo registro. -* Editar un registro existente. -* Eliminar un registro existente. - -De manera predeterminada, la orden se ejecuta en modo interactivo y hace preguntas para determinar el nombre de la entidad, el prefijo de la ruta o cuando o no generar acciones de escritura: - -.. code-block:: bash - - php app/console generate:doctrine:crud - -Para desactivar el modo interactivo, utiliza la opción ``--no-interaction`` pero no olvides suministrar todas las opciones necesarias: - -.. code-block:: bash - - php app/console generate:doctrine:crud --entity=AcmeBlogBundle:Post --format=annotation --with-write --no-interaction - -Opciones disponibles --------------------- - -* ``--entity``: El nombre de la entidad dado en notación de atajo que contiene el nombre del paquete en el que se encuentra la entidad y el nombre de la entidad. Por ejemplo: ``AcmeBlogBundle:Post``: - - .. code-block:: bash - - php app/console generate:doctrine:crud --entity=AcmeBlogBundle:Post - -* ``--route-prefix``: El prefijo que se utilizará para cada ruta que identifica una acción: - - .. code-block:: bash - - php app/console generate:doctrine:crud --route-prefix=acme_post - -* ``--with-write``: (**no**) [valores: ``yes`` | ``no``] Cuando o no generar las acciones ``new``, ``create``, ``edit``, ``update`` y ``delete``: - - .. code-block:: bash - - php app/console generate:doctrine:crud --with-write - -* ``--format``: (**annotation**) [valores: ``yml``, ``xml``, ``php`` o ``annotation``] - Determina el formato de enrutado a usar para los archivos de configuración generados. De manera predeterminada, la orden utiliza el formato ``annotation``. Al elegir el formato ``annotation`` se espera que el paquete ``SensioFrameworkExtraBundle`` ya esté instalado: - - .. code-block:: bash - - php app/console generate:doctrine:crud --format=annotation diff --git a/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_entity.txt b/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_entity.txt deleted file mode 100644 index 306d008..0000000 --- a/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_entity.txt +++ /dev/null @@ -1,46 +0,0 @@ -Generando una nueva entidad para resguardo con *Doctrine* -========================================================= - -Usando ------- - -La orden ``generate:doctrine:entity`` genera una nueva entidad para resguardo con *Doctrine*, incluyendo la definición de asignaciones y propiedades de clase, captadores y definidores. - -De manera predeterminada, la orden se ejecuta en modo interactivo y hace preguntas para determinar el nombre del paquete, ubicación, formato y estructura de configuración predeterminada: - -.. code-block:: bash - - php app/console generate:doctrine:entity - -La orden se puede ejecutar en modo no interactivo usando la opción ``--non-interaction`` sin olvidar todas las opciones necesarias: - -.. code-block:: bash - - php app/console generate:doctrine:entity --non-interaction --entity=AcmeBlogBundle:Post --fields="title:string(100) body:text" --format=xml - -Opciones disponibles --------------------- - -* ``--entity``: El nombre de la entidad dado en notación de atajo que contiene el nombre del paquete en el que se encuentra la entidad y el nombre de la entidad. Por ejemplo: ``AcmeBlogBundle:Post``: - - .. code-block:: bash - - php app/console generate:doctrine:entity --entity=AcmeBlogBundle:Post - -* ``--fields``: La lista de campos a generar en la clase entidad: - - .. code-block:: bash - - php app/console generate:doctrine:entity --fields="titulo:string(100) cuerpo:text" - -* ``--format``: (**annotation**) [valores: ``yml``, ``xml``, ``php`` o ``annotation``] Esta opción determina el formato a usar para los archivos de configuración generados como enrutado. De manera predeterminada, la orden utiliza el formato ``anotación``: - - .. code-block:: bash - - php app/console generate:doctrine:entity --format=annotation - -* ``--with-repository``: Esta opción indica si se debe o no generar la clase *Doctrine* relacionada con ``EntityRepository``: - - .. code-block:: bash - - php app/console generate:doctrine:entity --with-repository diff --git a/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_form.txt b/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_form.txt deleted file mode 100644 index ee38233..0000000 --- a/_sources/bundles/SensioGeneratorBundle/commands/generate_doctrine_form.txt +++ /dev/null @@ -1,20 +0,0 @@ -Generando una nueva clase de tipo *Form* basada en una entidad *Doctrine* -========================================================================== - -Usando ------- - -La orden ``generate:doctrine:form`` genera una clase de tipo ``form`` básica usando los metadatos de asignación de una determinada clase ``entidad``: - -.. code-block:: bash - - php app/console generate:doctrine:form AcmeBlogBundle:Post - -Argumentos obligatorios ------------------------ - -* ``entity``: El nombre de la entidad dado en notación de atajo que contiene el nombre del paquete en el que se encuentra la entidad y el nombre de la entidad. Por ejemplo: ``AcmeBlogBundle:Post``: - - .. code-block:: bash - - php app/console generate:doctrine:form AcmeBlogBundle:Post diff --git a/_sources/bundles/SensioGeneratorBundle/index.txt b/_sources/bundles/SensioGeneratorBundle/index.txt deleted file mode 100644 index baac72e..0000000 --- a/_sources/bundles/SensioGeneratorBundle/index.txt +++ /dev/null @@ -1,36 +0,0 @@ -``SensioGeneratorBundle`` -========================= - -El paquete ``SensioGeneratorBundle`` extiende la interfaz predeterminada de la línea de ordenes de *Symfony2*, ofreciendo nuevas ordenes interactivas e intuitivas para generar esqueletos de código como paquetes, clases de formulario o controladores ``CRUD`` basados en un esquema de *Doctrine*. - -Instalando ----------- - -`Descarga`_ el paquete y ponlo bajo el espacio de nombres ``Sensio\Bundle\``. -Luego, como con cualquier otro paquete, inclúyelo en tu clase núcleo:: - - public function registerBundles() - { - $bundles = array( - ... - - new Sensio\Bundle\GeneratorBundle\SensioGeneratorBundle(), - ); - - ... - } - -Lista de ordenes disponibles ----------------------------- - -El paquete ``SensioGeneratorBundle`` viene con cuatro nuevas ordenes que puedes ejecutar en modo interactivo o no. El modo interactivo te hace algunas preguntas para configurar los parámetros para que la orden genere el código definitivo. La lista de nuevas ordenes se enumera a continuación: - -.. toctree:: - :maxdepth: 1 - - commands/generate_bundle - commands/generate_doctrine_crud - commands/generate_doctrine_entity - commands/generate_doctrine_form - -.. _`Descarga`: http://github.com/sensio/SensioGeneratorBundle \ No newline at end of file diff --git a/_sources/bundles/index.txt b/_sources/bundles/index.txt deleted file mode 100644 index c1761f1..0000000 --- a/_sources/bundles/index.txt +++ /dev/null @@ -1,16 +0,0 @@ -Paquetes de la edición estándar de *Symfony* -============================================ - -.. toctree:: - :hidden: - - SensioFrameworkExtraBundle/index - SensioGeneratorBundle/index - JMSAopBundle/index - JMSDiExtraBundle/index - JMSSecurityExtraBundle/index - DoctrineFixturesBundle/index - DoctrineMigrationsBundle/index - DoctrineMongoDBBundle/index - -.. include:: /bundles/map.rst.inc diff --git a/_sources/cmf/bundles/block.txt b/_sources/cmf/bundles/block.txt deleted file mode 100644 index 5764ebf..0000000 --- a/_sources/cmf/bundles/block.txt +++ /dev/null @@ -1,218 +0,0 @@ -``BlockBundle`` -=============== - -El `BlockBundle `_ proporciona integración con ``SonataBlockBundle``. -Este te asiste gestionando fragmentos de contenido, conocidos como bloques. Lo que hace ``BlockBundle`` es similar a lo que hace *Twig*, pero para bloques almacenados en una *BD*. De esta manera, un editor puede modificar los bloques. -Además, el ``BlockBundle`` proporciona la lógica para determinar qué bloque se tendría que dibujar en cuáles páginas. - -El ``BlockBundle`` por sí mismo no proporciona una funcionalidad de edición para bloques. No obstante, puedes encontrar ejemplos de cómo hacer editables los bloques en el `recinto de seguridad del CMF de Symfony `_. - -.. index:: BlockBundle - -Dependencias ------------- - -Este paquete está basado en el `SonataBlockBundle `_ - -.. _bundle-block-configuration: - -Configurando ------------- - -La clave de configuración para este paquete es ``symfony_cmf_block`` - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_block: - document_manager_name: default - -.. _bundle-block-document: - -Bloque documento ----------------- - -Antes de que puedas dibujar un bloque, necesitas crear un objeto que represente los datos de tu bloque en el repositorio. -Lo puedes conseguir con el siguiente fragmento de código (ten en cuenta que ``$parentPage`` necesita ser una instancia de una página definida por el `ContentBundle `_): - -.. code-block:: php - - $myBlock = new SimpleBlock(); - $myBlock->setParentDocument($parentPage); - $myBlock->setName('sidebarBlock'); - $myBlock->setTitle('My first block'); - $myBlock->setContent('Hello block world!'); - - $documentManager->persist($myBlock); - -Ten en cuenta que ``sidebarBlock`` es el identificador que escogimos para el bloque. Junto con el documento padre del bloque, esto hace único al bloque. Las otras propiedades son específicas de ``Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock``. - -.. note:: - - El sencillo bloque ahora está listo para dibujarlo, ve :ref:`bundle-block-rendering`. - -.. note:: - - Siempre asegúrate de implementar la interfaz ``Sonata\BlockBundle\Model\BlockInterface`` o un bloque de documento existente tal como ``Symfony\Cmf\Bundle\BlockBundle\Document\BaseBlock``. - -.. _bundle-block-service: - -Bloque de servicio ------------------- - -Si miras dentro de la clase ``SimpleBlock``, notarás el método ``getType``. Este define el nombre del servicio de bloque que procesa el bloque al dibujarlo. - -Un bloque de servicio contiene: - -* Un método ``execute`` -* configuración predefinida -* configuración de formulario -* configuración de caché -* activos *js* y *css* por cargar -* un método ``load`` - -Échale un vistazo a los bloques de servicio en ``Symfony\Cmf\Bundle\BlockBundle\Block`` para ver algunos ejemplos. - -.. note:: - - Asegúrate de implementar siempre la interfaz ``Sonata\BlockBundle\Block\BlockServiceInterface`` o un bloque de servicio existente tal como el ``Sonata\BlockBundle\Block\BaseBlockService``. - -.. _bundle-block-execute: - -El método ``execute`` -^^^^^^^^^^^^^^^^^^^^^ - -Este contiene el ``controlador`` lógico. Combina las opciones predefinidas en este método si lo quieres utilizar: - -.. code-block:: php - - // ... - if ($block->getEnabled()) { - // combina las opciones - $settings = array_merge($this->getDefaultSettings(), $block->getSettings()); - - $feed = false; - if ($settings['url']) { - $feed = $this->feedReader->import($block); - } - - return $this->renderResponse($this->getTemplate(), array( - 'feed' => $feed, - 'block' => $block, - 'settings' => $settings - ), $response); - } - // ... - -.. note:: - - Si tienes que usar mucha lógica, la puedes mover a un servicio específico e inyectarlo en el bloque de servicio. Luego usar ese servicio específico en el método ``execute``. - -configuración predefinida -^^^^^^^^^^^^^^^^^^^^^^^^^ - -El método ``getDefaultSettings`` contiene las opciones predefinidas de un bloque. Las opciones después se pueden alterar en múltiples sitios, reflejándose en una cascada como esta: - -* las opciones predefinidas se almacenan en el bloque de servicio -* las opciones se pueden alterar a través de los ayudantes de plantilla: - - .. code-block:: jinja - - {{ sonata_block_render({ - 'type': 'acme_main.block.rss', - 'settings': { - 'title': 'Symfony2 CMF news', - 'url': 'http://cmf.symfony.com/news.rss' - } - }) }} - -* y las opciones también se pueden alterar en un bloque de documento, la ventaja es que las opciones están almacenadas en *PHPCR* y te permiten implementar una interfaz de usuario final o de administración para cambiar algunas o todas las opciones. - -configuración de formulario -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Los métodos ``buildEditForm`` y ``buildCreateForm`` contienen configuración de formulario para editar utilizando una interfaz de usuario final o de administración. El método ``validateBlock`` contiene la configuración de validación. - -configuración de caché -^^^^^^^^^^^^^^^^^^^^^^ - -El método ``getCacheKeys`` contiene las claves utilizadas para el bloque de la memoria caché. - -*js* y *css* -^^^^^^^^^^^^ - -Puedes usar los métodos ``getJavascripts`` y ``getStylesheets`` para definir activos *js* y *css*. Usa los ayudantes de *Twig* ``sonata_block_include_javascripts`` y ``sonata_block_include_stylesheets`` para reproducirlos. - -.. code-block:: jinja - - {{ sonata_block_include_javascripts() }} - {{ sonata_block_include_stylesheets() }} - -.. note:: - - Esto reproducirá los bloques *js* y *css** de todos los bloques cargados en el contenedor de servicios de tu aplicación. - -El método ``load`` -^^^^^^^^^^^^^^^^^^ - -Puedes usar el método ``load`` para cargar datos adicionales. Este se invoca cada vez que se reproduce un bloque y antes de llamar al método ``execute``. - -.. _bundle-block-rendering: - -Dibujando el bloque -------------------- - -Para realmente reproducir el bloque del ejemplo de la sección del bloque documento, solo añade el siguiente código a tu plantilla *Twig*: - -.. code-block:: jinja - - {{ sonata_block_render({'name': 'sidebarBlock'}) }} - -Esto hará que se dibuje el ``BlockBundle`` según el bloque en cada página que tenga un bloque llamado ``'sidebarBlock'``. -Naturalmente, es necesario que la página real sea reproducida por la plantilla que contiene el fragmento anterior. - -Esto pasa al reproducir un bloque, ve las siguientes secciones para más detalles: - -* se carga un documento basándose en el nombre -* si está configurada la memorización en caché, se revisa la caché y si el contenido se encuentra se devuelve -* Cada bloque documento también tiene un servicio ``block``, se invoca al método execute: - - * aquí puedes poner lógica como en un controlador - * esta llama a una plantilla - * el resultado es un objeto Respuesta - -También Puedes :ref:`incorporar bloques en contenido ` utilizando el filtro ``cmf_embed_blocks``. - -Tipos de bloque ---------------- - -El ``BlockBundle`` viene con cuatro bloques de propósito general: - -* SimpleBlock: Un bloque sencillo con nada más que un título y un campo de hipertexto. Este normalmente sería el que un editor modificaría directamente, por ejemplo, la información de contacto -* ``ContainerBlock``: Un bloque que contiene de ``0`` a ``n`` bloques descendientes -* ``ReferenceBlock``: Un bloque que hace referencia a un bloque almacenado en algún otro lugar en el árbol de contenido. Por ejemplo, podrías desear referir partes de la información de contacto desde la página inicial -* ``ActionBlock``: Un bloque que invoca acciones de *Symfony2*. «¿Por qué utilizar esto en vez de llamar a la acción directamente desde mi plantilla?», te podrías preguntar. Bien, imagina el siguiente caso: Provees un bloque que dibuja reflexiones de tus últimas noticias. Sin embargo, no hay ninguna regla de en dónde debería aparecer. En su lugar, tu cliente quiere decidir en qué páginas se mostraría este bloque. Proporcionando un ``ActionBlock`` acorde, permites a tu cliente hacerlo sin llamarte (¡una y otra vez!) para cambiar algunas plantillas. - -Además el ``BlockBundle`` tiene bloques más específicos: - -* ``RssBlock``: Este bloque extiende el ``ActionBlock``: El bloque documento guarda la *url* del alimentador y la acción del controlador recupera elmentos del alimentador. La implementación predefinida utiliza el `EkoFeedBundle `_ para leer elementos del alimentador. - -Ejemplos --------- - -Puedes encontrar ejemplos de uso de este paquete en el `Recinto de seguridad del CMF de Symfony `_. -Dale un vistazo en el ``BlockBundle`` del recinto de seguridad. También te muestra cómo hacer editables los bloques usando el `CreateBundle `_. - -Referencia ----------- - -.. toctree:: - :maxdepth: 1 - :numbered: - - block/types - block/create_your_own_blocks - block/cache - block/relation_to_sonata_block_bundle diff --git a/_sources/cmf/bundles/block/cache.txt b/_sources/cmf/bundles/block/cache.txt deleted file mode 100644 index 7d0050e..0000000 --- a/_sources/cmf/bundles/block/cache.txt +++ /dev/null @@ -1,196 +0,0 @@ -Almacenando en caché -==================== - -El paquete ``BlockBundle`` integra con el `SonataCacheBundle `_ para proporcionar varias soluciones de almacenamiento en caché. Dale un vistazo a los adaptadores disponibles en el ``SonataCacheBundle`` para ver todas las opciones. - -El ``BlockBundle`` además proporciona sus propios adaptadores para: - -* `ESI `_ -* `SSI `_ -* Javascript asíncrono -* Javascript sincrónico - -.. note:: - - Es aconsejable almacenar todas las opciones en el bloque documento al utilizar la memoria caché. - -Dependencias ------------- - -La funcionalidad de memorización en caché es opcional y depende del `SonataCacheBundle `_. - -Instalando ----------- - -La instalación está dividida entre el *SonataCacheBundle*, el *SymfonyCmfBlockBundle* y el *SonataBlockBundle*: - -1. *SonataCacheBundle* - Sigue las instrucciones de instalación de la `documentación del SonataCacheBundle `_. -2. *SymfonyCmfBlockBundle* - Al final de tu archivo de enrutado, añade las siguientes líneas: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - # ... - # rutas a los adaptadores de caché SymfonyCmfBlockBundle - block_cache: - resource: "@SymfonyCmfBlockBundle/Resources/config/routing/cache.xml" - prefix: / - -3. *SonataBlockBundle* - Utiliza la clave ``sonata_block`` para configurar el adaptador de caché para cada bloque de servicio. - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - sonata_block: - # ... - blocks: - symfony_cmf.block.action: - # Usa el id del adaptador del servicio cache - cache: symfony_cmf.block.cache.js_async - -Flujo de trabajo ----------------- - -Lo siguiente sucede al dibujar un bloque la memoria caché: - -* se carga un documento basándose en el nombre -* si está configurada la memorización en caché, se revisa la caché y si se encuentra el contenido se devuelve -* - - * las claves de la caché se componen utilizando: - - * las claves de la caché del bloque de servicio - * las extraCacheKeys pasadas desde la plantilla - - * El adaptador de caché es pedido por un elemento caché - - * los adaptadores *ESI* y *SSI* añaden una etiqueta específica y una *URL* para recuperar el contenido de bloque - * el adaptador *Javascript* añade *javascript* y una *URL* para recuperar el contenido del bloque - - * Si el elemento caché no ha expirado y tiene datos se devuelve -* se dibuja la plantilla: - - * Para *ESI* y *SSI* la *URL* es invocada para recuperar el contenido del bloque - * para *Javascript* el navegador llama una *URL* y reemplaza un marcador de posición con el contenido devuelto del bloque - -.. note:: - - Los adaptadores de caché adicionales del *BlockBundle* siempre regresan la caché encontrada, tiene una apariencia similar a la del método ``has`` de los adaptadores en el *SonataCacheBundle* para ver cómo responden ellos. - -Si caché está marcado y el adaptador de caché indica que ninguna caché se encontró, el flujo de trabajo procede así: - -* each block document also has a block service, the execute method of it is called to render the block and return a response -* if the response is cacheable the configured adapter creates a cache element, it contains - - * the computed cache keys - * the ttl of the response - * the response - * and additional contextual keys - -* the template is rendered - -Cache keys ----------- - -The block service has the responsibility to generate the cache keys, the method ``getCacheKeys`` returns these keys, see -:ref:`bundle-block-service`. - -The block services shipped with the BlockBunde use the ``getCacheKeys`` method of the ``Sonata\BlockBundle\Block\BaseBlockService``, -and return: - -* block_id -* updated_at - -.. note:: - - If block settings need to be persisted between requests it is advised to store them in the block document. Alternatively - they can be added to the cache keys. However be very cautious because, depending on the adapter, the cache keys can be - send to the browser and are not secure. - -Extra cache keys -~~~~~~~~~~~~~~~~ - -The extra cache keys array is used to store metadata along the cache element. The metadata can be used to invalidate a -set of cache elements. - -Contextual keys -~~~~~~~~~~~~~~~ - -The contextual cache array hold the object class and id used inside the template. This contextual cache array is then -added to the extra cache key. - -This feature can be use like this ``$cacheManager->remove(array('objectId' => 'id'))``. - -Of course not all cache adapters support this feature, varnish and mongodb do. - -The BlockBundle also has a cache invalidation listener that calls the ``flush`` method of a cache adapter automatically -when a cached block document is updated or removed. - -Dibujando el bloque -------------------- - -The following parameters can be used in the ``sonata_block_render`` code in your Twig template when using cache: - -* **useCache**: use the configured cache for a block (*default*: true) -* **extraCachekeys**: expects an array with extra cache keys (*default*: empty array) - -.. code-block:: jinja - - {{ sonata_block_render( - { 'name': 'rssBlock' }, - true, - { 'extra_key': 'my_block' } - ) }} - -Adaptadores ------------ - -*ESI* -~~~~~ - -This extends the default EsiCache adapter of the SonataCacheBundle. - -Configurando -"""""""""""" - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_block: - # ... - caches: - esi: - token: a unique security key # a random one is generated by default - servers: - - varnishadm -T 127.0.0.1:2000 {{ COMMAND }} "{{ EXPRESSION }}" - -SSI -~~~ - -This extends the default SsiCache adapter of the SonataCacheBundle. - -Configurando -"""""""""""" - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_block: - # ... - caches: - ssi: - token: una clave de seguridad única # de manera predeterminada se genera una aleatoria - -*JavaScript* -~~~~~~~~~~~~ - -Renders the block using javascript, the page is loaded and not waiting for the block to be finished rendering or -retrieving data. The block is then asynchronously or synchronously loaded and added to the page. \ No newline at end of file diff --git a/_sources/cmf/bundles/block/create_your_own_blocks.txt b/_sources/cmf/bundles/block/create_your_own_blocks.txt deleted file mode 100644 index 2c8c378..0000000 --- a/_sources/cmf/bundles/block/create_your_own_blocks.txt +++ /dev/null @@ -1,95 +0,0 @@ -Creando tus propios bloques -=========================== - -Sigue estos pasos para crear un bloque: - -* Crea un bloque documento -* Crea un bloque servicio y decláralo (opcional) -* create a data object representing your block in the repository, see :ref:`bundle-block-document` -* render the block, see :ref:`bundle-block-rendering` - -Digamos que estás trabajando en un proyecto donde tienes que integrar los datos recibidos de varios alimentadores ``RSS``. -Por supuesto podrías crear un ``ActionBlock`` para cada uno de estos alimentadores, ¿pero no es tonto eso? De hecho todas estas acciones se verían similares: Recibes los datos desde un alimentador, los saneas y los pasas a una plantilla. En su lugar decides crear tu propio bloque, el ``RSSBlock``. - -Creando un bloque documento -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -La primer cosa que necesitas es un documento que contenga el dato. Es recomendable extender el ``Symfony\Cmf\Bundle\BlockBundle\Document\BaseBlock`` -contenido en este paquete (aun así no estás forzado a hacerlo, siempre y cuando implementes la ``Sonata\BlockBundle\Model\BlockInterface``). En tu documento, necesitas definir el método ``getType`` que únicamente regresa 'acme_main.block.rss'. - -.. code-block:: php - - namespace Acme\MainBundle\Document; - - use Doctrine\ODM\PHPCR\Mapping\Annotations as PHPCRODM; - use Symfony\Cmf\Bundle\BlockBundle\Document\BaseBlock; - - /** - * Rss Block - * - * @PHPCRODM\Document(referenceable=true) - */ - class RssBlock extends BaseBlock - { - public function getType() - { - return 'acme_main.block.rss'; - } - } - -Creando un servicio bloque -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Podrías elegir utilizar un servicio de bloque existente porque su configuración y lógica ya satisfacen tus necesidades. Para nuestro bloque *rss* creamos un servicio que sabe cómo manejar los ``RSSBlocks``: - -* El método ``getDefaultSettings`` configura un título, una *URL* y la cantidad máxima de elementos - - .. code-block:: php - - // ... - public function getDefaultSettings() - { - return array( - 'url' => false, - 'title' => 'Insert the rss title', - 'maxItems' => 10, - ); - } - // .. - -* the execute method passes the settings to an rss reader service and forwards the feed items to a template, see :ref:`bundle-block-execute` - -Asegúrate de implementar la ``Sonata\BlockBundle\Block\BlockServiceInterface`` o un servicio bloque existente tal cómo ``Sonata\BlockBundle\Block\BaseBlockService``. - -Define el servicio en un archivo de configuración. Es importante etiquetar tu ``BlockService`` con ``'sonata.block'``, de lo contrario el paquete no lo reconocerá. - -.. configuration-block:: - - .. code-block:: yaml - - # services.yml - acme_main.rss_reader: - class: Acme\MainBundle\Feed\SimpleReader - - sandbox_main.block.rss: - class: Acme\MainBundle\Block\RssBlockService - arguments: - - "acme_main.block.rss" - - "@templating" - - "@sonata.block.renderer" - - "@acme_main.rss_reader" - tags: - - {name: "sonata.block"} - - .. code-block:: xml - - - - - - - acme_main.block.rss - - - - \ No newline at end of file diff --git a/_sources/cmf/bundles/block/relation_to_sonata_block_bundle.txt b/_sources/cmf/bundles/block/relation_to_sonata_block_bundle.txt deleted file mode 100644 index d0fdda1..0000000 --- a/_sources/cmf/bundles/block/relation_to_sonata_block_bundle.txt +++ /dev/null @@ -1,10 +0,0 @@ -Relación al paquete ``Block`` de ``Sonata`` -------------------------------------------- - -El ``BlockBundle`` está basado en el `SonataBlockBundle `_. -Este reemplaza componentes del paquete dónde es necesario que sea compatible con *PHPCR*. - -La siguiente imagen muestra dónde utilizamos nuestros propios componentes (azules): - -.. image:: ../../images/bundles/classdiagram.jpg - :align: center \ No newline at end of file diff --git a/_sources/cmf/bundles/block/types.txt b/_sources/cmf/bundles/block/types.txt deleted file mode 100644 index 171387f..0000000 --- a/_sources/cmf/bundles/block/types.txt +++ /dev/null @@ -1,40 +0,0 @@ -Tipos de bloque -=============== - -For each purpose a different block type can be used. The general purpose blocks can be used for several solutions. -The Block Bundle ships with more specific block types. - -RSSBlock --------- - -The RssBlock extends the ActionBlock and allows you to read feed items and display them in a list. - -Create a document: - -.. code-block:: php - - $myRssBlock = new RssBlock(); - $myRssBlock->setParentDocument($parentPage); - $myRssBlock->setName('rssBlock'); - $myRssBlock->setSetting('title', 'Symfony2 CMF news'); - $myRssBlock->setSetting('url', 'http://cmf.symfony.com/news.rss'); - $myRssBlock->setSetting('maxItems', 3); - - $documentManager->persist($myRssBlock); - -Todas las opciones disponibles son: - -* **url**: the url of the rss feed (*required*) -* **title**: the title for the list (*default*: Insert the rss title) -* **maxItems**: the maximum amount of items to return to the template (*default*: 10) -* **template**: the template to render the feed items (*default*: SymfonyCmfBlockBundle:Block:block_rss.html.twig) -* **ItemClass**: La clase utilizada para el objetO elemento pasado a la plantilla (*predefinido*: Symfony\Cmf\Bundle\BlockBundle\Model\FeedItem) - -The controller to get the feed items can also be changed: - -* define a different class for the controller service in your configuration using the DI service parameter ``symfony_cmf_block.rss_controller_class`` -* or set the actionName of your RssBlock document - -.. note:: - - El `Symfony CMF Sandbox `_ contiene un ejemplo del ``RssBlock``. \ No newline at end of file diff --git a/_sources/cmf/bundles/blog.txt b/_sources/cmf/bundles/blog.txt deleted file mode 100644 index ea6e950..0000000 --- a/_sources/cmf/bundles/blog.txt +++ /dev/null @@ -1,135 +0,0 @@ -``BlogBundle`` -============== - -El objetivo de este paquete es proporcionar todo lo que necesitas para crear un *blog* completo o sistema estilo *blog*. También incluye soporte integrado para el paquete ``Sonata Admin`` para ayudarte a levantar y correr rápidamente. - -Características actuales: - -* Host multiple blogs within a single website. -* Place blogs anywhere within your route hierarchy. -* Sonata Admin integration. - -Pending features: - -* Full tag support -* Frontend pagination (using knp-paginator) -* RSS/ATOM feed -* Comments -* User support (FOSUserBundle) - -Dependencias ------------- - -* :doc:`SymfonyCmfRoutingExtraBundle` is used to manage the routing. -* :doc:`PHPCR-ODM` is used to persist the bundles documents. - -Configurando ------------- - -The default configuration will work with the ``cmf-sandbox`` but you will probably -need to cusomize it to fit your own requirements. - -Parameters: - -* **routing_post_controller** - specifies which controller to use for showing posts. -* **routing_post_prefix** - this is the part of the URL which "prefixes" the post slug - e.g. with the default value the following post URL might be generated: ``http://example.com/my-blog/posts/this-is-my-post`` -* **blog_basepath** - *required* Specify the path where the blog content should be placed. -* **routing_basepath** - *required* Specify the basepath for the routing system. - -Ejemplo: - -.. code-block:: yaml - - symfony_cmf_blog: - routing_post_controller: symfony_cmf_blog.blog_controller:viewPost - routing_post_prefix: posts - blog_basepath: /cms/content - routing_basepath: /cms/routes - -.. note:: - - In the BlogBundle the controller is a *service*, and is referenced as such. You can - of course specify a controller using the standard `MyBundle:Controller:action` - syntax. See `controllers as services `_ in the official sf2 docs. - -Enrutando -~~~~~~~~~ - -To enable the routing system to automatically forward requests to the blog -controller when a Blog content is associated with a route, add the following -under the ``controllers_by_class`` section of ``symfony_cmf_routing_extra`` -in ``app/config/config.yml``: - -.. code-block:: yaml - - symfony_cmf_routing_extra: - ... - dynamic: - ... - controllers_by_class: - ... - Symfony\Cmf\Bundle\BlogBundle\Document\Blog: symfony_cmf_blog.blog_controller:listAction - -``Sonata Admin`` -~~~~~~~~~~~~~~~~ - -The ``BlogBundle`` has admin services defined for Sonata Admin, to make the blog -system visible on your dashboard, add the following to the -``sonata_admin`` section: - -.. code-block:: yaml - - sonata_admin: - ... - dashboard: - groups: - ... - blog: - label: blog - items: - - symfony_cmf_blog.admin - - symfony_cmf_post.admin - -Tree Browser Bundle -~~~~~~~~~~~~~~~~~~~ - -If you use the Symfony CMF Tree Browser bundle you can expose the blog routes -to enable blog edition from the tree browser. Expose the routes in the -``fos_js_routing`` section of ``app/config/config.yml``: - -.. code-block:: yaml - - fos_js_routing: - routes_to_expose: - ... - - admin_bundle_blog_blog_create - - admin_bundle_blog_blog_delete - - admin_bundle_blog_blog_edit - -Integration ------------ - -Templating -~~~~~~~~~~ - -The default templates are marked up for `Twitter Bootstrap `_. -But it is easy to completely customize the templates by **overriding** them. - -The one template you will have to override is the default layout, you will need -to change it and make it extend your applications layout. The easiest way to do -this is to create the following file: - -.. code-block:: jinja - - {# /app/Resources/SymfonyCmfBlogBundle/views/default_layout.html.twig #} - - {% extends "MyApplicationBundle::my_layout.html.twig" %} - - {% block content %} - {% endblock %} - -The blog will now use ``MyApplicationBundle::my_layout.html.twig`` instead of -``SymfonyCmfBlogBundle::default_layout.html.twig``. - -See `Overriding Bundle Templates `_ in the Symfony documentation for more information. diff --git a/_sources/cmf/bundles/content.txt b/_sources/cmf/bundles/content.txt deleted file mode 100644 index 62e65a1..0000000 --- a/_sources/cmf/bundles/content.txt +++ /dev/null @@ -1,31 +0,0 @@ -``ContentBundle`` -================= - -Este paquete proporciona un documento para contenido estático y el controlador para dibujarlo. - -Para una introducción ve la sección «Cómo empezar» de la página :doc:`../getting-started/content`. - -.. index:: ContentBundle - -Configurando ------------- - -La clave de configuración para este paquete es ``symfony_cmf_content`` - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_content: - admin_class: ~ - document_class: ~ - default_template: ~ - content_basepath: /cms/content - static_basepath: /cms/content/static - use_sonata_admin: auto - multilang: # la sección multilang completa es opcional - admin_class: ~ - document_class: ~ - use_sonata_admin: auto - locales: [] # si usas multilang, tienes que definir por lo menos una región diff --git a/_sources/cmf/bundles/core.txt b/_sources/cmf/bundles/core.txt deleted file mode 100644 index 3a1ca22..0000000 --- a/_sources/cmf/bundles/core.txt +++ /dev/null @@ -1,77 +0,0 @@ -``CoreBundle`` -============== - -Este es el `CoreBundle `_ para el ``CMF`` de *Symfony 2*. Este paquete proporciona funcionalidad común, ayudantes y utilidades para los otros paquetes del ``CMF``. - -Una de las características proporcionadas es una interfaz e implementación de un supervisor del flujo de trabajo para la publicación con una interfaz acompañante en la cual los modelos pueden implementar aquello que quieran apoyar con este supervisor. - -Además, proporciona un ayudante de *Twig* que expone varias funciones útiles para que las plantillas de *Twig* interaccinen con documentos *PHPCR-ODM*. - -.. index:: CoreBundle, PHPCR, ODM, publish workflow - -Configurando ------------- - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_core: - document_manager: default - role: IS_AUTHENTICATED_ANONYMOUSLY # usado por el supervisor del - # flujo de trabajo - # para publicación - -Supervisor del flujo de trabajo para publicación ------------------------------------------------- - -El paquete proporciona un servicio ``symfony_cmf_core.publish_workflow_checker`` que implementa la ``PublishWorkflowCheckerInterface``. Esta interfaz define un solo método ``checkIsPublished()``. - -.. code-block:: php - - $publishWorkflowChecker = $container->get('symfony_cmf_core.publish_workflow_checker'); - $ignoreRole = false // si ignora el rol al momento de decidir si considera - // el documento como publicado - if ($publishWorkflowChecker->checkIsPublished($document, $ignoreRole)) { - .. - } - -Extensión *Twig* ----------------- - -Implementa las siguientes funciones: - -* ``cmf_find``: Encuentra el documento por medio de la ruta y clase proporcionadas -* ``cmf_is_published``: Comprueba si el documento proporcionado está publicado -* ``cmf_prev``: Devuelve el anterior documento publicado examinando los nodos hijos del padre proporcionado -* ``cmf_next``: Devuelve el siguiente documento publicado examinando los nodos hijos del padre proporcionado -* ``cmf_children``: Devuelve un arreglo con todos los documentos hijo de los documentos proporcionados que están publicados -* ``cmf_document_locales``: Obtiene las regiones del documento proporcionado - -.. code-block:: jinja - - {% set page = cmf_find('/alguna/ruta') %} - - {% if cmf_is_published(page) %} - {% set prev = cmf_prev(page) %} - {% if prev %} - prev - {% endif %} - - {% set next = cmf_next(page) %} - {% if next %} - next - {% endif %} - - {% for news in cmf_children(page)|reverse %} -
  • {{ news.title }} ({{ news.publishStartDate | date('Y-m-d') }})
  • - {% endfor %} - - {% if 'de' in cmf_document_locales(page) %} - DE - {% endif %} - {% if 'fr' in cmf_document_locales(page) %} - DE - {% endif %} - {% endif %} diff --git a/_sources/cmf/bundles/create.txt b/_sources/cmf/bundles/create.txt deleted file mode 100644 index fed964c..0000000 --- a/_sources/cmf/bundles/create.txt +++ /dev/null @@ -1,323 +0,0 @@ -``CreateBundle`` -================ - -El `CreateBundle `_ integra a *Symfony 2* las bibliotecas ayudante :file:`create.js` y :file:`createphp`. - -:file:`create.js` es una comprensible interfaz de edición *web* para Sistemas de administración de contenido (*CMS*). Está diseñado para proporcionar un moderno gestor de contenido, totalmente basado en un entorno de navegadores HTML5. Creado para poder adaptarlo para trabajar en casi cualquier interfaz de administración de contenido. -Ve http://createjs.org/ - -``Createphp`` es una biblioteca *PHP* para ayudarte con anotaciones *RDF* en tus entidades/documentos. -Ve la documentación sobre como trabaja en https://github.com/flack/createphp. - -.. index:: CreateBundle - -Dependencias ------------- - -Este paquete incluye :file:`create.js` (el cuál empaca todas sus dependencias como *jquery*, *vie*, *hallo*, *backbone*, etc.) como submódulos *git*. No olvides añadir el guión del controlador de ``composer`` a tu archivo :file:`composer.json` como se describe abajo. - -Las dependencias de *PHP* son manejadas por medio de ``composer``. Utilizamos ``createphp`` así como ``AsseticBundle``, ``FOSRestBundle`` y por inferencia también ``JmsSerializerBundle``. Asegúrate de crear una instancia de todos estos paquetes en tu nucleo y de configurar correctamente el ``assetic``. - -Instalando ----------- - -Este paquete se incluye mejor utilizando ``Composer``. - -Edita el archivo ``composer`` de tu proyecto añadiendo un nuevo requisito para ``symfony-cmf/create-bundle``. -Luego, crea una sección de guiones o añádelo a una existente: - - -.. code-block:: yaml - - { - "scripts": { - "post-install-cmd": [ - "Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::initSubmodules", - ... - ], - "post-update-cmd": [ - "Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::initSubmodules", - ... - ] - } - } - - -Añade este paquete (y sus dependencias, si aún no están) al núcleo de tu aplicación: - -.. code-block:: php - - // application/ApplicationKernel.php - public function registerBundles() - { - return array( - // ... - new Symfony\Bundle\AsseticBundle\AsseticBundle(), - new JMS\SerializerBundle\JMSSerializerBundle($this), - new FOS\RestBundle\FOSRestBundle(), - new Symfony\Cmf\Bundle\CreateBundle\SymfonyCmfCreateBundle(), - // ... - ); - } - -También necesitas configurar el ``FOSRestBundle`` para manejar *json*: - - -.. code-block:: yaml - - fos_rest: - view: - formats: - json: true - -Concepto --------- - -``Createphp`` usa metadatos *RDF* sobre las clases de tu dominio, tal como *Doctrine* -sabe cómo se almacenan los metadatos de un objeto en la base de datos. Los metadatos son modelados por el tipo de la clase y pueden provenir de cualquier fuente. ``Createphp`` proporciona controladores de metadatos que leen *XML*, arreglos *PHP* y uno que justo introspecciona objetos y crea metadatos no semánticos que serán suficientes para editar con :file:`create.js`. - -El ``RdfMapper`` se usa para traducir entre tu capa de almacenamiento y ``createphp``. -Este es pasado al objeto ``dominio`` y al objeto metadatos pertinente. - -Con los metadatos y el ayudante de *Twig*, el contenido es dibujado con anotaciones *RDF*. :file:`create.js` es cargado y habilitado para editar las entidades. Guarda en la interfaz de administración las operaciones que ocurrieron en llamadas *ajax*. - -El controlador ``REST`` maneja esas llamadas *ajax*, y si quieres ser capaz de cargar imágenes, un controlador de imagen guarda las imágenes cargadas y devuelve la ubicación -de la imagen. - - -Configurando ------------- - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_create: - # cargando metadatos - - # Lista de directorios para buscar metadatos - rdf_config_dirs: - - "%kernel.root_dir%/Resources/rdf-mappings" - # busca asociaciones en /Resources/rdf-mappings - # auto_mapping: true - - # usa una clase diferente para el controlador REST - # rest_controller_class: FQN\Classname - - # manejo de imagen - imagen: - model_class: ~ - controller_class: ~ - - # rol para controlar el acceso para inclusión js, predefinido - # REST y controladores de imagen - # role: IS_AUTHENTICATED_ANONYMOUSLY - - # Habilita el asignador PHPCR-ODM de Doctrine - phpcr_odm: true - - # asociación de tipo rdf name => class nombre usado al añadir ítems a colecciones - map: - rdfname: FQN\Classname - - # url para mejora semántica stanbol, de lo contrario la - # predeterminada es la instalación del demo - # stanbol_url: http://dev.iks-project.eu:8081 - - # fija la barra de edición Hallo en lo alto de la página - # fixed_toolbar: true - - # los tipos RDF para elementos a modificar en texto plano - # plain_text_types: ['dcterms:title'] - - # los tipos RDFa para crear las rutas correspondientes después de - # añadido el contenido de estos tipos con Create.js. Esto no - # es necesario con el SimpleCmsBundle, porque el contenido y las - # rutas están en el mismo árbol del repositorio. - # create_routes_types: ['http://schema.org/NewsArticle'] - -El archivo javascript proporcionado configura :file:`create.js` y el editor «hallo». Activa algunos complementos como el editor de etiquetas para revisar colecciones de -atributos ``skos:related``. Esperamos poder añadir algunas opciones de configuración para modificar la configuración de :file:`create.js` pero también puedes utilizar el archivo como una plantilla y hacer la propia si necesitas mayor personalización. - - -Metadatos -+++++++++ - -:file:`createphp` necesita información de metadatos para cada clase de tu modelo del dominio. Por omisión, ``crea paquete`` usa el controlador de metadatos *XML* y busca los metadatos en los paquetes habilitados en /Resources/rdf-mappings. Si utilizas un paquete que no tiene ninguna asignación ``RDF``, puedes especificar una lista de ``rdf_config_dirs`` que se analizará adicionalmente para buscar metadatos. - -Consulta la documentación de `createphp `_ para el formato de los metadatos *XML*. - - -Controlando el acceso -+++++++++++++++++++++ - -Si utilizas el controlador ``REST`` predefinido, todos pueden editar el contenido una vez activado el ``crea paquete``. Para restringir el acceso, especifica un rol para el paquete que no sea el predefinido -``IS_AUTHENTICATED_ANONYMOUSLY``. -Si especificas un rol diferente, :file:`create.js` sólo se cargará si el usuario tiene ese rol y el controlador ``REST`` (y el controlador de imagen si está habilitado) revisará el rol. - -Si necesitas un control de acceso más fino, busca en el método asignador ``isEditable``. -Puedes extender el asignador a utilizar y sustituir ``isEditable`` para que responda si el objeto dominio que se le pasa es editable. - - -Manejando imágenes -++++++++++++++++++ - -Activa el controlador de imágenes simplista con la configuración ``image > model_class | controller_class``. Este controlador de imagen sólo despacha imágenes existentes en el repositorio ``PHPCR-ODM`` y también las sirve en peticiones. - -Si necesitas un diferente tratamiento de imágenes, puedes sobrescribir ``image.model_class`` y/o ``image.controller_class``, o implementar un servicio personalizado con ``ImageController`` y sustituir el ``symfony_cmf_create.image.controller``. - - -Asignación de peticiones a objetos -++++++++++++++++++++++++++++++++++ - -Por ahora, el paquete sólo proporciona un servicio para asociar al *PHPCR-ODM* de *Doctrine*. Activado al establecer ``phpcr_odm`` en ``true``. Si necesitas algo más, debes proporcionar un servicio ``symfony_cmf_create.object_mapper``. (Si necesitas un contenedor para el *ORM* de *Doctrine*, mira los asignadores de la biblioteca ``createphp`` y haz una petición de extracción en la biblioteca, y otra para exponer el *ORM* como asignador del servicio en el ``crea paquete``). - -Además, ten en cuenta que ``createphp`` apoyaría diferentes asignadores para diferentes tipos de ``RDF``. -Si necesitas esto, escarva un poco en ``createphp`` y ``crea paquete`` y haz una petición de extracción para activar esa característica. - -Para poder crear nuevos objetos, es necesario proporcionar una asociación entre los tipos ``RDF`` y los nombres de clase. (Pendiente: ¿no podemos indexar todas las asignaciones y hacer esto automáticamente?) - - -Enrutando -+++++++++ - -Por último añade la ruta correspondiente a tu configuración: - -.. configuration-block:: - - .. code-block:: yaml - - create: - resource: "@SymfonyCmfCreateBundle/Resources/config/routing/rest.xml" - create_image: - resource: "@SymfonyCmfCreateBundle/Resources/config/routing/image.xml" - - .. code-block:: xml - - - - -.. _bundle-create-usage-embed: - -Usando ------- - -Ajusta tu plantilla para cargar los archivos ``js`` del editor si la sesión actual tiene permitido editar el contenido. - -If you are using Symfony 2.2 or higher: - -.. code-block:: jinja - - {% render(controller("symfony_cmf_create.jsloader.controller:includeJSFilesAction")) with {'_locale': app.request.locale} %} - -For versions prior to 2.2, this will do: - -.. code-block:: jinja - - {% render "symfony_cmf_create.jsloader.controller:includeJSFilesAction" with {'_locale': app.request.locale} %} - -Además, asegúrate de que ``assetic`` está reescribiendo rutas en tus archivos *css*, y luego incluye los archivos *css* base (y personalízalos con tu propio *css* cuando sea necesario) con: - -.. code-block:: jinja - - {% include "SymfonyCmfCreateBundle::includecssfiles.html.twig" %} - -La otra cosa que tienes que hacer es proporcionar asignación ``RDF`` para las clases de tu modelo y ajusta tus plantillas para reproducir con ``createphp`` a modo de que :file:`create.js` sepa cual contenido es editable. - -Crea la asignación de metadatos *XML* en ``/Resources/rdf-mappings/`` o una ruta -a tu ``rdf_config_dirs`` configurado después del nombre completamente cualificado de tus clases del modelo sustituyendo ``\\`` con un punto (``.``), es decir, :file:`Symfony.Cmf.Bundle.SimpleCmsBundle.Document.MultilangPage.xml`. -Para un ejemplo de asociación ve los archivos en el :file:`cmf-sandbox`. La documentación de referencia está en el `repositorio de la biblioteca createphp `_. - -Para dibujar tu modelo, usa la etiqueta ``createphp`` de *Twig*: - -.. code-block:: html+jinja - - {% createphp page as="rdf" %} - {{ rdf|raw }} - {% endcreatephp %} - -O si necesitas más control sobre el *HTML* generado: - -.. code-block:: html+jinja - - {% createphp page as="rdf" %} -
    -

    {{ createphp_content( rdf.title ) }}

    -
    {{ createphp_content( rdf.body ) }}
    -
    - {% endcreatephp %} - - -Editores alternativos -+++++++++++++++++++++ - -You can write your own templates to load a javascript editor. They have to -follow the naming pattern ``SymfonyCmfCreateBundle::includejsfiles-%editor%.html.twig`` -to be loaded. In the includeJSFilesAction, you specify the editor parameter. -(Do not forget to add the ``controller`` call around the controller name inside -``render`` for Symfony 2.2, as in the example above.) - - {% render "symfony_cmf_create.jsloader.controller:includeJSFilesAction" with {'editor': 'aloha', '_locale': app.request.locale } %} - -.. note:: - - Create.js has built in support for Aloha and ckeditor, as well as the - default hallo editor. Those should be supported by the CreateBundle as well. - See these github issue for `ckeditor `_ - and `alhoa `_ integration. - - If you wrote the necessary code for one of those editors, or another editor - that could be useful for others, please send a pull request. - - -Desarrollando el editor ``wysiwyg`` ``hallo`` ---------------------------------------------- - -Puedes desarrollar el editor ``hallo`` dentro del ``Crea paquete``. De manera predefinida, se usa una versión minimizada del ``hallo`` empacada con ``Crea paquete``. Para desarrollar el código real, primero necesitas activar con ``checkout`` el repositorio ``hallo`` completo. Lo puedes hacer ejecutando la siguiente orden desde la línea de ordenes: - -.. code-block:: bash - - app/console cmf:create:init-hallo-devel - - -There is a special template to load the coffee script files. To load this, -just use the ``hallo-coffee`` editor with the includeJSFilesAction. -(Do not forget to add the ``controller`` call around the controller name inside -``render`` for Symfony 2.2, as in the example above.) - -.. code-block:: jinja - - {% render "symfony_cmf_create.jsloader.controller:includeJSFilesAction" with {'editor': 'hallo-coffee', '_locale': app.request.locale } %} - - -The hallo-coffee template uses assetic to load the coffee script files from -``Resources/public/vendor/hallo/src``, rather than the precompiled javascript -from ``Resources/public/vendor/create/deps/hallo-min.js``. This also means that -you need to add a mapping for coffeescript in your assetic configuration and -you need the `coffee compiler set up correctly `_. - -.. configuration-block:: - - .. code-block:: yaml - - assetic: - filters: - cssrewrite: ~ - coffee: - bin: %coffee.bin% - node: %coffee.node% - apply_to: %coffee.extension% - -In the cmf sandbox we did a little hack to not alwas trigger coffee script compiling. -En el archivo :file:`config.yml` hacemos configurable la extensión de ``coffee``. Ahora si :file:`parameters.yml` pone ``coffee.extension`` a ``\.coffee`` el ``coffeescript`` es compilado y el compilador de ``coffee`` debe estar instalado. Si lo pusiste a cualquier otra cosa tal como ``\.nocoffee`` entonces no es necesario tener instalado el compilador de ``coffee``. - -Los valores predefinidos para los tres parámetros son: - -.. configuration-block:: - - .. code-block:: yaml - - coffee.bin: /usr/local/bin/coffee - coffee.node: /usr/local/bin/node - coffee.extension: \.coffee diff --git a/_sources/cmf/bundles/doctrine_phpcr_admin.txt b/_sources/cmf/bundles/doctrine_phpcr_admin.txt deleted file mode 100644 index 7392fca..0000000 --- a/_sources/cmf/bundles/doctrine_phpcr_admin.txt +++ /dev/null @@ -1,41 +0,0 @@ -``SonataDoctrinePhpcrAdminBundle`` -================================== - -El `SonataDoctrinePhpcrAdminBundle `_ proporciona integración con el ``SonataAdminBundle`` para habilitar fácilmente la creación de interfaces de usuario. - -.. index:: TreeBundle - -Dependencias ------------- - -* `SonataAdminBundle `_ -* `TreeBundle `_ - -Configurando ------------- - -.. configuration-block:: - - .. code-block:: yaml - - sonata_doctrine_phpcr_admin: - templates: - form: - - # Predefinido: - - SonataDoctrinePHPCRAdminBundle:Form:form_admin_fields.html.twig - filter: - - # Predefinido: - - SonataDoctrinePHPCRAdminBundle:Form:filter_admin_fields.html.twig - types: - list: - - # Prototipo - name: [] [] - document_tree: - # Prototipo - class: # name of the class - # class names of valid children, manage tree operations for them and hide other children - valid_children: [] - image: \ No newline at end of file diff --git a/_sources/cmf/bundles/menu.txt b/_sources/cmf/bundles/menu.txt deleted file mode 100644 index f1f014d..0000000 --- a/_sources/cmf/bundles/menu.txt +++ /dev/null @@ -1,94 +0,0 @@ -``MenuBundle`` -============== - -El `MenuBundle `_ -proporciona menús desde un gestor de objetos de *Doctrine* con la ayuda de ``KnpMenuBundle``. - -.. index:: MenuBundle - -Dependencias ------------- - -Este paquete se extiende del `KnpMenuBundle `_. - -A menos que cambies los predefinidos y proporciones tus propias implementaciones, este paquete también depende de: - -* ``SymfonyRoutingExtraBundle`` para el servicio de enrutado ``symfony_cmf_routing_extra.dynamic_router``. - Ten en cuenta que necesitas habilitar explícitamente el enrutador dinámico puesto que de manera predeterminada no se carga. - Ve la :doc:`documentación del paquete routing extra ` para saber cómo hacer esto. -* :doc:`PHPCR-ODM ` para cargar documentos de ruta desde el contenido del repositorio - -Configurando ------------- - -Si quieres utilizar las configuraciones predefinidas, no debes cambiar nada. -Los valores son: - -.. configuration-block:: - - .. code-block:: yaml - - symfony_cmf_menu: - menu_basepath: /cms/menu - document_manager_name: default - admin_class: ~ - document_class: ~ - content_url_generator: router - content_key: ~ # (resuelve a DynamicRouter::CONTENT_KEY) - route_name: ~ # las rutas cmf son creadas por el contenido en lugar del nombre - content_basepath: ~ # por omisión a symfony_cmf_core.content_basepath - use_sonata_admin: auto # usa true/false para forzar si usar / no usar - # la administración de sonata - multilang: # la sección multilang completa es opcional - use_sonata_admin: auto # usa true/false para forzar el usar / no usar - # la administración de sonata - admin_class: ~ - document_class: ~ - locales: [] # si usas multilang, tienes que definir por lo menos una región - -Si quieres dibujar el menú desde *Twig*, asegúrate de no desactivar *Twig* en la sección de configuración ``knp_menu``. - -Si añades ``sonata-project/doctrine-phpcr-admin-bundle`` a la sección de requisitos del archivo :file:`composer.json`, los documentos del menú son expuestos en el ``SonataDoctrinePhpcrAdminBundle``. -Para instrucciones sobre cómo configurar este paquete ve :doc:`doctrine_phpcr_admin`. - -De manera predeterminada, ``use_sonata_admin`` se configura automáticamente basándose en si ``SonataDoctrinePhpcrAdminBundle`` está disponible, pero lo puedes desactivar explícitamente al incluirlo si ``sonata`` está habilitado, o habilitarlo explícitamente para obtener un error si se desactiva ``sonata``. - - -Entradas del menú ------------------ - -El documento ``MenuItem`` define entradas del menú. Puedes construir elementos del menú basándote en las rutas de *Symfony*, *url* absolutas o relativas o refiriendo documentos de contenido *PHPCR-ODM*. - -El árbol del menú se construye desde los documentos bajo ``[menu_basepath]/[menuname]``. Puedes utilizar diferentes clases de documento para elementos del menú, siempre y cuando implementen la ``Knp\Menu\NodeInterface`` para integrarse con ``KnpMenuBundle``. The default ``MenuNode`` -document discards children that do not implement this interface. - -La entrada resaltada actualmente es determinada al comprobar si el contenido asociado con un documento menú es igual que el contenido del ``DynamicRouter`` puesto en la petición. - - -Usando ------- - -Ajusta tu plantilla *Twig* para cargar el menú. - -.. code-block:: jinja - - {{ knp_menu_render('simple') }} - -El nombre del menú es el nombre del nodo bajo ``menu_basepath``. Por ejemplo si tu repositorio almacena los nodos del menú bajo ``/cms/menu``, dibujar «principal» significaría dibujar el menú que está en ``/cms/menu/principal`` - - -Cómo usar otros componentes no predefinidos -------------------------------------------- - -Si utilizas el menú del ``cmf`` con *PHPCR-ODM*, solo necesitas almacenar documentos de Ruta bajo ``menu_basepath``. Si utilizas un diferente gestor de objetos, debes asegúrarte que el documento raíz del menú se encuentra con: - -.. code-block:: php - - $dm->find($menu_document_class, $menu_basepath . $menu_name) - -The route document must implement ``Knp\Menu\NodeInterface`` - see -``MenuNode`` document for an example. Probablemente también necesites especificar la clase ``menu_document_class``, puesto que *PHPCR-ODM* sólo puede determinar el documento a partir del contenido en la base de datos. - -Si utilizas el menú ``CMF`` con el ``DynamicRouter``, no necesitas ningún nombre de ruta como el documento de menú, sólo debes proporcionar un campo ``content_key`` en las opciones. -Si quieres utilizar un diferente servicio para generar las *URL*, te debes asegurar de que tus entradas del menú proporcionan información en tu ``content_key`` seleccionada que el generador de la *url* pueda utilizar para generar la *url*. Dependiendo de tu generador, también podrías necesitar especificar un ``route_name``. -Ten en cuenta que si solo quieres generar rutas *Symfony* normales con un menú que está en la base de datos, puedes pasar el servicio de enrutado al núcleo como el ``content_url_generator``, asegúrate que el ``content_key`` nunca coincida y haz que tus documentos de menú proporcionen el nombre de ruta y eventuales ``routeParameters``. \ No newline at end of file diff --git a/_sources/cmf/bundles/phpcr-odm.txt b/_sources/cmf/bundles/phpcr-odm.txt deleted file mode 100644 index b49fb9f..0000000 --- a/_sources/cmf/bundles/phpcr-odm.txt +++ /dev/null @@ -1,758 +0,0 @@ -``DoctrinePHPCRBundle`` -======================= - -El `DoctrinePHPCRBundle `_ proporciona integración con el repositorio de contenido *PHP* y opcionalmente con el *PHPCR-ODM* de *Doctrine* -para proporcionar el gestor de documentos *ODM* en *Symfony*. - -Fuera de la caja, este paquete apoya las siguientes implementaciones *PHPCR*: - -* `Jackalope `_ (ambos transportes jackrabbit y doctrine-dbal) -* `Midgard2 `_ - - -.. index:: DoctrinePHPCRBundle, PHPCR, ODM - -.. tip:: - - Esta referencia sólo explica la integración de *Symfony2* con *PHPCR* y *PHPCR-ODM*. Para aprender cómo utilizar *PHPCR* ve al `sitio web de PHPCR `_ y para la documentación del `PHPCR-ODM `_ de *Doctrine* . - -Este paquete está basado en el ``AbstractDoctrineBundle`` y por lo tanto es similar a la configuración de los paquetes *ORM* y *MongoDB* de *Doctrine*. - -Configurando ------------- - -Ve :doc:`../tutorials/installing-configuring-doctrine-phpcr-odm` - - -Configurando ------------- - -.. tip:: - - Si sólo quieres utilizar el *PHPCR* sencillo sin el *PHPCR-ODM*, sencillamente no configures la sección ``odm`` para evitar que cargue los servicios. Ten en cuenta que muchos paquetes predefinidos del *CMF* usan documentos *PHPCR-ODM* y por ello necesitas habilitar el *ODM*. - - -Configurando la sesión *PHPCR* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La sesión necesita que especifiques una implementación *PHPCR* en la sección ``backend`` para el campo ``type``, junto con opciones de configuración al arranque de la implementación. Actualmente apoyamos ``jackrabbit``, ``doctrinedbal`` y ``midgard2``. -Independientemente de la interfaz de administración, cada sesión *PHPCR* necesita un espacio de trabajo, nombre de usuario y contraseña. - -.. tip:: - - Cada implementación de *PHPCR* debería proporcionar el espacio de trabajo llamado ``«default»``, pero puedes elegir uno diferente. Existe la orden ``doctrine:phpcr:workspace:create`` para iniciar un nuevo espacio de trabajo. See also :ref:`bundle-phpcr-odm-commands`. - -El nombre de usuario y contraseña que especifiques aquí son los utilizados en la capa *PHPCR* en -``PHPCR\SimpleCredentials``. Normalmente serán diferentes del nombre de usuario y contraseña utilizados por *Midgard2* o el *DBAL* de *Doctrine* para conectar al *RDBMS* subyacente donde de hecho se almacenan los datos. - -Si estás utilizando una de las interfaces de administración de ``Jackalope``, también puedes especificar ``opciones``. -Estas serán puestas en la sesión de ``Jackalope``. Actualmente esto se puede ajustar a nodos prerecuperados poniendo ``jackalope.fetch_depth`` a algo más grande que 0. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - # ve abajo cómo configurar la interfaz de administración que elejiste - workspace: default - username: admin - password: admin - ## ajusta opciones para jackrabbit y doctrinedbal (todas las versiones de jackalope) - # options: - # 'jackalope.fetch_depth': 1 - - - -Sesión *PHPCR* con ``Jackalope Jackrabbit`` -""""""""""""""""""""""""""""""""""""""""""" - -Es la única configuración requerida para instalar el ``Jackrabbit`` de apache (ve :ref:`instalando Jackrabbit `). - -La configuración necesita que el parámetro ``url`` apunte a tu ``jackrabbit``. Además puedes afinar algunas otras opciones específicas de ``jackrabbit``, por ejemplo para utilizarlas en la configuración de una carga balanceada o para fallar temprano y aminorar el costo de algunos viajes de ida y vuelta a la interfaz de administración. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - type: jackrabbit - url: http://localhost:8080/server/ - ## únicamente jackrabbit, opcional. ve https://github.com/jackalope/jackalope/blob/master/src/Jackalope/RepositoryFactoryJackrabbit.php - # default_header: ... - # expect: 'Esperaba: 100-continue' - # habilita si de cualquier manera quieres tener una excepción si - # falla el incio de sesión PHPCR - # check_login_on_server: false - # habilita si experimentas fallos de segmentación mientras - # trabajas con datos binarios en documentos - # disable_stream_wrapper: true - # habilita si no quieres usar transacciones y tampoco quieres - # que el odm use transacciones automáticamente, es altamente - # recomendable NO deshabilitar las transacciones - # disable_transactions: true - -.. _bundle-phpcr-odm-doctrinedbal: - -Sesión *PHPCR* con el ``Jackalope`` y *DBAL* de *Doctrine* -"""""""""""""""""""""""""""""""""""""""""""""""""""""""""" - -Este tipo usa ``Jackalope`` con una abstracción en la capa de transporte de base de datos de *Doctrine* para proporcionar *PHPCR* sin ningún requisito de instalación más allá de cualquiera de los *RDBMS* apoyados por *Doctrine*. - -Necesitas configurar una conexión de *Doctrine* según el *DBAL* elejido en -la `documentación de Doctrine en Symfony2 `_. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - type: doctrinedbal - connection: doctrine.dbal.default_connection - # habilita si quieres tener una excepción inmediatamente si falla - # el inicio de sesión PHPCR - # check_login_on_server: false - # habilita si experimentas fallos de segmentación mientras - # trabajas con datos binarios en documentos - # disable_stream_wrapper: true - # habilita si no quieres usar transacciones y tampoco quieres - # que el odm use transacciones automáticamente, es altamente - # recomendable NO deshabilitar las transacciones - # disable_transactions: true - - -Una vez configurada la conexión, puedes crear la base de datos y *debes* iniciar la base de datos con la orden ``doctrine:phpcr:init:dbal``. - -.. code-block:: bash - - app/console doctrine:database:create - app/console doctrine:phpcr:init:dbal - -.. tip:: - - Naturalmente, también puedes utilizar una diferente conexión en vez de la predefinida. - Es recomendable utilizar una conexión independiente para una base de datos separada si también *Doctrine* usa el acceso a datos del *ORM* o *DBAL* directamente, en lugar de mezclar este dato con las tablas generadas por ``jackalope-doctrine-dbal``. - Si tienes una conexión separada, necesitas pasar el nombre de la conexión alterna a la orden ``doctrine:database:create`` con la opción ``--connection``. Para las ordenes *PHPCR* de *Doctrine*, este parámetro no es necesario al configurar la conexión a utilizar. - - -Sesión *PHPCR* con ``Midgard2`` -""""""""""""""""""""""""""""""" - -``Midgard2`` es una aplicación que proporciona una extensión *PHP* compilada. Esta implementa la *API* de *PHPCR* en lo alto de un *RDBMS* estándar. - -To use the Midgard2 PHPCR provider, you must have both the `midgard2 PHP extension `_ -and `the midgard/phpcr package `_ installed. -The settings here correspond to Midgard2 repository parameters as explained in `the getting started document `_. - -La sesión de configuración de la interfaz de administración es la siguiente: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - type: midgard2 - db_type: MySQL - db_name: midgard2_test - db_host: "0.0.0.0" - db_port: 3306 - db_username: "" - db_password: "" - db_init: true - blobdir: /tmp/cmf-blobs - -Para más información, por favor, ve la `documentación oficial del PHPCR de Midgard `_. - -.. _bundle-phpcr-odm-configuration: - - -Configurando el *PHPCR-ODM* de *Doctrine* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Esta sección de configuración gestiona el sistema *PHPCR-ODM* de *Doctrine*. Si no configuras ninguna cosa aquí, los servicios *ODM* no serán cargados. - -Si habilitas ``auto_mapping``, puedes colocar tus asociaciones en ``/Resources/config/doctrine/.phpcr.xml`` resp. ``...yml`` para -configurar asociaciones a documentos proporcionadas en el directorio ``/Document``. De lo contrario necesitas configurar manualmente la sección de asignaciones. - -Si ``auto_generate_proxy_classes`` es ``false``, necesitas ejecutar la orden ``cache:warmup`` -para tener las clases delegadas generadas después de que modificaste un documento. También puedes ajustar cómo y dónde generar las clases delegadas con las opciones ``proxy_dir`` y ``proxy_namespace``. Normalmente las predefinidas están bien aquí. - -También puedes habilitar el `almacenamiento de metadatos en caché `_. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - odm: - configuration_id: ~ - auto_mapping: true - mappings: - : - mapping: true - type: ~ - dir: ~ - alias: ~ - prefix: ~ - is_bundle: ~ - auto_generate_proxy_classes: %kernel.debug% - proxy_dir: %kernel.cache_dir%/doctrine/PHPCRProxies - proxy_namespace: PHPCRProxies - - metadata_cache_driver: - type: array - host: ~ - port: ~ - instance_class: ~ - class: ~ - id: ~ - - -.. _bundle-phpcr-odm-multilang-config: - -Configuración de traducción -""""""""""""""""""""""""""" - -.. index:: I18N, Multilanguage - -Si estás utilizando documentos multilingües, necesitas configurar los idiomas disponibles. Para más información en documentos multilingües, ve la `documentación PHPCR-ODM en multilenguaje `_. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - odm: - ... - locales: - en: [en, de, fr] - de: [de, en, fr] - fr: [fr, en, de] - -Este bloque define el orden de regiones alternativas para ver si un documento no está traducido al idioma requerido. - - -Ajustes generales -~~~~~~~~~~~~~~~~~ - -Si ajustas la ruta a ``jackrabbit_jar``, puedes utilizar la orden de consola ``doctrine:phpcr:jackrabbit`` para arrancar y parar el ``jackrabbit``. - -Puedes poner a punto la producción de la orden ``doctrine:phpcr:dump`` con ``dump_max_line_length``. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - jackrabbit_jar: /ruta/a/jackrabbit.jar - dump_max_line_length: 120 - -.. _bundle-phpcr-odm-multiple-phpcr-sessions: - -Configurando múltiples sesiones -------------------------------- - -Si necesitas más de una interfaz de administración *PHPCR*, puedes definir ``sessions`` como hijo de la información de ``session``. Cada sesión tiene un nombre y configuración que puedes utilizar directamente en ``session``. también puedes sobrescribir qué sesión utilizar como ``default_session``. - - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - default_session: ~ - sessions: - : - workspace: ~ # Requerido - username: ~ - password: ~ - backend: - # como arriba - options: - # como arriba - -Si estás utilizando el *ODM*, también querrás configurar múltiples gestores de documento. - -Dentro de la sección *odm*, puedes añadir entradas nombradas en ``document_managers``. -Para utilizar la sesión no predefinida, especifica el atributo ``session``. - -.. configuration-block:: - - .. code-block:: yaml - - odm: - default_document_manager: ~ - document_managers: - : - # mismas claves como directamente en odm, ve arriba. - session: - - -Un ejemplo completo se ve como sigue: - -.. configuration-block:: - - .. code-block:: yaml - - doctrine_phpcr: - # configura las sesiones PHPCR - session: - sessions: - - default: - backend: %phpcr_backend% - workspace: %phpcr_workspace% - username: %phpcr_user% - password: %phpcr_pass% - - website: - backend: - type: jackrabbit - url: %magnolia_url% - workspace: website - username: %magnolia_user% - password: %magnolia_pass% - - dms: - backend: - type: jackrabbit - url: %magnolia_url% - workspace: dms - username: %magnolia_user% - password: %magnolia_pass% - # activa la capa ODM - odm: - document_managers: - default: - session: default - mappings: - SandboxMainBundle: ~ - SymfonyCmfContentBundle: ~ - SymfonyCmfMenuBundle: ~ - SymfonyCmfRoutingExtraBundle: ~ - - website: - session: website - configuration_id: sandbox_magnolia.odm_configuration - mappings: - SandboxMagnoliaBundle: ~ - - dms: - session: dms - configuration_id: sandbox_magnolia.odm_configuration - mappings: - SandboxMagnoliaBundle: ~ - - auto_generate_proxy_classes: %kernel.debug% - -.. tip:: - - Este ejemplo también utiliza diferentes configuraciones par repositorio (ve el atributo ``repository_id``). Este caso se explica en :doc:`../cookbook/phpcr-odm-custom-documentclass-mapper`. - -.. _bundle-phpcr-odm-commands: - - -Servicios ---------- - -Puedes acceder el servicios *PHPCR* con esto: - -.. code-block:: php - - container->get('doctrine_phpcr'); - // instancia de la sesión PHPCR - $session = $this->container->get('doctrine_phpcr.default_session'); - // instancia del gestor del documento ODM de PHPCR - $documentManager = $this->container->get('doctrine_phpcr.odm.default_document_manager'); - } - } - - -Eventos -------- - -You can tag services to listen to Doctrine PHPCR-ODM events. Esto trabaja de la misma manera que en el *ORM* de *Doctrine*. Las únicas diferencias son: - -* Usa la etiqueta ``doctrine_phpcr.event_listener`` resp. ``doctrine_phpcr.event_subscriber`` en vez de ``doctrine.event_listener``. -* expect the argument to be of class ``Doctrine\ODM\PHPCR\Event\LifecycleEventArgs`` rather than in the ORM namespace. - (this is subject to change, as doctrine commons 2.4 provides a common class for this event). - -You can register for the events as described in `the PHPCR-ODM documentation `_. -Or you can tag your services as event listeners resp. event subscribers. - -.. configuration-block:: - - .. code-block:: yaml - - services: - my.listener: - class: Acme\SearchBundle\EventListener\SearchIndexer - tags: - - { name: doctrine_phpcr.event_listener, event: postPersist } - - my.subscriber: - class: Acme\SearchBundle\EventSubscriber\MySubscriber - tags: - - { name: doctrine_phpcr.event_subscriber } - - -.. hint:: - - Doctrine event subscribers (both ORM and PHPCR-ODM) can not - return a flexible array of methods to call like the `Symfony event subscriber `_ - can do. Doctrine event subscribers must return a simple array of the event - names they subscribe to. Doctrine will then expect methods on the subscriber - with the names of the subscribed events, just as when using an event listener. - -More information with PHP code examples for the doctrine event system integration is in -this `Symfony cookbook entry `_. - - -Constraint validator --------------------- - -The bundle provides a ``ValidPhpcrOdm`` constraint validator you can use to -check if your document ``Id`` or ``Nodename`` and ``Parent`` fields are -correct. - - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/BlogBundle/Resources/config/validation.yml - Acme\BlogBundle\Entity\Author: - constraints: - - Doctrine\Bundle\PHPCRBundle\Validator\Constraints\ValidPhpcrOdm - - .. code-block:: php - - // src/Acme/BlogBundle/Entity/Author.php - - // ... - use Doctrine\Bundle\PHPCRBundle\Validator\Constraints as OdmAssert; - - /** - * @OdmAssert\ValidPhpcrOdm - */ - class Author - { - ... - } - - .. code-block:: xml - - - - - - - - - - -Form types ----------- - -The bundle provides a couple of handy form types for PHPCR and PHPCR-ODM specific cases, along with form type guessers. - - -``phpcr_odm_image`` -~~~~~~~~~~~~~~~~~~~ - -The ``phpcr_odm_image`` form maps to a document of type ``Doctrine\ODM\PHPCR\Document\Image`` -and provides a preview of the uploaded image. To use it, you need to include the -`LiipImagineBundle `_ in your project and define an -imagine filter for thumbnails. - -This form type is only available if explicitly enabled in your application configuration -by defining the ``imagine`` section under the ``odm`` section with at least ``enabled: true``. -You can also configure the imagine filter to use for the preview, as well as additional -filters to remove from cache when the image is replaced. If the filter is not specified, -it defaults to ``image_upload_thumbnail``. - -.. configuration-block:: - - .. code-block:: yaml - - doctrine_phpcr: - ... - odm: - imagine: - enabled: true - # filter: image_upload_thumbnail - # extra_filters: - # - imagine_filter_name1 - # - imagine_filter_name2 - - # Imagine Configuration - liip_imagine: - ... - filter_sets: - # define the filter to be used with the image preview - image_upload_thumbnail: - data_loader: phpcr - filters: - thumbnail: { size: [100, 100], mode: outbound } - -Then you can add images to document forms as follows: - -.. code-block:: php - - use Symfony\Component\Form\FormBuilderInterface; - - protected function configureFormFields(FormBuilderInterface $formBuilder) - { - $formBuilder - ->add('image', 'phpcr_odm_image', array('required' => false)) - ; - } - -.. tip:: - - If you set required to true for the image, the user must re-upload a new image - each time he edits the form. If the document must have an image, it makes sense - to require the field when creating a new document, but make it optional when - editing an existing document. - We are `trying to make this automatic `_. - - -Next you will need to add the ``fields.html.twig`` template from the DoctrinePHPCRBundle to the form.resources, -to actually see the preview of the uploaded image in the backend. - -.. configuration-block:: - - .. code-block:: yaml - - # Twig Configuration - twig: - form: - resources: - - 'DoctrinePHPCRBundle:Form:fields.html.twig' - - -The document that should contain the Image document has to implement a setter method. -To profit from the automatic guesser of the form layer, the name in the form element -and this method name have to match: - -.. code-block:: php - - public function setImage($image) - { - if (!$image) { - // Esto es normal y sucede cuándo ninguna nueva imagen se ha cargado - return; - } elseif ($this->image && $this->image->getFile()) { - // PENDIENTE: necesario hasta que se haya corregidi este fallo en PHPCRODM: https://github.com/doctrine/phpcr-odm/pull/262 - $this->image->getFile()->setFileContent($image->getFile()->getFileContent()); - } else { - $this->image = $image; - } - } - - -To delete an image, you need to delete the document containing the image. (There is a proposal -to improve the user experience for that in a `DoctrinePHPCRBundle issue `_.) - -.. note:: - - There is a doctrine listener to invalidate the imagine cache for the - filters you specified. This listener will only operate when an Image is - changed in a web request, but not when a CLI command changes images. When - changing images with commands, you should handle cache invalidation in - the command or manually remove the imagine cache afterwards. - - -phpcr_odm_reference_collection -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This form type handles editing ``ReferenceMany`` collections on PHPCR-ODM documents. -It is a choice field with an added ``referenced_class`` required option that specifies -the class of the referenced target document. - -To use this form type, you also need to specify the list of possible reference targets as an array of PHPCR-ODM ids or -PHPCR paths. - -The minimal code required to use this type looks as follows: - -.. code-block:: php - - $dataArr = array( - '/some/phpcr/path/item_1' => 'first item', - '/some/phpcr/path/item_2' => 'second item', - ); - - $formMapper - ->with('form.group_general') - ->add('myCollection', 'phpcr_odm_reference_collection', array( - 'choices' => $dataArr, - 'referenced_class' => 'Class\Of\My\Referenced\Documents', - )) - ->end(); - -.. tip:: - - When building an admin interface with :doc:`Sonata Admin` - there is also the ``sonata_type_model`` that is more powerful, allowing to add - to the referenced documents on the fly. Unfortunately it is - `currently broken `_. - - -phpcr_reference -~~~~~~~~~~~~~~~ - -The ``phpcr_reference`` represents a PHPCR Property of type REFERENCE or WEAKREFERENCE within a form. -The input will be rendered as a text field containing either the PATH or the UUID as per the -configuration. The form will resolve the path or id back to a PHPCR node to set the reference. - -This type extends the ``text`` form type. It adds an option ``transformer_type`` that can be set -to either ``path`` or ``uuid``. - - -Fixture loading ---------------- - -To use the ``doctrine:phpcr:fixtures:load`` command, you additionally need to install the -`DoctrineFixturesBundle `_ which brings the -`Doctrine data-fixtures `_ into Symfony2. - -Fixtures work the same way they work for Doctrine ORM. You write fixture classes implementing -``Doctrine\Common\DataFixtures\FixtureInterface``. If you place them in \DataFixtures\PHPCR, -they will be auto detected if you specify no path to the fixture loading command. - -A simple example fixture class looks like this: - -.. code-block:: php - - `_. - -Migration loading ------------------ - -The DoctrinePHPCRBundle also ships with a simple command to run migration scripts. Migrations -should implement the ``Doctrine\Bundle\PHPCRBundle\Migrator\MigratorInterface`` and registered -as a service with a ``doctrine_phpcr.migrator`` tag contains an ``alias`` attribute uniquely -identifying the migrator. There is an optional ``Doctrine\Bundle\PHPCRBundle\Migrator\AbstractMigrator`` -class to use as a basis. To find out available migrations run: - -.. code-block:: bash - - $ php app/console doctrine:phpcr:migrator - -Then pass in the name of the migrator to run it, optionally passing in an ``--identifier``, -``--depth`` or ``--session`` argument. The later argument determines which session name to -set on the migrator, while the first two arguments will simply be passed to the ``migrate()`` -method. You can find an example migrator in the SimpleCmsBundle. - -Órdenes del *PHPCR* de *Doctrine* ---------------------------------- - -Todas las órdenes sobre el *PHPCR* van prefijadas con ``doctrine:phpcr`` y puedes utilizar el argumento ``--session`` para utilizar una sesión distinta a la predefinida si configuraste varias sesiones *PHPCR*. - -Algunas de estas órdenes son específicas a una interfaz de administración o al *ODM*. Esas órdenes -sólo estarán disponibles si tal interfaz de administración está configurada. - -Usa ``app/console help `` para ver todas las opciones que tiene cada orden. - -- ``doctrine:phpcr:workspace:create`` Create a workspace in the configured repository -- ``doctrine:phpcr:workspace:list`` List all available workspaces in the configured repository -- ``doctrine:phpcr:purge`` Remove a subtree or all content from the repository -- ``doctrine:phpcr:repository:init`` Register node types and create base paths -- ``doctrine:phpcr:register-node-types`` Register node types from a .cnd file in the PHPCR repository -- ``doctrine:phpcr:fixtures:load`` Load data fixtures to your PHPCR database. -- ``doctrine:phpcr:import`` Import xml data into the repository, either in JCR system view format or arbitrary xml -- ``doctrine:phpcr:export`` Export nodes from the repository, either to the JCR system view format or the document view format -- ``doctrine:phpcr:dump`` Output all or some content of the repository -- ``doctrine:phpcr:query`` Execute a JCR SQL2 statement -- ``doctrine:phpcr:mapping:info`` Shows basic information about all mapped documents - - -.. note:: - - To use the ``doctrine:phpcr:fixtures:load`` command, you additionally need to install the - `DoctrineFixturesBundle `_ - and its dependencies. Ve su página de documentación para aprender a utilizar los accesorios. - - -Órdenes específicas de ``Jackrabbit`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si estás utilizando ``jackalope-jackrabbit``, también tienes una orden para arrancar y detener el servidor ``jackrabbit``: - -- ``jackalope:run:jackrabbit`` Arranca y detiene el servidor ``Jackrabbit`` - - -Órdenes específicas para el *DBAL* de *Doctrine* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si estás utilizando ``jackalope-doctrine-dbal``, tienes una orden para iniciar la base -de datos: - -- ``jackalope:init:dbal`` Prepara la base de datos para el *DBAL* de *Doctrine* con ``Jackalope`` - -Ten en cuenta que también puedes utilizar la orden *dbal* de *doctrine* para crear la base de datos. - - -Algunas ordenes de ejemplo -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ejecutando `consultas SQL2 `_ contra el repositorio - -.. code-block:: bash - - app/console doctrine:phpcr:query "SELECT title FROM [nt:unstructured] WHERE NAME() = 'home'" - - -Vertiendo nodos bajo /cms/simple incluyendo sus propiedades - -.. code-block:: bash - - app/console doctrine:phpcr:dump /cms/simple --props - - diff --git a/_sources/cmf/bundles/routing-extra.txt b/_sources/cmf/bundles/routing-extra.txt deleted file mode 100644 index f03458a..0000000 --- a/_sources/cmf/bundles/routing-extra.txt +++ /dev/null @@ -1,323 +0,0 @@ -``RoutingExtraBundle`` -====================== - -El `RoutingExtraBundle `_ integra el enrutado dinámico a *Symfony* utilizando :doc:`../components/routing`. - -La ``ChainRouter`` pretende reemplazar el enrutador predefinido de *Symfony*. Todo lo que hace es recoger una lista priorizada de rutas e intenta emparejar peticiones y generar las *URL* con todas ellas. Naturalmente, una de las rutas en esa cadena puede ser la ruta predefinida para que todavía la puedas utilizar de la manera estándar en algunas de tus rutas. - -Además, este paquete entrega útiles implementaciones de enrutado. Actualmente, existe el ``DynamicRouter`` que enruta basándose en una lógica de cargador personalizada para los objetos ``Ruta`` de *Symfony2*. Puedes implementar el proveedor usando una base de datos, por ejemplo con el `PHPCR-ODM`_ de *Doctrine* o el *ORM* de *Doctrine*. Este paquete proporciona una implementación predefinida para el `PHPCR-ODM`_ de *Doctrine*. - -El servicio ``DynamicRouter`` sólo está disponible cuándo se habilita explícitamente en la configuración de la aplicación. - -Finalmente estos paquetes proporcionan documentos de ruta para el `PHPCR-ODM`_ de *Doctrine* y un controlador para redirigir rutas. - -.. index:: RoutingExtraBundle -.. index:: Routing - -Dependencias ------------- - -* `Routing del CMF de Symfony `_ - -``ChainRouter`` ---------------- - -La ``ChainRouter`` puede reemplazar el sistema de enrutado predefinido de *Symfony* con una implementación habilitada en cadena. Esta no enruta nada por sí misma, sino que únicamente recorre todos los enrutadores encadenados. Para manejar las rutas estándar configuradas en *Symfony*, el enrutador predefinido de *Symfony* se puede poner en la cadena. - -Configurando ------------- - -En tu archivo :file:`app/config/config.yml`, puedes especificar cual servicio de enrutado quieres usar. Si no especificas el ``routers_by_id`` asigna todos, de manera predefinida el enrutador encadenado simplemente cargará el enrutador construido en *Symfony*. Cuándo especificas la lista de ``routers_by_id``, necesitas tener una entrada para ``router.default`` si quieres el enrutador de *Symfony2* (aquel que lee las rutas desde :file:`app/config/routing.yml`). - -El formato es ``service_name: priority`` --- el número de prioridad más alto anterior es preguntado a este servicio enrutador para emparejar una ruta o para generar una *url*: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - chain: - routers_by_id: - # activa el DynamicRouter con alta prioridad para redefinir - # el enrutador configurado con contenido - symfony_cmf_routing_extra.dynamic_router: 200 - # activa el enrutador predefinido de symfony con - # baja prioridad - router.default: 100 - # cuando la cadena de enrutadores debe sustituir al enrutador predefinido. por omisión es true - # si lo pones en false, el enrutador sólo estará disponible como servicio - # symfony_cmf_routing_extra.router y debes hacer algo para lanzarlo - # replace_symfony_router: true - -Cargando enrutadores con etiquetado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tus enrutadores se pueden registrar automáticamente, justo añadiéndolos como servicios etiquetados con `router` y una ``prioridad`` opcional. -Mientras más alta prioridad, más temprano será consultado tu enrutador para emparejar la ruta. Si no especificas la prioridad, tu enrutador vendrá al último. -Si hay varios enrutadores con la misma prioridad, el orden entre ellos es indeterminado. -El servicio etiquetado tendrá esta apariencia: - -.. configuration-block:: - - .. code-block:: yaml - - services: - my_namespace.my_router: - class: %my_namespace.my_router_class% - tags: - - { name: router, priority: 300 } - - .. code-block:: xml - - - - .. - - -Ve también la `documentación oficial de Symfony2 para las etiquetas de inyección de dependencias`_ - -Enrutador dinámico ------------------- - -Esta implementación de un ``enrutador`` utiliza ``NestedMatcher`` el cuál carga rutas de una ``RouteProviderInterface``. La interfaz del proveedor se puede implementar fácilmente con *Doctrine*. - -El ``enrutador`` trabaja con clases extendidas de ``UrlMatcher`` y ``UrlGenerator`` que añaden la carga de rutas desde la base de datos y el concepto de contenido referido. - -El servicio ``NestedMatcher`` está configurado con un proveedor de rutas. Ve cómo cambiar el ``route_repository_service`` en la sección de configuración y la siguiente sección para más detalles de la implementación predeterminada basada en `PHPCR-ODM`_. - -Quzás quieras configurar potenciadores de enrutado para decidir cual controlador usar para manejar la petición, para evitar la codificación directa de los nombres de controlador para enrutar tus documentos. - -La configuración mínima requerida para cargar el enrutador dinámico como un servicio ``symfony_cmf_routing_extra.dynamic_router`` es tenerlo como ``enabled: true`` en tu archivo :file:`config.yml` (El enrutador se activa automáticamente apenas añadas cualquier otra configuración a la entrada ``dynamic``). Sin habilitarlo, el servicio enrutador dinámico no será cargado en absoluto, dejándote utilizar la ``ChainRouter`` con tus propios enrutadores: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - dynamic: - enabled: true - -Integrando *PHPCR-ODM* -~~~~~~~~~~~~~~~~~~~~~~ - -Este paquete viene con una implementación de enrutador con repositorio para `PHPCR-ODM`_. -*PHPCR* es apropiado para la naturaleza del árbol de datos. Si usas `PHPCR-ODM`_ con un documento de ruta como el proporcionado, justo puedes dejar el servicio repositorio como el predefinido. - -El repositorio predefinido carga la ruta desde la ruta en la petición y todas las rutas padre para permitir que algunos de los segmentos de la ruta sigan siendo parámetros. Si necesitas una diferente manera de cargar rutas o, por ejemplo, nunca usar parámetros, puedes escribir tu propia implementación del repositorio para optimizarla (ve en el archivo :file:`cmf_routing.xml` cómo configurar el servicio). - -.. index:: PHPCR, ODM - -Proceso de concordancia -~~~~~~~~~~~~~~~~~~~~~~~ - -La mayoría del proceso de concordancia está descrito en la documentación del `componente enrutador del CMF`_. -La única diferencia es que el paquete colocará el ``contentDocument`` en los atributos de la petición en vez de en la ruta predefinida. - -Tus controladores pueden (y tienen que) declarar el parámetro ``$contentDocument`` en tus métodos ``Action`` si se supone que trabajen con el contenido referido por las rutas. -Ve ``Symfony\Cmf\Bundle\ContentBundle\Controller\ContentController`` para un ejemplo. - -.. _bundle-routing-route-enhancer: - -Configurando -~~~~~~~~~~~~ - -Para configurar qué controlador se utiliza para cuál contenido, puedes especificar potenciadores de ruta. La presencia de cualquiera de los potenciadores en la configuración hace que el contenedor de dependencias inyecte al ``DynamicRouter`` el potenciador respectivo. - -Los posibles potenciadores son (en orden de precedencia): - -* (Controlador explícito): Si existe un ``_controller`` en ``getRouteDefaults()``, ningún potenciador lo sustituye. -* Plantilla explícita: Requiere que el documento de ruta regrese un parámetro ``'_template'`` en ``getRouteDefaults``. El controlador genérico configurado está puesto por el potenciador. -* Controlador por alias: Requiere que el documento de ruta regrese un valor ``'type'`` en ``getRouteDefaults()`` -* Controlador por clase: Requiere que el documento de ruta regrese un objeto para ``getRouteContent()``. The content document is checked for being ``instanceof`` the - class names in the map and if matched that controller is used. - Se utiliza ``instanceof`` en vez de compararlo directamente para trabajar con clases delegadas y extender otras clases. -* Plantilla por clase: Requiere que el documento de ruta regrese un objeto para ``getRouteContent()``. The content document is checked for being ``instanceof`` the - class names in the map and if matched that template will be set as - '_template' in the $defaults and the generic controller used as controller. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - dynamic: - generic_controller: symfony_cmf_content.controller:indexAction - controllers_by_type: - editablestatic: sandbox_main.controller:indexAction - controllers_by_class: - Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent: symfony_cmf_content.controller:indexAction - templates_by_class: - Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent: SymfonyCmfContentBundle:StaticContent:index.html.twig - - # el proveedor de ruta es responsable de cargar rutas. - manager_registry: doctrine_phpcr - manager_name: default - - # Si usas la ruta predefinida del servicio repositorio de - # Doctrine, puedes usar esto para personalizar la ruta raíz - # para el RouteProvider de PHPCR-ODM. Esta ruta base - # es inyectada por el escucha\IdPrefix - pero sólo para - # las rutas que coincidan con el prefijo, para permitir - # más de una ruta fuente. - routing_repositoryroot: /cms/routes - - # Si deseas reemplazar el proveedor de ruta predefinido - # o tu repositorio de contenido puedes especificar sus - # identificadores de servicio aquí. - route_provider_service_id: my_bundle.provider.endpoint - content_repository_service_id: my_bundle.repository.endpoint - - # Un proveedor orm podría necesitar diferente configuración. - # Ve un ejemplo en cmf_routing.xml si necesitas definir - # tu propio servicio - - -Para ver algunos ejemplos, por favor ve en el `recinto de seguridad del CMF`_, específicamente la carga de accesorios de enrutado. - -.. tip:: - - You can also define your own RouteEnhancer classes for specific use cases. - See :ref:`bundle-routing-customize`. - -.. _bundle-routing-document: - -Usando el documento de ruta *PHPCR-ODM* ---------------------------------------- - -Todas las clases de ruta deben extender la clase ``Route`` del núcleo de *Symfony*. Los documentos tampoco se pueden crear por código (por ejemplo un guión de accesorios) o con una interfaz *web* como la proporcionada para la administración *PHPCR-ODM* de Sonata (ve abajo). - -*PHPCR-ODM* asocia todas las características de la ruta del núcleo al almacenamiento, así que puedes utilizar normalmente los métodos ``setDefault``, ``setRequirement``, ``setOption`` y ``setHostnamePattern``. -Además cuándo creas una ruta, puedes definir si ``.{_format}`` se debería anexar al patrón y configurar con requisitos el ``_format`` requerido. -La otra opción del constructor te permite controlar si la ruta debería anexar una barra inclinada final porque esto no se puede expresar con un nombre *PHPCR*. El predefinido es que no tenga ninguna barra inclinada final. - -Todas las rutas están localizadas bajo una ruta configurada como la raíz, por ejemplo ``'/cms/rutas'``. -Puedes crear una nueva ruta en código *PHP* de la siguiente manera: - -.. code-block:: php - - use Symfony\Cmf\Bundle\RoutingExtraBundle\Document\Route; - $route = new Route; - $route->setParent($dm->find(null, '/routes')); - $route->setName('projects'); - // establece el controlador explícitamente (ambas sintaxis trabajan - // servicio y Paquete:Nombre:acción) - $route->setDefault('_controller', 'sandbox_main.controller:specialAction'); - -Sin embargo, el ejemplo anterior probablemente se debería haber hecho como una ruta configurada en un archivo ``xml/yml`` de *Symfony*, a no ser que se suponga que el usuario final cambie la *URL* o el controlador. - -Para enlazar un contenido a esta ruta, sencillamente pon lo siguiente en el documento. - -.. code-block:: php - - $content = new Content('my content'); // el contenido se debe asociar a una clase - $route->setRouteContent($content); - -Esto pondrá el documento en los parámetros de la petición y si tu controlador especifica un parámetro llamado ``$contentDocument``, le será pasado este documento. - -También puedes utilizar patrones variables para la *URL* y definir requisitos y predeterminados. - -.. code-block:: php - - // No olvides la barra inclinada inicial si quieres /proyectos/{id} y no /proyectos{id} - $route->setVariablePattern('/{id}'); - $route->setRequirement('id', '\d+'); - $route->setDefault('id', 1); - -Esto te dará una ruta que empareja con la *URL* ``/proyectos/`` pero también con ``/proyectos`` ya que es el predefinido para el parámetro ``id``. Esta emparejará con ``/proyectos/7`` así como con ``/proyectos`` pero no con ``/proyectos/x-4``. -El documento todavía será almacenado en ``/rutas/proyectos``. Esto trabajará porque, como se mencionó anteriormente, el proveedor de ruta buscará documentos de ruta en todas las rutas posibles y elegirá la primera que coincida. En este ejemplo, si hay un documento de ruta en ``/rutas/proyectos/7`` que coincida (sin más parámetros) será seleccionado. De lo contrario comprobará si ``/rutas/proyectos`` tiene un patrón que coincida. Si no, el documento superior en ``/rutas`` será comprobado. - -Naturalmente también puedes tener varios parámetros, como es normal en las rutas de *Symfony*. La semántica y reglas para patrones, predefinidos y requisitos son exactamente iguales que en las rutas del núcleo. - -Tu controlador puede esperar el parámetro ``$id`` así como el ``$contentDocument`` tal como se -puso un contenido en la ruta. El contenido se podría usar para definir una sección de introducción que sea igual en cada proyecto u otro dato compartido. Si no necesitas contenido, puedes simplemente no ponerlo en el documento. - - -Configurando la administración de ``Sonata`` --------------------------------------------- - -Si añades ``sonata-project/doctrine-phpcr-admin-bundle`` en la sección ``require`` de tu archivo :file:`composer.json` y el ``SonataDoctrinePhpcrAdminBundle`` es cargado en el núcleo de la aplicación, los documentos de ruta son expuestos en el ``SonataDoctrinePhpcrAdminBundle``. -Para instrucciones sobre cómo configurar este paquete ve :doc:`doctrine_phpcr_admin`. - -De manera predefinida, ``use_sonata_admin`` se configura automáticamente basándose en si ``SonataDoctrinePhpcrAdminBundle`` está disponible, pero lo puedes deshabilitar explícitamente no poniéndolo incluso si ``sonata`` está habilitado, o lo puedes habilitar explícitamente para obtener un error si ``sonata`` queda inutilizable. - -Si quieres utilizar el ``admin``, debes configurar la opción ``content_basepath`` para que apunte a la raíz de tus documentos de contenido. - - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - use_sonata_admin: auto # usa true/false para forzar el uso / no uso de - # la administración de sonata - content_basepath: ~ # usado con la administración de sonata para - # manejar el contenido, por omisión a - # symfony_cmf_core.content_basepath - - -El tipo ``form`` ----------------- - -El paquete define un tipo ``form`` que puedes utilizar para la clásica casilla de verificación «aceptar términos» donde colocas las *url* en la etiqueta. Simplemente especifica ``symfony_cmf_routing_extra_terms_form_type`` como el nombre del tipo ``form`` y especifica una etiqueta y un arreglo con ``content_ids`` en las opciones - -.. code-block:: php - - $form->add('terms', - 'symfony_cmf_routing_extra_terms_form_type', - array('label' => 'I have seen the Team and More pages ...', - 'content_ids' => array('%team%' => '/cms/content/static/team', - '%more%' => '/cms/content/static/more') - )); - -El tipo ``form`` automáticamente genera las rutas para el contenido especificado y pasa las rutas al ayudante ``trans`` de *Twig* para sustituirlas en la etiqueta. - -Notas adicionales ------------------ - -Ve la documentación del `componente enrutador del CMF`_ para información sobre la ``RouteObjectInterface``, redirecciones y regiones. - -Notas: - -* RouteObjectInterface: Los documentos proporcionados implementan esta interfaz para asociar contenido a rutas y (opcionalmente) proporcionan un nombre de ruta personalizado en vez del nombre de ruta compatible con el núcleo de *Symfony*. -* Redirecciones: This bundle provides a controller to handle redirections. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - controllers_by_class: - Symfony\Cmf\Component\Routing\RedirectRouteInterface: symfony_cmf_routing_extra.redirect_controller:redirectAction - -.. _bundle-routing-customize: - -Personalizando --------------- - -Puedes añadir más implementaciones de la ``RouteEnhancerInterface`` si tienes un caso no manejado por el proporcionado. Sencillamente define servicios para tus potenciadores y eiquétalos con ``dynamic_router_route_enhancer`` para añadirlos al enrutado. - -Si usas un *ODM/ORM* diferente a `PHPCR-ODM`_, probablemente necesites especificar la clase para la entidad ruta (en `PHPCR-ODM`_, la clase es detectada automáticamente). Para necesidades más específicas, dale una mirada al ``DynamicRouter`` y ve si lo quieres extender. También puedes escribir tus propios enrutadores para engancharlos a la cadena. - -.. _`documentación oficial de Symfony2 para las etiquetas de inyección de dependencias`: http://gitnacho.github.com/symfony-docs-es/reference/dic_tags.html -.. _`recinto de seguridad del CMF`: https://github.com/symfony-cmf/cmf-sandbox -.. _`componente enrutador del CMF`: https://github.com/symfony-cmf/Routing -.. _`PHPCR-ODM`: https://github.com/doctrine/phpcr-odm - -Aprende más en el recetario ---------------------------- - -* :doc:`../cookbook/using-a-custom-route-repository` - -Notas adicionales ------------------ - -Para más información sobre el componente ``Routing`` del ``CMF`` de *Symfony*, por favor, consulta: - -- :doc:`../getting-started/routing` para una guía introductoria al paquete ``Routing`` -- :doc:`../components/routing` para la mayoría de la funcionalidad implementada actualmente -- La página del componente `Routing `_ de *Symfony2*. diff --git a/_sources/cmf/bundles/routing_auto.txt b/_sources/cmf/bundles/routing_auto.txt deleted file mode 100644 index ef61d61..0000000 --- a/_sources/cmf/bundles/routing_auto.txt +++ /dev/null @@ -1,500 +0,0 @@ -RoutingAutoBundle -================= - -The ``RoutingAutoBundle`` allows you to define automatically created routes -for documents. This implies a separation of the ``Route`` and ``Content`` -documents. If your needs are simple this bundle may not be for you and you -should have a look at :doc:`SimpleCmsBundle`. - -For the sake of example, we will imagine a blog application -that has two routeable contents, the blog itself, and the posts for the blog. -We will call these documents ``Blog`` and ``Post``, and we will class them as -*content documents*. - -.. note:: - - In our example we add an auto route for the blog, but in reality, as a blog - is something you create rarely, you will probably want to create routes for - your blog manually, but its up to you. - -If we create a new ``Blog`` with the title "My New Blog" the bundle could automatically -create the route ``/blogs/my-new-blog``. For each new ``Post`` it could create a route -such as ``/blogs/my-new-blog/my-posts-title``. This URL resolves to a special type of -route that we will call the *auto route*. - -By default, when we update a content document that has an auto route the -corresponding auto route will also be updated, when deleting a content document -the corresponding auto route will also be deleted. - -If required, the bundle can also be configured to do extra stuff, like, for example, -leaving a ``RedirectRoute`` when the location of a content document changes or -automatically displaying an index page when an unconfigured intermediate path is -accessed (for example, listing all the children under ``/blogs`` instead of returning -a ``404``). - -Why not simply use a single route? ----------------------------------- - -Of course, our fictional blog application could use a single route with a pattern -``/blogs/my-new-blog/{slug}`` which could be handled by a controller. Why not just -do this? - -1. By having a route for each page in the system the application has a knowledge of - which URLs are accessible, this can be very useful, for example, when specifying - endpoints for menu items are generating a site map. - -2. By separating the route from the content we allow the route to be customized independently - of the content, for example, a blog post may have the same title as another post but might - need a different URL. - -3. Separate route documents are translateable - this means we can have a URL for - *each language*, "/welcome" and "/bienvenue" would each reference - the same document in English and French respectively. This would be difficult if - the slug were embedded in the content document. - -4. By decoupling route and content the application doesn't care *what* is referenced in - the route document. This means that we can easily replace the class of document referenced. - -Anatomy of an automatic URL ---------------------------- - -The diagram below shows a fictional URL for a blog post. The first 6 elements -of the URL are what we will call the *content path*. The last element we will call -the *content name*. - -.. image:: ../_images/bundles/routing_auto_post_schema.png - -The content path is further broken down into *route stacks* and *routes*. A route -stack is a group of routes and routes are simply documents in the PHPCR tree. - -.. note:: - - Although routes in this case can be of any document class, only objects which - extend the :class:`Symfony\\Component\\Routing\\Route` object will be considered when matching a URL. - - The default behavior is to use Generic documents when generating a content path, and - these documents will result in a 404 when accessed directly. - -Internally each route stack is built up by a *builder unit*. Builder units contain -one *path provider* class and two actions classes one action to take if the provided -path exists in the PHPCR tree, the other if it does not. The goal -of each builder unit is to generate a path and then provide a route object for each -element in that path. - -The configuration for the example above could be as follows: - -.. code-block:: yaml - - symfony_cmf_routing_auto: - - auto_route_mapping: - My\Namespace\Bundle\BlogBundle\Document\Post: - content_path: - # corresponds first route stack in diagram: my, blog, my-blog - blog_path: - provider: - name: content_object - method: getBlog - exists_action: - strategy: use - not_exists_action: - strategy: throw_exception - - # corresponds to second route stack: 2013,04,06 - date: - provider: - name: content_datetime - method: getPublishedDate - exists_action: - strategy: use - not_exists_action: - strategy: create - - # corresponds to the content name: My Post Title - content_name: - provider: - name: content_method - method: getTitle - exists_action: - strategy: auto_increment - pattern: -%d - not_exists_action: - strategy: create - - -The ``Post`` document would then need to implement the methods named above as follows:: - - blog; - } - - public function getPublishedDate() - { - return new \DateTime('2013/04/06'); - } - - public function getTitle() - { - return "My post title"; - } - } - -Path Providers --------------- - -Path providers specify a target path which is used by the subsequent path actions to provide -the actual route documents. - -**Base** providers must be the first configured as the first builder in the content path chain. -This is because the paths that they provide correspond directly to an existing path, i.e. they -have an absolute reference. - -specified (base provider) -~~~~~~~~~~~~~~~~~~~~~~~~~ - -This is the most basic path provider and allows you to specify an exact (fixed) path. - -.. code-block:: yaml - - path_provider: - name: specified - path: this/is/a/path - -Options: - - - ``path`` - **required** The path to provide. - -.. note:: - - We never specifiy absolute paths in the auto route system. If the builder unit - is the first content path chain it is understood that it is the base of an absolute - path. - -content_object (base provider) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The content object provider will try and provide a path from an object implementing -``RouteAwareInterface`` provided by a designated method on the content document. For -example, if you have a ``Post`` class, which has a ``getBlog`` method, using -this provider you can tell the ``Post`` auto route to use the route of the blog as a -base. - -So basically, if your blog content has a path of ``/this/is/my/blog`` you can use this -path as the base of your ``Post`` auto-route. - -Ejemplo: - -.. code-block:: yaml - - provider: - name: content_object - method: getBlog - -.. note:: - - At the time of writing translated objects are not supported. This isn't hard to do, but well, I just - havn't done it yet. - -Options: - - - ``method``: **required** Method used to return the document whose route path we wish to use. - -content_method -~~~~~~~~~~~~~~ - -The ``content_method`` provider allows the content object (e.g. a blog ``Post``) to specify -a path using one of its methods. This is quite a powerful method as it allows the content -document to do whatever it can to produce the route, the disadvantage is that your content -document will have extra code in it. - -Example 1: - -.. code-block:: yaml - - path_provider: - name: content_method - method: getTitle - -This example will use the existing method "getTitle" of the ``Post`` document to retrieve the -title. By default all strings are *slugified*. - -The method can return the path either as a single string or an array of path elements -as shown in the following example:: - - format()` method. This means that you can specify your date in anyway you like and it will be - automatically slugified, also, by adding path separators in the `date_format` you are effectively creating - routes for each date component as slugify applies to **each element** of the path. - -Options: - - - ``method``: **required** Method used to return the route name / path / path elements. - - ``slugify``: If we should use the slugifier, default is ``true``. - - ``date_format``: Any date format accepted by the `DateTime` class, default ``Y-m-d``. - -Path Exists Actions -------------------- - -These are the default actions available to take if the path provided by a `path_provider` already exists and -so creating a new path would create a conflict. - -auto_increment -~~~~~~~~~~~~~~ - -The ``auto_increment`` action will add a numerical suffix to the path, for example ``my/path`` would first become -``my/path-1`` and if that path *also* exists it will try ``my/path-2``, ``my/path-3`` and so on into infinity until -it finds a path which *doesn't* exist. - -This action should typically be used in the ``content_name`` builder unit to resolve conflicts. Using it in the -``content_path`` builder chain would not make much sense (I can't imagine any use cases at the moment). - -Ejemplo: - -.. code-block:: yaml - - exists_action: - name: auto_increment - -Options: - - - None. - -use -~~~ - -The ``use`` action will simply take the existing path and use it. For example, in our post example the first -builder unit must first determine the blogs path, ``/my/blog``, if this path exists (and it should) then we -will *use* it in the stack. - -This action should typically be used in one of the content path builder units to specify that we should use -the existing route, on the other hand, using this as the content name builder action should cause the old -route to be overwritten. - -Ejemplo: - -.. code-block:: yaml - - exists_action: - name: use - -Options: - - - None. - -Path not exists actions ------------------------ - -These are the default actions available to take if the path provided by a ``path_provider`` does not exist. - -create -~~~~~~ - -The ``create`` action will create the path. **currently** all routes provided by the content path build units -will be created as ``Generic`` documents, whilst the content name route will be created as an ``AutoRoute`` document. - -.. code-block:: yaml - - not_exists_action: - name: create - -Options: - - - None. - -throw_exception -~~~~~~~~~~~~~~~ - -This action will throw an exception if the route provided by the path provider does not exist. You should take -this action if you are sure that the route *should* exist. - -.. code-block:: yaml - - not_exists_action: - name: create - -Options: - - - None. - -Personalizando --------------- - -.. _routingauto_customization_pathproviders: - -Adding Path Providers -~~~~~~~~~~~~~~~~~~~~~ - -The goal of a ``PathProvider`` class is to add one or several path elements to -the route stack. For example, the following provider will add the path "foo/bar" -to the route stack:: - - addPathElements(array('foo', 'bar')); - } - } - -To use the path provider you must register it in the **DIC** and add the -``symfony_cmf_routing_auto.provider`` tag and set the **alias** accordingly. - -.. configuration-block:: - - .. code-block:: xml - - - - - - .. code-block:: yaml - - my_cms.some_bundle.path_provider.foobar: - class: "FoobarProvider" - scope: prototype - tags: - - { name: symfony_cmf_routing_auto.provider, alias: "foobar"} - - .. code-block:: php - - $definition = new Definition('FooBarProvider'); - $definition->addTag('symfony_cmf_routing_auto.provider', array('alias' => 'foobar')); - $definition->setScope('prototype'); - $container->setDefinition('my_cms.some_bundle.path_provider.foobar', $definition); - -The **foobar** path provider is now available as **foobar**. - -.. note:: - - The that both path providers and path actions need to be defined with a - scope of "prototype". This ensures that each time the auto routing system - requests the class a new one is given and we do not have any state problems. - -Adding Path Actions -~~~~~~~~~~~~~~~~~~~ - -In the auto routing system, a "path action" is an action to take if the path provided -by the "path provider" exists or not. - -You can add a path action by extending the ``PathActionInterface`` and registering your -new class correctly in the DI configuration. - -This is a very simple implementation from the bundle - it is used to throw an exception -when a path already exists:: - - getFullPath()); - } - } - -It is registered in the DI configuration as follows: - -.. configuration-block:: - - .. code-block:: xml - - - - - - .. code-block:: yaml - - symfony_cmf_routing_auto.not_exists_action.throw_exception - class: "My\Cms\AutoRoute\PathNotExists\ThrowException" - scope: prototype - tags: - - { name: symfony_cmf_routing_auto.provider, alias: "throw_exception"} - - .. code-block:: php - - $definition = new Definition('My\Cms\AutoRoute\PathNotExists\ThrowException'); - $definition->addTag('symfony_cmf_routing_auto.provider', array('alias' => 'throw_exception')); - $definition->setScope('prototype'); - $container->setDefinition('my_cms.some_bundle.path_provider.throw_exception', $definition); - -Note the following: - -* **Scope**: Must *always* be set to *prototype*; -* **Tag**: The tag registers the service with the auto routing system, it can be one of the following; - * ``symfony_cmf_routing_auto.exists.action`` - if the action is to be used when a path exists; - * ``symfony_cmf_routing_auto.not_exists.action`` - if the action is to be used when a path does not exist; -* **Alias**: The alias of the tag is the name by which you will reference this action in the auto routing schema. diff --git a/_sources/cmf/bundles/search.txt b/_sources/cmf/bundles/search.txt deleted file mode 100644 index 35bac3b..0000000 --- a/_sources/cmf/bundles/search.txt +++ /dev/null @@ -1,30 +0,0 @@ -``SearchBundle`` -================ - -El `SearchBundle `_ proporciona integración con `LiipSearchBundle `_ para proporcionar búsqueda en un sitio completo. - -.. index:: SearchBundle - -Dependencias ------------- - -* `LiipSearchBundle `_ - -Configurando ------------- - -La clave de configuración para este paquete es ``symfony_cmf_search`` - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_search: - document_manager_name: default - translation_strategy: child # además se puede configurar como una cadena vacía o atributo - translation_strategy: attribute - search_path: /cms/content - search_fields: - title: title - summary: body \ No newline at end of file diff --git a/_sources/cmf/bundles/simple-cms.txt b/_sources/cmf/bundles/simple-cms.txt deleted file mode 100644 index 267a669..0000000 --- a/_sources/cmf/bundles/simple-cms.txt +++ /dev/null @@ -1,165 +0,0 @@ -``SimpleCmsBundle`` -=================== - -El `SimpleCmsBundle `_ proporciona un *CMS* simplista sobre los componentes y paquetes del ``CMF``. - -Si bien los componentes del núcleo del *CMF* se enfocan en la flexibilidad, el simple *CMS* especula con alguna de esa flexibilidad a favor de la simplicidad. - -El ``SimpleCmsBundle`` proporciona una solución para fácilmente asignar contenido, rutas y elementos del menú basando el repositorio de contenido en una única estructura de árbol. - -Para una sencilla instalación de ejemplo del paquete revisa la `Edición estándar del CMF de Symfony `_ - -Puedes encontrar una introducción al paquete en la sección `Primeros pasos <../getting-started/simplecms>`_. - -El `sitio web de CMF `_ es -otra aplicación que utiliza el ``SimpleCmsBundle``. - -.. index:: SimpleCmsBundle, i18n - -Dependencias ------------- - -Tal como se especifica en el archivo :file:`composer.json` este paquete depende de muchos de los paquetes del *CMF*. - -Configurando ------------- - -La clave de configuración para este paquete es ``symfony_cmf_simple_cms``. - -La opción ``use_menu`` automáticamente habilita un servicio para proporcionar los menús del simple *CMS* si ``MenuBundle`` está habilitado. También lo puedes desactivar explícitamente si tienes el paquete ``menu`` pero no quieres utilizar el servicio predefinido, o habilitarlo explícitamente para conseguir un error si el paquete ``menu`` se vuelve inasequible. - -La sección de enrutado define cual plantilla o controlador utilizar para una clase de contenido. This is reusing what routing extra does, please see the corresponding -:ref:`routing configuration section `. También explica el ``generic_controller``. - -Ve la sección para soporte multilingüe más adelante. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - use_menu: auto # usa true/false para forzar provisión / no provisión de un menú - use_sonata_admin: auto # use true/false to force using / not using sonata admin - sonata_admin: - sort: false # set to asc|desc to sort children by publication date - document_class: Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page - # controlador a usar para dibujar documentos - # únicamente con plantilla personalizada - generic_controller: symfony_cmf_content.controller:indexAction - # dónde almacenar las páginas en el árbol PHPCR - basepath: /cms/simple - routing: - content_repository_id: symfony_cmf_routing_extra.content_repository - controllers_by_class: - # ... - templates_by_class: - # ... - multilang: - locales: [] - -.. tip:: - - Si tienes habilitada la administración *PHPCR-ODM* de ``Sonata`` pero *NO* quieres mostrar la administración predefinida proporcionada por este paquete, puedes añadir lo siguiente a tu configuración: - - .. configuration-block:: - - .. code-block:: yaml - - symfony_cmf_simple_cms: - use_sonata_admin: false - -Soporte multilingüe -------------------- - -El modo ``multi-language-mode`` se habilita proporcionando la lista de regiones permitidas en el campo ``multilang > locales``. - -En ``multi-language-mode`` el paquete automáticamente utilizará ``Symfony\Cmf\Bundle\SimpleCmsBundle\Document\MultilangPage`` como la ``document_class`` a no ser que explícitamente esté configurada una clase diferente. - -Esta clase de manera predeterminada prefijará todas las rutas con ``/{_locale}``. Este comportamiento se puede desactivar poniendo a ``false`` el segundo parámetro en el constructor del modelo. - -Además, la capa de enrutado será configurada para utilizar ``Symfony\Cmf\Bundle\SimpleCmsBundle\Document\MultilangRouteRepository`` la cuál garantizará que incluso con el prefijo ``locale`` el nodo de contenido correcto será encontrado. Además, automáticamente añade un ``_locale`` el requisito enumerando las regiones actualmente disponibles para la ruta emparejada. - -.. note:: - - Debido a que ``SimpleCmsBundle`` sólo proporciona una sencilla estructura de árbol, todos los nodos tendrán el mismo nombre de nodo para todos los idiomas. Así que una *url* ``http://foo.com/en/bar`` para contenido en inglés se vería cómo ``http://foo.com/de/bar`` para contenido en alemán. A veces puede ser más factible utilizar enteros como nombres de nodo y sencillamente anexarlos al título del nodo en la región dada como una ancla. Así por ejemplo ``http://foo.com/de/1#my title`` y ``http://foo.com/de/1#mein title``. - Si necesitas las *URL* específicas al idioma, quieres usar el enrutado del paquete *CMF* y el paquete de contenido directamente para tener un documento de ruta separado por idioma. - - -Reproduciendo -------------- - -Puedes especificar la plantilla para reproducir una página del ``SimpleCms``, o utilizar un controlador donde luego proporciones el documento de página a la plantilla. Un sencillo ejemplo para tal plantilla es este: - - -.. code-block:: jinja - - {% block content %} - -

    {{ page.title }}

    - -
    {{ page.body|raw }}
    - -
      - {% foreach tag in page.tags %} -
    • {{ tag }}
    • - {% endforeach %} -
    - - {% endblock %} - - -Si tienes habilitado el ``CreateBundle``, también puedes producir el documento con anotaciones *RDF*, permitiéndote editar el contenido así como las etiquetas en la interfaz de usuario. La forma más sencilla es el siguiente bloque de *Twig*: - -.. code-block:: jinja - - {% block content %} - - {% createphp page as="rdf" %} - {{ rdf|raw }} - {% endcreatephp %} - - {% endblock %} - -Si quieres un control más detallado se tendría que mostrar con *RDF*, ve el capítulo :doc:`create`. - -Extendiendo la clase ``Page`` ------------------------------ - -El documento ``Page`` predefinido ``Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page`` es relativamente sencillo, viene con un puñado de las propiedades más comunes para construir una página típica: título, cuerpo, etiquetas, fechas de publicación, etc. - -Si esto no es suficiente para tu proyecto fácilmente puedes proporcionar tu propio documento extendiendo el documento ``Page`` predefinido y colocando los parámetros de configuración explícitamente a tu propia clase ``documento``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - ... - document_class: Acme\DemoBundle\Document\MySuperPage - ... - - -Alternativamente, el documento ``Page`` predefinido contiene una propiedad «extras». Esta almacena una clave --- valor (dónde el valor tiene que ser una cadena o ``null``) el cuál puedes usar para pequeñas adiciones triviales, sin tener que extender el documento ``Page`` predefinido. - -Por ejemplo: - -.. code-block:: php - - $page = new Page(); - - $page->setTitle('Hello World!'); - $page->setBody('Really interesting stuff...'); - - // set extras - $extras = array( - 'subtext' => 'Add CMS functionality to applications built with the Symfony2 PHP framework.', - 'headline-icon' => 'exclamation.png', - ); - - $page->setExtras($extras); - - $documentManager->persist($page); - -Luego, puedes acceder a estas propiedades en tu controlador o plantillas a través de los método ``getExtras()`` o ``getExtra($key)``. diff --git a/_sources/cmf/bundles/tree-browser.txt b/_sources/cmf/bundles/tree-browser.txt deleted file mode 100644 index da999aa..0000000 --- a/_sources/cmf/bundles/tree-browser.txt +++ /dev/null @@ -1,219 +0,0 @@ -``TreeBrowserBundle`` -===================== - -El `TreeBrowserBundle `_ proporciona un árbol de navegación en lo alto de un repositorio *PHPCR*. - -Este paquete consta de dos partes: - - * Árbol de navegación genérico con una ``TreeInterface`` - * Implementación del árbol *PHPCR* e *IGU* para un navegador *PHPCR* - -.. index:: TreeBrowserBundle - -Dependencias ------------- - -* `FOSJsRoutingBundle `_ -* Instala *jQuery*. `SonatajQueryBundle `_ fuertemente recomendado. - -Configurando ------------- - -La clave de configuración para este paquete es ``symfony_cmf_tree_browser`` - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_tree_browser: - session: default - -Enrutado --------- - -El paquete creará las rutas para cada implementación del árbol encontrado. In order to make -those routes available you need to include the following in your routing configuration: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - symfony_cmf_tree: - resource: . - type: 'symfony_cmf_tree' - -Usando ------- - -Tienes :file:`select.js` e :file:`init.js` los cuáles son una envoltura para construir un árbol *jQuery*. Úsalos con ``SelectTree.initTree`` ``resp.`` ``AdminTree.initTree`` - - * ``SelectTree`` en :file:`select.js` es un árbol para seleccionar un nodo para poner el ``id`` a un campo - * ``AdminTree`` en :file:`init.js` es para crear un árbol, mover y editar nodos - -Ambos tienen las siguientes opciones al crearlos: - - * config.selector: selector *jQuery* dónde enganchar en el árbol *js* - * config.rootNode: id para el nodo raíz de tu árbol, predefinido a «/» - * config.selected: id del nodo seleccionado - * config.ajax.children_url: *URL* al controlador que proporciona los hijos de un nodo - * config.routing_defaults: array for route parameters (such as _locale etc.) - * config.path.expanded: ruta de árbol donde el árbol se tendría que expandir al momento - * config.path.preloaded: árbol de rutas en el cual se debería precargar el nodo para acelerar la experiencia del usuario - -Solo :file:`select.js` -~~~~~~~~~~~~~~~~~~~~~~ - - * config.output: Dónde escribir el ``id`` del nodo seleccionado - -Solo :file:`init.js` -~~~~~~~~~~~~~~~~~~~~ - - * config.labels: arreglo conteniendo la traducción para las etiquetas del contexto menú (claves ``'createItem'`` y ``'deleteItem'``) - * config.ajax.move_url: *URL* al controlador para mover un hijo (es decir, asignarle un nuevo nodo padre) - * config.ajax.reorder_url: *URL* al controlador para reorganizar hermanos - * config.types: arreglo indexado con los tipos de nodo conteniendo información sobre ``valid_children``, iconos y rutas disponibles, utilizados para crear el contexto del menú y para comprobación durante las operaciones de movimiento. - -Ejemplos --------- - -Ve en las plantillas del paquete de administración de ``Sonata`` los ejemplos sobre cómo construir el árbol: - -* `init.js `_ -* `select.js `_ (busca ``doctrine_phpcr_type_tree_model_widget``) - -En el mismo paquete el `PhpcrOdmTree `_ implementa la interfaz de árbol y da un ejemplo sobre cómo implementar los métodos. - -Aquí tienes algunos consejos comunes sobre la utilización del ``TreeBrowser``: - -Define tree elements -~~~~~~~~~~~~~~~~~~~~ -The first step, is to define all the elements allowed in the tree and their children. -Have a look at the `cmf-sandbox configuration `_, the section document_tree in sonata_doctrine_phpcr_admin. - -This configuration is set for all your application trees regardless their type (admin or select). - -.. configuration-block:: - - .. code-block:: yaml - - sonata_doctrine_phpcr_admin: - document_tree_defaults: [locale] - document_tree: - Doctrine\ODM\PHPCR\Document\Generic: - valid_children: - - all - Symfony\Cmf\Bundle\ContentBundle\Document\MultilangStaticContent: - valid_children: - - Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock - - Symfony\Cmf\Bundle\BlockBundle\Document\ContainerBlock - - Symfony\Cmf\Bundle\BlockBundle\Document\ReferenceBlock - - Symfony\Cmf\Bundle\BlockBundle\Document\ActionBlock - Symfony\Cmf\Bundle\BlockBundle\Document\ReferenceBlock: - valid_children: [] - ... - - -How to add an admin tree to your page -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -This can be done either in an action template or in a custom block. - -You have to specify the tree root and the selected item, this allows you to have different type of content in your tree. - -In this example, we will have the menu elements. - -For Symfony 2.2 and later - -.. configuration-block:: - - .. code-block:: jinja - - {% render(controller('sonata.admin.doctrine_phpcr.tree_controller:treeAction')) with { 'root': websiteId~"/menu", 'selected': menuNodeId, '_locale': app.request.locale } %} - - -For Symfony 2.1 - -.. configuration-block:: - - .. code-block:: jinja - - {% render 'sonata.admin.doctrine_phpcr.tree_controller:treeAction' with { 'root': websiteId~"/menu", 'selected': menuNodeId, '_locale': app.request.locale } %} - - -How to customize the tree behaviour -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -The TreeBrowserBundle is based on `jsTree `_. jsTree works with events, dispatched everytime the user does an action. - -A simple way to customize the tree behavior is to bind your actions to those events. - -If you have a look at init.js and select.js, you will notice that actions are already bound to some of the tree events. If the default behavior is not -what you need, JQuery provide the unbind function to solve the problem. - -Here is a simple way to remove the context menu from the admin tree (add the -``controller`` call around the controller name inside ``render`` for Symfony 2.2) : - -.. configuration-block:: - - .. code-block:: jinja - - {% render 'sonata.admin.doctrine_phpcr.tree_controller:treeAction' with { 'root': websiteId~"/menu", 'selected': menuNodeId, '_locale': app.request.locale } %} - - - -By default, the item selection open the edit route of the admin class of the element. This action is bind to the "select_node.jstree". - -If you want to remove it, you just need to call the unbind function on this event : - -.. configuration-block:: - - .. code-block:: jinja - - - -Then you can bind it on another action. - -For example, if your want to open a custom action : - -.. configuration-block:: - - .. code-block:: jinja - - $('#tree').bind("select_node.jstree", function (event, data) { - if ((data.rslt.obj.attr("rel") == 'Symfony_Cmf_Bundle_MenuBundle_Document_MenuNode' - || data.rslt.obj.attr("rel") == 'Symfony_Cmf_Bundle_MenuBundle_Document_MultilangMenuNode') - && data.rslt.obj.attr("id") != '{{ menuNodeId }}' - ) { - var routing_defaults = {'locale': '{{ locale }}', '_locale': '{{ _locale }}'}; - routing_defaults["id"] = data.rslt.obj.attr("url_safe_id"); - window.location = Routing.generate('presta_cms_page_edit', routing_defaults); - } - }); - -Don't forget to add your custom route to the fos_js_routing.routes_to_expose configuration : - -.. configuration-block:: - - .. code-block:: yaml - - fos_js_routing: - routes_to_expose: - - symfony_cmf_tree_browser.phpcr_children - - symfony_cmf_tree_browser.phpcr_move - - sonata.admin.doctrine_phpcr.phpcrodm_children - - sonata.admin.doctrine_phpcr.phpcrodm_move - - presta_cms_page_edit - diff --git a/_sources/cmf/bundles/tree.txt b/_sources/cmf/bundles/tree.txt deleted file mode 100644 index bab3e87..0000000 --- a/_sources/cmf/bundles/tree.txt +++ /dev/null @@ -1,121 +0,0 @@ -``TreeBundle`` -============== - -Este paquete es obsoleto puesto que su contenido se movió al `TreeBrowserBundle `_. - -.. index:: TreeBundle - -Dependencias ------------- - -* Instala *jQuery*. `SonatajQueryBundle `_ fuertemente recomendado. - -Configurando ------------- - -No hay ninguna configuración para este paquete. - -Características ---------------- - - * Expandiendo/colapsando nodos. - * Cargando subárboles relajadamente vía respuestas *JSON* para llamadas *AJAX*. - * Función retrollamada al conmutar un nodo. - -Usando ------- - - * Incluye archivos *CSS* y *JS* en tu plantilla. - * Llama a ``$("#tree").jstree({/* parámetros */})`` --- suponiendo que aquí ``#tree`` es el selector de tu lista. - * Proporciona el valor *url* que apunta a alguna cosa de lado del servidor regresando listas de nodos hijo para cualquier ID de nodo dado. - -El servidor tiene que responder en formato *JSON*, este es un ejemplo: - -.. code-block:: javascript - - [ - {"data":"root","attr":{"id":"root","rel":"folder"},"state":"closed","children": - [ - {"data":"content","attr":{"id":"child1","rel":"folder"},"state":"closed"}, - {"data":"menu","attr":{"id":"child2","rel":"folder"},"state":"closed"}, - {"data":"routes","attr":{"id":"child3","rel":"folder"},"state":"closed"} - ] - } - ] - -Disponemos de más información sobre la configuración en el `sitio web de jsTree `_. - -Ejemplo *HTML* --------------- - -.. code-block:: html - - - - CMF Sandbox - Treeview test - - - - - - - - - - - - -
      -
    - -
    - -
    - - {% block content %} - Hello {{ name }}! - {% endblock %} - - diff --git a/_sources/cmf/components/routing.txt b/_sources/cmf/components/routing.txt deleted file mode 100644 index 148d6ff..0000000 --- a/_sources/cmf/components/routing.txt +++ /dev/null @@ -1,212 +0,0 @@ -Enrutando -========= - -La biblioteca del `componente Routing del CMF de Symfony `_ extiende el componente ``routing`` del núcleo de *Symfony2*. Incluso, aunque este tiene *Symfony* en su nombre, no necesita la plataforma *Symfony2* completa y se puede utilizar en proyectos independientes. Para integrarlo con *Symfony* proporcionamos el :doc:`../bundles/routing-extra`. - -En el núcleo del ``CMF`` de *Symfony* el componente de enrutado es la ``ChainRouter``, que se utiliza en vez del sistema de enrutamiento predefinido de *Symfony2*. La ``ChainRouter`` puede encadenar varias implementaciones de la ``RouterInterface``, una tras otra, para determinar cuál debería manejar cada petición. Puedes añadir a esta cadena el enrutador predeterminado de *Symfony2*, por lo tanto aún puedes utilizar el mecanismo de enrutado estándar. - -Adicionalmente, este componente pretende proporcionar útiles implementaciones de las interfaces de enrutado. Actualmente, proporciona el ``DynamicRouter``, el cuál utiliza una ``RequestMatcherInterface`` para cargar ``Rutas`` dinámicamente, y puede aplicar las estrategías de la ``RouteEnhancerInterface`` con el fin de manipularlas. El ``NestedMatcher`` proporcionado puede recuperar dinámicamente objetos `Route `_ de *Symfony2* -desde una ``RouteProviderInterface``. Estas interfaces abstraen una colección de ``Rutas``, que puedes almacenar en una base de datos, tal como *PHPCR-ODM* de *Doctrine* o el *ORM* de *Doctrine*. El ``DynamicRouter`` también utiliza una instancia del ``UrlGenerator`` para generar rutas y proporciona una implementación de esta bajo el ``ProviderBasedGenerator`` el cual puede generar las rutas cargadas desde una instancia de la ``RouteProviderInterface``, y en lo alto del ``ContentAwareGenerator`` para determinar el objeto ``ruta`` a partir de un objeto ``Contenido``. - -.. note:: - - Para utilizar este componente fuera del contexto de la plataforma *Symfony2*, tienes que ver el componente `Routing `_ en el núcleo de *Symfony2* para entender su fundamento. El enrutador del ``CMF`` justo extiende el comportamiento básico. - -.. index:: Routing - -Dependencias ------------- - -Este componente usa `composer `_. It needs the -Symfony2 Routing component and the Symfony2 HttpKernel (for the logger -interface and cache warm-up interface). - -Para el ``DynamicRouter`` necesitarás algo para implementar la ``RouteProviderInterface``. Se sugiere utilizar *Doctrine* puesto que proporciona una manera fácil de asociar clases a una base de datos. - -``ChainRouter`` ---------------- - -En el núcleo del ``CMF`` de *Symfony* el componente de enrutado es la ``ChainRouter``. -Este se utiliza en vez del sistema de enrutado predefinido de *Symfony2*, y es responsable de determinar los parámetros de cada petición. Típicamente necesitas determinar qué controlador manejará esta petición ---en la pila completa de la plataforma *Symfony2*, este está identificado por el campo ``_controller`` de los parámetros---. - -La ``ChainRouter`` trabaja aceptando un conjunto priorizado de implementaciones de estrategias de enrutado, `RouterInterface `_, generalmente conocidos como ``«Enrutadores»``. - -Al manejar una petición entrante, la ``ChainRouter`` itera sobre los ``enrutadores`` configurados, por su prioridad configurada, hasta que uno de ellos es capaz de `emparejar `_ -la petición y proporcionar sus parámetros. - -Enrutadores ------------ - -La ``ChainRouter`` en sí misma, es incapaz de tomar alguna decisión de enrutado real. -Its sole responsibility is managing the given set of Routers, which are responsible for matching a request and determining its parameters. - -You can easily create your own Routers by implementing -`RouterInterface `_ -but Symfony CMF already includes a powerful route matching system that you can -extend to your needs. - -.. note:: - - Si lo estás utilizando como parte de un proyecto completo con el ``CMF`` de *Symfony*, por favor, revisa el :doc:`../bundles/routing-extra` para instrucciones sobre cómo añadir ``Enrutadores`` a la ``ChainRouter``. De lo contrario, usa el método ``add`` de la ``ChainRouter`` para configurar nuevos ``Enrutadores``. - -Enrutador predefinido de *Symfony2* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El mecanismo de enrutado de *Symfony2* en sí mismo es una implementación de la ``RouterInterface``, lo cual significa que lo puedes utilizar como ``Enrutador`` en la ``ChainRouter``. Esto te permite utilizar las declaraciones del sistema de enrutado predefinido. - -Enrutador dinámico -~~~~~~~~~~~~~~~~~~ - -El ``Enrutador`` predefinido de *Symfony2* se desarrolló para manejar definiciones de ``Rutas`` estáticas, puesto que estas tradicionalmente son declaradas en archivos de configuración, antes de ejecutar la aplicación. -Esto lo vuelve una elección pobre para manejar rutas definidas dinámicamente, y para manejar tales situaciones, este paquete viene con el ``DynamicRouter``. Este es capaz de manejar rutas desde datos fuente más dinámicos, tal como los almacenados en bases de datos, y modificar los parámetros resultantes que utilizan un conjunto de potenciadores que puedes configurar fácilmente, muchos de ellos extendiendo la funcionalidad predefinida de *Symfony2*. - -Emparejador -^^^^^^^^^^^ - -El ``DynamicRouter`` utiliza una instancia de la ``RequestMatcherInterface`` o de la ``UrlMatcherInterface`` para emparejar la ``Petición`` o *URL* recibida, a un arreglo de parámetros, respectivamente. -La lógica de emparejado real depende de la implementación subyacente que elijas. -You can easily use you own matching strategy by passing it to the ``DynamicRouter`` -constructor. Como parte de este paquete, ya proporciona un ``NestedMatcher`` que puedes utilizar inmediatamente, o como referencia para tu propia implementación. - - -Su otra característica son las estrategias utilizadas por la ``RouteEnhancerInterface`` para inferir parámetros de enrutado a partir de la información proporcionada por la ruta coincidente (ve abajo). - -``NestedMatcher`` -^^^^^^^^^^^^^^^^^ - -La implementación de la ``RequestMatcherInterface`` proporcionada es ``NestedMatcher``. -It is suitable for use with ``DynamicRouter``, and it uses a multiple step -matching process to determine the resulting routing parameters from a given -`Request `_. - -Esta utiliza una implementación de la ``RouteProviderInterface``, la cual es capaz de cargar objetos candidatos a `Ruta `_ para una ``Petición`` dinámica desde una fuente de datos. Although it can be used -in other ways, the ``RouteProviderInterface``'s main goal is to be easily -implemented on top of Doctrine PHPCR ODM or a relational database, -effectively allowing you to store and manage routes dynamically from database. - -El ``NestedMatcher`` utiliza un proceso de emparejamiento de 3 pasos para determinar qué ``Ruta`` utilizar al manejar la ``Petición`` actual: - -* Pregunta a la ``RouteProviderInterface`` por la potencial colección de instancias de ``Ruta`` que emparejan la ``Petición`` -* Aplica todas las ``RouteFilterInterface`` para filtrar esa colección -* Permite que la instancia de ``FinalMatcherInterface`` decida la mejor coincidencia entre las restantes instancias de ``Ruta`` y la transforma en el arreglo de parámetros. - -``RouteProviderInterface`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Basándose en la ``Petición``, el ``NestedMatcher`` recuperará una colección ordenada de objetos ``Ruta`` desde la ``RouteProviderInterface``. La idea de esta proveedora es proporcionar todas las rutas que potencialmente podrían coincidir, pero **no** para hacer ninguna elaborada operación de emparejamiento todavía ---este es el trabajo de los siguientes pasos---. - -La implementación subyacente de la ``RouteProviderInterface`` no está en el alcance de este paquete. Por favor, revisa la declaración de la interfaz para más información. Para un ejemplo funcionando, ve el `RoutingExtraBundle `_. - -``RouteFilterInterface`` -~~~~~~~~~~~~~~~~~~~~~~~~ - -El ``NestedMatcher`` puede aplicar las implementaciones provistas por el usuario de la ``RouteFilterInterface`` para reducir los objetos ``Ruta`` proporcionados, p. ej. haciendo una negociación de contenido. -Es responsabilidad de cada filtro lanzar la ``ResourceNotFoundException`` si no quedan más rutas en la colección. - -``FinalMatcherInterface`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -La implementación de la ``FinalMatcherInterface`` tiene que determinar una ``Ruta`` exactamente como la mejor coincidencia o lanzar una excepción si ninguna empareja adecuadamente. La implementación predefinida utiliza el `UrlMatcher `_ -del componente ``Routing`` de *Symfony*. - -.. _component-routing-enhancers: - -Potenciadores de ruta -^^^^^^^^^^^^^^^^^^^^^ - -Optionally, and following the matching process, a set of ``RouteEnhancerInterface`` -instances can be applied by the ``DynamicRouter``. El objetivo de estas es permitirte manipular los parámetros de la ruta emparejada. Puedes utilizarlas, por ejemplo, para asignar dinámicamente un controlador o una plantilla a una ``Ruta`` o para ``convertir`` un parámetro de la ``Petición`` a objeto. Algunos potenciadores sencillos ya vienen incorporados en el paquete, puedes encontrar la documentación dentro de cada archivo de clase. - -Enlazando una ``Ruta`` con un ``Contenido`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Dependiendo de la lógica de tu aplicación, una *URL* solicitada puede tener contenido asociado desde la base de datos. Estas ``Rutas`` deberían implementar la ``RouteObjectInterface``, y opcionalmente pueden regresar una instancia del modelo. Si configuras la ``RouteContentEnhancer``, esta debe incluir ese contenido en el arreglo emparejado, con la clave ``_content``. Ten en cuenta que una ``Ruta`` puede implementar la interfaz mencionada anteriormente pero todavía no regresar ninguna instancia del modelo, en cuyo caso ningún objeto asociado será regresado. - -Además, los enrutadores que implementen esta interfaz también pueden proporcionar un nombre de ruta personalizado. La clave devuelta por ``getRouteKey`` será utilizada como el nombre de ruta en vez del nombre de ruta compatible con el núcleo de *Symfony* y puede contener cualquiera de los caracteres del juego de caracteres actual. Esto te permite, por ejemplo, poner una ruta como el nombre de la ruta. Ambos ``UrlMatchers`` proporcionados con el ``NestedMatcher`` reemplazan la clave ``_route`` con la instancia de la ``ruta`` y ponen el nombre proporcionado a ``_route_name``. - -Todos los enrutadores todavía necesitan extender la clase base ``Symfony\Component\Routing\Route``. - -Redirecciones -^^^^^^^^^^^^^ - -Puedes construir redirecciones implementando la ``RedirectRouteInterface``. -Esta puede redirigir a una *URI* absoluta, a una ``Ruta`` nombrada que puede generar cualquier ``Enrutador`` en la cadena o a otro objeto ``Ruta`` provisto por el enrutador. - -Ten en cuenta que la redirección lógica real no es manejada por el paquete. Deberías implementar tu propia lógica para manejar la redirección. Para un ejemplo implementando esta redirección bajo la pila completa de *Symfony2*, revisa :doc:`../bundles/routing-extra`. - - -Generando *URL* -~~~~~~~~~~~~~~~ - -Aparte de emparejar una petición entrante a un conjunto de parámetros, un ``Enrutador`` también es responsable de generar una *URL* a partir de una ``Ruta`` y sus parámetros. -La ``ChainRouter`` itera sobre sus enrutadores hasta que uno de ellos es capaz de generar una *URL* concordante. - - -Aparte de utilizar una ``RequestMatcherInterface`` o una ``UrlMatcherInterface`` para emparejar una ``Petición``/*URL* a sus parámetros correspondientes, el ``DynamicRouter`` también utiliza una instancia de la ``UrlGeneratorInterface``, misma que te permite generar una *URL* desde una ``Ruta``. - -The included ``ProviderBasedGenerator`` extends Symfony2's default -`UrlGenerator `_ -(which, in turn, implements ``UrlGeneratorInterface``) and - if $name is -not already a ``Route`` object - loads the route from the ``RouteProviderInterface``. -Entonces permite que la lógica del núcleo genere la *URL* de esa ``Ruta``. - -El paquete también incluye el ``ContentAwareGenerator``, el cual extiende el ``ProviderBasedGenerator`` para comprobar si el ``$name`` es un objeto implementando la ``RouteAwareInterface`` y, de ser así, consigue la ``Ruta`` a partir del ``contenido``. -Usando el ``ContentAwareGenerator``, puedes generar direcciones *URL* para tu contenido de tres maneras: - -* O bien, pasa un objeto ``Ruta`` como ``$name`` -* O pasa un objeto ``RouteAwareInterface`` que es tu contenido como ``$name`` -* O proporciona una implementación de la ``ContentRepositoryInterface`` y pasa el ``id`` del objeto ``Contenido`` como el parámetro ``content_id`` y ``null`` como ``$name``. - -.. _component-route-generator-and-locales: - -``ContentAwareGenerator`` y regiones -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes usar el valor predeterminado de ``_locale`` en una ``Ruta`` para crear una ruta por región, todas refiriéndose a la misma instancia del contenido multilingüe. El ``ContentAwareGenerator`` respeta el ``_locale`` al generar rutas a partir de instancias de ``Contenido``. Al resolver la ruta, el ``_locale`` se obtiene desde la petición y es recogido por el sistema regional de *Symfony2*. - -.. note:: - - Bajo *PHPCR-ODM*, las rutas nunca deberían ser documentos traducibles, puesto que un documento de ``Ruta`` representa una *URL* única, y no es recomendable servir varias traducciones bajo la misma *URL*. - - Si necesitas direcciones *URL* traducidas, haz que la región forme parte del nombre de la ruta. - - -Personalizando --------------- - -Los paquetes de enrutado cuentan con muchas opciones de personalización, dependiendo de tus necesidades específicas: - -* Puedes implementar tu propio ``RouteProvider`` para cargar rutas desde una fuente diferente -* Tus parámetros de ``Ruta`` se pueden manipular fácilmente utilizando potenciadores existentes -* También puedes añadir tus propios potenciadores al ``DynamicRouter`` -* Puedes añadir instancias de la ``RouteFilterInterface`` al ``NestedMatcher`` -* El ``DynamicRouter`` o sus componentes se pueden extender para permitir modificaciones -* Puedes implementar tus propios ``Enrutadores`` y añadirlos a la ``ChainRouter`` - -.. note:: - - Si sientes que tu potenciador o ``Enrutador`` específico le puede ser útil a otros, contáctanos e intentaremos incluirlo en el paquete. - -Integrando con *Symfony2* -------------------------- - -Tal como mencioné antes, este paquete fue diseñado para sólo requerir ciertas partes de *Symfony2*. Aun así, si deseas utilizarlo como parte de tu proyecto ``CMF`` de *Symfony*, también hay disponible un paquete de integración. Te recomendamos que le eches un vistazo en :doc:`../bundles/routing-extra`. - -Para una guía introductoria al paquete ``Routing`` y su integración con *Symfony2*, consulta :doc:`../getting-started/routing`. - -We strongly recommend reading Symfony2's -`Routing `__ -component documentation page, as it's the base of this bundle's implementation. - -Autores -------- - -* Filippo De Santis (p16) -* Henrik Bjornskov (henrikbjorn) -* Claudio Beatrice (omissis) -* Lukas Kahwe Smith (lsmith77) -* David Buchmann (dbu) -* Larry Garfield (Crell) -* `Y otros `_ - -El código original para la cadena de enrutadores fue aportado por Magnus Nordlander. diff --git a/_sources/cmf/contributing/code.txt b/_sources/cmf/contributing/code.txt deleted file mode 100644 index 8171cf0..0000000 --- a/_sources/cmf/contributing/code.txt +++ /dev/null @@ -1,15 +0,0 @@ -Colaborando -=========== - -El equipo del ``CMF`` de *Symfony2* sigue todas las reglas y directrices del `proceso de desarrollo `_ que el núcleo de *Symfony2*. - -Recursos / enlaces ------------------- - -* `GitHub `_ -* `Sitio web `_ -* `Wiki `_ -* `Rastreador de fallos `_ -* `Canal IRC `_ -* `Lista de correo para usuarios `_ -* `Lista de correo para desarrolladores `_ diff --git a/_sources/cmf/contributing/license.txt b/_sources/cmf/contributing/license.txt deleted file mode 100644 index e110e98..0000000 --- a/_sources/cmf/contributing/license.txt +++ /dev/null @@ -1,58 +0,0 @@ -Licencia -======== - -El objetivo del ``CMF`` de *Symfony2* es proporcionar licencias de código abierto liberales para su pila completa. - -Código ------- - -La pila del código está cubierta por la `licencia de Apache`_ para ``Jackalope`` y *PHPCR*, el resto de la pila, notablemente el código *Symfony2*, *PHPCR-ODM*, :file:`create.js` y :file:`Hallo.js` está `licenciado bajo MIT`_. Por favor, refiérete a los archivos de LICENCIA pertinentes en la fuente de los paquetes dados. - -Documentación -------------- - -La documentación de *Symfony2* está bajo una licencia `Creative Commons Attribution-Share Alike 3.0 Unported License`_. - -**Estás en libertad:** - -* para *Compartir* — copiar, distribuir y trasmitir públicamente la obra; - -* para *Derivar* — adaptando la obra. - -**Bajo las siguientes condiciones:** - -* *Atribución* — Debes atribuir el trabajo de la manera especificada por - el autor o licenciador (pero de ninguna manera que sugiera que apoya tu - uso de la obra). - -* *Compartir bajo la misma licencia* — Si alteras, transformas, o creas - sobre esta obra, sólo podrás distribuir la obra resultante bajo una - licencia idéntica o similar a esta. - -**En el entendido de:** - -* *Renuncia* — Cualquiera de estas condiciones puede no aplicarse si - obtienes el permiso del titular de los derechos de autor; - -* *Dominio Público* — Cuando la obra o cualquiera de sus elementos es del - dominio público bajo la legislación aplicable, este estatus de ninguna - manera es afectado por la licencia; - -* *Otros Derechos* — De ninguna manera cualquiera de los siguientes derechos - se ve afectado por la licencia: - - * Tu derecho leal o uso justo, u otros derechos de autor excepciones y limitaciones aplicables; - - * Los derechos morales del autor; - - * Otras personas pueden tener derechos ya sea en la propia obra o en la forma en que se utiliza la obra, como los derechos de publicidad o privacidad. - -* *Atención* — Para cualquier reutilización o distribución, debes dejar - claros los términos de la licencia de esta obra. La mejor manera de hacerlo es con un enlace a esta página web. - -Este es un resumen humanamente legible del `Texto legal (Licencia completa)`_. - -.. _`licencia de Apache`: http://en.wikipedia.org/wiki/Apache_license -.. _`licenciado bajo MIT`: http://en.wikipedia.org/wiki/MIT_License -.. _`Creative Commons Attribution-Share Alike 3.0 Unported License`: http://creativecommons.org/licenses/by-sa/3.0/ -.. _`Texto legal (Licencia completa)`: http://creativecommons.org/licenses/by-sa/3.0/legalcode \ No newline at end of file diff --git a/_sources/cmf/cookbook/installing-cmf-sandbox.txt b/_sources/cmf/cookbook/installing-cmf-sandbox.txt deleted file mode 100644 index 7794988..0000000 --- a/_sources/cmf/cookbook/installing-cmf-sandbox.txt +++ /dev/null @@ -1,263 +0,0 @@ -Instalando el recinto de seguridad del ``CMF`` -============================================== - -Esta guía muestra cómo instalar el recinto de seguridad del *CMF* de *Symfony*, una demostración de la plataforma dirigida a exhibir las características básicas de la herramienta corriendo en un entorno ``demo``. -La puedes usar para evaluar la plataforma o para ver código real en acción, ayudándote a entender la herramienta. - -Si bien lo puedes utilizar tal cual, este recinto de seguridad no pretende ser una plataforma de desarrollo. Si estás buscando instrucciones de instalación para una configuración de desarrollo, por favor revisa: - -- la página :doc:`../getting-started/installing-symfony-cmf` para instrucciones sobre cómo instalar rápidamente el *CMF* (recomendado para desarrollo) -- :doc:`../tutorials/installing-cmf-core` para una instalación paso a paso y detalles de configuración (si quieres conocer todos los detalles) - -.. index:: sandbox, instalación, recinto de seguridad - -Requisitos previos ------------------- - -Debido a que el ``CMF`` de *Symfony* está basado en *Symfony2*, deberías asegurarte de que cubres los `Requisitos para que funcione Symfony2 `_. -`Git 1.6+ `_, `Curl `_ y *PHP* ``Intl`` también son necesarios para seguir los pasos de instalación enumerados más adelante. - -If you wish to use Jackalope + Apache JackRabbit as the storage medium (recommended), -you will also need Java (JRE). Para otros mecanismos y sus requisitos, por favor refiérete a sus respectivas secciones. - -Instalando ----------- - -*Apache* ``Jackrabbit`` -~~~~~~~~~~~~~~~~~~~~~~~ - -El recinto de seguridad del ``CMF`` de *Symfony* de manera predeterminada usa ``Jackalope`` con *Apache* ``JackRabbit``. -Puedes configurar métodos de almacenamiento alternativo, pero este es el más probado, y debería ser el más fácil de configurar. - -Puedes conseguir la más reciente versión de *Apache* ``Jackrabbit`` en la página de descarga oficial del `proyecto `_. -Para lanzarlo, usa la siguiente orden: - -.. code-block:: bash - - java -jar jackrabbit-standalone-*.jar - -By default the server is listening on the 8080 port, you can change this -by specifying the port on the command line. - -.. code-block:: bash - - java -jar jackrabbit-standalone-*.jar --port 8888 - -For unix systems, you can get the start-stop script for /etc/init.d `here `_ - -Consiguiendo el código del recinto de seguridad -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El código fuente del recinto de seguridad del ``CMF`` de *Symfony* está disponible en *github*. Para conseguirlo usa: - -.. code-block:: bash - - git clone git://github.com/symfony-cmf/cmf-sandbox.git - -Ve al directorio y copia los archivos de configuración predefinidos: - -.. code-block:: bash - - cd cmf-sandbox - cp app/config/parameters.yml.dist app/config/parameters.yml - cp app/config/phpcr_jackrabbit.yml.dist app/config/phpcr.yml - -These two files include the default configuration parameters for the sandbox -storage mechanism. Los puedes modificar para cubrir mejor tus necesidades. - -.. note:: - - The second configuration file refers to specific jackalope + - jackrabbit configuration. There are other files available for - different stack setups. - -Next, get composer and install and the necessary bundles (this may take a while) - -.. code-block:: bash - - curl -s http://getcomposer.org/installer | php -- - php composer.phar install - -.. note:: - - En Windows necesitas ejecutar el intérprete de ordenes como administrador o editar el archivo :file:`composer.json`y cambiar la línea ``"symfony-assets-install": "symlink"`` a ``"symfony-assets-install": ""``. Si fallas al hacerlo podrías recibir: -.. code-block:: bash - - [Symfony\Component\Filesystem\Exception\IOException] - Imposible crear el enlace simbólico debido al error código 1314: 'El cliente no cuenta con un privilegio requerido'. ¿Tienes el privilegio de administrador requerido? - -Preparando el repositorio *PHPCR* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Now that you have all the code, you need to setup your PHPCR repository. -PHPCR organizes data in workspaces, and sandbox uses the "default" workspace, -which is exists by default in Jackrabbit. If you use other applications that -require Jackrabbit, or if you just wish to change the workspace name, you -can do so in app/config/phpcr.yml. La siguiente orden creará un nuevo espacio de trabajo llamado ``«sandbox»`` en ``Jackrabbit``. Si decides utilizar el espacio de trabajo ``«default»``, la puedes omitir. - -.. code-block:: bash - - app/console doctrine:phpcr:workspace:create sandbox - -Once your workspace is set up, you need to `register the node types `_ for phpcr-odm: - -.. code-block:: bash - - app/console doctrine:phpcr:repository:init - -Import the fixtures -~~~~~~~~~~~~~~~~~~~ - -La interfaz de administración sigue en una etapa temprana. Hasta que mejore, lo más fácil es crear los datos programáticamente. La mejor manera de hacerlo es con los accesorios de *Doctrine*. El ``DoctrinePHPCRBundle`` incluido en el repositorio ``symfony-cmf`` proporciona una orden para cargar accesorios. - -.. code-block:: bash - - app/console -v doctrine:phpcr:fixtures:load - -Ejecuta esta orden para cargar los accesorios desde el ``MainBundle`` del recinto de seguridad, la cual poblará tu repositorio con datos maniquí, es decir, carga las páginas ``demo``. - -Accediendo a tu recinto de seguridad -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El recinto de seguridad ahora debería ser accesible en tu servidor *web*. - -.. code-block:: text - - http://localhost/app_dev.php - -Para ejecutar el recinto de seguridad en modo de producción necesitas generar los delegados de *Doctrine* y volcar los activos de ``assetic``: - -.. code-block:: text - - app/console cache:warmup --env=prod --no-debug - app/console assetic:dump --env=prod --no-debug - - -Mecanismos de almacenamiento alternativo ----------------------------------------- - -El ``CMF`` de *Symfony* y el recinto de seguridad son de almacenamiento agnóstico, lo cual significa que puedes cambiar el mecanismo de almacenamiento sin tener que cambiar tu código. El mecanismo de almacenamiento predefinido para el recinto de seguridad es ``Jackalope`` + *Apache* ``Jackrabbit``, puesto que es la configuración más probada y estable. Sin embargo, hay otras alternativas disponibles. - -``Jackalope`` + *DBAL* de *Doctrine* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. note:: - - De manera predeterminada, cuándo utilizas el *DBAL* de *Doctrine*, los datos se almacenan utilizando una base de datos `Sqlite `_. - Revisa la página del proyecto para instrucciones de instalación. - Si deseas utilizar otros sistemas de base de datos, cambia los parámetros de configuración en el archivo :file:`app/config/parameters.yml`. Consulta la página sobre la `configuración del DBAL de Doctrine en Symfony `_ o la `documentación de Doctrine `_ para más información. - -Ve al directorio del recinto de seguridad y copia el archivo de configuración predefinido para -configurar el *DBAL* de *Doctrine*: - -.. code-block:: bash - - cd cmf-sandbox - cp app/config/phpcr_doctrine_dbal.yml.dist app/config/phpcr.yml - -Next, you need to install the actual Doctrine DBAL bundle required by jackalope: - -.. code-block:: bash - - php composer.phar require jackalope/jackalope-doctrine-dbal:dev-master - -Y crea e inicia tu base de datos: - -.. code-block:: bash - - app/console doctrine:database:create - app/console doctrine:phpcr:init:dbal - -Después de esto, deberías seguir los pasos en `Preparando el repositorio PHPCR`_. - -Memorizando en caché con *Doctrine* -+++++++++++++++++++++++++++++++++++ - -Opcionalmente, para mejorar el rendimiento y habilitar los metadatos, puedes instalar ``LiipDoctrineCacheBundle`` -escribiendo la siguiente orden: - -.. code-block:: bash - - php composer.phar require liip/doctrine-cache-bundle:dev-master - -And adding the following entry to your app/AppKernel.php: - -.. code-block:: php - - // app/AppKernel.php - public function registerBundles() - { - $bundles = array( - // ... - new Liip\DoctrineCacheBundle\LiipDoctrineCacheBundle(), - // ... - ); - } - -Finalmente descomenta las opciones de caché en el archivo :file:`phpcr.yml` así como las de ``liip_doctrine_cache`` en el archivo :file:`config.yml`. - -.. code-block:: yaml - - # app/config/phpcr.yml - caches: - meta: liip_doctrine_cache.ns.meta - nodes: liip_doctrine_cache.ns.nodes - -.. code-block:: yaml - - # app/config/config.yml - - # jackalope doctrine caching - liip_doctrine_cache: - namespaces: - meta: - type: file_system - nodes: - type: file_system - -Proveyendo *PHPCR* para *Midgard2* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si quieres correr el recinto de seguridad del ``CMF`` con el proveedor `PHPCR de Midgard2 `_ en vez de ``Jackrabbit``, necesitas instalar la extensión ``midgard2`` de *PHP*. -En sistemas *Debian/Ubuntu* actuales, esto se hace simplemente con: - -.. code-block:: bash - - sudo apt-get install php5-midgard2 - -En *OS X* lo puedes instalar utilizando cualquier `Homebrew `_ con: - -.. code-block:: bash - - brew install midgard2-php - -o `MacPorts `_ con - -.. code-block:: bash - - sudo port install php5-midgard2 - -You also need to download `midgard_tree_node.xml `_ -and `midgard_namespace_registry.xml `_ -schema files, and place them into "/schema" (defaults to "/usr/share/midgard2/schema") - -To have the Midgard2 PHPCR implementation installed run the following additional command: - -.. code-block:: bash - - php composer.phar require midgard/phpcr:dev-master - -Finalmente, cambia uno de los archivos de configuración de ``Midgard2``: - -.. code-block:: bash - - cp app/config/phpcr_midgard_mysql.yml.dist app/config/phpcr.yml - -o - -.. code-block:: bash - - cp app/config/phpcr_midgard_sqlite.yml.dist app/config/phpcr.yml - -After this, your should follow the steps in `Preparing the PHPCR repository`_ -to continue the installation process. \ No newline at end of file diff --git a/_sources/cmf/cookbook/phpcr-odm-custom-documentclass-mapper.txt b/_sources/cmf/cookbook/phpcr-odm-custom-documentclass-mapper.txt deleted file mode 100644 index ebe0fd4..0000000 --- a/_sources/cmf/cookbook/phpcr-odm-custom-documentclass-mapper.txt +++ /dev/null @@ -1,34 +0,0 @@ -Usando una clase documento personalizada asociada con *PHPCR-ODM* -================================================================= - -El asignador predefinido para la clase ``document`` *PHPCR-ODM* utiliza el atributo ``phpcr:class`` para almacenar y recuperar la clase ``documento`` de un nodo. Cuándo accedes a un repositorio *PHPCR* existente, podrías necesitar diferente lógica para decidir en la clase. - -Puedes extender el ``DocumentClassMapper`` o implementar la ``DocumentClassMapperInterface`` desde el principio. Los métodos importantes son ``getClassName`` que se necesita para encontrar el nombre de clase y ``writeMetadata`` que se necesita para garantizar que la clase de un nuevo documento almacenado se puede determinar al cargarlo de nuevo. - -Luego, puedes sustituir el servicio ``doctrine.odm_configuration`` para llamar a ``setDocumentClassMapper`` en él. Un ejemplo del `recinto de seguridad del CMF de Symfony`_ (rama de magnolia_integration): - -.. configuration-block:: - - .. code-block:: yaml - - # Resources/config/services.yml - - # Si quieres sobrescribir la configuración predefinida, de lo contrario utiliza - # un nombre personalizado y pónlo en el bloque de configuración del odm - - doctrine.odm_configuration: - class: %doctrine_phpcr.odm.configuration.class% - calls: - - [ setDocumentClassMapper, [@sandbox_magnolia.odm_mapper] ] - - sandbox_magnolia.odm_mapper: - class: "Sandbox\MagnoliaBundle\Document\MagnoliaDocumentClassMapper" - arguments: - - 'standard-templating-kit:pages/stkSection': 'Sandbox\MagnoliaBundle\Document\Section' - -Aquí creamos una asociación que usa una configuración para leer información del nodo y se asocia a una clase ``documento``. - -Si tienes varios repositorios, puedes usar una configuración por repositorio. -See :ref:`bundle-phpcr-odm-multiple-phpcr-sessions`. - -.. _`recinto de seguridad del CMF de Symfony`: https://github.com/symfony-cmf/cmf-sandbox/tree/magnolia_integration \ No newline at end of file diff --git a/_sources/cmf/cookbook/using-a-custom-route-repository.txt b/_sources/cmf/cookbook/using-a-custom-route-repository.txt deleted file mode 100644 index 2b3666a..0000000 --- a/_sources/cmf/cookbook/using-a-custom-route-repository.txt +++ /dev/null @@ -1,88 +0,0 @@ -Usando un repositorio de rutas personalizado con un enrutador dinámico -====================================================================== - -El enrutador dinámico te permite personalizar la ruta al repositorio (es decir, la clase responsable de recuperar rutas desde la base de datos), y por extensión, los objetos ``Ruta``. - -Creando el repositorio de rutas -------------------------------- - -El repositorio de rutas tiene que implementar la `RouteRepositoryInterface`. La siguiente clase proporciona una solución sencilla que utiliza un repositorio *ODM*. - -.. code-block:: php - - findOneBy(array( - 'url' => $url, - )); - - $pattern = $myDocument->getUrl(); // p. ej. "/esta/es/una/url" - - $collection = new RouteCollection(); - - // crea una nueva ruta y pone nuestro documento como - // predefinido (a modo de que lo podamos recuperar desde la petición) - $route = new SymfonyRoute($ep->getPath(), array( - 'document' => $document, - )); - - // Añade la ruta al RouteCollection usando - // como clave un ID único. - $collection->add('my_route_'.uniqid(), $route); - - return $collection; - } - - // este método se usa para generar URL, p. ej. {{ path('foobar') }} - public function getRouteByName($name, $params = array()) - { - $document = $this->findOneBy(array( - 'name' => $name, - )); - - if ($route) { - $route = new SymfonyRoute($route->getPattern(), array( - 'document' => $document, - )); - } - - return $route; - } - } - -.. tip:: - - Como posiblemente hayas notado regresamos un objeto `RouteCollection` --- ¿porqué no regresamos una sola ``Ruta``? El enrutador dinámico nos permite regresar muchas rutas *candidatas*, en otras palabras, rutas que *podrían* emparejar con la *URL* entrante. Esto es importante para habilitar la posibilidad de emparejar rutas *dinámicas*, `/page/{page_id}/edit` por ejemplo. - En nuestro ejemplo emparejamos las *URL* dadas exactamente y siempre regresar únicamente una sola ``Ruta``. - -Reemplazando el repositorio predefinido del *CMF* -------------------------------------------------- - -Para reemplazar el `RouteRepository` predefinido es necesario modificar tu configuración -de la siguiente manera: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - dynamic: - enabled: true - route_repository_service_id: my_bundle.repository.endpoint - -Donde `my_bundle.repository.endpoint` es el ID del servicio de tu repositorio. -Ve `Creando y configurando servicios en el contenedor `_ para información sobre la creación de servicios personalizados. diff --git a/_sources/cmf/getting-started/content.txt b/_sources/cmf/getting-started/content.txt deleted file mode 100644 index 7fa517d..0000000 --- a/_sources/cmf/getting-started/content.txt +++ /dev/null @@ -1,77 +0,0 @@ -.. index:: - single: Content, SymfonyCmfContentBundle - -Contenido -========= - -Concepto --------- - -El corazón de cada *CMS* está en el contenido, una abstracción que los editores pueden manipular y que más tarde será presentado a los usuarios de la página. La estructura del contenido depende mucho de las necesidades del proyecto, y este debe tener un significativo impacto en el futuro uso y desarrollo de la plataforma. - -La *EE* del ``CMF`` de *Symfony* viene con el ``ContentBundle``: una implementación básica de una estructura de contenido, incluyendo el soporte para múltiples idiomas y el almacenamiento de ``Rutas`` en la base de datos. - -Contenido estático ------------------- - -El ``StaticContent`` declara la estructura básica del contenido. Su estructura es muy similar a las utilizadas en los sistemas *ORM* de *Symfony2*, y la mayoría de sus campos son autoexplicativos, ya que son lo que esperarías de un *CMS* básico: -título, cuerpo, información de publicación y una referencia al padre, para organizar un árbol jerárquico. Este también incluye un bloque de referencia (más sobre esto más adelante). - -Las dos interfaces implementadas revelan dos de las características incluidas en esta implementación: - -- ``RouteAwareInterface`` significa que el contenido tiene ``Rutas`` asociadas. - -- ``PublishWorkflowInterface`` significa que el contenido tiene fechas de publicación y despublicación, las cuales serán manejadas por el núcleo del ``CMF`` de *Symfony* para determinar el acceso. - - -Contenido multilingüe estático ------------------------------- - -The ``MultilangStaticContent`` extends ``StaticContent``, offering the same -functionality with multi language support. Este especifica qué campos son traducidos (``title``, ``body`` y ``tags``) así como una variable para declarar la región. - -También especifica la estrategia de traducción: - -.. configuration-block:: - - .. code-block:: php-annotations - - /** - * @PHPCRODM\Document(translator="child", referenceable=true) - */ - -Para información sobre las estrategias de traducción disponibles, consulta la página -de *Doctrine* relacionada con el `Soporte multilingüe en el PHPCR-ODM `_ - - -Controlador de contenido ------------------------- - -Para manejar ambos tipos de contenido, también incluye un ``Controlador``. Its inner -workings are pretty straightforward: acepta una instancia de ``contenido`` y opcionalmente una plantilla a dibujar. If none is provided, it uses a pre-configured default. -It also takes into account the document's publishing status and multi language. -Ambas instancias de contenido y la plantilla opcional son proporcionadas al ``Controlador`` -por el ``DynamicRouter`` del ``RoutingExtraBundle``. More information on this is -available on the :ref:`Routing system getting started page ` -page. - -Admin Support -------------- - -El último componente necesario para manejar los tipos de contenido incluidos es una interfaz de administración. Symfony CMF can optionally support `SonataDoctrinePHPCRAdminBundle `_ -, a back office generation tool. Para más información sobre esta, por favor consulta la sección de documentación del `paquete `_. - -En ``ContentBundle``, las nterfaces de administración requeridas ya están declaradas en el directorio ``Admin`` y configuradas en el archivo :file:`Resources/config/admin.xml`, y serán cargadas automáticamente si instalas el ``SonataDoctrinePHPCRAdminBundle`` (ve :doc:`../tutorials/creating-cms-using-cmf-and-sonata` para instrucciones sobre esto). - -Configurando ------------- - -El paquete también apoya un conjunto opcional de parámetros de configuración. Consulta :doc:`../bundles/content` para la referencia de configuración completa. - -Consideraciones finales ------------------------ - -Si bien este pequeño paquete incluye algunos componentes vitales para un *CMS* completamente operativo, a menudo no proporciona todo lo que necesitas. The main idea behind it is to -provide developers with a small and easy to understand starting point you can -extend or use as inspiration to develop your own content types, Controllers and -Admin panels. diff --git a/_sources/cmf/getting-started/installing-symfony-cmf.txt b/_sources/cmf/getting-started/installing-symfony-cmf.txt deleted file mode 100644 index 1395aee..0000000 --- a/_sources/cmf/getting-started/installing-symfony-cmf.txt +++ /dev/null @@ -1,168 +0,0 @@ -.. index:: - single: Edición estándar, instalando - -Instalando la edición estándar del ``CMF`` de *Symfony* -======================================================= - -El objetivo de esta guía es instalar todos los componentes del ``CMF`` con la configuración mínima necesaria y algunos muy sencillos ejemplos en una aplicación *Symfony2* en funcionamiento. Esto se suele usar para familiarizarte con el ``CMF`` o para utilizarlo como punto de partida para personalizar una nueva aplicación. - -Si este es tu primer encuentro con el ``CMF`` de *Symfony* sería buena idea darle un vistazo primero a: - -- `El concepto `_ -- La demostración en línea del recinto de seguridad en `cmf.liip.ch `_ - -.. note:: - - Para otras guías de instalación del ``CMF`` de *Symfony*, por favor, consulta: - - - The cookbook entry on :doc:`../cookbook/installing-cmf-sandbox` for instructions on - how to install a more complete demo instance of Symfony CMF. - - :doc:`../tutorials/installing-cmf-core` for step-by-step installation and - configuration details of just the core components into an existing Symfony - application. - -Requisitos previos ------------------- - -Debido a que el ``CMF`` de *Symfony* está basado en *Symfony2*, deberías asegurarte de que cubres los `Requisitos para que funcione Symfony2 `_. -Además, necesitas tener instalada la extensión *PDO* de `SQLite `_ (``pdo_sqlite``), puesto que esta se utiliza como el medio de almacenamiento predeterminado. - -.. note:: - - By default, Symfony CMF uses Jackalope + Doctrine DBAL and SQLite as - the underlying DB. No obstante, el ``CMF`` de *Symfony* es de almacenamiento agnóstico, lo cual significa que puedes utilizar uno de varios mecanismos de almacenamiento de datos disponibles sin tener que reescribir tu código. Para más información en los diferentes mecanismos disponibles y cómo instalarlos y configurarlos, consulta el :doc:`../tutorials/installing-configuring-doctrine-phpcr-odm` - -`Git `_ and `Curl `_ are also -needed to follow the installation steps listed below. - - -Instalando ----------- - -La manera más fácil de instalar el ``CMF`` de *Symfony* es utilizando `Composer `_. -Consíguelo usando: - -.. code-block:: bash - - $ curl -sS https://getcomposer.org/installer | php - $ sudo mv composer.phar /usr/local/bin/composer - -Y luego consigue el código del ``CMF`` de *Symfony* con él (esto puede tomar un buen rato) - -.. code-block:: bash - - $ php composer.phar create-project symfony-cmf/standard-edition --stability=dev - $ cd - -.. note:: - - The path ```` should either inside your web server doc root or - configure a virtual host for ````. - -Esto clonará la edición estándar e instalará todas las dependencias y ejecutará algunas órdenes iniciales. -Estas órdenes requieren permisos de escritura en los directorios :file:`app/cache` y :file:`app/logs`. En caso de que al final las órdenes terminen dando errores de permisos, por favor sigue las `directrices en la documentación oficial `_ para configurar los permisos y luego ejecuta la orden ``composer.phar install`` mencionada abajo. - -Si lo prefieres, también puedes clonar el proyecto directamente: - -.. code-block:: bash - - $ git clone git://github.com/symfony-cmf/symfony-cmf-standard.git - $ cd - -Si ocurrieron problemas durante la orden ``create-project``, o si utilizaste ``git clone`` o si después activaste una versión anterior (con ``checkout``), siempre ejecuta la siguiente orden para actualizar las dependencias: - -.. code-block:: bash - - $ php composer.phar install - -The next step is to setup the database, if you want to use SQLite as your database backend just go -ahead and run the following: - -.. code-block:: bash - - $ php app/console doctrine:database:create - $ php app/console doctrine:phpcr:init:dbal - $ php app/console doctrine:phpcr:repository:init - $ php app/console doctrine:phpcr:fixtures:load - -This will create a file called ``app.sqlite`` inside your app folder, containing the database content. - -El proyecto ahora tendría que estar accesible en tu servidor *web*. Si tienes instalado *PHP 5.4*, alternativamente puedes utilizar el servidor *web* interno de *PHP*: - -.. code-block:: bash - - $ php app/console server:run - -Y entonces accede al ``CMF`` vía: - -.. code-block:: text - - http://localhost:8000 - -Si prefieres utilizar otro servidor de base de datos, por ejemplo *MySQL*, ejecuta el configurador (apunta tu navegador a ``/web/config.php``) o configura tus parámetros de conexión a la base de datos en el archivo :file:`app/config/parameters.yml`. Asegúrate de dejar la propiedad ``database_path`` en ``null`` para utilizar otro controlador distinto a *SQLite*. Dejar el campo en blanco en el configurador *web* lo debería poner a ``null``. - -Descripción ------------ - -Esta guía te ayudará a entender las partes básicas de la edición estándar del ``CMF`` de *Symfony* (*EE*) y cómo trabajan juntas para proporcionar las páginas predefinidas que puedes ver al explorar la instalación de la Edición estándar del ``CMF`` de *Symfony*. - -It assumes you have already installed Symfony CMF SE and have carefully -read `the Symfony2 book `_. - -.. note:: - - Para otras guías de instalación del ``CMF`` de *Symfony*, por favor, consulta: - - - The cookbook entry on :doc:`../cookbook/installing-cmf-sandbox` for instructions on how to - install a more complete demo instance of Symfony CMF. - - :doc:`../tutorials/installing-cmf-core` for step-by-step installation and configuration - details of just the core components into an existing Symfony application. - -``AcmeMainBundle`` y ``SimpleCMSBundle`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La *EE* del ``CMF`` de *Symfony* viene con un ``AcmeMainBundle`` predefinido para ayudarte a empezar, en una manera similar al ``AcmeDemoBundle`` que tiene *Symfony2*, proporcionándote algunas páginas de demostración visibles en tu navegador. Sin embargo, el ``AcmeMainBundle`` no incluye controladores o archivos de configuración, como probablemente esperarías. Este contiene poco más que un archivo *Twig* y datos `Accesorios `_, que fueron cargados a tu base de datos durante la instalación. - -Hay varios paquetes trabajando juntos para convertir los datos accesorios en un sitio web navegable. El proceso global simplificado, es el siguiente: - -- When a request is received, the Symfony CMF :doc:`routing`'s Dynamic Router is used to handle the - incoming request. -- The Dynamic Router is able to match the requested URL with a specific ContentBundle's Content - stored in the database. -- The retrieved content's information is used to determine which controller to pass it on to, and - which template to use. -- As configured, the retrieved content is passed to ContentBundle's ContentController, which will - handle it and render AcmeMainBundle's layout.html.twig. - -Una vez más, esto es un vistazo simplificado de un muy sencillo *CMS* construido en lo alto del ``CMF`` de *Symfony*. -To fully understand all the possibilities of the CMF, a careful look into -each component is needed. - -If you want to review the contents of the PHPCR database you can use the following command: - -.. code-block:: bash - - $ php app/console doctrine:phpcr:dump - -Adding new pages -~~~~~~~~~~~~~~~~ - -Symfony CMF SE does not provide any admin tools to create new pages. If you are interested in adding -an admin UI have a look at :doc:`../tutorials/creating-cms-using-cmf-and-sonata`. However if all you -want is a simple way to add new pages that you can then edit via the inline editing, then you can -use the SimpleCmsBundle ``page`` migrator. The Symfony CMF SE ships with an example yaml file stored -in ``app/Resources/data/pages/test.yml``. The contents of this file can be loaded into the PHPCR -database by calling: - -.. code-block:: bash - - $ php app/console doctrine:phpcr:migrator page --identifier=/cms/simple/test - -Note that the above identifier is mapped to ``app/Resources/data/pages/test.yml`` by stripping -off the ``basepath`` configuration of the SimpleCmsBundle, which defaults to ``/cms/simple``. -Therefore if you want to define a child page ``foo`` for ``/cms/simple/test`` you would need to -create a file ````app/Resources/data/pages/test/foo.yml`` and then run the following command: - -.. code-block:: bash - - $ php app/console doctrine:phpcr:migrator page --identifier=/cms/simple/test/foo diff --git a/_sources/cmf/getting-started/menu.txt b/_sources/cmf/getting-started/menu.txt deleted file mode 100644 index e1c80ac..0000000 --- a/_sources/cmf/getting-started/menu.txt +++ /dev/null @@ -1,152 +0,0 @@ -.. index:: - single: Menu, SymfonyCmfMenuBundle - -Menú -==== - -Concepto --------- - -Ningún sistema *CMS* está completo sin un sistema de menús que permita a los usuarios navegar entre las páginas de contenido y realizar determinadas acciones. Si bien, normalmente asignan el contenido real en una estructura de árbol, los menús a menudo tienen su propia lógica, incluyendo opciones no asignadas al contenido o existentes en múltiples contextos con múltiples opciones, por ello volviéndolos un problema complejo. - - -El sistema de menús del ``CMS`` de *Symfony* --------------------------------------------- - -La *EE* del ``CMF`` de *Symfony* incluye el ``MenuBundle``, una herramienta que te permite definir tus menús dinámicamente. It extends `KnpMenuBundle `_, -with a set of hierarchical, multi language menu elements, along with the tools -to load and store them from/to a database. It also includes the administration -panel definitions and related services needed for integration with -`SonataDoctrinePhpcrAdminBundle `_ - -.. note:: - - El ``MenuBundle`` extiende y en gran medida depende del `KnpMenuBundle `_, por lo tanto tienes que leer cuidadosamente la `documentación del KnpMenuBundle `_. - Para el resto de esta página se supone que has leido dicha documentación, y estás familiarizado con conceptos como proveedores de menús y factorías de menús. - - -Usando -~~~~~~ - -De manera predeterminada el ``MenuBundle`` usa los reproductores y ayudantes del ``KnpMenuBundle`` para imprimir tus menús. Puedes consultar la `página de documentación respectiva `_ para más información sobre el tema, pero una llamada básica sería: - -.. configuration-block:: - - .. code-block:: jinja - - {{ knp_menu_render('simple') }} - - .. code-block:: php - - render('simple') ?> - -El nombre del menú proporcionado será pasado a la implementación de la ``MenuProviderInterface``, la cual lo utilizará para identificar qué menú quieres dibujar en esa sección específica. - -El proveedor -~~~~~~~~~~~~ - -El núcleo del ``MenuBundle`` es ``PHPCRMenuProvider``, una implementación de la ``MenuProviderInterface`` que es responsable de cargar menús dinámicamente desde una base de datos *PHPCR*. The default provider service is configured with a ``menu_basepath`` to -know where in the PHPCR tree it will find menus. The menu ``name`` is given when -rendering the menu and must be a direct child of the menu base path. This allows the -``PHPCRMenuProvider`` to handle several menu hierarchies using a single -storage mechanism. - -To give a concrete example, if we have the configuration as given below and render the -menu ``simple``, the menu root node must be stored at ``/cms/menu/simple``. - -.. configuration-block:: - - .. code-block:: yaml - - symfony_cmf_menu: - menu_basepath: /cms/menu - - .. code-block:: xml - - - /cms/menu - - - .. code-block:: php - - $container->loadFromExtension('symfony_cmf_menu', array( - 'menu_basepath' => '/cms/menu', - )); - -If you need multiple menu roots, you can create further PHPCRMenuProvider instances -and register them with KnpMenu - see the CMF MenuBundle DependencyInjection code -for the details. - -El elemento del menú recuperado usando este proceso se utiliza como el nodo raíz del menú, y sus hijos serán cargados progresivamente conforme el ``MenuFactory`` vaya dibujando la estructura del menú completo. - - -La factoría -~~~~~~~~~~~ - -The ``ContentAwareFactory`` is a ``FactoryInterface`` implementation, which -generates the full ``MenuItem`` hierarchy from the provided MenuNode. Los datos generados de este modo se utilizan más tarde para generar la representación *HTML* real del menú. - -La implementación incluida se enfoca en generar instancias de ``MenuItem`` a partir de instancias de ``NodeInterface``, puesto que esa es la mejor aproximación para manejar estructuras estilo árbol como las utilizadas típicamente por los *CMS*. Other approaches are implemented -in the base classes, and their respective documentation pages can be found -in `KnpMenuBundle`_'s page. - -``ContentAwareFactory`` is responsible for loading the full menu hierarchy -and transforming the ``MenuNode`` instances from the root node it -receives from the ``MenuProviderInterface`` implementation. También es responsable de determinar cuál elemento del menú (si lo hay) está viendo el usuario actualmente. ``KnpMenu`` ya incluye una factoría específica apuntada en el componente de enrutado de *Symfony2*, la cual extiende este paquete, para añadir el soporte necesario para: - -- Databased stored ``Route`` instances (refer to :ref:`RoutingBundle's RouteProvider ` for more details - on this) -- ``Route`` instances with associated content (more on this on respective :ref:`RoutingBundle's section `) - -Como se mencionó antes, la ``ContentAwareFactory`` es responsable de cargar todos los nodos del menú a partir del elemento raíz proporcionado. Los nodos cargados realmente pueden ser de cualquier clase, incluso si son diferentes de la clase del nodo raíz, pero todos tienen que implementar la ``NodeInterface`` para poder incluirlos en el menú generado. - - -Los nodos del menú -~~~~~~~~~~~~~~~~~~ - -Además, incluidos en el ``MenuBundle`` vienen dos tipos de nodos de contenido del menú: ``MenuNode`` y ``MultilangMenuNode``. Si leíste la página de documentación relativa al :doc:`content`, encontrarás un tanto familiar esta implementación. El ``MenuNode`` -Implementa la ``NodeInterface`` mencionada arriba, y mantiene la información relacionada a una sola entrada del menú: Una ``etiqueta`` y una ``uri``, una lista de ``hijos``, tal como esperarías, más algunos ``atributos`` para él y sus -hijos, mismos que te permiten personalizar el proceso de reproducción real. -También incluye un campo ``Route`` y dos referencias al ``Contenido``. Estas se usan para almacenar un objeto ``Route`` asociado, más un elemento (no dos, a pesar del hecho de que existen dos campos) de ``Contenido``. El ``MenuNode`` puede tener una referencia fuerte (garantizando la integridad) o débil (sin garantizar la integridad) al elemento de ``Contenido`` real al que apunta, tu trabajo es elegir la que sea más adecuada en tu escenario. Puedes encontrar más información y referencias en la página de la documentación del `PHPCR de Doctrine `_. - -``MultilangMenuNode`` extiende al ``MenuNode`` con apoyo multilingüe. Añade un campo ``locale`` para identificar qué traducción pertenece a cada menú, más una ``etiqueta`` y una ``uri`` en los campos marcados como ``translated=true``, lo cual significa que diferirán entre traducciones, a diferencia de los otros campos. - -También especifica la estrategia utilizada para almacenar múltiples traducciones en la base de datos: - -.. configuration-block:: - - .. code-block:: php-annotations - - /** - * @PHPCRODM\Document(translator="attribute") - */ - -For information on the available translation strategies, refer to the Doctrine -page regarding `Multi language support in PHPCR-ODM `_ - - -Admin Support -------------- - -``MenuBundle`` also includes the administration panels and respective services -needed for integration with the backend admin tool :doc:`SonataDoctrinePhpcrAdminBundle <../bundles/doctrine_phpcr_admin>` - -The included administration panels will automatically available but need to be -explicitly put on the dashboard if you want to use them. See :doc:`../tutorials/creating-cms-using-cmf-and-sonata` -for instructions on how to install SonataDoctrinePhpcrAdminBundle. - - -Configurando ------------- - -Este paquete se configura usando un conjunto de parámetros, pero todos ellos son opcionales. You can go to the :doc:`../bundles/menu` reference page for the -full configuration options list and additional information. - -Further Notes -------------- - -Para más información sobre el ``MenuBundle`` del ``CMF`` de *Symfony*, por favor consulta: - -- :doc:`../bundles/menu` for advanced details and configuration reference -- `KnpMenuBundle`_ page for information on the bundle on which ``MenuBundle`` relies -- `KnpMenu `_ page for information on the underlying library used by ``KnpMenuBundle`` diff --git a/_sources/cmf/getting-started/overview.txt b/_sources/cmf/getting-started/overview.txt deleted file mode 100644 index 9274697..0000000 --- a/_sources/cmf/getting-started/overview.txt +++ /dev/null @@ -1,27 +0,0 @@ -Información general -=================== - -Esta guía te ayudará a entender las partes básicas de la edición estándar del ``CMF`` de *Symfony* (*EE*) y cómo trabajan juntas para proporcionar las páginas predefinidas que puedes ver al explorar la instalación de la Edición estándar del ``CMF`` de *Symfony*. - -Se supone que ya has instalado la edición estándar del ``CMF`` de *Symfony* y has leído cuidadosamente el libro de *Symfony2*. - -.. note:: - - Para otras guías de instalación del ``CMF`` de *Symfony*, por favor, consulta: - - El artículo :doc:`../cookbook/installing-cmf-sandbox` en el recetario para instrucciones sobre cómo instalar un ejemplo de una demostración más completa del ``CMF`` de *Symfony*. - - :doc:`../tutorials/installing-cmf-core` para instrucciones de instalación paso a paso y detalles de configuración de los componentes necesarios del núcleo en una aplicación de *Symfony* existente. - -``AcmeMainBundle`` y ``SimpleCMSBundle`` ----------------------------------------- - -La *EE* del ``CMF`` de *Symfony* viene con un ``AcmeMainBundle`` predefinido para ayudarte a empezar, en una manera similar al ``AcmeDemoBundle`` que tiene *Symfony2*, proporcionándote algunas páginas de demostración visibles en tu navegador. Sin embargo, el ``AcmeMainBundle`` no incluye controladores o archivos de configuración, como probablemente esperarías. Este contiene poco más que un archivo *Twig* y datos `Accesorios `_, que fueron cargados a tu base de datos durante la instalación. - -Hay varios paquetes trabajando juntos para convertir los datos accesorios en un sitio web navegable. El proceso global simplificado, es el siguiente: - -- Al recibir una petición, :doc:`/components/routing` del ``CMF`` de *Symfony*, el Enrutador dinámico se usa para manejar la petición entrante. -- El Enrutador dinámico es capaz de emparejar la *URL* solicitada con el contenido de un ``ContentBundle`` específico almacenado en la base de datos. -- La información del contenido recuperado se usa para determinar a cuál controlador pasarlo, y qué plantilla utilizar. -- Tal cual esté configurado, el contenido recuperado se pasa al ``ContentController`` del ``ContentBundle``, el cual lo manejará y reproducirá la plantilla :file:`layout.html.twig` del ``AcmeMainBundle``. - - Una vez más, esto es un vistazo simplificado de un muy sencillo *CMS* construido en lo alto del ``CMF`` de *Symfony*. - Para entender completamente todas las posibilidades del ``CMF``, es necesario darle un muy cuidadoso vistazo a cada componente. \ No newline at end of file diff --git a/_sources/cmf/getting-started/routing.txt b/_sources/cmf/getting-started/routing.txt deleted file mode 100644 index d1eea20..0000000 --- a/_sources/cmf/getting-started/routing.txt +++ /dev/null @@ -1,439 +0,0 @@ -.. index:: - single: Routing, SymfonyCmfRoutingExtraBundle - -Enrutando -========= - -Esta es una introducción para entender los conceptos detrás del enrutado del *CMF*. Para la documentación de referencia por favor ve :doc:`../components/routing` y :doc:`../bundles/routing-extra`. - -Concepto --------- - -Why a new Routing Mechanism? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Los *CMS* son sitios altamente dinámicos, donde la mayoría del contenido lo gestionan los administradores en lugar de los desarrolladores. El número de páginas disponibles fácilmente puede alcanzar miles, lo cual normalmente es multiplicado por el número de traducciones disponibles. Mejor accesibilidad y prácticas *SEO*, así como preferencias de usuario dictan que direcciones *URL* deberían ser definibles por los gestores de contenido. - -El mecanismo de enrutado predefinido de *Symfony2*, con enfoque de archivos de configuración, no es la mejor solución para este problema, puesto que no es apropiado para manejar rutas dinámicas definidas por el usuario, ni escalan bien para un gran número de rutas. - -The Solution -~~~~~~~~~~~~ - -Para afrontar estos problemas, se desarrolló un nuevo sistema de enrutado, este tiene en cuenta las necesidades típicas de enrutado de un *CMS*: - -- User defined URLs; -- Multi-site; -- Multi-language; -- Tree-like structure for easier management; -- Contenido, Menú y separación de ``Rutas`` para flexibilidad adicional. - -Con estos requisitos en mente, fue desarrollado el componente ``Routing`` del ``CMF`` de *Symfony*. - -La ``ChainRouter`` ------------------- - -En el núcleo del ``CMF`` de *Symfony* el componente de enrutado es la ``ChainRouter``. -Esta se utiliza como sustituta para el sistema de enrutado predefinido de *Symfony2* y, como tal, es responsable de determinar cuál ``Controlador`` manejará cada ``petición``. - -The ``ChainRouter`` works by accepting a set of prioritized routing strategies, -:class:`Symfony\\Component\\Routing\\RouterInterface` implementations, -commonly referred to as "Routers". The routers are responsible for matching an -incoming request to an actual Controller and, to do so, the ``ChainRouter`` -iterates over the configured Routers according to their configured priority: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - chain: - routers_by_id: - # enable the DynamicRouter with high priority to allow overwriting - # configured routes with content - symfony_cmf_routing_extra.dynamic_router: 200 - - # activa el enrutador predefinido de symfony con - # baja prioridad - router.default: 100 - - .. code-block:: xml - - - - - - 200 - - - - 100 - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_routing_extra', array( - 'chain' => array( - 'routers_by_id' => array( - 'symfony_cmf_routing_extra.dynamic_router' => 200, - 'router.default' => 100, - ), - ), - )); - -You can also load Routers using tagged services, by using the ``router`` tag -and an optional ``priority``. Mientras más alta prioridad, más temprano será consultado tu enrutador para emparejar la ruta. Si no especificas la prioridad, tu enrutador vendrá al último. Si hay varios enrutadores con la misma prioridad, el orden entre ellos es indeterminado. El servicio etiquetado se verá como este: - -.. configuration-block:: - - .. code-block:: yaml - - services: - my_namespace.my_router: - class: "%my_namespace.my_router_class%" - tags: - - { name: router, priority: 300 } - - .. code-block:: xml - - - - - - - .. code-block:: php - - $container - ->register('my_namespace.my_router', '%my_namespace.my_router_class%') - ->addTag('router', array('priority' => 300)) - ; - -El sistema de enrutado del ``CMF`` de *Symfony* añade un nuevo ``DynamicRouter``, el cual complementa el ``Router`` predefinido de *Symfony2*. - -The Default Symfony2 Router ---------------------------- - -A pesar de que reemplaza el mecanismo de enrutado predefinido, el ``Routing`` del ``CMF`` de *Symfony* te permite seguir utilizando el sistema existente. De hecho, el enrutado predefinido está habilitado por omisión, así que puedes seguir utilizando las rutas que declaraste en tus archivos de configuración, o como fueron declaradas por otros paquetes. - -.. _start-routing-dynamic-router: - -El ``DynamicRouter`` --------------------- - -Este enrutador puede cargar instancias de ``Ruta`` dinámicamente desde un determinado proveedor. Luego usa un proceso para emparejar la petición entrante a una ``Ruta`` específica, la cual en turno suele determinar a qué ``Controlador`` enviar la petición. - -La configuración predefinida del paquete declara que el ``DynamicRouter`` está inhabilitado -por omisión. Para activarlo, sólo añade lo siguiente a tu archivo de configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - dynamic: - enabled: true - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_routing_extra', array( - 'dynamic' => array( - 'enabled' => true, - ), - )); - -Esta es la configuración mínima requirida para cargar el ``DynamicRouter`` como servicio, esta lo capacita para realizar cualquier enrutamiento. De hecho, cuándo exploras las páginas predefinidas que vienen con la *EE* del ``CMF`` de *Symfony*, el ``DynamicRouter`` es el que empareja tus peticiones con los Controladores y Plantillas. - -.. _start-routing-getting-route-object: - -Getting the Route Object -~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes configurar el proveedor a usar para adaptarlo a las necesidades de cada implementación, este debe implementar la ``RouteProviderInterface``. Como parte de este paquete, se proporciona una implementación para el `PHPCR-ODM `_, pero fácilmente puedes crear una propia, puesto que el ``Enrutador`` es de almacenamiento agnóstico. El proveedor predefinido carga la ruta como el camino en la petición y todas las rutas padre permiten que algunos segmentos de la ruta sean parámetros. - -Para información más detallada sobre esta implementación y cómo la puedes personalizar o extender, consulta el :doc:`../bundles/routing-extra`. - -El ``DynamicRouter`` es capaz de emparejar la petición entrante con un objeto ``Ruta`` del proveedor subyacente. Los detalles sobre cómo se lleva a cabo el proceso de emparejamiento se pueden encontrar en :doc:`../components/routing`. - -.. note:: - - Para hacer que el proveedor de rutas encuentre rutas, también necesitas proporcionar los datos en tu almacenamiento. Con *PHPCR-ODM*, esto se hace a través de la interfaz de admininistración (ve más adelante) o con accesorios. - - No obstante, antes de poder explicar cómo hacerlo, necesitas entender cómo trabaja el ``DynamicRouter``. An example will come :ref:`later in this document `. - -.. _start-routing-getting-controller-template: - -Consiguiendo el controlador y la plantilla -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Una ``Ruta`` necesita especificar cuál ``Controlador`` debería manejar una ``Petición`` específica. -El ``DynamicRouter`` utiliza uno de varios métodos posibles para determinarlo (por orden de precedencia): - -- Explicito: The stored Route document itself can explicitly declare the target - Controller by specifying the '_controller' value in ``getRouteDefaults()``. -- Por alias: the Route returns a 'type' value in ``getRouteDefaults()``, - which is then matched against the provided configuration from config.yml -- By class: requires the Route instance to implement ``RouteObjectInterface`` - and return an object for ``getRouteContent()``. The returned class type is - then matched against the provided configuration from config.yml. -- Predefinido: Si está configurado, utilizará un controlador predefinido. - -Apart from this, the ``DynamicRouter`` is also capable of dynamically specifying -which Template will be used, in a similar way to the one used to determine -the Controller (in order of precedence): - -- Explicito: The stored Route document itself can explicitly declare the target - Template in ``getRouteDefaults()``. -- Por clase: requires the Route instance to implement ``RouteObjectInterface`` - and return an object for ``getRouteContent()``. The returned class type is - then matched against the provided configuration from config.yml. - -Aquí tienes un ejemplo sobre cómo configurar las opciones mencionadas anteriormente: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - dynamic: - generic_controller: symfony_cmf_content.controller:indexAction - controllers_by_type: - editablestatic: sandbox_main.controller:indexAction - controllers_by_class: - Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent: symfony_cmf_content.controller::indexAction - templates_by_class: - Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent: SymfonyCmfContentBundle:StaticContent:index.html.twig - - .. code-block:: xml - - - - - - sandbox_main.controller:indexAction - - - - symfony_cmf_content.controller::indexAction - - - - SymfonyCmfContentBundle:StaticContent:index.html.twig - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_routing_extra', array( - 'dynamic' => array( - 'generic_controller' => 'symfony_cmf_content.controller:indexAction', - 'controllers_by_type' => array( - 'editablestatic' => 'sandbox_main.controller:indexAction', - ), - 'controllers_by_class' => array( - 'Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent' => 'symfony_cmf_content.controller::indexAction', - ), - 'templates_by_class' => array( - 'Symfony\Cmf\Bundle\ContentBundle\Document\StaticContent' => 'SymfonyCmfContentBundle:StaticContent:index.html.twig', - ), - ), - )); - -Ten en cuenta que ``enabled: true`` ya no está presente. It's only required if -no other configuration parameter is provided. The router is automatically -enabled as soon as you add any other configuration to the ``dynamic`` entry. - -.. note:: - - Internamente, el componente ``Routing`` asocia estas opciones de configuración a varias instancias de la ``RouteEnhancerInterface``. El alcance real de estos potenciadores es mucho mayor, y puedes encontrar más información sobre ellos en la página :doc:`../components/routing` de la documentación. - -.. _start-routing-linking-a-route-with-a-model-instance: - -Linking a Route with a Model Instance -------------------------------------- - -Dependiendo de la lógica de tu aplicación, una *URL* solicitada puede tener una instancia -del modelo asociado en la base de datos. Estas ``Rutas`` pueden implementar la ``RouteObjectInterface``, y opcionalmente regresar una instancia del modelo, esta automáticamente será pasada al ``Controlador`` como la variable ``$contentDocument``, si la declaras como parámetro. - -Ten en cuenta que una ``Ruta`` puede implementar la interfaz mencionada anteriormente pero todavía no regresar ninguna instancia del modelo, en cuyo caso ningún objeto asociado será proporcionado. - -Además, las ``Rutas`` que implementen esta interfaz también pueden tener un nombre de ``Ruta`` personalizado, en vez del predefinido compatible con el núcleo de *Symfony*, y pueden contener -cualquier carácter. Esto te permite, por ejemplo, poner una ruta como el nombre de la ruta. - -Redirecciones -------------- - -Puedes construir redirecciones implementando la ``RedirectRouteInterface``. -Si estás utilizando el proveedor de ruta ``PHPCR-ODM`` predefinido, uno listo para usar la implementación es proporcionado en el ``Documento`` ``RedirectRoute``. Este puede redirigir o bien a una *URI* absoluta, a una ``Ruta`` nombrada que puede generar cualquier ``Enrutador`` en la cadena o a otro objeto ``Ruta`` conocido por el proveedor de rutas. La redirección real es manejada por un ``Controlador`` específico, el cual puedes configurar como gustes: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - controllers_by_class: - Symfony\Cmf\Component\Routing\RedirectRouteInterface: symfony_cmf_routing_extra.redirect_controller:redirectAction - - .. code-block:: xml - - - - - symfony_cmf_routing_extra.redirect_controller:redirectAction - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_routing_extra', array( - 'controllers_by_class' => array( - 'Symfony\Cmf\Component\Routing\RedirectRouteInterface' => 'symfony_cmf_routing_extra.redirect_controller:redirectAction', - ), - )); - -.. note:: - - The actual configuration for this association exists as a service, not as part of - a ``config.yml`` file. Como se explicó antes, puedes utilizar cualquier enfoque. - -URL Generation --------------- - -El componente de enrutado del ``CMF`` de *Symfony* utiliza los componentes predefinidos de *Symfony2* para manejar la generación de rutas, así que puedes utilizar los métodos predefinidos para generar tus *url*, con unas cuantas posibilidades añadidas: - -* Pass either an implementation of ``RouteObjectInterface`` or a - ``RouteAwareInterface`` as ``name`` parameter -* Or supply an implementation of ``ContentRepositoryInterface`` and the id of - the model instance as parameter ``content_id`` - -The route generation handles locales as well, see :ref:`component-route-generator-and-locales`. - -.. _start-routing-document: - -The PHPCR-ODM Route Document ----------------------------- - -Como se mencionó arriba, puedes utilizar cualquier proveedor de rutas. El ejemplo en esta sección aplica si utilizas el proveedor de rutas *PHPCR-ODM* predefinido. - -Todas las rutas están localizadas bajo una ruta configurada como la raíz, por ejemplo ``'/cms/rutas'``. -A new route can be created in PHP code as follows:: - - use Symfony\Cmf\Bundle\RoutingExtraBundle\Document\Route; - - $route = new Route; - $route->setParent($dm->find(null, '/routes')); - $route->setName('projects'); - - // enlaza un contenido a la ruta - $content = new Content('my content'); - $route->setRouteContent($content); - - // ahora configura algún parámetro, no olvides la barra inclinada inicial si - // quieres /proyectos/{id} y no /proyectos{id} - $route->setVariablePattern('/{id}'); - $route->setRequirement('id', '\d+'); - $route->setDefault('id', 1); - -This will give you a document that matches the URL ``/projects/`` but -also ``/projects`` as there is a default for the id parameter. - -Your controller can expect the ``$id`` parameter as well as the -``$contentDocument`` as we set a content on the route. The content could be -used to define an intro section that is the same for each project or other -shared data. If you don't need content, you can just not set it in the -document. - -For more details, see the :ref:`route document section in the RoutingExtraBundle documentation `. - - -Integrando con *SonataAdmin* ----------------------------- - -Si en la sección ``require`` del archivo :file:`composer.json` añades el ``sonata-project/doctrine-phpcr-admin-bundle``, los documentos de ruta serán expuestos en el ``SonataDoctrinePhpcrAdminBundle``. -Para instrucciones sobre cómo configurar este paquete ve :doc:`../bundles/doctrine_phpcr_admin`. - -De manera predefinida, ``use_sonata_admin`` se pone automáticamente basándose en si ``SonataDoctrinePhpcrAdminBundle`` está disponible pero lo puedes desactivar explícitamente si no tienes habilitado sonata, o activarlo explícitamente para conseguir un error si sonata se vuelve inasequible. - -Tienes un par de opciones de configuración para la administración. El ``content_basepath`` apunta a la raíz de tus documentos de contenido. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - use_sonata_admin: auto # usa true/false para forzar el uso / no uso de - # la administración de sonata - content_basepath: ~ # usado con la administración de sonata para - # manejar el contenido, por omisión a - # symfony_cmf_core.content_basepath - - .. code-block:: xml - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_routing_extra', array( - 'use_sonata_admin' => 'auto', - 'content_basepath' => null, - )); - -Términos del tipo ``Form`` --------------------------- - -El paquete define un tipo ``form`` que puedes utilizar para la clásica casilla de verificación «aceptar términos» donde colocas las *url* en la etiqueta. Simply specify -``symfony_cmf_routing_extra_terms_form_type`` as the form type name and specify -a label and an array with ``content_ids`` in the options:: - - $form->add('terms', 'symfony_cmf_routing_extra_terms_form_type', array( - 'label' => 'I have seen the Team and More pages ...', - 'content_ids' => array( - '%team%' => '/cms/content/static/team', - '%more%' => '/cms/content/static/more' - ), - )); - -El tipo ``form`` automáticamente genera las rutas para el contenido especificado y pasa las rutas al ayudante ``trans`` de *Twig* para sustituirlas en la etiqueta. - -Further Notes -------------- - -Para más información sobre el componente ``Routing`` del ``CMF`` de *Symfony*, por favor, consulta: - -- :doc:`../components/routing` for most of the actual functionality implementation -- :doc:`../bundles/routing-extra` for Symfony2 integration bundle for Routing Bundle -- Symfony2's `Routing `_ component page -- :doc:`../tutorials/handling-multilang-documents` for some notes on multilingual routing diff --git a/_sources/cmf/getting-started/simplecms.txt b/_sources/cmf/getting-started/simplecms.txt deleted file mode 100644 index a889e81..0000000 --- a/_sources/cmf/getting-started/simplecms.txt +++ /dev/null @@ -1,374 +0,0 @@ -.. index:: - single: SimpleCMS, SymfonyCmfSimpleCMSBundle - -SimpleCMS -========= - -Concepto --------- - -In the previous documentation pages all the basic components of Symfony CMF -have been analysed: the :doc:`routing` that allows you to associate URLs -with your :doc:`content`, which users can browse using a :doc:`menu`. - -These three components complement each other but are independent: they work -without each other, allowing you to choose which ones you want to use, extend -or ignore. In some cases, however, you might just want a simple implementation -that gathers all those functionalities in a ready-to-go package. For that -purpose, the ``SimpleCMSBundle`` was created. - - -SimpleCMSBundle ---------------- - -``SimpleCMSBundle`` is implemented on top of most of the other Symfony CMF -Bundles, combining them into a functional CMS. It's a simple solution, but -you will find it very useful when you start implementing your own CMS using -Symfony CMF. Whether you decide to extend or replace it, it's up to you, -but in both cases, it's a good place to start developing your first CMS. - - -Page -~~~~ - -``SimpleCMSBundle`` basic content type is ``Page``. Its class declaration -points out many of the features available: - -- It extends ``Route``, meaning it's not only a ``Content`` instance, but - also a ``Route``. In this case, as declared in ``getRouteContent()``, the - ``Route`` as an associated content, itself. -- It implements ``RouteAwareInterface``, which means it has associated ``Route`` - instances. As expected, and as seen in ``getRoutes()``, it has only one ``Route`` - associated: itself. -- It implements ``NodeInterface``, which means it can be used by ``MenuBundle`` - to generate a menu structure. - - -The class itself is similar to the ``StaticContent`` already described in -the documentation page regarding :doc:`content`, although some key atributes, -like ``parent`` or ``path`` come from the ``Route`` class it extends. - - -Three-in-one -~~~~~~~~~~~~ - -Like explained before, this bundle gathers functionality from three distinct -bundles: :doc:`content`, :doc:`routing` and :doc:`menu`. The routing component -receives a request, that it matches to a ``Route`` instance loaded from database. -That ``Route`` points to a ``Content`` instance: itself. A controller is -also determined by the routing component, that renders the ``Content`` using -a template which, in turn, presents the user with a HTML visualization of -the stored information tree structure, rendered using ``MenuItem`` obtained -from equivalent ``NodeInterface`` instances. - -``SimpleCMSBundle`` simplifies this process: ``Content``, ``Route`` and ``NodeInterface`` -are gathered in one class: ``Page``. This three-in-one approach is the key -concept behind this bundle. - - -MultilangPage -~~~~~~~~~~~~~ - -Like you would expect, a multilanguage version of ``Page`` is also included. -``MultilangPage`` defines a ``locale`` variable and which fields will be -translated (``title``, ``label`` and ``body``). It also includes ``getStaticPrefix()`` -to handle the path prefix of the ``Page``. This is part of the route handling -mechanism, and will be analysed bellow. - - The ``MultilangPage`` uses the ``attribute`` strategy for translation: the - several translations coexist in the same database entry, and the several - translated versions of each field are stored as different attributes in - that same entry. - - As the routing is not separated from the content, it is not possible to - create different routes for different languages. This is one of the biggest - disadvantages of the ``SimpleCmsBundle``. - -Configuring the Content Class -............................. - -By default, ``SimpleCMSBundle`` will use ``Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page`` -as the content class if multilanguage is not enabled (default). If no other -class is chosen, and multilanguage support is enabled, it will automatically -switch to ``Symfony\Cmf\Bundle\SimpleCmsBundle\Document\MultilangPage``. -You can explicitly specify your content class and/or enable multilanguage -support using the configuration parameters: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - document_class: ~ # Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page - multilang: - locales: ~ # defaults to [], declare your locales here to enable multilanguage - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_simple_cms', array( - 'document_class' => null, - 'multilang' => array( - 'locales' => null, - ), - )); - - -SimpleCMSBundle in Detail -------------------------- - -Now that you understand what ``SimpleCMSBundle`` does, we'll detail how it -does it. Several other components are part of this bundle, that change the -default behaviour of its dependencies. - - -The Routing -~~~~~~~~~~~ - -``SimpleCMSBundle`` doesn't add much functionality to the routing part of -Symfony CMF. Instead, it greatly relies on ``RoutingExtraBundle`` and its -set of configurable functionalities to meet its requirements. It declares -an independent ``DynamicRouter``, with it's own specific ``RouteProvider``, -``NestedMatcher``, Enhancers set and other useful services, all of them instances -of the classes bundled with ``RoutingBundle`` and ``RoutingExtraBudle``. -This service declaration duplication allows you to reuse the original ``RoutingExtraBundle`` -configuration options to declare another Router, if you wish to do so. - -The only exception to this is ``RouteProvider``: the ``SimpleCMSBundle`` -has its own strategy to retrieve ``Route`` instances from database. This -is related with the way ``Route`` instances are stored in database by ``RoutingExtraBundle``. -By default, the ``path`` parameter will hold the prefixed full URI, including -the locale identifier. This would mean an independent ``Route`` instance -should exist for each translation of the same ``Content``. However, as we've -seen, ``MultilangPage```stores all translations in the same entry. So, to -avoid duplication, the locale prefix is stripped from the URI prior to persistance, -and ``SimpleCMSBundle`` includes ``MultilangRouteProvider``, which is responsible -for fetching ``Route`` instances taking that into account. - -When rendering the actual URL from ``Route``, the locale prefix needs to be -put back, otherwise the resulting addresses wouldn't specify the locale they -refer to. To do so, ``MultilangPage`` uses the already mentioned ``getStaticPrefix()`` -implementation. - -Exemplifying: An incoming request for ``contact`` would be prefixed with -``/cms/simple`` basepath, and the storage would be queried for ``/cms/simple/contact/``. -However, in a multilanguage setup, the locale is prefixed to the URI, resulting -in a query either for ``/cms/simple/en/contact/`` or ``/cms/simple/de/contact/``, -which would require two independent entries to exist for the same actual -content. With the above mentioned approach, the ``locale`` is stripped from -the URI prior to ``basepath`` prepending, resulting in a query for ``/cms/simple/contact/`` -in both cases. - - -Routes and Redirections -....................... - -``SimpleCMSBundle`` includes ``MultilangRoute`` and ``MultilangRedirectRoute``, -extensions to the ``Route`` and ``RedirectRoute`` found in ``RoutingExtraBudle``, -but with the necessary changes to handle the prefix strategy discussed earlier. - - -Content Handling -~~~~~~~~~~~~~~~~ - -``Route`` instances are responsible for determining which ``Controller`` -will handle the current request. :ref:`start-routing-getting-controller-template` -shows how Symfony CMF SE can determine which ``Controller`` to use when rendering -a certain content, and ``SimpleCMSBundle`` uses these mechanisms to do so. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - generic_controller: ~ # symfony_cmf_content.controller:indexAction - - .. code-block:: xml - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_simple_cms', array( - 'generic_controller' => null, - )); - -By default, it uses the above mentioned service, which instanciates ``ContentController`` -from ``ContentBundle``. The default configuration associates all ``document_class`` -instances with this ``Controller``, and specifies no default template. However, -you can configure several ``controllers_by_class`` and ``templates_by_class`` -rules, which will associate, respectively, ``Controller`` and templates to -a specific Content type. Symfony CMF SE includes an example of both in its -default configuration. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - routing: - templates_by_class: - Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page: SymfonyCmfSimpleCmsBundle:Page:index.html.twig - controllers_by_class: - Symfony\Cmf\Bundle\RoutingExtraBundle\Document\RedirectRoute: symfony_cmf_routing_extra.redirect_controller:redirectAction - - .. code-block:: xml - - - - - - SymfonyCmfSimpleCmsBundle:Page:index.html.twig - - symfony_cmf_routing_extra.redirect_controller:redirectAction - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_simple_cms', array( - 'routing' => array( - 'templates_by_class' => array( - 'Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page' => SymfonyCmfSimpleCmsBundle:Page:index.html.twig, - 'Symfony\Cmf\Bundle\RoutingExtraBundle\Document\RedirectRoute' => 'symfony_cmf_routing_extra.redirect_controller:redirectAction', - ), - ), - )); - -These configuration parameters will be used to instantiate :ref:`Route Enhancers `. -More information about them can be found in the :doc:`../components/routing` -component documentation page. - -These specific example determines that content instances of class ``Page`` -will be rendered using the above mentioned template, if no other is explicitly -provided by the associated ``Route`` (which, in this case, is ``Page`` itself). -It also states that all contents that instantiate ``RedirectRoute`` will -be rendered using the mentioned ``Controller`` instead of the default. Again, -the actual ``Route`` can provided a controller, in will take priority over -this one. Both the template and the controller are part of ``SimpleCMSBundle``. - - -Menu Generation -~~~~~~~~~~~~~~~ - -Like mentioned before, ``Page`` implements ``NodeInterface``, which means -it can be used to generate ``MenuItem`` that will, in turn, be rendered into -HTML menus presented to the user. - -To do so, the default ``MenuBundle`` mechanisms are used, only a custom ``basepath`` -is provided to the ``PHPCRMenuProvider`` instance. This is defined in ``SimpleCMSBundle`` -configuration options, and used when handling content storage, to support -functionality as described in :doc:`menu` documentation. This parameter is -optional, can be configured like so: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - use_menu: ~ # defaults to auto , true/false can be used to force providing / not providing a menu - basepath: ~ # /cms/simple - - .. code-block:: xml - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_simple_cms', array( - 'use_menu' => null, - 'basepath' => null, - )); - - -Admin Support -------------- - -``SimpleCMSBundle`` also includes the administration panel and respective -service needed for integration with `SonataDoctrinePHPCRAdminBundle `_, -a backoffice generation tool that can be installed with Symfony CMF. Para más información sobre esta, por favor consulta la sección de documentación del `paquete `_. - -Las interfaces de administración incluidas serán cargadas automáticamente si instalas el ``SonataDoctrinePHPCRAdminBundle`` (consulta :doc:`../tutorials/creating-cms-using-cmf-and-sonata` para instrucciones sobre cómo hacerlo). You can change this behaviour with the -following configuration option: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - use_sonata_admin: ~ # defaults to auto , true/false can be used to using / not using SonataAdmin - - .. code-block:: xml - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('symfony_cmf_simple_cms', array( - 'use_sonata_admin' => null, - )); - - -Fixtures --------- - -``SimpleCMSBundle`` includes a support class for integration with `DoctrineFixturesBundle `_, -aimed at making loading initial data easier. A working example is provided -in Symfony CMF SE, that illustrates how you can easily generate ``MultilangPage`` -and ``MultilangMenuNode`` instances from yml files. - - -Configurando ------------- - -Este paquete se configura usando un conjunto de parámetros, pero todos ellos son opcionales. You can go to the :doc:`../bundles/simple-cms` reference page for the -full configuration options list and aditional information. - -Further Notes -------------- - -For more information on the SimpleCMSBundle, please refer to: - -- :doc:`../bundles/simple-cms` for configuration reference and advanced details - about the bundle. -- :doc:`../getting-started/routing` for information about the routing component - in which ``SimpleCMSBundle`` is based on. -- :doc:`../getting-started/content` for information about the base content - bundle that ``SimpleCMSBundle`` depends on. -- :doc:`../getting-started/menu` for information about the menu system used - by ``SimpleCMSBundle``. diff --git a/_sources/cmf/index.txt b/_sources/cmf/index.txt deleted file mode 100644 index 45e526a..0000000 --- a/_sources/cmf/index.txt +++ /dev/null @@ -1,126 +0,0 @@ -Documentación del ``CMF`` de *Symfony* -====================================== - -Bienvenido a la documentación oficial del `Symfony Content Management Framework`_ (``CMF`` en adelante). - -El proyecto ``CMF`` de *Symfony2* está organizado por la comunidad de *Symfony* y cuenta con muchas compañías patrocinadoras y prominentes líderes del código abierto que implementan la filosofía del `CMS desacoplado`_. Puedes leer y aprender más del proyecto en su página de `información`_. - -Esta documentación actualmente está en desarrollo y todavía muy lejos de completarse. Ve `planificación de la documentación`_ para un vistazo general del trabajo que falta hacer. ¿Quieres ayudar? ¡Gracias, toda ayuda es muy apreciada! -La fuente de `la documentación está alojada en github`_. - -Declaración de misión ---------------------- - - El proyecto ``CMF`` de *Symfony* facilita a los desarrolladores la adición de funcionalidad *CMS* a sus aplicaciones construidas con la plataforma *Symfony2* de *PHP*. Los principios de desarrollo clave para el conjunto de paquetes proporcionados son la escalabilidad, usabilidad, documentación y un completo banco de pruebas. - -¿Por qué otro *CMS*? --------------------- - -De hecho, consideramos que este proyecto es un ``CMF`` (por **content management framework**) un **marco para gestionar contenido**, más que -un *CMS* (sistema de administración de contenido). La razón es que sólo estamos **proporcionando herramientas para construir un CMS personalizado**. Evidentemente hoy día existen muchas soluciones *CMS* disponibles, pero tienden a ser adaptadas en primer lugar hacia usuarios finales y también muchas tienden -a llevar mucho equipaje delegado que las hace menos que **ideales para desarrollar aplicaciones altamente personalizables** tal como es posible con `Symfony2`_. - -¿A quién está dirigido? ------------------------ - -Básicamente hay dos audiencias principales: - -#. Developers that have built an existing custom application with Symfony2 and need a fast - way to add support for content management. Be it sophisticated CMS features like semantic - content, inline editing, multi-channel delivery etc. or just a few content pages for things - like the about/contact pages. - -#. Developers that need to build a highly customized authoring and content delivery - solution that no out-of-the-box CMS can properly provide through customization alone. - - -Cómo empezar ------------- - -¿Empiezas a conocer el ``CMF``? ¿Quieres saber si el ``CMF`` es adecuado para tu proyecto? Comienza aquí. - -.. toctree:: - :maxdepth: 1 - - getting-started/installing-symfony-cmf - getting-started/routing - getting-started/content - getting-started/menu - getting-started/simplecms - - -Guías rápidas -------------- - -¿Quieres saber más sobre el ``CMF`` y cómo puedes configurar cada parte? Hay una guía para cada una de ellas. - -.. toctree:: - :maxdepth: 1 - - tutorials/choosing-a-storage-layer - tutorials/installing-cmf-core - tutorials/installing-configuring-doctrine-phpcr-odm - tutorials/installing-configuring-inline-editing - tutorials/creating-cms-using-cmf-and-sonata - tutorials/using-blockbundle-and-contentbundle - tutorials/handling-multilang-documents - -Paquetes --------- - -¿Buscando algúna información detallada sobre un paquete del ``CMF``? ¿Quieres una lista de todas las opciones de configuración de un paquete? ¿Quiere saber si puedes utilizar un paquete independientemente y cómo hacerlo? En este caso la referencia es el sitio ideal para ti. - -.. toctree:: - :maxdepth: 1 - - bundles/block - bundles/blog - bundles/content - bundles/core - bundles/create - bundles/phpcr-odm - bundles/menu - bundles/routing-extra - bundles/routing_auto - bundles/search - bundles/simple-cms - bundles/doctrine_phpcr_admin - bundles/tree-browser - -Recetario ---------- - -Soluciones específicas para necesidades especiales que van más allá del uso estándar. - -.. toctree:: - :maxdepth: 1 - - cookbook/phpcr-odm-custom-documentclass-mapper - cookbook/using-a-custom-route-repository - cookbook/installing-cmf-sandbox - -Componentes ------------ - -¿Buscando alguna información sobre los componentes de bajo nivel del ``CMF``? - -.. toctree:: - :maxdepth: 1 - - components/routing - -Colaborando ------------ - -.. toctree:: - :maxdepth: 1 - - contributing/code - contributing/license - -.. _`CMS desacoplado`: http://decoupledcms.org -.. _`Symfony2`: http://symfony.com -.. _`información`: http://cmf.symfony.com/about -.. _`planificación de la documentación`: https://github.com/symfony-cmf/symfony-cmf/wiki/Documentation-Planning -.. _`Symfony Content Management Framework`: http://cmf.symfony.com -.. _`la documentación está alojada en github`: https://github.com/symfony-cmf/symfony-cmf-docs diff --git a/_sources/cmf/tutorials/choosing-a-storage-layer.txt b/_sources/cmf/tutorials/choosing-a-storage-layer.txt deleted file mode 100644 index c5bcf89..0000000 --- a/_sources/cmf/tutorials/choosing-a-storage-layer.txt +++ /dev/null @@ -1,85 +0,0 @@ -Eligiendo una capa de almacenamiento -==================================== - -Cuándo construyes un *CMS*, sin duda, la elección para la capa de almacenamiento es una de las decisiones clave a tomar. Tienes que considerar muchos factores, la buena noticia es que con todos los componentes y paquetes en el ``CMF`` nos preocupamos en proporcionar los puntos de extensión necesarios para garantizar que contamos con una **capa de almacenamiento que sigue siendo agnóstica para el CMF**. - -El objetivo de esta guía es explicar las consideraciones y por qué sugerimos `PHPCR `_ y `PHPCR-ODM `_ como la base ideal para un *CMS*. Sin embargo, todos los componentes y paquetes se pueden integrar con otras soluciones con un muy pequeño esfuerzo. - -.. index:: PHPCR, ODM, ORM - -Requisitos para la capa de almacenamiento de un *CMS* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Al nivel más fundamental de un *CMS* está el almacenamiento, por lo tanto el primer requisito de un *CMS* es que debe proporcionar los medios para almacenar contenido con diferentes propiedades. - -Un *CMS* tiene muy diferentes necesidades de almacenamiento que por ejemplo un sistema para procesar órdenes. -Aun así observa que es enteramente posible y de hecho así se pretende que la iniciativa del ``CMF`` habilite a los desarrolladores para combinar el ``CMF`` con un sistema para procesar órdenes. Así por ejemplo, uno podría crear una solución de compra utilizando el ``CMF`` para almacenar el catalogo de productos, mientras utilizas otro sistema para mantener el inventario, datos del cliente y pedidos. Esto da lugar al segundo requisito, *un CMS debe proporcionar los medios para referenciar el contenido*, almacenando ambos dentro del *CMS*, pero también en otros sistemas. - -El contenido real en un *CMS* tiende a estar organizado en una estructura de árbol, imitando un sistema de archivos. Ten en cuenta que los autores de contenido podrían querer utilizar diferentes estructuras para organizar el contenido y para organizar otros aspectos como el menú y el enrutado. -Esto nos lleva al tercer requisito, *un CMS debe proporcionar los medios para representar el contenido como una estructura de árbol*. -Además, un cuarto requisito es que *un CMS debería permitir mantener varias estructuras de árbol independientes*. - -En general, los datos dentro de un *CMS* tienden a no estar estructurados. Si bien, varias páginas dentro del *CMS* podrían ser muy similares, hay una buena posibilidad de que habría muchas permutaciones que necesitarían diferentes campos extra, por lo tanto *un CMS no tiene que aplicar un esquema singular al contenido*. -Dicho esto, a fin de mantener mejor la estructura y contenido de las capas de la interfaz de usuario que genéricamente permiten la visualización de elementos de contenido, es importante ser capaz de expresar reglas opcionales que se deben seguir y que también pueden ayudar a anexar significado semántico adicional. Así que *un CMS debe proporcionar los medios para opcionalmente definir elementos del esquema de contenido*. - -Este requisito en realidad también está relacionado a otra necesidad, en que un *CMS* debe facilitar a los autores de contenido la preparación de una serie de cambios en un entorno de ensayo que luego se tiene que enviar al sitio vivo en un solo paso. Esto significa que es otro requisito en que es necesario que *un CMS debe apoyar el movimiento y exportación de contenido entre estructuras de árbol independientes*. -Ten en cuenta que la exportación puede ser útil también para las copias de seguridad. - -Al realizar cambios en ella sin embargo también sería útil poder versionar los conjuntos cambiados, de manera que permanezcan disponibles para fines históricos, pero también para poder revertirlos cuando sea necesario. Por lo tanto, el siguiente requisito es que un *CMS debería proporcionar la habilidad de versionar el contenido*. - -Puesto que vivimos en un mundo globalizado, los sitios web deben ofrecer contenido en múltiples idiomas abarcando diferentes regiones. No obstante no todas las piezas del contenido se deben traducir y eventualmente sólo algunas se deberían traducir, pero hasta entonces se le debe presentar al usuario el contenido en uno de los idiomas disponibles, por lo que *un CMS debería proporcionar la capacidad de almacenar el contenido en diferentes idiomas, con opcionales reglas alternativas*. - -Puesto que generalmente un *CMS* tiende a almacenar una creciente cantidad de contenido será necesario proporcionar alguna forma para que los usuarios busquen contenido, incluso, cuando el usuario sólo tiene una muy difusa idea sobre el contenido que está buscando, dando lugar al requisito de que *un CMS debe proporcionar completa capacidad de búsqueda de texto*, idealmente, aprovechando tanto la estructura del árbol de contenido como el esquema de datos. - -Otra necesidad popular es limitar la lectura y/o escritura de contenido a usuarios o grupos específicos. Idealmente, esta solución también se podría integrar con la estructura de árbol. Por lo que debería ser útil que *un CMS ofreciera funciones para definir el control de acceso que aproveche la estructura de árbol para gestionar rápidamente el acceso a subárboles completos*. - -Por último, no todos los pasos del proceso de creación de contenido los llevará a cabo la misma persona. -De hecho, puede haber múltiples pasos, todos los cuales no los debería llevar a cabo una sola persona. En cambio algunos de los pasos incluso los podría ejecutar una máquina. Así, por ejemplo un fotógrafo puede cargar una nueva imagen, un autor de contenido puede adjuntar la foto a algún texto, entonces el sistema automáticamente genera miniaturas y optimiza la interpretación en la *web*, y finalmente, un editor decide sobre la publicación final. Por lo tanto *un CMS debe proporcionar la capacidad para ayudar en la gestión del flujo de trabajo*. - -Resumen -------- - -Aquí tienes un resumen de los requisitos anteriores. Ten en cuenta que algunos de los requisitos tienen un *debe*, mientras otros sólo tienen un *debería*. Evidentemente dependiendo de tu caso de uso podrías priorizar las características de manera diferente: - -* Un *CMS* debe proporcionar un medio para almacenar contenido con diferentes propiedades -* Un *CMS* debe proporcionar un medio para referirse al contenido -* Un *CMS* debe proporcionar medio para representar el contenido como una estructura de árbol -* Un *CMS* debe proporcionar completa capacidad de búsqueda de texto -* Un *CMS* no debería aplicar un esquema singular para el contenido -* Un *CMS* debe proporcionar los medios para opcionalmente definir un esquema para elementos del contenido -* Un *CMS* debería permitir mantener varias estructuras de árbol independientes -* Un *CMS* debería ser compatible con el movimiento y exportación de contenido entre estructuras de árbol independientes -* Un *CMS* debería proporcionar la habilidad para tener contenido versionado -* Un *CMS* debería proporcionar la habilidad de almacenar contenido en diferentes idiomas, con opcionales reglas alternativas -* Un *CMS* debe proporcionar la capacidad para definir controles de acceso -* Un *CMS* debería proporcionar la capacidad para asistir en la administración de los flujos de trabajo - -*RDBMS* -~~~~~~~ - -Viendo los requisitos anteriores se pone de manifiesto que fuera de la caja un *RDBMS* es el instrumento adecuado para hacer frente a las necesidades de un *CMS*. Los *RDBMS* nunca tuvieron la intención de almacenar estructuras de árbol de contenido no estructurado. Realmente el único requisito que cubre el *RDBMS* de la lista anterior es la capacidad de almacenar contenido, alguna manera para hacer referencia al contenido, mantener múltiples estructuras de contenido independientes y un nivel básico de control de acceso y disparadores. - -Este no es un defecto del *RDBMS* en el sentido de que fueron diseñados simplemente para un caso de uso diferente: la capacidad de almacenar, manipular y agregar datos estructurados. Esto las hace ideales para el almacenamiento de inventarios y pedidos. - -Lo cual no quiere decir que es imposible construir un sistema en lo alto de un *RDBMS* que se ocupe de algunos o incluso la totalidad de los temas anteriores. Algunos *RDBMS* nativamente soportan consultas recursivas, lo cual puede ser útil para recuperar las estructuras de árbol. Incluso si falta tal soporte nativo, hay algoritmos como camino materializado y conjuntos anidados que pueden permitir un eficiente almacenamiento y recuperación de estructuras de árbol para diferentes casos de uso. - -El punto es, sin embargo, que se requieren estos algoritmos y código en lo alto de un *RDBMS* que también se vinculen a la lógica de negocio con un determinado *RDBMS* y/o algoritmo, aunque algunos de ellos se pueden abstraer. Así que de nuevo usando un *ORM* podrías crear un sistema para gestionar las estructuras de árbol con diferentes algoritmos que impidan la unión de la lógica de negocio del *CMS* a un algoritmo particular. - -Sin embargo hay que decir una vez más, que todos los paquetes y componentes del ``CMF`` se han desarrollado para aceptar cualquier *API* de almacenamiento persistente y damos la bienvenida a las contribuciones para agregar implementaciones para otros sistemas de almacenamiento. Así, por ejemplo actualmente ``RoutingExtraBundle`` sólo ofrece clases de documento para el *ODM* de *PHPCR*, pero las interfaces definidas en el componente de enrutado son de almacenamiento agnóstico y aceptaríamos una contribución para añadir soporte al *ORM* de *Doctrine*. - -*PHPCR* -~~~~~~~ - -Esencialmente *PHPCR* es un conjunto de interfaces para abordar la mayoría de los requisitos de la lista anterior. -Esto significa que *PHPCR* es totalmente independiente del almacenamiento en el sentido de que es posible realmente poner cualquier solución de persistencia detrás de *PHPCR*. Así pues, en la misma forma que un *ORM* puede soportar diferentes algoritmos de almacenamiento de árbol a través de algún complemento, *PHPCR* tiene como objetivo proporcionar una *API* para relajar completamente las necesidades del *CMS*, por lo tanto separando limpiamente toda la lógica del negocio de tu *CMS* a causa de la elección de persistencia. Como asunto natural, la única de las características anteriores no soportada nativamente por *PHPCR* es el apoyo para las traducciones. - -Gracias a la disponibilidad de varias implementaciones de *PHPCR* que apoyan diversos tipos de opciones de persistencia, la creación de un *CMS* en lo alto de *PHPCR* significa que los usuarios finales tienen la capacidad de escoger y elegir lo que funciona mejor para ellos, sus recursos, su experiencia y sus necesidades de escalabilidad. - -Así que para los casos de uso más simples por ejemplo está una solución basada en el *DBAL* de *Doctrine* ofrecida por la implementación *PHPCR* de `Jackalope `_ que puede utilizar el *RDBMS* de *SQLite* que viene con *PHP*. En el otro extremo del espectro ``Jackalope`` también es compatible con `Jackrabbit `_ el cual apoya las agrupaciones y puede manejar eficientemente los datos en cientos de gigabytes. De manera predeterminada ``Jackrabbit`` simplemente utiliza el sistema de archivos para la persistencia, pero también puede utilizar un *RDBMS*. Sin embargo, futuras versiones apoyarán a *MongoDB* y otras soluciones *NoSQL* como *CouchDB* o *Cassandra* es totalmente posible. Una vez más, el cambio de la solución de persistencia no requerirá cambios en el código puesto que la lógica de negocio sólo va unida a las interfaces *PHPCR*. - -Por favor ve :doc:`installing-configuring-doctrine-phpcr-odm` para más detalles sobre las implementaciones *PHPCR* disponibles y sus requisitos y la forma de configurar *Symfony2* con una de ellas. - -*ODM* de *PHPCR* -~~~~~~~~~~~~~~~~ - -Cuando mencione arriba utilizar *PHPCR* no significa renunciar a un *RDBMS*. En muchas maneras, puedes considerar a *PHPCR* una solución *ORM* especializada para *CMS*. No obstante, mientras *PHPCR* trabaja con los así llamados *nodos*, en un *ORM* las personas esperan ser capaces de asignar instancias de clase a una capa de persistencia. Esto, exactamente es lo que proporciona el *ODM* de *PHPCR*. Sigue las mismas clases de interfaz del *ORM* de *Doctrine* al mismo tiempo que expone todas las capacidades adicionales de *PHPCR*, como los árboles y el versionado. Por otra parte, también ofrece soporte nativo para traducciones, que cubren la única omisión del *PHPCR* de la lista de requisitos para una solución de almacenamiento de un *CMS* mencionada anteriormente. diff --git a/_sources/cmf/tutorials/creating-cms-using-cmf-and-sonata.txt b/_sources/cmf/tutorials/creating-cms-using-cmf-and-sonata.txt deleted file mode 100644 index 0936378..0000000 --- a/_sources/cmf/tutorials/creating-cms-using-cmf-and-sonata.txt +++ /dev/null @@ -1,224 +0,0 @@ -Creando un *CMS* con ``CMF`` y *Sonata* -======================================= - -El objetivo de esta guía es crear un sistema de administración de contenido sencillo que utiliza el ``CMF`` así como `SonataAdminBundle `_ y :doc:`../bundles/doctrine_phpcr_admin`. - - -.. index:: Sonata, SonataAdminBundle, SonataDoctrinePHPCRAdminBundle, SonatajQueryBundle, FOSJsRoutingBundle, TreeBundle, TreeBrowserBundle - -Requisitos previos ------------------- - -- :doc:`installing-cmf-core` -- `Symfony SecurityBundle `_ (requerido por las plantillas predefinidas del ``SonataAdminBundle``) - -Instalando ----------- - -Descarga los paquetes -~~~~~~~~~~~~~~~~~~~~~ - -Añade lo siguiente a tu archivo :file:`composer.json`: - -.. code-block:: javascript - - "require": { - ... - "sonata-project/doctrine-phpcr-admin-bundle": "1.0.*", - } - -Y luego ejecuta: - -.. code-block:: bash - - php composer.phar update - -Iniciando los paquetes -~~~~~~~~~~~~~~~~~~~~~~ - -Luego, inicia los paquetes en el archivo :file:`app/AppKernel.php` añadiéndolos al método ``registerBundles``: - -.. code-block:: php - - public function registerBundles() - { - $bundles = array( - // ... - - // soporte para la administración - new Symfony\Cmf\Bundle\TreeBrowserBundle\SymfonyCmfTreeBrowserBundle(), - new Sonata\jQueryBundle\SonatajQueryBundle(), - new Sonata\BlockBundle\SonataBlockBundle(), - new Sonata\AdminBundle\SonataAdminBundle(), - new Sonata\DoctrinePHPCRAdminBundle\SonataDoctrinePHPCRAdminBundle(), - new FOS\JsRoutingBundle\FOSJsRoutingBundle(), - ); - // ... - } - -Configurando ------------- - -Añade los paquetes de ``sonata`` a la configuración de tu aplicación - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - sonata_block: - default_contexts: [cms] - blocks: - sonata.admin.block.admin_list: - contexts: [admin] - sonata_admin_doctrine_phpcr.tree_block: - settings: - id: '/cms' - contexts: [admin] - - sonata_admin: - templates: - # plantillas globales predefinidas - ajax: SonataAdminBundle::ajax_layout.html.twig - dashboard: - blocks: - # muestra un bloque panel - - { position: right, type: sonata.admin.block.admin_list } - - { position: left, type: sonata_admin_doctrine_phpcr.tree_block } - - sonata_doctrine_phpcr_admin: - document_tree: - Doctrine\ODM\PHPCR\Document\Generic: - valid_children: - - all - Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page: ~ - Symfony\Cmf\Bundle\RoutingExtraBundle\Document\Route: - valid_children: - - Symfony\Cmf\Bundle\RoutingExtraBundle\Document\Route - - Symfony\Cmf\Bundle\RoutingExtraBundle\Document\RedirectRoute - Symfony\Cmf\Bundle\RoutingExtraBundle\Document\RedirectRoute: - valid_children: [] - Symfony\Cmf\Bundle\MenuBundle\Document\MenuNode: - valid_children: - - Symfony\Cmf\Bundle\MenuBundle\Document\MenuNode - - Symfony\Cmf\Bundle\MenuBundle\Document\MultilangMenuNode - Symfony\Cmf\Bundle\MenuBundle\Document\MultilangMenuNode: - valid_children: - - Symfony\Cmf\Bundle\MenuBundle\Document\MenuNode - - Symfony\Cmf\Bundle\MenuBundle\Document\MultilangMenuNode - - fos_js_routing: - routes_to_expose: - - admin_sandbox_main_editablestaticcontent_create - - admin_sandbox_main_editablestaticcontent_delete - - admin_sandbox_main_editablestaticcontent_edit - - admin_bundle_menu_menunode_create - - admin_bundle_menu_menunode_delete - - admin_bundle_menu_menunode_edit - - admin_bundle_menu_multilangmenunode_create - - admin_bundle_menu_multilangmenunode_delete - - admin_bundle_menu_multilangmenunode_edit - - admin_bundle_content_multilangstaticcontent_create - - admin_bundle_content_multilangstaticcontent_delete - - admin_bundle_content_multilangstaticcontent_edit - - admin_bundle_routingextra_route_create - - admin_bundle_routingextra_route_delete - - admin_bundle_routingextra_route_edit - - admin_bundle_simplecms_page_create - - admin_bundle_simplecms_page_delete - - admin_bundle_simplecms_page_edit - - symfony_cmf_tree_browser.phpcr_children - - symfony_cmf_tree_browser.phpcr_move - - sonata.admin.doctrine_phpcr.phpcrodm_children - - sonata.admin.doctrine_phpcr.phpcrodm_move - -Añade la ruta a la configuración de enrutado: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - admin: - resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml' - prefix: /admin - - sonata_admin: - resource: . - type: sonata_admin - prefix: /admin - - doctrine_phpcr_admin_bundle_odm_browser: - resource: "@SonataDoctrinePHPCRAdminBundle/Resources/config/routing/phpcrodmbrowser.xml" - - fos_js_routing: - resource: "@FOSJsRoutingBundle/Resources/config/routing/routing.xml" - - symfony_cmf_tree: - resource: . - type: 'symfony_cmf_tree' - - -Activos de ``Sonata`` ---------------------- - -.. code-block:: bash - - app/console assets:install --symlink - - -Defining own Admin classes --------------------------- - -The CMF bundles come with predefined admin classes which will be activated -automatically if Sonata PHPCR-ODM Admin is loaded. If you need to write -different admins and do not want to load the defaults, you can deactivate the -loading - see the documentation of the respective bundles. - -To load your own Admin service, you need to declare it as a service, tag with -``sonata.admin`` with ``manager_type="doctrine_phpcr"``. For the admin to work -properly, you need to add a call for method ``setRouteBuilder`` to set it to -the service ``sonata.admin.route.path_info_slashes``, or your Admin will not -work. - -The constructor expects three arguments, code, document class and controller -name. You can pass an empty argument for the code, the document class must be -the fully qualified class name of the document this admin is for and the third -argument can be used to set a custom controller that does additional operations -over the default sonata CRUD controller. - -.. configuration-block:: - - .. code-block:: xml - - - - - %my_bundle.document_class% - SonataAdminBundle:CRUD - - - - - - - -Finalmente ----------- - -Ahora ``Sonata`` está configurado para trabajar con *PHPCR*, puedes acceder al panel vía ``/admin/dashboard`` de tu sitio. - - -Tree Problems -------------- - -If you have not yet added anything to the content repository, the tree view will not load as it -cannot find a root node. To fix this, load some data as fixtures by following this doc: - -- :doc:`using-blockbundle-and-contentbundle` - -Lectura complementaria ----------------------- - -* :doc:`../bundles/doctrine_phpcr_admin` -* :doc:`handling-multilang-documents` diff --git a/_sources/cmf/tutorials/handling-multilang-documents.txt b/_sources/cmf/tutorials/handling-multilang-documents.txt deleted file mode 100644 index a01a4c3..0000000 --- a/_sources/cmf/tutorials/handling-multilang-documents.txt +++ /dev/null @@ -1,210 +0,0 @@ -Gestionando documentos multilingües -=================================== - -The goal of the tutorial is to describe all the steps that are needed -to be taken to use multi-language documents as clearly as possible. - - -Por favor, primero lee esto ---------------------------- - -* Necesitas entender cómo utilizar el *PHPCR-ODM*. Ve la introducción en :doc:`Documentación del paquete PHPCR-ODM de Doctrine <../bundles/phpcr-odm>` - - -Lunetics LocaleBundle ---------------------- - -The CMF recommends to rely on the `LuneticsLocaleBundle `_ -to handle initial locale selection when a user first visits the site, -and to provide a locale switcher. - -To install the bundle, require it in your project with ``./composer.phar require lunetics/locale-bundle`` -and then instantiate ``Lunetics\LocaleBundle\LuneticsLocaleBundle`` in your AppKernel.php. - -You also need the ``intl`` php extension installed and enabled. (Otherwise -composer will tell you it can't find ext-intl.) If you get issues that some -locales can not be loaded, have a look at `this discussion about ICU `_. - -Then configure it in the main application configuration file. As -there are several CMF bundles wanting the list of allowed locales, -we recommend putting them into a parameter ``%locales%``, see the -`cmf-sandbox config.yml file `_ for an example. - -.. tip:: - - Whenever you do a sub-request, for example to call a controller from a twig - template, do not forget to pass the ``app.request.locale`` along or you will - lose the request locale and fall back to the default. - See for example the action to include the create.js javascript files in the - :ref:`create.js reference `. - -PHPCR-ODM multi-language Documents ----------------------------------- - -You can mark any properties as being translatable and have the document manager -store and load the correct language for you. Note that translation always happens -on a document level, not on the individual translatable fields. - -.. code-block:: php - - `_ -and see :ref:`bundle-phpcr-odm-multilang-config` to configure PHPCR-ODM correctly. - -Most of the CMF bundles provide multi-language documents, for example MultilangStaticContent, -MultilangMenuNode or MultilangSimpleBlock. The routing is different, as explained in the next -section. - - -Enrutado --------- - -The DynamicRouter uses a route source to get routes that could match a -request. The concept of the default PHPCR-ODM source is to map the request URL -onto an id, which in PHPCR terms is the repository path to a node. This -allows for a very efficient lookup without needing a full search over the -repository. But a PHPCR node has exactly one path, therefore we need a separate -route document for each locale. The cool thing with this is that we can localize -the URL for each language. Simply create one route document per locale, and -set a default value for _locale to point to the locale of that route. - -As all routes point to the same content, the route generator can handle picking -the correct route for you when you generate the route from the content. -See also :ref:`component-route-generator-and-locales`. - - -Sonata PHPCR-ODM Admin ----------------------- - -This section explains how to make Sonata Admin handle multi-language documents. You should -already have set up Sonata PHPCR-ODM Admin and understand how it works, see -:doc:`Creating a CMS using the CMF and Sonata `. - -.. note:: - - The following assumes that you installed the LuneticsLocaleBundle as explained above. - If you want to use something else or write your own locale handling, first think if - it would not make sense to give the Lunetics bundle a try. If you are still convinced - you will need to adapt the following template examples to your way of building a - locale switcher. - - -The first step is to configure sonata admin. We are going to place the LuneticsLocaleBundle -language switcher in the ``topnav`` bar. -To do this we need to configure the template for the "user_block" as shown below: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - sonata_admin: - ... - templates: - user_block: AcmeCoreBundle:Admin:admin_topnav.html.twig - -Y la plantilla se ve como esta - -.. code-block:: jinja - - {# src/Acme/CoreBundle/Resources/views/Admin/admin_topnav.html.twig #} - {% extends 'SonataAdminBundle:Core:user_block.html.twig' %} - - {% block user_block %} - {{ locale_switcher(null, null, 'AcmeCoreBundle:Admin:switcher_links.html.twig') }} - {{ parent() }} - {% endblock %} - -We tell the ``locale_switcher`` to use a custom template to display the links, which looks like this: - -.. code-block:: jinja - - {# src/Acme/CoreBundle/Resources/views/Admin/switcher_links.html.twig #} - Switch to : - {% for locale in locales %} - {% if loop.index > 1 %} | {% endif %}{{ locale.locale_target_language }} - {% endfor %} - - -Now what is left to do is to update the sonata routes to become locale aware: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - - admin_dashboard: - pattern: /{_locale}/admin/ - defaults: - _controller: FrameworkBundle:Redirect:redirect - route: sonata_admin_dashboard - permanent: true # este para 301 - - admin: - resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml' - prefix: /{_locale}/admin - - sonata_admin: - resource: . - type: sonata_admin - prefix: /{_locale}/admin - - # redirige las rutas a las rutas no regionales - admin_without_locale: - pattern: /admin - defaults: - _controller: FrameworkBundle:Redirect:redirect - route: sonata_admin_dashboard - permanent: true # este para 301 - - admin_dashboard_without_locale: - pattern: /admin/dashboard - defaults: - _controller: FrameworkBundle:Redirect:redirect - route: sonata_admin_dashboard - permanent: true # este para 301 - -When we now reload the admin dashboard, the url should be prefixed with our -default locale, for example ``/de/admin/dashboard``. When clicking on the -language switcher the page reloads and displays the correct content for the -requested language. - -The provided sonata admin classes map the locale field of the multi-language -documents to the form. You need to do the same in your admins, in order -to create new translations. Otherwise the language fallback of PHPCR-ODM will -make you update the original language, even when you request a different locale. -With the mapped locale field, the editor can chose if he needs to create a new -language version or updates the loaded one. - -Happy editing. - - -Frontend editing and multi-language ------------------------------------ - -When using the CreateBundle, you do not need to do anything at all to get -multi-language support. PHPCR-ODM will deliver the document in the requested -language, and the callback URL is generated in the request locale, -leading to save the edited document in the same language as it was loaded. - - -.. note:: - - If a translation is missing, language fallback kicks in, both when viewing the - page but also when saving the changes, so you always update the current locale. - - It would make sense to offer the user the choice whether he wants to create - a new translation or update the existing one. There is this `issue `_ - in the CreateBundle issue tracker. diff --git a/_sources/cmf/tutorials/installing-cmf-core.txt b/_sources/cmf/tutorials/installing-cmf-core.txt deleted file mode 100644 index 30a5deb..0000000 --- a/_sources/cmf/tutorials/installing-cmf-core.txt +++ /dev/null @@ -1,126 +0,0 @@ -Instalando y configurando el núcleo del ``CMF`` -=============================================== - -El objetivo de esta guía es instalar los componentes mínimos necesarios (el «núcleo») del ``CMF`` con la mínima configuración requerida. A partir de aquí, puedes empezar a incorporar en tu aplicación la funcionalidad del ``CMF`` que necesites. - -Esta guía está dirigida a usuarios experimentados que quieren conocer todos los -detalles sobre el ``CMF`` de *Symfony*. Si este es tu primer encuentro con el ``CMF`` de *Symfony* es buena idea empezar con: - -- La página :doc:`../getting-started/installing-symfony-cmf` para instrucciones sobre cómo instalar rápidamente el ``CMF`` (recommendada para desarrolladores) -- :doc:`../cookbook/installing-cmf-sandbox` para instrucciones sobre cómo instalar una demostración del recinto de seguridad. - -.. index:: RoutingExtraBundle, CoreBundle, MenuBundle, ContentBundle, SonataBlockBundle, KnpMenuBundle, instalando - -Requisitos previos ------------------- - -- `Instalando Symfony2 `_ (2.1.x) -- :doc:`installing-configuring-doctrine-phpcr-odm` - -Instalando ----------- - -Descarga los paquetes -~~~~~~~~~~~~~~~~~~~~~ - -Añade lo siguiente a tu archivo :file:`composer.json`: - -.. code-block:: javascript - - "minimum-stability": "dev", - "require": { - ... - "symfony-cmf/symfony-cmf": "1.0.*" - } - -Y luego ejecuta: - -.. code-block:: bash - - php composer.phar update - - -Iniciando paquetes -~~~~~~~~~~~~~~~~~~ - -Luego, inicia los paquetes en :file:`AppKernel.php` añadiéndolos al método ``registerBundles``: - -.. code-block:: php - - // app/AppKernel.php - - public function registerBundles() - { - $bundles = array( - // ... - - new Symfony\Cmf\Bundle\RoutingExtraBundle\SymfonyCmfRoutingExtraBundle(), - new Symfony\Cmf\Bundle\CoreBundle\SymfonyCmfCoreBundle(), - new Symfony\Cmf\Bundle\MenuBundle\SymfonyCmfMenuBundle(), - new Symfony\Cmf\Bundle\ContentBundle\SymfonyCmfContentBundle(), - new Symfony\Cmf\Bundle\BlockBundle\SymfonyCmfBlockBundle(), - - // Dependencias del SymfonyCmfMenuBundle - new Knp\Bundle\MenuBundle\KnpMenuBundle(), - - // Dependencias del SymfonyCmfBlockBundle - new Sonata\BlockBundle\SonataBlockBundle(), - ); - - // ... - } - -.. note:: - - Esto también habilita el *PHPCR-ODM* y dependencias relacionadas; las instrucciones de configuración se pueden encontrar en la documentación dedicada. - - -Configurando ------------- - -Para lograr que tu aplicación funcione, es muy poca la configuración necesaria. - -Configuración mínima -~~~~~~~~~~~~~~~~~~~~ - -Estos pasos son necesarios para garantizar que tu ``AppKernel`` todavía se ejecuta. - -Si no lo has hecho ya, asegúrate de que has seguido estos pasos en :doc:`installing-configuring-doctrine-phpcr-odm`: - - -- Inicia el ``DoctrinePHPCRBundle`` en :file:`app/AppKernel.php` -- Asegúrate que hay una sección ``doctrine_phpcr:`` en :file:`app/config/config.yml` -- Y la línea ``AnnotationRegistry::registerFile`` en :file:`app/autoload.php` - -Configura el ``BlockBundle`` en tu archivo :file:`config.yml`: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - sonata_block: - default_contexts: [cms] - -Configuración adicional -~~~~~~~~~~~~~~~~~~~~~~~ - -Puesto que la mayoría de los componentes del ``CMF`` utilizan el ``DynamicRouter`` del ``RoutingExtraBundle``, el cual de manera predeterminada no se carga, necesitarás habilitarlo de la siguiente manera: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_routing_extra: - chain: - routers_by_id: - symfony_cmf_routing_extra.dynamic_router: 200 - router.default: 100 - dynamic: - enabled: true - -Podrías querer configurar más en el enrutador dinámico, es decir escoger automáticamente los controladores basados en contenido. -Ve :doc:`../bundles/routing-extra` para detalles. - -Por ahora esta es la única configuración que necesitamos. Dominar la configuración de los diferentes paquetes será el tema de otras guías. Si estás buscando la configuración de un paquete específico dale un vistazo al :doc:`artículo del paquete <../index>` correspondiente. diff --git a/_sources/cmf/tutorials/installing-configuring-doctrine-phpcr-odm.txt b/_sources/cmf/tutorials/installing-configuring-doctrine-phpcr-odm.txt deleted file mode 100644 index e55865d..0000000 --- a/_sources/cmf/tutorials/installing-configuring-doctrine-phpcr-odm.txt +++ /dev/null @@ -1,335 +0,0 @@ -Instalando y configurando *Doctrine* y *PHPCR-ODM* -================================================== - -El ``CMF`` de *Symfony2* necesita almacenar el contenido en algún lugar. Muchos de los paquetes proporcionan documentos y asignación para el contenido del repositorio *PHP* --- Asignación para el objeto documento (*PHPCR-ODM*), y la documentación que actualmente está basada en torno a la utilización de este. No obstante, también debe ser posible utilizar cualquier otra forma de almacenamiento de contenido, tal como otro *ORM/ODM* o *MongoDB*. - -El objetivo de esta guía es instalar y configurar *Doctrine* y el *PHPCR-ODM*, listo para que empieces con el ``CMF``. - -Para más detalles ve la `documentación del PHPCR-ODM completa `_. -Alguna información adicional se puede encontrar en la referencia del :doc:`../bundles/phpcr-odm`, la cual mayoritariamente imita al `DoctrineBundle `_ estándar. - -Finalmente para información sobre *PHPCR* ve al `sitio web oficial de PHPCR `_. - -.. tip:: - - Si sólo quieres usar el *PHPCR* pero no el *PHPCR-ODM*, puedes omitir el paso sobre el registro de anotaciones y las partes que empiezan con ``odm`` de la sección de configuración. - -.. index:: PHPCR, ODM - -Requisitos previos ------------------- - -- php >= 5.3 -- libxml versión >= 2.7.0 (debido a un error en libxml http://bugs.php.net/bug.php?id=36501) -- phpunit >= 3.6 (si quieres ejecutar las pruebas) -- Symfony2 (versión 2.1.x) - -Instalando ----------- - -Escogiendo un repositorio para el contenido -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La primer cosa a decidir es qué repositorio de contenido utilizar. Un repositorio de contenido esencialmente es una base de datos que será la responsable para almacenar todo el contenido que quieras persistir. Este proporciona una *API* optimizada para las necesidades de un *CMS* (estructuras de árbol, referencias, versionado, completa búsqueda -de texto, etc.). Si bien, cada repositorio de contenido puede tener requisitos y características de rendimiento muy diferentes, la *API* es igual para todos ellos. - -Además, debido a que la *API* define un formato de importación/exportación, siempre puedes cambiar a una -diferente implementación del repositorio de contenido más tarde. - -Estas son las opciones disponibles: - -* **Jackalope con Jackrabbit** (Jackrabbit requiere Java, este puede persistir al sistema de archivos, a una base de datos, etc.) -* **Jackalope con DBAL de Doctrine** (requiere un *RDBMS* como *MySQL*, *PostgreSQL* o *SQLite*) -* **Midgard** (Requiere la extensión ``midgard2`` de *PHP* y un *RDBMS* como *MySQL*, *PostgreSQL* o *SQLite*) - -La siguiente documentación incluye ejemplos para todas las opciones de arriba. - -.. tip:: - - Si justo estás empezando con el ``CMF``, es mejor escoger un repositorio de contenido basado en un motor de almacenamiento con el cual ya estés familiarizado. Por ejemplo, **Jackalope con DBAL de Doctrine** trabajará con tu *RDBMS* existente y no requiere instalar *Java* o la extensión ``midgard2`` de *PHP*. Una vez que tienes una aplicación trabajando debe ser fácil cambiar a otra opción. - - -Descarga los paquetes -~~~~~~~~~~~~~~~~~~~~~ - -Añade lo siguiente a tu archivo :file:`composer.json`, dependiendo del repositorio de contenido que hayas escogido. - -| **Jackalope con Jackrabbit** - -.. code-block:: javascript - - "minimum-stability": "dev", - "require": { - ... - "jackalope/jackalope-jackrabbit": "1.0.*", - "doctrine/phpcr-bundle": "1.0.*", - "doctrine/phpcr-odm": "1.0.*" - } - -| **Jackalope con Doctrine DBAL** - -.. code-block:: javascript - - "minimum-stability": "dev", - "require": { - ... - "jackalope/jackalope-doctrine-dbal": "dev-master", - "doctrine/phpcr-bundle": "1.0.*", - "doctrine/phpcr-odm": "1.0.*" - } - -**Midgard** - -.. code-block:: javascript - - "minimum-stability": "dev", - "require": { - ... - "midgard/phpcr": "dev-master", - "doctrine/phpcr-bundle": "1.0.*", - "doctrine/phpcr-odm": "1.0.*" - } - -.. note:: - - Para todo lo anterior, si también estás utilizando el *ORM* de *Doctrine*, asegúrate de usar ``"doctrine/orm": "2.3.*"``, o de lo contrario ``composer`` no podrá resolver las dependencias cuando *Doctrine* dependa del *PHPCR-ODM* más nuevo de *Doctrine 2.3*. (la edición estándar de *Symfony 2.1* utiliza «2.2.*».) - -Para instalar las dependencias anteriores, ejecuta: - -.. code-block:: bash - - php composer.phar update - -Registrando anotaciones -~~~~~~~~~~~~~~~~~~~~~~~ - -*PHPCR-ODM* usa anotaciones y es necesario registrarlas en tu archivo :file:`app/autoload.php`. Añade la siguiente línea, inmediatamente después de la última línea ``AnnotationRegistry::registerFile``: - -.. code-block:: php - - // app/autoload.php - - // ... - AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php'); - // ... - -Iniciando los paquetes -~~~~~~~~~~~~~~~~~~~~~~ - -Luego, inicia los paquetes en el archivo :file:`app/AppKernel.php` añadiéndolos al método ``registerBundle``: - -.. code-block:: php - - // app/AppKernel.php - - public function registerBundles() - { - $bundles = array( - // ... - - // Doctrine PHPCR - new Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(), - - ); - // ... - } - - -Configurando ------------- - -El siguiente paso es configurar los paquetes. - -Sesión *PHPCR* -~~~~~~~~~~~~~~ - -La configuración básica para cada repositorio de contenido se muestra abajo; añade las líneas apropiadas a tu archivo :file:`app/config/config.yml`. Puedes encontrar más información sobre la configuración de este paquete en el capítulo del :doc:`../bundles/phpcr-odm` de la referencia. - -Los parámetros del espacio de trabajo, nombre de usuario y contraseña son para el repositorio *PHPCR* y no los deberías confundir con las posibles credenciales de la base de datos. Estos provienen de la configuración de tu repositorio de contenido. Si quieres utilizar un diferente espacio de trabajo que ``«default»`` primero lo tienes que crear en tu repositorio. - -Si también quieres utilizar el *PHPCR-ODM*, además, ve la siguiente sección. - -**Jackalope con Jackrabbit** - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - type: jackrabbit - url: http://localhost:8080/server/ - workspace: default - username: admin - password: admin - # configuración del odm ve abajo - -**Jackalope con DBAL de Doctrine** - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - type: doctrinedbal - connection: doctrine.dbal.default_connection - workspace: default - username: admin - password: admin - # configuración del odm ve abajo - -.. note:: - - Además, asegúrate de configurar la sección principal de ``doctrine:`` al *RDBMS* que escogiste. - Si quieres utilizar una diferente conexión en lugar de la predefinida, configúrala en la sección *dbal* y especifícala en el parámetro de conexión. Un ejemplo configuración típica es este: - - doctrine: - dbal: - driver: %database_driver% - host: %database_host% - port: %database_port% - dbname: %database_name% - user: %database_user% - password: %database_password% - charset: UTF8 - - Ve `Bases de datos y Doctrine `_ para más información. - -**Midgard** - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - type: midgard2 - db_type: MySQL - db_name: midgard2_test - db_host: "0.0.0.0" - db_port: 3306 - db_username: "" - db_password: "" - db_init: true - blobdir: /tmp/cmf-blobs - workspace: default - username: admin - password: admin - # configuración del odm ve abajo - - -*Doctrine* con *PHPCR-ODM* -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cualquiera de las configuraciones anteriores te darán una sesión *PHPCR* válida. Si quieres utilizar el gestor Objeto-Documento, necesitas configurarlo también. Lo más sencillo es poner ``auto_mapping: true`` para hacer que el paquete *PHPCR* reconoce documentos en el directorio ``/Document`` y buscar asociaciones en ``/Resources/config/doctrine/.phpcr.xml`` resp. ``...yml``. De lo contrario necesitas configurar manualmente la sección de asignaciones. See the -:ref:`configuration reference of the PHPCR-ODM bundle ` for details. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - ... - odm: - auto_mapping: true - - -Configurando el repositorio de contenido ----------------------------------------- - -.. _tutorials-installing-phpcr-jackrabbit: - -**Jackalope Jackrabbit** - -.. index:: Jackrabbit - -Estos son los pasos necesarios para instalar *Apache Jackrabbit*: - -- Asegúrate de que tienes instalada en tu caja la máquina virtual de *Java*. Si no, puedes conseguir una aquí: http://www.java.com/en/download/manual.jsp -- Descarga la versión más reciente de `Jackrabbit de la página de descarga `_ -- Ejecuta el servidor. Ve al directorio donde descargaste el archivo ``.jar`` y lánzalo: - -.. code-block:: bash - - java -jar jackrabbit-standalone-*.jar - -Ve a http://localhost:8080/, ahora *Apache* te debería mostrar una página ``Jackrabbit``. - -Puedes encontrar más información sobre cómo `ejecutar un servidor Jackrabbit `_ en el *wiki* de ``Jackalope``. - -.. _tutorials-installing-phpcr-doctrinedbal: - -**Jackalope DBAL de Doctrine** - -.. index:: Doctrine, DBAL, RDBMS - -Ejecuta las siguientes órdenes para crear la base de datos e instalar un esquema predefinido: - -.. code-block:: bash - - app/console doctrine:database:create - app/console doctrine:phpcr:init:dbal - -For more information on how to configure Doctrine DBAL with Symfony2, see the -`Doctrine chapter in the Symfony2 documentation `_ -and the explanations in the :ref:`reference of the PHPCR-ODM bundle `. - -.. _tutorials-installing-phpcr-midgard: - -**Midgard** - -.. index:: Midgard, RDBMS - -*Midgard* es una extensión *C* que implementa la *API* de *PHPCR* en lo alto de un *RDBMS* estándar. - -Ve la `documentación oficial de Midgard PHPCR `_. - -Registrando tipos de nodo del sistema -------------------------------------- - -*PHPCR-ODM* utiliza un `tipo de nodo personalizado `_ para dar seguimiento a la metainformación sin interferir con tu contenido. There is a command that makes it -trivial to register this type and the PHPCR namespace, as well as all base paths of bundles: - -.. code-block:: bash - - php app/console doctrine:phpcr:repository:init - -Usando la restricción de validación ``ValidPhpcrOdm`` ------------------------------------------------------ - -El paquete proporciona la restricción de validación ``ValidPhpcrOdm`` que puedes utilizar para comprobar si los campos ``Id`` o ``Nodename`` y ``Parent`` de tu documento son correctos: - -.. code-block:: php - - `_ y `create.js `_ para proporcionar edición en línea basada en la salida de `RDFa `_. - -Para más información ---por ahora--- ve la documentación del `CreateBundle `_ - -.. index:: VIE.js, CreateBundle, FOSRestBundle, JMSSerializerBundle, RDFa, create.js, hallo.js - -Instalando ----------- - -Descarga los paquetes -~~~~~~~~~~~~~~~~~~~~~ - -Añade lo siguiente a tu archivo :file:`composer.json`: - -.. code-block:: javascript - - "require": { - ... - "symfony-cmf/create-bundle": "1.0.*" - }, - "scripts": { - "post-install-cmd": [ - "Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::initSubmodules", - ... - ], - "post-update-cmd": [ - "Symfony\\Cmf\\Bundle\\CreateBundle\\Composer\\ScriptHandler::initSubmodules", - ... - ] - }, - -Y luego ejecuta: - -.. code-block:: bash - - php composer.phar update symfony-cmf/create-bundle - -Iniciando los paquetes -~~~~~~~~~~~~~~~~~~~~~~ - -Luego, inicia los paquetes en el archivo :file:`app/AppKernel.php` añadiéndolos al método ``registerBundles``: - -.. code-block:: php - - public function registerBundles() - { - $bundles = array( - // ... - - new Symfony\Cmf\Bundle\CreateBundle\SymfonyCmfCreateBundle(), - new FOS\RestBundle\FOSRestBundle(), - new JMS\SerializerBundle\JMSSerializerBundle($this), - ); - // ... - } - -Configurando ------------- - -El siguiente paso es configurar los paquetes. - -Configuración básica, añade lo siguiente a la configuración de tu aplicación: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_create: - phpcr_odm: true - map: - '': 'Symfony\Cmf\Bundle\MultilangContentBundle\Document\MultilangStaticContent' - image: - model_class: Symfony\Cmf\Bundle\CreateBundle\Document\Image - controller_class: Symfony\Cmf\Bundle\CreateBundle\Controller\PHPCRImageController - -Si tienes tus propios documentos, agregalos a la asignación y coloca la asignación ``RDFa`` en ``Resources/rdf-mappings`` ya sea dentro del directorio ``app`` o dentro de algún paquete. -El nombre de archivo es el nombre de clase completamente cualificado que incluye el espacio de nombres sustituyendo la barra inversa ``\`` con un punto ``(.)``. - - -Referencia ----------- - -Ve el :doc:`../bundles/create` diff --git a/_sources/cmf/tutorials/installing-configuring-simple-cms.txt b/_sources/cmf/tutorials/installing-configuring-simple-cms.txt deleted file mode 100644 index 59dc273..0000000 --- a/_sources/cmf/tutorials/installing-configuring-simple-cms.txt +++ /dev/null @@ -1,82 +0,0 @@ -Instalando y configurando el ``SimpleCmsBundle`` -================================================ - -El objetivo de esta guía es instalar y configurar un sencillo *CMS* con la ayuda del ``SimpleCmsBundle``. - -.. note:: - - Para que trabaje el ``CMF`` se requiere de *Symfony 2.1* (actualmente maestro). - -El ``SimpleCmsBundle`` proporciona una solución para fácilmente asignar contenido, rutas y elementos del menú basando el repositorio de contenido en una única estructura de árbol. - -Para más información ---por ahora--- ve la documentación del `SymfonyCmfSimpleCmsBundle `_ - -Para una sencilla instalación de ejemplo del paquete revisa la `Edición estándar del CMF de Symfony `_ - -El `sitio web de CMF `_ es -otra aplicación que utiliza el ``SimpleCmsBundle``. - - -.. index:: SimpleCmsBundle - -Requisitos previos ------------------- - -- :doc:`installing-cmf-core` - -Instalando ----------- - -Descarga los paquetes -~~~~~~~~~~~~~~~~~~~~~ - -Añade lo siguiente a tu archivo :file:`composer.json`: - -.. code-block:: javascript - - "require": { - ... - "symfony-cmf/simple-cms-bundle": "1.0.*" - } - -Y luego ejecuta: - -.. code-block:: bash - - php composer.phar update - - -Iniciando los paquetes -~~~~~~~~~~~~~~~~~~~~~~ - -Luego, inicia el paquete en el archivo :file:`app/AppKernel.php` añadiéndolo al método ``registerBundles``. - -.. code-block:: php - - public function registerBundles() - { - $bundles = array( - // ... - - new Symfony\Cmf\Bundle\SimpleCmsBundle\SymfonyCmfSimpleCmsBundle(), - - ); - // ... - } - -Configurando ------------- - -El siguiente paso es configurar los paquetes. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_simple_cms: - routing: - templates_by_class: - Symfony\Cmf\Bundle\SimpleCmsBundle\Document\Page: SymfonyCmfSimpleCmsBundle:Page:index.html.twig - -Ve la referencia completa de :doc:`../bundles/simple-cms`. diff --git a/_sources/cmf/tutorials/using-blockbundle-and-contentbundle.txt b/_sources/cmf/tutorials/using-blockbundle-and-contentbundle.txt deleted file mode 100644 index 77f8523..0000000 --- a/_sources/cmf/tutorials/using-blockbundle-and-contentbundle.txt +++ /dev/null @@ -1,661 +0,0 @@ -Usando ``BlockBundle`` y ``ContentBundle`` con *PHPCR* -====================================================== - -El objetivo de esta guía es demostrar cómo puedes utilizar el ``CMF`` :doc:`../bundles/block` y :doc:`../bundles/content` como componentes independientes, y mostrarte cómo complementan al *PHPCR*. - -Esta guía demuestra el uso más sencillo posible, para instalarlo y ejecutarlo -rápidamente. Una vez que estés familiarizado con lo básico, la documentación en profundidad de ambos paquetes te -ayudará a adaptar estos ejemplos básicos para servir casos de uso más avanzados. - -Empezarás utilizando únicamente el ``BlockBundle``, con bloques de contenido enlazados directamente al *PHPCR*. -Luego, verás una introducción al ``ContentBundle`` para mostrarte cómo puedes representar contenido en las páginas con bloques. - -.. note:: - - A pesar de que no es un requisito utilizar ``BlockBundle`` o ``ContentBundle``, esta guía también usa `DoctrineFixturesBundle `_. - Esto es porque este proporciona una manera fácil de cargar algún contenido de prueba. - - -Requisitos previos ------------------- - -- `Instalando Symfony2 `_ (2.1.x) -- :doc:`installing-configuring-doctrine-phpcr-odm` - -.. note:: - - Esta guía está basada en la utilización de *PHPCR-ODM* instalado con ``Jackalope``, *DBAL* de *Doctrine* y una base de datos *MySQL*. Debería ser fácil adaptarlo para trabajar con una de las otras opciones *PHPCR* documentadas en :doc:`installing-configuring-doctrine-phpcr-odm`. - - -Creando y configurando la base de datos ---------------------------------------- - -Puedes utilizar una base de datos existente, o crear una ahora para ayudarte a seguir esta guía. Para una nueva base de datos, ejecuta estas órdenes en *MySQL*: - -.. code-block:: sql - - CREATE DATABASE symfony DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_general_ci; - CREATE USER 'symfony'@'localhost' IDENTIFIED BY 'UseABetterPassword'; - GRANT ALL ON symfony.* TO 'symfony'@'localhost'; - -Tu archivo :file:`parameters.yml` necesita emparejar lo anterior, por ejemplo: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - database_driver: pdo_mysql - database_host: localhost - database_port: ~ - database_name: symfony - database_user: symfony - database_password: UseABetterPassword - - -Configurando el componente *PHPCR* de *Doctrine* ------------------------------------------------- - -.. note:: - - Si seguiste la guía :doc:`installing-configuring-doctrine-phpcr-odm`, puedes saltarte esta sección. - -Necesitas instalar los componentes del *PHPCR-ODM*. Añade los siguiente a tu archivo :file:`composer.json`: - -.. code-block:: javascript - - "require": { - ... - "jackalope/jackalope-jackrabbit": "1.0.*", - "jackalope/jackalope-doctrine-dbal": "dev-master", - "doctrine/phpcr-bundle": "1.0.*", - "doctrine/phpcr-odm": "1.0.*" - } - -Para instalar lo anterior, ejecuta: - -.. code-block:: bash - - php composer.phar update - -En tu archivo :file:`config.yml`, añade la siguiente configuración para ``doctrine_phpcr``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine_phpcr: - session: - backend: - type: doctrinedbal - connection: doctrine.dbal.default_connection - workspace: default - odm: - auto_mapping: true - -Añade la siguiente línea al método ``registerBundles()`` en el archivo :file:`AppKernel.php`: - -.. code-block:: php - - // app/AppKernel.php - - public function registerBundles() - { - $bundles = array( - // ... - new Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(), - ); - - // ... - } - -Añade la siguiente línea a tu archivo :file:`autoload.php`, inmediatamente después de la -última línea ``AnnotationRegistry::registerFile``: - -.. code-block:: php - - // app/autoload.php - - // ... - AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php'); - // ... - -Crea el esquema de la base de datos y registra el tipo de nodo *PHPCR* usando las siguientes órdenes de consola: - -.. code-block:: bash - - php app/console doctrine:phpcr:init:dbal - php app/console doctrine:phpcr:repository:init - -Ahora deberías tener una serie de tablas en tu base de datos *MySQL* con el prefijo ``phpcr_``. - - -Instala los componentes necesarios del ``CMF`` de *Symfony* ------------------------------------------------------------ - -Añade lo siguiente al archivo :file:`composer.json`: - -.. code-block:: javascript - - "require": { - ... - "symfony-cmf/block-bundle": "dev-master" - } - -Para instalar las dependencias anteriores, ejecuta: - -.. code-block:: bash - - php composer.phar update - -Añade las siguientes líneas al archivo :file:`AppKernel.php`: - -.. code-block:: php - - // app/AppKernel.php - - public function registerBundles() - { - $bundles = array( - // ... - new Sonata\BlockBundle\SonataBlockBundle(), - new Symfony\Cmf\Bundle\BlockBundle\SymfonyCmfBlockBundle(), - ); - - // ... - } - -``SonataBlockBundle`` es una dependencia del ``BlockBundle`` de ``CMF`` y necesitas configurarlas. Añade lo siguiente a tu archivo :file:`config.yml`: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - sonata_block: - default_contexts: [cms] - - -Instala ``DoctrineFixturesBundle`` ----------------------------------- - -.. note:: - - Como mencioné al inicio, este no es un requisito para ``BlockBundle`` o ``ContentBundle``; No obstante, es una buena manera de manejar contenido predefinido o de ejemplo. - -Añade lo siguiente al archivo :file:`composer.json`: - -.. code-block:: javascript - - "require": { - ... - "doctrine/doctrine-fixtures-bundle": "dev-master" - } - -Para instalar las dependencias anteriores, ejecuta: - -.. code-block:: bash - - php composer.phar update - -Añade la siguiente línea al método ``registerBundles()`` en el archivo :file:`AppKernel.php`: - -.. code-block:: php - - // app/AppKernel.php - - public function registerBundles() - { - $bundles = array( - // ... - new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), - ); - - // ... - } - - -Cargando accesorios -------------------- - -Basándote en la `documentación de DoctrineFixturesBundle `_, necesitarás crear una clase ``fixtures``. - -Para empezar, crea un directorio ``DataFixtures`` dentro de tu propio paquete (p. ej. ``«MainBundle»``), y dentro, crea un directorio llamado ``PHPCR``. Si quieres seguir los ejemplos de más adelante, el ``DoctrineFixturesBundle`` cargará automáticamente las clases ``fixtures`` colocadas allí. - -En el cargador de ``accesorios``, un ejemplo de la creación de un bloque de contenido podría tener esta apariencia: - -.. code-block:: php - - $myBlock = new SimpleBlock(); - $myBlock->setParentDocument($parentPage); - $myBlock->setName('sidebarBlock'); - $myBlock->setTitle('My first block'); - $myBlock->setContent('Hello block world!'); - - $documentManager->persist($myBlock); - -Aún así, lo anterior en sí mismo no será suficiente, porque no hay ninguna página padre (``$parentPage``) con la cual enlazar los bloques. Hay varias posibles opciones que puedes utilizar como el padre: - -- Enlaza los bloques directamente al documento raíz (no mostrado) -- Crea un documento del paquete *PHPCR* (mostrado abajo usando el tipo de documento ``Genérico``) -- Crea un documento del ``ContentBundle`` del ``CMF`` (mostrado abajo utilizando el tipo de documento ``StaticContent``) - - -Usando el *PHPCR* ------------------ - -Para almacenar un bloque directamente en el *PHPCR* del ``CMF``, crea la siguiente clase dentro de tu directorio ``DataFixtures/PHPCR``: - -.. code-block:: html+php - - find(null, '/'); - - // Crea un documento PHPCR genérico bajo la raíz, para usarlo - // como el tipo de categoría para los bloques - $document = new Generic(); - $document->setParent($rootDocument); - $document->setNodename('blocks'); - $manager->persist($document); - - // Crea un nuevo SimpleBlock (ve - // http://gitnacho.github.com/symfony-doc-es/cmf/bundles/block.html#block-types) - $myBlock = new SimpleBlock(); - $myBlock->setParentDocument($document); - $myBlock->setName('testBlock'); - $myBlock->setTitle('CMF BlockBundle only'); - $myBlock->setContent('Block from CMF BlockBundle, parent from the PHPCR (Generic document).'); - $manager->persist($myBlock); - - // Confirma los cambios de $document y $block a la base de datos - $manager->flush(); - } - - public function setContainer(ContainerInterface $container = null) - { - $this->container = $container; - } - } - -Esta clase carga un bloque de contenido de ejemplo que utiliza el ``BlockBundle`` de ``CMF`` (sin necesitar ningún otro paquete del ``CMF``). Para asegurar el bloque tienes un padre en el repositorio, el cargador también crea un documento ``Genérico`` llamado ``'blocks'`` dentro del *PHPCR*. - -Ahora, carga los accesorios usando la consola: - -.. code-block:: bash - - php app/console doctrine:phpcr:fixtures:load - -El contenido en tu base de datos ahora se tendría que ver algo así: - - -.. code-block:: sql - - SELECT path, parent, local_name FROM phpcr_nodes; - -+-------------------+---------+------------+ -| path | parent | local_name | -+===================+=========+============+ -| / | | | -+-------------------+---------+------------+ -| /blocks | / | blocks | -+-------------------+---------+------------+ -| /blocks/testBlock |/ blocks | testBlock | -+-------------------+---------+------------+ - - -Usando el ``ContentBundle`` del ``CMF`` ---------------------------------------- - -Sigue este ejemplo para utilizar ambos componentes ``Block`` y ``Content``. - -The ContentBundle is best used together with the RoutingExtraBundle. Add the -following to ``composer.json``: - -.. code-block:: javascript - - "require": { - ... - "symfony-cmf/content-bundle": "dev-master", - "symfony-cmf/routing-extra-bundle": "dev-master" - } - -Instala todo de la manera habitual: - -.. code-block:: bash - - php composer.phar update - -Añade la siguiente línea al archivo :file:`AppKernel.php`: - -.. code-block:: php - - // app/AppKernel.php - - public function registerBundles() - { - $bundles = array( - // ... - new Symfony\Cmf\Bundle\ContentBundle\SymfonyCmfContentBundle(), - ); - - // ... - } - -Ahora deberías tener todo lo necesario para cargar una página de contenido con un bloque de ejemplo, así que crea la clase ``LoadBlockWithCmfParent.php``: - -.. code-block:: html+php - - getPhpcrSession(); - $basepath = $this->container->getParameter('symfony_cmf_content.static_basepath'); - - // Crea la ruta en el repositorio - NodeHelper::createPath($session, $basepath); - - // Crea un nuevo documento que utiliza StaticContent de ContentBundle del CMF - $document = new StaticContent(); - $document->setPath($basepath . '/blocks'); - $manager->persist($document); - - // Crea un nuevo SimpleBlock (ve - // http://gitnacho.github.com/symfony-doc-es/cmf/bundles/block.html#block-types) - $myBlock = new SimpleBlock(); - $myBlock->setParentDocument($document); - $myBlock->setName('testBlock'); - $myBlock->setTitle('CMF BlockBundle and ContentBundle'); - $myBlock->setContent('Block from CMF BlockBundle, parent from CMF ContentBundle (StaticContent).'); - $manager->persist($myBlock); - - // Confirma los cambios de $document y $block a la base de datos - $manager->flush(); - } - - public function setContainer(ContainerInterface $container = null) - { - $this->container = $container; - } - } - -Esta clase crea una página de contenido de ejemplo que utiliza el ``ContentBundle`` del ``CMF``. Luego, carga el bloque de ejemplo como antes, usando la nueva página de contenido como su padre. - -De manera predeterminada, la ruta base para el contenido es :file:`/cms/content/static`. Para mostrar cómo lo puedes configurar a cualquier ruta, añade la siguiente entrada opcional a tu archivo :file:`config.yml`: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_content: - static_basepath: /content - -Ahora deberías poder cargar los accesorios anteriores: - -.. code-block:: bash - - php app/console doctrine:phpcr:fixtures:load - -Todo va sobre ruedas, el contenido en tu base de datos se tendría que ver algo así (si también seguiste el ejemplo ``LoadBlockWithPhpcrParent``, todavía deberías tener dos entradas ``/blocks`` también): - -.. code-block:: sql - - SELECT path, parent, local_name FROM phpcr_nodes; - -+---------------------------+-----------------+------------+ -| path | parent | local_name | -+===========================+=================+============+ -| / | | | -+---------------------------+-----------------+------------+ -| /content | / | content | -+---------------------------+-----------------+------------+ -| /content/blocks | /content | blocks | -+---------------------------+-----------------+------------+ -| /content/blocks/testBlock | /content/blocks | testBlock | -+---------------------------+-----------------+------------+ - - -Dibujando los bloques ---------------------- - -Esto lo maneja el ``BlockBundle`` de ``Sonata``. ``sonata_block_render`` ya está registrado como extensión de *Twig* al incluir ``SonataBlockBundle`` en el archivo :file:`AppKernel.php`. Por lo tanto, puedes dibujar cualquier bloque dentro de cualquier plantilla simplemente refiriéndote a su ruta. - -El siguiente código muestra el resultado de ambas instancias de los bloques ``testBlock`` de los ejemplos anteriores. -Si sólo seguiste uno de los ejemplos, asegúrate de incluir únicamente ese bloque: - -.. code-block:: text - - {# src/Acme/Bundle/MainBundle/resources/views/Default/index.html.twig #} - - {# Incluye esto si seguiste el ejemplo BlockBundle con PHPCR #} - {{ sonata_block_render({ - 'name': '/blocks/testBlock' - }) }} - - {# incluye esto si seguiste el ejemplo BlockBundle con ContentBundle #} - {{ sonata_block_render({ - 'name': '/content/blocks/testBlock' - }) }} - -Ahora tu página ``index`` debería mostrar lo siguiente (suponiendo que seguiste ambos ejemplos): - -.. code-block:: text - - CMF BlockBundle only - Block from CMF BlockBundle, parent from the PHPCR (Generic document). - - CMF BlockBundle and ContentBundle - Block from CMF BlockBundle, parent from CMF ContentBundle (StaticContent). - -Esto sucede al dibujar un bloque, ve el... index:: ``BlockBundle`` para más detalles: - -- un documento se carga basándose en el nombre -- si está configurada la caché, se comprueba la caché y si se encuentra devuelve el contenido -- cada bloque de documento también tiene un servicio de bloque, llama al método ``execute``: - - - puedes poner aquí la lógica que quieras tal como lo harías en un controlador - - esta llama a una plantilla - - el resultado es un objeto ``Respuesta`` - -.. note:: - - Un bloque también se puede configurar usando opciones, estas te permiten crear bloques más avanzados y reutilizables. Las opciones predefinidas se configuran en el bloque del servicio y se pueden alterar en el ayudante de *Twig* y el bloque del documento. - Un ejemplo es un bloque lector de *RSS*, la *URL* y el título se almacenan en las opciones del bloque del documento, la cantidad -máxima de elementos a mostrar se especifica al llamar a ``sonata_block_render``. - -.. _tutorial-block-embed: - -Embedding blocks in WYSIWYG content ------------------------------------ - -The SymfonyCmfBlockBundle provides a twig filter ``cmf_embed_blocks`` that -looks through the content and looks for special tags to render blocks. To use -the tag, you need to apply the ``cmf_embed_blocks`` filter to your output. -If you can, render your blocks directly in the template. This feature is only a -cheap solution for web editors to place blocks anywhere in their HTML content. -A better solution to build composed pages is to build it from blocks. (There -might be a CMF bundle at some point for this.) - -.. code-block:: jinja - - {# template.twig.html #} - {{ page.content|cmf_embed_blocks }} - -When you apply the filter, your users can use this tag to embed a block in -their HTML content: - -.. code-block:: html - - %block:"/absolute/path/to/block"% - -You can change the prefix and postfix (the parts ``%block:`` and -``%`` to have a different tag for your users. Say you want to write -``%%%block:"/absolute/path"%%%`` then you do: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - symfony_cmf_block: - twig: - cmf_embed_blocks: - prefix: %%%block: - postfix: %%% - - -.. warning:: - - Currently there is no limitation built into this feature. Only enable it on - content for which you are sure only trusted users may edit it. Restrictions - about what block can be where that are built into an admin interface are - not respected here. - - -Siguientes pasos ----------------- - -Ahora deberías estar listo para usar en tu aplicación el ``BlockBundle`` y/o el ``ContentBundle``, o para explorar los otros paquetes disponibles en el ``CMF``. - -- Ve la documentación de los paquetes :doc:`../bundles/block` y :doc:`../bundles/content` para aprender usos más avanzados de estos paquetes - para ver una manera mejor de cargar accesorios, mira los `accesorios en el recinto de seguridad del CMF `_ -- Dale un vistazo a la `guía PHPCR `_ para entender mejor el repositorio de contenido subyacente - - -Solucionando problemas ----------------------- - -Si tienes problemas, posiblemente sea más fácil empezar con una instalación de *Symfony2* fresca. También puedes probar ejecutando y modificando el código en el ejemplo externo del `recinto de seguridad del bloque del CMF `_. - -**Configuración de Doctrine** - -Si empezaste con la distribución estándar de *Symfony2* (versión 2.1.x), ya deberías tener correctamente configurado tu archivo :file:`config.yml`. Si no, inténtalo usando la siguiente sección: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - driver: "%database_driver%" - host: "%database_host%" - port: "%database_port%" - dbname: "%database_name%" - user: "%database_user%" - password: "%database_password%" - charset: UTF8 - orm: - auto_generate_proxy_classes: "%kernel.debug%" - auto_mapping: true - -**"Ninguna orden definida" al cargar accesorios** - -.. code-block:: text - - [InvalidArgumentException] - No hay ninguna orden definida en el espacio de nombres ``doctrine:phpcr:fixtures``. - -Asegúrate de que tu archivo :file:`AppKernel.php` contiene las siguientes líneas: - -.. code-block:: php - - new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(), - new Doctrine\Bundle\PHPCRBundle\DoctrinePHPCRBundle(), - -**"No configuraste una sesión"** - -.. code-block:: text - - [InvalidArgumentException] - No configuraste una sesión para los gestores de documento - -Asegúrate de que tienes lo siguiente en tu archivo :file:`app/config.yml`: - -.. configuration-block:: - - .. code-block:: yaml - - doctrine_phpcr: - session: - backend: - type: doctrinedbal - connection: doctrine.dbal.default_connection - workspace: default - odm: - auto_mapping: true - -**"La anotación no existe"** - -.. code-block:: text - - [Doctrine\Common\Annotations\AnnotationException] - [Error semántico] La anotación «@Doctrine\ODM\PHPCR\Mapping\Annotations\Document» en la clase «Doctrine\ODM\PHPCR\Document\Generic» no existe, o no se puede cargar automáticamente. - -Asegúrate de añadir esta línea a tu archivo :file:`app/autoload.php` (inmediatamente después de la línea ``AnnotationRegistry::registerLoader``): - -.. code-block:: php - - AnnotationRegistry::registerFile(__DIR__.'/../vendor/doctrine/phpcr-odm/lib/Doctrine/ODM/PHPCR/Mapping/Annotations/DoctrineAnnotations.php'); - -**no se encuentra la clase SimpleBlock** - -.. code-block:: text - - [Doctrine\Common\Persistence\Mapping\MappingException] - La clase 'Symfony\Cmf\Bundle\BlockBundle\Document\SimpleBlock' no se encuentra en la cadena de espacios de nombres configurada Doctrine\ODM\PHPCR\Document, Sonata\UserBundle\Document, FOS\UserBundle\Document - -Asegúrate de que está instalado el ``BlockBundle`` del ``CMF`` y de que se carga en el archivo :file:`app/AppKernel.php`: - -.. code-block:: php - - new Symfony\Cmf\Bundle\BlockBundle\SymfonyCmfBlockBundle(), - -**No se encuentra la RouteAwareInterface** - -.. code-block:: text - - Error fatal: No se encuentra la interfaz 'Symfony\Cmf\Component\Routing\RouteAwareInterface' en /var/www/your-site/vendor/symfony-cmf/content-bundle/Symfony/Cmf/Bundle/ContentBundle/Document/StaticContent.php en la línea 15 - -Si estás utilizando el ``ContentBundle``, asegúrate de instalar el ``RoutingExtraBundle``, también: - -.. code-block:: javascript - - // composer.json - "symfony-cmf/routing-extra-bundle": "dev-master" - -...e instala: - -.. code-block:: bash - - php composer.phar update diff --git a/_sources/components/class_loader.txt b/_sources/components/class_loader.txt deleted file mode 100644 index 8746f80..0000000 --- a/_sources/components/class_loader.txt +++ /dev/null @@ -1,106 +0,0 @@ -.. index:: - pair: Autocargador; Configuración - single: Componentes; ClassLoader - -El componente ``ClassLoader`` -============================= - - El componente ``ClassLoader`` carga automáticamente las clases de tu proyecto si estas siguen algunas convenciones *PHP* estándar. - -Siempre que uses una clase no definida, *PHP* utiliza el mecanismo de carga automática para delegar la carga de un archivo de definición de clase. *Symfony2* proporciona un cargador automático «universal», que es capaz de cargar clases desde los archivos que implementan uno de los siguientes convenios: - -* Los `estándares`_ de interoperabilidad técnica para los espacios de nombres y nombres de clases de *PHP 5.3*; - -* La convención de nomenclatura de clases de `PEAR`_. - -Si tus clases y las bibliotecas de terceros que utilizas en tu proyecto siguen estas normas, el cargador automático de *Symfony2* es el único cargador automático que necesitarás siempre. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/ClassLoader); -* :doc:`Instalándolo vía Composer ` (``symfony/class-loader`` en `Packagist`_). - -Usando ------- - -.. versionadded:: 2.1 - El método ``useIncludePath`` se agregó en *Symfony* 2.1. - -Registrar la clase del cargador automático :class:`Symfony\\Component\\ClassLoader\\UniversalClassLoader` es sencillo:: - - require_once '/ruta/a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; - - use Symfony\Component\ClassLoader\UniversalClassLoader; - - $loader = new UniversalClassLoader(); - - // como último recurso busca en include_path. - $loader->useIncludePath(true); - - // ... aquí registra el espacio de nombres y prefijos - // - ve más abajo - - $loader->register(); - -Para una menor ganancia en rendimiento puedes memorizar en caché las rutas de las clases usando *APC*, con sólo registrar la clase :class:`Symfony\\Component\\ClassLoader\\ApcUniversalClassLoader`:: - - require_once '/ruta/a/src/Symfony/Component/ClassLoader/UniversalClassLoader.php'; - require_once '/ruta/a/src/Symfony/Component/ClassLoader/ApcUniversalClassLoader.php'; - - use Symfony\Component\ClassLoader\ApcUniversalClassLoader; - - $loader = new ApcUniversalClassLoader('apc.prefix.'); - $loader->register(); - -El cargador automático es útil sólo si agregas algunas bibliotecas al cargador automático. - -.. note:: - - El autocargador se registra automáticamente en una aplicación *Symfony2* (consulta el ``app/autoload.php``). - -Si las clases a cargar automáticamente utilizan espacios de nombres, utiliza cualquiera de los métodos :method:`Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerNamespace` o :method:`Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerNamespaces`:: - - $loader->registerNamespace('Symfony', __DIR__.'/vendor/symfony/symfony/src'); - - $loader->registerNamespaces(array( - 'Symfony' => __DIR__.'/../vendor/symfony/symfony/src', - 'Monolog' => __DIR__.'/../vendor/monolog/monolog/src', - )); - - $loader->register(); - -Para las clases que siguen la convención de nomenclatura de PEAR, utiliza cualquiera de los métodos :method:`Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerPrefix` o :method:`Symfony\\Component\\ClassLoader\\UniversalClassLoader::registerPrefixes`:: - - $loader->registerPrefix('Twig_', __DIR__.'/vendor/twig/twig/lib'); - - $loader->registerPrefixes(array( - 'Swift_' => __DIR__.'/vendor/swiftmailer/swiftmailer/lib/classes', - 'Twig_' => __DIR__.'/vendor/twig/twig/lib', - )); - - $loader->register(); - -.. note:: - - Algunas bibliotecas también requieren que su ruta de acceso raíz esté registrada en la ruta de include *PHP* (``set_include_path()``). - -Las clases de un subespacio de nombres o una subjerarquía de clases *PEAR* se pueden buscar en una lista de ubicaciones para facilitar la utilización de un subconjunto de clases de terceros en proyectos grandes:: - - $loader->registerNamespaces(array( - 'Doctrine\\Common' => __DIR__.'/vendor/doctrine/common/lib', - 'Doctrine\\DBAL\\Migrations' => __DIR__.'/vendor/doctrine/migrations/lib', - 'Doctrine\\DBAL' => __DIR__.'/vendor/doctrine/dbal/lib', - 'Doctrine' => __DIR__.'/vendor/doctrine/orm/lib', - )); - - $loader->register(); - -En este ejemplo, si intentas utilizar una clase en el espacio de nombres ``Doctrine\Common`` o una de sus hijas, el cargador automático buscará primero la clase en el directorio ``doctrine\common``, y entonces, si no lo encuentra, vuelve al directorio de reserva predeterminado, el último ``Doctrine`` configurado antes de darse por vencido. -El orden de registro es importante en este caso. - -.. _`estándares`: http://symfony.com/PSR0 -.. _`PEAR`: http://pear.php.net/manual/en/standards.php -.. _`Packagist`: https://packagist.org/packages/symfony/class-loader diff --git a/_sources/components/config/caching.txt b/_sources/components/config/caching.txt deleted file mode 100644 index ba4b027..0000000 --- a/_sources/components/config/caching.txt +++ /dev/null @@ -1,44 +0,0 @@ -.. index:: - single: Config; Almacenamiento en caché basado en recursos - -Almacenamiento en caché basado en recursos -========================================== - -Al cargar todos los recursos de configuración, posiblemente desees procesar los valores de configuración y combinarlos en un solo archivo. Este archivo actúa como la memorización en caché. Su contenido no se tiene que regenerar cada vez que ejecutas la aplicación - sólo cuando se modifican los recursos de configuración. - -Por ejemplo, el componente de enrutamiento de Symfony permite cargar todas las rutas, y luego volcar un comparador de URL o un generador de URL basándose en estas rutas. En este caso, cuando uno de los recursos se ha modificado (y estás trabajando en un entorno de desarrollo), debes invalidar el archivo generado y regenerarlo. -Esto se puede lograr usando la clase :class:`Symfony\\Component\\Config\\ConfigCache`. - -El siguiente ejemplo muestra cómo recolectar los recursos, luego generar algún código basado en los recursos que se han cargado, y escribir el código en la memoria caché. La memoria caché también recibe la colección de recursos que se utilizaron para generar el código. Al mirar la marca de tiempo de la «última modificación» de esos recursos, puedes saber si la caché aún está fresca o que debes regenerar su contenido:: - - use Symfony\Component\Config\ConfigCache; - use Symfony\Component\Config\Resource\FileResource; - - $cachePath = __DIR__.'/cache/appUserMatcher.php'; - - // el segundo argumento indica cuando o no deseas usar el modo de depuración - $userMatcherCache = new ConfigCache($cachePath, true); - - if (!$userMatcherCache->isFresh()) { - // llénalo con un arreglo de rutas de los archivos 'users.yml' - $yamlUserFiles = ...; - - $resources = array(); - - foreach ($yamlUserFiles as $yamlUserFile) { - // ver el artículo anterior «Cargando recursos» para - // ver de dónde proviene $delegatingLoader - $delegatingLoader->load($yamlUserFile); - $resources[] = new FileResource($yamlUserFile); - } - - // el código del UserMatcher se genera en otros lugares - $code = ...; - - $userMatcherCache->write($code, $resources); - } - - // posiblemente desees requerir el código almacenado en caché: - require $cachePath; - -En modo de depuración, se creará un archivo ``.meta`` en el mismo directorio que el propio archivo de caché. Este archivo ``.meta`` contiene los recursos serializados, cuya marca de tiempo se utiliza para determinar si la caché todavía está fresca. Cuando no estás en modo de depuración, la caché se considera que es «fresca» tan pronto como existe, y por lo tanto no se generará el archivo ``.meta``. diff --git a/_sources/components/config/definition.txt b/_sources/components/config/definition.txt deleted file mode 100644 index 60df7c5..0000000 --- a/_sources/components/config/definition.txt +++ /dev/null @@ -1,505 +0,0 @@ -.. index:: - single: Config; Definiendo y procesando valores de configuración - -Definiendo y procesando valores de configuración -================================================ - -Validando valores de configuración ----------------------------------- - -Después de cargar los valores de configuración de todo tipo de recursos, los valores y su estructura se pueden validar usando la parte «Definición» del componente ``Config``. Generalmente, se espera que los valores de configuración muestren algún tipo de jerarquía. Además, los valores deben ser de un determinado tipo, estar limitados en número o ser uno de un determinado conjunto de valores. Por ejemplo, la siguiente configuración (en *YAML*) muestra una clara jerarquía y algunas reglas de validación que se deben aplicar (como: «el valor de ``auto_connect`` debe ser un valor booleano»): - -.. code-block:: yaml - - auto_connect: true - default_connection: mysql - connections: - mysql: - host: localhost - driver: mysql - username: user - password: pass - sqlite: - host: localhost - driver: sqlite - memory: true - username: user - password: pass - -Al cargar varios archivos de configuración, debería ser posible combinar y sobrescribir algunos valores. Otros valores no se deben fusionar y quedarse como estaban cuando se encontraron por primera vez. Además, algunas claves sólo están disponibles cuando otra clave tiene un valor específico (en la configuración del ejemplo anterior: la clave ``memory`` sólo tiene sentido cuando ``driver`` es ``sqlite``). - -Definiendo una jerarquía de valores configuración usando el ``TreeBuilder`` ---------------------------------------------------------------------------- - -Puedes definir todas las normas relativas a los valores de configuración usando el :class:`Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder`. - -Debes devolver una instancia de :class:`Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder` desde una clase ``Configuration`` personalizada que implemente la :class:`Symfony\\Component\\Config\\Definition\\ConfigurationInterface`:: - - namespace Acme\DatabaseConfiguration; - - use Symfony\Component\Config\Definition\ConfigurationInterface; - use Symfony\Component\Config\Definition\Builder\TreeBuilder; - - class DatabaseConfiguration implements ConfigurationInterface - { - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('database'); - - // ... añade definiciones de nodo a la raíz del árbol - - return $treeBuilder; - } - } - -Añadiendo definiciones de nodo al árbol ---------------------------------------- - -Variables de nodo -~~~~~~~~~~~~~~~~~ - -Un árbol contiene definiciones de nodo que se pueden establecer de manera semántica. -Esto significa que, usando la sangría y una sencilla notación, es posible reflejar la estructura real de los valores de configuración:: - - $rootNode - ->children() - ->booleanNode('auto_connect') - ->defaultTrue() - ->end() - ->scalarNode('default_connection') - ->defaultValue('default') - ->end() - ->end() - ; - -El nodo raíz en sí es un arreglo de nodos, y tiene hijos, como el nodo booleano ``auto_connect`` y el nodo escalar ``default_connection``. En general: -después de definir un nodo, una llamada a ``end()`` te lleva un paso más arriba en la jerarquía. - -Tipo «node» -~~~~~~~~~~~ - -Es posible validar el tipo de un valor proporcionado utilizando la definición -de nodo apropiada. El tipo de nodo está disponible para: - -* ``scalar`` -* ``boolean`` -* ``array`` -* ``enum`` (nuevo en 2.1) -* ``integer`` (nuevo en 2.2) -* ``float`` (nuevo en 2.2) -* variable (sin validación) - -y se crean con ``node($nombre, $tipo)`` o su método abreviado asociado -``xxxxNode($nombre)``. - -Restricciones de nodos numéricos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.2 - Los nodos numéricos (``float`` e ``integer``) son nuevos en 2.2 - -Los nodos numéricos (``float`` e ``integer``) proporcionan dos restricciones extra --- -:method:`Symfony\\Component\\Config\\Definition\\Builder::min` y -:method:`Symfony\\Component\\Config\\Definition\\Builder::max` --- que te permiten validar el valor:: - - $rootNode - ->children() - ->integerNode('positive_value') - ->min(0) - ->end() - ->floatNode('big_value') - ->max(5E45) - ->end() - ->integerNode('value_inside_a_range') - ->min(-50)->max(50) - ->end() - ->end() - ; - -Nodos del arreglo -~~~~~~~~~~~~~~~~~ - -Es posible añadir un nivel más profundo a la jerarquía, añadiendo un nodo al arreglo. El arreglo de nodos en sí mismo, puede tener un conjunto predefinido de variables de nodo:: - - $rootNode - ->children() - ->arrayNode('connection') - ->children() - ->scalarNode('driver')->end() - ->scalarNode('host')->end() - ->scalarNode('username')->end() - ->scalarNode('password')->end() - ->end() - ->end() - ->end() - ; - -O puedes definir un prototipo para cada nodo dentro del arreglo de nodos:: - - $rootNode - ->children() - ->arrayNode('connections') - ->prototype('array') - ->children() - ->scalarNode('driver')->end() - ->scalarNode('host')->end() - ->scalarNode('username')->end() - ->scalarNode('password')->end() - ->end() - ->end() - ->end() - ; - -Puedes utilizar un prototipo para añadir una definición la cual se puede repetir muchas veces dentro del nodo actual. De acuerdo con la definición del prototipo en el ejemplo anterior, es posible tener varios arreglos de conexión (conteniendo un ``driver``, ``host``, etc.). - -Opciones del arreglo de nodos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de definir los hijos de un arreglo de nodos, puedes proveer opciones como: - -``useAttributeAsKey()`` - Provee el nombre de un nodo hijo, dónde se debe usar el valor como la clave en el arreglo resultante. -``requiresAtLeastOneElement()`` - Cuando menos debe haber un elemento en el arreglo (únicamente trabaja cuando también se invoca a ``isRequired()``). -``addDefaultsIfNotSet()`` - Si algún nodo hijo tiene valores predefinidos, se utilizan si no proporcionas valores explícitos. - -Un ejemplo de esto:: - - $rootNode - ->children() - ->arrayNode('parameters') - ->isRequired() - ->requiresAtLeastOneElement() - ->useAttributeAsKey('name') - ->prototype('array') - ->children() - ->scalarNode('value')->isRequired()->end() - ->end() - ->end() - ->end() - ->end() - ; - -En *YAML*, la configuración podría tener esta apariencia: - -.. code-block:: yaml - - database: - parameters: - param1: { value: param1val } - -En *XML*, cada nodo ``parameters`` tendría un atributo ``name`` (junto con -``value``), el cual se sacaría y utilizarí como la clave para ese elemento en el -arreglo final. El ``useAttributeAsKey`` es útil para normalizar cómo se especifican los arreglos entre diferentes formatos como *XML* y *YAML*. - -Valores predefinidos y requeridos ---------------------------------- - -Para todos los tipos de nodo, es posible definir valores predeterminados y valores de sustitución en caso de que un nodo tenga un cierto valor: - -``defaultValue()`` - Establece un valor predeterminado -``isRequired()`` - Se debe definir (pero puede estar vacío) -``cannotBeEmpty()`` - No puede contener un valor vacío -``default*()`` - (``null``, ``true``, ``false``), acceso directo para ``defaultValue()`` -``treat*Like()`` - (``null``, ``true``, ``false``), provee un valor sustituto en caso de que el valor sea ``*.`` - -.. code-block:: php - - $rootNode - ->children() - ->arrayNode('connection') - ->children() - ->scalarNode('driver') - ->isRequired() - ->cannotBeEmpty() - ->end() - ->scalarNode('host') - ->defaultValue('localhost') - ->end() - ->scalarNode('username')->end() - ->scalarNode('password')->end() - ->booleanNode('memory') - ->defaultFalse() - ->end() - ->end() - ->end() - ->arrayNode('settings') - ->addDefaultsIfNotSet() - ->children() - ->scalarNode('name') - ->isRequired() - ->cannotBeEmpty() - ->defaultValue('value') - ->end() - ->end() - ->end() - ->end() - ; - -Secciones opcionales --------------------- - -.. versionadded:: 2.2 - Los métodos ``canBeEnabled`` y ``canBeDisabled`` son nuevos en *Symfony 2.2* - -Si tienes secciones enteras que son opcionales y se pueden activar/desactivar, puedes aprovechar los métodos abreviados :method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeEnabled` y -:method:`Symfony\\Component\\Config\\Definition\\Builder\\ArrayNodeDefinition::canBeDisabled`:: - - $arrayNode - ->canBeEnabled() - ; - - // es equivalente a - - $arrayNode - ->treatFalseLike(array('enabled' => false)) - ->treatTrueLike(array('enabled' => true)) - ->treatNullLike(array('enabled' => true)) - ->children() - ->booleanNode('enabled') - ->defaultFalse() - ; - -El método ``canBeDisabled`` se ve igual salvo que de manera predefinida la sección estaría habilitada. - -Combinando opciones -------------------- - -Puedes proporcionar opciones adicionales sobre el proceso de mezcla. Para arreglos: - -``performNoDeepMerging()`` - Cuando también defines el valor en un segundo arreglo de configuración, no se intenta combinar un arreglo, sino que lo sobrescribe por completo. - -Para todos los nodos: - -``cannotBeOverwritten()`` - No permite que otros arreglos de configuración sobrescriban un valor existente de este nodo. - -Anexando secciones ------------------- - -Si tienes una configuración de validación compleja, entonces el árbol puede crecer bastante y posiblemente quieras dividirlo en secciones. Lo puedes conseguir haciendo una sección en un nodo separado y luego anexándolo al árbol principal con ``append()``:: - - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('database'); - - $rootNode - ->children() - ->arrayNode('connection') - ->children() - ->scalarNode('driver') - ->isRequired() - ->cannotBeEmpty() - ->end() - ->scalarNode('host') - ->defaultValue('localhost') - ->end() - ->scalarNode('username')->end() - ->scalarNode('password')->end() - ->booleanNode('memory') - ->defaultFalse() - ->end() - ->end() - ->append($this->addParametersNode()) - ->end() - ->end() - ; - - return $treeBuilder; - } - - public function addParametersNode() - { - $builder = new TreeBuilder(); - $node = $builder->root('parameters'); - - $node - ->isRequired() - ->requiresAtLeastOneElement() - ->useAttributeAsKey('name') - ->prototype('array') - ->children() - ->scalarNode('value')->isRequired()->end() - ->end() - ->end() - ; - - return $node; - } - -Esto también es útil para ayudarte a evitar repeticiones si tienes repetida la configuración de esas secciones en diferentes sitios. - -Normalización -------------- - -Cuándo son procesados los archivos de configuración primero se normalizan, luego se fusionan -y finalmente el árbol se usa para validar el arreglo resultante. El proceso de normalización se usa para sacar algunos de las diferencias que resultan de diferentes formatos de configuración, principalmente las diferencias entre *Yaml* y *XML*. - -El separador utilizado en las claves en *Yaml* típicamente es ``_`` y ``-`` en *XML*. Por ejemplo, ``auto_connect`` en *Yaml* y ``auto-connect`` en *XML*. La normalización debería hacer que ambos fueran ``auto_connect``. - -.. caution:: - - La clave destino no será alterada si está mezclada como ``foo-bar_moo`` o si ya existe. - -Otra diferencia entre *Yaml* y *XML* es la manera en que se pueden representar los valores del arreglo. En *Yaml* puedes tener: - -.. code-block:: yaml - - twig: - extensions: ['twig.extension.foo', 'twig.extension.bar'] - -y en *XML*: - -.. code-block:: xml - - - twig.extension.foo - twig.extension.bar - - -Esta diferencia se puede sacar en la normalización pluralizando la clave utilizada -en *XML*. Puedes especificar si deseas que un clave se pluralice de este modo con ``fixXmlConfig()``:: - - $rootNode - ->fixXmlConfig('extension') - ->children() - ->arrayNode('extensions') - ->prototype('scalar')->end() - ->end() - ->end() - ; - -Si es una pluralización irregular puedes especificar el plural a utilizar como -segundo argumento:: - - $rootNode - ->fixXmlConfig('child', 'children') - ->children() - ->arrayNode('children') - ->end() - ; - -Así como se corrigió este, ``fixXmlConfig`` garantiza que solo los elementos -*XML* todavía se convierten en un arreglo. Por lo tanto puedes tener: - -.. code-block:: xml - - default - extra - -y a veces sólo: - -.. code-block:: xml - - default - -De manera predefinida ``connection`` sería un arreglo en el primer caso y una cadena -en el segundo dificultando su validación. Puedes garantizar que siempre sea un arreglo con con ``fixXmlConfig``. - -Puedes controlar el proceso de normalización más allá, de ser necesario. Por ejemplo, posiblemente quieras permitir que se pueda configurar una cadena y utilizarla como una clave particular o que se configuren varias claves explícitamente. De modo que, si todo aparte del ``name`` es opcional en esta configuración: - -.. code-block:: yaml - - connection: - name: my_mysql_connection - host: localhost - driver: mysql - username: user - password: pass - -También puedes permitir lo siguiente: - -.. code-block:: yaml - - connection: my_mysql_connection - -Cambiando el valor de una cadena a un arreglo asociativo con ``name`` como la clave:: - - $rootNode - ->children() - ->arrayNode('connection') - ->beforeNormalization() - ->ifString() - ->then(function($v) { return array('name'=> $v); }) - ->end() - ->children() - ->scalarNode('name')->isRequired() - // ... - ->end() - ->end() - ->end() - ; - -Reglas de validación --------------------- - -Puedes proporcionar reglas de validación más avanzadas utilizando la :class:`Symfony\\Component\\Config\\Definition\\Builder\\ExprBuilder`. Este constructor implementa una interfaz fluida para un bien conocido control de la estructura. -El constructor se utiliza para agregar reglas avanzadas de validación a las definiciones de nodo, como:: - - $rootNode - ->children() - ->arrayNode('connection') - ->children() - ->scalarNode('driver') - ->isRequired() - ->validate() - ->ifNotInArray(array('mysql', 'sqlite', 'mssql')) - ->thenInvalid('Invalid database driver "%s"') - ->end() - ->end() - ->end() - ->end() - ->end() - ; - -Una regla de validación siempre tiene una parte ``«if»``. Puedes especificar esta parte de las siguientes maneras: - -- ``ifTrue()`` -- ``ifString()`` -- ``ifNull()`` -- ``ifArray()`` -- ``ifInArray()`` -- ``ifNotInArray()`` -- ``always()`` - -Una regla de validación también requiere una parte ``«then»``: - -- ``then()`` -- ``thenEmptyArray()`` -- ``thenInvalid()`` -- ``thenUnset()`` - -Normalmente, ``«then»`` es un cierre. Su valor de retorno se utiliza como un nuevo valor para el nodo, en lugar del valor original. - -Procesando valores de configuración ------------------------------------ - -La clase :class:`Symfony\\Component\\Config\\Definition\\Processor` utiliza el árbol que fue construido usando el :class:`Symfony\\Component\\Config\\Definition\\Builder\\TreeBuilder` para procesar múltiples arreglos de valores de configuración que se deben combinar. -Si algún valor no es del tipo esperado, todavía es obligatorio e indefinido, o no se pudo validar de alguna otra manera, será lanzada una excepción. -De lo contrario, el resultado es un arreglo de valores de configuración limpio:: - - use Symfony\Component\Yaml\Yaml; - use Symfony\Component\Config\Definition\Processor; - use Acme\DatabaseConfiguration; - - $config1 = Yaml::parse(__DIR__.'/src/Matthias/config/config.yml'); - $config2 = Yaml::parse(__DIR__.'/src/Matthias/config/config_extra.yml'); - - $configs = array($config1, $config2); - - $processor = new Processor(); - $configuration = new DatabaseConfiguration; - $processedConfiguration = $processor->processConfiguration( - $configuration, - $configs) - ; - diff --git a/_sources/components/config/index.txt b/_sources/components/config/index.txt deleted file mode 100644 index c8a80fa..0000000 --- a/_sources/components/config/index.txt +++ /dev/null @@ -1,10 +0,0 @@ -``Config`` -========== - -.. toctree:: - :maxdepth: 2 - - introduction - resources - caching - definition diff --git a/_sources/components/config/introduction.txt b/_sources/components/config/introduction.txt deleted file mode 100644 index 9d286ef..0000000 --- a/_sources/components/config/introduction.txt +++ /dev/null @@ -1,28 +0,0 @@ -.. index:: - single: Config - single: Componentes; Config - -El componente ``Config`` -======================== - -Introducción ------------- - -El componente ``Config`` proporciona varias clases para ayudarte a encontrar, cargar, combinar, autocompletar y validar los valores de configuración de cualquier tipo, sea cual sea su fuente, puede ser (*YAML*, *XML*, archivos *INI*, o por ejemplo, una base de datos). - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Config); -* :doc:`Instalándolo vía Composer ` (``symfony/config`` en `Packagist`_). - -Secciones ---------- - -* :doc:`/components/config/resources` -* :doc:`/components/config/caching` -* :doc:`/components/config/definition` - -.. _`Packagist`: https://packagist.org/packages/symfony/config \ No newline at end of file diff --git a/_sources/components/config/resources.txt b/_sources/components/config/resources.txt deleted file mode 100644 index 813f59a..0000000 --- a/_sources/components/config/resources.txt +++ /dev/null @@ -1,71 +0,0 @@ -.. index:: - single: Config; Cargando recursos - -Cargando recursos -================= - -Localizando recursos --------------------- - -La carga de la configuración normalmente se inicia con la búsqueda de recursos, en la mayoría de los casos: archivos. Esto se puede hacer con el :class:`Symfony\\Component\\Config\\FileLocator`:: - - use Symfony\Component\Config\FileLocator; - - $configDirectories = array(__DIR__.'/app/config'); - - $locator = new FileLocator($configDirectories); - $yamlUserFiles = $locator->locate('users.yml', null, false); - -El localizador recibe un conjunto de lugares donde se deben buscar los archivos. -El primer argumento de ``locate()`` es el nombre del archivo a buscar. El segundo argumento puede ser la ruta actual y cuando si lo suministras, el localizador primero verá en ese directorio. El tercer argumento indica si el localizador debe devolver el primer archivo encontrado, o un arreglo conteniendo todas las coincidencias. - -Cargadores de recursos ----------------------- - -Debes definir un cargador para cada tipo de recurso (*YAML*, *XML*, anotaciones, etc.). -Cada cargador debe implementar la :class:`Symfony\\Component\\Config\\Loader\\LoaderInterface` o extender la clase abstracta :class:`Symfony\\Component\\Config\\Loader\\FileLoader`, la cual permite la importación recursiva de otros recursos:: - - use Symfony\Component\Config\Loader\FileLoader; - use Symfony\Component\Yaml\Yaml; - - class YamlUserLoader extends FileLoader - { - public function load($resource, $type = null) - { - $configValues = Yaml::parse($resource); - - // ... manipula los valores de configuración - - // tal vez importe algún otro recurso: - - // $this->import('extra_users.yml'); - } - - public function supports($resource, $type = null) - { - return is_string($resource) && 'yml' === pathinfo( - $resource, - PATHINFO_EXTENSION - ); - } - } - -Encontrando el cargador adecuado --------------------------------- - -La clase :class:`Symfony\\Component\\Config\\Loader\\LoaderResolver` recibe como primer argumento el constructor de una colección de cargadores. Cuando se debe cargar un recurso (por ejemplo, un archivo *XML*), este realiza un bucle a través de esta colección de cargadores y devuelve el cargador compatible con ese tipo de recurso en particular. - -La clase :class:`Symfony\\Component\\Config\\Loader\\DelegatingLoader` usa el :class:`Symfony\\Component\\Config\\Loader\\LoaderResolver`. Cuando se le pide que cargue un recurso, delega esta cuestión a la clase :class:`Symfony\\Component\\Config\\Loader\\LoaderResolver`. En caso de que la resolución haya encontrado un cargador adecuado, se le pedirá a ese cargador que cargue el recurso:: - - use Symfony\Component\Config\Loader\LoaderResolver; - use Symfony\Component\Config\Loader\DelegatingLoader; - - $loaderResolver = new LoaderResolver(array(new YamlUserLoader($locator))); - $delegatingLoader = new DelegatingLoader($loaderResolver); - - $delegatingLoader->load(__DIR__.'/users.yml'); - /* - El YamlUserLoader se utiliza para cargar este recurso, - debido a que es compatible con la extensión de archivo - ".yml" - */ diff --git a/_sources/components/console.txt b/_sources/components/console.txt deleted file mode 100644 index 2182b52..0000000 --- a/_sources/components/console.txt +++ /dev/null @@ -1,316 +0,0 @@ -.. index:: - single: Console; CLI - single: Componentes; Console - -El componente ``Console`` -========================= - - El componente ``Console`` facilita la creación de bellas y comprobables interfaces de línea de ordenes. - -El componente ``Console`` te permite crear instrucciones para la línea de ordenes. Tus ordenes de consola se pueden utilizar para cualquier tarea repetitiva, como tareas programadas (``cronjobs``), importaciones, u otros trabajos por lotes. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Console); -* Instalándolo a través de *PEAR* (`pear.symfony.com/Console`); -* Instalándolo vía ``Composer`` (`symfony/console` en Packagist). - -Creando una orden básica ------------------------- - -Para hacer una orden de consola que nos de la bienvenida desde la línea de ordenes, crea el archivo :file:`GreetCommand.php` y agrégale lo siguiente: - -.. code-block:: php - - namespace Acme\DemoBundle\Command; - - use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputArgument; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Input\InputOption; - use Symfony\Component\Console\Output\OutputInterface; - - class GreetCommand extends Command - { - protected function configure() - { - $this - ->setName('demo:greet') - ->setDescription('Greet someone') - ->addArgument('name', InputArgument::OPTIONAL, 'Who do you want to greet?') - ->addOption('yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters') - ; - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $name = $input->getArgument('name'); - if ($name) { - $text = 'Hello '.$name; - } else { - $text = 'Hello'; - } - - if ($input->getOption('yell')) { - $text = strtoupper($text); - } - - $output->writeln($text); - } - } - -También necesitas crear el archivo para ejecutar la línea de ordenes, el cual crea una ``Application`` y le agrega ordenes: - -.. code-block:: html+php - - #!/usr/bin/env php - # app/console - add(new GreetCommand); - $application->run(); - -Prueba la nueva consola de ordenes ejecutando lo siguiente - -.. code-block:: bash - - $ app/console demo:greet Fabien - -Esto imprimirá lo siguiente en la línea de ordenes: - -.. code-block:: text - - Hello Fabien - -También puedes utilizar la opción ``--yell`` para convertir todo a mayúsculas: - -.. code-block:: bash - - $ app/console demo:greet Fabien --yell - -Esto imprime:: - - HELLO FABIEN - -Coloreando la salida -~~~~~~~~~~~~~~~~~~~~ - -Cada vez que produces texto, puedes escribir el texto con etiquetas para colorear tu salida. Por ejemplo:: - - // texto verde - $output->writeln('foo'); - - // texto amarillo - $output->writeln('foo'); - - // texto negro sobre fondo cían - $output->writeln('foo'); - - // texto blanco sobre fondo rojo - $output->writeln('foo'); - -Es posible definir tu propio estilo usando la clase :class:`Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle`:: - - $style = new OutputFormatterStyle('red', 'yellow', array('bold', 'blink')); - $output->getFormatter()->setStyle('fire', $style); - $output->writeln('foo'); - -Los colores disponibles para el fondo y primer plano son: ``black``, ``red``, ``green``, ``yellow``, ``blue``, ``magenta``, ``cyan`` y ``white``. - -Y las opciones disponibles son: ``bold``, ``underscore``, ``blink``, ``reverse`` y ``conceal``. - -Utilizando argumentos de ordenes --------------------------------- - -La parte más interesante de las ordenes son los argumentos y opciones que puedes hacer disponibles. Los argumentos son cadenas ---separadas por espacios--- que vienen después del nombre de la orden misma. Ellos están ordenados, y pueden ser opcionales u obligatorios. Por ejemplo, añade un argumento ``last_name`` opcional a la orden y haz que el argumento ``name`` sea obligatorio:: - - $this - // ... - ->addArgument('name', InputArgument::REQUIRED, 'Who do you want to greet?') - ->addArgument('last_name', InputArgument::OPTIONAL, 'Your last name?'); - -Ahora tienes acceso a un argumento ``last_name`` en la orden:: - - if ($lastName = $input->getArgument('last_name')) { - $text .= ' '.$lastName; - } - -Ahora la orden se puede utilizar en cualquiera de las siguientes maneras: - -.. code-block:: bash - - $ app/console demo:greet Fabien - $ app/console demo:greet Fabien Potencier - -Usando las opciones de la orden -------------------------------- - -A diferencia de los argumentos, las opciones no están ordenadas (lo cual significa que las puedes especificar en cualquier orden) y se especifican con dos guiones (por ejemplo, ``--yell`` también puedes declarar un atajo de una letra que puedes invocar con un único guión como ``-y``). Las opciones son: *always* opcional, y se puede configurar para aceptar un valor (por ejemplo, ``dir=src``) o simplemente como una variable lógica sin valor (por ejemplo, ``yell``). - -.. tip:: - - También es posible hacer que un argumento *opcionalmente* acepte un valor (de modo que ``--yell`` o ``yell=loud`` funcione). Las opciones también se pueden configurar para aceptar una matriz de valores. - -Por ejemplo, añadir una nueva opción a la orden que se puede usar para especificar cuántas veces se debe imprimir el mensaje en una fila:: - - $this - // ... - ->addOption('iterations', null, InputOption::VALUE_REQUIRED, 'How many times should the message be printed?', 1); - -A continuación, utilízalo en la orden para imprimir el mensaje varias veces: - -.. code-block:: php - - for ($i = 0; $i < $input->getOption('iterations'); $i++) { - $output->writeln($text); - } - -Ahora, al ejecutar la tarea, si lo deseas, puedes especificar un indicador ``--iterations``: - -.. code-block:: bash - - $ app/console demo:greet Fabien - $ app/console demo:greet Fabien --iterations=5 - -El primer ejemplo sólo se imprimirá una vez, ya que ``iterations`` está vacía y el predeterminado es ``1`` (el último argumento de ``addOption``). El segundo ejemplo se imprimirá cinco veces. - -Recordemos que a las opciones no les preocupa su orden. Por lo tanto, cualquiera de las siguientes trabajará: - -.. code-block:: bash - - $ app/console demo:greet Fabien --iterations=5 --yell - $ app/console demo:greet Fabien --yell --iterations=5 - -Hay 4 variantes de la opción que puedes utilizar: - -=========================== ================================================================================================== -Opción Valor -=========================== ================================================================================================== -InputOption::VALUE_IS_ARRAY Esta opción acepta múltiples valores (por ejemplo: ``--dir=/foo --dir=/bar``) -InputOption::VALUE_NONE No acepta entradas para esta opción (por ejemplo: ``--yell``) -InputOption::VALUE_REQUIRED Este valor es obligatorio (por ejemplo: ``--iterations=5``), la opción es sí misma aún es opcional -InputOption::VALUE_OPTIONAL Esta opción puede o no tener un valor (por ejemplo: ``yell`` o ``yell=loud``) -=========================== ================================================================================================== - -Puedes combinar el VALUE_IS_ARRAY con VALUE_REQUIRED o VALUE_OPTIONAL de la siguiente manera: - -.. code-block:: php - - $this - // ... - ->addOption('iterations', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'How many times should the message be printed?', 1); - -Pidiendo información al usuario -------------------------------- - -Al crear ordenes, tienes la capacidad de recopilar más información de los usuarios haciéndoles preguntas. Por ejemplo, supongamos que deseas confirmar una acción antes de llevarla a cabo realmente. Añade lo siguiente a tu orden:: - - $dialog = $this->getHelperSet()->get('dialog'); - if (!$dialog->askConfirmation($output, 'Continue with this action?', false)) { - return; - } - -En este caso, el usuario tendrá que "Continuar con esta acción", y, a menos que responda con ``y``, la tarea se detendrá. El tercer argumento de ``askConfirmation`` es el valor predeterminado que se devuelve si el usuario no introduce algo. - -También puedes hacer preguntas con más que una simple respuesta sí/no. Por ejemplo, si necesitas saber el nombre de algo, puedes hacer lo siguiente:: - - $dialog = $this->getHelperSet()->get('dialog'); - $name = $dialog->ask($output, 'Please enter the name of the widget', 'foo'); - -Probando ordenes ----------------- - -*Symfony2* proporciona varias herramientas para ayudarte a probar las ordenes. La más útil es la clase :class:`Symfony\\Component\\Console\\Tester\\CommandTester`. Esta utiliza clases entrada y salida especiales para facilitar la prueba sin una consola real:: - - use Symfony\Component\Console\Application; - use Symfony\Component\Console\Tester\CommandTester; - use Acme\DemoBundle\Command\GreetCommand; - - class ListCommandTest extends \PHPUnit_Framework_TestCase - { - public function testExecute() - { - $application = new Application(); - $application->add(new GreetCommand()); - - $command = $application->find('demo:greet'); - $commandTester = new CommandTester($command); - $commandTester->execute(array('command' => $command->getName())); - - $this->assertRegExp('/.../', $commandTester->getDisplay()); - - // ... - } - } - -El método :method:`Symfony\\Component\\Console\\Tester\\CommandTester::getDisplay` devuelve lo que se ha exhibido durante una llamada normal de la consola. - -Puedes probar enviando argumentos y opciones a la orden pasándolos como una matriz al método :method:`symfony\\Component\\Console\\Tester\\CommandTester::getDisplay`:: - - use Symfony\Component\Console\Application; - use Symfony\Component\Console\Tester\CommandTester; - use Acme\DemoBundle\Command\GreetCommand; - - class ListCommandTest extends \PHPUnit_Framework_TestCase - { - // ... - - public function testNameIsOutput() - { - $application = new Application(); - $application->add(new GreetCommand()); - - $command = $application->find('demo:greet'); - $commandTester = new CommandTester($command); - $commandTester->execute( - array('command' => $command->getName(), 'name' => 'Fabien') - ); - - $this->assertRegExp('/Fabien/', $commandTester->getDisplay()); - } - } - -.. tip:: - - También puedes probar toda una aplicación de consola utilizando :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester`. - -Llamando una orden existente ----------------------------- - -Si una orden depende de que se ejecute otra antes, en lugar de obligar al usuario a recordar el orden de ejecución, puedes llamarla directamente tú mismo. -Esto también es útil si deseas crear una "metaorden" que ejecute un montón de otras ordenes (por ejemplo, todas las ordenes que se deben ejecutar cuando el código del proyecto ha cambiado en los servidores de producción: vaciar la caché, generar delegados *Doctrine2*, volcar activos ``Assetic``, ...). - -Llamar a una orden desde otra es sencillo:: - - protected function execute(InputInterface $input, OutputInterface $output) - { - $command = $this->getApplication()->find('demo:greet'); - - $arguments = array( - 'command' => 'demo:greet', - 'name' => 'Fabien', - '--yell' => true, - ); - - $input = new ArrayInput($arguments); - $returnCode = $command->run($input, $output); - - // ... - } - -En primer lugar, tu :method:`Symfony\\Component\\Console\\Application::find` busca la orden que deseas ejecutar pasando el nombre de la orden. - -Entonces, es necesario crear una nueva clase :class:`Symfony\\Component\\Console\\Input\\ArrayInput` con los argumentos y opciones que desees pasar a la orden. - -Finalmente, invocar al método ``run()`` ejecuta la orden realmente y devuelve el código devuelto de la orden (el valor del método ``execute()``). - -.. note:: - - La mayor parte del tiempo, llamar a una orden desde código que no se ejecuta en la línea de ordenes no es una buena idea por varias razones. En primer lugar, la salida de la orden se ha optimizado para la consola. Pero lo más importante, puedes pensar de una orden como si fuera un controlador; este debe utilizar el modelo para hacer algo y mostrar algún comentario al usuario. Así, en lugar de llamar una orden desde la *Web*, reconstruye tu código y mueve la lógica a una nueva clase. diff --git a/_sources/components/console/events.txt b/_sources/components/console/events.txt deleted file mode 100644 index c1fd20b..0000000 --- a/_sources/components/console/events.txt +++ /dev/null @@ -1,103 +0,0 @@ -.. index:: - single: Console; Eventos - -Usando eventos -============== - -.. versionadded:: 2.3 - Los eventos de consola se añadieron en *Symfony 2.3*. - -La clase ``Application`` del componente ``Console`` te permite engancharla opcionalmente al ciclo de vida de una aplicación de consola a través de eventos. En vez de reinventar la rueda, esta utiliza el componente ``EventDispatcher`` de *Symfony* para hacer el trabajo:: - - use Symfony\Component\Console\Application; - use Symfony\Component\EventDispatcher\EventDispatcher; - - $application = new Application(); - $application->setDispatcher($dispatcher); - $application->run(); - -El evento ``ConsoleEvents::COMMAND`` ------------------------------------- - -**Propósito típico**: Hacer algo antes de ejecutar cualquier orden (tal como registrar qué orden se va a ejecutar), o mostrar algo sobre el evento a ejecutar. - -Justo antes de ejecutar cualquier orden, se despacha el evento ``ConsoleEvents::COMMAND``. Lo escuchas reciben un evento :class:`Symfony\\Component\\Console\\Event\\ConsoleCommandEvent`:: - - use Symfony\Component\Console\Event\ConsoleCommandEvent; - use Symfony\Component\Console\ConsoleEvents; - - $dispatcher->addListener(ConsoleEvents::COMMAND, function (ConsoleCommandEvent $event) { - // consigue la instancia de entrada - $input = $event->getInput(); - - // consigue la instancia de salida - $output = $event->getOutput(); - - // consigue la orden a ejecutar - $command = $event->getCommand(); - - // escribe algo sobre la orden - $output->writeln(sprintf('Before running command %s', $command->getName())); - - // consigue la aplicación - $application = $command->getApplication(); - }); - -El evento ``ConsoleEvents::TERMINATE`` --------------------------------------- - -**Propósito típico**: Para realizar alguna limpieza en las acciones después de ejecutar la orden. - -Después de ejecutar la orden, se despacha el evento``ConsoleEvents::TERMINATE``. Este se puede se puede usar para hacer cualquier acción que se deba ejecutar para todas las -órdenes o para limpiar lo que iniciaste en un escucha de ``ConsoleEvents::COMMAND`` (como enviar registros, cerrar una conexión de base de datos, enviar correos electrónicos, ...). Un escucha también podría cambiar el código de salida. - -Los escuchas reciben un evento :class:`Symfony\\Component\\Console\\Event\\ConsoleTerminateEvent`:: - - use Symfony\Component\Console\Event\ConsoleTerminateEvent; - use Symfony\Component\Console\ConsoleEvents; - - $dispatcher->addListener(ConsoleEvents::TERMINATE, function (ConsoleTerminateEvent $event) { - // consigue la salida - $output = $event->getOutput(); - - // consigue la orden que se ha ejecutado - $command = $event->getCommand(); - - // muestra algo - $output->writeln(sprintf('After running command %s', $command->getName())); - - // cambia el código de salida - $event->setExitCode(128); - }); - -.. tip:: - - Este evento también se despacha cuando la orden lanza una excepción. - Este entonces se despacha justo antes del evento ``ConsoleEvents::EXCEPTION``. - El código de salida recibido en este caso es el código de la excepción. - -El evento ``ConsoleEvents::EXCEPTION`` --------------------------------------- - -**Propósito típico**: manejar excepciones lanzadas durante la ejecución de una orden. - -Siempre que una orden lanza una excepción, se lanza el evento ``ConsoleEvents::EXCEPTION``. Un escucha puede envolver o cambiar la excepción o hacer cualquier cosa útil antes de que la aplicación lance la excepción. - -Los escuchas reciben un evento :class:`Symfony\\Component\\Console\\Event\\ConsoleForExceptionEvent`:: - - use Symfony\Component\Console\Event\ConsoleForExceptionEvent; - use Symfony\Component\Console\ConsoleEvents; - - $dispatcher->addListener(ConsoleEvents::EXCEPTION, function (ConsoleForExceptionEvent $event) { - $output = $event->getOutput(); - - $output->writeln(sprintf('Oops, exception thrown while running command %s', $command->getName())); - - // consigue el código de salida actual (el código de la excepción - // o el código de salida establecido por el evento - // ConsoleEvents::TERMINATE) - $exitCode = $event->getExitCode(); - - // cambia la excepción por otra - $event->setException(new \LogicException('Caught exception', $exitCode, $event->getException())); - }); diff --git a/_sources/components/console/helpers/dialoghelper.txt b/_sources/components/console/helpers/dialoghelper.txt deleted file mode 100644 index 20b7fc8..0000000 --- a/_sources/components/console/helpers/dialoghelper.txt +++ /dev/null @@ -1,152 +0,0 @@ -.. index:: - single: Ayudantes de consola; Ayudantes de diálogo - -Ayudantes de diálogo -==================== - -El clase :class:`Symfony\\Component\\Console\\Helper\\DialogHelper` proporciona -funciones para solicitar más información al usuario. Estos se incluyen en el conjunto de ayudantes predefinido, el cual puedes obtener llamando al método :method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`:: - - $dialog = $this->getHelperSet()->get('dialog'); - -Todos los métodos dentro de los ayudantes de diálogo tienen una clase :class:`Symfony\\Component\\Console\\Output\\OutputInterface` como primer argumento, la pregunta como segundo argumento y el valor predefinido como último argumento. - -Pidiendo confirmación al usuario --------------------------------- - -Supón que quieres confirmar una acción antes de ejecutarla realmente. Añade lo siguiente a tu orden:: - - // ... - if (!$dialog->askConfirmation( - $output, - 'Continue with this action?', - false - )) { - return; - } - -En este caso, se preguntará al usuario «¿Continúar con esta acción?», y regresará -``true`` si el usuario respondió con ``y`` o ``false`` en cualquier otro caso. El tercer argumento de ``askConfirmation`` es el valor predeterminado que se devuelve si el usuario no introduce algo. - -Pidiendo información al usuario -------------------------------- - -También puedes preguntar algó más complejo que una sencilla respuesta sí/no. Por ejemplo, si quieres saber un nombre de paquete, puedes añadir esto a tu orden:: - - // ... - $bundle = $dialog->ask( - $output, - 'Please enter the name of the bundle', - 'AcmeDemoBundle' - ); - -Se pedirá al usuario «Por favor, introduce el nombre del paquete». El usuario escribe algún nombre, el cual será devuelto por el método ``ask``. Si lo deja vacío, el valor predefinido (``AcmeDemoBundle`` aquí) será regresado. - -Ocultando la respuesta del usuario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.2 - El método ``askHiddenResponse`` se añadió en *Symfony 2.2*. - -Además, puedes hacer una pregunta y ocultar la respuesta. Esto, particularmente es conveniente para contraseñas:: - - $dialog = $this->getHelperSet()->get('dialog'); - $password = $dialog->askHiddenResponse( - $output, - 'What is the database password?', - false - ); - -.. caution:: - - Cuándo pides una respuesta oculta, *Symfony* utilizará o bien un binario, cambiará al modo ``stty`` o utilizará algún otro truco para ocultar la respuesta. Si ninguno está disponible, se replegará y dejará la respuesta visible a no ser que pases ``false`` como tercer argumento tal cómo en el ejemplo anterior. En este caso, lanzará una excepción en tiempo de ejecución (``RuntimeException``). - -Validando la respuesta ----------------------- - -Incluso, puedes validar la respuesta. Por ejemplo, en el último fragmento de código solicitaste el nombre del paquete. Siguiendo las convenciones de nomenclatura de *Symfony2*, se tiene que sufijar con ``Bundle``. Puedes validar esto utilizando el método :method:`Symfony\\Component\\Console\\Helper\\DialogHelper::askAndValidate`:: - - // ... - $bundle = $dialog->askAndValidate( - $output, - 'Please enter the name of the bundle', - function ($answer) { - if ('Bundle' !== substr($answer, -6)) { - throw new \RunTimeException( - 'The name of the bundle should be suffixed with \'Bundle\'' - ); - } - return $answer; - }, - false, - 'AcmeDemoBundle' - ); - -Estos métodos tienen 2 nuevos argumentos, la firma completa es:: - - askAndValidate( - OutputInterface $output, - string|array $question, - callback $validator, - integer $attempts = false, - string $default = null - ) - -El ``$validator`` es una retrollamada que maneja la validación. Este debe lanzar una excepción si hay algo incorrecto. El mensaje de la excepción se muestra en la consola, por lo tanto es una buena práctica poner alguna información útil en él. La función retrollamada también debería regresar el valor ingresado por el usuario si la validación fue satisfactoria. - -Puedes poner el número máximo de veces a preguntar en el argumento ``$attempts``. -Si alcanzas este número máximo utilizas el valor predeterminado, el cual es dado en el último argumento. Utilizar ``false`` significa que la cantidad de intentos es infinita. -El usuario será preguntado mientras proporcione una respuesta nula y sólo será capaz de proceder si su entrada es válida. - -Ocultando la respuesta del usuario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.2 - El método ``askHiddenResponseAndValidate`` se añadió en *Symfony 2.2*. - -También puedes hacer una pregunta y validar una respuesta oculta:: - - $dialog = $this->getHelperSet()->get('dialog'); - - $validator = function ($value) { - if (trim($value) == '') { - throw new \Exception('The password can not be empty'); - } - }; - - $password = $dialog->askHiddenResponseAndValidate( - $output, - 'Please enter the name of the widget', - $validator, - 20, - false - ); - -Si quieres permitir que la respuesta sea visible si ---por alguna razón--- no se puede ocultar, pasa ``true`` como quinto argumento. - -Permitiendo al usuario escoger de una lista de Respuestas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.2 - El método :method:`Symfony\\Component\\Console\\Helper\\DialogHelper::select` se añadió en *Symfony* 2.2. - -Si tienes un conjunto de respuestas predefinido del cual pueda escoger el usuario, podrías utilizar el método ``ask`` descrito anteriormente o, asegurarte de que el usuario proporcionó una respuesta correcta, el método ``askAndValidate``. Ambos tienen la desventaja de que necesitas manejar los valores incorrectos. - -En su lugar, puedes utilizar el método :method:`Symfony\\Component\\Console\\Helper\\DialogHelper::select`, el cual se asegura de que el usuario sólo puede introducir una cadena válida de una lista predefinida:: - - $dialog = $app->getHelperSet()->get('dialog'); - $colors = array('red', 'blue', 'yellow'); - - $color = $dialog->select( - $output, - 'Please select your favorite color (default to red)', - $colors, - 0 - ); - $output->writeln('You have just selected: ' . $colors[$color]); - - // ... hace algo con el color - -La opción que se debería seleccionar por omisión la proporciona el cuarto argumento. El valor predefinido es ``null``, lo cual significa que ninguna opción es la predefinida. - -Si el usuario introduce una cadena no válida, un mensaje de error será mostrado instando al usuario a proporcionar la respuesta de nuevo, hasta que introduzca una cadena válida o alcance el máximo de intentos (el cuál puedes definir con el quinto argumento). El valor predefinido para los intentos es ``false``, lo cual significa que no hay límite en los intentos. Puedes definir tu propio mensaje de error en el sexto argumento. diff --git a/_sources/components/console/helpers/formatterhelper.txt b/_sources/components/console/helpers/formatterhelper.txt deleted file mode 100644 index 75315f7..0000000 --- a/_sources/components/console/helpers/formatterhelper.txt +++ /dev/null @@ -1,48 +0,0 @@ -.. index:: - single: Ayudantes de consola; Ayudante formateador - -Ayudante formateador -==================== - -Los ayudantes formateadores proporcionan funciones para aplicar formato al resultado con colores. -Puedes hacer cosas más avanzadas con este ayudante de las que puedes hacer con :ref:`components-console-coloring`. - -La clase :class:`Symfony\\Component\\Console\\Helper\\FormatterHelper` está incluida en el conjunto de ayudantes predefinido, el cual te puedes conseguir llamando al método :method:`Symfony\\Component\\Console\\Command\\Command::getHelperSet`:: - - $formatter = $this->getHelperSet()->get('formatter'); - -Los métodos regresan una cadena, la cual normalmente dibujas en la consola pasándola al -método :method:`OutputInterface::writeln` - -Imprimiendo mensajes en una sección ------------------------------------ - -*Symfony* ofrece un estilo definido cuándo imprimes un mensaje que pertenece a alguna -«sección». Este imprime la sección en color encerrada entre corchetes y el mensaje real a la derecha de esta. Menos el color, tiene esta apariencia: - -.. code-block:: text - - [SomeSection] Here is some message related to that section - -Para reproducir este estilo, puedes utilizar el método :method:`Symfony\\Component\\Console\\Helper\\FormatterHelper::formatSection`:: - - $formattedLine = $formatter->formatSection( - 'SomeSection', - 'Here is some message related to that section' - ); - $output->writeln($formattedLine); - -Imprimiendo mensajes en un bloque ---------------------------------- - -A veces quieres poder imprimir un bloque de texto entero con un color de fondo. *Symfony* lo usa al imprimir mensajes de error. - -Si imprimes manualmente tu mensaje de error en más de una línea, notarás que el fondo es sólo a lo largo de cada línea individual. Usa el método :method:`Symfony\\Component\\Console\\Helper\\FormatterHelper::formatBlock` para generar una producción de bloque:: - - $errorMessages = array('Error!', 'Something went wrong'); - $formattedBlock = $formatter->formatBlock($errorMessages, 'error'); - $output->writeln($formattedBlock); - -Como puedes ver, al pasar un arreglo de mensajes al método :method:`Symfony\\Component\\Console\\Helper\\FormatterHelper::formatBlock` el método crea el resultado deseado. Si pasas ``true`` como tercer argumento, el bloque será formateado con más relleno (una línea en blanco por encima y debajo de los mensajes y 2 espacios a la izquierda y derecha). - -El «estilo» exacto a utilizar en el bloque depende de ti. En este caso, estás utilizando el estilo de ``error`` predefinido, pero hay otros estilos, o puedes crear el tuyo propio. Ve :ref:`components-console-coloring`. \ No newline at end of file diff --git a/_sources/components/console/helpers/index.txt b/_sources/components/console/helpers/index.txt deleted file mode 100644 index d8a319f..0000000 --- a/_sources/components/console/helpers/index.txt +++ /dev/null @@ -1,17 +0,0 @@ -.. index:: - single: Console; Ayudantes de consola - -Los ayudantes de consola -======================== - -.. toctree:: - :hidden: - - dialoghelper - formatterhelper - progresshelper - tablehelper - -Los componentes de Consola vienen con algunos útiles ayudantes. Estos ayudantes contienen funciones para aligerar algunas tareas comunes. - -.. include:: map.rst.inc diff --git a/_sources/components/console/helpers/progresshelper.txt b/_sources/components/console/helpers/progresshelper.txt deleted file mode 100644 index 41b35d3..0000000 --- a/_sources/components/console/helpers/progresshelper.txt +++ /dev/null @@ -1,71 +0,0 @@ -.. index:: - single: Ayudantes de consola; El ayudante Progress - -El ayudante ``Progress`` -======================== - -.. versionadded:: 2.2 - El ayudante ``progress`` se añadió en *Symfony 2.2*. - -.. versionadded:: 2.3 - El método ``setCurrent`` se añadió en *Symfony 2.3*. - -Cuándo ejecutes órdenes que consumen demasiado tiempo en su ejecución, puede ser útil mostrar información de progreso, la cual se actualiza conforme se ejecuta la orden: - -.. image:: /images/components/console/progress.png - -Para mostrar detalles de progreso, usa la clase :class:`Symfony\\Component\\Console\\Helper\\ProgressHelper`, pasándole el número total de unidades, y adelanta el progreso conforme se ejecute la orden:: - - $progress = $this->getHelperSet()->get('progress'); - - $progress->start($output, 50); - $i = 0; - while ($i++ < 50) { - // ... hace algún trabajo - - // avanza la barra de progreso 1 unidad - $progress->advance(); - } - - $progress->finish(); - -.. tip:: - - Además, puedes ajustar el progreso actual llamando al método :method:`Symfony\\Component\\Console\\Helper\\ProgressHelper::setCurrent`. - -El aspecto de la barra de progreso también se puede personalizar, con una serie -de diferentes niveles de detalle. Cada uno de los cuales muestra los diferentes elementos posibles ---tal cómo porcentaje completado, una barra de progreso en movimiento, o información actual/total (por ejemplo, 10/50):: - - $progress->setFormat(ProgressHelper::FORMAT_QUIET); - $progress->setFormat(ProgressHelper::FORMAT_NORMAL); - $progress->setFormat(ProgressHelper::FORMAT_VERBOSE); - $progress->setFormat(ProgressHelper::FORMAT_QUIET_NOMAX); - // el valor predefinido - $progress->setFormat(ProgressHelper::FORMAT_NORMAL_NOMAX); - $progress->setFormat(ProgressHelper::FORMAT_VERBOSE_NOMAX); - -También puedes controlar los diferentes caracteres y el ancho utilizado por la barra de progreso:: - - // la parte terminada de la barra - $progress->setBarCharacter('='); - // la parte inacabada de la barra - $progress->setEmptyBarCharacter(' '); - $progress->setProgressCharacter('|'); - $progress->setBarWidth(50); - -Para ver otras opciones disponibles, revisa la clase :class:`Symfony\\Component\\Console\\Helper\\ProgressHelper` en la documentación de la *API*. - -.. caution:: - - Por razones de rendimiento, se prudente en no poner la cantidad total de pasos a un número alto. Por ejemplo, si estás iterando sobre una gran cantidad de elementos, considera un número de «paso» más pequeño que se actualizace en algunas iteraciones únicamente:: - - $progress->start($output, 500); - $i = 0; - while ($i++ < 50000) { - // ... hace algún trabajo - - // avanza cada 100 iteraciones - if ($i % 100 == 0) { - $progress->advance(); - } - } diff --git a/_sources/components/console/helpers/tablehelper.txt b/_sources/components/console/helpers/tablehelper.txt deleted file mode 100644 index ac22c1a..0000000 --- a/_sources/components/console/helpers/tablehelper.txt +++ /dev/null @@ -1,53 +0,0 @@ -.. index:: - single: Ayudantes de consola; Ayudante Table - -Ayudante Table -============== - -.. versionadded:: 2.3 - El ayudante ``table`` se añadió en *Symfony 2.3*. - -Cuándo construyes una aplicación de consola puede ser útil mostrar datos tabulares: - -.. image:: /images/components/console/table.png - -Para mostrar una tabla, usa la clase :class:`Symfony\\Component\\Console\\Helper\\TableHelper`, establece las cabeceras, filas y dibújala:: - - $table = $app->getHelperSet()->get('table'); - $table - ->setHeaders(array('ISBN', 'Title', 'Author')) - ->setRows(array( - array('99921-58-10-7', 'Divine Comedy', 'Dante Alighieri'), - array('9971-5-0210-0', 'A Tale of Two Cities', 'Charles Dickens'), - array('960-425-059-0', 'The Lord of the Rings', 'J. R. R. Tolkien'), - array('80-902734-1-6', 'And Then There Were None', 'Agatha Christie'), - )) - ; - $table->render($output); - -El diseño de la tabla se puede personalizar también. Hay dos maneras para personalizar la representación de la tabla: Utilizando diseños nombrados u opciones de representación personalizadas. - -Personalizando el diseño de la tabla usando diseños nombrados -------------------------------------------------------------- - -El ayudante ``table`` viene con dos diseños de tabla preconfigurados: - -* ``TableHelper::LAYOUT_DEFAULT`` - -* ``TableHelper::LAYOUT_BORDERLESS`` - -Puedes configurar el diseño utilizando el método :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setLayout`. - -Personalizando el diseño de la tabla con opciones de dibujo ------------------------------------------------------------ - -También puedes controlar cómo se dibuja la tabla poniendo los valores de la opción de dibujo: - -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setPaddingChar` -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setHorizontalBorderChar` -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setVerticalBorderChar` -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setVrossingChar` -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setVellHeaderFormat` -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setVellRowFormat` -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setBorderFormat` -* :method:`Symfony\\Component\\Console\\Helper\\TableHelper::setPadType` diff --git a/_sources/components/console/index.txt b/_sources/components/console/index.txt deleted file mode 100644 index 796abf7..0000000 --- a/_sources/components/console/index.txt +++ /dev/null @@ -1,11 +0,0 @@ -Consola -======= - -.. toctree:: - :maxdepth: 2 - - introduction - usage - single_command_tool - events - helpers/index diff --git a/_sources/components/console/introduction.txt b/_sources/components/console/introduction.txt deleted file mode 100644 index ae29f55..0000000 --- a/_sources/components/console/introduction.txt +++ /dev/null @@ -1,389 +0,0 @@ -.. index:: - single: Console; CLI - single: Componentes; Console - -El componente ``Console`` -========================= - - El componente ``Console`` facilita la creación de bellas y comprobables interfaces de línea de ordenes. - -El componente ``Console`` te permite crear instrucciones para la línea de ordenes. Tus ordenes de consola se pueden utilizar para cualquier tarea repetitiva, como tareas programadas (``cronjobs``), importaciones, u otros trabajos por lotes. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Console); -* :doc:`Instalándolo vía Composer ` (``symfony/console`` en `Packagist`_). - -.. note:: - - Windows no apoya colores *ANSI* de manera predeterminada así que el componente ``Console`` detecta y deshabilita colores donde Windows no cuenta con soporte. Sin embargo, si Windows no está configurado con un controlador *ANSI* y tus órdenes de consola invocan a otros guiones que emiten secuencias de color *ANSI*, serán mostradas como secuencias de caracteres escapadas. - - Para habilitar el apoyo de color *ANSI* en Windows, debes instalar `ANSICON`_. - -Creando una orden básica ------------------------- - -Para hacer una orden de consola que dé la bienvenida desde la línea de ordenes, crea el archivo :file:`GreetCommand.php` y agrégale lo siguiente:: - - namespace Acme\DemoBundle\Command; - - use Symfony\Component\Console\Command\Command; - use Symfony\Component\Console\Input\InputArgument; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Input\InputOption; - use Symfony\Component\Console\Output\OutputInterface; - - class GreetCommand extends Command - { - protected function configure() - { - $this - ->setName('demo:greet') - ->setDescription('Greet someone') - ->addArgument( - 'name', - InputArgument::OPTIONAL, - 'Who do you want to greet?' - ) - ->addOption( - 'yell', - null, - InputOption::VALUE_NONE, - 'If set, the task will yell in uppercase letters' - ) - ; - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $name = $input->getArgument('name'); - if ($name) { - $text = 'Hello '.$name; - } else { - $text = 'Hello'; - } - - if ($input->getOption('yell')) { - $text = strtoupper($text); - } - - $output->writeln($text); - } - } - -También necesitas crear el archivo para ejecutar la línea de ordenes, el cual crea una ``Application`` y le agrega ordenes:: - - #!/usr/bin/env php - add(new GreetCommand); - $application->run(); - -Prueba la nueva consola de ordenes ejecutando lo siguiente: - -.. code-block:: bash - - $ app/console demo:greet Fabien - -Esto imprimirá lo siguiente en la línea de ordenes: - -.. code-block:: text - - Hello Fabien - -También puedes utilizar la opción ``--yell`` para convertir todo a mayúsculas: - -.. code-block:: bash - - $ app/console demo:greet Fabien --yell - -Esto imprime:: - - HELLO FABIEN - -.. _components-console-coloring: - -Coloreando la salida -~~~~~~~~~~~~~~~~~~~~ - -Cada vez que produces texto, puedes escribir el texto con etiquetas para colorear tu salida. Por ejemplo:: - - // texto verde - $output->writeln('foo'); - - // texto amarillo - $output->writeln('foo'); - - // texto negro sobre fondo cían - $output->writeln('foo'); - - // texto blanco sobre fondo rojo - $output->writeln('foo'); - -Es posible definir tu propio estilo usando la clase :class:`Symfony\\Component\\Console\\Formatter\\OutputFormatterStyle`:: - - $style = new OutputFormatterStyle('red', 'yellow', array('bold', 'blink')); - $output->getFormatter()->setStyle('fire', $style); - $output->writeln('foo'); - -Los colores disponibles para el fondo y primer plano son: ``black``, ``red``, ``green``, ``yellow``, ``blue``, ``magenta``, ``cyan`` y ``white``. - -Y las opciones disponibles son: ``bold``, ``underscore``, ``blink``, ``reverse`` y ``conceal``. - -También puedes configurar estos colores y opciones dentro del nombre de la etiqueta:: - - // texto verde - $output->writeln('foo'); - - // texto negro en fondo cian - $output->writeln('foo'); - - // text negro en fondo amarillo - $output->writeln('foo'); - -Niveles de verbosidad -~~~~~~~~~~~~~~~~~~~~~ - -La consola tiene 3 niveles de verbosidad. Estos están definidos en -la clase :class:`Symfony\\Component\\Console\\Output\\OutputInterface`: - -================================== ======================================= -Opción Valor -================================== ======================================= -OutputInterface::VERBOSITY_QUIET No muestra ningún mensaje -OutputInterface::VERBOSITY_NORMAL El nivel de verbosidad predeterminado -OutputInterface::VERBOSITY_VERBOSE Incrementa la verbosidd de los mensajes -================================== ======================================= - -Puedes especificar el nivel de verbosidad tranquilo con la opción ``--quiet`` o ``-q``. La opción ``--verbose`` o ``-v`` se utiliza cuando quieres aumentar el nivel de verbosidad. - -.. tip:: - - La traza completa de la excepción se imprime si utilizas el nivel de verbosidad ``VERBOSITY_VERBOSE``. - -Es posible imprimir un mensaje en una orden para únicamente un nivel de verbosidad -específico. Por ejemplo:: - - if (OutputInterface::VERBOSITY_VERBOSE === $output->getVerbosity()) { - $output->writeln(...); - } - -Cuándo se utiliza el nivel tranquilo, toda la producción es suprimida como la regresa el método predeterminado :method:`Symfony\Component\Console\Output::write` -sin de hecho imprimirla. - -Utilizando argumentos de ordenes --------------------------------- - -La parte más interesante de las ordenes son los argumentos y opciones que puedes hacer disponibles. Los argumentos son cadenas ---separadas por espacios--- que vienen después del nombre de la orden misma. Ellos están ordenados, y pueden ser opcionales u obligatorios. Por ejemplo, añade un argumento ``last_name`` opcional a la orden y haz que el argumento ``name`` sea obligatorio:: - - $this - // ... - ->addArgument( - 'name', - InputArgument::REQUIRED, - 'Who do you want to greet?' - ) - ->addArgument( - 'last_name', - InputArgument::OPTIONAL, - 'Your last name?' - ); - -Ahora tienes acceso a un argumento ``last_name`` en la orden:: - - if ($lastName = $input->getArgument('last_name')) { - $text .= ' '.$lastName; - } - -Ahora la orden se puede utilizar en cualquiera de las siguientes maneras: - -.. code-block:: bash - - $ app/console demo:greet Fabien - $ app/console demo:greet Fabien Potencier - -Usando las opciones de la orden -------------------------------- - -A diferencia de los argumentos, las opciones no están ordenadas (lo cual significa que las puedes especificar en cualquier orden) y se especifican con dos guiones (por ejemplo, ``--yell`` también puedes declarar un atajo de una letra que puedes invocar con un único guión como ``-y``). Las opciones son: *always* opcional, y se puede configurar para aceptar un valor (por ejemplo, ``dir=src``) o simplemente como una variable lógica sin valor (por ejemplo, ``yell``). - -.. tip:: - - También es posible hacer que un argumento *opcionalmente* acepte un valor (de modo que ``--yell`` o ``yell=loud`` funcione). Las opciones también se pueden configurar para aceptar un arreglo de valores. - -Por ejemplo, añadir una nueva opción a la orden que se puede usar para especificar cuántas veces se debe imprimir el mensaje en una fila:: - - $this - // ... - ->addOption( - 'iterations', - null, - InputOption::VALUE_REQUIRED, - 'How many times should the message be printed?', - 1 - ); - -A continuación, utilízalo en la orden para imprimir el mensaje varias veces: - -.. code-block:: php - - for ($i = 0; $i < $input->getOption('iterations'); $i++) { - $output->writeln($text); - } - -Ahora, al ejecutar la tarea, si lo deseas, puedes especificar un indicador ``--iterations``: - -.. code-block:: bash - - $ app/console demo:greet Fabien - $ app/console demo:greet Fabien --iterations=5 - -El primer ejemplo sólo se imprimirá una vez, ya que ``iterations`` está vacía y el predeterminado es ``1`` (el último argumento de ``addOption``). El segundo ejemplo se imprimirá cinco veces. - -Recordemos que a las opciones no les preocupa su orden. Por lo tanto, cualquiera de las siguientes trabajará: - -.. code-block:: bash - - $ app/console demo:greet Fabien --iterations=5 --yell - $ app/console demo:greet Fabien --yell --iterations=5 - -Hay 4 variantes de la opción que puedes utilizar: - -=========================== ================================================================================================== -Opción Valor -=========================== ================================================================================================== -InputOption::VALUE_IS_ARRAY Esta opción acepta múltiples valores (por ejemplo: ``--dir=/foo --dir=/bar``) -InputOption::VALUE_NONE No acepta entradas para esta opción (por ejemplo: ``--yell``) -InputOption::VALUE_REQUIRED Este valor es obligatorio (por ejemplo: ``--iterations=5``), la opción en sí misma aún es opcional -InputOption::VALUE_OPTIONAL Esta opción puede o no tener un valor (por ejemplo: ``yell`` o ``yell=loud``) -=========================== ================================================================================================== - -Puedes combinar el VALUE_IS_ARRAY con VALUE_REQUIRED o VALUE_OPTIONAL de la siguiente manera: - -.. code-block:: php - - $this - // ... - ->addOption( - 'iterations', - null, - InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, - 'How many times should the message be printed?', - 1 - ); - -Ayudantes de consola --------------------- - -El componente ``console`` también contiene un conjunto de «ayudantes» ---pequeñas herramientas capaces de ayudarte con diferentes tareas---: - -* :doc:`/components/console/helpers/dialoghelper`: Pregunta interactivamente determinada información al usuario -* :doc:`/components/console/helpers/formatterhelper`: Personaliza la colorización del resultado :doc:`/components/console/helpers/progresshelper`: Muestra una barra de progreso - -Probando ordenes ----------------- - -*Symfony2* proporciona varias herramientas para ayudarte a probar tus ordenes. La más útil es la clase :class:`Symfony\\Component\\Console\\Tester\\CommandTester`. Esta utiliza clases entrada y salida especiales para facilitar la prueba sin una consola real:: - - use Symfony\Component\Console\Application; - use Symfony\Component\Console\Tester\CommandTester; - use Acme\DemoBundle\Command\GreetCommand; - - class ListCommandTest extends \PHPUnit_Framework_TestCase - { - public function testExecute() - { - $application = new Application(); - $application->add(new GreetCommand()); - - $command = $application->find('demo:greet'); - $commandTester = new CommandTester($command); - $commandTester->execute(array('command' => $command->getName())); - - $this->assertRegExp('/.../', $commandTester->getDisplay()); - - // ... - } - } - -El método :method:`Symfony\\Component\\Console\\Tester\\CommandTester::getDisplay` devuelve lo que se ha exhibido durante una llamada normal de la consola. - -Puedes probar enviando argumentos y opciones a la orden pasándolos como un arreglo al método :method:`Symfony\\Component\\Console\\Tester\\CommandTester::execute`:: - - use Symfony\Component\Console\Application; - use Symfony\Component\Console\Tester\CommandTester; - use Acme\DemoBundle\Command\GreetCommand; - - class ListCommandTest extends \PHPUnit_Framework_TestCase - { - // ... - - public function testNameIsOutput() - { - $application = new Application(); - $application->add(new GreetCommand()); - - $command = $application->find('demo:greet'); - $commandTester = new CommandTester($command); - $commandTester->execute( - array('command' => $command->getName(), 'name' => 'Fabien') - ); - - $this->assertRegExp('/Fabien/', $commandTester->getDisplay()); - } - } - -.. tip:: - - También puedes probar toda una aplicación de consola utilizando :class:`Symfony\\Component\\Console\\Tester\\ApplicationTester`. - -Llamando una orden existente ----------------------------- - -Si una orden depende de que se ejecute otra antes, en lugar de obligar al usuario a recordar el orden de ejecución, la puedes llamar tú mismo directamente. -Esto también es útil si deseas crear una «metaorden» que ejecute un montón de otras ordenes (por ejemplo, todas las ordenes que se deben ejecutar cuando el código del proyecto ha cambiado en los servidores de producción: vaciar la caché, generar delegados *Doctrine2*, volcar activos ``Assetic``, ...). - -Llamar a una orden desde otra es sencillo:: - - protected function execute(InputInterface $input, OutputInterface $output) - { - $command = $this->getApplication()->find('demo:greet'); - - $arguments = array( - 'command' => 'demo:greet', - 'name' => 'Fabien', - '--yell' => true, - ); - - $input = new ArrayInput($arguments); - $returnCode = $command->run($input, $output); - - // ... - } - -En primer lugar, tu :method:`Symfony\\Component\\Console\\Application::find` busca la orden que deseas ejecutar pasando el nombre de la orden. - -Entonces, es necesario crear una nueva clase :class:`Symfony\\Component\\Console\\Input\\ArrayInput` con los argumentos y opciones que desees pasar a la orden. - -Finalmente, invocar al método ``run()`` ejecuta la orden realmente y devuelve el código regresado por la orden (el valor del método ``execute()``). - -.. note:: - - La mayor parte del tiempo, llamar a una orden desde código que no se ejecuta en la línea de ordenes no es una buena idea por varias razones. En primer lugar, la salida de la orden se ha optimizado para la consola. Pero lo más importante, puedes pensar de una orden como si fuera un controlador; este debe utilizar el modelo para hacer algo y mostrar algún comentario al usuario. Así, en lugar de llamar una orden desde la *Web*, reconstruye tu código y mueve la lógica a una nueva clase. - -¡Aprende más! ------------------ - -* :doc:`/components/console/usage` -* :doc:`/components/console/single_command_tool` - -.. _`Packagist`: https://packagist.org/packages/symfony/console -.. _ANSICON: http://adoxa.3eeweb.com/ansicon/ diff --git a/_sources/components/console/single_command_tool.txt b/_sources/components/console/single_command_tool.txt deleted file mode 100644 index 54af899..0000000 --- a/_sources/components/console/single_command_tool.txt +++ /dev/null @@ -1,73 +0,0 @@ -.. index:: - single: Console; Aplicación de una sola orden - -Construyendo una aplicación de una sola orden -============================================= - -Cuándo construyes una herramienta para la línea de ordenes, posiblemente no necesites proporcionar varias órdenes. -En tal caso, tener que pasar el nombre de orden cada vez es tedioso. Afortunadamente, es posible evadir esta necesidad extendiendo la ``application``:: - - namespace Acme\Tool; - - use Symfony\Component\Console\Application; - use Symfony\Component\Console\Input\InputInterface; - - class MyApplication extends Application - { - /** - * Obtiene el nombre de la orden basándose en la entrada. - * - * @param InputInterface $input The input interface - * - * @return string The command name - */ - protected function getCommandName(InputInterface $input) - { - // Esto debería regresar el nombre de tu orden. - return 'my_command'; - } - - /** - * Obtiene las ordenes predefinidas que siempre tendrían que estar disponibles. - * - * @return array An array of default Command instances - */ - protected function getDefaultCommands() - { - // mantiene las ordenes predefinidas del núcleo - // para que tengan el HelpCommand cuál es utilizado cuándo - // se usa la opción --help - $defaultCommands = parent::getDefaultCommands(); - - $defaultCommands[] = new MyCommand(); - - return $defaultCommands; - } - - /** - * Lo sustituye para que la aplicación no espere el nombre - * de la orden como el primer argumento. - */ - public function getDefinition() - { - $inputDefinition = parent::getDefinition(); - // limpia el primer argumento de la salida normal, que es el nombre de la orden - $inputDefinition->setArguments(); - - return $inputDefinition; - } - } - -Cuándo llames a tu guión de consola, entonces siempre se utilizará la orden ``MyCommand``, sin tener que pasar su nombre. - -También puedes simplificar cómo ejecutas la aplicación:: - - #!/usr/bin/env php - run(); - diff --git a/_sources/components/console/usage.txt b/_sources/components/console/usage.txt deleted file mode 100644 index 647c61f..0000000 --- a/_sources/components/console/usage.txt +++ /dev/null @@ -1,131 +0,0 @@ -.. index:: - single: Console; Usando - -Usando órdenes de consola, atajos y órdenes integradas -====================================================== - -Además de las opciones que especifiques para tus órdenes, hay algunas opciones integradas así como un par de órdenes para el componente ``console``. - -.. note:: - - Estos ejemplos suponen que añadiste un archivo :file:`app/console` para ejecutarlo en la interfaz de la línea de ordenes:: - - #!/usr/bin/env php - # app/console - run(); - -Ordenes integradas -~~~~~~~~~~~~~~~~~~ - -Hay una orden ``list`` integrada que produce todas las opciones y ordenes estándar registradas: - -.. code-block:: bash - - $ php app/console list - -También puedes obtener el mismo resultado sin especificar ninguna orden: - -.. code-block:: bash - - $ php app/console - -La orden ``help`` produce toda la información de ayuda para la orden especificada. Por ejemplo, para conseguir ayuda para la orden ``list``: - -.. code-block:: bash - - $ php app/console help list - -Al ejecutar ``help`` sin especificar una orden enumerará las opciones globales: - -.. code-block:: bash - - $ php app/console help - -Opciones globales -~~~~~~~~~~~~~~~~~ - -Puedes conseguir información de ayuda para cualquier orden con la opción ``--help``. Para conseguir ayuda para la orden ``list``: - -.. code-block:: bash - - $ php app/console list --help - $ php app/console list -h - -Puedes suprimir el resultado con: - -.. code-block:: bash - - $ php app/console list --quiet - $ php app/console list -q - -Puedes conseguir mensajes más detallados (si la orden cuenta con tal información) con: - -.. code-block:: bash - - $ php app/console list --verbose - $ php app/console list -v - -Si pusiste los argumentos opcionales para dar a tu aplicación un nombre y versión:: - - $application = new Application('Acme Console Application', '1.2'); - -Entonces puedes usar: - -.. code-block:: bash - - $ php app/console list --version - $ php app/console list -V - -para lograr mostrar esta información: - -.. code-block:: text - - Acme Console Application version 1.2 - -Si no proporcionaste ambos argumentos entonces solo producirá: - -.. code-block:: text - - console tool - -Puedes forzar el coloreado *ANSI* del resultado con: - -.. code-block:: bash - - $ php app/console list --ansi - -O apagarlo con: - -.. code-block:: bash - - $ php app/console list --no-ansi - -Puedes suprimir cualquier pregunta interactiva de la orden que estés ejecutando con: - -.. code-block:: bash - - $ php app/console list --no-interaction - $ php app/console list -n - -Sintaxis de atajos -~~~~~~~~~~~~~~~~~~ - -No tienes que escribir los nombres de las ordenes completos. Sólo escribe el nombre unívoco más corto para ejecutar una orden. Por lo tanto si las ordenes no chocan, entonces puedes ejecutar ``help`` así: - -.. code-block:: bash - - $ php app/console h - -Si tienes ordenes que utilizan ``:`` para un espacio de nombres entonces solo tienes que escribir el texto unívoco más corto para cada parte. Si has creado el ejemplo ``demo:greet`` como muestra :doc:`/components/console/introduction`, entonces lo puedes ejecutar con: - -.. code-block:: bash - - $ php app/console d:g Fabien - -Si escoges una orden demasiado corta y por tanto ambigua (es decir, que más de una orden coincide), entonces ninguna orden será ejecutada y mostrará algunas sugerencias con las posibles ordenes que puedes elegir. diff --git a/_sources/components/css_selector.txt b/_sources/components/css_selector.txt deleted file mode 100644 index 6e8dc45..0000000 --- a/_sources/components/css_selector.txt +++ /dev/null @@ -1,75 +0,0 @@ -.. index:: - single: Selector CSS - single: Componentes; CssSelector - -El componente ``CssSelector`` -============================= - - El componente ``CssSelector`` convierte selectores *CSS* a expresiones ``XPath``. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/CssSelector); -* :doc:`Instalándolo vía via Composer ` (``symfony/css-selector`` en `Packagist`_). - -Usando ------- - -¿Por qué usar selectores *CSS*? -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando estás analizando un archivo *HTML* o un documento *XML*, con mucho, el método más poderoso -es *XPath*. - -Las expresiones *XPath* son increíblemente flexibles, por lo tanto casi siempre hay una expresión *XPath* que encuentra el elemento que necesitas. Desafortunadamente, también puede llegar a ser muy complicado, y la curva de aprendizaje es muy empinada. Incluso operaciones comunes (por ejemplo, la búsqueda de un elemento con una clase en particular) puede requerir expresiones largas y difíciles de manejar. - -Muchos desarrolladores ---particularmente los desarrolladores web--- se sienten más cómodos usando selectores *CSS* para encontrar elementos. Además de trabajar en hojas de estilo, los selectores *CSS* se utilizan en *Javascript* con la función ``querySelectorAll`` y en las bibliotecas populares de *Javascript* como *jQuery*, *Prototype* y *MooTools*. - -Los selectores *CSS* son menos poderosos que *XPath*, pero mucho más fáciles de escribir, leer -y entender. Debido a que son menos poderosos, casi todos los selectores *CSS* se pueden convertir a una expresión *XPath* equivalente. Entonces, puedes utilizar esta expresión *XPath* con otras funciones y clases que utilizan *XPath* para buscar elementos en un documento. - -El componente ``CssSelector`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El único objetivo del componente es el de convertir selectores *CSS* a su *XPath* -equivalente:: - - use Symfony\Component\CssSelector\CssSelector; - - print CssSelector::toXPath('div.item > h4 > a'); - -Esto produce el siguiente resultado: - -.. code-block:: text - - descendant-or-self::div[contains(concat(' ',normalize-space(@class), ' '), ' item ')]/h4/a - -Puedes utilizar esta expresión con, por ejemplo, :phpclass:`DOMXPath` o :phpclass:`SimpleXMLElement` para encontrar elementos en un documento. - -.. tip:: - - El método :method:`Crawler::filter()` utiliza el componente ``CssSelector`` para encontrar elementos basándose en una cadena selectora *CSS*. Ve :doc:`/components/dom_crawler` para más detalles. - -Limitaciones del componente ``cssSelector`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -No todos los selectores *CSS* se pueden convertir a *XPath* equivalentes. - -Hay varios selectores *CSS* que sólo tienen sentido en el contexto de un navegador *web*. - -* estado de selectores de enlace: ``:link``, ``:visited``, ``:target`` -* selectores basados en la acción del usuario: ``:hover``, ``:focus``, ``:active`` -* Estado de selectores de la interfaz del usuario: ``:enabled``, ``:disabled``, ``:indeterminate`` (``however``, ``:checked`` y ``:unchecked`` están disponibles) - -Seudoelementos (``:before``, ``:after``, ``:first-line``, ``:first-letter``) no son compatibles, debido a que seleccionan porciones de texto en lugar de elementos. - -Algunas seudoclases todavía no cuentan con soporte: - -* ``:lang(language)`` -* ``root`` -* ``*:first-of-type``, ``*:last-of-type``, ``*:nth-of-type``, ``*:nth-last-of-type``, ``*:only-of-type``. (Estas funcionan con un elemento nombre (por ejemplo ``li:first-of-type``), pero no con ``*``. - -.. _`Packagist`: https://packagist.org/packages/symfony/css-selector \ No newline at end of file diff --git a/_sources/components/debug.txt b/_sources/components/debug.txt deleted file mode 100644 index 48911d3..0000000 --- a/_sources/components/debug.txt +++ /dev/null @@ -1,74 +0,0 @@ -.. index:: - single: Depuración - single: Componentes; Debug - -El componente ``Debug`` -======================= - - El componente ``Debug`` proporciona herramientas para facilitar la depuración del código PHP. - -.. versionadded:: 2.3 - El componente ``Debug`` es nuevo en *Symfony 2.3*. Anteriormente, las clases se localizaban en el componente ``HttpKernel``. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Debug); -* :doc:`Instalándolo vía Composer ` (``symfony/debug`` en `Packagist`_). - -Usando ------- - -The Debug component provides several tools to help you debug PHP code. -Enabling them all is as easy as it can get:: - - use Symfony\Component\Debug\Debug; - - Debug::enable(); - -The :method:`Symfony\\Component\\Debug\\Debug::enable` method registers an -error handler and an exception handler. If the :doc:`ClassLoader component -` is available, a special class loader is also -registered. - -Read the following sections for more information about the different available -tools. - -.. caution:: - - You should never enable the debug tools in a production environment as - they might disclose sensitive information to the user. - -Enabling the Error Handler --------------------------- - -The :class:`Symfony\\Component\\Debug\\ErrorHandler` class catches PHP errors -and converts them to exceptions (of class :phpclass:`ErrorException` or -:class:`Symfony\\Component\\Debug\\Exception\\FatalErrorException` for PHP -fatal errors):: - - use Symfony\Component\Debug\ErrorHandler; - - ErrorHandler::register(); - -Enabling the Exception Handler ------------------------------- - -The :class:`Symfony\\Component\\Debug\\ExceptionHandler` class catches -uncaught PHP exceptions and converts them to a nice PHP response. It is useful -in debug mode to replace the default PHP/XDebug output with something prettier -and more useful:: - - use Symfony\Component\Debug\ExceptionHandler; - - ExceptionHandler::register(); - -.. note:: - - If the :doc:`HttpFoundation component ` is - available, the handler uses a Symfony Response object; if not, it falls - back to a regular PHP response. - -.. _`Packagist`: https://packagist.org/packages/symfony/debug diff --git a/_sources/components/dependency_injection.txt b/_sources/components/dependency_injection.txt deleted file mode 100644 index 07b6465..0000000 --- a/_sources/components/dependency_injection.txt +++ /dev/null @@ -1,268 +0,0 @@ -.. index:: - single: Inyección de dependencias - -El componente ``Inyección de dependencias`` -=========================================== - - El componente ``Inyección de dependencias``, te permite estandarizar y centralizar la forma en que se construyen los objetos en tu aplicación. - -Para una introducción general a los contenedores de inyección de dependencias y servicios consulta el capítulo :doc:`/book/service_container` del libro. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/DependencyInjection); -* Instalándolo a través de *PEAR* ( `pear.symfony.com/DependencyInjection`); -* Instalándolo vía ``Composer`` (`symfony/dependency-injection` en Packagist). - -Uso básico ----------- - -Tal vez tengas una simple clase ``Mailer`` como la siguiente, la cual quieres hacer disponible como un servicio: - -.. code-block:: php - - class Mailer - { - private $transport; - - public function __construct() - { - $this->transport = 'sendmail'; - } - - // ... - } - -La puedes registrar en el contenedor como un servicio: - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $sc = new ContainerBuilder(); - $sc->register('mailer', 'Mailer'); - -Una mejora a la clase para hacerla más flexible sería permitir que el contenedor establezca el ``transporte`` utilizado. Si cambias la clase para que este sea pasado al constructor: - -.. code-block:: php - - class Mailer - { - private $transport; - - public function __construct($transport) - { - $this->transport = $transport; - } - - // ... - } - -Entonces, puedes configurar la opción de transporte en el contenedor: - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $sc = new ContainerBuilder(); - $sc->register('mailer', 'Mailer') - ->addArgument('sendmail')); - -Esta clase ahora es mucho más flexible puesto que hemos separado la elección del transporte fuera de la implementación y en el contenedor. - -Qué transporte de correo has elegido, puede ser algo sobre lo cual los demás servicios necesitan saber. Puedes evitar tener que cambiarlo en varios lugares volviéndolo un parámetro en el contenedor y luego refiriendo este parámetro como argumento para el constructor del servicio ``Mailer``: - - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $sc = new ContainerBuilder(); - $sc->setParameter('mailer.transport', 'sendmail'); - $sc->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%')); - -Ahora que el servicio ``mailer`` está en el contenedor lo puedes inyectar como una dependencia de otras clases. Si tienes una clase ``NewsletterManager`` como esta: - -.. code-block:: php - - use Mailer; - - class NewsletterManager - { - private $mailer; - - public function __construct(Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -Entonces, también la puedes registrar como un servicio y pasarla a servicio ``mailer``: - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Reference; - - $sc = new ContainerBuilder(); - - $sc->setParameter('mailer.transport', 'sendmail'); - $sc->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%')); - - $sc->register('newsletter_manager', 'NewsletterManager') - ->addArgument(new Reference('mailer')); - -Si el ``NewsletterManager`` no requiere el ``Mailer`` y la inyección sólo era opcional, entonces, puedes utilizar el método definidor para inyectarlo en su lugar: - -.. code-block:: php - - use Mailer; - - class NewsletterManager - { - private $mailer; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -Ahora puedes optar por no inyectar un ``Mailer`` en el ``NewsletterManager``. -Si quieres, aunque entonces el contenedor puede llamar al método definidor: - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Reference; - - $sc = new ContainerBuilder(); - - $sc->setParameter('mailer.transport', 'sendmail'); - $sc->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%')); - - $sc->register('newsletter_manager', 'NewsletterManager') - ->addMethodCall('setMailer', new Reference('mailer')); - -A continuación, puedes obtener tu servicio ``newsletter_manager`` desde el contenedor de esta manera: - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Reference; - - $sc = new ContainerBuilder(); - - //-- - - $newsletterManager = $sc->get('newsletter_manager'); - -Evitando que tu código sea dependiente en el contenedor -------------------------------------------------------- - -Si bien puedes recuperar los servicios directamente desde el contenedor, lo mejor es minimizar esto. Por ejemplo, en el ``NewsletterManager`` inyectamos el servicio ``Mailer`` en vez de solicitarlo desde el contenedor. -Podríamos haber inyectado el contenedor y recuperar el servicio ``Mailer`` desde ahí, pero entonces, estaría vinculado a este contenedor particular, lo cual dificulta la reutilización de la clase en otro lugar. - -Tendrás que conseguir un servicio desde el contenedor en algún momento, pero esto debe ser tan pocas veces como sea posible en el punto de entrada a tu aplicación. - -Configurando el contenedor con archivos de configuración --------------------------------------------------------- - -Así como la creación de los servicios utilizando *PHP* ---como arriba--- también puedes utilizar archivos de configuración. Para ello además necesitas instalar el componente ``Config``: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Config); -* Instalándolo a través de *PEAR* ( `pear.symfony.com/Config`); -* Instalándolo vía ``Composer`` (`symfony/config` en Packagist). - -Cargando un archivo de configuración *XML*: - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\Config\FileLocator; - use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; - - $sc = new ContainerBuilder(); - $loader = new XmlFileLoader($sc, new FileLocator(__DIR__)); - $loader->load('services.xml'); - -Cargando un archivo de configuración *YAML*: - -.. code-block:: php - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\Config\FileLocator; - use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; - - $sc = new ContainerBuilder(); - $loader = new YamlFileLoader($sc, new FileLocator(__DIR__)); - $loader->load('services.yml'); - -Puedes configurar los servicios ``newsletter_manager`` y ``mailer`` usando archivos de configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/HelloBundle/Resources/config/services.yml - parameters: - # ... - mailer.transport: sendmail - - services: - my_mailer: - class: Mailer - arguments: [@mailer] - newsletter_manager: - class: NewsletterManager - calls: - - [ setMailer, [ @mailer ] ] - - .. code-block:: xml - - - - - sendmail - - - - - %mailer.transport% - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Reference; - - // ... - $sc->setParameter('mailer.transport', 'sendmail'); - $sc->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%')); - - $sc->register('newsletter_manager', 'NewsletterManager') - ->addMethodCall('setMailer', new Reference('mailer')); - - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/service_container/factories` -* :doc:`/cookbook/service_container/parentservices` diff --git a/_sources/components/dependency_injection/advanced.txt b/_sources/components/dependency_injection/advanced.txt deleted file mode 100644 index 9aeb9b1..0000000 --- a/_sources/components/dependency_injection/advanced.txt +++ /dev/null @@ -1,111 +0,0 @@ -.. index:: - single: Inyección de dependencias; Configuración avanzada - -Configuración avanzada del contenedor -===================================== - -Marcando servicios como públicos/privados ------------------------------------------ - -Cuando definas servicios, generalmente, querrás poder acceder a estas definiciones dentro del código de tu aplicación. Estos servicios se llaman ``public``. Por ejemplo, el servicio ``doctrine`` registrado en el contenedor cuando se utiliza ``DoctrineBundle`` es un servicio público al que puedes acceder a través de:: - - $doctrine = $container->get('doctrine'); - -Sin embargo, hay casos de uso cuando no quieres que un servicio sea público. Esto es común cuando sólo se define un servicio, ya que se podría utilizar como argumento para otro servicio. - -.. note:: - - Si utilizas un servicio privado como único argumento para otro servicio, resultará en la creación de ejemplares (por ejemplo. ``new PrivateFooBar()``) dentro de este otro servicio, haciéndolo públicamente inutilizable en tiempo de ejecución. - -Simplemente dice: El servicio será privado cuando no deseas acceder a él directamente desde tu código. - -Aquí está un ejemplo: - -.. configuration-block:: - - .. code-block:: yaml - - services: - foo: - class: Example\Foo - public: false - - .. code-block:: xml - - - - .. code-block:: php - - $definition = new Definition('Example\Foo'); - $definition->setPublic(false); - $container->setDefinition('foo', $definition); - -Ahora que el servicio es privado, *no* puedes llamar a:: - - $container->get('foo'); - -Sin embargo, si has marcado un servicio como privado, todavía puedes asignarle un alias (ve más abajo) para acceder a este servicio (a través del alias). - -.. note:: - - De manera predeterminada los servicios son públicos. - -Apodando --------- - -A veces posiblemente quieras usar atajos para acceder a algunos servicios. Lo puedes hacer por su alias y, además, incluso puedes apodar servicios no públicos. - -.. configuration-block:: - - .. code-block:: yaml - - services: - foo: - class: Example\Foo - bar: - alias: foo - - .. code-block:: xml - - - - - - .. code-block:: php - - $definition = new Definition('Example\Foo'); - $container->setDefinition('foo', $definition); - - $containerBuilder->setAlias('bar', 'foo'); - -Esto significa que cuando utilizas el contenedor directamente, puedes acceder al servicio ``foo`` al pedir el servicio ``bar`` así:: - - $container->get('bar'); // debería devolver el servicio foo - -Requiriendo archivos --------------------- - -Puede haber casos de uso cuando necesites incluir otro archivo justo antes de cargar el servicio en sí. Para ello, puedes utilizar la directiva ``file``. - -.. configuration-block:: - - .. code-block:: yaml - - services: - foo: - class: Example\Foo\Bar - file: "%kernel.root_dir%/src/ruta/al/archivo/foo.php" - - .. code-block:: xml - - - %kernel.root_dir%/src/ruta/al/archivo/foo.php - - - .. code-block:: php - - $definition = new Definition('Example\Foo\Bar'); - $definition->setFile('%kernel.root_dir%/src/ruta/al/archivo/foo.php'); - $container->setDefinition('foo', $definition); - -Ten en cuenta que internamente *Symfony* llama a la función *PHP* ``require_once``, lo cual significa que el archivo se incluirá una sola vez por petición. diff --git a/_sources/components/dependency_injection/compilation.txt b/_sources/components/dependency_injection/compilation.txt deleted file mode 100644 index 7ce902f..0000000 --- a/_sources/components/dependency_injection/compilation.txt +++ /dev/null @@ -1,416 +0,0 @@ -.. index:: - single: Inyección de dependencias; Compilación - -Compilando el contenedor -======================== - -El contenedor de servicios se puede compilar por varios motivos. Estas razones incluyen la comprobación de posibles problemas, tal como referencias circulares y volver más eficiente al contenedor usando la resolución de parámetros y remoción de servicios no utilizados. - -Se compila ejecutando:: - - $container->compile(); - -El método de compilación utiliza «\ ``Compiler Passes``\ » para la compilación. El componente *Inyección de dependencias* viene con varios pases que se registran automáticamente para la compilación. Por ejemplo, la clase :class:`Symfony\\Component\\DependencyInjection\\Compiler\\CheckDefinitionValidityPass` comprueba varios potenciales problemas relacionados con las definiciones que se han establecido en el contenedor. Después de este y varios otros pases que comprueban la validez del contenedor, se utilizan pases adicionales del compilador para optimizar la configuración antes de almacenarla en caché. Por ejemplo, se quitan los servicios privados y abstractos, y se resuelven los alias. - -.. _components-dependency-injection-extension: - -Gestionando la configuración con extensiones --------------------------------------------- - -Así como cargar la configuración directamente en el contenedor como se muestra en :doc:`/components/dependency_injection/introduction`, la puedes gestionar registrando extensiones en el contenedor. El primer paso en el proceso de compilación es cargar en el contenedor la configuración de cualquier clase registrada en la extensión. A diferencia de la configuración cargada directamente, esta sólo se procesa cuándo el contenedor es compilado. Si tu aplicación es modular entonces las extensiones dejan que cada módulo registre y gestione su propio servicio de configuración. - -Las extensiones deben implementar la :class:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface` y las puedes registrar en el contenedor con:: - - $container->registerExtension($extension); - -El trabajo principal de la extensión se realiza en el método ``load``. En el método ``load`` puedes cargar la configuración desde uno o más archivos de configuración, así como manipular las definiciones del contenedor utilizando los métodos indicados en :doc:`/components/dependency_injection/definitions`. - -Al método ``load`` se le pasa un nuevo contenedor para configurarlo, el cual posteriormente se fusiona en el contenedor con el que se haya registrado. Esto te permite tener varias extensiones para gestionar las definiciones del contenedor de forma independiente. -Las extensiones no agregan configuración a los contenedores cuando se añaden, pero se procesan cuando se llama al método ``compile`` del contenedor. - -Una muy sencilla extensión puede justo cargar al contenedor archivos de configuración:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; - use Symfony\Component\DependencyInjection\Extension\ExtensionInterface; - use Symfony\Component\Config\FileLocator; - - class AcmeDemoExtension implements ExtensionInterface - { - public function load(array $configs, ContainerBuilder $container) - { - $loader = new XmlFileLoader( - $container, - new FileLocator(__DIR__.'/../Resources/config') - ); - $loader->load('services.xml'); - } - - // ... - } - -Esto no mejora mucho comparado a cargar el archivo directamente al construir el contenedor global. Justo deja los archivos para ser divididos entre módulos/paquetes. Para poder afectar la configuración de un módulo desde archivos de configuración externos al módulo/paquete es necesario hacer configurable una aplicación compleja. Esto se puede hacer especificando se carguen secciones de archivos de configuración directamente al contenedor cuando son para una extensión en particular. Estas secciones en la configuración no serán procesadas directamente por el contenedor sino por la extensión pertinente. - -La extensión debe especificar un método ``getAlias`` para implementar la interfaz:: - - // ... - - class AcmeDemoExtension implements ExtensionInterface - { - // ... - - public function getAlias() - { - return 'acme_demo'; - } - } - -Para archivos de configuración *YAML* especificar el alias para la extensión como clave significará que aquellos valores se pasan al método ``load`` de la extensión: - -.. code-block:: yaml - - # ... - acme_demo: - foo: fooValue - bar: barValue - -Si este archivo se carga en la configuración entonces los valores en él sólo son -procesados cuándo el contenedor es compilado en el punto que se cargan las extensiones:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\Config\FileLocator; - use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; - - $container = new ContainerBuilder(); - $container->registerExtension(new AcmeDemoExtension); - - $loader = new YamlFileLoader($container, new FileLocator(__DIR__)); - $loader->load('config.yml'); - - // ... - $container->compile(); - -.. note:: - - Al cargar un archivo de configuración que usa un alias de extensión como clave, la extensión ya se debe haber registrado en el constructor del contenedor o se lanzará una excepción. - -Los valores de esas secciones de los archivos de configuración se pasan en el primer -argumento del método ``load`` de la extensión:: - - public function load(array $configs, ContainerBuilder $container) - { - $foo = $configs[0]['foo']; //fooValue - $bar = $configs[0]['bar']; //barValue - } - -El argumento ``$configs`` es un arreglo conteniendo cada diferente archivo de configuración que se carga en el contenedor. Sólo estás cargando un único archivo de configuración en el ejemplo anterior pero todavía será en un arreglo. El arreglo se verá como este:: - - array( - array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - ), - ) - -Si bien puedes gestionar manualmente la fusión de diferentes archivos, es mucho mejor -utilizar el :doc:`componente Config ` para combinar -y validar los valores de configuración. Usando el procesamiento de configuración podrías acceder a los valores de configuración de la siguiente manera:: - - use Symfony\Component\Config\Definition\Processor; - // ... - - public function load(array $configs, ContainerBuilder $container) - { - $configuration = new Configuration(); - $processor = new Processor(); - $config = $processor->processConfiguration($configuration, $configs); - - $foo = $config['foo']; //fooValue - $bar = $config['bar']; //barValue - - // ... - } - -Aun faltan dos métodos que debes implementar. Uno para regresar el espacio de nombres *XML* de modo que las partes pertinentes de un archivo de configuración *XML* es pasado a la extensión. El otro para especificar la ruta a los archivos *XSD* base para validar la configuración *XML*:: - - public function getXsdValidationBasePath() - { - return __DIR__.'/../Resources/config/'; - } - - public function getNamespace() - { - return 'http://www.ejemplo.com/symfony/schema/'; - } - -.. note:: - - La validación *XSD* es opcional, regresando ``false`` desde el método ``getXsdValidationBasePath`` lo inhabilitará. - -La versión *XML* de la configuración entonces se parecería a esta: - -.. code-block:: xml - - - - - - fooValue - barValue - - - - -.. note:: - - En la plataforma *Symfony2* completa hay una clase ``Extensión`` base que implementa estos métodos así como un acceso directo al método para procesar la configuración. Consulta :doc:`/cookbook/bundles/extension` para más detalles. - -El valor de configuración procesado ahora se puede añadir como parámetro del contenedor como si estuviera enumerado en una sección ``parameters`` del archivo de configuración pero con el beneficio adicional de fusionar y validar múltiples archivos de configuración:: - - public function load(array $configs, ContainerBuilder $container) - { - $configuration = new Configuration(); - $processor = new Processor(); - $config = $processor->processConfiguration($configuration, $configs); - - $container->setParameter('acme_demo.FOO', $config['foo']); - - // ... - } - -Puedes proveer requisitos de configuración más complejos para las clases en la extensión. Por ejemplo, puedes elegir cargar un archivo de configuración de un servicio principal pero también cargar uno secundario sólo si se ajusta un determinado parámetro:: - - public function load(array $configs, ContainerBuilder $container) - { - $configuration = new Configuration(); - $processor = new Processor(); - $config = $processor->processConfiguration($configuration, $configs); - - $loader = new XmlFileLoader( - $container, - new FileLocator(__DIR__.'/../Resources/config') - ); - $loader->load('services.xml'); - - if ($config['advanced']) { - $loader->load('advanced.xml'); - } - } - -.. note:: - - El sólo hecho de registrar una extensión en el contenedor no es suficiente para lograr que se incluya en las extensiones procesadas al compilar el contenedor. - Al cargar la configuración que utiliza el alias de la extensión como clave como en los ejemplos anteriores te aseguras de que estas estensiones sean cargadas. También puedes instruir al constructor del contenedor para que las cargue con su método :method:`Symfony\\Component\\DependencyInjection\\ContainerBuilder::loadFromExtension`:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $container = new ContainerBuilder(); - $extension = new AcmeDemoExtension(); - $container->registerExtension($extension); - $container->loadFromExtension($extension->getAlias()); - $container->compile(); - - -.. note:: - - Si necesitas manipular la configuración cargada por una extensión, entonces no lo puedes hacer desde otra extensión, ya que esta utiliza un contenedor nuevo. - En su lugar, debes utilizar un pase del compilador que trabaje con el contenedor completo después de haber procesado las extensiones. - -.. _components-dependency-injection-compiler-passes: - -Añadiendo al principio la configuración pasada a la extensión -------------------------------------------------------------- - -.. versionadded:: 2.2 - La habilidad para añadir al principio la configuración de un paquete es nueva en *Symfony 2.2*. - -Una Extensión puede prefijar la configuración de cualquier paquete antes del método ``load()`` llamada al implementar la :class:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface`:: - - use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; - // ... - - class AcmeDemoExtension implements ExtensionInterface, PrependExtensionInterface - { - // ... - - public function prepend() - { - // ... - - $container->prependExtensionConfig($name, $config); - - // ... - } - } - -Para más detalles, ve :doc:`/cookbook/bundles/prepend_extension`, la cual es específica a la plataforma *Symfony2*, pero contiene más detalles sobre esta característica. - -Creando un pase del compilador ------------------------------- - -También puedes crear y registrar tu propio pase del compilador en el contenedor. -Para crear un pase del compilador tienes que implementar la interfaz :class:`Symfony\\Component\\DependencyInjection\\Compiler\\CompilerPassInterface`. El compilador te brinda la oportunidad de manipular las definiciones de los servicios que se han compilado. Esto puede ser muy poderoso, pero no es algo necesario en el uso cotidiano. - -El pase del compilador debe tener el método ``process`` que se pasa al contenedor compilado:: - - use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; - use Symfony\Component\DependencyInjection\ContainerBuilder; - - class CustomCompilerPass implements CompilerPassInterface - { - public function process(ContainerBuilder $container) - { - // ... - } - } - -Los parámetros del contenedor y las definiciones se pueden manipular usando los métodos descritos en :doc:`/components/dependency_injection/definitions`. -Una cosa común por hacer en un pase del compilador es buscar todos los servicios que tienen una determinada etiqueta, a fin de procesarla de alguna manera o dinámicamente conectar cada una con algún otro servicio. - -Registrando un pase del compilador ----------------------------------- - -Necesitas registrar tu pase personalizado del compilador en el contenedor. El método ``process`` será llamado al compilar el contenedor:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $container = new ContainerBuilder(); - $container->addCompilerPass(new CustomCompilerPass); - -.. note:: - - Los pases del compilador son registrados de manera diferente si estás usando la pila completa de la plataforma, ve :doc:`/cookbook/service_container/compiler_passes` para más detalles. - -Controlando el orden de los pases -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Los pases predeterminados del compilador se agrupan en pases de optimización y pases de remoción. Los pases de optimización se ejecutan primero e incluyen tareas como la resolución de referencias con las definiciones. Los pases de remoción realizan tareas tales como la eliminación de alias privados y servicios no utilizados. Puedes elegir en qué orden se ejecutará cualquier pase personalizado que añadas. De manera predeterminada, se ejecutará antes de los pases de optimización. - -Puedes utilizar las siguientes constantes como segundo argumento al registrar un pase en el contenedor para controlar en qué orden va: - -* ``PassConfig::TYPE_BEFORE_OPTIMIZATION`` -* ``PassConfig::TYPE_OPTIMIZE`` -* ``PassConfig::TYPE_BEFORE_REMOVING`` -* ``PassConfig::TYPE_REMOVE`` -* ``PassConfig::TYPE_AFTER_REMOVING`` - -Por ejemplo, para correr tu pase personalizado después de quitar el pase predeterminado tienes que ejecutar:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Compiler\PassConfig; - - $container = new ContainerBuilder(); - $container->addCompilerPass( - new CustomCompilerPass, - PassConfig::TYPE_AFTER_REMOVING - ); - -.. _components-dependency-injection-dumping: - -Vertiendo la configuración para mejorar el rendimiento ------------------------------------------------------- - -Puede ser mucho más fácil entender el uso de los archivos de configuración para gestionar el contenedor de servicios que usar *PHP* una vez que hay una gran cantidad de servicios. Esta facilidad tiene un costo, aunque cuando se trata de rendimiento, puesto que los archivos de configuración se tienen que analizar y la configuración de *PHP* construida desde ellos. El proceso de compilación hace más eficiente al contenedor pero toma tiempo su ejecución. Puedes tener lo mejor de ambos mundos aunque usando archivos de configuración y, luego vertiendo y almacenando en caché la configuración resultante. El ``PhpDumper`` fácilmente vierte el contenedor compilado:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Dumper\PhpDumper; - - $file = __DIR__ .'/cache/container.php'; - - if (file_exists($file)) { - require_once $file; - $container = new ProjectServiceContainer(); - } else { - $container = new ContainerBuilder(); - // ... - $container->compile(); - - $dumper = new PhpDumper($container); - file_put_contents($file, $dumper->dump()); - } - -``ProjectServiceContainer`` es el nombre predeterminado aplicado a la clase vertida en el contenedor, aun así, lo puedes cambiar con la opción ``class`` cuándo lo viertes:: - - // ... - $file = __DIR__ .'/cache/container.php'; - - if (file_exists($file)) { - require_once $file; - $container = new MyCachedContainer(); - } else { - $container = new ContainerBuilder(); - // ... - $container->compile(); - - $dumper = new PhpDumper($container); - file_put_contents( - $file, - $dumper->dump(array('class' => 'MyCachedContainer')) - ); - } - -Ahora obtendrás la velocidad del contenedor *PHP* configurado con ---los fáciles de usar--- archivos de configuración. Además verter el contenedor de este modo optimiza aún más la manera en que son creados los servicios por el contenedor. - -En el ejemplo anterior tendrás que borrar los archivos del contenedor memorizados en caché cada vez que hagas algún cambio. Añadir una comprobación por una variable que determina si estás en modo de depuración te permite mantener la velocidad del contenedor memorizado en caché en producción, sino consiguiendo una actualización de la configuración, mientras desarrollas tu aplicación:: - - // ... - - // basándose en algo de tu proyecto - $isDebug = ...; - - $file = __DIR__ .'/cache/container.php'; - - if (!$isDebug && file_exists($file)) { - require_once $file; - $container = new MyCachedContainer(); - } else { - $container = new ContainerBuilder(); - // ... - $container->compile(); - - if (!$isDebug) { - $dumper = new PhpDumper($container); - file_put_contents( - $file, - $dumper->dump(array('class' => 'MyCachedContainer')) - ); - } - } - -Esto se podría mejorar aún más únicamente recompilando el contenedor en modo de depuración cuándo se han hecho cambios a su configuración en lugar de en cada petición. Esto se puede hacer memorizando en caché los archivos de recurso usados para configurar el contenedor de la manera descrita en «:doc:`/components/config/caching`» en la documentación de configuración del componente. - -No necesitas preocuparte de cuál archivo memorizar en caché puesto que el constructor del contenedor mantiene la pista de todos los recursos utilizados para configurarlo, no solo los archivos de configuración sino las clases de extensión y pases del compilador también. Esto significa que cualquier cambio a cualquiera de estos archivos invalidará la caché y provocará la reconstrucción del contenedor. Sólo necesitas preguntar el contenedor por estos recursos y utilizarlos como metadatos para la caché:: - - // ... - - // basándose en algo de tu proyecto - $isDebug = ...; - - $file = __DIR__ .'/cache/container.php'; - $containerConfigCache = new ConfigCache($file, $isDebug); - - if (!$containerConfigCache->isFresh()) { - $containerBuilder = new ContainerBuilder(); - // ... - $containerBuilder->compile(); - - $dumper = new PhpDumper($containerBuilder); - $containerConfigCache->write( - $dumper->dump(array('class' => 'MyCachedContainer')), - $containerBuilder->getResources() - ); - } - - require_once $file; - $container = new MyCachedContainer(); - -Ahora la caché vertida al contenedor se utiliza independientemente de si el modo de depuración está activo o no. -La diferencia es que el ``ConfigCache`` está puesto en modo para depurar con su segundo -argumento del constructor. Cuándo la caché no está en modo de depuración siempre se utilizará el contenedor memorizado en caché, si existe. En modo de depuración, un archivo de metadatos adicional se escribe con la marca de tiempo de todos los archivos de recursos. Entonces, estos se comprueban para ver si los archivos han cambiado, si están en caché se consideran viejos. - -.. note:: - - En la pila completa de la plataforma se tiene cuidado por ti de la compilación y almacenamiento en caché del contenedor. diff --git a/_sources/components/dependency_injection/configurators.txt b/_sources/components/dependency_injection/configurators.txt deleted file mode 100644 index ec34fe0..0000000 --- a/_sources/components/dependency_injection/configurators.txt +++ /dev/null @@ -1,193 +0,0 @@ -.. index:: - single: Inyección de dependencias; Configuradores de servicio - -Configurando servicios con un configurador de servicio -====================================================== - -El configurador de servicios es una característica del contenedor de inyección de dependencias que te permite utilizar una función ejecutable para configurar un servicio después de su creación. - -Puedes especificar un método en otro servicio, una función *PHP* o un método estático en una clase. El servicio ``instance`` se pasa al ejecutable, permitiendo al configurador hacer cualquier cosa necesaria para configurar el servicio después de su creación. - -Puedes utilizar un configurador de servicio, por ejemplo, cuándo tienes un servicio que requiere una configuración compleja basada en opciones de configuración que provienen de diferentes fuentes/servicios. Usando un configurador externo, puedes mantener limpia la implementación del servicio y mantenerla desacoplada de los otros objetos que proporcionan la configuración necesaria. - -Otro interesante caso de uso es cuándo tienes múltiples objetos que comparten una configuración común o que se deberían configurar de manera similar en en tiempo de ejecución. - -Por ejemplo, supón que tienes una aplicación donde envías diferentes tipos de correo electrónico a los usuarios. Los mensajes de correo electrónico se pasan a través de diferentes formateadores que podrían estar habilitados o no dependiendo de algunas opciones dinámicas de la aplicación. Empiezas definiedo una clase ``NewsletterManager`` como esta:: - - class NewsletterManager implements EmailFormatterAwareInterface - { - protected $mailer; - protected $enabledFormatters; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } - - public function setEnabledFormatters(array $enabledFormatters) - { - $this->enabledFormatters = $enabledFormatters; - } - - // ... - } - - -y además una clase ``GreetingCardManager``:: - - class GreetingCardManager implements EmailFormatterAwareInterface - { - protected $mailer; - protected $enabledFormatters; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } - - public function setEnabledFormatters(array $enabledFormatters) - { - $this->enabledFormatters = $enabledFormatters; - } - - // ... - } - - -Como se mencionó antes, el objetivo es configurar los formateadores en tiempo de ejecución dependiendo de la configuración de la aplicación. Para ello, también tienes una clase ``EmailFormatterManager`` que es la responsable de cargar y validar los formateadores habilitados en la aplicación:: - - class EmailFormatterManager - { - protected $enabledFormatters; - - public function loadFormatters() - { - // código para configurar cuales formateadores usar - $enabledFormatters = array(); - // ... - - $this->enabledFormatters = $enabledFormatters; - } - - public function getEnabledFormatters() - { - return $this->enabledFormatters; - } - - // ... - } - -Si tu objetivo es evitar el tener un par de ``NewsletterManager`` y ``GreetingCardManager`` con ``EmailFormatterManager``, entonces podrías desear crear una clase configuradora para ajustar estos casos:: - - class EmailConfigurator - { - private $formatterManager; - - public function __construct(EmailFormatterManager $formatterManager) - { - $this->formatterManager = $formatterManager; - } - - public function configure(EmailFormatterAwareInterface $emailManager) - { - $emailManager->setEnabledFormatters( - $this->formatterManager->getEnabledFormatters() - ); - } - - // ... - } - -El trabajo del ``EmailConfigurator`` es inyectar los filtros habilitados en el ``NewsletterManager`` y ``GreetingCardManager`` porque no están conscientes de donde provienen los filtros habilitados. Por otro lado, el ``EmailFormatterManager`` mantiene el conocimiento sobre los formateadores habilitados y cómo cargarlos, únicamente manteniendo el principio de responsabilidad. - -Configurador del servicio ``Config`` ------------------------------------- - -El servicio ``config`` de las clases anteriores se vería algo así: - -.. configuration-block:: - - .. code-block:: yaml - - services: - my_mailer: - # ... - - email_formatter_manager: - class: EmailFormatterManager - # ... - - email_configurator: - class: EmailConfigurator - arguments: ["@email_formatter_manager"] - # ... - - newsletter_manager: - class: NewsletterManager - calls: - - [setMailer, ["@my_mailer"]] - configurator: ["@email_configurator", configure] - - greeting_card_manager: - class: GreetingCardManager - calls: - - [setMailer, ["@my_mailer"]] - configurator: ["@email_configurator", configure] - - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setDefinition('my_mailer', ...); - $container->setDefinition('email_formatter_manager', new Definition( - 'EmailFormatterManager' - )); - $container->setDefinition('email_configurator', new Definition( - 'EmailConfigurator' - )); - $container->setDefinition('newsletter_manager', new Definition( - 'NewsletterManager' - ))->addMethodCall('setMailer', array( - new Reference('my_mailer'), - ))->setConfigurator(array( - new Reference('email_configurator'), - 'configure', - ))); - $container->setDefinition('greeting_card_manager', new Definition( - 'GreetingCardManager' - ))->addMethodCall('setMailer', array( - new Reference('my_mailer'), - ))->setConfigurator(array( - new Reference('email_configurator'), - 'configure', - ))); diff --git a/_sources/components/dependency_injection/definitions.txt b/_sources/components/dependency_injection/definitions.txt deleted file mode 100644 index c784614..0000000 --- a/_sources/components/dependency_injection/definitions.txt +++ /dev/null @@ -1,114 +0,0 @@ -.. index:: - single: Inyección de dependencias; Definiendo servicios - - -Working with Container Service Definitions -========================================== - -Obteniendo y estableciendo definiciones de servicios ----------------------------------------------------- - -There are some helpful methods for working with the service definitions. - -Para saber si hay una definición para un ``ID`` de servicio:: - - $container->hasDefinition($serviceId); - -Esto es útil si sólo quieres hacer algo si existe una definición particular. - -Puedes recuperar una definición con:: - - $container->getDefinition($serviceId); - -o:: - - $container->findDefinition($serviceId); - -que a diferencia de ``getDefinition()`` también resuelve los alias de manera tal que si el argumento ``$serviceId`` es un alias esta recuperará la definición subyacente. - -Las definiciones de servicios en sí mismas son objetos, por tanto, si recuperas una definición de dichos métodos y les haces cambios se verán reflejados en el contenedor. Sin embargo, si estás creando una nueva definición, entonces lo puedes añadir al contenedor usando:: - - $container->setDefinition($id, $definition); - -Trabajando con una definición ------------------------------ - -Creando una nueva definición -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si necesitas crear una nueva definición en lugar de manipular una recuperada desde ese contenedor, entonces la clase de la definición es :class:`Symfony\\Component\\DependencyInjection\\Definition`. - -Clase -~~~~~ - -En primer lugar es la clase de una definición, ésta es la clase del objeto devuelto cuando solicitas el servicio desde el contenedor. - -Para determinar qué clase dicta la definición:: - - $definition->getClass(); - -y para establecer una clase diferente:: - - $definition->setClass($class); // nombre de clase completamente cualificado como cadena - -Argumentos del constructor -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para obtener un arreglo de los argumentos del constructor para una definición puedes usar:: - - $definition->getArguments(); - -o para obtener un solo argumento por posición:: - - $definition->getArgument($index); - // p. ej. $definition->getArguments(0) para el primer argumento - -Puedes agregar un nuevo argumento al final del arreglo de argumentos usando:: - - $definition->addArgument($argument); - -El argumento puede ser una cadena, un arreglo, un parámetro de servicio usando ``%paramater_name%`` o un identificador de servicio usando:: - - use Symfony\Component\DependencyInjection\Reference; - - // ... - - $definition->addArgument(new Reference('service_id')); - -De manera similar puedes sustituir un argumento ya establecido por índice usando:: - - $definition->replaceArgument($index, $argument); - -También puedes reemplazar todos los argumentos (o establecer alguno si no hay) con un arreglo de argumentos:: - - $definition->replaceArguments($arguments); - -Llamadas a método -~~~~~~~~~~~~~~~~~ - -Si el servicio en que estás trabajando usa la inyección de definidores entonces puedes manipular cualquier llamada a método en las definiciones también. - -Puedes obtener un arreglo de todas las llamadas al método con:: - - $definition->getMethodCalls(); - -Añade una llamada al método con:: - - $definition->addMethodCall($method, $arguments); - -Donde ``$method`` es el nombre del método y ``$arguments`` es un arreglo de los argumentos con los cuales llamar al método. Los argumentos pueden ser cadenas, arreglos, parámetros o identificadores de servicio al igual que los argumentos del constructor. - -También puedes sustituir cualquier llamada existente a un método con un arreglo de otras nuevas con:: - - $definition->setMethodCalls($methodCalls); - -.. tip:: - - Hay más ejemplos de maneras específicas de trabajar con definiciones en los bloques de código *PHP* de los ejemplos de configuración en páginas como :doc:`/components/dependency_injection/factories` y :doc:`/components/dependency_injection/parentservices`. - -.. note:: - - The methods here that change service definitions can only be used before - the container is compiled, once the container is compiled you cannot - manipulate service definitions further. To learn more about compiling - the container see :doc:`/components/dependency_injection/compilation`. diff --git a/_sources/components/dependency_injection/factories.txt b/_sources/components/dependency_injection/factories.txt deleted file mode 100644 index 95db394..0000000 --- a/_sources/components/dependency_injection/factories.txt +++ /dev/null @@ -1,190 +0,0 @@ -.. index:: - single: Inyección de dependencias; Factorías - -Usando el patrón factoría para crear servicios -============================================== - -Los contenedores de servicios de *Symfony2* proporcionan una forma eficaz de controlar la creación de objetos, lo cual te permite especificar los argumentos pasados al constructor, así como llamar a los métodos y establecer parámetros. A veces, sin embargo, esto no te proporcionará todo lo necesario para construir tus objetos. -Por esta situación, puedes utilizar una factoría para crear el objeto y decirle al contenedor de servicios que llame a un método en la factoría y no crear directamente una instancia del objeto. - -Supongamos que tienes una factoría que configura y devuelve un nuevo objeto ``NewsletterManager``:: - - class NewsletterFactory - { - public function get() - { - $newsletterManager = new NewsletterManager(); - - // ... - - return $newsletterManager; - } - } - -Para que el objeto ``NewsletterManager`` esté disponible como servicio, puedes configurar el contenedor de servicios para usar la clase factoría ``NewsletterFactory``: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - newsletter_factory.class: NewsletterFactory - services: - newsletter_manager: - class: "%newsletter_manager.class%" - factory_class: "%newsletter_factory.class%" - factory_method: get - - .. code-block:: xml - - - - NewsletterManager - NewsletterFactory - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('newsletter_factory.class', 'NewsletterFactory'); - - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%' - ))->setFactoryClass( - '%newsletter_factory.class%' - )->setFactoryMethod( - 'get' - ); - -Cuando especificas la clase que utiliza la factoría (a través de ``factory_class``), el método será llamado estáticamente. Si la factoría debe crear una instancia y se llama al método del objeto resultante (como en este ejemplo), configura como servicio la propia factoría: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - newsletter_factory.class: NewsletterFactory - services: - newsletter_factory: - class: "%newsletter_factory.class%" - newsletter_manager: - class: "%newsletter_manager.class%" - factory_service: newsletter_factory - factory_method: get - - .. code-block:: xml - - - - NewsletterManager - NewsletterFactory - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('newsletter_factory.class', 'NewsletterFactory'); - - $container->setDefinition('newsletter_factory', new Definition( - '%newsletter_factory.class%' - )) - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%' - ))->setFactoryService( - 'newsletter_factory' - )->setFactoryMethod( - 'get' - ); - -.. note:: - - El servicio factoría se especifica por su nombre de ``id`` y no una referencia al propio servicio. Por lo tanto, no es necesario utilizar la sintaxis @. - -Pasando argumentos al método factoría -------------------------------------- - -Si tienes que pasar argumentos al método factoría, puedes utilizar la opción ``arguments`` dentro del contenedor de servicios. Por ejemplo, supongamos que el método ``get`` en el ejemplo anterior tiene el servicio de ``templating`` como argumento: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - newsletter_factory.class: NewsletterFactory - services: - newsletter_factory: - class: "%newsletter_factory.class%" - newsletter_manager: - class: "%newsletter_manager.class%" - factory_service: newsletter_factory - factory_method: get - arguments: - - "@templating" - - .. code-block:: xml - - - - NewsletterManager - NewsletterFactory - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('newsletter_factory.class', 'NewsletterFactory'); - - $container->setDefinition('newsletter_factory', new Definition( - '%newsletter_factory.class%' - )) - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%', - array(new Reference('templating')) - ))->setFactoryService( - 'newsletter_factory' - )->setFactoryMethod( - 'get' - ); diff --git a/_sources/components/dependency_injection/index.txt b/_sources/components/dependency_injection/index.txt deleted file mode 100644 index 058dc59..0000000 --- a/_sources/components/dependency_injection/index.txt +++ /dev/null @@ -1,18 +0,0 @@ -Inyección de dependencias -========================= - -.. toctree:: - :maxdepth: 2 - - introduction - types - parameters - definitions - compilation - tags - factories - configurators - parentservices - advanced - workflow - diff --git a/_sources/components/dependency_injection/introduction.txt b/_sources/components/dependency_injection/introduction.txt deleted file mode 100644 index 468858f..0000000 --- a/_sources/components/dependency_injection/introduction.txt +++ /dev/null @@ -1,259 +0,0 @@ -.. index:: - single: Inyección de dependencias - single: Componentes; DependencyInjection - -El componente ``Inyección de dependencias`` -=========================================== - - El componente ``Inyección de dependencias``, te permite estandarizar y centralizar la forma en que se construyen los objetos en tu aplicación. - -Para una introducción general a los contenedores de inyección de dependencias y servicios consulta el capítulo :doc:`/book/service_container` del libro. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/DependencyInjection); -* :doc:`Instalándolo vía Composer ` (``symfony/dependency-injection`` en `Packagist`_). - -Uso básico ----------- - -Tal vez tengas una simple clase ``Mailer`` como la siguiente, la cual quieres hacer disponible como un servicio:: - - class Mailer - { - private $transport; - - public function __construct() - { - $this->transport = 'sendmail'; - } - - // ... - } - -La puedes registrar en el contenedor como un servicio:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $container = new ContainerBuilder(); - $container->register('mailer', 'Mailer'); - -Una mejora a la clase para hacerla más flexible sería permitir que el contenedor establezca el ``transporte`` utilizado. Si cambias la clase para que esta sea pasada al constructor:: - - class Mailer - { - private $transport; - - public function __construct($transport) - { - $this->transport = $transport; - } - - // ... - } - -Entonces, puedes configurar la opción de transporte en el contenedor:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $container = new ContainerBuilder(); - $container - ->register('mailer', 'Mailer') - ->addArgument('sendmail'); - -Esta clase ahora es mucho más flexible puesto que separamos la elección del transporte de la implementación y la colocamos en el contenedor. - -Cual transporte de correo has elegido, puede ser algo sobre lo cual los demás servicios necesiten saber. Puedes evitar tener que cambiarlo en varios lugares volviéndolo un parámetro en el contenedor y luego refiriendo este parámetro como argumento en el constructor del servicio ``Mailer``:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $container = new ContainerBuilder(); - $container->setParameter('mailer.transport', 'sendmail'); - $container - ->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%'); - -Ahora que el servicio ``mailer`` está en el contenedor lo puedes inyectar como una dependencia de otras clases. Si tienes una clase ``NewsletterManager`` como esta:: - - class NewsletterManager - { - private $mailer; - - public function __construct(\Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -Entonces, también la puedes registrar como un servicio y pasarla al servicio ``mailer``:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Reference; - - $container = new ContainerBuilder(); - - $container->setParameter('mailer.transport', 'sendmail'); - $container - ->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%'); - - $container - ->register('newsletter_manager', 'NewsletterManager') - ->addArgument(new Reference('mailer')); - -Si el ``NewsletterManager`` no requiere el ``Mailer`` y la inyección sólo era opcional, entonces, en su lugar puedes utilizar el método definidor para inyectarlo:: - - class NewsletterManager - { - private $mailer; - - public function setMailer(\Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -Ahora puedes optar por no inyectar un ``Mailer`` en el ``NewsletterManager``. -Aún así, si quieres entonces el contenedor puede llamar al método definidor:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Reference; - - $container = new ContainerBuilder(); - - $container->setParameter('mailer.transport', 'sendmail'); - $container - ->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%'); - - $container - ->register('newsletter_manager', 'NewsletterManager') - ->addMethodCall('setMailer', array(new Reference('mailer'))); - -A continuación, puedes obtener tu servicio ``newsletter_manager`` desde el contenedor de esta manera:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $container = new ContainerBuilder(); - - // ... - - $newsletterManager = $container->get('newsletter_manager'); - -Evitando que tu código sea dependiente en el contenedor -------------------------------------------------------- - -Si bien puedes recuperar los servicios directamente desde el contenedor, lo mejor es minimizar esto. Por ejemplo, en el ``NewsletterManager`` inyectamos el servicio ``Mailer`` en vez de solicitarlo desde el contenedor. -Podríamos haber inyectado el contenedor y recuperar el servicio ``Mailer`` desde ahí, pero entonces, estaría vinculado a ese contenedor particular, lo cual dificulta la reutilización de la clase en cualquier otro lugar. - -Tendrás que conseguir un servicio desde el contenedor en algún momento, pero esto debe ser tan pocas veces como sea posible en el punto de entrada a tu aplicación. - -.. _components-dependency-injection-loading-config: - -Configurando el contenedor con archivos de configuración --------------------------------------------------------- - -As well as setting up the services using PHP as above you can also use -configuration files. This allows you to use XML or Yaml to write the definitions -for the services rather than using PHP to define the services as in the above -examples. In anything but the smallest applications it make sense to organize -the service definitions by moving them into one or more configuration files. -To do this you also need to install -:doc:`the Config Component`. - -Cargando un archivo de configuración *XML*:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\Config\FileLocator; - use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; - - $container = new ContainerBuilder(); - $loader = new XmlFileLoader($container, new FileLocator(__DIR__)); - $loader->load('services.xml'); - -Cargando un archivo de configuración *YAML*:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\Config\FileLocator; - use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; - - $container = new ContainerBuilder(); - $loader = new YamlFileLoader($container, new FileLocator(__DIR__)); - $loader->load('services.yml'); - -.. note:: - - Si quieres cargar archivos de configuración *YAML* entonces también necesitarás instalar el :doc:`componente YAML `. - -If you *do* want to use PHP to create the services then you can move this -into a separate config file and load it in a similar way:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\Config\FileLocator; - use Symfony\Component\DependencyInjection\Loader\PhpFileLoader; - - $container = new ContainerBuilder(); - $loader = new PhpFileLoader($container, new FileLocator(__DIR__)); - $loader->load('services.php'); - -You can now set up the ``newsletter_manager`` and ``mailer`` services using -config files: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - mailer.transport: sendmail - - services: - mailer: - class: Mailer - arguments: ["%mailer.transport%"] - newsletter_manager: - class: NewsletterManager - calls: - - [setMailer, ["@mailer"]] - - .. code-block:: xml - - - - sendmail - - - - - %mailer.transport% - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('mailer.transport', 'sendmail'); - $container - ->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%'); - - $container - ->register('newsletter_manager', 'NewsletterManager') - ->addMethodCall('setMailer', array(new Reference('mailer'))); - -.. _`Packagist`: https://packagist.org/packages/symfony/dependency-injection diff --git a/_sources/components/dependency_injection/parameters.txt b/_sources/components/dependency_injection/parameters.txt deleted file mode 100644 index 4d2a357..0000000 --- a/_sources/components/dependency_injection/parameters.txt +++ /dev/null @@ -1,262 +0,0 @@ -.. index:: - single: Inyección de dependencias; Parameters - -Introduction to Parameters -========================== - -You can define parameters in the service container which can then be used -directly or as part of service definitions. This can help to separate out -values that you will want to change more regularly. - -Obteniendo y estableciendo contenedores de parámetros ------------------------------------------------------ - -Working with container parameters is straightforward using the container's -accessor methods for parameters. Puedes comprobar si se ha definido un parámetro en el contenedor con:: - - $container->hasParameter('mailer.transport'); - -You can retrieve a parameter set in the container with:: - - $container->getParameter('mailer.transport'); - -y establecer un parámetro en el contenedor con:: - - $container->setParameter('mailer.transport', 'sendmail'); - -.. note:: - - You can only set a parameter before the container is compiled. To learn - more about compiling the container see - :doc:`/components/dependency_injection/compilation`. - -Parameters in Configuration Files ---------------------------------- - -You can also use the ``parameters`` section of a config file to set parameters: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - mailer.transport: sendmail - - .. code-block:: xml - - - sendmail - - - .. code-block:: php - - $container->setParameter('mailer.transport', 'sendmail'); - -As well as retrieving the parameter values directly from the container you -can use them in the config files. You can refer to parameters elsewhere by -surrounding them with percent (``%``) signs, e.g. ``%mailer.transport%``. -One use for this is to inject the values into your services. This allows -you to configure different versions of services between applications or multiple -services based on the same class but configured differently within a single -application. You could inject the choice of mail transport into the ``Mailer`` -class directly but by making it a parameter. This makes it easier to change -rather than being tied up and hidden with the service definition: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - mailer.transport: sendmail - - services: - mailer: - class: Mailer - arguments: ['%mailer.transport%'] - - .. code-block:: xml - - - sendmail - - - - - %mailer.transport% - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('mailer.transport', 'sendmail'); - $container - ->register('mailer', 'Mailer') - ->addArgument('%mailer.transport%'); - -If you were using this elsewhere as well, then you would only need to change -the parameter value in one place if needed. - -You can also use the parameters in the service definition, for example, -making the class of a service a parameter: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - mailer.transport: sendmail - mailer.class: Mailer - - services: - mailer: - class: '%mailer.class%' - arguments: ['%mailer.transport%'] - - .. code-block:: xml - - - sendmail - Mailer - - - - - %mailer.transport% - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('mailer.transport', 'sendmail'); - $container->setParameter('mailer.class', 'Mailer'); - $container - ->register('mailer', '%mailer.class%') - ->addArgument('%mailer.transport%'); - - $container - ->register('newsletter_manager', 'NewsletterManager') - ->addMethodCall('setMailer', array(new Reference('mailer'))); - -.. note:: - - El signo de porcentaje en un parámetro o argumento, como parte de la cadena, se debe escapar con otro signo de porcentaje: - - .. configuration-block:: - - .. code-block:: yaml - - arguments: ['http://symfony.com/?foo=%%s&bar=%%d'] - - .. code-block:: xml - - http://symfony.com/?foo=%%s&bar=%%d - - .. code-block:: php - - ->addArgument('http://symfony.com/?foo=%%s&bar=%%d'); - -.. _component-di-parameters-array: - -Array Parameters ----------------- - -Los parámetros no tienen que ser cadenas planas, sino que también pueden ser arreglos. For the XML -format, you need to use the ``type="collection"`` attribute for all parameters that are -arrays. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - parameters: - my_mailer.gateways: - - mail1 - - mail2 - - mail3 - my_multilang.language_fallback: - en: - - en - - fr - fr: - - fr - - en - - .. code-block:: xml - - - - - mail1 - mail2 - mail3 - - - - en - fr - - - fr - en - - - - - .. code-block:: php - - // app/config/config.php - use Symfony\Component\DependencyInjection\Definition; - - $container->setParameter('my_mailer.gateways', array('mail1', 'mail2', 'mail3')); - $container->setParameter('my_multilang.language_fallback', array( - 'en' => array('en', 'fr'), - 'fr' => array('fr', 'en'), - )); - -.. _component-di-parameters-constants: - -Constants as Parameters ------------------------ - -El contenedor también cuenta con apoyo para fijar constantes *PHP* como parámetros. Para aprovechar esta característica, asigna el nombre de tu constante a un parámetro clave, y define el tipo como ``constant``. - -.. configuration-block:: - - .. code-block:: xml - - - - - - - GLOBAL_CONSTANT - My_Class::CONSTANT_NAME - - - - .. code-block:: php - - $container->setParameter('global.constant.value', GLOBAL_CONSTANT); - $container->setParameter('my_class.constant.value', My_Class::CONSTANT_NAME); - -.. note:: - - This does not works for Yaml configuration. If you're using Yaml, you can - import an XML file to take advantage of this functionality: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - imports: - - { resource: parameters.xml } diff --git a/_sources/components/dependency_injection/parentservices.txt b/_sources/components/dependency_injection/parentservices.txt deleted file mode 100644 index 810681c..0000000 --- a/_sources/components/dependency_injection/parentservices.txt +++ /dev/null @@ -1,485 +0,0 @@ -.. index:: - single: Inyección de dependencias; Servicios padre - -Gestionando dependencias comunes con servicios padre -==================================================== - -A medida que agregas más funcionalidad a tu aplicación, puedes comenzar a tener clases relacionadas que comparten algunas de las mismas dependencias. Por ejemplo, puedes tener un gestor de ``boletines`` que utiliza inyección para definir sus dependencias:: - - class NewsletterManager - { - protected $mailer; - protected $emailFormatter; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } - - public function setEmailFormatter(EmailFormatter $emailFormatter) - { - $this->emailFormatter = $emailFormatter; - } - - // ... - } - -y también una clase para tus ``Tarjetas de saludo`` que comparte las mismas dependencias:: - - class GreetingCardManager - { - protected $mailer; - protected $emailFormatter; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } - - public function setEmailFormatter(EmailFormatter $emailFormatter) - { - $this->emailFormatter = $emailFormatter; - } - - // ... - } - -La configuración del servicio de estas clases se vería algo como esto: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - greeting_card_manager.class: GreetingCardManager - services: - my_mailer: - # ... - my_email_formatter: - # ... - newsletter_manager: - class: %newsletter_manager.class% - calls: - - [setMailer, ["@my_mailer"]] - - [setEmailFormatter, ["@my_email_formatter"]] - - greeting_card_manager: - class: "%greeting_card_manager.class%" - calls: - - [setMailer, ["@my_mailer"]] - - [setEmailFormatter, ["@my_email_formatter"]] - - .. code-block:: xml - - - - NewsletterManager - GreetingCardManager - - - - - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('greeting_card_manager.class', 'GreetingCardManager'); - - $container->setDefinition('my_mailer', ...); - $container->setDefinition('my_email_formatter', ...); - $container->setDefinition('newsletter_manager', new Definition( - '%newsletter_manager.class%' - ))->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); - $container->setDefinition('greeting_card_manager', new Definition( - '%greeting_card_manager.class%' - ))->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); - -Hay mucha repetición, tanto en las clases como en la configuración. Esto significa que si cambias, por ejemplo, las clases de correo de la aplicación ``Mailer`` de ``EmailFormatter`` para inyectarlas a través del constructor, tendrías que actualizar la configuración en dos lugares. Del mismo modo, si necesitas hacer cambios en los métodos definidores tendrías que hacerlo en ambas clases. La forma típica de hacer frente a los métodos comunes de estas clases relacionadas es extraerlas en una superclase:: - - abstract class MailManager - { - protected $mailer; - protected $emailFormatter; - - public function setMailer(Mailer $mailer) - { - $this->mailer = $mailer; - } - - public function setEmailFormatter(EmailFormatter $emailFormatter) - { - $this->emailFormatter = $emailFormatter; - } - - // ... - } - -Entonces ``NewsletterManager`` y ``GreetingCardManager`` pueden extender esta -superclase:: - - class NewsletterManager extends MailManager - { - // ... - } - -y:: - - class GreetingCardManager extends MailManager - { - // ... - } - -De manera similar, el contenedor de servicios de *Symfony2* también apoya la extensión de servicios en la configuración por lo que también puedes reducir la repetición especificando un padre para un servicio. - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - greeting_card_manager.class: GreetingCardManager - services: - my_mailer: - # ... - my_email_formatter: - # ... - mail_manager: - abstract: true - calls: - - [setMailer, ["@my_mailer"]] - - [setEmailFormatter, ["@my_email_formatter"]] - - newsletter_manager: - class: "%newsletter_manager.class%" - parent: mail_manager - - greeting_card_manager: - class: "%greeting_card_manager.class%" - parent: mail_manager - - .. code-block:: xml - - - - NewsletterManager - GreetingCardManager - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\DefinitionDecorator; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('greeting_card_manager.class', 'GreetingCardManager'); - - $container->setDefinition('my_mailer', ...); - $container->setDefinition('my_email_formatter', ...); - $container->setDefinition('mail_manager', new Definition( - ))->setAbstract( - true - )->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); - $container->setDefinition('newsletter_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%newsletter_manager.class%' - ); - $container->setDefinition('greeting_card_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%greeting_card_manager.class%' - ); - -En este contexto, tener un servicio ``padre`` implica que los argumentos y las llamadas a métodos del servicio padre se deben utilizar en los servicios descendientes. -En concreto, los métodos definidores especificados para el servicio padre serán llamados cuando se crean instancias del servicio descendiente. - -.. note:: - - Si quitas la clave de configuración del ``padre``, el servicio todavía seguirá siendo una instancia, por supuesto, extendiendo la clase ``MailManager``. La diferencia es que la omisión del ``padre`` en la clave de configuración significa que las ``llamadas`` definidas en el servicio ``mail_manager`` no se ejecutarán al crear instancias de los servicios descendientes. - -The parent service is abstract as it should not be directly retrieved from the -container or passed into another service. It exists merely as a "template" that -other services can use. This is why it can have no ``class`` configured which -would cause an exception to be raised for a non-abstract service. - -.. note:: - - In order for parent dependencies to resolve, the ``ContainerBuilder`` must - first be compiled. See :doc:`/components/dependency_injection/compilation` - for more details. - -Sustituyendo dependencias del padre ------------------------------------ - -Puede haber ocasiones en las que deses sustituir cual clase se pasa a una dependencia en un único servicio hijo. Afortunadamente, añadiendo la llamada al método de configuración para el servicio hijo, se sustituyen las dependencias establecidas por la clase principal. Así que si necesitas pasar una dependencia diferente sólo para la clase ``NewsletterManager``, la configuración sería la siguiente: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - greeting_card_manager.class: GreetingCardManager - services: - my_mailer: - # ... - my_alternative_mailer: - # ... - my_email_formatter: - # ... - mail_manager: - abstract: true - calls: - - [setMailer, ["@my_mailer"]] - - [setEmailFormatter, ["@my_email_formatter"]] - - newsletter_manager: - class: "%newsletter_manager.class%" - parent: mail_manager - calls: - - [setMailer, ["@my_alternative_mailer"]] - - greeting_card_manager: - class: "%greeting_card_manager.class%" - parent: mail_manager - - .. code-block:: xml - - - - NewsletterManager - GreetingCardManager - - - - - - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\DefinitionDecorator; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('greeting_card_manager.class', 'GreetingCardManager'); - - $container->setDefinition('my_mailer', ...); - $container->setDefinition('my_alternative_mailer', ...); - $container->setDefinition('my_email_formatter', ...); - $container->setDefinition('mail_manager', new Definition( - ))->setAbstract( - true - )->addMethodCall('setMailer', array( - new Reference('my_mailer') - ))->addMethodCall('setEmailFormatter', array( - new Reference('my_email_formatter') - )); - $container->setDefinition('newsletter_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%newsletter_manager.class%' - )->addMethodCall('setMailer', array( - new Reference('my_alternative_mailer') - )); - $container->setDefinition('greeting_card_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%greeting_card_manager.class%' - ); - -El ``GreetingCardManager`` recibirá las mismas dependencias que antes, pero la ``NewsletterManager`` será pasada a ``my_alternative_mailer`` en lugar del servicio ``my_mailer``. - -Colección de dependencias -------------------------- - -Cabe señalar que el método definidor sustituido en el ejemplo anterior en realidad se invoca dos veces --- una vez en la definición del padre y otra más en la definición del hijo. En el ejemplo anterior, esto estaba muy bien, ya que la segunda llamada a ``setMailer`` sustituye al objeto ``mailer`` establecido por la primera llamada. - -En algunos casos, sin embargo, esto puede ser un problema. Por ejemplo, si la sustitución a la llamada al método consiste en añadir algo a una colección, entonces se agregarán dos objetos a la colección. A continuación mostramos tal caso, si la clase padre se parece a esto:: - - abstract class MailManager - { - protected $filters; - - public function setFilter($filter) - { - $this->filters[] = $filter; - } - - // ... - } - -Si tienes la siguiente configuración: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - # ... - newsletter_manager.class: NewsletterManager - services: - my_filter: - # ... - another_filter: - # ... - mail_manager: - abstract: true - calls: - - [setFilter, ["@my_filter"]] - - newsletter_manager: - class: "%newsletter_manager.class%" - parent: mail_manager - calls: - - [setFilter, ["@another_filter"]] - - .. code-block:: xml - - - - NewsletterManager - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\DefinitionDecorator; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setParameter('newsletter_manager.class', 'NewsletterManager'); - $container->setParameter('mail_manager.class', 'MailManager'); - - $container->setDefinition('my_filter', ...); - $container->setDefinition('another_filter', ...); - $container->setDefinition('mail_manager', new Definition( - ))->setAbstract( - true - )->addMethodCall('setFilter', array( - new Reference('my_filter') - )); - $container->setDefinition('newsletter_manager', new DefinitionDecorator( - 'mail_manager' - ))->setClass( - '%newsletter_manager.class%' - )->addMethodCall('setFilter', array( - new Reference('another_filter') - )); - -En este ejemplo, el ``setFilter`` del servicio ``newsletter_manager`` se llamará dos veces, dando lugar a que el array ``$filters`` contenga ambos objetos ``my_filter`` y ``another_filter``. Esto es genial si sólo quieres agregar filtros adicionales para subclases. If you want to replace the filters -passed to the subclass, removing the parent setting from the config will -prevent the base class from calling ``setFilter``. - -.. tip:: - - In the examples shown there is a similar relationship between the parent - and child services and the underlying parent and child classes. This does - not need to be the case though, you can extract common parts of similar - service definitions into a parent service without also inheriting a parent - class. diff --git a/_sources/components/dependency_injection/tags.txt b/_sources/components/dependency_injection/tags.txt deleted file mode 100644 index 680790b..0000000 --- a/_sources/components/dependency_injection/tags.txt +++ /dev/null @@ -1,254 +0,0 @@ -.. index:: - single: Inyección de dependencias; Etiquetas - -Trabajando con servicios etiquetados -==================================== - -Las etiquetas son una cadena genérica (junto con algunas opciones) que puedes aplicar a cualquier servicio. Por sí mismas, las etiquetas en realidad no hacen la funcionalidad de tu servicio en modo alguno. Pero si quieres, puedes pedir al constructor del contenedor que consiga una lista de todos los servicios que estén etiquetados con alguna etiqueta específica. Esto es útil en los pases del compilador donde puedes encontrar estos servicios y usarlos o -modificarlos de alguna manera específica. - -Por ejemplo, si estás usando Swift Mailer te puedes imaginar que quieres implementar una «cadena de transporte», que es una colección de clases que implementan ``\Swift_Transport``. Usando la cadena, querrá que Swift Mailer pruebe varias maneras de transportar el mensaje hasta que una lo consiga. - -En primer lugar, define la clase ``TransportChain``:: - - class TransportChain - { - private $transports; - - public function __construct() - { - $this->transports = array(); - } - - public function addTransport(\Swift_Transport $transport) - { - $this->transports[] = $transport; - } - } - -Entonces, define la cadena como un servicio: - -.. configuration-block:: - - .. code-block:: yaml - - parameters: - acme_mailer.transport_chain.class: TransportChain - - services: - acme_mailer.transport_chain: - class: "%acme_mailer.transport_chain.class%" - - .. code-block:: xml - - - TransportChain - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - - $container->setParameter('acme_mailer.transport_chain.class', 'TransportChain'); - - $container->setDefinition('acme_mailer.transport_chain', new Definition('%acme_mailer.transport_chain.class%')); - -Definiendo servicios con una etiqueta personalizada ---------------------------------------------------- - -Ahora, posiblemente quieras que varias de las clases ``\Swift_Transport`` sean creadas y añadidas automáticamente a la cadena usando el método ``addTransport()``. -Como ejemplo, puedes añadir los siguientes transportes como servicios: - -.. configuration-block:: - - .. code-block:: yaml - - services: - acme_mailer.transport.smtp: - class: \Swift_SmtpTransport - arguments: - - "%mailer_host%" - tags: - - { name: acme_mailer.transport } - acme_mailer.transport.sendmail: - class: \Swift_SendmailTransport - tags: - - { name: acme_mailer.transport } - - .. code-block:: xml - - - %mailer_host% - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - - $definitionSmtp = new Definition('\Swift_SmtpTransport', array('%mailer_host%')); - $definitionSmtp->addTag('acme_mailer.transport'); - $container->setDefinition('acme_mailer.transport.smtp', $definitionSmtp); - - $definitionSendmail = new Definition('\Swift_SendmailTransport'); - $definitionSendmail->addTag('acme_mailer.transport'); - $container->setDefinition('acme_mailer.transport.sendmail', $definitionSendmail); - -Ten en cuenta que a cada uno se le dio una etiqueta llamada ``acme_mailer.transport``. Esta es la etiqueta personalizada que utilizarás en tu pase del compilador. El pase del compilador es lo que da algún «significado» a la etiqueta. - -Crear un ``CompilerPass`` -------------------------- - -Tu pase del compilador ahora puede pedir al contenedor cualquier servicio con la etiqueta personalizada:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; - use Symfony\Component\DependencyInjection\Reference; - - class TransportCompilerPass implements CompilerPassInterface - { - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('acme_mailer.transport_chain')) { - return; - } - - $definition = $container->getDefinition( - 'acme_mailer.transport_chain' - ); - - $taggedServices = $container->findTaggedServiceIds( - 'acme_mailer.transport' - ); - foreach ($taggedServices as $id => $attributes) { - $definition->addMethodCall( - 'addTransport', - array(new Reference($id)) - ); - } - } - } - -El método ``process()`` comprueba la existencia del servicio ``acme_mailer.transport_chain``, a continuación, busca todos los servicios etiquetados cómo ``acme_mailer.transport``. Los añade a la definición del servicio ``acme_mailer.transport_chain`` llamando a ``addTransport()`` por cada servicios ``"acme_mailer.transport"`` que haya encontrado. -El primer argumento de cada una de estas llamadas será el servicio de transporte de correo en sí mismo. - -Registrando el pase en el contenedor ------------------------------------- - -También necesitas registrar el pase en el contenedor, entonces se ejecutará al compilar el contenedor:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - - $container = new ContainerBuilder(); - $container->addCompilerPass(new TransportCompilerPass); - -.. note:: - - Los pases del compilador son registrados de manera diferente si estás usando la pila completa de la plataforma. Ve :doc:`/cookbook/service_container/compiler_passes` para más detalles. - -Añadiendo atributos adicionales en etiquetas --------------------------------------------- - -Algunas veces necesitarás información adicional acerca de cada servicio que se ha marcado con tu etiqueta. -Por ejemplo, posiblemente quieras agregar un alias a cada ``TransportChain``. - -Para empezar, cambia la clase ``TransportChain``:: - - class TransportChain - { - private $transports; - - public function __construct() - { - $this->transports = array(); - } - - public function addTransport(\Swift_Transport $transport, $alias) - { - $this->transports[$alias] = $transport; - } - - public function getTransport($alias) - { - if (array_key_exists($alias, $this->transports)) { - return $this->transports[$alias]; - } - else { - return; - } - } - } - -Como puedes ver, cuando se llama a ``addTransport``, no sólo necesita un objeto ``Swift_Transport``, sino también una cadena con el alias del transporte. Por lo tanto, ¿cómo podemos permitir que cada servicio etiquetado como transporte también suministre un alias? - -Para responder a esto, cambia la declaración del servicio: - -.. configuration-block:: - - .. code-block:: yaml - - services: - acme_mailer.transport.smtp: - class: \Swift_SmtpTransport - arguments: - - "%mailer_host%" - tags: - - { name: acme_mailer.transport, alias: foo } - acme_mailer.transport.sendmail: - class: \Swift_SendmailTransport - tags: - - { name: acme_mailer.transport, alias: bar } - - - .. code-block:: xml - - - %mailer_host% - - - - - - - -Ten en cuenta que agregaste una clave genérica ``alias`` a la etiqueta. Para utilizarla efectivamente, actualiza el compilador:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; - use Symfony\Component\DependencyInjection\Reference; - - class TransportCompilerPass implements CompilerPassInterface - { - public function process(ContainerBuilder $container) - { - if (!$container->hasDefinition('acme_mailer.transport_chain')) { - return; - } - - $definition = $container->getDefinition( - 'acme_mailer.transport_chain' - ); - - $taggedServices = $container->findTaggedServiceIds( - 'acme_mailer.transport' - ); - foreach ($taggedServices as $id => $tagAttributes) { - foreach ($tagAttributes as $attributes) { - $definition->addMethodCall( - 'addTransport', - array(new Reference($id), $attributes["alias"]) - ); - } - } - } - } - -La parte más complicada es la variable ``$attributes``. Debido a que puedes utilizar la misma etiqueta muchas veces en el mismo servicio (por ejemplo, en teoría, podrías etiquetar el mismo servicio 5 veces con la etiqueta ``acme_mailer.transport``), ``$attributes`` es un arreglo con información de la etiqueta de cada una de las etiquetas en ese servicio. diff --git a/_sources/components/dependency_injection/types.txt b/_sources/components/dependency_injection/types.txt deleted file mode 100644 index d6c2293..0000000 --- a/_sources/components/dependency_injection/types.txt +++ /dev/null @@ -1,196 +0,0 @@ -.. index:: - single: Inyección de dependencias; Tipos de inyección - -Tipos de inyección -================== - -Hacer dependencias de una clase explícita y requerir que se inyecte es una buena manera de hacer una clase más reutilizable, comprobable y desacoplada de las demás. - -Hay varias maneras en que se pueden inyectar dependencias. Cada punto de inyección tiene sus ventajas y desventajas a considerar, así como las diferentes formas de trabajar con ellas cuando se utiliza el contenedor de servicios. - -Inyectando en el constructor ----------------------------- - -La manera más común para inyectar dependencias es a través del constructor de la clase. -Para ello es necesario añadir un argumento a la firma del constructor para aceptar la dependencia:: - - class NewsletterManager - { - protected $mailer; - - public function __construct(\Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -Puedes especificar qué tipo de servicio deseas inyectar en la configuración del contenedor de servicios: - -.. configuration-block:: - - .. code-block:: yaml - - services: - my_mailer: - # ... - newsletter_manager: - class: NewsletterManager - arguments: ["@my_mailer"] - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setDefinition('my_mailer', ...); - $container->setDefinition('newsletter_manager', new Definition( - 'NewsletterManager', - array(new Reference('my_mailer')) - )); - -.. tip:: - - Aludir el tipo de objeto a inyectar significa que puedes estar seguro de haber inyectado la dependencia adecuada. Al aludir el tipo, de inmediato obtendrás un error claro si inyectas una dependencia inadecuada. Al aludir el tipo usando una interfaz en lugar de una clase puedes hacer más flexible la elección de la dependencia. Y suponiendo que sólo utilizas los métodos definidos en la interfaz, te puedes beneficiar de la flexibilidad y seguridad al utilizar el objeto. - -Hay varias ventajas al utilizar la inyección en el constructor: - -* Si la dependencia es un requisito y la clase no puede funcionar sin ella, entonces la inyección a través del constructor garantiza que está presente al utilizar la clase debido a que la clase no se puede construir sin ella. - -* El constructor sólo se invoca una vez cuando se crea el objeto, por lo tanto puedes estar seguro de que la dependencia no va a cambiar durante la vida del objeto. - -Estas ventajas no significan que la inyección en el constructor no sea adecuada para trabajar con dependencias opcionales. Ademas, es más difícil usar en combinación con jerarquías de clase: si una clase utiliza la inyección en el constructor entonces extender y reemplazar el constructor se vuelve problemático. - -Inyectando en un definidor --------------------------- - -Otro posible punto de inyección en una clase es añadiendo un método definidor que acepte la dependencia:: - - class NewsletterManager - { - protected $mailer; - - public function setMailer(\Mailer $mailer) - { - $this->mailer = $mailer; - } - - // ... - } - -.. configuration-block:: - - .. code-block:: yaml - - services: - my_mailer: - # ... - newsletter_manager: - class: NewsletterManager - calls: - - [setMailer, ["@my_mailer"]] - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setDefinition('my_mailer', ...); - $container->setDefinition('newsletter_manager', new Definition( - 'NewsletterManager' - ))->addMethodCall('setMailer', array(new Reference('my_mailer'))); - -Esta vez las ventajas son: - -* La inyección en el definidor funciona bien con dependencias opcionales. Si no necesitas la dependencia, entonces simplemente no llames al definidor. - -* Puedes llamar al definidor varias veces. Esto es particularmente útil si el método añade la dependencia a una colección. Entonces, puedes tener un número variable de dependencias. - -Las desventajas de la inyección en definidor son: - -* El definidor se puede llamar más que justo al momento de la construcción por lo que no puedes estar seguro de que la dependencia no sea reemplazada durante la vida del objeto (salvo que escribas el definidor de tal manera que compruebe si ya se ha llamado). - -* No puedes estar seguro de que el definidor se llamó y necesitas añadir comprobaciones para verificar que se inyecten las dependencias necesarias. - -Inyectando en propiedades -------------------------- - -Otra posibilidad es sólo configurar campos públicos en la clase directamente:: - - class NewsletterManager - { - public $mailer; - - // ... - } - -.. configuration-block:: - - .. code-block:: yaml - - services: - my_mailer: - # ... - newsletter_manager: - class: RNewsletterManager - properties: - mailer: "@my_mailer" - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - // ... - $container->setDefinition('my_mailer', ...); - $container->setDefinition('newsletter_manager', new Definition( - 'NewsletterManager' - ))->setProperty('mailer', new Reference('my_mailer'))); - - -Hay desventajas, principalmente sólo para usar la inyección de la propiedad, es similar a la inyección del definidor, pero con estos importantes problemas adicionales: - -* No puedes controlar cuando la dependencia se ha fijado del todo, esta puede cambiar en cualquier momento durante la vida del objeto. - -* No puedes usar la alusión de tipo, por lo tanto no puedes estar seguro de cual dependencia se inyecte, salvo que escribas código en la clase para probar explícitamente la clase de la instancia antes de usarla. - -Sin embargo, es útil saber que esto se puede hacer en el contenedor de servicios, sobre todo si trabajas con código que está fuera de tu control, como en una biblioteca de terceros, que utiliza propiedades públicas para sus dependencias. - diff --git a/_sources/components/dependency_injection/workflow.txt b/_sources/components/dependency_injection/workflow.txt deleted file mode 100644 index 16312f5..0000000 --- a/_sources/components/dependency_injection/workflow.txt +++ /dev/null @@ -1,40 +0,0 @@ -.. index:: - single: Inyección de dependencias; Flujo de trabajo; - -Construyendo el flujo de trabajo del contenedor -=============================================== - -En las páginas anteriores de esta sección, ha habido muy poco que decir acerca de donde se deben ubicar los diversos archivos y clases. Esto es porque eso depende de la aplicación, biblioteca o plataforma en la que deseas utilizar el contenedor. En cuanto a la forma de configurar el contenedor y construirlo en la pila completa de la plataforma, *Symfony2* te ayudará a ver cómo encaja todo esto junto, si estás utilizando la pila completa de la plataforma o buscando cómo utilizar el contenedor de servicios en otra aplicación. - -La pila completa de la plataforma usa el componente ``HttpKernel`` para manejar la carga de la configuración del contenedor de servicios desde la aplicación y paquetes, y también se encarga de su compilación y almacenamiento en caché. Incluso si no estás usando el ``HttpKernel``, debería darte una idea de una forma de organizar la configuración en una aplicación modular. - -Trabajando con contenedores en caché ------------------------------------- - -Antes de construrlo, el núcleo comprueba si existe una versión en caché del contenedor. El ``HttpKernel`` tiene un ajuste de depuración y si este es falso, se utiliza la versión en caché si existe. Si ``debug`` es verdadero, entonces el núcleo :doc:`comprueba si la configuración es fresca ` y si lo es, utiliza la versión en caché del contenedor. Si no, entonces el contenedor es construido a partir de la configuración a nivel de la aplicación y la configuración de los paquetes de extensión. - -Revisa :ref:`Volcando la configuración por rendimiento ` para más detalles. - -Configurando a nivel de la aplicación -------------------------------------- - -La configuración a nivel de la aplicación se carga desde el directorio ``app/chonfig``. Se cargan varios archivos, mismos que luego se fusionan al procesar las extensiones. Esto permite una diferente configuración para diferentes entornos, por ejemplo, ``dev``, ``prod``. - -Estos archivos contienen los parámetros y servicios que se cargan directamente en el contenedor de acuerdo a :ref:`Configurando el contenedor con archivos de configuración `. -También contienen configuración que es procesada por extensiones de acuerdo con :ref:`Gestionando la configuración con extensiones `. -Estos se consideran configuración del paquete, ya que cada paquete contiene la extensión de una clase. - -Configuración a nivel de paquete con las extensiones ----------------------------------------------------- - -Por convención, cada paquete contiene la extensión de una clase que se encuentra en directorio ``DependencyInjection`` del paquete. Éstas son registradas con el ``ContainerBuilder`` al arrancar el núcleo. Cuando el ``ContainerBuilder`` es :doc:`compilado `, la configuración a nivel de la aplicación correspondiente a la extensión del paquete se pasa a la extensión que también suele cargar sus propios archivos de configuración, típicamente desde el directorio ``Resources/config``. La configuración a nivel de la aplicación normalmente es procesada con un :doc: `objeto Configuration ` también almacenado en el directorio ``DependencyInjection`` del paquete. - -Pases del compilador para permitir la interacción entre paquetes ----------------------------------------------------------------- - -Los :ref:`pases del compilador ` se utilizan para permitir la interacción entre los diferentes paquetes, ya que estos no pueden afectar a la configuración de las demás clases de la extensión. Uno de sus principales usos es el procesamiento de servicios etiquetados, lo cual permite a los paquetes registrar servicios captados por otros paquetes, tal como registradores de *Monolog*, extensiones *Twig* y Colectores de datos para el generador de perfiles web. Se suele colocar los pases del compilador en el directorio ``DependencyInjection/Compiler``. - -Compilación y almacenamiento en caché -------------------------------------- - -Después de cargar el proceso de compilación los servicios de configuración, extensiones y pases del compilador, se vuelcan para que la memoria caché se pueda utilizar la próxima vez. La versión vertida se utiliza durante las posteriores peticiones, ya que es más eficiente. diff --git a/_sources/components/dom_crawler.txt b/_sources/components/dom_crawler.txt deleted file mode 100644 index d49cd6d..0000000 --- a/_sources/components/dom_crawler.txt +++ /dev/null @@ -1,312 +0,0 @@ -.. index:: - single: DomCrawler - single: Componentes; DomCrawler - -El componente ``DomCrawler`` -============================ - - El componente ``DomCrawler`` facilita la navegación por el *DOM* de documentos *HTML* y *XML*. - -.. note:: - - Aunque es posible, el componente ``DomCrawler`` no está diseñado para manipular el *DOM* o volcar *HTML*/*XML*. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/DomCrawler); -* :doc:`Instalándolo vía Composer ` (``symfony/dom-crawler`` en `Packagist`_). - -Usando ------- - -La clase :class:`Symfony\\Component\\DomCrawler\\Crawler` proporciona métodos para consultar y manipular documentos *HTML* y *XML*. - -Una instancia del rastreador (Crawler) representa un conjunto (:phpclass:`SplObjectStorage`) -de objetos :phpclass:`DOMElement`, los cuales básicamente son nodos que puedes recorrer fácilmente:: - - use Symfony\Component\DomCrawler\Crawler; - - $html = <<<'HTML' - - - -

    Hello World!

    -

    Hello Crawler!

    - - - HTML; - - $crawler = new Crawler($html); - - foreach ($crawler as $domElement) { - print $domElement->nodeName; - } - -Las clases especializadas :class:`Symfony\\Component\\DomCrawler\\Link` y :class:`Symfony\\Component\\DomCrawler\\Form` son útiles para interactuar con enlaces y formularios *html* conforme recorres el árbol *HTML*. - -Filtrando nodos -~~~~~~~~~~~~~~~ - -Usando expresiones *XPath* esto es realmente fácil:: - - $crawler = $crawler->filterXPath('descendant-or-self::body/p'); - -.. tip:: - - ``DOMXPath::query`` se utiliza internamente para llevar a cabo una consulta *XPath* realmente. - -El filtrado incluso es mucho más fácil si tienes instalado el componente ``CssSelector``. -Este te permite utilizar selectores similares a ``jQuery`` para recorrerlos:: - - $crawler = $crawler->filter('body > p'); - -Puedes utilizar funciones anónimas para filtrar con criterios más complejos:: - - $crawler = $crawler->filter('body > p')->reduce(function ($node, $i) { - // filtra nodos pares - return ($i % 2) == 0; - }); - -Para eliminar un nodo la función anónima debe regresar ``false``. - -.. note:: - - Todos los métodos devuelven una nueva instancia de la clase :class:`Symfony\\Component\\DomCrawler\\Crawler` con contenido filtrado. - -Recorriendo nodos -~~~~~~~~~~~~~~~~~ - -Accede al nodo por su posición en la lista:: - - $crawler->filter('body > p')->eq(0); - -Obtiene el primero o último nodo de la selección actual:: - - $crawler->filter('body > p')->first(); - $crawler->filter('body > p')->last(); - -Obtiene los nodos del mismo nivel de la selección actual:: - - $crawler->filter('body > p')->siblings(); - -Obtiene los nodos del mismo nivel después o antes de la selección actual:: - - $crawler->filter('body > p')->nextAll(); - $crawler->filter('body > p')->previousAll(); - -Obtiene todos los nodos hijos o padres:: - - $crawler->filter('body')->children(); - $crawler->filter('body > p')->parents(); - -.. note:: - - Todos los métodos de recorrido devuelven una nueva instancia de la clase :class:`Symfony\\Component\\DomCrawler\\Crawler`. - -Accediendo a valores de nodo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Accede al valor del primer nodo de la selección actual:: - - $message = $crawler->filterXPath('//body/p')->text(); - -Accede al valor del atributo del primer nodo de la selección actual:: - - $class = $crawler->filterXPath('//body/p')->attr('class'); - -Extrae el atributo y/o los valores del nodo desde la lista de nodos:: - - $attributes = $crawler - ->filterXpath('//body/p') - ->extract(array('_text', 'class')) - ; - -.. note:: - - El atributo especial ``_text`` representa el valor de un nodo. - -Invoca a una función anónima en cada nodo de la lista:: - - $nodeValues = $crawler->filter('p')->each(function ($node, $i) { - return $node->nodeValue; - }); - -La función anónima recibe como argumentos la posición y el nodo. -El resultado es un arreglo de valores devueltos por las llamadas a la función anónima. - -Añadiendo contenido -~~~~~~~~~~~~~~~~~~~ - -El ``crawler`` cuenta con soporte para múltiples formas de añadir contenido:: - - $crawler = new Crawler(''); - - $crawler->addHtmlContent(''); - $crawler->addXmlContent(''); - - $crawler->addContent(''); - $crawler->addContent('', 'text/xml'); - - $crawler->add(''); - $crawler->add(''); - -Puesto que la implementación del ``Crawler`` está basada en la extensión *DOM*, esta también puede interactuar con los objetos nativos :phpclass:`DOMDocument`, :phpclass:`DOMNodeList` y :phpclass:`DOMNode`: - -.. code-block:: php - - $document = new \DOMDocument(); - $document->loadXml(''); - $nodeList = $document->getElementsByTagName('node'); - $node = $document->getElementsByTagName('node')->item(0); - - $crawler->addDocument($document); - $crawler->addNodeList($nodeList); - $crawler->addNodes(array($node)); - $crawler->addNode($node); - $crawler->add($document); - -.. sidebar:: Manipulando y volcando un ``Crawler`` - - Estos métodos en el ``Crawler`` inicialmente pretenden poblar tu ``Crawler`` y no intentan usarse más allá de la manipulación del *DOM* (aunque esto es posible). Sin embargo, debido a que el ``Crawler`` es un conjunto de objetos :phpclass:`DOMElement`, puedes utilizar cualquier método o propiedad disponible en :phpclass:`DOMElement`, :phpclass:`DOMNode` o :phpclass:`DOMDocument`. - Por ejemplo, podrías conseguir el *HTML* de un ``Crawler`` con algo como esto:: - - $html = ''; - - foreach ($crawler as $domElement) { - $html .= $domElement->ownerDocument->saveHTML($domElement); - } - -Compatibilidad con formularios y enlaces -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Dentro del árbol del *DOM* los enlaces y formularios reciben un tratamiento especial. - -Enlaces -....... - -Para un enlace por nombre (o una imagen clicable por su atributo ``alt``), usa el método ``selectLink`` en un ``crawler`` existente. Este devuelve una instancia de un rastreador con únicamente el/los enlace(s) seleccionado(s). Al invocar a ``link()`` recibes un objeto especial :class:`Symfony\\Component\\DomCrawler\\Link`:: - - $linksCrawler = $crawler->selectLink('Go elsewhere...'); - $link = $linksCrawler->link(); - - // O haz todo esto de una vez - $link = $crawler->selectLink('Go elsewhere...')->link(); - -El objeto :class:`Symfony\\Component\\DomCrawler\\Link` tiene varios métodos útiles para obtener más información sobre el propio enlace seleccionado:: - - // Devuelve la URI adecuada que puedes utilizar para hacer otra petición - $uri = $link->getUri(); - -.. note:: - - El ``getUri()`` es especialmente útil, ya que limpia el valor de ``href`` y lo transforma a cómo se debe procesar realmente. Por ejemplo, para un enlace con ``href="#foo"``, este devolvería la *URI* completa de la página actual sufijada con ``#foo``. El valor de retorno de``getUri()`` siempre es una *URI* completa en la cual puedes actuar. - -Formularios -........... - -También se dá un trato especial a los formularios. Hay disponible un método ``selectButton()`` en el rastreador, el cual devuelve otro rastreador que coincide con un botón (``input[type=submit]``, ``input[type=image]``, o un ``button``) con el -texto dado. Este método es especialmente útil porque lo puedes utilizar para devolver un objeto :class:`Symfony\\Component\\DomCrawler\\Form` que representa al formulario en el que vive el botón:: - - $form = $crawler->selectButton('validate')->form(); - - // o "llena" los campos del formulario con datos - $form = $crawler->selectButton('validate')->form(array( - 'name' => 'Ryan', - )); - -El objeto :class:`Symfony\\Component\\DomCrawler\\Form` tiene un montón de métodos muy útiles para trabajar con formularios:: - - $uri = $form->getUri(); - - $method = $form->getMethod(); - -El método :method:`Symfony\\Component\\DomCrawler\\Form::getUri` hace más que solo devolver el atributo ``action`` del formulario. Si el método del formulario es ``GET``, entonces imita el comportamiento del navegador y devuelve el atributo ``action`` seguido de una cadena de consulta con todos los valores del formulario. - -Prácticamente puedes configurar y obtener valores en el formulario:: - - // Internamente configura valores en el formulario - $form->setValues(array( - 'registration[username]' => 'symfonyfan', - 'registration[terms]' => 1, - )); - - // recupera un arreglo de valores - en el arreglo 'plano' como el de arriba - $values = $form->getValues(); - - // devuelve los valores como los devolvería PHP, - // donde «registro» de su propio arreglo - $values = $form->getPhpValues(); - -Para trabajar con campos multidimensionales:: - -
    - - - -
    - -Pass an array of values:: - - // Set a single field - $form->setValues(array('multi' => array('value'))); - - // Set multiple fields at once - $form->setValues(array('multi' => array( - 1 => 'value', - 'dimensional' => 'an other value' - ))); - -¡Esto es muy bueno, pero se pone mejor! El objeto ``formulario`` te permite interactuar con tu formulario como un navegador, seleccionando los valores de radio, casillas de verificación marcadas, y archivos cargados:: - - $form['registration[username]']->setValue('symfonyfan'); - - // Activar o desactiva una casilla de verificación - $form['registration[terms]']->tick(); - $form['registration[terms]']->untick(); - - // selecciona una opción - $form['registration[birthday][year]']->select(1984); - - // selecciona varias opciones en una "select o checkboxes" múltiple - $form['registration[interests]']->select(array('symfony', 'cookies')); - - / / Incluso finge una carga de archivo - $form['registration[photo]']->upload('/ruta/a/lucas.jpg'); - -¿Cuál es el punto de hacer todo esto? Si estás probando internamente, puedes tomar la información de tu formulario, como si sólo se hubiera presentado utilizando valores *PHP*:: - - $values = $form->getPhpValues(); - $files = $form->getPhpFiles(); - -Si estás usando un cliente *HTTP* externo, puedes utilizar el formulario para recuperar toda la información que necesites para crear una petición ``POST`` para el formulario:: - - $uri = $form->getUri(); - $method = $form->getMethod(); - $values = $form->getValues(); - $files = $form->getFiles(); - - // Ahora usa un cliente *HTTP* y después utiliza esa información - -Un gran ejemplo de un sistema integrado que utiliza todo esto es `Goutte`_. -``Goutte`` entiende el objeto rastreador de *Symfony* y lo puedes utilizar para enviar formularios -directamente:: - - use Goutte\Client; - - // hace una petición real a un sitio externo - $client = new Client(); - $crawler = $client->request('GET', 'https://github.com/login'); - - // selecciona el formulario y completa algunos valores - $form = $crawler->selectButton('Log in')->form(); - $form['login'] = 'symfonyfan'; - $form['password'] = 'anypass'; - - // envía el formulario - $crawler = $client->submit($form); - -.. _`Goutte`: https://github.com/fabpot/goutte -.. _`Packagist`: https://packagist.org/packages/symfony/dom-crawler diff --git a/_sources/components/event_dispatcher.txt b/_sources/components/event_dispatcher.txt deleted file mode 100644 index 9a583dc..0000000 --- a/_sources/components/event_dispatcher.txt +++ /dev/null @@ -1,376 +0,0 @@ -.. index:: - single: Despachador de evento - -El componente despachador de eventos -==================================== - -Introducción ------------- - -El paradigma orientado a objetos ha recorrido un largo camino para garantizar la extensibilidad del código. Al crear clases que tienen responsabilidades bien definidas, el código se vuelve más flexible y un desarrollador lo puede extender con subclases para modificar su comportamiento. Pero si quieres compartir tus cambios con otros desarrolladores que también han hecho sus propias subclases, es discutible que la herencia de código sea la respuesta. - -Consideremos un ejemplo del mundo real en el que deseas proporcionar un sistema de complementos a tu proyecto. Un complemento debe ser capaz de agregar métodos, o hacer algo antes o después de ejecutar un método, sin interferir con otros complementos. Este no es un problema fácil de resolver con la herencia simple y herencia múltiple (en caso de que fuera posible con *PHP*) tiene sus propios inconvenientes. - -El despachador de eventos de *Symfony2* implementa el patrón `observador`_ en una manera sencilla y efectiva para hacer todo esto posible y para realmente hacer extensibles tus proyectos. - -Tomemos un ejemplo simple desde el `componente HttpKernel de Symfony2`_. Una vez creado un objeto ``Respuesta``, puede ser útil permitir que otros elementos en el sistema lo modifiquen (por ejemplo, añadan algunas cabeceras de caché) antes de utilizarlo realmente. Para hacer esto posible, el núcleo de *Symfony2* lanza un evento ---``kernel.response``---. Así es como funciona: - -* Un *escucha* (objeto *PHP*) le dice a un objeto *despachador* central que quiere escuchar el evento ``kernel.response``; - -* En algún momento, el núcleo de *Symfony2* dice al objeto *despachador* que difunda el evento ``kernel.response``, pasando con este un objeto ``Evento`` que tiene acceso al objeto ``Respuesta``; - -* El despachador notifica a (es decir, llama a un método en) todos los escuchas del evento ``kernel.response``, permitiendo que cada uno de ellos haga modificaciones al objeto ``Respuesta``. - -.. index:: - single: Despachador de evento; Eventos - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/EventDispatcher); -* Instalándolo a través de *PEAR* ( `pear.symfony.com/EventDispatcher`); -* Instalándolo vía ``Composer`` (`symfony/event-dispatcher` en Packagist). - -Usando ------- - -Eventos -~~~~~~~ - -Cuando se envía un evento, es identificado por un nombre único (por ejemplo, ``kernel.response``), al que cualquier cantidad de escuchas podría estar atento. También se crea una instancia de :class:`Symfony\\Component\\EventDispatcher\\Event` y se pasa a todos los escuchas. Como veremos más adelante, el objeto ``Evento`` mismo, a menudo contiene datos sobre cuando se despachó el evento. - -.. index:: - pair: Despachador de evento; Convenciones de nomenclatura - -Convenciones de nomenclatura -............................ - -El nombre único del evento puede ser cualquier cadena, pero opcionalmente sigue una serie de convenciones de nomenclatura simples: - -* Sólo usa letras minúsculas, números, puntos (``.``) y guiones bajos (``_``); - -* Prefija los nombres con un espacio de nombres seguido de un punto (por ejemplo, ``kernel.``); - -* Termina los nombres con un verbo que indica qué acción se está tomando (por ejemplo, ``request``). - -Estos son algunos ejemplos de nombres de evento aceptables: - -* ``kernel.response`` -* ``form.pre_set_data`` - -.. index:: - single: Despachador de evento; Subclases de evento - -Nombres de evento y objetos evento -.................................. - -Cuando el despachador notifica a los escuchas, este pasa un objeto ``Evento`` real a los escuchas. La clase base ``Evento`` es muy simple: contiene un método para detener la :ref:`propagación del evento `, pero no mucho más. - -Muchas veces, los datos acerca de un evento específico se tienen que pasar junto con el objeto ``Evento`` para que los escuchas tengan la información necesaria. En el caso del evento ``kernel.response``, el objeto ``Evento`` creado y pasado a cada escucha realmente es de tipo :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent`, una subclase del objeto ``Evento`` base. Esta clase contiene métodos como ``getResponse`` y ``setResponse``, que permiten a los escuchas recibir e incluso sustituir el objeto ``Respuesta``. - -La moraleja de la historia es la siguiente: Cuando creas un escucha para un evento, el objeto ``Evento`` que se pasa al escucha puede ser una subclase especial que tiene métodos adicionales para recuperar información desde y para responder al evento. - -El despachador -~~~~~~~~~~~~~~ - -El despachador es el objeto central del sistema despachador de eventos. En general, se crea un único despachador, el cual mantiene un registro de escuchas. Cuando se difunde un evento a través del despachador, este notifica a todos los escuchas registrados con ese evento. - -.. code-block:: php - - use Symfony\Component\EventDispatcher\EventDispatcher; - - $dispatcher = new EventDispatcher(); - -.. index:: - single: Despachador de evento; Escuchas - -Conectando escuchas -~~~~~~~~~~~~~~~~~~~ - -Para aprovechar las ventajas de un evento existente, es necesario conectar un escucha con el despachador para que pueda ser notificado cuando se despache el evento. Una llamada al método despachador ``addListener()`` asocia cualquier objeto *PHP* ejecutable a un evento: - -.. code-block:: php - - $listener = new AcmeListener(); - $dispatcher->addListener('foo.action', array($listener, 'onFooAction')); - -El método ``addListener()`` toma hasta tres argumentos: - -* El nombre del evento (cadena) que este escucha quiere atender; - -* Un objeto *PHP* ejecutable que será notificado cuando se produzca un evento al que está atento; - -* Un entero de prioridad opcional (mayor es igual a más importante) que determina cuando un escucha se activa frente a otros escuchas (por omisión es ``0``). Si dos escuchas tienen la misma prioridad, se ejecutan en el orden en que se agregaron al despachador. - -.. note:: - - Un `PHP ejecutable`_ es una variable *PHP* que la función ``call_user_func()`` puede utilizar y devuelve ``true`` cuando pasa a la función ``is_callable()``. Esta puede ser una instancia de ``\Closure``, un objeto que implementa un método ``__invoke`` (que es lo que ---de hecho--- hacen los cierres), una cadena que representa una función, o una matriz que representa a un método de un objeto o a un método de clase. - - Hasta ahora, hemos visto cómo los objetos *PHP* se pueden registrar como escuchas. También puedes registrar `Cierres`_ *PHP* como escuchas de eventos: - - .. code-block:: php - - use Symfony\Component\EventDispatcher\Event; - - $dispatcher->addListener('foo.action', function (Event $event) { - // se debe ejecutar al despachar el evento foo.action - }); - -Una vez que se registra el escucha en el despachador, este espera hasta que el evento sea notificado. En el ejemplo anterior, cuando se despacha el evento ``foo.action``, el despachador llama al método ``AcmeListener::onFooAction`` y pasa el objeto ``Evento`` como único argumento: - -.. code-block:: php - - use Symfony\Component\EventDispatcher\Event; - - class AcmeListener - { - // ... - - public function onFooAction(Event $event) - { - // Hace algo - } - } - -En muchos casos, una subclase especial ``Evento`` específica para el evento dado es pasada al escucha. Esto le da al escucha acceso a información especial sobre el evento. Consulta la documentación o la implementación de cada evento para determinar la instancia exacta de ``Symfony\Component\EventDispatcher\Event`` que se ha pasado. Por ejemplo, el evento ``kernel.event`` pasa una instancia de ``Symfony\Component\HttpKernel\Event\FilterResponseEvent``: - -.. code-block:: php - - use Symfony\Component\HttpKernel\Event\FilterResponseEvent - - public function onKernelResponse(FilterResponseEvent $event) - { - $response = $event->getResponse(); - $request = $event->getRequest(); - - // ... - } - -.. _event_dispatcher-closures-as-listeners: - -.. index:: - single: Despachador de evento; Creando y despachando un evento - -Creando y despachando un evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Además de registrar escuchas con eventos existentes, puedes crear y despachar tus propios eventos. Esto es útil cuando creas bibliotecas compartidas y también cuando deseas mantener flexibles y disociados de tu propio sistema diferentes componentes. - -La clase estática ``Events`` -............................ - -Supongamos que deseas crear un nuevo evento ---``store.order``--- el cual se despacha cada vez que es creada una orden dentro de tu aplicación. Para mantener las cosas organizadas, empieza por crear una clase ``StoreEvents`` dentro de tu aplicación que sirva para definir y documentar tu evento: - -.. code-block:: php - - namespace Acme\StoreBundle; - - final class StoreEvents - { - /** - * El evento 'store.order' es lanzado cada vez que se crea una orden - * en el sistema. - * - * El escucha del evento recibe una instancia de - * Acme\StoreBundle\Event\FilterOrderEvent. - * - * @var string - */ - const onStoreOrder = 'store.order'; - } - -Ten en cuenta que esta clase en realidad *no hace* nada. El propósito de la clase ``StoreEvents`` sólo es ser un lugar donde se pueda centralizar la información sobre los eventos comunes. Observa también que se pasará una clase especial ``FilterOrderEvent`` a cada escucha de este evento. - -Creando un objeto ``Evento`` -............................ - -Más tarde, cuando despaches este nuevo evento, debes crear una instancia del ``Evento`` y pasarla al despachador. Entonces el despachador pasa esta misma instancia a cada uno de los escuchas del evento. Si no necesitas pasar alguna información a tus escuchas, puedes utilizar la clase predeterminada ``Symfony\Component\EventDispatcher\Event``. La mayoría de las veces, sin embargo, *necesitarás* pasar información sobre el evento a cada escucha. Para lograrlo, vamos a crear una nueva clase que extiende a ``Symfony\Component\EventDispatcher\Event``. - -En este ejemplo, cada escucha tendrá acceso a algún objeto ``Order``. Crea una clase ``Evento`` que lo hace posible: - -.. code-block:: php - - namespace Acme\StoreBundle\Event; - - use Symfony\Component\EventDispatcher\Event; - use Acme\StoreBundle\Order; - - class FilterOrderEvent extends Event - { - protected $order; - - public function __construct(Order $order) - { - $this->order = $order; - } - - public function getOrder() - { - return $this->order; - } - } - -Ahora, cada escucha tiene acceso al objeto ``Order`` a través del método ``getOrder``. - -Despachando el evento -..................... - -El método :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch` notifica a todos los escuchas que el evento ha ocurrido. Este toma dos argumentos: el nombre del evento a despachar, y la instancia del ``Evento`` a pasar a cada escucha de ese evento: - -.. code-block:: php - - use Acme\StoreBundle\StoreEvents; - use Acme\StoreBundle\Order; - use Acme\StoreBundle\Event\FilterOrderEvent; - - // la orden de alguna manera es creada o recuperada - $order = new Order(); - // ... - - // crea el FilterOrderEvent y lo despacha - $event = new FilterOrderEvent($order); - $dispatcher->dispatch(StoreEvents::onStoreOrder, $event); - -Ten en cuenta que el objeto especial ``FilterOrderEvent`` se crea y pasa al método ``dispatch``. Ahora, cualquier escucha del evento ``store.order`` recibirá el ``FilterOrderEvent`` y tendrá acceso al objeto ``Order`` a través del método ``getOrder``: - -.. code-block:: php - - // alguna clase escucha que se ha registrado para onStoreOrder - use Acme\StoreBundle\Event\FilterOrderEvent; - - public function onStoreOrder(FilterOrderEvent $event) - { - $order = $event->getOrder(); - // haz algo para o con la orden - } - -Pasando el objeto despachador de evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si echas un vistazo a la clase ``EventDispatcher``, te darás cuenta de que la clase no actúa como una instancia única (no hay un método estático ``getInstance()``). -Esto es intencional, ya que posiblemente desees tener varios despachadores de eventos simultáneos en una sola petición *PHP*. Pero también significa que necesitas una manera de pasar el despachador a los objetos que necesitan conectar o notificar eventos. - -La mejor práctica consiste en inyectar el objeto despachador de eventos en tus objetos, también conocido como inyección de dependencias. - -Puedes usar la inyección del constructor:: - - use Symfony\Component\EventDispatcher\EventDispatcherInterface; - - class Foo - { - protected $dispatcher = null; - - public function __construct(EventDispatcherInterface $dispatcher) - { - $this->dispatcher = $dispatcher; - } - } - -O inyección en el definidor:: - - use Symfony\Component\EventDispatcher\EventDispatcherInterface; - - class Foo - { - protected $dispatcher = null; - - public function setEventDispatcher(EventDispatcherInterface $dispatcher) - { - $this->dispatcher = $dispatcher; - } - } - -La elección entre los dos realmente es cuestión de gusto. Muchos tienden a preferir el constructor de inyección porque los objetos son totalmente iniciados en tiempo de construcción. Pero cuando tienes una larga lista de dependencias, la inyección de definidores puede ser el camino a seguir, especialmente para dependencias opcionales. - -.. index:: - single: Despachador de evento; Suscriptores de evento - -Usando suscriptores de evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La forma más común para escuchar a un evento es registrar un *escucha de evento* con el despachador. Este escucha puede estar atento a uno o más eventos y ser notificado cada vez que se envían los eventos. - -Otra forma de escuchar eventos es a través de un *suscriptor de eventos*. Un suscriptor de eventos es una clase *PHP* que es capaz de decir al despachador exactamente a cuales eventos debe estar suscrito. Este implementa la interfaz :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`, que requiere un solo método estático llamado ``getSubscribedEvents``. Considera el siguiente ejemplo de un suscriptor que está inscrito a los eventos ``kernel.response`` y ``store.order``: - -.. code-block:: php - - namespace Acme\StoreBundle\Event; - - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Symfony\Component\HttpKernel\Event\FilterResponseEvent; - - class StoreSubscriber implements EventSubscriberInterface - { - static public function getSubscribedEvents() - { - return array( - 'kernel.response' => array( - array('onKernelResponsePre', 10), - array('onKernelResponseMid', 5), - array('onKernelResponsePost', 0), - ), - 'store.order' => array('onStoreOrder', 0), - ); - } - - public function onKernelResponsePre(FilterResponseEvent $event) - { - // ... - } - - public function onKernelResponseMid(FilterResponseEvent $event) - { - // ... - } - - public function onKernelResponsePost(FilterResponseEvent $event) - { - // ... - } - - public function onStoreOrder(FilterOrderEvent $event) - { - // ... - } - } - -Esto es muy similar a una clase escucha, salvo que la propia clase puede decir al despachador cuales eventos debe escuchar. Para registrar un suscriptor al despachador, utiliza el método :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::addSubscriber`: - -.. code-block:: php - - use Acme\StoreBundle\Event\StoreSubscriber; - - $subscriber = new StoreSubscriber(); - $dispatcher->addSubscriber($subscriber); - -El despachador registrará automáticamente al suscriptor para cada evento devuelto por el método ``getSubscribedEvents``. Este método devuelve una matriz indexada por el nombre del evento y cuyos valores son el nombre del método a llamar o una matriz compuesta por el nombre del método a llamar y la prioridad. El ejemplo anterior muestra cómo registrar varios métodos escucha para el mismo evento en un suscriptor, y, además muestra cómo transmitir la prioridad de cada uno de los métodos escucha. - -.. index:: - single: Despachador de evento; Deteniendo el flujo del evento - -.. _event_dispatcher-event-propagation: - -Deteniendo el flujo/propagación del evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En algunos casos, puede tener sentido que un escucha evite que se llame a otros escuchas. En otras palabras, el escucha tiene que poder decirle al despachador detenga la propagación del evento a todos los escuchas en el futuro (es decir, no notificar a más escuchas). Esto se puede lograr desde el interior de un escucha a través del método :method:`Symfony\\Component\\EventDispatcher\\Event::stopPropagation`: - -.. code-block:: php - - use Acme\StoreBundle\Event\FilterOrderEvent; - - public function onStoreOrder(FilterOrderEvent $event) - { - // ... - - $event->stopPropagation(); - } - -Ahora, cualquier escucha de ``store.order`` que no haya llamado aún, *no* será invocado. - -.. _`observador`: http://en.wikipedia.org/wiki/Observer_pattern -.. _`componente HttpKernel de Symfony2`: https://github.com/symfony/HttpKernel -.. _`Cierres`: http://www.php.net/manual/es/functions.anonymous.php -.. _`PHP ejecutable`: http://www.php.net/manual/es/language.pseudo-types.php#language.types.callback \ No newline at end of file diff --git a/_sources/components/event_dispatcher/container_aware_dispatcher.txt b/_sources/components/event_dispatcher/container_aware_dispatcher.txt deleted file mode 100644 index b009a53..0000000 --- a/_sources/components/event_dispatcher/container_aware_dispatcher.txt +++ /dev/null @@ -1,91 +0,0 @@ -.. index:: - single: Despachador de evento; Contenedor consciente del servicio - -Contenedor consciente del despachador de eventos -================================================ - -.. versionadded:: 2.1 - Esta característica se movió al componente ``EventDispatcher`` en *Symfony 2.1*. - -Introducción ------------- - -La clase :class:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher` es una implementación especial del despachador de eventos que es conectada al contenedor del servicio -que forma parte del :doc:`componente de Inyección de dependencias `. -Esto te permite especificar servicios como escuchas de evento volviendo extremadamente potente al despachador de eventos. - -Los servicios se cargan de manera diferida, lo cual significa que los servicios adjuntos como escuchas sólo se crearán si se despacha un evento que requiere de esos escuchas. - -Configurando ------------- - -La instalación es sencilla inyectando una :class:`Symfony\\Component\\DependencyInjection\\ContainerInterface` en :class:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher`:: - - use Symfony\Component\DependencyInjection\ContainerBuilder; - use Symfony\Component\EventDispatcher\ContainerAwareEventDispatcher; - - $container = new ContainerBuilder(); - $dispatcher = new ContainerAwareEventDispatcher($container); - -Añadiendo escuchas ------------------- - -El *contenedor consciente del despachador de eventos* puede cargar determinados servicios directamente, o servicios que implementan la :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`. - -Los siguientes ejemplos asumen que el contenedor de servicios se ha cargado con algunos -servicios ya mencionados. - -.. note:: - - Los servicios se tienen que marcar como públicos en el contenedor. - -Agregando servicios -~~~~~~~~~~~~~~~~~~~ - -Para conectar las definiciones de servicios existentes, usa el método :method:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher::addListenerService` dónde ``$callback`` es un arreglo de ``array($serviceId, $methodName)``:: - - $dispatcher->addListenerService($eventName, array('foo', 'logListener')); - -Agregando suscriptores de servicios -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes agregar ``EventSubscribers`` usando el método :method:`Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher::addSubscriberService` dónde el primer argumento es el identificador del suscriptor del servicio, y el segundo argumento es el nombre de la clase del servicio (el cual debe implementar la :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`) de la siguiente manera:: - - $dispatcher->addSubscriberService( - 'kernel.store_subscriber', - 'StoreSubscriber' - ); - -La ``EventSubscriberInterface`` será exactamente como era de esperar:: - - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - // ... - - class StoreSubscriber implements EventSubscriberInterface - { - public static function getSubscribedEvents() - { - return array( - 'kernel.response' => array( - array('onKernelResponsePre', 10), - array('onKernelResponsePost', 0), - ), - 'store.order' => array('onStoreOrder', 0), - ); - } - - public function onKernelResponsePre(FilterResponseEvent $event) - { - // ... - } - - public function onKernelResponsePost(FilterResponseEvent $event) - { - // ... - } - - public function onStoreOrder(FilterOrderEvent $event) - { - // ... - } - } diff --git a/_sources/components/event_dispatcher/generic_event.txt b/_sources/components/event_dispatcher/generic_event.txt deleted file mode 100644 index 1add79b..0000000 --- a/_sources/components/event_dispatcher/generic_event.txt +++ /dev/null @@ -1,98 +0,0 @@ -.. index:: - single: Despachador de evento - -El objeto evento genérico -========================= - -.. versionadded:: 2.1 - La clase del evento ``GenericEvent`` se añadió en *Symfony 2.1*. - -La clase base :class:`Symfony\\Component\\EventDispatcher\\Event` proporcionada por el componente ``Despachador de eventos`` deliberadamente es escasa para permitir la creación por herencia de la *API* de objetos ``Evento`` específicos usando programación orientada a objetos. Esto permite código elegante y fácil de leer en aplicaciones complejas. - -La clase :class:`Symfony\\Component\\EventDispatcher\\GenericEvent` está disponible por conveniencia para aquellos que quieran utilizar un solo objeto ``Evento`` en toda su aplicación. Este es adecuado para la mayoría de los propósitos fuera de la caja, puesto que sigue el patrón ``observador estándar`` en el que el objeto ``evento`` encapsula el ``tema`` de un ``evento``, pero que adicionalmente tiene argumentos opcionales extra. - -La clase :class:`Symfony\\Component\\EventDispatcher\\GenericEvent` tiene una *API* sencilla, además de la clase base :class:`Symfony\\Component\\EventDispatcher\\Event`: - -* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::__construct`: - El constructor toma el ``tema`` y argumentos del ``evento``; - -* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::getSubject`: - Obtiene el ``tema``; - -* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::setArgument`: - Define un argumento por ``clave``; - -* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::setArguments`: - Establece el arreglo de argumentos; - -* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::getArgument`: - Obtiene un argumento por ``clave``; - -* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::getArguments`: - Captador para todos los argumentos; - -* :method:`Symfony\\Component\\EventDispatcher\\GenericEvent::hasArgument`: - Devuelve ``true`` si existe el argumento ``clave``; - -El ``GenericEvent`` también implementa la clase :phpclass:`ArrayAccess` en los argumentos del evento, lo cual lo hace muy conveniente para pasar argumentos adicionales relacionados al ``tema`` del ``evento``. - -Los siguientes ejemplos muestran casos de uso para darte una idea general de su flexibilidad. -Los ejemplos asumen que los escuchas de eventos se han añadido al despachador. - -Basta con pasar un ``tema`` (``$subject``):: - - use Symfony\Component\EventDispatcher\GenericEvent; - - $event = GenericEvent($subject); - $dispatcher->dispatch('foo', $event); - - class FooListener - { - public function handler(GenericEvent $event) - { - if ($event->getSubject() instanceof Foo) { - // ... - } - } - } - -Pasando y procesando argumentos usando la *API* de :phpclass:`ArrayAccess` para acceder a los argumentos del ``evento``:: - - use Symfony\Component\EventDispatcher\GenericEvent; - - $event = new GenericEvent( - $subject, - array('type' => 'foo', 'counter' => 0) - ); - $dispatcher->dispatch('foo', $event); - - echo $event['counter']; - - class FooListener - { - public function handler(GenericEvent $event) - { - if (isset($event['type']) && $event['type'] === 'foo') { - // ... hace algo - } - - $event['counter']++; - } - } - -Filtrando datos:: - - use Symfony\Component\EventDispatcher\GenericEvent; - - $event = new GenericEvent($subject, array('data' => 'foo')); - $dispatcher->dispatch('foo', $event); - - echo $event['data']; - - class FooListener - { - public function filter(GenericEvent $event) - { - strtolower($event['data']); - } - } diff --git a/_sources/components/event_dispatcher/index.txt b/_sources/components/event_dispatcher/index.txt deleted file mode 100644 index f9fb4db..0000000 --- a/_sources/components/event_dispatcher/index.txt +++ /dev/null @@ -1,9 +0,0 @@ -Despachador de eventos -====================== - -.. toctree:: - :maxdepth: 2 - - introduction - generic_event - container_aware_dispatcher diff --git a/_sources/components/event_dispatcher/introduction.txt b/_sources/components/event_dispatcher/introduction.txt deleted file mode 100644 index ba24e0d..0000000 --- a/_sources/components/event_dispatcher/introduction.txt +++ /dev/null @@ -1,470 +0,0 @@ -.. index:: - single: Despachador de eventos - single: Componentes; EventDispatcher - -El componente despachador de eventos -==================================== - -Introducción ------------- - -El paradigma orientado a objetos ha recorrido un largo camino para garantizar la extensibilidad del código. Al crear clases que tienen responsabilidades bien definidas, el código se vuelve más flexible y un desarrollador lo puede extender con subclases para modificar su comportamiento. Pero si quieres compartir tus cambios con otros desarrolladores que también han hecho sus propias subclases, es discutible que la herencia de código sea la respuesta. - -Consideremos un ejemplo del mundo real en el que deseas proporcionar un sistema de complementos a tu proyecto. Un complemento debe ser capaz de agregar métodos, o hacer algo antes o después de ejecutar un método, sin interferir con otros complementos. Este no es un problema fácil de resolver con la herencia simple y herencia múltiple (en caso de que fuera posible con *PHP*) tiene sus propios inconvenientes. - -El despachador de eventos de *Symfony2* implementa el patrón `observador`_ en una manera sencilla y efectiva para hacer todo esto posible y para realmente hacer extensibles tus proyectos. - -Estudia un sencillo ejemplo desde el :doc:`/components/http_kernel/introduction`. Una vez creado un objeto ``Respuesta``, puede ser útil permitir que otros elementos en el sistema lo modifiquen (por ejemplo, añadan algunas cabeceras de caché) antes de utilizarlo realmente. Para hacer esto posible, el núcleo de *Symfony2* lanza un evento ---``kernel.response``---. Así es como funciona: - -* Un *escucha* (objeto *PHP*) le dice a un objeto *despachador* central que quiere escuchar el evento ``kernel.response``; - -* En algún momento, el núcleo de *Symfony2* dice al objeto *despachador* que difunda el evento ``kernel.response``, pasando con este un objeto ``Evento`` que tiene acceso al objeto ``Respuesta``; - -* El despachador notifica a (es decir, llama a un método en) todos los escuchas del evento ``kernel.response``, permitiendo que cada uno de ellos haga modificaciones al objeto ``Respuesta``. - -.. index:: - single: Despachador de evento; Eventos - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/EventDispatcher); -* :doc:`Instalándolo vía Composer ` (``symfony/event-dispatcher`` en `Packagist`_). - -Usando ------- - -Eventos -~~~~~~~ - -Cuando se envía un evento, es identificado por un nombre único (por ejemplo, ``kernel.response``), al que cualquier cantidad de escuchas podría estar atento. También se crea una instancia de :class:`Symfony\\Component\\EventDispatcher\\Event` y se pasa a todos los escuchas. Como veremos más adelante, el objeto ``Evento`` mismo, a menudo contiene datos sobre cuando se despachó el evento. - -.. index:: - pair: Despachador de evento; Convenciones de nomenclatura - -Convenciones de nomenclatura -............................ - -El nombre único del evento puede ser cualquier cadena, pero opcionalmente sigue una serie de convenciones de nomenclatura simples: - -* Sólo usa letras minúsculas, números, puntos (``.``) y guiones bajos (``_``); - -* Prefija los nombres con un espacio de nombres seguido de un punto (por ejemplo, ``kernel.``); - -* Termina los nombres con un verbo que indica qué acción se está tomando (por ejemplo, ``request``). - -Estos son algunos ejemplos de nombres de evento aceptables: - -* ``kernel.response`` -* ``form.pre_set_data`` - -.. index:: - single: Despachador de evento; Subclases de evento - -Nombres de evento y objetos evento -.................................. - -Cuando el despachador notifica a los escuchas, este pasa un objeto ``Evento`` real a los escuchas. La clase base ``Evento`` es muy simple: contiene un método para detener la :ref:`propagación del evento `, pero no mucho más. - -Muchas veces, los datos acerca de un evento específico se tienen que pasar junto con el objeto ``Evento`` para que los escuchas tengan la información necesaria. En el caso del evento ``kernel.response``, el objeto ``Evento`` creado y pasado a cada escucha realmente es de tipo :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent`, una subclase del objeto ``Evento`` base. Esta clase contiene métodos como ``getResponse`` y ``setResponse``, que permiten a los escuchas recibir e incluso sustituir el objeto ``Respuesta``. - -La moraleja de la historia es la siguiente: Cuando creas un escucha para un evento, el objeto ``Evento`` que se pasa al escucha puede ser una subclase especial que tiene métodos adicionales para recuperar información desde y para responder al evento. - -El despachador -~~~~~~~~~~~~~~ - -El despachador es el objeto central del sistema despachador de eventos. En general, se crea un único despachador, el cual mantiene un registro de escuchas. Cuando se difunde un evento a través del despachador, este notifica a todos los escuchas registrados a ese evento:: - - use Symfony\Component\EventDispatcher\EventDispatcher; - - $dispatcher = new EventDispatcher(); - -.. index:: - single: Despachador de evento; Escuchas - -Conectando escuchas -~~~~~~~~~~~~~~~~~~~ - -Para aprovechar las ventajas de un evento existente, es necesario conectar un escucha con el despachador para que pueda ser notificado cuando se despache el evento. Una llamada al método despachador ``addListener()`` asocia cualquier objeto *PHP* ejecutable a un evento:: - - $listener = new AcmeListener(); - $dispatcher->addListener('foo.action', array($listener, 'onFooAction')); - -El método ``addListener()`` toma hasta tres argumentos: - -* El nombre del evento (cadena) que este escucha quiere atender; - -* Un objeto *PHP* ejecutable que será notificado cuando se produzca un evento al que está atento; - -* Un entero de prioridad opcional (mayor es igual a más importante) que determina cuando un escucha se activa frente a otros escuchas (por omisión es ``0``). Si dos escuchas tienen la misma prioridad, se ejecutan en el orden en que se agregaron al despachador. - -.. note:: - - Un `PHP ejecutable`_ es una variable *PHP* que la función ``call_user_func()`` puede utilizar y devuelve ``true`` cuando pasa a la función ``is_callable()``. Esta puede ser una instancia de ``\Closure``, un objeto que implementa un método ``__invoke`` (que es lo que ---de hecho--- hacen los cierres), una cadena que representa una función, o un arreglo que representa a un método de un objeto o a un método de clase. - - Hasta ahora, hemos visto cómo los objetos *PHP* se pueden registrar como escuchas. También puedes registrar `Cierres`_ *PHP* como escuchas de eventos:: - - use Symfony\Component\EventDispatcher\Event; - - $dispatcher->addListener('foo.action', function (Event $event) { - // se debe ejecutar al despachar el evento foo.action - }); - -Una vez que se registra el escucha en el despachador, este espera hasta que el evento sea notificado. En el ejemplo anterior, cuando se despacha el evento ``foo.action``, el despachador llama al método ``AcmeListener::onFooAction`` y le pasa el objeto ``Evento`` como único argumento:: - - use Symfony\Component\EventDispatcher\Event; - - class AcmeListener - { - // ... - - public function onFooAction(Event $event) - { - // ... hace algo - } - } - -En muchos casos, una subclase especial ``Evento`` específica para el evento dado es pasada al escucha. Esto le da al escucha acceso a información especial sobre el evento. Consulta la documentación o la implementación de cada evento para determinar la instancia exacta de ``Symfony\Component\EventDispatcher\Event`` que se ha pasado. Por ejemplo, el evento ``kernel.event`` pasa una instancia de ``Symfony\Component\HttpKernel\Event\FilterResponseEvent``:: - - use Symfony\Component\HttpKernel\Event\FilterResponseEvent; - - public function onKernelResponse(FilterResponseEvent $event) - { - $response = $event->getResponse(); - $request = $event->getRequest(); - - // ... - } - -.. _event_dispatcher-closures-as-listeners: - -.. index:: - single: Despachador de evento; Creando y despachando un evento - -Creando y despachando un evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Además de registrar escuchas con eventos existentes, puedes crear y despachar tus propios eventos. Esto es útil cuando creas bibliotecas compartidas y también cuando deseas mantener flexibles y disociados de tu propio sistema diferentes componentes. - -La clase estática ``Events`` -............................ - -Supongamos que deseas crear un nuevo evento ---``store.order``--- el cual se despacha cada vez que es creada una orden dentro de tu aplicación. Para mantener las cosas organizadas, empieza por crear una clase ``StoreEvents`` dentro de tu aplicación que sirva para definir y documentar tu evento:: - - namespace Acme\StoreBundle; - - final class StoreEvents - { - /** - * El evento «store.order» es lanzado cada vez que se crea una orden - * en el sistema. - * - * El escucha del evento recibe una - * instancia de Acme\StoreBundle\Event\FilterOrderEvent. - * - * @var string - */ - const STORE_ORDER = 'store.order'; - } - -Ten en cuenta que esta clase en realidad *no hace* nada. El propósito de la clase ``StoreEvents`` sólo es ser un lugar donde se pueda centralizar la información sobre los eventos comunes. Observa también que se pasará una clase especial ``FilterOrderEvent`` a cada escucha de este evento. - -Creando un objeto ``Evento`` -............................ - -Más tarde, cuando despaches este nuevo evento, debes crear una instancia del ``Evento`` y pasarla al despachador. Entonces el despachador pasa esta misma instancia a cada uno de los escuchas del evento. Si no necesitas pasar alguna información a tus escuchas, puedes utilizar la clase predeterminada ``Symfony\Component\EventDispatcher\Event``. La mayoría de las veces, sin embargo, *necesitarás* pasar información sobre el evento a cada escucha. Para lograrlo, vamos a crear una nueva clase que extiende a ``Symfony\Component\EventDispatcher\Event``. - -En este ejemplo, cada escucha tendrá acceso a algún objeto ``Order``. Crea una clase ``Evento`` que lo hace posible:: - - namespace Acme\StoreBundle\Event; - - use Symfony\Component\EventDispatcher\Event; - use Acme\StoreBundle\Order; - - class FilterOrderEvent extends Event - { - protected $order; - - public function __construct(Order $order) - { - $this->order = $order; - } - - public function getOrder() - { - return $this->order; - } - } - -Ahora, cada escucha tiene acceso al objeto ``Order`` a través del método ``getOrder``. - -Despachando el evento -..................... - -El método :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::dispatch` notifica a todos los escuchas que el evento ha ocurrido. Este toma dos argumentos: el nombre del evento a despachar, y la instancia del ``Evento`` a pasar a cada escucha de ese evento:: - - use Acme\StoreBundle\StoreEvents; - use Acme\StoreBundle\Order; - use Acme\StoreBundle\Event\FilterOrderEvent; - - // la orden de alguna manera es creada o recuperada - $order = new Order(); - // ... - - // crea el FilterOrderEvent y lo despacha - $event = new FilterOrderEvent($order); - $dispatcher->dispatch(StoreEvents::STORE_ORDER, $event); - -Ten en cuenta que el objeto especial ``FilterOrderEvent`` se crea y pasa al método ``dispatch``. Ahora, cualquier escucha del evento ``store.order`` recibirá el ``FilterOrderEvent`` y tendrá acceso al objeto ``Order`` a través del método ``getOrder``:: - - // alguna clase escucha registrada al evento "STORE_ORDER" - use Acme\StoreBundle\Event\FilterOrderEvent; - - public function onStoreOrder(FilterOrderEvent $event) - { - $order = $event->getOrder(); - // haz algo para o con la orden - } - -.. index:: - single: Despachador de evento; Suscriptores de evento - -.. _event_dispatcher-using-event-subscribers: - -Usando suscriptores de evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La forma más común para escuchar a un evento es registrar un *escucha de evento* con el despachador. Este escucha puede estar atento a uno o más eventos y ser notificado cada vez que se envían los eventos. - -Otra forma de escuchar eventos es a través de un *suscriptor de eventos*. Un suscriptor de eventos es una clase *PHP* que es capaz de decir al despachador exactamente a cuales eventos debe estar suscrito. Este implementa la interfaz :class:`Symfony\\Component\\EventDispatcher\\EventSubscriberInterface`, que requiere un solo método estático llamado ``getSubscribedEvents``. Considera el siguiente ejemplo de un suscriptor que está inscrito a los eventos ``kernel.response`` y ``store.order``:: - - namespace Acme\StoreBundle\Event; - - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - use Symfony\Component\HttpKernel\Event\FilterResponseEvent; - - class StoreSubscriber implements EventSubscriberInterface - { - public static function getSubscribedEvents() - { - return array( - 'kernel.response' => array( - array('onKernelResponsePre', 10), - array('onKernelResponseMid', 5), - array('onKernelResponsePost', 0), - ), - 'store.order' => array('onStoreOrder', 0), - ); - } - - public function onKernelResponsePre(FilterResponseEvent $event) - { - // ... - } - - public function onKernelResponseMid(FilterResponseEvent $event) - { - // ... - } - - public function onKernelResponsePost(FilterResponseEvent $event) - { - // ... - } - - public function onStoreOrder(FilterOrderEvent $event) - { - // ... - } - } - -Esto es muy similar a una clase escucha, salvo que la propia clase puede decir al despachador cuales eventos debe escuchar. Para registrar un suscriptor al despachador, utiliza el método :method:`Symfony\\Component\\EventDispatcher\\EventDispatcher::addSubscriber`:: - - use Acme\StoreBundle\Event\StoreSubscriber; - - $subscriber = new StoreSubscriber(); - $dispatcher->addSubscriber($subscriber); - -El despachador registrará automáticamente al suscriptor para cada evento devuelto por el método ``getSubscribedEvents``. Este método devuelve un arreglo indexado por el nombre del evento y cuyos valores son el nombre del método a llamar o un arreglo compuesto por el nombre del método a llamar y la prioridad. El ejemplo anterior muestra cómo registrar varios métodos escucha para el mismo evento en un suscriptor, y, además muestra cómo transmitir la prioridad de cada uno de los métodos escucha. -The higher the priority, the earlier the method is called. In the above -example, when the ``kernel.response`` event is triggered, the methods -``onKernelResponsePre``, ``onKernelResponseMid``, and ``onKernelResponsePost`` -are called in that order. - -.. index:: - single: Despachador de evento; Deteniendo el flujo del evento - -.. _event_dispatcher-event-propagation: - -Deteniendo el flujo/propagación del evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En algunos casos, puede tener sentido que un escucha evite que se llame a otros escuchas. En otras palabras, el escucha tiene que poder decirle al despachador detenga la propagación del evento a todos los escuchas en el futuro (es decir, no notificar a más escuchas). Esto se puede lograr desde el interior de un escucha a través del método :method:`Symfony\\Component\\EventDispatcher\\Event::stopPropagation`:: - - use Acme\StoreBundle\Event\FilterOrderEvent; - - public function onStoreOrder(FilterOrderEvent $event) - { - // ... - - $event->stopPropagation(); - } - -Ahora, cualquier escucha de ``store.order`` que no haya llamado aún, *no* será invocado. - -Es posible detectar si un evento fue detenido utilizando el método :method:`Symfony\\Component\\EventDispatcher\\Event::isPropagationStopped` que devuelve un valor booleano:: - - $dispatcher->dispatch('foo.event', $event); - if ($event->isPropagationStopped()) { - // ... - } - -.. index:: - single: Despachador de evento; Despachador de eventos consciente de eventos y escuchas - -.. _event_dispatcher-dispatcher-aware-events: - -El ``EventDispatcher`` está consciente de eventos y escuchas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El objeto ``Event`` contiene una referencia al despachador que lo invocó desde *Symfony 2.1* - -El ``EventDispatcher`` siempre inyecta una referencia a sí mismo en el objeto evento que se le pasa. Esto significa que todos los escuchas tienen acceso directo al objeto ``EventDispatcher`` que notifica al escucha a través del método :method:`Symfony\\Component\\EventDispatcher\\Event::getDispatcher` del objeto ``Event`` transmitido. - -Esto puede llevar a algunas aplicaciones avanzadas del ``EventDispatcher`` incluyendo dejar que los escuchas envíen otros eventos, encadenando eventos o incluso cargando escuchas de manera diferida en el objeto ``Despachador``. Algunos ejemplos: - -Cargando escuchas de manera diferida:: - - - use Symfony\Component\EventDispatcher\Event; - use Acme\StoreBundle\Event\StoreSubscriber; - - class Foo - { - private $started = false; - - public function myLazyListener(Event $event) - { - if (false === $this->started) { - $subscriber = new StoreSubscriber(); - $event->getDispatcher()->addSubscriber($subscriber); - } - - $this->started = true; - - // ... más código - } - } - -Despachando otro event desde un escucha:: - - use Symfony\Component\EventDispatcher\Event; - - class Foo - { - public function myFooListener(Event $event) - { - $event->getDispatcher()->dispatch('log', $event); - - // ... más código - } - } - -Si bien lo anterior es suficiente para la mayoría de los casos, si tu aplicación utiliza múltiples -instancias del ``EventDispatcher``, posiblemente tengas que inyectar específicamente una -instancia conocida del ``EventDispatcher`` en tus escuchas. Esto se podría hacer inyectándolo en el constructor o con un definidor de la siguiente manera: - -Inyección en el constructor:: - - use Symfony\Component\EventDispatcher\EventDispatcherInterface; - - class Foo - { - protected $dispatcher = null; - - public function __construct(EventDispatcherInterface $dispatcher) - { - $this->dispatcher = $dispatcher; - } - } - -O inyección en el definidor:: - - use Symfony\Component\EventDispatcher\EventDispatcherInterface; - - class Foo - { - protected $dispatcher = null; - - public function setEventDispatcher(EventDispatcherInterface $dispatcher) - { - $this->dispatcher = $dispatcher; - } - } - -La elección entre los dos realmente es cuestión de gusto. Muchos tienden a preferir el constructor de inyección porque los objetos son totalmente iniciados en tiempo de construcción. Pero cuando tienes una larga lista de dependencias, la inyección de definidores puede ser el camino a seguir, especialmente para dependencias opcionales. - -.. index:: - single: Despachador de evento; Atajos del despachador - -.. _event_dispatcher-shortcuts: - -Atajos del despachador -~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El método ``EventDispatcher::dispatch()`` devuelve el evento desde *Symfony 2.1*. - -El Método :method:`EventDispatcher::dispatch ` siempre devuelve un objeto :class:`Symfony\\Component\\EventDispatcher\\Event`. Este permite varios atajos. Por ejemplo, si uno no necesita un objeto evento personalizado, simplemente puedes confiar en un simple objeto :class:`Symfony\\Component\\EventDispatcher\\Event`. Ni siquiera lo tienes que pasar al despachador ya que de manera predeterminada creará uno a menos que específicamente le pases uno:: - - $dispatcher->dispatch('foo.event'); - -Por otra parte, el ``EventDispatcher`` siempre devuelve cualquier objeto ``evento`` que le hayas enviado, es decir, ya sea el evento que le hayas pasado o la instancia que internamente haya creado el despachador. Esto da pie a bonitos accesos directos:: - - if (!$dispatcher->dispatch('foo.event')->isPropagationStopped()) { - // ... - } - -O:: - - $barEvent = new BarEvent(); - $bar = $dispatcher->dispatch('bar.event', $barEvent)->getBar(); - -O:: - - $response = $dispatcher->dispatch('bar.event', new BarEvent())->getBar(); - -y así sucesivamente... - -.. index:: - single: Despachador de evento; Inspeccionando el nombre del evento - -.. _event_dispatcher-event-name-introspection: - -introspección el nombre del evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El nombre del evento se añadió al objeto ``Event`` desde *Symfony 2.1* - -Puesto que el ``EventDispatcher`` ya sabe el nombre del evento al despacharlo, el nombre del evento también se inyecta en los objetos :class:`Symfony\\Component\\EventDispatcher\\Event`, poniéndolo a disposición de los escuchas de eventos a través del método :method:`Symfony\\Component\\EventDispatcher\\Event::getName`. - -El nombre del evento, (como con cualquier otro dato en un objeto evento personalizado) se puede utilizar como parte de la lógica de procesamiento del escucha:: - - use Symfony\Component\EventDispatcher\Event; - - class Foo - { - public function myEventListener(Event $event) - { - echo $event->getName(); - } - } - -.. _`observador`: http://en.wikipedia.org/wiki/Observer_pattern -.. _`Cierres`: http://www.php.net/manual/es/functions.anonymous.php -.. _`PHP ejecutable`: http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback -.. _`Packagist`: https://packagist.org/packages/symfony/event-dispatcher diff --git a/_sources/components/filesystem.txt b/_sources/components/filesystem.txt deleted file mode 100644 index 45ccb1a..0000000 --- a/_sources/components/filesystem.txt +++ /dev/null @@ -1,215 +0,0 @@ -.. index:: - single: Filesystem - -El componente ``Filesystem`` -============================ - - El componente ``Filesystem`` proporciona herramientas básicas para el sistema de archivos. - -.. versionadded:: 2.1 - El componente ``Filesystem`` es una novedad en *Symfony 2.1*. Anteriormente, la clase ``Filesystem`` se encontraba en el componente ``HttpKernel``. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Filesystem); -* Instalándolo vía ``Composer`` (``symfony/filesystem`` en `Packagist`_). - -Usando ------- - -La clase :class:`Symfony\\Component\\Filesystem\\Filesystem` es el único punto final para las operaciones del sistema de archivos:: - - use Symfony\Component\Filesystem\Filesystem; - use Symfony\Component\Filesystem\Exception\IOException; - - $fs = new Filesystem(); - - try { - $fs->mkdir('/tmp/random/dir/' . mt_rand()); - } catch (IOException $e) { - echo "An error occurred while creating your directory"; - } - -.. note:: - - Los métodos :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir`, :method:`Symfony\\Component\\Filesystem\\Filesystem::exists`, :method:`Symfony\\Component\\Filesystem\\Filesystem::touch`, :method:`Symfony\\Component\\Filesystem\\Filesystem::remove`, :method:`Symfony\\Component\\Filesystem\\Filesystem::chmod`, :method:`Symfony\\Component\\Filesystem\\Filesystem::chown` y :method:`Symfony\\Component\\Filesystem\\Filesystem::chgrp` pueden recibir una cadena, un arreglo o cualquier objeto que implemente :phpclass:`Traversable` como el argumento ``destino``. - - -``Mkdir`` -~~~~~~~~~ - -``Mkdir`` crea directorios. En sistemas de archivos *POSIX*, de manera predeterminada los directorios se crean con un valor de `0777` para el modo. Puedes utilizar el segundo argumento para establecer tu propio modo:: - - $fs->mkdir('/tmp/photos', 0700); - -.. note:: - - Puedes pasar como primer argumento un arreglo o cualquier otro objeto :phpclass:`Traversable`. - -``Exists`` -~~~~~~~~~~ - -``Exists`` comprueba la presencia de todos los archivos o directorios y devuelve ``false`` si falta un archivo:: - - // este directorio existe, devuelve true - $fs->exists('/tmp/photos'); - - // rabbit.jpg existe, bottle.png no existe, devuelve false - $fs->exists(array('rabbit.jpg', 'bottle.png')); - -.. note:: - - Puedes pasar como primer argumento un arreglo o cualquier otro objeto :phpclass:`Traversable`. - -``Copy`` -~~~~~~~~ - -Este método se utiliza para copiar archivos. If the target already exists, the file is -copied only if the source modification date is later than the target. Puedes redefinir este comportamiento con el tercer argumento booleano:: - - // sólo funciona si image-ICC.jpg se modificó después de image.jpg - $fs->copy('image-ICC.jpg', 'image.jpg'); - - // image.jpg será reemplazada - $fs->copy('image-ICC.jpg', 'image.jpg', true); - -``Touch`` -~~~~~~~~~ - -``Touch`` establece el acceso y la hora de modificación de un archivo. De manera predeterminada utiliza la hora actual. Puedes configurar la tuya con el segundo argumento. El tercer argumento es la hora de acceso:: - - // establece la hora de modificación a la fecha y hora actual - $fs->touch('file.txt'); - // establece la hora de modificación 10 segundos en el futuro - $fs->touch('file.txt', time() + 10); - // establece la hora de acceso 10 segundos en el pasado - $fs->touch('file.txt', time(), time() - 10); - -.. note:: - - Puedes pasar como primer argumento un arreglo o cualquier otro objeto :phpclass:`Traversable`. - -``Chown`` -~~~~~~~~~ - -Utiliza ``chown`` para cambiar el propietario de un archivo. El tercer argumento es una opción booleana recursiva:: - - // establece el propietario del video lolcat a www-data - $fs->chown('lolcat.mp4', 'www-data'); - // cambia recursivamente el propietario del directorio video - $fs->chown('/video', 'www-data', true); - -.. note:: - - Puedes pasar como primer argumento un arreglo o cualquier otro objeto :phpclass:`Traversable`. - -``Chgrp`` -~~~~~~~~~ - -Utiliza ``chgrp`` para cambiar el grupo de un archivo. El tercer argumento es una opción booleana recursiva:: - - // establecer el grupo del vídeo lolcat a nginx - $fs->chgrp('lolcat.mp4', 'nginx'); - // cambia recursivamente el grupo del directorio video - $fs->chgrp('/video', 'nginx', true); - - -.. note:: - - Puedes pasar como primer argumento un arreglo o cualquier otro objeto :phpclass:`Traversable`. - -``Chmod`` -~~~~~~~~~ - -``Chmod`` se utiliza para cambiar el modo de un archivo. The fourth argument is a boolean -recursive option:: - - // establece el modo de vídeo a 0600 - $fs->chmod('video.ogg', 0600); - // cambia recursivamente el modo del directorio src - $fs->chmod('src', 0700, 0000, true); - -.. note:: - - Puedes pasar como primer argumento un arreglo o cualquier otro objeto :phpclass:`Traversable`. - -``Remove`` -~~~~~~~~~~ - -``Remove`` te permite eliminar fácilmente archivos, enlaces simbólicos y directorios:: - - $fs->remove(array('symlink', '/ruta/al/directorio', 'activity.log')); - -.. note:: - - Puedes pasar como primer argumento un arreglo o cualquier otro objeto :phpclass:`Traversable`. - -``Rename`` -~~~~~~~~~~ - -Utiliza ``rename`` para cambiar el nombre de archivos y directorios:: - - // renombra un archivo - $fs->rename('/tmp/processed_video.ogg', '/ruta/a/tienda/video_647.ogg'); - // renombra un directorio - $fs->rename('/tmp/files', '/ruta/a/tienda/files'); - -``symlink`` -~~~~~~~~~~~ - -Crea un enlace simbólico desde fuente hasta destino. Si el sistema de archivos no es compatible con los enlaces simbólicos, hay disponible un tercer argumento booleano:: - - // crea un enlace simbólico - $fs->symlink('/ruta/a/source', '/ruta/a/destination'); - // duplica el directorio fuente, si el sistema de archivos no - // es compatible con enlaces simbólicos - $fs->symlink('/ruta/a/source', '/ruta/a/destination', true); - -``makePathRelative`` -~~~~~~~~~~~~~~~~~~~~ - -Devuelve la ruta relativa de un directorio dado:: - - // devuelve '../' - $fs->makePathRelative( - '/var/lib/symfony/src/Symfony/', - '/var/lib/symfony/src/Symfony/Component' - ); - // devuelve 'videos' - $fs->makePathRelative('/tmp', '/tmp/videos'); - -``mirror`` -~~~~~~~~~~ - -Refleja un directorio:: - - $fs->mirror('/ruta/a/source', '/ruta/a/target'); - -``isAbsolutePath`` -~~~~~~~~~~~~~~~~~~ - -``isAbsolutePath`` devuelve ``true`` si la ruta especificada es absoluta, ``false`` en caso contrario:: - - // devuelve true - $fs->isAbsolutePath('/tmp'); - // devuelve true - $fs->isAbsolutePath('c:\\Windows'); - // devuelve false - $fs->isAbsolutePath('tmp'); - // devuelve false - $fs->isAbsolutePath('../dir'); - -Manejando errores ------------------ - -Cuando sucede algo malo, se lanza una excepción que implementa la clase -:class:`Symfony\\Component\\Filesystem\\Exception\\ExceptionInterface`. - -.. note:: - - Antes de la versión 2.1, :method:`Symfony\\Component\\Filesystem\\Filesystem::mkdir` devolvía un valor lógico y no lanzaba excepciones. A partir de 2.1, se lanza una :class:`Symfony\\Component\\Filesystem\\Exception\\IOException` si falla la creación del directorio. - -.. _`Packagist`: https://packagist.org/packages/symfony/filesystem diff --git a/_sources/components/finder.txt b/_sources/components/finder.txt deleted file mode 100644 index bb6237b..0000000 --- a/_sources/components/finder.txt +++ /dev/null @@ -1,284 +0,0 @@ -.. index:: - single: Finder - single: Componentes; Finder - -El componente ``Finder`` -======================== - - El componente ``Finder`` busca archivos y directorios a través de una sencilla e intuitiva interfaz. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Finder); -* :doc:`Instalándolo vía Composer ` (``symfony/finder`` en `Packagist`_). - -Usando ------- - -La clase :class:`Symfony\\Component\\Finder\\Finder` busca archivos y/o -directorios:: - - use Symfony\Component\Finder\Finder; - - $finder = new Finder(); - $finder->files()->in(__DIR__); - - foreach ($finder as $file) { - // Imprime la ruta absoluta - print $file->getRealpath()."\n"; - - // Imprime la ruta relativa al archivo, omitiendo el nombre de archivo - print $file->getRelativePath()."\n"; - - // Imprime la ruta relativa para el archivo - print $file->getRelativePathname()."\n"; - } - -``$file`` es una instancia de :class:`Symfony\\Component\\Finder\\SplFileInfo` la cual extiende a :phpclass:`SplFileInfo` para proporcionar métodos para trabajar con rutas relativas. - -El código anterior imprime recursivamente los nombres de todos los archivos en el directorio actual. La clase ``Finder`` utiliza una interfaz fluida, por lo que todos los métodos devuelven la instancia del ``Finder``. - -.. tip:: - - Una instancia del ``Finder`` es un :phpclass:`Iterator` de *PHP*. Por lo tanto, en lugar de iterar sobre el ``Finder`` con ``foreach``, también lo puedes convertir en un arreglo con el método :phpfunction:`iterator_to_array` u obtener el número de elementos con :phpfunction:`iterator_count`. - -.. caution:: - - Al buscar a través de múltiples ubicaciones pasadas al método :method:`Symfony\\Component\\Finder\\Finder::in`, internamente se crea un iterador independiente por cada ubicación. Esto significa que tienes múltiples resultados agregados a uno. - Debido a que :phpfunction:`iterator_to_array` de manera predefinida usa el conjunto de claves del resultado, al convertirlo en arreglo, algunas claves se podrían duplicar y por lo tanto sustituir sus valores. Esto se puede evitar pasando ``false`` como segundo argumento a :phpfunction:`iterator_to_array`. - -Criterios ---------- - -Hay muchas maneras de filtrar y ordenar tu resultado. - -Ubicación -~~~~~~~~~ - -La ubicación es el único criterio obligatorio. Este dice al buscador cual directorio utilizar para la búsqueda:: - - $finder->in(__DIR__); - -Busca en varios lugares encadenando llamadas al método :method:`Symfony\\Component\\Finder\\Finder::in`:: - - $finder->files()->in(__DIR__)->in('/elsewhere'); - -.. versionadded:: 2.2 - El soporte necesario para el comodín se añadió en la versión 2.2. - -Usa caracteres comodín para buscar en los directorios que emparejen un patrón:: - - $finder->in('src/Symfony/*/*/Resources'); - -Cada patrón tiene que resolver cuando menos con una ruta de directorio. - -Excluye directorios coincidentes con el método :method:`Symfony\\Component\\Finder\\Finder::exclude`:: - - $finder->in(__DIR__)->exclude('ruby'); - -Debido a que ``Finder`` utiliza iteradores *PHP*, puedes pasar cualquier *URL* con un `protocolo`_ compatible:: - - $finder->in('ftp://ejemplo.com/pub/'); - -Y también trabaja con flujos definidos por el usuario:: - - use Symfony\Component\Finder\Finder; - - $s3 = new \Zend_Service_Amazon_S3($key, $secret); - $s3->registerStreamWrapper("s3"); - - $finder = new Finder(); - $finder->name('photos*')->size('< 100K')->date('since 1 hour ago'); - foreach ($finder->in('s3://bucket-name') as $file) { - // ... hace algo - - print $file->getFilename()."\n"; - } - -.. note:: - - Lee la documentación de `Flujos`_ para aprender a crear tus propios flujos. - -Archivos o directorios -~~~~~~~~~~~~~~~~~~~~~~ - -De manera predeterminada, ``Finder`` devuelve archivos y directorios; Pero los métodos :method:`Symfony\\Component\\Finder\\Finder::files` y -:method:`Symfony\\Component\\Finder\\Finder::directories` controlan:: - - $finder->files(); - - $finder->directories(); - -Si quieres seguir los enlaces, utiliza el método ``followLinks()``:: - - $finder->files()->followLinks(); - -De forma predeterminada, el iterador ignora archivos VCS populares. Esto se puede cambiar con el método ``ignoreVCS()``:: - - $finder->ignoreVCS(false); - -Ordenación -~~~~~~~~~~ - -Ordena el resultado por nombre o por tipo (primero directorios, luego archivos):: - - $finder->sortByName(); - - $finder->sortByType(); - -.. note:: - - Ten en cuenta que los métodos ``sort*`` necesitan obtener todos los elementos para hacer su trabajo. Para iteradores grandes, es lento. - -También puedes definir tu propio algoritmo de ordenación con el método ``sort()``:: - - $sort = function (\SplFileInfo $a, \SplFileInfo $b) - { - return strcmp($a->getRealpath(), $b->getRealpath()); - }; - - $finder->sort($sort); - -Nombre de archivo -~~~~~~~~~~~~~~~~~ - -Filtra archivos por nombre con el método :method:`Symfony\\Component\\Finder\\Finder::name`:: - - $finder->files()->name('*.php'); - -El método ``name()`` acepta globos, cadenas o expresiones regulares:: - - $finder->files()->name('/\.php$/'); - -El método ``notName()`` excluye archivos coincidentes con un patrón:: - - $finder->files()->notName('*.rb'); - -Contenido del archivo -~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - Los métodos ``contains()`` y ``notContains()`` se añadieron en la versión 2.1 - -Restringiendo el contenido de archivos con el método :method:`Symfony\\Component\\Finder\\Finder::contains`:: - - $finder->files()->contains('lorem ipsum'); - -El método ``contains()`` acepta cadenas o expresiones regulares:: - - $finder->files()->contains('/lorem\s+ipsum$/i'); - -El método ``notContains()`` excluye archivos que contienen un determinado patrón:: - - $finder->files()->notContains('dolor sit amet'); - -``path`` -~~~~~~~~ - -.. versionadded:: 2.2 - Los métodos ``path()`` y ``notPath()`` se añadieron en la versión 2.2. - -Restringe archivos y directorios por ruta con el -método :method:`Symfony\\Component\\Finder\\Finder::path`:: - - $finder->path('some/special/dir'); - -En todos los ambientes se debería usar la barra inclinada (es decir, ``/``) como separador de directorio. - -El el método ``path()`` acepta una cadena o una expresión regular:: - - $finder->path('foo/bar'); - $finder->path('/^foo\/bar/'); - -Internamente, las cadenas se convierten a expresiones regulares escapando las barras invertidas y añadiendo delimitadores: - -.. code-block:: text - - dirname ===> /dirname/ - a/b/c ===> /a\/b\/c/ - -El método :method:`Symfony\\Component\\Finder\\Finder::notPath` excluye archivos por ruta:: - - $finder->notPath('otro/dir'); - -Tamaño de archivo -~~~~~~~~~~~~~~~~~ - -Filtra archivos por tamaño con el método :method:`Symfony\\Component\\Finder\\Finder::size`:: - - $finder->files()->size('< 1.5K'); - -Filtra por rangos de tamaño encadenando llamadas a:: - - $finder->files()->size('>= 1K')->size('<= 2K'); - -El operador de comparación puede ser cualquiera de los siguientes: ``>``, ``>=``, ``<``, ``<=``, -``==``, ``!=``. - -.. versionadded:: 2.1 - El operador ``!=`` se añadió en la versión 2.1. - -El valor destino puede utilizar magnitudes de *kilobytes* (``k``, ``ki``), *megabytes* (``m``, ``mi``), o *gigabytes* (``g``, ``gi``). Los sufijos con una ``i`` usan la versión ``2**n`` adecuada de acuerdo al `estándar IEC`_. - -Fecha de archivo -~~~~~~~~~~~~~~~~ - -Filtra archivos por fecha de última modificación con el método :method:`Symfony\\Component\\Finder\\Finder::date`:: - - $finder->date('since yesterday'); - -El operador de comparación puede ser cualquiera de los siguientes: ``>``, ``>=``, ``<``, ``<=``, -``==``. También puedes utilizar ``since`` o ``after`` como alias para ``>``, y ``until`` o ``before`` como alias para ``<``. - -El valor destino puede ser cualquier fecha compatible con la función `strtotime`_. - -Profundidad de directorio -~~~~~~~~~~~~~~~~~~~~~~~~~ - -De manera predeterminada, ``Finder`` recorre directorios recurrentemente. Filtra la profundidad del recorrido con el método :method:`Symfony\\Component\\Finder\\Finder::depth`:: - - $finder->depth('== 0'); - $finder->depth('< 3'); - -Filtrado personalizado -~~~~~~~~~~~~~~~~~~~~~~ - -Para restringir que el archivo coincida con su propia estrategia, utiliza el método :method:`Symfony\\Component\\Finder\\Finder::filter`:: - - $filter = function (\SplFileInfo $file) - { - if (strlen($file) > 10) { - return false; - } - }; - - $finder->files()->filter($filtro); - -El método ``filter()`` toma un cierre como argumento. Por cada archivo coincidente, este es invocado con el archivo como una instancia de :class:`Symfony\\Component\\Finder\\SplFileInfo`. El archivo se excluye del conjunto de resultados si el cierre devuelve ``false``. - -Leyendo el contenido de los archivos devueltos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El método ``getContents()`` se introdujo en la versión 2.1. - -El contenido devuelto de los archivos se puede leer con el método :method:`Symfony\\Component\\Finder\\SplFileInfo::getContents`:: - - use Symfony\Component\Finder\Finder; - - $finder = new Finder(); - $finder->files()->in(__DIR__); - - foreach ($finder as $file) { - $contents = $file->getContents(); - ... - } - -.. _`strtotime`: http://www.php.net/manual/es/datetime.formats.php -.. _`protocolo`: http://www.php.net/manual/es/wrappers.php -.. _`Flujos`: http://www.php.net/streams -.. _`estándar IEC`: http://physics.nist.gov/cuu/Units/binary.html -.. _`Packagist`: https://packagist.org/packages/symfony/finder diff --git a/_sources/components/http_foundation.txt b/_sources/components/http_foundation.txt deleted file mode 100644 index 03c2e3c..0000000 --- a/_sources/components/http_foundation.txt +++ /dev/null @@ -1,314 +0,0 @@ -.. index:: - single: HTTP - single: HttpFoundation - -El componente ``HttpFoundation`` -================================ - - El componente ``HttpFoundation`` define una capa orientada a objetos para la especificación *HTTP*. - -En *PHP*, la petición está representada por algunas variables globales (``$_GET``, ``$_POST``, ``$_FILE``, ``$_COOKIE``, ``$_SESSION``...) y la respuesta es generada por algunas funciones (``echo``, ``header``, ``setcookie``, ...). - -El componente *HttpFoundation* de *Symfony2* sustituye estas variables globales y funciones de *PHP* por una capa orientada a objetos. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/HttpFoundation); -* Instalándolo a través de *PEAR* (`pear.symfony.com/HttpFoundation`); -* Instalándolo vía ``Composer`` (`symfony/http-foundation` en Packagist). - -Petición --------- - -La manera más común para crear una petición es basándose en las variables globales de *PHP* con -:method:`Symfony\\Component\\HttpFoundation\\Request::createFromGlobals`:: - - use Symfony\Component\HttpFoundation\Request; - - $request = Request::createFromGlobals(); - -que es casi equivalente a la más prolija, pero también más flexible, llamada a -:method:`Symfony\\Component\\HttpFoundation\\Request::__construct`:: - - $request = new Request($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER); - -Accediendo a datos de la ``Petición`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Un objeto ``Petición`` contiene información sobre la petición del cliente. Puedes acceder a esta información a través de muchas propiedades públicas: - -* ``request``: equivalente de ``$_POST``; - -* ``query``: equivalente de ``$_GET`` (``$request->query->get('name')``); - -* ``cookies``: equivalente de ``$_COOKIE``; - -* ``attributes``: sin equivalente --- utilizado por tu aplicación para almacenar otros datos (ve :ref:`más adelante `) - -* ``files``: equivalente de ``$_FILE``; - -* ``server``: equivalente de ``$_SERVER``; - -* ``headers``: en su mayoría equivalente a un subconjunto de ``$_SERVER`` (``$request->headers->get('Content-Type')``). - -Cada propiedad es una instancia de :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` (o una subclase), que es una clase que contiene datos: - -* ``request``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``query``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``cookies``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``attributes``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``files``: :class:`Symfony\\Component\\HttpFoundation\\FileBag`; - -* ``server``: :class:`Symfony\\Component\\HttpFoundation\\ServerBag`; - -* ``headers``: :class:`Symfony\\Component\\HttpFoundation\\HeaderBag`. - -Todas las instancias de la clase :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` tienen métodos para recuperar y actualizar sus datos: - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::all`: Devuelve los parámetros; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::keys`: Devuelve las claves de los parámetros; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::replace`: - Sustituye los parámetros actuales por un nuevo conjunto; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::add`: Añade parámetros; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::get`: Devuelve un parámetro por nombre; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::set`: Establece un parámetro por nombre; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::has`: Devuelve ``true`` si el parámetro está definido; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::remove`: Elimina un parámetro. - -La instancia de :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` también tiene algunos métodos para filtrar los valores introducidos: - -* :method:`Symfony\\Component\\HttpFoundation\\Request::getAlpha`: Devuelve los caracteres alfanuméricos del valor del parámetro; - -* :method:`Symfony\\Component\\HttpFoundation\\Request::getAlnum`: Devuelve los caracteres alfanuméricos y dígitos del valor del parámetro; - -* :method:`Symfony\\Component\\HttpFoundation\\Request::getDigits`: Devuelve los dígitos del valor del parámetro; - -* :method:`Symfony\\Component\\HttpFoundation\\Request::getInt`: Devuelve el valor del parámetro convertido a entero; - -* :method:`Symfony\\Component\\HttpFoundation\\Request::filter`: Filtra el parámetro usando la función ``filter_var()`` de *PHP*. - -Todos los captadores toman hasta tres argumentos: el primero es el nombre del parámetro y el segundo es el valor predeterminado a devolver si el parámetro no existe:: - - // La cadena de consulta es '?foo=bar' - - $request->query->get('foo'); - // devuelve bar - - $request->query->get('bar'); - // devuelve null - - $request->query->get('bar', 'bar'); - // devuelve 'bar' - - -Cuando *PHP* importa la consulta de la petición, manipula los parámetros de la petición como -``foo[bar]=bar`` de una manera especial, ya que crea una matriz. Para que puedas obtener el parámetro ``foo`` y devolver una matriz con un elemento ``bar``. pero a veces, posiblemente quieras obtener el valor por medio del nombre "original" del parámetro: -``foo[bar]``. Esto es posible con todos los captadores de ``ParameterBag`` como -:method:`Symfony\\Component\\HttpFoundation\\Request::get` a través del tercer argumento:: - - // la cadena de consulta es '?foo[bar]=bar' - - $request->query->get('foo'); - // devuelve array('bar' => 'bar') - - $request->query->get('foo[bar]'); - // devuelve null - - $request->query->get('foo[bar]', null, true); - // devuelve 'bar' - -.. _component-foundation-attributes: - -Por último, pero no menos importante, también puedes almacenar más datos en la petición, gracias a la propiedad pública ``attributes``, que también es una instancia de :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`. Casi siempre se usa para adjuntar información que pertenece a la ``Petición`` y que necesitas acceder desde diferentes puntos de tu aplicación. Para información sobre cómo se utiliza en la plataforma *Symfony2*, consulta la :ref:`propiedad pública attributes `. - -Identificando una ``Petición`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En tu aplicación, necesitas una manera de identificar una petición; la mayor parte del tiempo, esto se hace a través de la "información de ruta" de la petición, a la que puedes acceder a través del método :method:`Symfony\\Component\\HttpFoundation\\Request::getPathInfo`:: - - // Para una petición a http://example.com/blog/index.php/post/hello-world - // la información de ruta es "/post/hello-world" - $request->getPathInfo(); - -Simulando una ``Petición`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En lugar de crear una petición basada en las variables globales de *PHP*, también puedes simular -una ``Petición``:: - - $request = Request::create('/hello-world', 'GET', array('name' => 'Fabien')); - -El método :method:`Symfony\\Component\\HttpFoundation\\Request::create` crea una petición basándose en la información de una ruta, un método y algunos parámetros (los parámetros de consulta o los de la petición en función del método *HTTP*); y, por supuesto, también puedes sustituir todas las otras variables (de manera predeterminada, *Symfony* crea parámetros predeterminados apropiados para todas las variables globales de *PHP*). - -En base a dicha petición, puedes sustituir las variables globales de *PHP* a través de :method:`Symfony\\Component\\HttpFoundation\\Request::overrideGlobals`:: - - $request->overrideGlobals(); - -.. tip:: - - También puedes duplicar una consulta existente a través del método :method:`Symfony\\Component\\HttpFoundation\\Request::duplicate` o cambiar un montón de parámetros con una sola llamada a :method:`Symfony\\Component\\HttpFoundation\\Request::initialize`. - -Accediendo a la ``Sesión`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si tienes una sesión adjunta a la ``Petición``, puedes acceder a ella a través del método :method:`Symfony\\Component\\HttpFoundation\\Request::getSession`; -el método :method:`Symfony\\Component\\HttpFoundation\\Request::hasPreviousSession` te dice si la petición contiene una sesión que se inició en una de las peticiones anteriores. - -Accediendo a otros datos -~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase ``Petición`` tiene muchos otros métodos que puedes utilizar para acceder a la información de la petición. Echa un vistazo a la *API* para más información sobre ellos. - -``Respuesta`` -------------- - -Un objeto :class:`Symfony\\Component\\HttpFoundation\\Response` contiene toda la información que debes enviar de vuelta al cliente desde una determinada ``Petición``. El constructor toma hasta tres argumentos: el contenido de la respuesta, el código de estado, y una serie de cabeceras *HTTP*:: - - - use Symfony\Component\HttpFoundation\Response; - - $response = new Response('Content', - 200, - array('content-type' => 'text/html') - ); - -También puedes manipular esta información después de haber creado la ``Respuesta``:: - - $response->setContent('Hello World'); - - // el atributo público headers es un ResponseHeaderBag - $response->headers->set('Content-Type', 'text/plain'); - - $response->setStatusCode(404); - -Al establecer el ``Content-Type`` de la respuesta, puedes configurar el juego de caracteres, pero es mejor configurarlo a través del método :method:`Symfony\\Component\\HttpFoundation\\Response::setCharset`:: - - $response->setCharset('ISO-8859-1'); - -Ten en cuenta que de manera predeterminada, *Symfony* asume que sus respuestas están codificadas en -*UTF-8*. - -Enviando la ``Respuesta`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de enviar la respuesta, te puedes asegurar de que es compatible con la especificación del protocolo *HTTP* llamando al método :method:`Symfony\\Component\\HttpFoundation\\Response::prepare`:: - - $response->prepare($request); - -Enviar la respuesta entonces es tan sencillo cómo invocar al método :method:`Symfony\\Component\\HttpFoundation\\Response::send`:: - - $response->send(); - -Enviando ``Cookies`` -~~~~~~~~~~~~~~~~~~~~ - -Puedes manipular las ``cookies`` de la respuesta por medio del atributo público ``headers``:: - - use Symfony\Component\HttpFoundation\Cookie; - - $response->headers->setCookie(new Cookie('foo', 'bar')); - -El método :method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::setCookie` toma como argumento una instancia de :class:`Symfony\\Component\\HttpFoundation\\Cookie`. - -Puedes borrar una :dfn:`cookie` a través del método :method:`Symfony\\Component\\HttpFoundation\\Response::clearCookie`. - -Gestionando la caché *HTTP* -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\HttpFoundation\\Response` tiene un rico conjunto de métodos para manipular las cabeceras *HTTP* relacionadas con la memoria caché: - -* :method:`Symfony\\Component\\HttpFoundation\\Response::setPublic`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setPrivate`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::expire`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setMaxAge`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setSharedMaxAge`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setTtl`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setClientTtl`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setEtag`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setVary`; - -Puedes utilizar el método :method:`Symfony\\Component\\HttpFoundation\\Response::setCache` para establecer la información de caché utilizada comúnmente en una llamada al método:: - - $response->setCache(array( - 'etag' => 'abcdef', - 'last_modified' => new \DateTime(), - 'max_age' => 600, - 's_maxage' => 600, - 'private' => false, - 'public' => true, - )); - -Para comprobar si los validadores de la ``Respuesta`` (``ETag``, ``Last-Modified``) coinciden con un valor condicional especificado en la petición del cliente, utiliza el método :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`:: - - if ($response->isNotModified($request)) { - $response->send(); - } - -Si la respuesta no se ha modificado, se establece el código de estado a 304 y elimina el contenido real de la respuesta. - -Redirigiendo al usuario -~~~~~~~~~~~~~~~~~~~~~~~ - -Para redirigir al cliente a otra *URL*, puedes utilizar la clase :class:`Symfony\\Component\\HttpFoundation\\RedirectResponse`:: - - use Symfony\Component\HttpFoundation\RedirectResponse; - - $response = new RedirectResponse('http://ejemplo.com/'); - -Transmitiendo una ``Respuesta`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - La compatibilidad a la transmisión de respuestas se añadió en *Symfony 2.1*. - -La clase :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` te permite transmitir la respuesta de vuelta al cliente. El contenido de la respuesta está representado por una función *PHP* ejecutable en lugar de una cadena:: - - use Symfony\Component\HttpFoundation\StreamedResponse; - - $response = new StreamedResponse(); - $response->setCallback(function () { - echo 'Hello World'; - flush(); - sleep(2); - echo 'Hello World'; - flush(); - }); - $response->send(); - -Descargando archivos -~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El método ``makeDisposition`` fue añadido en *Symfony 2.1*. - -Al cargar un archivo, debes agregar una cabecera ``Content-Disposition`` a tu respuesta. Es fácil crear esta cabecera básica para descargar archivos, utilizando nombres de archivo -no *ASCII* más envolventes. El método :method:`:Symfony\\Component\\HttpFoundation\\Response:makeDisposition` abstrae el trabajo duro detrás de una sencilla *API*: - -.. code-block:: php - - use Symfony\Component\HttpFoundation\ResponseHeaderBag; - - $d = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'foo.pdf'); - - $response->headers->set('Content-Disposition', $d); - -``Session`` ------------ - -TBD --- De esta parte no se ha escrito nada, ya que probablemente será reconstruida próximamente en *Symfony 2.1*. diff --git a/_sources/components/http_foundation/index.txt b/_sources/components/http_foundation/index.txt deleted file mode 100644 index 45fcd96..0000000 --- a/_sources/components/http_foundation/index.txt +++ /dev/null @@ -1,11 +0,0 @@ -Fundamento *HTTP* -================= - -.. toctree:: - :maxdepth: 2 - - introduction - sessions - session_configuration - session_testing - trusting_proxies diff --git a/_sources/components/http_foundation/introduction.txt b/_sources/components/http_foundation/introduction.txt deleted file mode 100644 index 29ba376..0000000 --- a/_sources/components/http_foundation/introduction.txt +++ /dev/null @@ -1,439 +0,0 @@ -.. index:: - single: HTTP - single: HttpFoundation - single: Componentes; HttpFoundation - -El componente ``HttpFoundation`` -================================ - - El componente ``HttpFoundation`` define una capa orientada a objetos para la especificación *HTTP*. - -En *PHP*, la petición está representada por algunas variables globales (``$_GET``, ``$_POST``, ``$_FILE``, ``$_COOKIE``, ``$_SESSION``, ...) y la respuesta es generada por algunas funciones (``echo``, ``header``, ``setcookie``, ...). - -El componente *HttpFoundation* de *Symfony2* sustituye estas variables globales y funciones predefinidas en *PHP* por una capa orientada a objetos. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/HttpFoundation); -* :doc:`Instalándolo vía Composer ` (``symfony/http-foundation`` en `Packagist`_). - -Petición --------- - -La manera más común para crear una petición es basándose en las variables globales de *PHP* con -:method:`Symfony\\Component\\HttpFoundation\\Request::createFromGlobals`:: - - use Symfony\Component\HttpFoundation\Request; - - $request = Request::createFromGlobals(); - -que es casi equivalente a la más prolija, pero también más flexible, llamada al método :method:`Symfony\\Component\\HttpFoundation\\Request::__construct`:: - - $request = new Request( - $_GET, - $_POST, - array(), - $_COOKIE, - $_FILES, - $_SERVER - ); - -Accediendo a datos de la ``Petición`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Un objeto ``Petición`` contiene información sobre la petición del cliente. Puedes acceder a esta información a través de muchas propiedades públicas: - -* ``request``: equivalente de ``$_POST``; - -* ``query``: equivalente de ``$_GET`` (``$request->query->get('name')``); - -* ``cookies``: equivalente de ``$_COOKIE``; - -* ``attributes``: sin equivalente --- utilizado por tu aplicación para almacenar otros datos (ve :ref:`más adelante `) - -* ``files``: equivalente de ``$_FILE``; - -* ``server``: equivalente de ``$_SERVER``; - -* ``headers``: en su mayoría equivalente a un subconjunto de ``$_SERVER`` (``$request->headers->get('Content-Type')``). - -Cada propiedad es una instancia de :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` (o una subclase), que es una clase que contiene datos: - -* ``request``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``query``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``cookies``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``attributes``: :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`; - -* ``files``: :class:`Symfony\\Component\\HttpFoundation\\FileBag`; - -* ``server``: :class:`Symfony\\Component\\HttpFoundation\\ServerBag`; - -* ``headers``: :class:`Symfony\\Component\\HttpFoundation\\HeaderBag`. - -Todas las instancias de la clase :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` tienen métodos para recuperar y actualizar sus datos: - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::all`: Devuelve los parámetros; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::keys`: Devuelve las claves de los parámetros; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::replace`: - Sustituye los parámetros actuales por un nuevo conjunto; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::add`: Añade parámetros; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::get`: Devuelve un parámetro por nombre; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::set`: Establece un parámetro por nombre; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::has`: Devuelve ``true`` si el parámetro está definido; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::remove`: Elimina un parámetro. - -La instancia de :class:`Symfony\\Component\\HttpFoundation\\ParameterBag` también tiene algunos métodos para filtrar los valores introducidos: - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlpha`: Devuelve los caracteres alfanuméricos del valor del parámetro; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getAlnum`: Devuelve los caracteres alfanuméricos y dígitos del valor del parámetro; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getDigits`: Devuelve los dígitos del valor del parámetro; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::getInt`: Devuelve el valor del parámetro convertido a entero; - -* :method:`Symfony\\Component\\HttpFoundation\\ParameterBag::filter`: Filtra el parámetro usando la función ``filter_var()`` de *PHP*. - -Todos los captadores toman hasta tres argumentos: el primero es el nombre del parámetro y el segundo es el valor predeterminado a devolver si el parámetro no existe:: - - // La cadena de consulta es '?foo=bar' - - $request->query->get('foo'); - // devuelve bar - - $request->query->get('bar'); - // devuelve null - - $request->query->get('bar', 'bar'); - // devuelve 'bar' - - -Cuando *PHP* importa la consulta de la petición, manipula los parámetros de la petición como -``foo[bar]=bar`` de una manera especial, ya que crea un arreglo. Para que puedas obtener el parámetro ``foo`` y devolver un arreglo con un elemento ``bar``. pero a veces, posiblemente quieras obtener el valor por medio del nombre «original» del parámetro: -``foo[bar]``. Esto es posible con todos los captadores de ``ParameterBag`` como -:method:`Symfony\\Component\\HttpFoundation\\Request::get` a través del tercer argumento:: - - // la cadena de consulta es '?foo[bar]=bar' - - $request->query->get('foo'); - // devuelve array('bar' => 'bar') - - $request->query->get('foo[bar]'); - // devuelve null - - $request->query->get('foo[bar]', null, true); - // devuelve 'bar' - -.. _component-foundation-attributes: - -Por último, pero no menos importante, también puedes almacenar datos adicionales en la petición, gracias a la propiedad pública ``attributes``, que también es una instancia de la clase :class:`Symfony\\Component\\HttpFoundation\\ParameterBag`. Casi siempre se usa para adjuntar información que pertenece a la ``Petición`` y que necesitas acceder desde diferentes puntos de tu aplicación. Para información sobre cómo se utiliza en la plataforma *Symfony2*, consulta la :ref:`propiedad pública attributes `. - -Identificando una ``Petición`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En tu aplicación, necesitas una manera de identificar una petición; la mayor parte del tiempo, esto se hace a través de la «información de ruta» de la petición, a la que puedes acceder a través del método :method:`Symfony\\Component\\HttpFoundation\\Request::getPathInfo`:: - - // Para una petición a http://example.com/blog/index.php/post/hello-world - // la información de ruta es "/post/hello-world" - $request->getPathInfo(); - -Simulando una ``Petición`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En lugar de crear una petición basada en las variables globales de *PHP*, también puedes simular -una ``Petición``:: - - $request = Request::create( - '/hello-world', - 'GET', - array('name' => 'Fabien') - ); - -El método :method:`Symfony\\Component\\HttpFoundation\\Request::create` crea una petición basándose en la información de una ruta, un método y algunos parámetros (los parámetros de consulta o los de la petición en función del método *HTTP*); and of -course, you can also override all other variables as well (by default, Symfony -creates sensible defaults for all the PHP global variables). - -En base a dicha petición, puedes sustituir las variables globales de *PHP* a través de :method:`Symfony\\Component\\HttpFoundation\\Request::overrideGlobals`:: - - $request->overrideGlobals(); - -.. tip:: - - También puedes duplicar una consulta existente a través del método :method:`Symfony\\Component\\HttpFoundation\\Request::duplicate` o cambiar un montón de parámetros con una sola llamada a :method:`Symfony\\Component\\HttpFoundation\\Request::initialize`. - -Accediendo a la ``Sesión`` -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si tienes una sesión adjunta a la ``Petición``, puedes acceder a ella a través del método :method:`Symfony\\Component\\HttpFoundation\\Request::getSession`; -el método :method:`Symfony\\Component\\HttpFoundation\\Request::hasPreviousSession` te dice si la petición contiene una sesión que se inició en una de las peticiones anteriores. - -Accediendo a los datos de cabeceras `Accept-*` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes acceder fácilmente a los dato básicos extraídos desde las cabeceras ``Accept-*`` utilizando los siguientes métodos: - -* :method:`Symfony\\Component\\HttpFoundation\\Request::getAcceptableContentTypes`: - devuelve la lista de tipos de contenido aceptados ordenados descendentemente por cualidad; - -* :method:`Symfony\\Component\\HttpFoundation\\Request::getLanguages`: - devuelve la lista de idiomas aceptados ordenados descendentemente por cualidad; - -* :method:`Symfony\\Component\\HttpFoundation\\Request::getCharsets`: - devuelve la lista de juegos de caracteres («charsets») aceptados ordenados descendentemente por cualidad; - -.. versionadded:: 2.2 - La clase :class:`Symfony\\Component\\HttpFoundation\\AcceptHeader` es nueva en *Symfony 2.2*. - -Si necesitas conseguir acceso completo a los datos analizados de ``Accept``, ``Accept-Language``, ``Accept-Charset`` o ``Accept-Encoding``, puedes usar la clase utilitaria :class:`Symfony\\Component\\HttpFoundation\\AcceptHeader`:: - - use Symfony\Component\HttpFoundation\AcceptHeader; - - $accept = AcceptHeader::fromString($request->headers->get('Accept')); - if ($accept->has('text/html')) { - $item = $accept->get('html'); - $charset = $item->getAttribute('charset', 'utf-8'); - $quality = $item->getQuality(); - } - - // acepta elementos que están ordenados descendentemente por cualidad - $accepts = AcceptHeader::fromString($request->headers->get('Accept'))->all(); - -Accediendo a otros datos -~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase ``Petición`` tiene muchos otros métodos que puedes utilizar para acceder a la información de la petición. Echa un vistazo a la *API* para más información sobre ellos. - -``Respuesta`` -------------- - -Un objeto :class:`Symfony\\Component\\HttpFoundation\\Response` contiene toda la información que debes enviar de vuelta al cliente desde una determinada ``Petición``. El constructor toma hasta tres argumentos: el contenido de la respuesta, el código de estado, y una serie de cabeceras *HTTP*:: - - - use Symfony\Component\HttpFoundation\Response; - - $response = new Response( - 'Content', - 200, - array('content-type' => 'text/html') - ); - -También puedes manipular esta información después de haber creado la ``Respuesta``:: - - $response->setContent('Hello World'); - - // el atributo público headers es un ResponseHeaderBag - $response->headers->set('Content-Type', 'text/plain'); - - $response->setStatusCode(404); - -Al establecer el ``Content-Type`` de la respuesta, puedes configurar el juego de caracteres, pero es mejor configurarlo a través del método :method:`Symfony\\Component\\HttpFoundation\\Response::setCharset`:: - - $response->setCharset('ISO-8859-1'); - -Ten en cuenta que de manera predeterminada, *Symfony* asume que sus respuestas están codificadas en -*UTF-8*. - -Enviando la ``Respuesta`` -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de enviar la respuesta, te puedes asegurar de que es compatible con la especificación del protocolo *HTTP* llamando al método :method:`Symfony\\Component\\HttpFoundation\\Response::prepare`:: - - $response->prepare($request); - -Enviar la respuesta entonces es tan sencillo cómo invocar al método :method:`Symfony\\Component\\HttpFoundation\\Response::send`:: - - $response->send(); - -Enviando ``Cookies`` -~~~~~~~~~~~~~~~~~~~~ - -Puedes manipular las ``cookies`` de la respuesta por medio del atributo público ``headers``:: - - use Symfony\Component\HttpFoundation\Cookie; - - $response->headers->setCookie(new Cookie('foo', 'bar')); - -El método :method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::setCookie` toma como argumento una instancia de :class:`Symfony\\Component\\HttpFoundation\\Cookie`. - -Puedes borrar una :dfn:`cookie` a través del método :method:`Symfony\\Component\\HttpFoundation\\ResponseHeaderBag::clearCookie`. - -Gestionando la caché *HTTP* -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\HttpFoundation\\Response` tiene un rico conjunto de métodos para manipular las cabeceras *HTTP* relacionadas con la memoria caché: - -* :method:`Symfony\\Component\\HttpFoundation\\Response::setPublic`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setPrivate`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::expire`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setExpires`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setMaxAge`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setSharedMaxAge`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setTtl`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setClientTtl`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setLastModified`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setEtag`; -* :method:`Symfony\\Component\\HttpFoundation\\Response::setVary`; - -Puedes utilizar el método :method:`Symfony\\Component\\HttpFoundation\\Response::setCache` para establecer la información de caché utilizada comúnmente en una llamada al método:: - - $response->setCache(array( - 'etag' => 'abcdef', - 'last_modified' => new \DateTime(), - 'max_age' => 600, - 's_maxage' => 600, - 'private' => false, - 'public' => true, - )); - -Para comprobar si los validadores de la ``Respuesta`` (``ETag``, ``Last-Modified``) coinciden con un valor condicional especificado en la petición del cliente, utiliza el método :method:`Symfony\\Component\\HttpFoundation\\Response::isNotModified`:: - - if ($response->isNotModified($request)) { - $response->send(); - } - -Si la respuesta no se ha modificado, se establece el código de estado a 304 y elimina el contenido real de la respuesta. - -Redirigiendo al usuario -~~~~~~~~~~~~~~~~~~~~~~~ - -Para redirigir al cliente a otra *URL*, puedes utilizar la clase :class:`Symfony\\Component\\HttpFoundation\\RedirectResponse`:: - - use Symfony\Component\HttpFoundation\RedirectResponse; - - $response = new RedirectResponse('http://ejemplo.com/'); - -Transmitiendo una ``Respuesta`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - La compatibilidad a la transmisión de respuestas se añadió en *Symfony 2.1*. - -La clase :class:`Symfony\\Component\\HttpFoundation\\StreamedResponse` te permite transmitir la respuesta de vuelta al cliente. El contenido de la respuesta está representado por una función *PHP* ejecutable en lugar de una cadena:: - - use Symfony\Component\HttpFoundation\StreamedResponse; - - $response = new StreamedResponse(); - $response->setCallback(function () { - echo 'Hello World'; - flush(); - sleep(2); - echo 'Hello World'; - flush(); - }); - $response->send(); - -.. note:: - - The ``flush()`` function does not flush buffering. If ``ob_start()`` has - been called before or the ``output_buffering`` php.ini option is enabled, - you must call ``ob_flush()`` before ``flush()``. - - Additionally, PHP isn't the only layer that can buffer output. Your web - server might also buffer based on its configuration. Even more, if you - use fastcgi, buffering can't be disabled at all. - -.. _component-http-foundation-serving-files: - -Serving Files -~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El método ``makeDisposition`` fue añadido en *Symfony 2.1*. - -When sending a file, you must add a ``Content-Disposition`` header to your -response. Es fácil crear esta cabecera básica para descargar archivos, utilizando nombres de archivo -no *ASCII* más envolventes. El método :method:`Symfony\\Component\\HttpFoundation\\Response::makeDisposition` abstrae el trabajo pesado detrás de una sencilla *API*:: - - use Symfony\Component\HttpFoundation\ResponseHeaderBag; - - $d = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'foo.pdf'); - - $response->headers->set('Content-Disposition', $d); - -.. versionadded:: 2.2 - The :class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse` - class was added in Symfony 2.2. - -Alternatively, if you are serving a static file, you can use a -:class:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse`:: - - use Symfony\Component\HttpFoundation\BinaryFileResponse - - $file = 'path/to/file.txt'; - $response = new BinaryFileResponse($file); - -The ``BinaryFileResponse`` will automatically handle ``Range`` and -``If-Range`` headers from the request. It also supports ``X-Sendfile`` -(see for `Nginx`_ and `Apache`_). To make use of it, you need to determine -whether or not the ``X-Sendfile-Type`` header should be trusted and call -:method:`Symfony\\Component\\HttpFoundation\\BinaryFileResponse::trustXSendfileTypeHeader` -if it should:: - - $response::trustXSendfileTypeHeader(); - -You can still set the ``Content-Type`` of the sent file, or change its ``Content-Disposition``:: - - $response->headers->set('Content-Type', 'text/plain') - $response->setContentDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, 'filename.txt'); - - -.. _component-http-foundation-json-response: - -Creando una respuesta *JSON* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Puedes crear cualquier tipo de respuesta vía la clase :class:`Symfony\\Component\\HttpFoundation\\Response` poniendo las cabeceras y contenido correctos. Una respuesta *JSON* podría tener la siguiente apariencia:: - - use Symfony\Component\HttpFoundation\Response; - - $response = new Response(); - $response->setContent(json_encode(array( - 'data' => 123, - ))); - $response->headers->set('Content-Type', 'application/json'); - -.. versionadded:: 2.1 - The :class:`Symfony\\Component\\HttpFoundation\\JsonResponse` - class was added in Symfony 2.1. - -Además, hay una muy útil clase :class:`Symfony\\Component\\HttpFoundation\\JsonResponse`, la cual incluso, puede hacer esto mucho más fácil:: - - use Symfony\Component\HttpFoundation\JsonResponse; - - $response = new JsonResponse(); - $response->setData(array( - 'data' => 123 - )); - -Esto codifica tu arreglo de datos a *JSON* y pone la cabecera ``Content-Type`` a ``application/json``. Si estás utilizando *JSONP*, puedes especifiar a cual función retrollamada se tendría que pasar el dato:: - - $response->setCallback('handleResponse'); - -En este caso, la cabecera ``Content-Type`` será ``text/javascript`` y el -contenido de respuesta tendrá esta apariencia: - -.. code-block:: javascript - - handleResponse({'data': 123}); - -``Session`` ------------ - -La información de la sesión está en su propio documento: :doc:`/components/http_foundation/sessions`. - -.. _`Packagist`: https://packagist.org/packages/symfony/http-foundation -.. _Nginx: http://wiki.nginx.org/XSendfile -.. _Apache: https://tn123.org/mod_xsendfile/ diff --git a/_sources/components/http_foundation/session_configuration.txt b/_sources/components/http_foundation/session_configuration.txt deleted file mode 100644 index aafc4de..0000000 --- a/_sources/components/http_foundation/session_configuration.txt +++ /dev/null @@ -1,179 +0,0 @@ -.. index:: - single: HTTP - single: HttpFoundation, Sesiones - -Configurando sesiones y controladores de guardado -================================================= - -Esta sección se ocupa de cómo configurar la gestión de sesiones y ajustarla a tus necesidades específicas. Esta documentación incluye los controladores de guardado, que almacenan y recuperan datos de la sesión y configuración del comportamiento de la sesión. - -Controladores de guardado -~~~~~~~~~~~~~~~~~~~~~~~~~ - -El flujo de trabajo en la sesión *PHP* tiene 6 posibles operaciones que pueden ocurrir. El flujo de sesión normal sigue el proceso de ``apertura``, ``lectura``, ``escritura`` y ``cierre``, con la posibilidad de ``destrucción`` y ``gc`` (por ``garbage collection`` o recolección de basura, la cual cerrará todas las sesiones abiertas: la `gc` se invoca aleatoriamente de acuerdo a la configuración de *PHP* y si es llamada directamente, su invocación se difiere hasta después de una operación de ``apertura``). Puedes leer más sobre esto en `php.net/session.customhandler`_ - - -Controladores de guardado nativos de *PHP* ------------------------------------------- - -Los llamados controladores 'nativos', son controladores de guardado que son o bien compilados en *PHP* o proporcionados por extensiones *PHP*, como *PHP SQLite*, *PHP-Memcached*, etc. - -Todos los controladores de guardado nativos son internos a *PHP* y como tal, no tienen enfrente *API* pública. -Ellos deben estar configurados en las directivas ``ini`` de *PHP*, por lo general ``session.save_path`` y potencialmente otros controladores específicos para la directiva. Puedes encontrar los detalles específicos en el bloque de documentación del método ``setOptions()`` de cada clase. - -Mientras los controladores de guardado nativos se pueden activar utilizando directamente ``ini_set('session.save_handler', $nombre);``, *Symfony2* proporciona una manera conveniente de activar estos en la misma forma que los controladores personalizados. - -*Symfony2* proporciona controladores para el siguiente controlador de guardado nativo, por ejemplo: - - * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeFileSessionHandler`; - -Ejemplo de uso:: - - use Symfony\Component\HttpFoundation\Session\Session; - use Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage; - use Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeFileSessionHandler; - - $storage = new NativeSessionStorage(array(), new NativeFileSessionHandler()); - $session = new Session($storage); - -.. note:: - - Salvo el controlador ``files`` que está integrado en *PHP* y siempre disponible, la disponibilidad de los otros controladores depende de las extensiones de *PHP* activas en tiempo de ejecución. - -.. note:: - - Los controladores de guardado nativos proporcionan una rápida solución de almacenamiento de sesión, sin embargo, en sistemas complejos donde se necesita más control, los controladores de guardado personalizados pueden proporcionar más libertad y flexibilidad. - *Symfony2* proporciona varias implementaciones que se pueden personalizar aún más según sea necesario. - - -Controladores de guardado personalizados ----------------------------------------- - -Los controladores personalizados son aquellos que sustituyen por completo a los controladores de guardado de sesión integrados en *PHP*, proveyendo seis funciones retrollamadas que *PHP* invoca internamente en varios puntos en el flujo de trabajo de la sesión. - -*HttpFoundation* de *Symfony2*, por omisión, ofrece algunos de estos y fácilmente te pueden servir como -ejemplos, si quieres escribir uno propio. - - * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\PdoSessionHandler` - * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcacheSessionHandler` - * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MemcachedSessionHandler` - * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\MongoDbSessionHandler` - * :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NullSessionHandler` - -Ejemplo de uso:: - - use Symfony\Component\HttpFoundation\Session\Session; - use Symfony\Component\HttpFoundation\Session\Storage\SessionStorage; - use Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler; - - $storage = new NativeSessionStorage(array(), new PdoSessionHandler()); - $session = new Session($storage); - - -Configurando sesiones *PHP* -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage` puede ajustar la mayoría de las directivas de configuración de *PHP*, las cuales están documentadas en `php.net/session.configuration`_. - -Para configurar estas opciones, pasa las claves (omitiendo la primera parte ``session.`` de la clave) como un arreglo de clave/valor como el argumento ``$options`` del constructor. -O ajústalas a través del método :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage::setOptions`. - -En aras de la claridad, algunas de las opciones principales se explican en esta documentación. - -Duración de la ``cookie`` de sesión -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para mayor seguridad, generalmente se recomienda enviar fragmentos de sesión como ``cookies`` de sesión. -Puedes configurar el tiempo de vida de las ``cookies`` de sesión, especificando la duración (en segundos) usando la clave ``cookie_lifetime`` en el argumento ``$options`` del constructor en :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage`. - -Establecer una ``cookie_lifetime`` a ``0`` provocará que la ``cookie`` viva sólo mientras el navegador sigue abierto. En general, ``cookie_lifetime`` se establece en un número de días, semanas o meses relativamente grande. No es raro dejar ``cookies`` durante un año o más, dependiendo de la aplicación. - -Dado que las ``cookies`` de sesión son sólo una muestra del lado del cliente, estas son menos importantes al controlar los detalles de la configuración de seguridad que en última instancia sólo se pueden controlar en el lado del servidor. - -.. note:: - - La opción ``cookie_lifetime`` es el número de segundos que la ``cookie`` debería vivir, esta no es una marca de tiempo Unix. La ``cookie`` de sesión resultante será sellada durante un plazo de ``time()`` + ``cookie_lifetime`` donde el tiempo se toma desde el servidor. - -Configurando la recolección de basura -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando se abre una sesión, *PHP* llama aleatoriamente al controlador ``gc`` de acuerdo a la probabilidad establecida por ``session.gc_probability`` / ``session.gc_divisor``. Por ejemplo, si éstos se establecieron en ``5/100``, respectivamente, significaría una probabilidad del 5%. Del mismo modo, ``3/4`` significaría invocarlo en 3 de cada 4 oportunidades, es decir, un 75%. - -Si se invoca el controlador de recolección de basura, *PHP* pasará el valor almacenado en la directiva ``ini`` ``session.gc_maxlifetime`` de *PHP*. El significado en este contexto es que se debería suprimir cualquier sesión almacenada que se guardó por más de ``maxlifetime``. Esto le permite a uno expirar los registros basándose en el tiempo de inactividad. - -Puedes configurar estas opciones pasando ``gc_probability``, ``gc_divisor`` y ``gc_maxlifetime`` en un arreglo al constructor de :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage` o al método :method:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage::setOptions()`. - -Tiempo de vida de la sesión -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando se crea una nueva sesión, significa que *Symfony2* emite una nueva ``cookie`` de sesión para el cliente, la ``cookie`` será sellada con un tiempo de caducidad. Este se calcula sumando el valor de configuración de ``session.cookie_lifetime`` en *PHP* con la hora actual del servidor. - -.. note:: - - *PHP* sólo emitirá una ``cookie`` una vez. Se espera que el cliente guarde esa ``cookie`` toda la vida. Sólo se otorgará una nueva ``cookie`` cuando la sesión sea destruida, se elimine la ``cookie`` en el navegador, o se vuelva a regenerar el identificador de sesión usando los métodos ``migrate()`` o ``invalidate()`` de la clase ``Session``. - - La duración inicial de la ``cookie`` se puede establecer configurando ``NativeSessionStorage`` utilizando el método ``setOptions(array('cookie_lifetime' => 1234))``. - -.. note:: - - Una ``galeta`` con una vida útil de ``0`` significa que la ``galleta`` expira al cerrar el navegador. - -Tiempo de inactividad/Mantener viva la sesión -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A menudo hay circunstancias en las que posiblemente quieras proteger, o reducir al mínimo el uso no autorizado de una sesión cuando un usuario se aleja de su terminal mientras está conectado destruyendo la sesión después de cierto periodo de tiempo de inactividad. Por ejemplo, es común que las aplicaciones de banca cierren la sesión después de sólo 5 a 10 minutos de inactividad. Ajustar la duración de la ``cookie`` aquí no es apropiado debido a que el cliente la puede manipular, por lo que debemos hacer la expiración de lado del servidor. La forma más fácil es implementarla a través de la recolección de basura la cual se ejecuta con razonable frecuencia. El ``lifetime`` de la ``cookie`` se establece a un valor relativamente alto, y la recolección de basura ``maxlifetime`` se establecería para destruir sesiones en cualquiera que sea el periodo de inactividad deseado. - -La otra opción es comprobar específicamente si una sesión ha caducado después de haber iniciado la sesión. La sesión se puede destruir si es necesario. Este método de procesamiento puede permitir la integración de la expiración de sesiones en la experiencia del usuario, por ejemplo, visualizando un mensaje. - -*Symfony2* registra algunos metadatos básicos acerca de cada sesión para darte completa libertad en este ámbito. - -Metadatos de sesión -~~~~~~~~~~~~~~~~~~~ - -Las sesiones están decoradas con un poco de metadatos básicos para permitirte un control preciso sobre la configuración de seguridad. El objeto ``Sesión`` tiene un captador de metadatos, :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag` que -expone una instancia de :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MetadataBag`:: - - $session->getMetadataBag()->getCreated(); - $session->getMetadataBag()->getLastUsed(); - -Ambos métodos devuelven una marca de tiempo Unix (relativa al servidor). - -Puedes utilizar estos metadatos para expirar la sesión explícitamente en el acceso, por ejemplo:: - - $session->start(); - if (time() - $session->getMetadataBag()->getLastUsed() > $maxIdleTime) { - $session->invalidate(); - throw new SessionExpired(); // redirige a la página de sesión expirada - } - -También es posible decir cuál es el ``cookie_lifetime`` establecido en una ``cookie`` en particular leyendo el método ``getLifetime()``:: - - $session->getMetadataBag()->getLifetime(); - -Puedes determinar el tiempo de caducidad de la ``cookie`` sumando la fecha y hora de creación y el ``lifetime``. - -Compatibilidad con *PHP 5.4* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A partir de *PHP 5.4.0*, están disponibles :phpclass:`SessionHandler` y :phpclass:`SessionHandlerInterface`. *Symfony 2.1* proporciona compatibilidad para :phpclass:`SessionHandlerInterface` por lo tanto la puedes utilizar en *PHP 5.3*. Esto, gratamente mejora la interoperabilidad con otras bibliotecas. - -:phpclass:`SessionHandler` es una clase interna especial de *PHP* que expone los controladores de guardado nativos para el espacio de usuario de *PHP*. - -Con el fin de proporcionar una solución para aquellos que utilizan *PHP 5.4*, *Symfony2* tiene una clase especial llamada :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeSessionHandler` que bajo *PHP 5.4*, se extiende desde `\SessionHandler` y bajo *PHP 5.3* es sólo una clase base vacía. Esto proporciona interesantes oportunidades para aprovechar la funcionalidad de *PHP 5.4* si está disponible. - -Controladores delegados de guardado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Hay dos tipos de controladores delegados de clases de guardado que heredan de :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\AbstractProxy`: -estas son :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeProxy` y :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerProxy`. - -La :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\NativeSessionStorage` automáticamente inyecta los controladores de guardado en un controlador de guardado delegado, a menos que ya lo envuelva uno. - -La clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\NativeProxy` se utiliza automáticamente en *PHP 5.3*, cuando los controladores de guardado internos de *PHP* se especifican usando las clases ``Native*SessionHandler``, mientras que la clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\Handler\\SessionHandlerProxy` se utiliza para envolver cualquier controlador de guardado personalizado, esta implementa la :phpclass:`SessionHandlerInterface`. - -En *PHP 5.4* y superior, todos los controladores de sesión implementan la :phpclass:`SessionHandlerInterface` incluyendo las clases ``Native*SessionHandler`` que heredan de :phpclass:`SessionHandler`. - -El mecanismo delegado te permite involucrarte profundamente en las clases controladoras de guardado de sesión. Podrías utilizar un delegado, por ejemplo, para cifrar cualquier transacción de la sesión sin el conocimiento específico del controlador de guardado. - -.. _`php.net/session.customhandler`: http://www.php.net/manual/es/session.customhandler.php -.. _`php.net/session.configuration`: http://dk2.php.net/session.configuration diff --git a/_sources/components/http_foundation/session_testing.txt b/_sources/components/http_foundation/session_testing.txt deleted file mode 100644 index 476e2be..0000000 --- a/_sources/components/http_foundation/session_testing.txt +++ /dev/null @@ -1,43 +0,0 @@ -.. index:: - single: HTTP - single: HttpFoundation, Sesiones - -Probando con sesiones -===================== - -*Symfony2* se diseñó desde el principio con la comprobabilidad de código en mente. Con el fin de hacer que el código que utiliza la sesión sea fácilmente comprobable, disponemos de dos mecanismos de almacenamiento independientes para simular ambas, pruebas unitarias y pruebas funcionales. - -Probar el código usando sesiones reales es difícil porque el estado del flujo de trabajo de *PHP* es global y no es posible tener varias sesiones simultáneas en el mismo proceso *PHP*. - -Los motores de almacenamiento fingido simulan el flujo de trabajo de las sesiones *PHP* sin tener que iniciar una, lo cual te permite probar tu código sin complicaciones. También es posible ejecutar varias instancias en el mismo proceso *PHP*. - -Los controladores de almacenamiento simulado no leen ni escriben las globales del sistema ``session_id()`` o ``session_name()``. Y proporcionan métodos para simularlas de ser necesario: - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::getId`: Recupera el ``id`` de la sesión. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::setId`: Establece el ``id`` de la sesión. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::getName`: Recupera el nombre de la sesión. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionStorageInterface::setName`: Establece el nombre de la sesión. - -Pruebas unitarias ------------------ - -Para las pruebas unitarias donde no es necesario persistir la sesión, simplemente debes intercambiar el motor de almacenamiento predefinido con :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockArraySessionStorage`:: - - use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage; - use Symfony\Component\HttpFoundation\Session\Session; - - $session = new Session(new MockArraySessionStorage()); - -Probando la funcionalidad -------------------------- - -Para las pruebas funcionales donde posiblemente necesites conservar los datos de sesión a través de procesos *PHP* independientes, basta con cambiar el motor de almacenamiento a la clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\\MockFileSessionStorage`:: - - use Symfony\Component\HttpFoundation\Session\Session; - use Symfony\Component\HttpFoundation\Session\Storage\MockFileSessionStorage; - - $session = new Session(new MockFileSessionStorage()); - diff --git a/_sources/components/http_foundation/sessions.txt b/_sources/components/http_foundation/sessions.txt deleted file mode 100644 index 0b1706c..0000000 --- a/_sources/components/http_foundation/sessions.txt +++ /dev/null @@ -1,275 +0,0 @@ -.. index:: - single: HTTP - single: HttpFoundation, Sesiones - -Gestión de la sesión -==================== - -El componente ``HttpFoundation`` de *Symfony2* tiene un subsistema de sesión muy potente y flexible, que está diseñado para proporcionar la gestión de sesiones a través de una sencilla interfaz orientada a objetos, utilizando una variedad de controladores para el almacenamiento de la sesión. - -.. versionadded:: 2.1 - La interfaz :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`, así como una serie de otros cambios, son nuevas a partir de *Symfony 2.1*. - -Las sesiones se utilizan a través de la simple clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` que implementa la interfaz :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`. - -Ejemplo rápido:: - - use Symfony\Component\HttpFoundation\Session\Session; - - $session = new Session(); - $session->start(); - - // establece y obtiene atributos de sesión - $session->set('name', 'Drak'); - $session->get('name'); - - // configura mensajes flash - $session->getFlashBag()->add('notice', 'Profile updated'); - - // recupera mensajes - foreach ($session->getFlashBag()->get('notice', array()) as $message) { - echo "
    $message
    "; - } - -.. note:: - - Las sesiones *Symfony* están diseñadas para sustituir varias funciones nativas de *PHP*. - Las aplicaciones deben evitar el uso de ``session_start()``, ``session_regenerate_id()``, `` session_id()``, ``session_name()``, y ``session_destroy()``, y en su lugar usar la *API* en la siguiente sección. - -.. note:: - - Si bien se recomienda empezar una sesión explícitamente, unas sesiones de hecho empezarán bajo demanda, es decir, si se hace alguna petición de sesión para leer/escribir dato de sesión. - -.. caution:: - - Las sesiones de *Symfony* son incompatibles con la directiva ``ini`` ``session.auto_start = 1`` de *PHP*. Esta directiva se debe apagar en ``php.ini``, en las directivas del servidor web o en ``.htaccess``. - -*API* de sesión -~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` implementa la interfaz :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionInterface`. - -La clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Session` tiene una *API* sencilla subdividida en un par de grupos. - -Flujo de trabajo en la sesión - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::start`: - Inicia la sesión --- no usa ``session_start()``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::migrate`: - Regenera el ``id`` de la sesión --- no usa ``session_regenerate_id()``---. - Opcionalmente, este método puede cambiar el tiempo de vida de la nueva ``cookie`` que será emitida al llamar a este método. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::invalidate`: - Limpia todos los datos de la sesión y regenera el ``id`` de la sesión. No usa ``session_destroy()``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getId`: Recupera el ``id`` de la sesión. Sin usar ``session_id()``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setId`: Establece el ``id`` de la sesión. Sin usar ``session_id()``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getName`: Recupera el nombre de la sesión. Sin usar ``session_name()``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::setName`: Establece el nombre de la sesión. Sin usar ``session_name()``. - -Atributos de la sesión - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::set`: - Establece un atributo por su clave; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::get`: - Recupera un atributo por su clave; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::all`: - Recupera todos los atributos como un arreglo de ``clave => valor``; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::has`: - Devuelve ``true`` si el atributo existe; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::keys`: - Devuelve un arreglo de claves de atributo guardadas; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::replace`: - Establece múltiples atributos simultáneamente: toma un arreglo indexado y configura cada pareja ``clave => valor``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::remove`: - Elimina un atributo por clave; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::clear`: - Limpia todos los atributos; - -Internamente los atributos se almacenan en una ``«Bag»`` ('Bolsa', en adelante), un objeto *PHP* que actúa como un arreglo. Unos cuantos métodos existentes para gestionar una ``«Bolsa»``: - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::registerBag`: - Registra una :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getBag`: - Consigue una :class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` por medio del nombre de la «Bolsa». - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getFlashBag`: - Recupera la :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface`. - Esta, únicamente es un conveniente atajo. - -Metadatos de sesión - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Session::getMetadataBag`: - Obtiene la clase :class:`Symfony\\Component\\HttpFoundation\\Session\\Storage\MetadataBag` la cual contiene información de la sesión. - - -Gestionando datos de sesión -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La gestión de sesiones *PHP* requiere el uso de la superglobal ``$_SESSION``, sin embargo, esto interfiere un poco con la comprobabilidad y encapsulación de código en un paradigma de programación orientado a objetos. Para ayudar a superar esta situación, *Symfony2* usa 'bolsas de sesión' vinculadas a la sesión para encapsular un conjunto de datos específico a los 'atributos' o 'mensajes flash'. - -Este enfoque también mitiga la contaminación del espacio de nombres dentro de la superglobal ``$_SESSION``, porque cada bolsa almacena todos sus datos bajo un único espacio de nombres. -Esto le permite a *Symfony2* coexistir pacíficamente con otras aplicaciones o bibliotecas que puedan usar la superglobal ``$_SESSION`` y todos los datos siguen siendo totalmente compatibles con la gestión de sesiones de *Symfony2*. - -*Symfony2* ofrece 2 tipos de bolsas para almacenamiento, con dos implementaciones independientes. -Todo está escrito contra interfaces para que las puedas ampliar o crear tus propios tipos de bolsa si es necesario. - -:class:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface` tiene la siguiente *API* destinada principalmente para uso interno: - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getStorageKey`: - Devuelve la clave, que en última instancia es la bolsa que va a almacenar tu arreglo bajo ``$_SESSION``. - En general, puedes dejar este valor en su predefinido y es para uso interno. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::initialize`: - *Symfony2* llama internamente a este método para almacenar clases de sesión con datos para vincularlos a la bolsa de la sesión. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\SessionBagInterface::getName`: - Devuelve el nombre de la bolsa de sesión. - - -``Atributos`` -~~~~~~~~~~~~~ - -El propósito de la implementación de la :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface` de bolsas, es manejar el almacenamiento de los atributos de la sesión. Esto puede incluir cosas como el ``id`` de usuario, y la funcionalidad «recuérdame» en la configuración del inicio de sesión u otra información basada en el estado del usuario. - -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBag` Esta es la implementación estándar predeterminada. - -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\NamespacedAttributeBag` Esta implementación permite que los atributos sean almacenados en un espacio de nombres estructurado. - -Cualquier sistema de almacenamiento sencillo ``clave => valor`` está limitado en la medida en que se puedan almacenar datos complejos, ya que cada clave debe ser única. Lo puedes lograr introduciendo una convención a la nomenclatura de los espacios de nombres para que las claves sean partes diferentes de tu aplicación lo cual podría funcionar sin colisiones. Por ejemplo, ``modulo1.foo`` y ``modulo2.foo``. Sin embargo, a veces esto no es muy práctico cuando los datos de los atributos están en un arreglo, por ejemplo, un conjunto de iniciales. En este caso, la gestión del arreglo se convierte en una carga porque hay que recuperar el arreglo, luego, procesarlo y almacenarlo de nuevo:: - - $tokens = array('tokens' => array('a' => 'a6c1e0b6', - 'b' => 'f4a7b1f3')); - -Así que cualquier procesamiento de este tipo rápidamente se puede poner feo, aunque sólo añadas un elemento al arreglo:: - - $tokens = $session->get('tokens'); - $tokens['c'] = $value; - $session->set('tokens', $tokens); - -Con el espacio de nombre estructurado, la clave se puede trasladar a la estructura del arreglo tal cual usando un carácter del espacio de nombres (por omisión es ``/``):: - - $session->set('tokens/c', $value); - -De esta manera puedes acceder a una clave dentro del arreglo almacenado directa y fácilmente. - -La :class:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface` cuenta con una *API* sencilla - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::set`: - Establece un atributo por su clave; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::get`: - Recupera un atributo por su clave; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::all`: - Recupera todos los atributos como un arreglo de ``clave => valor``; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::has`: - Devuelve ``true`` si el atributo existe; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::keys`: - Devuelve un arreglo de claves de atributo guardadas; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::replace`: - Establece múltiples atributos simultáneamente: toma un arreglo indexado y configura cada pareja ``clave => valor``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::remove`: - Elimina un atributo por clave; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Attribute\\AttributeBagInterface::clear`: - Vacía la bolsa; - - -Mensajes flash -~~~~~~~~~~~~~~ - -El propósito de la :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface` es proporcionar una forma de configurar y recuperar mensajes basándose en la sesión. -El flujo de trabajo habitual de los mensajes flash se encuentra en una ``Petición``, y se muestran después de redirigir a una página. Por ejemplo, un usuario envía un formulario que llegará a un controlador de actualización, y después de procesar el controlador redirige al usuario o bien a la página actualizada o a una página de error. Los mensajes flash establecidos en la ``Petición`` de página anterior se muestran inmediatamente al cargar la siguiente página en esa sesión. -Esta, sin embargo, sólo es una aplicación para los mensajes flash. - -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\AutoExpireFlashBag` Esta implementación de mensajes se ajusta al cargar la página para que esté disponible para visualizarlo al cargar la siguiente página. Estos mensajes expirarán automáticamente independientemente de si se recuperan o no. - -* :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBag` En esta implementación, los mensajes se mantendrán en la sesión hasta que se recuperen o borren explícitamente. Esto hace posible el uso de la caché *ESI*. - -La :class:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface` tiene una sencilla *API* - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::add`: - Añade un mensaje flash a la pila del tipo especificado; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::set`: - Configura mensajes flash por tipo; Este método toma ambos, mensajes como una ``cadena`` o varios mensajes en un ``arreglo``. - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::get`: - Recupera mensajes por tipo y los quita de la bolsa; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::setAll`: - Establece todos los flashes, acepta un arreglo de arreglos indexado ``tipo => array(mensajes)``; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::all`: - Recupera todos los mensajes flash (en forma de arreglo de arreglos indexado) y quita los mensajes de la bolsa; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peek`: - Recupera mensajes flash por tipo (sólo lectura); - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::peekAll`: - Recupera todos los mensajes flash (sólo lectura) como arreglo de arreglos indexado; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::has`: - Devuelve ``true`` si el tipo existe, ``false`` en caso contrario; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::keys`: - Devuelve un arreglo de tipos flash guardados; - -* :method:`Symfony\\Component\\HttpFoundation\\Session\\Flash\\FlashBagInterface::clear`: - Limpia la bolsa; - -Para aplicaciones simples por lo general es suficiente tener un mensaje flash por tipo, por ejemplo, un aviso de confirmación después de enviar un formulario. Sin embargo, los mensajes Flash se almacenan en un arreglo indexado por el ``$tipo`` del flash, lo cual significa que tu aplicación puede emitir varios mensajes de un determinado tipo. Esto te permite utilizar la *API* para mensajes más complejos en tu aplicación. - -Ejemplos de configuración de múltiples mensajes flash:: - - use Symfony\Component\HttpFoundation\Session\Session; - - $session = new Session(); - $session->start(); - - // añade mensajes flash - $session->getFlashBag()->add( - 'warning', - 'Your config file is writable, it should be set read-only' - ); - $session->getFlashBag()->add('error', 'Failed to update name'); - $session->getFlashBag()->add('error', 'Another error'); - -Para exhibir los mensajes flash puedes usar algo como esto: - -Simple, muestra un tipo de mensaje:: - - // muestra advertencias - foreach ($session->getFlashBag()->get('warning', array()) as $message) { - echo "
    $message
    "; - } - - // muestra errores - foreach ($session->getFlashBag()->get('error', array()) as $message) { - echo "
    $message
    "; - } - -Método compacto para procesar la exhibición simultánea de todos los mensajes:: - - foreach ($session->getFlashBag()->all() as $type => $messages) { - foreach ($messages as $message) { - echo "
    $message
    \n"; - } - } diff --git a/_sources/components/http_foundation/trusting_proxies.txt b/_sources/components/http_foundation/trusting_proxies.txt deleted file mode 100644 index 0e7aea4..0000000 --- a/_sources/components/http_foundation/trusting_proxies.txt +++ /dev/null @@ -1,42 +0,0 @@ -.. index:: - single: Petición; Delegados confiables - -Delegados confiables -==================== - -Si te encuentras detrás de alguna clase de delegado ---tal como un balanceador de carga--- entonces cierta información de las cabeceras se te puede enviar usando cabeceras ``X-Forwarded-*`` especiales. Por ejemplo, la cabecera *HTTP* ``Host`` normalmente se utiliza para regresar el servidor solicitado. Pero cuándo estás detrás de un delegado, el verdadero servidor se puede almacenar en una cabecera ``X-Forwarded-Host``. - -Debido a que las cabeceras *HTTP* se pueden suplantar, de manera predeterminada *Symfony2* *no* confía en estas cabeceras de delegados. Si estás detrás de un delegado, tienes que enumerar manualmente tu delegado en una lista blanca:: - - use Symfony\Component\HttpFoundation\Request; - - $request = Request::createFromGlobals(); - // únicamente confía en las cabeceras de delegados que vienen - // de esta dirección IP - $request->setTrustedProxies(array(192.0.0.1)); - -Configurando nombres de cabeceras ---------------------------------- - -De manera predeterminada, las siguientes cabeceras de delegados son confiables: - -* ``X-Forwarded-For`` Usada en :method:`Symfony\\Component\\HttpFoundation\\Request::getClientIp`; -* ``X-Forwarded-Host`` Usada en :method:`Symfony\\Component\\HttpFoundation\\Request::getHost`; -* ``X-Forwarded-Port`` Usada en :method:`Symfony\\Component\\HttpFoundation\\Request::getPort`; -* ``X-Forwarded-Proto`` Usada en :method:`Symfony\\Component\\HttpFoundation\\Request::getScheme` y :method:`Symfony\\Component\\HttpFoundation\\Request::isSecure`; - -Si tu delegado inverso utiliza un nombre de cabecera diferente para cualquiera de estas, puedes configurar el nombre de la cabecera a través del método :method:`Symfony\\Component\\HttpFoundation\\Request::setTrustedHeaderName`:: - - $request->setTrustedHeaderName(Request::HEADER_CLIENT_IP, 'X-Proxy-For'); - $request->setTrustedHeaderName(Request::HEADER_CLIENT_HOST, 'X-Proxy-Host'); - $request->setTrustedHeaderName(Request::HEADER_CLIENT_PORT, 'X-Proxy-Port'); - $request->setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, 'X-Proxy-Proto'); - -Determinadas cabeceras no confiables ------------------------------------- - -De manera predeterminada, si pones en una lista blanca tus direcciones *IP* delegadas, entonces todas tus cabeceras enumeradas arriba serán confiables. Si necesitas confiar en algunas de esas cabeceras pero no en otras, también lo puedes hacer:: - - // desactiva la confianza en la cabecera ``X-Forwarded-Proto``, usa - // la cabecera predefinida - $request->setTrustedHeaderName(Request::HEADER_CLIENT_PROTO, ''); diff --git a/_sources/components/http_kernel/index.txt b/_sources/components/http_kernel/index.txt deleted file mode 100644 index a60c91c..0000000 --- a/_sources/components/http_kernel/index.txt +++ /dev/null @@ -1,7 +0,0 @@ -*HTTP* del núcleo -================= - -.. toctree:: - :maxdepth: 2 - - introduction diff --git a/_sources/components/http_kernel/introduction.txt b/_sources/components/http_kernel/introduction.txt deleted file mode 100644 index 4be51c9..0000000 --- a/_sources/components/http_kernel/introduction.txt +++ /dev/null @@ -1,482 +0,0 @@ -.. index:: - single: HTTP - single: HttpKernel - single: Componentes; HttpKernel - -El componente ``HttpKernel`` -============================ - - El componente ``HttpKernel`` proporciona un proceso estructurado para convertir una ``Petición`` en una ``Respuesta`` usando el despachador de eventos. - Es bastante flexible para crear una plataforma completa (*Symfony*), una microplataforma (*Silex*) o un sistema *CMS* avanzado (*Drupal*). - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/httpkernel); -* :doc:`Instalándolo vía Composer ` (``symfony/http-kernel`` en `Packagist`_). - -El flujo de trabajo de una ``Petición`` ---------------------------------------- - -Cada interacción *web* de *HTTP* empieza con una petición y termina con una respuesta. -Tu trabajo como desarrollador es crear el código *PHP* que lee la información de la petición -(p. ej. la *URL*) y crea y devuelve una respuesta (p. ej. una página *HTML* o una cadena *JSON*). - -.. image:: /images/components/http_kernel/request-response-flow.png - :align: center - -Típicamente, alguna clase de la plataforma o sistema está construida para manejar todas las tareas -repetitivas (p. ej. enrutado, seguridad, etc.) a modo de que un desarrollador pueda fácilmente construir -cada *página* de la aplicación. Exactamente *cómo* están construidos estos sistemas varía mucho. El componente ``HttpKernel`` proporciona una interfaz que formaliza el proceso de empezar con una petición y crear la respuesta apropiada. -El componente pretende ser el corazón de cualquier aplicación o plataforma, no importa qué tanto varíe la arquitectura de ese sistema:: - - namespace Symfony\Component\HttpKernel; - - use Symfony\Component\HttpFoundation\Request; - - interface HttpKernelInterface - { - // ... - - /** - * @return Response Una instancia de Response - */ - public function handle( - Request $request, - $type = self::MASTER_REQUEST, - $catch = true - ); - } - -Internamente, el método :method:`HttpKernel::handle()` --- implementa concretamente el método :method:`HttpKernelInterface::handle()` --- define un flujo de trabajo que inicia con una :class:`Symfony\\Component\\HttpFoundation\\Request` y termina con una :class:`Symfony\\Component\\HttpFoundation\\Response`. - -.. image:: /images/components/http_kernel/01-workflow.png - :align: center - -Los detalles exactos de este flujo de trabajo son la clave para entender cómo trabaja el núcleo (y la plataforma *Symfony* o cualquier otra biblioteca que use el núcleo). - -``HttpKernel``: Conducido por eventos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El método ``HttpKernel::handle()`` internamente trabaja despachando eventos. -Esto hace al método tanto flexible, pero también un poco abstracto, debido a que todo el «trabajo» de una aplicación/plataforma construida con ``HttpKernel`` está hecha con escuchas de eventos. - -Para ayudar a explicar este proceso, este documento mira cada paso del proceso y habla sobre cómo trabaja una implementación específica del ``HttpKernel`` ---la plataforma *Symfony*---. - -Inicialmente, utilizar el :class:`Symfony\\Component\\HttpKernel\\HttpKernel` es realmente sencillo, e implica crear un :doc:`despachador de eventos ` y un :ref:`resolutor de controlador ` (explicado más adelante). Para completar tu núcleo, añadirás más escuchas de eventos para los eventos explicados más adelante:: - - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpKernel\HttpKernel; - use Symfony\Component\EventDispatcher\EventDispatcher; - use Symfony\Component\HttpKernel\Controller\ControllerResolver; - - // crea el objeto Request - $request = Request::createFromGlobals(); - - $dispatcher = new EventDispatcher(); - // ... añade algunos escuchas de eventos - - // crea tu resolutor de controlador - $resolver = new ControllerResolver(); - // crea una instancia del núcleo - $kernel = new HttpKernel($dispatcher, $resolver); - - // en realidad ejecuta el núcleo, el cuál convierte la petición en una respuesta - // despachando eventos, invocando a un controlador, y devolviendo la respuesta - $response = $kernel->handle($request); - - // difunde el contenido y envía las cabeceras - $response->send(); - - // desencadena el evento kernel.terminate - $kernel->terminate($request, $response); - -Ve «:ref:`http-kernel-working-example`» para más implementaciones concretas. - -Para información general sobre cómo añadir escuchas a los siguientes eventos, ve :ref:`http-kernel-creating-listener`. - -.. tip:: - - Fabien Potencier también escribió una magnífica serie sobre la utilización del componente ``HttpKernel`` y otros componentes de *Symfony2* para crear tu propia plataforma. Ve `Crea tu propia plataforma... en lo alto de los componentes de Symfony2`_. - -.. _component-http-kernel-kernel-request: - -1) El evento ``kernel.request`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Propósito típico**: Añadir más información a la ``Petición``, iniciar partes del sistema, o regresar una ``Respuesta`` si es posible (p. ej. una capa -de seguridad que niega el acceso). - -:ref:`Tabla de información de eventos del núcleo ` - -El primer evento despachado dentro del método :method:`HttpKernel::handle` es ``kernel.request``, el cual puede tener una serie de diferentes escuchas. - -.. image:: /images/components/http_kernel/02-kernel-request.png - :align: center - -Los escuchas de este evento pueden ser bastante diversos. Algunos escuchas ---tal como un escucha de seguridad--- podrían tener suficiente información para crear un objeto ``Respuesta`` inmediatamente. -Por ejemplo, si un escucha de seguridad determinó que un usuario no tiene acceso, ese escucha puede regresar una :class:`Symfony\\Component\\HttpFoundation\\RedirectResponse` a la página de inicio de sesión o una respuesta 403 de «Acceso denegado». - -Si al llegar a este punto regresa una ``Respuesta``, el proceso salta directamente al evento :ref:`kernel.response`. - -.. image:: /images/components/http_kernel/03-kernel-request-response.png - :align: center - -Otros escuchas sencillamente inician cosas o añaden más información a la petición. -Por ejemplo, un escucha podría determinar y poner la región en el objeto ``Petición``. - -Otro escucha común es el de enrutado. Un escucha enrutador puede procesar la ``Petición`` y determinar el controlador que se debería ejecutar (ve la próxima sección). -De hecho, el objeto ``Petición`` tiene una bolsa de «:ref:`atributos `» misma que es un sitio perfecto para almacenar este dato extra, específico de la aplicación sobre la petición. Esto significa que si tu escucha enrutador de alguna manera determina el controlador, lo puedes almacenar en los atributos de la ``Petición`` (los cuáles puede utilizar el resolutor del controlador). - -En general, el propósito del evento ``kernel.request`` es o bien, crear y regresar una ``Respuesta`` directamente, o añadir información a la ``Petición`` (p. ej. poniendo la región o configurando alguna otra información en los atributos de la ``Petición``). - -.. sidebar:: ``kernel.request`` en la plataforma *Symfony* - - El escucha más importante para ``kernel.request`` en la plataforma *Symfony* es el :class:`Symfony\\Component\\HttpKernel\\EventListener\\RouterListener`. - Esta clase ejecuta la capa de enrutado, la cual regresa un *arreglo* de información sobre la petición emparejada, incluyendo el ``_controller`` y cualquier marcador de posición que esté en el patrón de la ruta (p. ej. ``{slug}``). Ve el :doc:`componente Routing `. - - Este arreglo de información está almacenado en el arreglo ``attributes`` del objeto :class:`Symfony\\Component\\HttpFoundation\\Request`. Añadir la información de enrutado aquí no hace nada todavía, pero se utiliza luego al resolver el controlador. - -.. _component-http-kernel-resolve-controller: - -2) Resolviendo el controlador -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Suponiendo que ningún escucha del ``kernel.request`` fuera capaz de crear una ``Respuesta``, el próximo paso en ``HttpKernel`` es determinar y preparar (es decir, resolver) el controlador. El controlador es la parte final del código de la aplicación que es responsable de crear y regresar la ``Respuesta`` para una página específica. -El único requisito es que sea una función *PHP* que se pueda llamar ---es decir, una función o método en un objeto, o un ``Cierre``---. - -Pero *el cómo* determinar el controlador exacto para una petición es responsabilidad enteramente de tu aplicación. Este es el trabajo del «resolutor de controlador» ---una clase que implementa la :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface` y es uno de los argumentos del constructor del ``HttpKernel``. - -.. image:: /images/components/http_kernel/04-resolve-controller.png - :align: center - -Tu trabajo es crear una clase que implemente la interfaz y rellenar sus dos métodos: ``getController`` y ``getArguments``. De hecho, ya existe una implementación predefinida, la cual puedes utilizar directamente o estudiar para ver cómo trabaja: -:class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver`. -Esta implementación se explica más en la barra lateral de abajo:: - - namespace Symfony\Component\HttpKernel\Controller; - - use Symfony\Component\HttpFoundation\Request; - - interface ControllerResolverInterface - { - public function getController(Request $request); - - public function getArguments(Request $request, $controller); - } - -Internamente, el ``HttpKernel::handle`` primero llama al método :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getController` en el resolutor del controlador. Este método es pasado a la ``Petición`` y es el responsable de determinar de alguna manera y regresar un ejecutable *PHP* (el controlador) basándose en la información de la petición. - -El segundo método, :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`, será llamado después de lanzado otro evento ---``kernel.controller``---. - -.. sidebar:: Resolviendo el controlador en la plataforma *Symfony2* - - La plataforma *Symfony* utiliza la clase integrada :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver` (de hecho, utiliza una subclase, la cual cuenta con alguna funcionalidad extra, mencionada más adelante). Esta clase aprovecha la información colocada en la propiedad ``attributes`` del objeto ``Petición`` durante el ``RouterListener``. - - **getController** - - El ``ControllerResolver`` busca una clave ``_controller`` en la propiedad ``attributes`` del objeto ``Petición`` (recuerda que esta información típicamente se coloca en la ``Petición`` vía el ``RouterListener``). - Esta cadena entonces es transformada a un ejecutable *PHP* haciendo lo siguiente: - - a) El formato del ``AcmeDemoBundle:Default:index`` de la clave ``_controller`` se cambia a otra cadena que contiene la clase completa y nombre del método controlador siguiendo la convención utilizada en *Symfony2* ---p. ej. ``Acme\DemoBundle\Controller\DefaultController::indexAction``---. Esta transformación es específica a la subclase :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver` utilizada por la plataforma *Symfony2*. - - b) Se crea una nueva instancia de tu clase controlador sin argumentos del constructor. - - c) Si el controlador implementa la :class:`Symfony\\Component\\DependencyInjection\\ContainerAwareInterface`, se llama a ``setContainer`` en el objeto controlador y se pasa al contenedor. Este paso también es específico de la subclase :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver` utilizada por la plataforma *Symfony2*. - - También hay otras cuantas variaciones más en el proceso anterior (p. ej. si estás registrando tus controladores como servicios). - -3) El evento ``kernel.controller`` ----------------------------------- - -**Propósito típico**: Iniciar cosas o cambiar el controlador justo antes de ejecutarlo. - -:ref:`Tabla de información de eventos del núcleo ` - -Después de haber determinado el controlador a ejecutar, ``HttpKernel::handle`` desencadena el evento ``kernel.controller``. Los escuchas de este evento podrían iniciar alguna parte necesaria del sistema después de determinar ciertas cosas (p. ej. el controlador, información de enrutado) pero antes de ejecutar el controlador. Para algunos ejemplos, ve la sección *Symfony2* abajo. - -.. image:: /images/components/http_kernel/06-kernel-controller.png - :align: center - -Los escuchas de este evento también pueden cambiar completamente el controlador ejecutable llamando al método :method:`FilterControllerEvent::setController` en el objeto ``Evento`` pasado a los escuchas en este evento. - -.. sidebar:: ``kernel.controller`` en la plataforma *Symfony* - - Hay unos cuantos escuchas menores del evento ``kernel.controller`` en la plataforma *Symfony*, y muchos tratan con la recolección de datos del perfilador cuándo está habilitado. - - Un interesante escucha proviene del :doc:`SensioFrameworkExtraBundle `, el cual viene empacado con la *Edición estándar de Symfony*. El escucha :doc:`@ParamConverter ` es la funcionalidad que te permite pasar un objeto completo (p. ej. un objeto ``Post``) a tu controlador en vez de un valor escalar (p. ej. un parámetro ``id`` que venía en tu ruta). El escucha ---``ParamConverterListener``--- usa la reflexión para mirar en cada uno de los argumentos del controlador e intenta utilizar diferentes métodos para convertirlos a objetos, los cuales entonces son almacenados en la propiedad ``attributes`` del objeto ``Petición``. Lee la siguiente sección para ver por qué esto es importante. - -4) Obteniendo los argumentos del controlador --------------------------------------------- - -Luego, ``HttpKernel::handle`` llama al método :method:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface::getArguments`. -Recuerda que el controlador devuelto en ``getController`` es ejecutable. -El propósito de ``getArguments`` es devolver el arreglo de argumentos que se deberían pasar a ese controlador. Exactamente cómo se hace esto es responsabilidad completamente de tu diseño, aunque el :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver` incorporado es un buen ejemplo. - -.. image:: /images/components/http_kernel/07-controller-arguments.png - :align: center - -Al llegar a este punto el núcleo tiene un ejecutable *PHP* (el controlador) y un arreglo de argumentos que se debería pasar al llamar al ejecutable. - -.. sidebar:: Obteniendo los argumentos del controlador en la plataforma *Symfony2* - - Ahora que sabes exactamente cuál es el controlador a ejecutar (normalmente un método dentro de un objeto ``Controlador``), el ``ControllerResolver`` usa la `Reflexión`_ en el ejecutable para devolver un arreglo con los *nombres* de cada uno de los argumentos. - Luego, itera sobre cada uno de estos argumentos y utiliza los siguientes trucos para determinar qué valor se debería pasar a cada argumento: - - a) Si la bolsa de atributos de la ``Petición`` contiene una clave que coincide con el nombre del argumento, utiliza ese valor. Por ejemplo, si el primer argumento para un controlador es ``$slug``, y hay una clave ``slug`` en la bolsa de ``atributos`` de la ``Petición``, se utiliza ese valor (y típicamente ese valor provino del ``RouterListener``). - - b) Si el argumento en el controlador cuenta con una insinuación de tipo en el objeto :class:`Symfony\\Component\\HttpFoundation\\Request` de *Symfony*, entonces se pasa la ``Petición`` como el valor. - -.. _component-http-kernel-calling-controller: - -5) Llamando al controlador -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -¡El siguiente paso es sencillo! ``HttpKernel::handle`` ejecuta el controlador. - -.. image:: /images/components/http_kernel/08-call-controller.png - :align: center - -El trabajo del controlador es construir la respuesta para el recurso dado. -Esta podría ser una página *HTML*, una cadena *JSON* o cualquier otra cosa. A diferencia de todas las demás partes del proceso hasta ahora, este paso es implementado por el «desarrollador final», por cada página construida. - -Normalmente, el controlador regresará un objeto ``Respuesta``. Si esto es cierto, ¡entonces el trabajo del núcleo está hecho! En este caso, el próximo paso es el evento :ref:`kernel.response `. - -.. image:: /images/components/http_kernel/09-controller-returns-response.png - :align: center - -Pero si el controlador regresa cualquier otra cosa más alla de una ``Respuesta``, entonces el núcleo tiene que hacer un poco más de trabajo ---:ref:`kernel.view ` (debido a que el objetivo final *siempre* es generar un objeto ``Respuesta``). - -.. note:: - - Un controlador tiene que regresar *algo*. Si un controlador regresa ``null``, inmediatamente se lanzará una excepción. - -.. _component-http-kernel-kernel-view: - -6) El evento ``kernel.view`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Propósito típico**: Transformar en ``Respuesta`` un valor devuelto por un controlador que no es una ``Respuesta``. - -:ref:`Tabla de información de eventos del núcleo ` - -Si el controlador no regresa un objeto ``Respuesta``, entonces el núcleo despacha otro evento ---``kernel.view``---. El trabajo de un escucha de este evento es utilizar el valor devuelto por el controlador (p. ej. un arreglo de datos o un objeto) para crear una ``Respuesta``. - -.. image:: /images/components/http_kernel/10-kernel-view.png - :align: center - -Esto puede ser útil si quieres utilizar una capa para la «vista»: en vez de regresar una ``Respuesta`` desde el controlador, regresas los datos que representan la página. -Un escucha de este evento entonces podría utilizar estos datos para crear una ``Respuesta`` en el formato correcto (p. ej. *HTML*, *json*, etc.). - -En esta etapa, si ningún escucha pone una respuesta en el evento, entonces se lanza una excepción: ya sea un controlador *o* uno de los escuchas de la vista siempre deben regresar una ``Respuesta``. - -.. sidebar:: ``kernel.view`` en la plataforma *Symfony* - - No hay escucha predefinido dentro de la plataforma *Symfony* para el ``kernel.view``. Sin embargo, un paquete del núcleo ---:doc:`SensioFrameworkExtraBundle `--- *añade* un escucha para este evento. Si tu controlador regresa un arreglo, y colocas la anotación :doc:`@Template` encima del controlador, entonces este escucha reproduce una plantilla, pasándo a esa plantilla el arreglo que regresó tu controlador, y crea una ``Respuesta`` con el contenido devuelto por esa plantilla. - - Además, un popular paquete de la comunidad `FOSRestBundle`_ implementa un escucha para este evento cuyo objetivo es darte una robusta capa para la vista capaz de utilizar un solo controlador para regresar muchas respuestas con diferente ``content-type`` (p. ej. *HTML*, *JSON*, *XML*, etc.). - -.. _component-http-kernel-kernel-response: - -7) El evento ``kernel.response`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Propósito típico**: Modificar el objeto ``Respuesta`` justo antes de enviarlo - -:ref:`Tabla de información de eventos del núcleo ` - -El objetivo final del núcleo es transformar una ``Petición`` a una ``Respuesta``. La ``Respuesta`` se podría crear durante el evento :ref:`kernel.request`, regresado del :ref:`controller`, o regresado por uno de los escuchas del evento :ref:`kernel.view`. - -Independientemente de quién creó la ``Respuesta``, otro evento ---``kernel.response``--- es despachado inmediatamente después. Un escucha típico de este evento modificará de alguna manera el objeto ``Respuesta``, tal como modificar cabeceras, añadir ``galletas``, o incluso cambiando el contenido de la ``Respuesta`` él (p. ej. inyectando algún -*JavaScript* antes de la etiqueta ```` final de una respuesta *HTML*). - -Después de despachado este evento, el objeto ``Respuesta`` final es regresado desde el método :method:`Symfony\\Component\\HttpKernel\\HttpKernel::handle`. En el caso de uso más típico, entonces puedes llamar al método :method:`Symfony\\Component\\HttpFoundation\\Response::send`, el cual envía las cabeceras e imprime el contenido de la ``Respuesta``. - -.. sidebar:: ``kernel.response`` en la plataforma *Symfony* - - Hay varios escuchas menores de este evento dentro de la plataforma *Symfony*, y la mayoría modifica la respuesta en alguna manera. Por ejemplo, el :class:`Symfony\\Bundle\\WebProfilerBundle\\EventListener\\WebDebugToolbarListener` inyecta algún *JavaScript* en el fondo de tu página en el entorno ``dev`` lo cual provoca que la barra de depuración web sea mostrada. Otro escucha, :class:`Symfony\\Component\\Security\\Http\\Firewall\\ContextListener` serializa la información del usuario actual en la sesión a modo de que se pueda recargar en la próxima petición. - -8) El evento ``kernel.terminate`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El evento ``kernel.terminate`` es nuevo en *Symfony 2.1*. - -**Propósito típico**: Realizar alguna acción «pesada» después de transmitir la respuesta al usuario. - -:ref:`Tabla de información de eventos del núcleo ` - -The final event of the HttpKernel process is ``kernel.terminate`` and is unique -because it occurs *after* the ``HttpKernel::handle`` method, and after the -response is sent to the user. Recuerda de arriba, entonces que el código que usa el núcleo, acaba así:: - - // difunde el contenido y envía las cabeceras - $response->send(); - - // desencadena el evento kernel.terminate - $kernel->terminate($request, $response); - -Como puedes ver, al llamar al ``$kernel->terminate`` después de enviar la respuesta, desencadenas el evento ``kernel.terminate`` donde puedes llevar a cabo determinadas acciones que puediste haber diferido para regresar la respuesta al cliente tan rápidamente como fuera posible (p. ej. el envío de correo electrónico). - -.. note:: - - Usar el evento ``kernel.terminate`` es opcional, y sólo lo deberías llamar si tu núcleo implementa la :class:`Symfony\\Component\\HttpKernel\\TerminableInterface`. - -.. sidebar:: ``kernel.terminate`` en la plataforma *Symfony* - - Si utilizas el ``SwiftmailerBundle`` con *Symfony2* y usas operaciones simultánea en ``memoria``, entonces la clase :class:`Symfony\\Bundle\\SwiftmailerBundle\\EventListener\\EmailSenderListener` es activada, la cual de hecho entrega cualquier correo electrónico que tengas planificado enviar durante la petición. - -.. _component-http-kernel-kernel-exception: - -Manejando excepciones: el evento ``kernel.exception`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -**Propósito típico**: Manejar algún tipo de excepción y crear una ``Respuesta`` apropiada para que la regrese la excepción. - -:ref:`Tabla de información de eventos del núcleo ` - -Si se desencadena una excepción en cualquier punto dentro del ``HttpKernel::handle``, se lanza otro evento ---``kernel.exception``---. Internamente, el cuerpo de la función ``handle`` está envuelto en un bloque prueba-captura. Al lanzar cualquier excepción, el ``kernel.exception`` es lanzado a modo de que tu sistema pueda responder de alguna manera a la excepción. - -.. image:: /images/components/http_kernel/11-kernel-exception.png - :align: center - -Cada escucha de este evento se pasa a un objeto :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent`, el cual puedes utilizar para acceder a la excepción original vía el método :method:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent::getException`. Un escucha típico de este evento comprobará algún determinado tipo de excepción y creará una ``Respuesta`` de error apropiada. - -Por ejemplo, para generar una página 404, podrías lanzar un tipo de excepción especial y luego añadir un escucha para este evento que busque esta excepción y genere y regrese una ``Respuesta`` 404. De hecho, el componente ``HttpKernel`` viene con un :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`, el cual si eliges usarlo, de manera predeterminada hará esto y más (ve la barra lateral abajo para más detalles). - -.. sidebar:: ``kernel.exception`` en la plataforma *Symfony* - - Al utilizar la plataforma *Symfony* hay dos escuchas principales del ``kernel.exception``. - - **ExceptionListener en HttpKernel** - - El primero viene desde el núcleo del componente ``HttpKernel`` y se llama :class:`Symfony\\Component\\HttpKernel\\EventListener\\ExceptionListener`. - El escucha tiene varios objetivos: - - 1) Al lanzar una excepción esta es convertida a un objeto :class:`Symfony\\Component\\HttpKernel\\Exception\\FlattenException`, la cual contiene toda la información sobre la petición, pero esta se puede imprimir y serializar. - - 2) Si la excepción original implementa la :class:`Symfony\\Component\\HttpKernel\\Exception\\HttpExceptionInterface`, entonces ``getStatusCode`` y ``getHeaders`` son invocados en la excepción y utilizados para poblar las cabeceras y código de estado del objeto ``FlattenException``. La idea es que estos se utilicen en el siguiente paso al crear la respuesta final. - - 3) Un controlador es ejecutado y pasado a la excepción aplanada. El controlador exacto a ejecutar se pasa como argumento del constructor de este escucha. - Este controlador regresará la ``Respuesta`` final para esta página de error. - - **ExceptionListener en Security** - - El otro escucha importante es :class:`Symfony\\Component\\Security\\Http\\Firewall\\ExceptionListener`. - El objetivo de este escucha es manejar excepciones de seguridad y, cuándo sea apropiado, *ayudar* a autentificar al usuario (p. ej. redirigiéndolo a la página de inicio de sesión). - -.. _http-kernel-creating-listener: - -Creando un escucha del evento ------------------------------ - -Como has visto, puedes crear y suscribir escuchas de evento a cualquiera de los eventos despachados durante el ciclo del ``HttpKernel::handle``. Típicamente un escucha es una clase *PHP* con un método retrollamado, pero puede ser cualquier cosa. Para más información sobre la creación y suscripción de escuchas de evento, ve :doc:`/components/event_dispatcher/introduction`. - -El nombre de cada uno de los eventos del «núcleo» está definido como constante en la clase :class:`Symfony\\Component\\HttpKernel\\KernelEvents` Además, a cada escucha de evento se le pasa un único argumento, el cual es alguna subclase de :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`. -Este objeto contiene información sobre el estado actual del sistema y cada evento tiene su propio objeto ``Evento``: - -.. _component-http-kernel-event-table: - -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ -| **Nombre** | **Constante** ``KernelEvents`` | **Argumento pasado al escucha** | -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ -| kernel.request | ``KernelEvents::REQUEST`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent` | -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ -| kernel.controller | ``KernelEvents::CONTROLLER`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FilterControllerEvent` | -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ -| kernel.view | ``KernelEvents::VIEW`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent` | -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ -| kernel.response | ``KernelEvents::RESPONSE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\FilterResponseEvent` | -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ -| kernel.terminate | ``KernelEvents::TERMINATE`` | :class:`Symfony\\Component\\HttpKernel\\Event\\PostResponseEvent` | -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ -| kernel.exception | ``KernelEvents::EXCEPTION`` | :class:`Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent` | -+-------------------+--------------------------------+-------------------------------------------------------------------------------------+ - -.. _http-kernel-working-example: - -Un ejemplo completo funcionando -------------------------------- - -Cuándo utilizas el componente ``HttpKernel``, eres libre de asociar cualquier escucha a los eventos del núcleo y utilizar cualquier resolutor de controlador que implemente la :class:`Symfony\\Component\\HttpKernel\\Controller\\ControllerResolverInterface`. -No obstante, el componente ``HttpKernel`` viene con algunos escuchas incorporados y un ``ControllerResolver`` que puedes usar para crear un ejemplo que funcione:: - - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpFoundation\Response; - use Symfony\Component\HttpKernel\HttpKernel; - use Symfony\Component\EventDispatcher\EventDispatcher; - use Symfony\Component\HttpKernel\Controller\ControllerResolver; - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - use Symfony\Component\Routing\Matcher\UrlMatcher; - use Symfony\Component\Routing\RequestContext; - - $routes = new RouteCollection(); - $routes->add('hello', new Route('/hello/{name}', array( - '_controller' => function (Request $request) { - return new Response(sprintf("Hello %s", $request->get('name'))); - } - ), - )); - - $request = Request::createFromGlobals(); - - $matcher = new UrlMatcher($routes, new RequestContext()); - - $dispatcher = new EventDispatcher(); - $dispatcher->addSubscriber(new RouterListener($matcher)); - - $resolver = new ControllerResolver(); - $kernel = new HttpKernel($dispatcher, $resolver); - - $response = $kernel->handle($request); - $response->send(); - - $kernel->terminate($request, $response); - -Subpeticiones -------------- - -Además de la petición «principal» enviada al ``HttpKernel::handle``, también puedes enviar las así llamadas «subpeticiones». Una subpetición se ve y actúa como cualquier otra petición, pero típicamente sirve para reproducir sólo una pequeña porción de una -página en vez de una página completa. Generalmente haces subpeticiones desde tu controlador (o quizás desde dentro de una plantilla, aquella que está reproduciendo tu controlador). - -.. image:: /images/components/http_kernel/sub-request.png - :align: center - -Para ejecutar una subpetición, usa el ``HttpKernel::handle``, pero cambia el segundo argumento de la siguiente manera:: - - use Symfony\Component\HttpFoundation\Request; - use Symfony\Component\HttpKernel\HttpKernelInterface; - - // ... - - // crea alguna otra petición manualmente como se necesaria - $request = new Request(); - // por ejemplo, posiblemente definas este _controller manualmente - $request->attributes->add('_controller', '...'); - - $response = $kernel->handle($request, HttpKernelInterface::SUB_REQUEST); - // hace algo con esta respuesta - -Esto crea otro ciclo petición-respuesta completo donde esta nueva ``Petición`` es transformada en una ``Respuesta``. La única diferencia es que internamente algunos escuchas (p. ej. seguridad) sólo pueden actuar en la petición maestra. A cada escucha se le pasa alguna subclase de :class:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent`, dónde puedes usar el método :method:`Symfony\\Component\\HttpKernel\\Event\\KernelEvent::getRequestType` para determinar si la petición actual es la «maestra» o una «subpetición». - -Por ejemplo, un escucha que únicamente debe actuar en la petición maestra puede tener esta apariencia:: - - use Symfony\Component\HttpKernel\HttpKernelInterface; - // ... - - public function onKernelRequest(GetResponseEvent $event) - { - if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) { - return; - } - - // ... - } - -.. _`Packagist`: https://packagist.org/packages/symfony/http-kernel -.. _`Reflexión`: http://php.net/manual/en/book.reflection.php -.. _FOSRestBundle: https://github.com/friendsofsymfony/FOSRestBundle -.. _`Crea tu propia plataforma... en lo alto de los componentes de Symfony2`: http://fabien.potencier.org/article/50/create-your-own-framework-on-top-of-the-symfony2-components-part-1 diff --git a/_sources/components/index.txt b/_sources/components/index.txt deleted file mode 100644 index fe3d217..0000000 --- a/_sources/components/index.txt +++ /dev/null @@ -1,31 +0,0 @@ -Componentes -=========== - -.. toctree:: - :hidden: - - using_components - class_loader - config/index - console/index - css_selector - debug - dom_crawler - dependency_injection/index - event_dispatcher/index - filesystem - finder - http_foundation/index - http_kernel/index - locale - options_resolver - process - property_access/index - routing/index - security/index - serializer - stopwatch - templating - yaml/index - -.. include:: /components/map.rst.inc diff --git a/_sources/components/locale.txt b/_sources/components/locale.txt deleted file mode 100644 index ac46931..0000000 --- a/_sources/components/locale.txt +++ /dev/null @@ -1,73 +0,0 @@ -.. index:: - single: Locale - single: Componentes; Locale - -El componente ``Locale`` -======================== - - El componente ``Locale`` proporciona el código de reserva para manejar aquellos casos en que falta la extensión ``intl``. - Además esta extiende la implementación de una clase :phpclass:`Locale` nativa con varios métodos útiles. - -Proveyendo el reemplazo de las siguientes funciones y clases: - -* :phpfunction:`intl_is_failure` -* :phpfunction:`intl_get_error_code` -* :phpfunction:`intl_get_error_message` -* :phpclass:`Collator` -* :phpclass:`IntlDateFormatter` -* :phpclass:`Locale` -* :phpclass:`NumberFormatter` - -.. note:: - - La implementación únicamente es compatible con la región "\ ``en``\ ". - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Locale); -* :doc:`Instalándolo vía Composer ` (``symfony/locale`` en `Packagist`_). - -Usando ------- - -Aprovechar el código de reserva incluye requerir funciones cooperantes y añadir clase cooperantes al cargador automático. - -Cuando se utiliza el componente ``ClassLoader`` el siguiente código es suficiente para complementar la extensión ``intl`` faltante: - -.. code-block:: php - - if (!function_exists('intl_get_error_code')) { - require __DIR__.'/ruta/a/src/Symfony/Component/Locale/Resources/stubs/functions.php'; - - $loader->registerPrefixFallbacks( - array(__DIR__.'/ruta/a/src/Symfony/Component/Locale/Resources/stubs') - ); - } - -:class:`Symfony\\Component\\Locale\\Locale` enriquece a la clase nativa :phpclass:`Locale` con características adicionales: - -.. code-block:: php - - use Symfony\Component\Locale\Locale; - - // Obtiene los nombres de países para una región u obtiene todos los códigos de país - $countries = Locale::getDisplayCountries('pl'); - $countryCodes = Locale::getCountries(); - - // Obtiene los nombres de idiomas para una región u obtiene todos los códigos de idioma - $languages = Locale::getDisplayLanguages('fr'); - $languageCodes = Locale::getLanguages(); - - // Obtiene los nombres de configuración regional para un determinado código - // u obtiene todos los códigos de región - $locales = Locale::getDisplayLocales('en'); - $localeCodes = Locale::getLocales(); - - // Obtiene versiones ICU - $icuVersion = Locale::getIntlIcuVersion(); - $icuDataVersion = Locale::getIcuDataVersion(); - -.. _`Packagist`: https://packagist.org/packages/symfony/locale diff --git a/_sources/components/options_resolver.txt b/_sources/components/options_resolver.txt deleted file mode 100644 index c698d38..0000000 --- a/_sources/components/options_resolver.txt +++ /dev/null @@ -1,300 +0,0 @@ -.. index:: - single: Options Resolver - single: Componentes; OptionsResolver - -The OptionsResolver Component -============================= - - The OptionsResolver Component helps you configure objects with option - arrays. It supports default values, option constraints and lazy options. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Use the official Git repository (https://github.com/symfony/OptionsResolver -* :doc:`Install it via Composer` (``symfony/options-resolver`` on `Packagist`_) - -Usando ------- - -Imagine you have a ``Person`` class which has 2 options: ``firstName`` and -``lastName``. These options are going to be handled by the OptionsResolver -Component. - -First, create the ``Person`` class:: - - class Person - { - protected $options; - - public function __construct(array $options = array()) - { - } - } - -You could of course set the ``$options`` value directly on the property. Instead, -use the :class:`Symfony\\Component\\OptionsResolver\\OptionsResolver` class -and let it resolve the options by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::resolve`. -The advantages of doing this will become more obvious as you continue:: - - use Symfony\Component\OptionsResolver\OptionsResolver; - - // ... - public function __construct(array $options = array()) - { - $resolver = new OptionsResolver(); - - $this->options = $resolver->resolve($options); - } - -The ``$options`` property is an instance of -:class:`Symfony\\Component\\OptionsResolver\\Options`, which implements -:phpclass:`ArrayAccess`, :phpclass:`Iterator` and :phpclass:`Countable`. That -means you can handle it just like a normal array:: - - // ... - public function getFirstName() - { - return $this->options['firstName']; - } - - public function getFullName() - { - $name = $this->options['firstName']; - - if (isset($this->options['lastName'])) { - $name .= ' '.$this->options['lastName']; - } - - return $name; - } - -Now, try to actually use the class:: - - $person = new Person(array( - 'firstName' => 'Wouter', - 'lastName' => 'de Jong', - )); - - echo $person->getFirstName(); - -Right now, you'll receive a -:class:`Symfony\\Component\\OptionsResolver\\Exception\\InvalidOptionsException`, -which tells you that the options ``firstName`` and ``lastName`` do not exist. -This is because you need to configure the ``OptionsResolver`` first, so it -knows which options should be resolved. - -.. tip:: - - To check if an option exists, you can use the - :method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isKnown` - function. - -A best practice is to put the configuration in a method (e.g. -``setDefaultOptions``). You call this method in the constructor to configure -the ``OptionsResolver`` class:: - - use Symfony\Component\OptionsResolver\OptionsResolver; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class Person - { - protected $options; - - public function __construct(array $options = array()) - { - $resolver = new OptionsResolver(); - $this->setDefaultOptions($resolver); - - $this->options = $resolver->resolve($options); - } - - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - // ... configure the resolver, you will learn this in the sections below - } - } - -Required Options ----------------- - -Suppose the ``firstName`` option is required: the class can't work without -it. You can set the required options by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setRequired`:: - - // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setRequired(array('firstName')); - } - -You are now able to use the class without errors:: - - $person = new Person(array( - 'firstName' => 'Wouter', - )); - - echo $person->getFirstName(); // 'Wouter' - -If you don't pass a required option, a -:class:`Symfony\\Component\\OptionsResolver\\Exception\\MissingOptionsException` -will be thrown. - -To determine if an option is required, you can use the -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::isRequired` -method. - -Optional Options ----------------- - -Sometimes, an option can be optional (e.g. the ``lastName`` option in the -``Person`` class). You can configure these options by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setOptional`:: - - // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - // ... - - $resolver->setOptional(array('lastName')); - } - -Set Default Values ------------------- - -Most of the optional options have a default value. You can configure these -options by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setDefaults`:: - - // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - // ... - - $resolver->setDefaults(array( - 'age' => 0, - )); - } - -The default age will be ``0`` now. When the user specifies an age, it gets -replaced. You don't need to configure ``age`` as an optional option. The -``OptionsResolver`` already knows that options with a default value are -optional. - -The ``OptionsResolver`` component also has an -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::replaceDefaults` -method. This can be used to override the previous default value. The closure -that is passed has 2 parameters: - -* ``$options`` (an :class:`Symfony\\Component\\OptionsResolver\\Options` - instance), with all the default options -* ``$value``, the previous set default value - -Default Values that depend on another Option -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Suppose you add a ``gender`` option to the ``Person`` class, whose default -value you guess based on the first name. You can do that easily by using a -Closure as the default value:: - - use Symfony\Component\OptionsResolver\Options; - - // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - // ... - - $resolver->setDefaults(array( - 'gender' => function (Options $options) { - if (GenderGuesser::isMale($options['firstName'])) { - return 'male'; - } - - return 'female'; - }, - )); - } - -.. caution:: - - The first argument of the Closure must be typehinted as ``Options``, - otherwise it is considered as the value. - -Configure allowed Values ------------------------- - -Not all values are valid values for options. For instance, the ``gender`` -option can only be ``female`` or ``male``. You can configure these allowed -values by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedValues`:: - - // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - // ... - - $resolver->setAllowedValues(array( - 'gender' => array('male', 'female'), - )); - } - -There is also an -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedValues` -method, which you can use if you want to add an allowed value to the previous -set allowed values. - -Configure allowed Types -~~~~~~~~~~~~~~~~~~~~~~~ - -You can also specify allowed types. For instance, the ``firstName`` option can -be anything, but it must be a string. You can configure these types by calling -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::setAllowedTypes`:: - - // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - // ... - - $resolver->setAllowedTypes(array( - 'firstName' => 'string', - )); - } - -Possible types are the one associated with the ``is_*`` php functions or a -class name. You can also pass an array of types as the value. For instance, -``array('null', 'string')`` allows ``firstName`` to be ``null`` or a -``string``. - -There is also an -:method:`Symfony\\Component\\OptionsResolver\\OptionsResolver::addAllowedTypes` -method, which you can use to add an allowed type to the previous allowed types. - -Normalize the Options ---------------------- - -Some values need to be normalized before you can use them. For instance, the -``firstName`` should always start with an uppercase letter. To do that, you can -write normalizers. These Closures will be executed after all options are -passed and return the normalized value. You can configure these normalizers by -calling -:method:`Symfony\\Components\\OptionsResolver\\OptionsResolver::setNormalizers`:: - - // ... - protected function setDefaultOptions(OptionsResolverInterface $resolver) - { - // ... - - $resolver->setNormalizers(array( - 'firstName' => function (Options $options, $value) { - return ucfirst($value); - }, - )); - } - -You see that the closure also get an ``$options`` parameter. Sometimes, you -need to use the other options for normalizing. - -.. _`Packagist`: https://packagist.org/packages/symfony/options-resolver diff --git a/_sources/components/process.txt b/_sources/components/process.txt deleted file mode 100644 index 6c5f331..0000000 --- a/_sources/components/process.txt +++ /dev/null @@ -1,142 +0,0 @@ -.. index:: - single: Process - single: Componentes; Process - -El componente ``Process`` -========================= - - El componente ``Process`` ejecuta ordenes en subprocesos. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Process); -* :doc:`Instalándolo vía Composer ` (``symfony/process`` en `Packagist`_). - -Usando ------- - -La clase :class:`Symfony\\Component\\Process\\Process` te permite ejecutar una orden en un subproceso:: - - use Symfony\Component\Process\Process; - - $process = new Process('ls -lsa'); - $process->run(); - - // executes after the command finishes - if (!$process->isSuccessful()) { - throw new \RuntimeException($process->getErrorOutput()); - } - - print $process->getOutput(); - -The component takes care of the subtle differences between the different platforms -when executing the command. - -.. versionadded:: 2.2 - Los métodos ``getIncrementalOutput()`` y ``getIncrementalErrorOutput()`` fueron añadidos en *Symfony 2.2*. - -El método ``getOutput()`` siempre regresa el contenido entero de la salida estándar de la orden y ``getErrorOutput()`` el contenido de la salida de error. Alternativamente, los métodos :method:`Symfony\\Component\\Process\\Process::getIncrementalOutput` -y :method:`Symfony\\Component\\Process\\Process::getIncrementalErrorOutput` regresan la nueva producción desde la última llamada. - -Cuando se ejecuta una orden que consume demasiado tiempo (tal como la resincronización de archivos con un servidor remoto), puedes retroalimentar en tiempo real al usuario final suministrando una función anónima al método :method:`Symfony\\Component\\Process\\Process::run`:: - - use Symfony\Component\Process\Process; - - $process = new Process('ls -lsa'); - $process->run(function ($type, $buffer) { - if ('err' === $type) { - echo 'ERR > '.$buffer; - } else { - echo 'OUT > '.$buffer; - } - }); - -.. versionadded:: 2.1 - The non-blocking feature was added in 2.1. - -You can also start the subprocess and then let it run asynchronously, retrieving -output and the status in your main process whenever you need it. Use the -:method:`Symfony\\Component\\Process\\Process::start` method to start an asynchronous -process, the :method:`Symfony\\Component\\Process\\Process::isRunning` method -to check if the process is done and the -:method:`Symfony\\Component\\Process\\Process::getOutput` method to get the output:: - - $process = new Process('ls -lsa'); - $process->start(); - - while ($process->isRunning()) { - // waiting for process to finish - } - - echo $process->getOutput(); - -You can also wait for a process to end if you started it asynchronously and -are done doing other stuff:: - - $process = new Process('ls -lsa'); - $process->start(); - - // ... do other things - - $process->wait(function ($type, $buffer) { - if ('err' === $type) { - echo 'ERR > '.$buffer; - } else { - echo 'OUT > '.$buffer; - } - }); - -Si deseas ejecutar algún código *PHP* independiente, en su lugar usa ``PhpProcess``:: - - use Symfony\Component\Process\PhpProcess; - - $process = new PhpProcess(<< - EOF - ); - $process->run(); - -.. versionadded:: 2.1 - The ``ProcessBuilder`` class was added in Symfony 2.1. - -Para hacer que tu código trabaje mejor en todas las plataformas, posiblemente en su lugar quieras usar la clase :class:`Symfony\\Component\\Process\\ProcessBuilder`:: - - use Symfony\Component\Process\ProcessBuilder; - - $builder = new ProcessBuilder(array('ls', '-lsa')); - $builder->getProcess()->run(); - -Process Timeout ---------------- - -You can limit the amount of time a process takes to complete by setting a -timeout (in seconds):: - - use Symfony\Component\Process\Process; - - $process = new Process('ls -lsa'); - $process->setTimeout(3600); - $process->run(); - -If the timeout is reached, a -:class:`Symfony\\Process\\Exception\\RuntimeException` is thrown. - -For long running commands, it is your responsibility to perform the timeout -check regularly:: - - $process->setTimeout(3600); - $process->start(); - - while ($condition) { - // ... - - // check if the timeout is reached - $process->checkTimeout(); - - usleep(200000); - } - -.. _`Packagist`: https://packagist.org/packages/symfony/process diff --git a/_sources/components/property_access/index.txt b/_sources/components/property_access/index.txt deleted file mode 100644 index 8356ecf..0000000 --- a/_sources/components/property_access/index.txt +++ /dev/null @@ -1,7 +0,0 @@ -Propiedad ``Access`` -==================== - -.. toctree:: - :maxdepth: 2 - - introduction diff --git a/_sources/components/property_access/introduction.txt b/_sources/components/property_access/introduction.txt deleted file mode 100644 index 7080a74..0000000 --- a/_sources/components/property_access/introduction.txt +++ /dev/null @@ -1,250 +0,0 @@ -.. index:: - single: PropertyAccess - single: Componentes; PropertyAccess - -El componente ``PropertyAccess`` -================================ - - El componente ``PropertyAccess`` proporciona una función para leer y escribir de/a un objeto o arreglo usando una sencilla notación de cadena. - -.. versionadded:: 2.2 - El componente ``PropertyAccess`` es nuevo para *Symfony 2.2*. Anteriormente, la clase ``PropertyPath`` se localizaba en el componente ``Form``. - -Instalando ----------- - -Puedes instalar el componente en dos diferentes maneras: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/propertyaccess); -* :doc:`Instalándolo vía Composer ` (``symfony/property-access`` en `Packagist`_). - -Usando ------- - -El punto de entrada de este componente es el método fábrica :method:`PropertyAccess::getPropertyAccessor`. Esta fábrica creará una nueva instancia de la clase :class:`Symfony\\Component\\PropertyAccess\\PropertyAccessor` con la configuración predefinida:: - - use Symfony\Component\PropertyAccess\PropertyAccess; - - $accessor = PropertyAccess::getPropertyAccessor(); - -Leyendo desde arreglos ----------------------- - -Puedes leer un arreglo con el método :method:`PropertyAccessor::getValue `. Esto se hace utilizando la notación de índice utilizada en *PHP*:: - - // ... - $person = array( - 'first_name' => 'Wouter', - ); - - echo $accessor->getValue($person, '[first_name]'); // 'Wouter' - echo $accessor->getValue($person, '[age]'); // null - -Como puedes ver, el método regresará ``null`` si el índice no existe. - -También puedes utilizar arreglos multidimensionales:: - - // ... - $persons = array( - array( - 'first_name' => 'Wouter', - ), - array( - 'first_name' => 'Ryan', - ) - ); - - echo $accessor->getValue($persons, '[0][first_name]'); // 'Wouter' - echo $accessor->getValue($persons, '[1][first_name]'); // 'Ryan' - -Leyendo Objetos ---------------- - -El método ``getValue`` es un método muy robusto, y puedes ver todas sus características al trabajar con objetos. - -Accediendo a propiedades públicas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para leer propiedades, usa la notación de «punto»:: - - // ... - $person = new Person(); - $person->firstName = 'Wouter'; - - echo $accessor->getValue($person, 'firstName'); // 'Wouter' - - $child = new Person(); - $child->firstName = 'Bar'; - $person->children = array($child); - - echo $accessor->getValue($person, 'children[0].firstName'); // 'Bar' - -.. caution:: - - Acceder a las propiedades públicas es la última opción utilizada por ``PropertyAccessor``. - Este intenta acceder al valor utilizando los siguientes métodos primero antes de utilizar la propiedad directamente. Por ejemplo, si tienes una propiedad pública que tiene un método captador, lo utiliza. - -Usando Captadores -~~~~~~~~~~~~~~~~~ - -El método ``getValue`` también apoya la lectura utilizando captadores. El método será creado utilizando convenciones de nomenclatura comunes para los captadores. Este intercala mayúsculas en el nombre de la propiedad (``first_name`` se convierte en ``FirstName``) y lo prefija con ``get``. Por lo tanto el método real es ``getFirstName``:: - - // ... - class Person - { - private $firstName = 'Wouter'; - - public function getFirstName() - { - return $this->firstName; - } - } - - $person = new Person(); - - echo $accessor->getValue($person, 'first_name'); // 'Wouter' - -Using Hassers/Issers -~~~~~~~~~~~~~~~~~~~~ - -And it doesn't even stop there. If there is no getter found, the accessor will -look for an isser or hasser. This method is created using the same way as -getters, this means that you can do something like this:: - - // ... - class Person - { - private $author = true; - private $children = array(); - - public function isAuthor() - { - return $this->author; - } - - public function hasChildren() - { - return 0 !== count($this->children); - } - } - - $person = new Person(); - - if ($accessor->getValue($person, 'author')) { - echo 'He is an author'; - } - if ($accessor->getValue($person, 'children')) { - echo 'He has children'; - } - -Esto producirá: ``He is an author`` - -Magic Methods -~~~~~~~~~~~~~ - -At last, ``getValue`` can use the magic ``__get`` method too:: - - // ... - class Person - { - private $children = array( - 'wouter' => array(...), - ); - - public function __get($id) - { - return $this->children[$id]; - } - } - - $person = new Person(); - - echo $accessor->getValue($person, 'Wouter'); // array(...) - -Writing to Arrays ------------------ - -The ``PropertyAccessor`` class can do more than just read an array, it can -also write to an array. This can be achieved using the -:method:`PropertyAccessor::setValue` -method:: - - // ... - $person = array(); - - $accessor->setValue($person, '[first_name]', 'Wouter'); - - echo $accessor->getValue($person, '[first_name]'); // 'Wouter' - // o - // echo $person['first_name']; // 'Wouter' - -Writing to Objects ------------------- - -The ``setValue`` method has the same features as the ``getValue`` method. You -can use setters, the magic ``__set`` or properties to set values:: - - // ... - class Person - { - public $firstName; - private $lastName; - private $children = array(); - - public function setLastName($name) - { - $this->lastName = $name; - } - - public function __set($property, $value) - { - $this->$property = $value; - } - - // ... - } - - $person = new Person(); - - $accessor->setValue($person, 'firstName', 'Wouter'); - $accessor->setValue($person, 'lastName', 'de Jong'); - $accessor->setValue($person, 'children', array(new Person())); - - echo $person->firstName; // 'Wouter' - echo $person->getLastName(); // 'de Jong' - echo $person->children; // array(Person()); - -Mezclando objetos y arreglos ----------------------------- - -también puedes mezcla objetos y arreglos:: - - // ... - class Person - { - public $firstName; - private $children = array(); - - public function setChildren($children) - { - return $this->children; - } - - public function getChildren() - { - return $this->children; - } - } - - $person = new Person(); - - $accessor->setValue($person, 'children[0]', new Person); - // igual que $person->getChildren()[0] = new Person() - - $accessor->setValue($person, 'children[0].firstName', 'Wouter'); - // igual a $person->getChildren()[0]->firstName = 'Wouter' - - echo 'Hello '.$accessor->getValue($person, 'children[0].firstName'); // 'Wouter' - // igual a $person->getChildren()[0]->firstName - -.. _`Packagist`: https://packagist.org/packages/symfony/property-access diff --git a/_sources/components/routing.txt b/_sources/components/routing.txt deleted file mode 100644 index 1fe3e75..0000000 --- a/_sources/components/routing.txt +++ /dev/null @@ -1,273 +0,0 @@ -.. index:: - single: Enrutando - single: Componentes; Routing - -El componente ``Routing`` -========================= - - El componente ``Routing`` asigna una ``petición`` *HTTP* a un conjunto de variables de configuración. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Routing); -* Instalándolo a través de *PEAR* (`pear.symfony.com/Routing`); -* Instalándolo vía ``Composer`` (`symfony/routing` en Packagist) - -Usando ------- - -Con el fin de establecer un sistema de enrutado básico necesitas tres partes: - -* Una clase :class:`Symfony\\Component\\Routing\\RouteCollection`, que contiene las definiciones de las rutas (instancias de la clase :class:`Symfony\\Component\\Routing\\Route`) -* Una clase :class:`Symfony\\Component\\Routing\\RequestContext`, que contiene información sobre la petición -* Una clase :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher`, que realiza la asignación de la petición a una sola ruta - -Veamos un ejemplo rápido. Ten en cuenta que esto supone que ya has configurado el cargador automático para cargar el componente ``Routing``: - -.. code-block:: php - - - use Symfony\Component\Routing\Matcher\UrlMatcher; - use Symfony\Component\Routing\RequestContext; - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $routes = new RouteCollection(); - $routes->add('route_name', new Route('/foo', array('controller' => 'MyController'))); - - $context = new RequestContext($_SERVER['REQUEST_URI']); - - $matcher = new UrlMatcher($routes, $context); - - $parameters = $matcher->match('/foo'); - // array('controller' => 'MyController', '_route' => 'route_name') - -.. note:: - - Ten cuidado al usar ``$_SERVER['REQUEST_URI']``, ya que este puede incluir los parámetros de consulta en la URL, lo cual causará problemas con la ruta coincidente. Una manera fácil de solucionar esto es usando el componente ``HTTPFoundation`` como se explica :ref:`abajo `. - -Puedes agregar tantas rutas como quieras a una clase :class:`Symfony\\Component\\Routing\\RouteCollection`. - -El método :method:`RouteCollection::add()` toma dos argumentos. El primero es el nombre de la ruta. El segundo es un objeto :class:`Symfony\\Component\\Routing\\Route`, que espera una ruta *URL* y algún arreglo de variables personalizadas en su constructor. Este arreglo de variables personalizadas puede ser *cualquier cosa* que tenga significado para tu aplicación, y es devuelto cuando dicha ruta corresponda. - -Si no hay ruta coincidente debe lanzar una :class:`Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException`. - -Además de tu arreglo de variables personalizadas, se añade una clave ``_route``, que contiene el nombre de la ruta buscada. - -Definiendo rutas -~~~~~~~~~~~~~~~~ - -Una definición de la ruta completa puede contener un máximo de cuatro partes: - -1. El patrón de la ruta *URL*. Este se compara con la *URL* pasada al ``RequestContext``, y puede contener comodines marcadores de posición nombrados (por ejemplo, ``{placeholders}``) para que coincidan con elementos dinámicos en la *URL*. - -2. Una matriz de valores predeterminados. Esta contiene un conjunto de valores arbitrarios que serán devueltos cuando la petición coincide con la ruta. - -3. Un arreglo de requisitos. Estos definen las restricciones para los valores de los marcadores de posición como las expresiones regulares. - -4. Un arreglo de opciones. Estas contienen la configuración interna de la ruta y son las menos necesarias comúnmente. - -Veamos la siguiente ruta, que combina varias de estas ideas: - -.. code-block:: php - - $route = new Route( - '/archive/{month}', // ruta - array('controller' => 'showArchive'), // valores predefinidos - array('month' => '[0-9]{4}-[0-9]{2}'), // requisitos - array() // options - ); - - // ... - - $parameters = $matcher->match('/archive/2012-01'); - // array('controller' => 'showArchive', 'month' => '2012-01', '_route' => ...) - - $parameters = $matcher->match('/archive/foo'); - // lanza la ResourceNotFoundException - -En este caso, la ruta coincide con ``/archive/2012-01``, porque el comodín ``{month}`` coincide con el comodín de la expresión regular suministrada. Sin embargo, ``/archive/f oo`` *no* coincide, porque el comodín del mes "foo" no concuerda. - -Además de las restricciones de la expresión regular, hay dos requisitos especiales que puedes definir: - -* el ``_method`` impone un determinado método de la petición *HTTP* (``HEAD``, ``GET``, ``POST``, ...) -* ``_scheme`` impone un determinado esquema *HTTP* (``http``, ``https``) - -Por ejemplo, la siguiente ruta sólo acepta peticiones a ``/foo`` con el método ``POST`` y una conexión segura: - -.. code-block:: php - - $route = new Route('/foo', array(), array('_method' => 'post', '_scheme' => 'https' )); - -.. tip:: - - Si quieres hacer coincidir todas las *URL* que comiencen con una cierta ruta y terminan en un sufijo arbitrario puedes utilizar la siguiente definición de ruta:: - - $route = new Route('/start/{suffix}', array('suffix' => ''), array('suffix' => '.*')); - - -Usando prefijos -~~~~~~~~~~~~~~~ - -Puedes agregar rutas u otras instancias de :class:`Symfony\\Component\\Routing\\RouteCollection` a *otra* colección. -De esta manera puedes construir un árbol de rutas. Además, puedes definir un prefijo, los requisitos predeterminados y las opciones predefinidas para todas las rutas de un subárbol: - -.. code-block:: php - - $rootCollection = new RouteCollection(); - - $subCollection = new RouteCollection(); - $subCollection->add(...); - $subCollection->add(...); - - $rootCollection->addCollection($subCollection, '/prefix', array('_scheme' => 'https')); - -Configurando los parámetros de la Petición -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\Routing\\RequestContext` proporciona información sobre la ``petición`` actual. Puedes definir todos los parámetros de una ``petición`` *HTTP* con esta clase a través de su constructor: - -.. code-block:: php - - public function __construct($baseUrl = '', $method = 'GET', $host = 'localhost', $scheme = 'http', $httpPort = 80, $httpsPort = 443) - -.. _components-routing-http-foundation: - -Normalmente puedes pasar los valores de la variable ``$_SERVER`` para poblar la :class:`Symfony\\Component\\Routing\\RequestContext`. Pero si utilizas el componente :doc:`HttpFoundation , puedes utilizar su :class:`Symfony\\Component\\HttpFoundation\\Request` para alimentar el :class:`Symfony\\Component\\Routing\\RequestContext` en un método abreviado:: - - use Symfony\Component\HttpFoundation\Request; - - $context = new RequestContext(); - $context->fromRequest(Request::createFromGlobals()); - -Generando una URL -~~~~~~~~~~~~~~~~~ - -Si bien la :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher` trata de encontrar una ruta que se adapte a la petición dada, esta también puede construir una *URL* a partir de una ruta determinada: - -.. code-block:: php - - use Symfony\Component\Routing\Generator\UrlGenerator; - - $routes = new RouteCollection(); - $routes->add('show_post', new Route('/show/{slug}')); - - $context = new RequestContext($_SERVER['REQUEST_URI']); - - $generator = new UrlGenerator($routes, $context); - - $url = $generator->generate('show_post', array( - 'slug' => 'my-blog-post' - )); - // /show/my-blog-post - -.. note:: - - Si has definido el requisito ``_scheme``, se genera una *URL* absoluta si el esquema del :class:`Symfony\\Component\\Routing\\RequestContext` actual no coincide con el requisito. - -Cargando rutas desde un archivo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ya hemos visto lo fácil que es añadir rutas a una colección dentro de *PHP*. Pero también puedes cargar rutas de una serie de archivos diferentes. - -El componente de enrutado viene con una serie de clases cargadoras, cada una dotándote de la capacidad para cargar una colección de definiciones de ruta desde un archivo externo en algún formato. -Cada cargador espera una instancia del :class:`Symfony\\Component\\Config\\FileLocator` como argumento del constructor. Puedes utilizar el :class:`Symfony\\Component\\Config\\FileLocator` para definir una serie de rutas en las que el cargador va a buscar los archivos solicitados. -Si se encuentra el archivo, el cargador devuelve una :class:`Symfony\\Component\\Routing\\RouteCollection`. - -Si estás usando el ``YamlFileLoader``, entonces las definiciones de ruta tienen este aspecto: - -.. code-block:: yaml - - # routes.yml - route1: - pattern: /foo - defaults: { controller: 'MyController::fooAction' } - - route2: - pattern: /foo/bar - defaults: { controller: 'MyController::foobarAction' } - -Para cargar este archivo, puedes utilizar el siguiente código. Este asume que tu archivo :file:`routes.yml` está en el mismo directorio que el código de abajo: - -.. code-block:: php - - use Symfony\Component\Config\FileLocator; - use Symfony\Component\Routing\Loader\YamlFileLoader; - - // busca dentro de *este* directorio - $locator = new FileLocator(array(__DIR__)); - $loader = new YamlFileLoader($locator); - $collection = $loader->load('routes.yml'); - -Además del :class:`Symfony\\Component\\Routing\\Loader\\YamlFileLoader` hay otros dos cargadores que funcionan de manera similar: - -* :class:`Symfony\\Component\\Routing\\Loader\\XmlFileLoader` -* :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` - -Si utilizas el :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` debes proporcionar el nombre del un archivo *php* que devuelva una :class:`Symfony\\Component\\Routing\\RouteCollection`: - -.. code-block:: php - - // ProveedorDeRuta.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('route_name', new Route('/foo', array('controller' => 'ExampleController'))); - // ... - - return $collection; - -Rutas como cierres -.................. - -También está el :class:`Symfony\\Component\\Routing\\Loader\\ClosureLoader`, que -llama a un cierre y utiliza el resultado como una :class:`Symfony\\Component\\Routing\\RouteCollection`: - -.. code-block:: php - - use Symfony\Component\Routing\Loader\ClosureLoader; - - $closure = function() { - return new RouteCollection(); - }; - - $loader = new ClosureLoader(); - $collection = $loader->load($closure); - -Rutas como anotaciones -...................... - -Por último, pero no menos importante, están las :class:`Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader` y :class:`Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader` para cargar -definiciones de ruta a partir de las anotaciones de la clase. Los detalles específicos se dejan aquí. - -El ruteador todo en uno -~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\Routing\\Router` es un paquete todo en uno para utilizar rápidamente el componente de enrutado. El constructor espera una instancia del cargador, una ruta a la definición de la ruta principal y algunas otras opciones: - -.. code-block:: php - - public function __construct(LoaderInterface $loader, $resource, array $options = array(), RequestContext $context = null, array $defaults = array()); - -Con la opción ``cache_dir`` puedes habilitar la caché de enrutado (si proporcionas una ruta) o desactivar el almacenamiento en caché (si la configuras a ``null``). El almacenamiento en caché automáticamente se hace en segundo plano si lo quieres usar. Un ejemplo básico de la clase :class:`Symfony\\Component\\Routing\\Router` se vería así: - -.. code-block:: php - - $locator = new FileLocator(array(__DIR__)); - $requestContext = new RequestContext($_SERVER['REQUEST_URI']); - - $router = new Router( - new YamlFileLoader($locator), - "routes.yml", - array('cache_dir' => __DIR__.'/cache'), - $requestContext, - ); - $router->match('/foo/bar'); - -.. note:: - - Si utilizas el almacenamiento en caché, el componente ``Routing`` compilará las nuevas clases guardándolas en ``cache_dir``. Esto significa que el archivo debe tener permisos de escritura en esa ubicación. diff --git a/_sources/components/routing/hostname_pattern.txt b/_sources/components/routing/hostname_pattern.txt deleted file mode 100644 index ab75a79..0000000 --- a/_sources/components/routing/hostname_pattern.txt +++ /dev/null @@ -1,160 +0,0 @@ -.. index:: - single: Enrutando; Reconociendo el nombre del servidor - -Cómo emparejar una ruta basándose en el servidor -================================================ - -.. versionadded:: 2.2 - El soporte necesario para emparejar con el servidor se añadió en *Symfony* 2.2 - -También puedes emparejar con el *host* *HTTP* de la petición entrante. - -.. configuration-block:: - - .. code-block:: yaml - - mobile_homepage: - path: / - host: m.example.com - defaults: { _controller: AcmeDemoBundle:Main:mobileHomepage } - - homepage: - path: / - defaults: { _controller: AcmeDemoBundle:Main:homepage } - - .. code-block:: xml - - - - - - - AcmeDemoBundle:Main:mobileHomepage - - - - AcmeDemoBundle:Main:homepage - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('mobile_homepage', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Main:mobileHomepage', - ), array(), array(), 'm.example.com')); - - $collection->add('homepage', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Main:homepage', - ))); - - return $collection; - -Ambas rutas emparejan con la misma ruta ``/``, aun así la primera sólo emparejará si el servidor es ``m.example.com``. - -Marcadores de posición y requisitos en patrones de nombre del servidor ----------------------------------------------------------------------- - -Si estás utilizando el :doc:`componente DependencyInjection ` (o la plataforma *Symfony2* en su totalidad), entonces puedes utilizar los :ref:`parámetros del contenedor de servicios ` como variables en cualquier lugar en tus rutas. - -Puedes evitar codificar el nombre de dominio usando un marcador de posición y un requisito. -El ``%domain%`` en los requisitos se sustituye por el valor del parámetro ``domain`` en el contenedor de inyección de dependencias. - -.. configuration-block:: - - .. code-block:: yaml - - mobile_homepage: - path: / - host: m.{domain} - defaults: { _controller: AcmeDemoBundle:Main:mobileHomepage } - requirements: - domain: %domain% - - homepage: - path: / - defaults: { _controller: AcmeDemoBundle:Main:homepage } - - .. code-block:: xml - - - - - - - AcmeDemoBundle:Main:mobileHomepage - %domain% - - - - AcmeDemoBundle:Main:homepage - - - - .. code-block:: php - - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add('mobile_homepage', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Main:mobileHomepage', - ), array( - 'domain' => '%domain%', - ), array(), 'm.{domain}')); - - $collection->add('homepage', new Route('/', array( - '_controller' => 'AcmeDemoBundle:Main:homepage', - ))); - - return $collection; - -.. _component-routing-host-imported: - -Añadiendo un servidor a la expresión regular de las rutas importadas --------------------------------------------------------------------- - -Puedes poner una expresión regular para el servidor en las rutas importadas: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - acme_hello: - resource: "@AcmeHelloBundle/Resources/config/routing.yml" - host: "hello.example.com" - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/routing.php - use Symfony\Component\Routing\RouteCollection; - - $collection = new RouteCollection(); - $collection->addCollection($loader->import("@AcmeHelloBundle/Resources/config/routing.php"), '', array(), array(), array(), 'hello.example.com'); - - return $collection; - -El servidor ``hello.example.com`` será puesto en cada ruta cargada desde el nuevo recurso -de enrutado. diff --git a/_sources/components/routing/index.txt b/_sources/components/routing/index.txt deleted file mode 100644 index 9daa4c7..0000000 --- a/_sources/components/routing/index.txt +++ /dev/null @@ -1,8 +0,0 @@ -Enrutando -========= - -.. toctree:: - :maxdepth: 2 - - introduction - hostname_pattern diff --git a/_sources/components/routing/introduction.txt b/_sources/components/routing/introduction.txt deleted file mode 100644 index 3af3d9c..0000000 --- a/_sources/components/routing/introduction.txt +++ /dev/null @@ -1,289 +0,0 @@ -.. index:: - single: Enrutando - single: Componentes; Routing - -El componente ``Routing`` -========================= - - El componente ``Routing`` asocia una ``Petición`` *HTTP* a un conjunto de variables de configuración. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Routing); -* :doc:`Instalándolo vía Composer ` (``symfony/routing`` en `Packagist`_). - -Usando ------- - -Con el fin de establecer un sistema de enrutado básico necesitas tres partes: - -* Una clase :class:`Symfony\\Component\\Routing\\RouteCollection`, que contiene las definiciones de las rutas (instancias de la clase :class:`Symfony\\Component\\Routing\\Route`) -* Una clase :class:`Symfony\\Component\\Routing\\RequestContext`, que contiene información sobre la petición -* Una clase :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher`, que realiza la asignación de la petición a una sola ruta - -Veamos un ejemplo rápido. Ten en cuenta que esto supone que ya has configurado el cargador automático para cargar el componente ``Routing``:: - - use Symfony\Component\Routing\Matcher\UrlMatcher; - use Symfony\Component\Routing\RequestContext; - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $route = new Route('/foo', array('controller' => 'MyController')); - $routes = new RouteCollection(); - $routes->add('route_name', $route); - - $context = new RequestContext($_SERVER['REQUEST_URI']); - - $matcher = new UrlMatcher($routes, $context); - - $parameters = $matcher->match('/foo'); - // array('controller' => 'MyController', '_route' => 'route_name') - -.. note:: - - Ten cuidado al usar ``$_SERVER['REQUEST_URI']``, ya que este puede incluir los parámetros de consulta en la URL, lo cual causará problemas con la ruta coincidente. Una manera fácil de solucionar esto es usando el componente ``HTTPFoundation`` como se explica :ref:`abajo `. - -Puedes agregar tantas rutas como quieras a una clase :class:`Symfony\\Component\\Routing\\RouteCollection`. - -El método :method:`RouteCollection::add()` toma dos argumentos. El primero es el nombre de la ruta. El segundo es un objeto :class:`Symfony\\Component\\Routing\\Route`, que espera una ruta *URL* y algún arreglo de variables personalizadas en su constructor. Este arreglo de variables personalizadas puede ser *cualquier cosa* que tenga significado para tu aplicación, y es devuelto cuando dicha ruta corresponda. - -Si no hay ruta coincidente debe lanzar una :class:`Symfony\\Component\\Routing\\Exception\\ResourceNotFoundException`. - -Además de tu arreglo de variables personalizadas, se añade una clave ``_route``, que contiene el nombre de la ruta buscada. - -Definiendo rutas -~~~~~~~~~~~~~~~~ - -Una definición de ruta completa puede contener hasta siete partes: - -1. El trayecto a la *URL* de la ruta. Este se compara con la *URL* pasada al ``RequestContext``, y puede contener comodines marcadores de posición nombrados (por ejemplo, ``{placeholders}``) para que coincidan con elementos dinámicos en la *URL*. - -2. Un arreglo de valores predeterminados. Esta contiene un conjunto de valores arbitrarios que serán devueltos cuando la petición coincida con la ruta. - -3. Un arreglo de requisitos. Estos definen las restricciones para los valores de los marcadores de posición como las expresiones regulares. - -4. Un arreglo de opciones. Estas contienen la configuración interna de la ruta y comúnmente son las menos necesarias. - -5. Un servidor. Este se empareja contra el servidor de la petición. Ve :doc:`/components/routing/hostname_pattern` para más detalles. - -6. Un arreglo de esquemas. Estos imponen un determinado esquema *HTTP* (``http``, ``https``). - -7. Un arreglo de métodos. Estos fuerzan un determinado método de petición *HTTP* (``HEAD``, ``GET``, ``POST``, ...). - -.. versionadded:: 2.2 - El soporte necesario para emparejar con el servidor se añadió en *Symfony* 2.2 - -Veamos la siguiente ruta, que combina varias de estas ideas:: - - $route = new Route( - '/archive/{month}', // ruta - array('controller' => 'showArchive'), // valores predefinidos - array('month' => '[0-9]{4}-[0-9]{2}', 'subdomain' => 'www|m'), // requisitos - array(), // opciones - '{subdomain}.example.com', // servidor - array(), // esquemas - array() // métodos - ); - - // ... - - $parameters = $matcher->match('/archive/2012-01'); - // array( - // 'controller' => 'showArchive', - // 'month' => '2012-01', - // 'subdomain' => 'www', - // '_route' => ... - // ) - - $parameters = $matcher->match('/archive/foo'); - // lanza la ResourceNotFoundException - -En este caso, la ruta coincide con ``/archive/2012-01``, porque el comodín ``{month}`` coincide con el comodín de la expresión regular suministrada. Sin embargo, ``/archive/f oo`` *no* coincide, porque el comodín del mes «foo» no concuerda. - -.. tip:: - - Si quieres hacer coincidir todas las *URL* que comiencen con una cierta ruta y terminan en un sufijo arbitrario puedes utilizar la siguiente definición de ruta:: - - $route = new Route( - '/start/{suffix}', - array('suffix' => ''), - array('suffix' => '.*') - ); - -Usando prefijos -~~~~~~~~~~~~~~~ - -Puedes agregar rutas u otras instancias de :class:`Symfony\\Component\\Routing\\RouteCollection` a *otra* colección. -De esta manera puedes construir un árbol de rutas. Además, puedes definir un prefijo, requisitos predeterminados, opciones predefinidas y servidores a todas las rutas de un subárbol con el método :method:`Symfony\\Component\\Routing\\RouteCollection::addPrefix`:: - - $rootCollection = new RouteCollection(); - - $subCollection = new RouteCollection(); - $subCollection->add(...); - $subCollection->add(...); - $subCollection->addPrefix( - '/prefix', // prefijo - array(), // requisitos - array(), // opciones - 'admin.example.com', // servidor - array('https') // esquemas - ); - - $rootCollection->addCollection($subCollection); - -.. versionadded:: 2.2 - El método ``addPrefix`` se añadió en *Symfony* 2.2. Este era parte del método ``addCollection`` en versiones anteriores. - -Configurando los parámetros de la Petición -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\Routing\\RequestContext` proporciona información sobre la ``Petición`` actual. Puedes definir todos los parámetros de una ``Petición`` *HTTP* con esta clase a través de su constructor:: - - public function __construct( - $baseUrl = '', - $method = 'GET', - $host = 'localhost', - $scheme = 'http', - $httpPort = 80, - $httpsPort = 443 - ) - -.. _components-routing-http-foundation: - -Normalmente puedes pasar los valores de la variable ``$_SERVER`` para poblar la :class:`Symfony\\Component\\Routing\\RequestContext`. Pero si utilizas el componente :doc:`HttpFoundation `, puedes utilizar su clase :class:`Symfony\\Component\\HttpFoundation\\Request` para alimentar al :class:`Symfony\\Component\\Routing\\RequestContext` en un método abreviado:: - - use Symfony\Component\HttpFoundation\Request; - - $context = new RequestContext(); - $context->fromRequest(Request::createFromGlobals()); - -Generando una URL -~~~~~~~~~~~~~~~~~ - -Si bien la :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher` trata de encontrar una ruta que se adapte a la petición dada, esta también puede construir una *URL* a partir de una ruta determinada:: - - use Symfony\Component\Routing\Generator\UrlGenerator; - - $routes = new RouteCollection(); - $routes->add('show_post', new Route('/show/{slug}')); - - $context = new RequestContext($_SERVER['REQUEST_URI']); - - $generator = new UrlGenerator($routes, $context); - - $url = $generator->generate('show_post', array( - 'slug' => 'my-blog-post', - )); - // /show/my-blog-post - -.. note:: - - Si definiste un esquema, se genera una *URL* absoluta si el esquema de la clase :class:`Symfony\\Component\\Routing\\RequestContext` actual no concuerda con el requisito. - -Cargando rutas desde un archivo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ya hemos visto lo fácil que es añadir rutas a una colección dentro de *PHP*. Pero también puedes cargar rutas de una serie de archivos diferentes. - -El componente de enrutado viene con una serie de clases cargadoras, cada una dotándote de la capacidad para cargar una colección de definiciones de ruta desde un archivo externo en algún formato. -Cada cargador espera una instancia del :class:`Symfony\\Component\\Config\\FileLocator` como argumento del constructor. Puedes utilizar el :class:`Symfony\\Component\\Config\\FileLocator` para definir una serie de rutas en las que el cargador va a buscar los archivos solicitados. -Si se encuentra el archivo, el cargador devuelve una :class:`Symfony\\Component\\Routing\\RouteCollection`. - -Si estás usando el ``YamlFileLoader``, entonces las definiciones de ruta tienen este aspecto: - -.. code-block:: yaml - - # routes.yml - route1: - path: /foo - defaults: { _controller: 'MyController::fooAction' } - - route2: - path: /foo/bar - defaults: { _controller: 'MyController::foobarAction' } - -Para cargar este archivo, puedes utilizar el siguiente código. Este asume que tu archivo :file:`routes.yml` está en el mismo directorio que el código de abajo:: - - use Symfony\Component\Config\FileLocator; - use Symfony\Component\Routing\Loader\YamlFileLoader; - - // busca dentro de *este* directorio - $locator = new FileLocator(array(__DIR__)); - $loader = new YamlFileLoader($locator); - $collection = $loader->load('routes.yml'); - -Además del :class:`Symfony\\Component\\Routing\\Loader\\YamlFileLoader` hay otros dos cargadores que funcionan de manera similar: - -* :class:`Symfony\\Component\\Routing\\Loader\\XmlFileLoader` -* :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` - -Si utilizas el :class:`Symfony\\Component\\Routing\\Loader\\PhpFileLoader` debes proporcionar el nombre del un archivo *php* que devuelva una :class:`Symfony\\Component\\Routing\\RouteCollection`:: - - // ProveedorDeRuta.php - use Symfony\Component\Routing\RouteCollection; - use Symfony\Component\Routing\Route; - - $collection = new RouteCollection(); - $collection->add( - 'route_name', - new Route('/foo', array('controller' => 'ExampleController')) - ); - // ... - - return $collection; - -Rutas como cierres -.................. - -También está el :class:`Symfony\\Component\\Routing\\Loader\\ClosureLoader`, que -llama a un cierre y utiliza el resultado como una :class:`Symfony\\Component\\Routing\\RouteCollection`:: - - use Symfony\Component\Routing\Loader\ClosureLoader; - - $closure = function() { - return new RouteCollection(); - }; - - $loader = new ClosureLoader(); - $collection = $loader->load($closure); - -Rutas como anotaciones -...................... - -Por último, pero no menos importante, están las :class:`Symfony\\Component\\Routing\\Loader\\AnnotationDirectoryLoader` y :class:`Symfony\\Component\\Routing\\Loader\\AnnotationFileLoader` para cargar -definiciones de ruta a partir de las anotaciones de la clase. Los detalles específicos se dejan aquí. - -El ruteador todo en uno -~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\Routing\\Router` es un paquete todo en uno para utilizar rápidamente el componente de enrutado. El constructor espera una instancia del cargador, una ruta a la definición de la ruta principal y algunas otras opciones:: - - public function __construct( - LoaderInterface $loader, - $resource, - array $options = array(), - RequestContext $context = null, - array $defaults = array() - ); - -Con la opción ``cache_dir`` puedes habilitar la caché de enrutado (si proporcionas una ruta) o desactivar el almacenamiento en caché (si la configuras a ``null``). El almacenamiento en caché automáticamente se hace en segundo plano si lo quieres usar. Un ejemplo básico de la clase :class:`Symfony\\Component\\Routing\\Router` se vería así:: - - $locator = new FileLocator(array(__DIR__)); - $requestContext = new RequestContext($_SERVER['REQUEST_URI']); - - $router = new Router( - new YamlFileLoader($locator), - 'routes.yml', - array('cache_dir' => __DIR__.'/cache'), - $requestContext - ); - $router->match('/foo/bar'); - -.. note:: - - Si utilizas el almacenamiento en caché, el componente ``Routing`` compilará las nuevas clases guardándolas en ``cache_dir``. Esto significa que el archivo debe tener permisos de escritura en esa ubicación. - -.. _`Packagist`: https://packagist.org/packages/symfony/routing diff --git a/_sources/components/security/authentication.txt b/_sources/components/security/authentication.txt deleted file mode 100644 index 323f336..0000000 --- a/_sources/components/security/authentication.txt +++ /dev/null @@ -1,180 +0,0 @@ -.. index:: - single: Seguridad, autenticación - -Autenticación -============= - -Cuándo una petición apunta a una área protegida, y uno de los escuchas del -cortafuegos asociado es capaz de extraer las credenciales del usuario desde el objeto :class:`Symfony\\Component\\HttpFoundation\\Request` actual, este debe crear una ficha conteniendo estas credenciales. Lo siguiente que el escucha debería hacer es consultar al gestor de autenticación para validar la ficha dada, y regresar una ficha *autenticada* si resulta que las credenciales suministradas son válidas. -El escucha entonces debería almacenar la ficha autenticada en el contexto de seguridad:: - - use Symfony\Component\Security\Http\Firewall\ListenerInterface; - use Symfony\Component\Security\Core\SecurityContextInterface; - use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface; - use Symfony\Component\HttpKernel\Event\GetResponseEvent; - use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken; - - class SomeAuthenticationListener implements ListenerInterface - { - /** - * @var SecurityContextInterface - */ - private $securityContext; - - /** - * @var AuthenticationManagerInterface - */ - private $authenticationManager; - - /** - * @var string Uniquely identifies the secured area - */ - private $providerKey; - - // ... - - public function handle(GetResponseEvent $event) - { - $request = $event->getRequest(); - - $username = ...; - $password = ...; - - $unauthenticatedToken = new UsernamePasswordToken( - $username, - $password, - $this->providerKey - ); - - $authenticatedToken = $this - ->authenticationManager - ->authenticate($unauthenticatedToken); - - $this->securityContext->setToken($authenticatedToken); - } - } - -.. note:: - - Una ficha puede ser cualquier clase, siempre y cuando implemente la interfaz :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface`. - -El gestor de autenticación --------------------------- - -El gestor de autenticación predefinido es una instancia de la clase :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationProviderManager`:: - - use Symfony\Component\Security\Core\Authentication\AuthenticationProviderManager; - - // instancias de Symfony\Component\Security\Core\Authentication\AuthenticationProviderInterface - $providers = array(...); - - $authenticationManager = new AuthenticationProviderManager($providers); - - try { - $authenticatedToken = $authenticationManager - ->authenticate($unauthenticatedToken); - } catch (AuthenticationException $failed) { - // falló la autenticación - } - -El ``AuthenticationProviderManager``, al crear una instancia, recibe varios proveedores de autenticación, cada uno compatible a un diferente tipo de señal. - -.. note:: - - Por supuesto, puedes escribir tu propio gestor de autenticación, sólo tienes que implementar la :class:`Symfony\\Component\\Security\\Core\\Authentication\\AuthenticationManagerInterface`. - -.. _authentication_providers: - -Proveedores de autenticación ----------------------------- - -Cada proveedor (debido a que implementan la clase :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface`) tiene un método :method:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::supports` por medio del cual el ``AuthenticationProviderManager`` puede determinar si es compatible con la ficha dada. Si este es el caso, el gestor, entonces llama al método :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\AuthenticationProviderInterface::authenticate` del proveedor -. -Este método debe devolver una ficha autenticada o lanzar una :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException` (o cualquier otra excepción que extienda esta). - -Autenticando usuarios por nombre de usuario y contraseña -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Un proveedor de autenticación intentará autenticar a un usuario basándose en las credenciales que él proporcionó. Generalmente, se trata de un nombre de usuario y una contraseña. -La mayoría de las aplicaciones *web* guardan el nombre de usuario y una codificación de la contraseña del usuario combinada con una «sal» generada aleatoriamente. Esto significa que la autenticación promedio consistirá en recuperar la sal y la contraseña codificada del almacén de datos del usuario, la contraseña codificada del usuario es proporcionada (por ejemplo, utilizando un formulario de inicio de sesión) con la sal y comparando ambas para determinar si la contraseña dada es válida. - -Esta funcionalidad es ofrecida por el :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`. -Esta recupera los datos del usuario a partir de una :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`, utilizando una :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface` para crear la codificación de la contraseña y devolver una ficha autenticada si la contraseña es válida:: - - use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider; - use Symfony\Component\Security\Core\User\UserChecker; - use Symfony\Component\Security\Core\User\InMemoryUserProvider; - use Symfony\Component\Security\Core\Encoder\EncoderFactory; - - $userProvider = new InMemoryUserProvider( - array( - 'admin' => array( - // password is "foo" - 'password' => '5FZ2Z8QIkA7UTZ4BYkoC+GsReLf569mSKDsfods6LYQ8t+a8EW9oaircfMpmaLbPBh4FOBiiFyLfuZmTSUwzZg==', - 'roles' => array('ROLE_ADMIN'), - ), - ) - ); - - // para alguna comprobación extra: ¿la cuenta está habilitada, cerrada, expirada, etc.? - $userChecker = new UserChecker(); - - // un arreglo de codificadores de contraseña (ve abajo) - $encoderFactory = new EncoderFactory(...); - - $provider = new DaoAuthenticationProvider( - $userProvider, - $userChecker, - 'secured_area', - $encoderFactory - ); - - $provider->authenticate($unauthenticatedToken); - -.. note:: - - El ejemplo anterior demuestra el uso del proveedor de usuarios «en memoria», pero puedes utilizar cualquier proveedor de usuario, siempre y cuando implemente la :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`. - También es posible permitir que múltiples proveedores de usuario intenten encontrar los datos del usuario, utilizando la :class:`Symfony\\Component\\Security\\Core\\User\\ChainUserProvider`. - -La factoría codificadora de contraseñas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider` utiliza una factoría codificadora para crear una contraseña codificada para un determinado tipo de usuario. Esto te permite utilizar diferentes estrategias de codificado para diferentes tipos de usuario. La clase :class:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory` recibe un arreglo de codificadores:: - - use Symfony\Component\Security\Core\Encoder\EncoderFactory; - use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder; - - $defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000); - $weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1); - - $encoders = array( - 'Symfony\\Component\\Security\\Core\\User\\User' => $defaultEncoder, - 'Acme\\Entity\\LegacyUser' => $weakEncoder, - - // ... - ); - - $encoderFactory = new EncoderFactory($encoders); - -Cada codificador debería implementar la :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface` o ser un arreglo con una clave ``class`` y ``arguments``, lo cual permite a la factoría codificadora construir el codificador sólo cuándo es necesario. - -Codificadores de contraseña -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuándo invocas al método :method:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory::getEncoder` de la factoría codificadora de contraseñas con el objeto ``usuario`` como primer argumento, regresa un codificador de tipo :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface` el cuál deberías usar para codificar la contraseña de ese usuario:: - - // recupera un usuario de tipo Acme\Entity\LegacyUser - $user = ... - - $encoder = $encoderFactory->getEncoder($user); - - // debe regresar $weakEncoder (ve arriba) - - $encodedPassword = $encoder->encodePassword($password, $user->getSalt()); - - // comprueba si la contraseña es válida: - - $validPassword = $encoder->isPasswordValid( - $user->getPassword(), - $password, - $user->getSalt()); diff --git a/_sources/components/security/authorization.txt b/_sources/components/security/authorization.txt deleted file mode 100644 index 375ce81..0000000 --- a/_sources/components/security/authorization.txt +++ /dev/null @@ -1,189 +0,0 @@ -.. index:: - single: Seguridad, autorización - -Autorización -============ - -Cuando alguno de los proveedores de autenticación (ve :ref:`authentication_providers`) ha verificado la ficha no autenticada aún, devolverá una ficha autenticada. El escucha debe configurar la autenticación de esta ficha directamente en la :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface` utilizando su método :method:`Symfony\\Component\\Security\\Core\\SecurityContextInterface::setToken`. - -A partir de ahí, el usuario está autenticado, es decir, identificado. Ahora, otras partes de la aplicación pueden utilizar la ficha para decidir si o no el usuario puede solicitar una determinada *URI*, o modificar un determinado objeto. Esta decisión la tomará una instancia de la :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManagerInterface`. - -Una decisión de autorización siempre se basará en un par de cosas: - -* La ficha actual - Por ejemplo, puedes utilizar el método :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles` de la ficha para recuperar las funciones del usuario actual (por ejemplo, ``ROLE_SUPER_ADMIN``), o una decisión puede estar basada en la clase de la ficha. -* Un conjunto de atributos - Cada atributo representa un determinado derecho que el usuario debe tener, por ejemplo, ``ROLE_ADMIN`` para asegurarse de que el usuario es un administrador. -* Un objeto (opcional) - Cualquier objeto con el cual controlar el acceso necesario, como un artículo o un objeto ``Comentario``. - -Gestor de la decisión de acceso -------------------------------- - -Debido que decidir si un usuario está autorizado a realizar una cierta acción puede ser un proceso complicado, la clase :class:`Symfony\\Component\\Security\\Core\\Authorization\\AccessDecisionManager` estándar en sí misma depende de varios votantes, y hace un veredicto final basándose en todos los votos (positivos, negativos o neutros) que ha recibido. Este reconoce varias estrategias: - -* ``affirmative`` (predeterminada) - concede acceso tan pronto como cualquier votante devuelve una respuesta afirmativa; - -* ``consensus`` - concede acceso si son más los votantes que garantizan el acceso de los que lo niegan; - -* ``unanimous`` - permiten el acceso sólo si ninguno de los votantes niega el acceso; - -.. code-block:: php - - use Symfony\Component\Security\Core\Authorization\AccessDecisionManager; - - // instancias de - // Symfony\Component\Security\Core\Authorization\Voter\VoterInterface - $voters = array(...); - - // uno de "affirmative", "consensus", "unanimous" - $strategy = ...; - - // si o no conceder acceso cuando todos los votantes se abstienen - $allowIfAllAbstainDecisions = ...; - - // Si o no conceder el acceso cuándo no hay mayoría - // (aplica sólo a la estrategia de consenso) - $allowIfEqualGrantedDeniedDecisions = ...; - - $accessDecisionManager = new AccessDecisionManager( - $voters, - $strategy, - $allowIfAllAbstainDecisions, - $allowIfEqualGrantedDeniedDecisions - ); - -Votantes --------- - -Los votantes son instancias -de :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, lo cual significa que tienen que implementar algunos métodos que permiten los utilice el gestor de decisión: - -* ``supportsAttribute($attribute)`` - se utiliza para comprobar si el votante sabe cómo manejar el atributo dado; - -* ``supportsClass($class)`` - se utiliza para comprobar si el votante es capaz de conceder o denegar el acceso a un objeto de la clase dada; - -* ``vote(TokenInterface $token, $object, array $attributes)`` - Este método hará la votación propiamente dicha y devuelve un valor igual a una de las constantes de la clase :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\VoterInterface`, es decir, ``VoterInterface::ACCESS_GRANTED``, ``VoterInterface::ACCESS_DENIED`` o ``VoterInterface::ACCESS_ABSTAIN``; - -El componente de seguridad contiene algunos votantes estándar que cubren muchos casos de uso: - -``AuthenticatedVoter`` -~~~~~~~~~~~~~~~~~~~~~~ - -La clase votante :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\AuthenticatedVoter` admite los atributos ``IS_AUTHENTICATED_FULLY``, ``IS_AUTHENTICATED_REMEMBERED``, e ``IS_AUTHENTICATED_ANONYMOUSLY`` y garantiza el acceso basado en el nivel de autenticación actual, es decir, ¿el usuario está autenticado completamente, o sólo en base a una ``galleta`` «recuérdame», o incluso autenticado anónimamente? - -.. code-block:: php - - use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver; - - $anonymousClass = 'Symfony\Component\Security\Core\Authentication\Token\AnonymousToken'; - $rememberMeClass = 'Symfony\Component\Security\Core\Authentication\Token\RememberMeToken'; - - $trustResolver = new AuthenticationTrustResolver($anonymousClass, $rememberMeClass); - - $authenticatedVoter = new AuthenticatedVoter($trustResolver); - - // instancia de Symfony\Component\Security\Core\Authentication\Token\TokenInterface - $token = ...; - - // algún objeto - $object = ...; - - $vote = $authenticatedVoter->vote($token, $object, array('IS_AUTHENTICATED_FULLY'); - -``RoleVoter`` -~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` es compatible con atributos que empiezan con ``ROLE_`` y conceden acceso al usuario cuándo todos los atributos ``ROLE_*`` requeridos se pueden encontrar en el arreglo de roles devuelto por el método :method:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\TokenInterface::getRoles`:: - - use Symfony\Component\Security\Core\Authorization\Voter\RoleVoter; - - $roleVoter = new RoleVoter('ROLE_'); - - $roleVoter->vote($token, $object, 'ROLE_ADMIN'); - -``RoleHierarchyVoter`` -~~~~~~~~~~~~~~~~~~~~~~ - -La clase :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleHierarchyVoter` extiende a :class:`Symfony\\Component\\Security\\Core\\Authorization\\Voter\\RoleVoter` y proporciona alguna funcionalidad adicional: Esta sabe cómo manejar una jerarquía de roles. Por ejemplo, un rol ``ROLE_SUPER_ADMIN`` puede tener los subroles ``ROLE_ADMIN`` y ``ROLE_USER``, de modo que cuando un determinado objeto requiere que el usuario tenga el rol ``ROLE_ADMIN``, concede acceso a usuarios que de hecho tienen el rol ``ROLE_ADMIN``, pero también a usuarios que tienen el rol ``ROLE_SUPER_ADMIN``:: - - use Symfony\Component\Security\Core\Authorization\Voter\RoleHierarchyVoter; - use Symfony\Component\Security\Core\Role\RoleHierarchy; - - $hierarchy = array( - 'ROLE_SUPER_ADMIN' => array('ROLE_ADMIN', 'ROLE_USER'), - ); - - $roleHierarchy = new RoleHierarchy($hierarchy); - - $roleHierarchyVoter = new RoleHierarchyVoter($roleHierarchy); - -.. note:: - - Cuándo haces tu propio votante, naturalmente puedes utilizar su constructor para inyectar cualquier dependencia necesaria para tomar una decisión. - -Roles ------ - -Los roles son objetos que expresan un determinado derecho que tiene el usuario. -El único requisito es que implementen la :class:`Symfony\\Component\\Security\\Core\\Role\\RoleInterface`, lo cual significa también que deberían tener un método :method:`Symfony\\Component\\Security\\Core\\Role\\Role\\RoleInterface::getRole` que regrese una cadena representando su rol. La clase :class:`Symfony\\Component\\Security\\Core\\Role\\Role` simplemente devuelve el primer argumento del constructor:: - - use Symfony\Component\Security\Core\Role\Role; - - $role = new Role('ROLE_ADMIN'); - - // hará eco de 'ROLE_ADMIN' - echo $role->getRole(); - -.. note:: - - La mayoría de las fichas de autenticación extienden la clase :class:`Symfony\\Component\\Security\\Core\\Authentication\\Token\\AbstractToken`, lo cual significa que los roles dados a su constructor serán convertidos automáticamente de cadenas a estos sencillos objetos ``Role``. - -Usando el gestor de decisión ----------------------------- - -El escucha de acceso -~~~~~~~~~~~~~~~~~~~~ - -El gestor de decisión de acceso se puede utilizar en cualquier punto en una petición para decidir si o no el usuario actual tiene derecho para acceder a un determinado recurso. Un opcional ---pero útil--- método para restringir el acceso basándose en un patrón de *URL* es el :class:`Symfony\\Component\\Security\\Http\\Firewall\\AccessListener`, el cual es uno de los escuchas del cortafuegos (ve :ref:`firewall_listeners`) que es lanzado en cada petición que coincide con el cortafuegos asociado (ve :ref:`firewall`). - -Este utiliza un mapa de acceso (el cuál debería ser una instancia de la :class:`Symfony\\Component\\Security\\Http\\AccessMapInterface`) que contiene la petición y el conjunto de atributos correspondiente necesarios para que el usuario actual consiga acceso a la aplicación:: - - use Symfony\Component\Security\Http\AccessMap; - use Symfony\Component\HttpFoundation\RequestMatcher; - use Symfony\Component\Security\Http\Firewall\AccessListener; - - $accessMap = new AccessMap(); - $requestMatcher = new RequestMatcher('^/admin'); - $accessMap->add($requestMatcher, array('ROLE_ADMIN')); - - $accessListener = new AccessListener( - $securityContext, - $accessDecisionManager, - $accessMap, - $authenticationManager - ); - -Contexto de seguridad -~~~~~~~~~~~~~~~~~~~~~ - -El gestor de decisión de acceso también está disponible en otras partes de la aplicación vía el método :method:`Symfony\\Component\\Security\\Core\\SecurityContext::isGranted` de la clase :class:`Symfony\\Component\\Security\\Core\\SecurityContext`. -Una llamada a este método delegará la cuestión directamente al gestor de decisión de acceso:: - - use Symfony\Component\Security\SecurityContext; - use Symfony\Component\Security\Core\Exception\AccessDeniedException; - - $securityContext = new SecurityContext( - $authenticationManager, - $accessDecisionManager - ); - - if (!$securityContext->isGranted('ROLE_ADMIN')) { - throw new AccessDeniedException(); - } diff --git a/_sources/components/security/firewall.txt b/_sources/components/security/firewall.txt deleted file mode 100644 index 9fb1a0d..0000000 --- a/_sources/components/security/firewall.txt +++ /dev/null @@ -1,97 +0,0 @@ -.. index:: - single: Seguridad, cortafuegos - -Cortafuegos y el contexto de seguridad -====================================== - -Una parte central del componente de seguridad es el contexto de seguridad, el cual es una instancia de la clase :class:`Symfony\\Component\\Security\\Core\\SecurityContextInterface`. Cuando todos los pasos en el proceso de autenticar al usuario han sido exitosos, puedes preguntar al contexto de seguridad si el usuario autenticado tiene acceso a una determinada acción o recurso de la aplicación:: - - use Symfony\Component\Security\SecurityContext; - use Symfony\Component\Security\Core\Exception\AccessDeniedException; - - $securityContext = new SecurityContext(); - - // ... autentifica al usuario - - if (!$securityContext->isGranted('ROLE_ADMIN')) { - throw new AccessDeniedException(); - } - -.. _firewall: - -Un cortafuegos para peticiones *HTTP* -------------------------------------- - -La autenticación de un usuario se hizo para el cortafuegos. Una aplicación puede tener múltiples áreas protegidas, así que el cortafuegos está configurado utilizando un mapa de estas áreas protegidas. Para cada una de estas áreas, el mapa contiene un emparejador con la petición y una colección de escuchas. El emparejador de petición da al cortafuegos la habilidad de descubrir si la petición actual apunta a una área protegida. -Los escuchas entonces son consultados para ver si se puede usar la petición actual para autenticar al usuario:: - - use Symfony\Component\Security\Http\FirewallMap; - use Symfony\Component\HttpFoundation\RequestMatcher; - use Symfony\Component\Security\Http\Firewall\ExceptionListener; - - $map = new FirewallMap(); - - $requestMatcher = new RequestMatcher('^/secured-area/'); - - // instancias de - // Symfony\Component\Security\Http\Firewall\ListenerInterface - $listeners = array(...); - - $exceptionListener = new ExceptionListener(...); - - $map->add($requestMatcher, $listeners, $exceptionListener); - -El mapa del cortafuegos será asociado al cortafuegos como su primer argumento, junto con el despachador de eventos utilizado por la clase :class:`Symfony\\Component\\HttpKernel\\HttpKernel`:: - - use Symfony\Component\Security\Http\Firewall; - use Symfony\Component\HttpKernel\KernelEvents; - - // el EventDispatcher usado por el HttpKernel - $dispatcher = ...; - - $firewall = new Firewall($map, $dispatcher); - - $dispatcher->addListener(KernelEvents::REQUEST, - array($firewall, - 'onKernelRequest' - )); - -El cortafuegos es registrado para escuchar el evento ``kernel.request`` que será despachado por el ``HttpKernel`` al principio de cada petición procesada. De este modo, el cortafuegos puede impedir al usuario ir más allá de dónde tiene permitido. - -.. _firewall_listeners: - -Escuchas del cortafuegos -~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuándo el cortafuegos es notificado del evento ``kernel.request``, este consulta el mapa del cortafuegos para ver si la petición coincide con una de las áreas protegidas. La primer área protegida que coincide con la petición regresará el correspondiente conjunto de escuchas del cortafuegos (el cual implementa la :class:`Symfony\\Component\\Security\\Http\\Firewall\\ListenerInterface`). -Todos estos escuchas serán consultados para manejar la petición actual. Esto básicamente significa: descubrir si la petición actual contiene alguna información por medio de la cual se podría autenticar al usuario (para casos de autenticación *HTTP* básicos el escucha comprueba si la petición tiene una cabecera llamada ``PHP_AUTH_USER``). - -Escucha de excepciones -~~~~~~~~~~~~~~~~~~~~~~ - -Si cualquiera de los escuchas lanza una :class:`Symfony\\Component\\Security\\Core\\Exception\\AuthenticationException`, el escucha de la excepción proporcionado al añadir las áreas protegidas saltará al mapa del cortafuegos. - -El escucha de la excepción determina qué pasa luego, basándose en los argumentos recibidos al crearlo. Este puede empezar el procedimiento de autenticación, quizás ---nuevamente--- haciendo que el usuario suministre sus credenciales (cuándo sólo ha sido -autenticado basándose en una ``galleta`` «recuérdame»), o transformando la excepción a una clase :class:`Symfony\\Component\\HttpKernel\\Exception\\AccessDeniedHttpException`, la cual finalmente resulta en una «respuesta 403 de *HTTP/1.1*: Acceso denegado». - -Puntos de entrada -~~~~~~~~~~~~~~~~~ - -Cuándo el usuario no es autenticado en absoluto (es decir, cuándo el contexto de seguridad -no tiene ficha todavía), el punto de entrada del cortafuegos será llamado para que «empiece» -el proceso de autenticación. Un punto de entrada debería implementar :class:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface`, la cual sólo tiene un método: :method:`Symfony\\Component\\Security\\Http\\EntryPoint\\AuthenticationEntryPointInterface::start`. -Este método recibe el objeto :class:`Symfony\\Component\\HttpFoundation\\Request` actual y la excepción por la cual el escucha de excepciones fue lanzado. -El método debería regresar un objeto :class:`Symfony\\Component\\HttpFoundation\\Response`. Este podría ser, por ejemplo, la página que contiene el formulario de inicio de sesión o, en el caso de autenticación *HTTP* básica, una respuesta con la cabecera ``WWW-Authenticate``, la cual hará que el usuario suministre su nombre de usuario y contraseña. - -Flujo: cortafuegos, autenticación, autorización ------------------------------------------------ - -Con algo de suerte, ahora puedes ver un poco de cómo «fluye» el trabajo del contexto de seguridad: - -#. El cortafuegos es registrado como escucha en el evento ``kernel.request``; -#. Al principio de la petición, el cortafuegos comprueba el mapa del cortafuegos para ver si algún cortafuegos debería estar activo para esa *URL*; -#. Si se encontró un cortafuegos en el mapa para esta *URL*, sus escuchas son notificados -#. Cada escucha comprueba si la petición actual contiene alguna información de autenticación ---un escucha puede (a) autenticar un usuario, (b) lanzar una ``AuthenticationException``, o (c) no hacer nada (porque no hay información de autenticación en la petición); -#. Una vez autenticado un usuario, utilizarás la :doc:`/components/security/authorization` para denegar el acceso a determinados recursos. - -Lee las siguientes secciones para descubrir más sobre :doc:`/components/security/authentication` y :doc:`/components/security/authorization`. \ No newline at end of file diff --git a/_sources/components/security/index.txt b/_sources/components/security/index.txt deleted file mode 100644 index dc404bb..0000000 --- a/_sources/components/security/index.txt +++ /dev/null @@ -1,10 +0,0 @@ -Seguridad -========= - -.. toctree:: - :maxdepth: 2 - - introduction - firewall - authentication - authorization \ No newline at end of file diff --git a/_sources/components/security/introduction.txt b/_sources/components/security/introduction.txt deleted file mode 100644 index 91a530e..0000000 --- a/_sources/components/security/introduction.txt +++ /dev/null @@ -1,29 +0,0 @@ -.. index:: - single: Seguridad - -El componente ``Security`` -========================== - -Introducción ------------- - -El componente ``Security`` proporciona un sistema de seguridad completo para tu aplicación *web*. Este viene con utilerías para autentificar usando autenticación *HTTP* básica o por medio de suma de comprobación, inicio de sesión interactivo o con certificado X.509, pero también te permite implementar tus propias estrategias de autenticación. -Además, el componente proporciona maneras de autorizar usuarios autenticados -basándose en sus roles, y contiene un avanzado sistema *ACL*. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Security); -* :doc:`Instalándolo vía Composer ` (``symfony/security`` en `Packagist`_). - -Secciones ---------- - -* :doc:`/components/security/firewall` -* :doc:`/components/security/authentication` -* :doc:`/components/security/authorization` - -.. _`Packagist`: https://packagist.org/packages/symfony/security \ No newline at end of file diff --git a/_sources/components/serializer.txt b/_sources/components/serializer.txt deleted file mode 100644 index 23c56f2..0000000 --- a/_sources/components/serializer.txt +++ /dev/null @@ -1,120 +0,0 @@ -.. index:: - single: Serializer - single: Componentes; Serializer - -El componente ``Serializer`` -============================ - - El componente ``Serializer`` se pretende sea utilizado para convertir objetos a un formato específico (*XML*, *JSON*, *Yaml*, ...) y a la inversa. - -Para ello, el componente ``Serializer`` sigue el siguiente sencillo esquema. - -.. _component-serializer-encoders: -.. _component-serializer-normalizers: - -.. image:: /images/components/serializer/serializer_workflow.png - -Como puedes ver en la imagen anterior, se utiliza un arreglo como un hombre en -el medio. De este modo, los codificadores sólo tratarán convirtiendo **formatos** específicos en **arreglos** y viceversa. Del mismo modo, los normalizadores tratarán cambiando **objetos** específicos en **arreglos** y viceversa. - -La serialización es un tema complicado, y a pesar de que este componente no puede trabajar en todos los casos, puede ser una útil herramienta mientras desarrollas herramientas para serializar y deserializar tus objetos. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Serializer); -* :doc:`Install it via Composer ` (``symfony/serializer`` on `Packagist`_). - -Usando ------- - -Utilizar el componente ``Serializer`` es realmente sencillo. Sólo necesitas instalar la clase :class:`Symfony\\Component\\Serializer\\Serializer` especificando qué codificadores y normalizadores van a estar disponibles:: - - use Symfony\Component\Serializer\Serializer; - use Symfony\Component\Serializer\Encoder\XmlEncoder; - use Symfony\Component\Serializer\Encoder\JsonEncoder; - use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; - - $encoders = array(new XmlEncoder(), new JsonEncoder()); - $normalizers = array(new GetSetMethodNormalizer()); - - $serializer = new Serializer($normalizers, $encoders); - -Serializando un objeto -~~~~~~~~~~~~~~~~~~~~~~ - -Por el bien de este ejemplo, supongamos que la siguiente clase ya existe en tu proyecto:: - - namespace Acme; - - class Person - { - private $age; - private $name; - - // Captadores - public function getName() - { - return $this->name; - } - - public function getAge() - { - return $this->age; - } - - // Definidores - public function setName($name) - { - $this->name = $name; - } - - public function setAge($age) - { - $this->age = $age; - } - } - -Ahora, si queremos serializar este objeto a *JSON*, sólo necesitamos utilizar el servicio ``Serializer`` creado anteriormente:: - - $person = new Acme\Person(); - $person->setName('foo'); - $person->setAge(99); - - $jsonContent = $serializer->serialize($person, 'json'); - - // $jsonContent contains {"name":"foo","age":99} - - echo $jsonContent; // or return it in a Response - -El primer argumento del método :method:`Symfony\\Component\\Serializer\\Serializer::serialize` es el objeto a serializar y el segundo suele escoger el codificador apropiado, en este caso :class:`Symfony\\Component\\Serializer\\Encoder\\JsonEncoder`. - -Deserializando un Objeto -~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora veremos cómo hacer exactamente lo opuesto. Esta vez, la información de la clase ``Personas`` estaría codificada en formato *XML*:: - - $data = << - foo - 99 - - EOF; - - $person = $serializer->deserialize($data,'Acme\Person','xml'); - -En este caso, el método :method:`Symfony\\Component\\Serializer\\Serializer::deserialize` necesita tres argumentos: - -1. La información a descodificar -2. El nombre de la clase a la que esta información será descodificada -3. El codificador a utilizar para convertir esa información a un arreglo - -``JMSSerializer`` ------------------ - -Una popular biblioteca de terceros, `JMS serializer`_, proporciona una solución más sofisticada aunque más compleja. Esta biblioteca incluye la habilidad para configurar cómo se deberían serializar/deserializar tus objetos a través de anotaciones (así como *YML*, *XML* y *PHP*), integrarlos con el *ORM* de *Doctrine*, y manejar otros casos complejos (p. ej. en referencias circulares). - -.. _`JMS serializer`: https://github.com/schmittjoh/serializer -.. _`Packagist`: https://packagist.org/packages/symfony/serializer diff --git a/_sources/components/stopwatch.txt b/_sources/components/stopwatch.txt deleted file mode 100644 index d7c4bdd..0000000 --- a/_sources/components/stopwatch.txt +++ /dev/null @@ -1,95 +0,0 @@ -.. index:: - single: Stopwatch - single: Componentes; Stopwatch - -El componente ``Stopwatch`` -=========================== - - El componente ``Stopwatch`` proporciona una manera para codificar el perfil. - -.. versionadded:: 2.2 - El componente ``Stopwatch`` es nuevo en *Symfony 2.2*. Anteriormente, la clase ``Stopwatch`` estaba localizada en el componente ``HttpKernel`` (y era nueva en 2.1). - -Instalando ----------- - -Puedes instalar el componente en dos diferentes maneras: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Stopwatch); -* :doc:`Instalándolo vía Composer ` (``symfony/stopwatch`` en `Packagist`_). - -Usando ------- - -El el componente ``stopwatch`` proporciona una manera fácil y coherente para medir el tiempo de ejecución de ciertas partes de código a modo de que no tengas que analizar la duración de algo. En cambio, usas la sencilla clase :class:`Symfony\\Component\\Stopwatch\\Stopwatch`:: - - use Symfony\Component\Stopwatch\Stopwatch; - - $stopwatch = new Stopwatch(); - // Inicia el evento llamado 'eventName' - $stopwatch->start('eventName'); - // ... aquí va algún código - $event = $stopwatch->stop('eventName'); - -También puedes proporcionar un nombre de categoría a un evento:: - - $stopwatch->start('eventName', 'categoryName'); - -Puedes considerar a las categorías como una conveniente manera de etiquetar eventos. Por ejemplo, la herramienta del perfilador de *Symfony* usa categorías para codificar coloridamente diferentes eventos. - -Periodos --------- - -Como sabes del mundo real, todos los cronómetros vienen con dos botones. -Uno para arrancar y detener el cronómetro, otro para medir el tiempo transcurrido. -Esto exactamente es lo que hace el método :method:`Symfony\\Component\\Stopwatch\\Stopwatch::lap`:: - - $stopwatch = new Stopwatch(); - // Inicia el evento llamado 'foo' - $stopwatch->start('foo'); - // ... aquí va algún código - $stopwatch->lap('foo'); - // ... aquí va algún código - $stopwatch->lap('foo'); - // ... aquí va algún otro código - $event = $stopwatch->stop('foo'); - -La información del lapso se almacena como «periodos» en el evento. Para obtener dicha información llama a:: - - $event->getPeriods(); - -Además de obtener los periodos, puedes conseguir otra útil información del objeto evento. -Por ejemplo:: - - $event->getCategory(); // Devuelve la categoría en que se inició el evenento - $event->getOrigin(); // Devuelve el lapso transcurrido desde que se inicio - // el evento en milisegundos - $event->ensureStopped(); // Detiene todos los periodos que no se han detenido ya - $event->getStartTime(); // Regresa el inicio del primer periodo - $event->getEndTime(); // Regresa el tiempo final del último periodo - $event->getDuration(); // Obtiene la duración del evento incluyendo todos los - // periodos - $event->getMemory(); // Devuelve el máximo uso de memoria de todos los periodos - -Secciones ---------- - -Las secciones son una manera de dividir lógicamente la cronología en grupos. Puedes ver cómo usa *Symfony* las secciones visualizando agradablemente el ciclo de vida con -la herramienta perfiladora de *Symfony*. Aquí tienes un ejemplo del uso básico de las secciones:: - - $stopwatch = new Stopwatch(); - - $stopwatch->openSection(); - $stopwatch->start('parsing_config_file', 'filesystem_operations'); - $stopwatch->stopSection('routing'); - - $events = $stopwatch->getSectionEvents('routing'); - -You can reopen a closed section by calling the :method:`Symfony\\Component\\Stopwatch\\Stopwatch::openSection`` -method and specifying the id of the section to be reopened:: - - $stopwatch->openSection('routing'); - $stopwatch->start('building_config_tree'); - $stopwatch->stopSection('routing'); - -.. _`Packagist`: https://packagist.org/packages/symfony/stopwatch diff --git a/_sources/components/templating.txt b/_sources/components/templating.txt deleted file mode 100644 index 30710ff..0000000 --- a/_sources/components/templating.txt +++ /dev/null @@ -1,102 +0,0 @@ -.. index:: - single: Templating - single: Componentes; Plantillas - -El componente ``Templating`` -============================ - - El componente ``Templating`` proporciona todas las herramientas necesarias para construir cualquier tipo de sistema de plantillas. - - Este proporciona una infraestructura para cargar los archivos de plantilla y opcionalmente controlar sus cambios. También proporciona una implementación del motor de plantillas concreto usando *PHP* con herramientas adicionales para escapar y separar plantillas en bloques y diseños. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Templating); -* :doc:`Instalándolo vía Composer` (``symfony/templating`` en `Packagist`_). - -Usando ------- - -La clase :class:`Symfony\\Component\\Templating\\PhpEngine` es el punto de entrada del componente. Este necesita un analizador de nombres de plantilla (:class:`Symfony\\Component\\Templating\\TemplateNameParserInterface`) para convertir un nombre de plantilla a una referencia a la plantilla y cargador de plantilla (:class:`Symfony\\Component\\Templating\\Loader\\LoaderInterface`) para encontrar la plantilla asociada a una referencia:: - - use Symfony\Component\Templating\PhpEngine; - use Symfony\Component\Templating\TemplateNameParser; - use Symfony\Component\Templating\Loader\FilesystemLoader; - - $loader = new FilesystemLoader(__DIR__ . '/views/%name%'); - - $view = new PhpEngine(new TemplateNameParser(), $loader); - - echo $view->render('hello.php', array('firstname' => 'Fabien')); - -El método :method:`Symfony\\Component\\Templating\\PhpEngine::render` ejecuta el archivo -:file:`views/hello.php` y devuelve el texto producido. - -.. code-block:: html+php - - - Hello, ! - -Herencia de plantillas con ranuras ----------------------------------- - -La herencia de plantillas está diseñada para compartir diseños con muchas plantillas. - - -.. code-block:: html+php - - - - - <?php $view['slots']->output('title', 'Default title') ?> - - - output('_content') ?> - - - -El método :method:`Symfony\\Component\\Templating\\PhpEngine::extend` es llamado en la subplantilla para definir la plantilla padre. - -.. code-block:: html+php - - - extend('layout.php') ?> - - set('title', $page->title) ?> - -

    - title ?> -

    -

    - body ?> -

    - -Para usar la herencia de plantillas, debes registrar la clase ayudante :class:`Symfony\\Component\\Templating\\Helper\\SlotsHelper`:: - - use Symfony\Component\Templating\Helper\SlotsHelper; - - $view->set(new SlotsHelper()); - - // Retrieve page object - $page = ...; - - echo $view->render('page.php', array('page' => $page)); - -.. note:: - - Es posible la herencia multinivel: un diseño puede extender a otro. - -Mecanismo de escape -------------------- - -Esta documentación todavía se está escribiendo. - -El ayudante ``Asset`` ---------------------- - -Esta documentación todavía se está escribiendo. - -.. _`Packagist`: https://packagist.org/packages/symfony/templating \ No newline at end of file diff --git a/_sources/components/using_components.txt b/_sources/components/using_components.txt deleted file mode 100644 index edecdac..0000000 --- a/_sources/components/using_components.txt +++ /dev/null @@ -1,91 +0,0 @@ -.. index:: - single: Componentes; Instalando - single: Componentes; Usando - -Cómo instalar y usar componentes de *Symfony2* -============================================== - -Si estás comenzando un nuevo proyecto (o ya tienes uno) que utilizará uno o más componentes, la forma más fácil de integrar todo es con `Composer`_. -``Composer`` es lo suficientemente inteligente como para descargar el/los componente(s) que necesita(s) y tiene cuidado de cargalos automáticamente para que puedas empezar a utilizar las bibliotecas de inmediato. - -Este artículo te llevará a través del uso del :doc:`/components/finder`, aunque esto se aplica a la utilización de cualquier componente. - -Usando el componente ``Finder`` -------------------------------- - -**1.** Si vas a crear un nuevo proyecto, crea un nuevo directorio vacío para él. - -**2.** Crea un nuevo archivo llamado ``composer.json`` y pega el siguiente código en él: - -.. code-block:: json - - { - "require": { - "symfony/finder": "2.2.*" - } - } - -Si ya tienes un archivo ``composer.json``, sólo agrégale esta línea. Posiblemente también necesites ajustar la versión (p. ej. ``2.1.1`` o ``2.2.*``). - -Puedes averiguar el nombre de los componentes y sus versiones en `packagist.org`_. - -**3.** `Instala composer`_ si no está presente en tu sistema: - -**4.** Descarga las bibliotecas de proveedores y genera el archivo :file:`vendor/autoload.php`: - -.. code-block:: bash - - $ php composer.phar install - -**5.** Escribe tu código: - -Una vez que ``Composer`` ha descargado el/los componente(s), todo lo que necesitas hacer es incluir el archivo ``vendor/autoload.php`` generado por ``Composer``. Este archivo se preocupa de cagar automáticamente todas las bibliotecas a modo que las puedas utilizar inmediatamente:: - - // Archivo: src/script.php - - // actualiza esto a la ruta al directorio «vendor/», relativa a este archivo - require_once '../vendor/autoload.php'; - - use Symfony\Component\Finder\Finder; - - $finder = new Finder(); - $finder->in('../data/'); - - // ... - -.. tip:: - - Si quieres utilizar todos los componentes de *Symfony2*, entonces en vez de añadirlos uno por uno: - - .. code-block:: json - - { - "require": { - "symfony/finder": "2.2.*", - "symfony/dom-crawler": "2.2.*", - "symfony/css-selector": "2.2.*" - } - } - - puedes usar: - - .. code-block:: json - - { - "require": { - "symfony/symfony": "2.2.*" - } - } - - Esto incluirá el paquete y bibliotecas puente, el cual posiblemente no necesites de hecho. - -¿Ahora qué? ------------ - -Ahora que el componente está instalado y se carga automáticamente, lee la documentación específica del componente para descubrir más sobre cómo utilizarlo. - -¡Y diviértete! - -.. _Composer: http://getcomposer.org -.. _`Instala composer`: http://getcomposer.org/download/ -.. _packagist.org: https://packagist.org/ \ No newline at end of file diff --git a/_sources/components/yaml.txt b/_sources/components/yaml.txt deleted file mode 100644 index a2f2c78..0000000 --- a/_sources/components/yaml.txt +++ /dev/null @@ -1,430 +0,0 @@ -.. index:: - single: Yaml - single: Componentes; Yaml - -El componente *YAML* -==================== - - El componente *YAML* carga y vuelca archivos *YAML*. - -¿Qué es entonces? ------------------ - -El componente *YAML* de *Symfony2* analiza cadenas *YAML* para convertirlas en arreglos *PHP*. -También es capaz de convertir arreglos *PHP* a cadenas *YAML*. - -`YAML`_, *YAML no es un lenguaje de marcado*, es un estándar para la serialización de datos amigable con los humanos para todos los lenguajes de programación. YAML es un formato ideal para tus archivos de configuración. Los archivos *YAML* son tan expresivos como los archivos *XML* y tan fáciles de leer como los archivos ``INI``. - -El componente *YAML* de *Symfony2* implementa la versión 1.2 de la -especificación *YAML*. - - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Yaml); -* :doc:`Instalándolo vía Composer ` (``symfony/yaml`` en `Packagist`_). - -¿Por qué? ---------- - -Rapidez -~~~~~~~ - -Uno de los objetivos del componente *YAML* de *Symfony* es encontrar el balance adecuado entre características y velocidad. Es compatible con las características necesarias para manipular archivos de configuración. - -Analizador real -~~~~~~~~~~~~~~~ - -Este exporta un analizador real y es capaz de analizar un gran subconjunto de especificaciones *YAML*, para todas tus necesidades de configuración. También significa que el analizador es bastante robusto, fácil de entender, y lo suficientemente simple como para ampliarlo. - -Borrando mensajes de error -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada vez que tienes un problema de sintaxis con tus archivos *YAML*, la biblioteca genera un útil mensaje de ayuda con el nombre y número de línea donde está el problema. Lo cual facilita mucho la depuración. - -Compatibilidad con el volcado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -También es capaz de volcar arreglos de *PHP* a *YAML* con apoyo a objetos, y el nivel de configuración en línea es bastante bueno. - -Compatibilidad de tipos -~~~~~~~~~~~~~~~~~~~~~~~ - -Es compatible con la mayoría de los tipos *YAML* integrados como fechas, números enteros, octales, booleanos, y mucho más... - -Completa compatibilidad con la fusión de claves -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Completa compatibilidad para referencias, alias y completa fusión de claves. No repitas tú mismo haciendo referencia a segmentos de configuración comunes. - -Utilizando el componente *YAML* de *Symfony2* ---------------------------------------------- - -El componente *YAML* de *Symfony2* es muy simple y consiste de dos clases principales: -una analiza cadenas *YAML* (:class:`Symfony\\Component\\Yaml\\Parser`), y la otra -vuelca un array *PHP* en una cadena *YAML* (:class:`Symfony\\Component\\Yaml\\Dumper`). - -Además de estas dos clases, la clase :class:`Symfony\\Component\\Yaml\\Yaml` actúa como un delgado contenedor que simplifica los usos más comunes. - -Leyendo archivos *YAML* -~~~~~~~~~~~~~~~~~~~~~~~ - -El método :method:`Symfony\\Component\\Yaml\\Parser::parse` analiza una cadena *YAML* y la convierte en un arreglo *PHP*: - -.. code-block:: php - - use Symfony\Component\Yaml\Parser; - - $yaml = new Parser(); - - $value = $yaml->parse(file_get_contents('/path/to/file.yml')); - -Si ocurre un error durante el análisis, el analizador lanza una excepción :class:`Symfony\\Component\\Yaml\\Exception\\ParseException` que indica el tipo de error y la línea en la cadena *YAML* original en la que ocurrió el error: - -.. code-block:: php - - use Symfony\Component\Yaml\Exception\ParseException; - - try { - $value = $yaml->parse(file_get_contents('/path/to/file.yml')); - } catch (ParseException $e) { - printf("Unable to parse the YAML string: %s", $e->getMessage()); - } - -.. tip:: - - Debido a que el analizador es reentrante, puedes utilizar el mismo objeto analizador para cargar diferentes cadenas *YAML*. - -Al cargar un archivo *YAML*, a veces es mejor usar el contenedor del método :method:`Symfony\\Component\\Yaml\\Yaml::parse`: - -.. code-block:: php - - use Symfony\Component\Yaml\Yaml; - - $yaml = Yaml::parse('/path/to/file.yml'); - -El método estático :method:`Symfony\\Component\\Yaml\\Yaml::parse` toma una cadena *YAML* o un archivo que contiene *YAML*. Internamente, llama al método :method:`Symfony\\Component\\Yaml\\Parser::parse`, pero mejora el error si algo sale mal, añadiendo el nombre del archivo al mensaje. - -Ejecutando *PHP* dentro de los archivos *YAML* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - El método ``Yaml::enablePhpParsing()`` es nuevo en *Symfony 2.1*. Antes de 2.1, *PHP* *siempre* se ejecuta al llamar a la función ``parse()``. - -De manera predeterminada, si incluyes *PHP* dentro de un archivo *YAML*, no se va a analizar. -Si quiere que *PHP* se analice, debes llamar a ``Yaml::enablePhpParsing()`` antes de analizar el archivo para activar ese modo. Si sólo deseas permitir código *PHP* para un único archivo *YAML*, asegúrate de desactivar el intérprete *PHP* después de analizar ese archivo llamando a ``Yaml::$enablePhpParsing = false;``, (``$enablePhpParsing`` es una propiedad pública). - -Escribiendo archivos *YAML* -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El método :method:`Symfony\\Component\\Yaml\\Dumper::dump` vuelca cualquier arreglo *PHP* en su representación *YAML*: - -.. code-block:: php - - use Symfony\Component\Yaml\Dumper; - - $array = array( - 'foo' => 'bar', - 'bar' => array('foo' => 'bar', 'bar' => 'baz') - ); - - $dumper = new Dumper(); - - $yaml = $dumper->dump($array); - - file_put_contents('/path/to/file.yml', $yaml); - -.. note:: - - Por supuesto, el vertedor *YAML* de *Symfony2* no es capaz de volcar recursos. Por otra parte, aun y cuando el vertedor es capaz de volcar objetos *PHP*, se considera como una función **No** admitida. - -Si ocurre un error durante el volcado, el analizador lanza una excepción :class:`Symfony\\Component\\Yaml\\Exception\\DumpException`. - -Si sólo necesitas volcar un arreglo, puedes utilizar el método estático :method:`Symfony\\Component\\Yaml\\Yaml::dump`: - -.. code-block:: php - - use Symfony\Component\Yaml\Yaml; - - $yaml = Yaml::dump($array, $inline); - -El formato *YAML* admite dos tipos de representación de arreglos, la ampliada, y en línea. Por omisión, el vertedor utiliza la representación en línea: - -.. code-block:: yaml - - { foo: bar, bar: { foo: bar, bar: baz } } - -El segundo argumento del método :method:`Symfony\\Component\\Yaml\\Dumper::dump` personaliza el nivel en el cual el resultado cambia de la representación ampliada a en línea: - -.. code-block:: php - - echo $dumper->dump($array, 1); - -.. code-block:: yaml - - foo: bar - bar: { foo: bar, bar: baz } - -.. code-block:: php - - echo $dumper->dump($array, 2); - -.. code-block:: yaml - - foo: bar - bar: - foo: bar - bar: baz - -El formato *YAML* ------------------ - -El sitio *web* de `YAML`_ dice que es «un estándar para la serialización de datos humanamente legibles para todos los lenguajes de programación». - -Si bien el formato *YAML* puede describir complejas estructuras de datos anidadas, este capítulo sólo describe el conjunto mínimo de características necesarias para usar *YAML* como un formato de archivo de configuración. - -*YAML* es un lenguaje simple que describe datos. Como *PHP*, que tiene una sintaxis para tipos simples, como cadenas, booleanos, números en punto flotante, o enteros. Pero a diferencia de *PHP*, este distingue entre arreglos (secuencias) y ``hashes`` (asignaciones). - -Escalares -~~~~~~~~~ - -La sintaxis para escalares es similar a la sintaxis de *PHP*. - -Cadenas -....... - -.. code-block:: yaml - - Una cadena en YAML - -.. code-block:: yaml - - 'Una cadena entre comillas simples en YAML' - -.. tip:: - - En una cadena entre comillas simples, una comilla simple ``'`` debe ser doble: - - .. code-block:: yaml - - 'Una comilla simple '' en una cadena entre comillas simples' - -.. code-block:: yaml - - "Una cadena entre comillas dobles en YAML\n" - -Los estilos de citado son útiles cuando una cadena empieza o termina con uno o más espacios relevantes. - -.. tip:: - - El estilo entre comillas dobles proporciona una manera de expresar cadenas arbitrarias, utilizando secuencias de escape ``\``. Es muy útil cuando se necesita incrustar un ``\n`` o un carácter Unicode en una cadena. - -Cuando una cadena contiene saltos de línea, puedes usar el estilo literal, indicado por la línea vertical (``|``), para indicar que la cadena abarcará varias líneas. En literales, se conservan los saltos de línea: - -.. code-block:: yaml - - | - \/ /| |\/| | - / / | | | |__ - -Alternativamente, puedes escribir cadenas con el estilo de plegado, denotado por ``>``, en el cual cada salto de línea es sustituido por un espacio: - -.. code-block:: yaml - - > - Esta es una oración muy larga - que se extiende por varias líneas en el YAML - pero que se reproduce como una cadena - sin retornos de carro. - -.. note:: - - Observa los dos espacios antes de cada línea en los ejemplos anteriores. Ellos no aparecen en las cadenas *PHP* resultantes. - -Números -....... - -.. code-block:: yaml - - # un entero - 12 - -.. code-block:: yaml - - # un octal - 014 - -.. code-block:: yaml - - # un hexadecimal - 0xC - -.. code-block:: yaml - - # un float - 13.4 - -.. code-block:: yaml - - # un número exponencial - 1.2e+34 - -.. code-block:: yaml - - # infinito - .inf - -Nulos -..... - -Los nulos en *YAML* se pueden expresar con ``null`` o ``~``. - -Booleanos -......... - -Los booleanos en *YAML* se expresan con ``true`` y ``false``. - -Fechas -...... - -*YAML* utiliza la norma ISO-8601 para expresar fechas: - -.. code-block:: yaml - - 2001-12-14t21:59:43.10-05:00 - -.. code-block:: yaml - - # fecha simple - 2002-12-14 - -Colecciones -~~~~~~~~~~~ - -Rara vez se utiliza un archivo *YAML* para describir un simple escalar. La mayoría de las veces, describe una colección. Una colección puede ser una secuencia o una asignación de elementos. Ambas, secuencias y asignaciones se convierten en arreglos *PHP*. - -Las secuencias usan un guión seguido por un espacio: - -.. code-block:: yaml - - - PHP - - Perl - - Python - -El archivo *YAML* anterior es equivalente al siguiente código *PHP*: - -.. code-block:: php - - array('PHP', 'Perl', 'Python'); - -La asignación utiliza dos puntos seguidos por un espacio (``:`` ) para marcar cada par clave/valor: - -.. code-block:: yaml - - PHP: 5.2 - MySQL: 5.1 - Apache: 2.2.20 - -el cual es equivalente a este código *PHP*: - -.. code-block:: php - - array('PHP' => 5.2, 'MySQL' => 5.1, 'Apache' => '2.2.20'); - -.. note:: - - En una asignación, una clave puede ser cualquier escalar válido. - -El número de espacios entre los dos puntos y el valor no importa: - -.. code-block:: yaml - - PHP: 5.2 - MySQL: 5.1 - Apache: 2.2.20 - -*YAML* utiliza sangría con uno o más espacios para describir colecciones anidadas: - -.. code-block:: yaml - - "symfony 1.0": - PHP: 5.0 - Propel: 1.2 - "symfony 1.2": - PHP: 5.2 - Propel: 1.3 - -El *YAML* anterior es equivalente al siguiente código *PHP*: - -.. code-block:: php - - array( - 'symfony 1.0' => array( - 'PHP' => 5.0, - 'Propel' => 1.2, - ), - 'symfony 1.2' => array( - 'PHP' => 5.2, - 'Propel' => 1.3, - ), - ); - -Hay una cosa importante que tienes que recordar cuando utilices sangría en un archivo *YAML*: *La sangría se debe hacer con uno o más espacios, pero nunca con tabulaciones*. - -Puedes anidar secuencias y asignaciones a tu gusto: - -.. code-block:: yaml - - 'Chapter 1': - - Introduction - - Event Types - 'Chapter 2': - - Introduction - - Helpers - -*YAML* también puede utilizar estilos de flujo para colecciones, utilizando indicadores explícitos en lugar de sangría para denotar el alcance. - -Puedes escribir una secuencia como una lista separada por comas entre corchetes (``[]``): - -.. code-block:: yaml - - [PHP, Perl, Python] - -Una asignación se puede escribir como una lista separada por comas de clave/valores dentro de llaves (``{}``): - -.. code-block:: yaml - - { PHP: 5.2, MySQL: 5.1, Apache: 2.2.20 } - -Puedes mezclar y combinar estilos para conseguir mejor legibilidad: - -.. code-block:: yaml - - 'Chapter 1': [Introduction, Event Types] - 'Chapter 2': [Introduction, Helpers] - -.. code-block:: yaml - - "symfony 1.0": { PHP: 5.0, Propel: 1.2 } - "symfony 1.2": { PHP: 5.2, Propel: 1.3 } - -Comentarios -~~~~~~~~~~~ - -En *YAML* puedes añadir comentarios anteponiendo una almohadilla (``#``): - -.. code-block:: yaml - - # Comentario en una línea - "symfony 1.0": { PHP: 5.0, Propel: 1.2 } # Comentario al final de una línea - "symfony 1.2": { PHP: 5.2, Propel: 1.3 } - -.. note:: - - Los comentarios simplemente son ignorados por el analizador de *YAML* y no es necesario sangrarlos de acuerdo al nivel de anidamiento actual de una colección. - -.. _YAML: http://yaml.org/ -.. _`Packagist`: https://packagist.org/packages/symfony/yaml diff --git a/_sources/components/yaml/index.txt b/_sources/components/yaml/index.txt deleted file mode 100644 index fecaad9..0000000 --- a/_sources/components/yaml/index.txt +++ /dev/null @@ -1,8 +0,0 @@ -*Yaml* -====== - -.. toctree:: - :maxdepth: 2 - - introduction - yaml_format diff --git a/_sources/components/yaml/introduction.txt b/_sources/components/yaml/introduction.txt deleted file mode 100644 index 4eba55e..0000000 --- a/_sources/components/yaml/introduction.txt +++ /dev/null @@ -1,178 +0,0 @@ -.. index:: - single: Yaml - single: Componentes; Yaml - -El componente *YAML* -==================== - - El componente *YAML* carga y vuelca archivos *YAML*. - -¿Qué es entonces? ------------------ - -El componente *YAML* de *Symfony2* analiza cadenas *YAML* para convertirlas en arreglos *PHP*. -También es capaz de convertir arreglos *PHP* a cadenas *YAML*. - -`YAML`_, *YAML no es un lenguaje de marcado*, es un estándar para la serialización de datos amigable con los humanos para todos los lenguajes de programación. YAML es un formato ideal para tus archivos de configuración. Los archivos *YAML* son tan expresivos como los archivos *XML* y tan fáciles de leer como los archivos ``INI``. - -El componente *YAML* de *Symfony2* implementa la versión 1.2 de la -especificación *YAML*. - - -.. tip:: - - Aprende más sobre el componente *Yaml* en el artículo :doc:`/components/yaml/yaml_format`. - -Instalando ----------- - -Puedes instalar el componente de varias maneras diferentes: - -* Usando el repositorio *Git* oficial (https://github.com/symfony/Yaml); -* :doc:`Instalándolo vía Composer ` (``symfony/yaml`` en `Packagist`_). - -¿Por qué? ---------- - -Rapidez -~~~~~~~ - -Uno de los objetivos del componente *YAML* de *Symfony* es encontrar el balance adecuado entre características y velocidad. Es compatible con las características necesarias para manipular archivos de configuración. - -Analizador real -~~~~~~~~~~~~~~~ - -Este exporta un analizador real y es capaz de analizar un gran subconjunto de especificaciones *YAML*, para todas tus necesidades de configuración. También significa que el analizador es bastante robusto, fácil de entender, y lo suficientemente simple como para ampliarlo. - -Borrando mensajes de error -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada vez que tienes un problema de sintaxis con tus archivos *YAML*, la biblioteca genera un útil mensaje de ayuda con el nombre y número de línea donde está el problema. Lo cual facilita mucho la depuración. - -Compatibilidad con el volcado -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -También es capaz de volcar arreglos de *PHP* a *YAML* con apoyo a objetos, y el nivel de configuración en línea es bastante bueno. - -Compatibilidad de tipos -~~~~~~~~~~~~~~~~~~~~~~~ - -Es compatible con la mayoría de los tipos *YAML* integrados como fechas, números enteros, octales, booleanos, y mucho más... - -Completa compatibilidad con la fusión de claves -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Completa compatibilidad para referencias, alias y completa fusión de claves. No repitas tú mismo haciendo referencia a segmentos de configuración comunes. - -Utilizando el componente *YAML* de *Symfony2* ---------------------------------------------- - -El componente *YAML* de *Symfony2* es muy simple y consiste de dos clases principales: -una analiza cadenas *YAML* (:class:`Symfony\\Component\\Yaml\\Parser`), y la otra -vuelca un array *PHP* en una cadena *YAML* (:class:`Symfony\\Component\\Yaml\\Dumper`). - -Además de estas dos clases, la clase :class:`Symfony\\Component\\Yaml\\Yaml` actúa como un delgado contenedor que simplifica los usos más comunes. - -Leyendo archivos *YAML* -~~~~~~~~~~~~~~~~~~~~~~~ - -El método :method:`Symfony\\Component\\Yaml\\Parser::parse` analiza una cadena *YAML* y la convierte en un arreglo *PHP*: - -.. code-block:: php - - use Symfony\Component\Yaml\Parser; - - $yaml = new Parser(); - - $value = $yaml->parse(file_get_contents('/ruta/a/file.yml')); - -Si ocurre un error durante el análisis, el analizador lanza una excepción :class:`Symfony\\Component\\Yaml\\Exception\\ParseException` que indica el tipo de error y la línea en la cadena *YAML* original en la que ocurrió el error: - -.. code-block:: php - - use Symfony\Component\Yaml\Exception\ParseException; - - try { - $value = $yaml->parse(file_get_contents('/ruta/a/file.yml')); - } catch (ParseException $e) { - printf("Unable to parse the YAML string: %s", $e->getMessage()); - } - -.. tip:: - - Debido a que el analizador es reentrante, puedes utilizar el mismo objeto analizador para cargar diferentes cadenas *YAML*. - -Al cargar un archivo *YAML*, a veces es mejor usar el contenedor del método :method:`Symfony\\Component\\Yaml\\Yaml::parse`: - -.. code-block:: php - - use Symfony\Component\Yaml\Yaml; - - $yaml = Yaml::parse('/ruta/a/file.yml'); - -El método estático :method:`Symfony\\Component\\Yaml\\Yaml::parse` toma una cadena *YAML* o un archivo que contiene *YAML*. Internamente, llama al método :method:`Symfony\\Component\\Yaml\\Parser::parse`, pero mejora el error si algo sale mal, añadiendo el nombre del archivo al mensaje. - -Escribiendo archivos *YAML* -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El método :method:`Symfony\\Component\\Yaml\\Dumper::dump` vuelca cualquier arreglo *PHP* en su representación *YAML*: - -.. code-block:: php - - use Symfony\Component\Yaml\Dumper; - - $array = array( - 'foo' => 'bar', - 'bar' => array('foo' => 'bar', 'bar' => 'baz'), - ); - - $dumper = new Dumper(); - - $yaml = $dumper->dump($array); - - file_put_contents('/ruta/a/file.yml', $yaml); - -.. note:: - - Por supuesto, el vertedor *YAML* de *Symfony2* no es capaz de volcar recursos. Por otra parte, aun y cuando el vertedor es capaz de volcar objetos *PHP*, se considera como una función **No** admitida. - -Si ocurre un error durante el volcado, el analizador lanza una excepción :class:`Symfony\\Component\\Yaml\\Exception\\DumpException`. - -Si sólo necesitas volcar un arreglo, puedes utilizar el método estático :method:`Symfony\\Component\\Yaml\\Yaml::dump`: - -.. code-block:: php - - use Symfony\Component\Yaml\Yaml; - - $yaml = Yaml::dump($array, $inline); - -El formato *YAML* admite dos tipos de representación de arreglos, la ampliada, y en línea. Por omisión, el vertedor utiliza la representación en línea: - -.. code-block:: yaml - - { foo: bar, bar: { foo: bar, bar: baz } } - -El segundo argumento del método :method:`Symfony\\Component\\Yaml\\Dumper::dump` personaliza el nivel en el cual el resultado cambia de la representación ampliada a en línea: - -.. code-block:: php - - echo $dumper->dump($array, 1); - -.. code-block:: yaml - - foo: bar - bar: { foo: bar, bar: baz } - -.. code-block:: php - - echo $dumper->dump($array, 2); - -.. code-block:: yaml - - foo: bar - bar: - foo: bar - bar: baz - -.. _YAML: http://yaml.org/ -.. _`Packagist`: https://packagist.org/packages/symfony/yaml diff --git a/_sources/components/yaml/yaml_format.txt b/_sources/components/yaml/yaml_format.txt deleted file mode 100644 index e6f014a..0000000 --- a/_sources/components/yaml/yaml_format.txt +++ /dev/null @@ -1,251 +0,0 @@ -.. index:: - single: Yaml; Formato YAML - -El formato *YAML* -================= - -El sitio *web* de `YAML`_ dice que es «un estándar para la serialización de datos humanamente legibles para todos los lenguajes de programación». - -Si bien el formato *YAML* puede describir complejas estructuras de datos anidadas, este capítulo sólo describe el conjunto mínimo de características necesarias para usar *YAML* como un formato de archivo de configuración. - -*YAML* es un lenguaje simple que describe datos. Como *PHP*, que tiene una sintaxis para tipos simples, como cadenas, booleanos, números en punto flotante, o enteros. Pero a diferencia de *PHP*, este distingue entre arreglos (secuencias) y ``hashes`` (asignaciones). - -Escalares ---------- - -La sintaxis para escalares es similar a la sintaxis de *PHP*. - -Cadenas -~~~~~~~ - -.. code-block:: yaml - - Una cadena en YAML - -.. code-block:: yaml - - 'Una cadena entre comillas simples en YAML' - -.. tip:: - - En una cadena entre comillas simples, una comilla simple ``'`` debe ser doble: - - .. code-block:: yaml - - 'Una comilla simple '' en una cadena entre comillas simples' - -.. code-block:: yaml - - "Una cadena entre comillas dobles en YAML\n" - -Los estilos de citado son útiles cuando una cadena empieza o termina con uno o más espacios relevantes. - -.. tip:: - - El estilo entre comillas dobles proporciona una manera de expresar cadenas arbitrarias, utilizando secuencias de escape ``\``. Es muy útil cuando se necesita incrustar un ``\n`` o un carácter Unicode en una cadena. - -Cuando una cadena contiene saltos de línea, puedes usar el estilo literal, indicado por la línea vertical (``|``), para indicar que la cadena abarcará varias líneas. En literales, se conservan los saltos de línea: - -.. code-block:: yaml - - | - \/ /| |\/| | - / / | | | |__ - -Alternativamente, puedes escribir cadenas con el estilo de plegado, denotado por ``>``, en el cual cada salto de línea es sustituido por un espacio: - -.. code-block:: yaml - - > - Esta es una oración muy larga - que se extiende por varias líneas en el YAML - pero que se reproduce como una cadena - sin retornos de carro. - -.. note:: - - Observa los dos espacios antes de cada línea en los ejemplos anteriores. Ellos no aparecen en las cadenas *PHP* resultantes. - -Números -~~~~~~~ - -.. code-block:: yaml - - # un entero - 12 - -.. code-block:: yaml - - # un octal - 014 - -.. code-block:: yaml - - # un hexadecimal - 0xC - -.. code-block:: yaml - - # un float - 13.4 - -.. code-block:: yaml - - # un número exponencial - 1.2e+34 - -.. code-block:: yaml - - # infinito - .inf - -Nulos -~~~~~ - -Los nulos en *YAML* se pueden expresar con ``null`` o ``~``. - -Booleanos -~~~~~~~~~ - -Los booleanos en *YAML* se expresan con ``true`` y ``false``. - -Fechas -~~~~~~ - -*YAML* utiliza la norma ISO-8601 para expresar fechas: - -.. code-block:: yaml - - 2001-12-14t21:59:43.10-05:00 - -.. code-block:: yaml - - # fecha simple - 2002-12-14 - -Colecciones -~~~~~~~~~~~ - -Rara vez se utiliza un archivo *YAML* para describir un simple escalar. La mayoría de las veces, describe una colección. Una colección puede ser una secuencia o una asignación de elementos. Ambas, secuencias y asignaciones se convierten en arreglos *PHP*. - -Las secuencias usan un guión seguido por un espacio: - -.. code-block:: yaml - - - PHP - - Perl - - Python - -El archivo *YAML* anterior es equivalente al siguiente código *PHP*: - -.. code-block:: php - - array('PHP', 'Perl', 'Python'); - -La asignación utiliza dos puntos seguidos por un espacio (``:`` ) para marcar cada par clave/valor: - -.. code-block:: yaml - - PHP: 5.2 - MySQL: 5.1 - Apache: 2.2.20 - -el cual es equivalente a este código *PHP*: - -.. code-block:: php - - array('PHP' => 5.2, 'MySQL' => 5.1, 'Apache' => '2.2.20'); - -.. note:: - - En una asignación, una clave puede ser cualquier escalar válido. - -El número de espacios entre los dos puntos y el valor no importa: - -.. code-block:: yaml - - PHP: 5.2 - MySQL: 5.1 - Apache: 2.2.20 - -*YAML* utiliza sangría con uno o más espacios para describir colecciones anidadas: - -.. code-block:: yaml - - "symfony 1.0": - PHP: 5.0 - Propel: 1.2 - "symfony 1.2": - PHP: 5.2 - Propel: 1.3 - -El *YAML* anterior es equivalente al siguiente código *PHP*: - -.. code-block:: php - - array( - 'symfony 1.0' => array( - 'PHP' => 5.0, - 'Propel' => 1.2, - ), - 'symfony 1.2' => array( - 'PHP' => 5.2, - 'Propel' => 1.3, - ), - ); - -Hay una cosa importante que tienes que recordar cuando utilices sangría en un archivo *YAML*: *La sangría se debe hacer con uno o más espacios, pero nunca con tabulaciones*. - -Puedes anidar secuencias y asignaciones a tu gusto: - -.. code-block:: yaml - - 'Chapter 1': - - Introduction - - Event Types - 'Chapter 2': - - Introduction - - Helpers - -*YAML* también puede utilizar estilos de flujo para colecciones, utilizando indicadores explícitos en lugar de sangría para denotar el alcance. - -Puedes escribir una secuencia como una lista separada por comas entre corchetes (``[]``): - -.. code-block:: yaml - - [PHP, Perl, Python] - -Una asignación se puede escribir como una lista separada por comas de clave/valores dentro de llaves (``{}``): - -.. code-block:: yaml - - { PHP: 5.2, MySQL: 5.1, Apache: 2.2.20 } - -Puedes mezclar y combinar estilos para conseguir mejor legibilidad: - -.. code-block:: yaml - - 'Chapter 1': [Introduction, Event Types] - 'Chapter 2': [Introduction, Helpers] - -.. code-block:: yaml - - "symfony 1.0": { PHP: 5.0, Propel: 1.2 } - "symfony 1.2": { PHP: 5.2, Propel: 1.3 } - -Comentarios ------------ - -En *YAML* puedes añadir comentarios anteponiendo una almohadilla (``#``): - -.. code-block:: yaml - - # Comentario en una línea - "symfony 1.0": { PHP: 5.0, Propel: 1.2 } # Comentario al final de una línea - "symfony 1.2": { PHP: 5.2, Propel: 1.3 } - -.. note:: - - Los comentarios simplemente son ignorados por el analizador de *YAML* y no es necesario sangrarlos de acuerdo al nivel de anidamiento actual de una colección. - -.. _YAML: http://yaml.org/ diff --git a/_sources/contributing/code/bugs.txt b/_sources/contributing/code/bugs.txt deleted file mode 100644 index 88131f6..0000000 --- a/_sources/contributing/code/bugs.txt +++ /dev/null @@ -1,30 +0,0 @@ -Informando de errores -===================== - -Cada vez que encuentres un error en *Symfony2*, te rogamos que lo informes. Nos ayuda a hacer un mejor *Symfony2*. - -.. caution:: - - Si piensas que has encontrado un problema de seguridad, por favor, en su lugar, usa el :doc:`procedimiento ` especial. - -Antes de enviar un error: - -* Revisa cuidadosamente la `documentación`_ oficial para ver si no estás usando incorrectamente la plataforma; - -* Pide ayuda en la `lista de correo de usuarios`_, el `foro`_, o en el `canal IRC`_ *#symfony* si no estás seguro de que el problema realmente sea un error. - -Si tu problema definitivamente se ve como un error, informa el fallo usando el `tracker`_ oficial siguiendo algunas reglas básicas: - -* Utiliza el campo título para describir claramente el problema; - -* Describe los pasos necesarios para reproducir el error con breves ejemplos de código (proporcionando una prueba unitaria que ilustre mejor el error); - -* Indica lo más detalladamente posible tu entorno (sistema operativo, versión de *PHP*, versión de *Symfony*, extensiones habilitadas, ...); - -* *(opcional)* Adjunta un :doc:`parche `. - -.. _`documentación`: http://symfony.com/doc/2.0/ -.. _`lista de correo de usuarios`: http://groups.google.com/group/symfony-users -.. _`foro`: http://forum.symfony-project.org/ -.. _`canal IRC`: irc://irc.freenode.net/symfony -.. _`tracker`: https://github.com/symfony/symfony/issues diff --git a/_sources/contributing/code/conventions.txt b/_sources/contributing/code/conventions.txt deleted file mode 100644 index deb7aa1..0000000 --- a/_sources/contributing/code/conventions.txt +++ /dev/null @@ -1,90 +0,0 @@ -Convenciones -============ - -El documento :doc:`estándares ` describe las normas de codificación de los proyectos *Symfony2* y los paquetes internos de terceros. Este documento describe los estándares de codificación y convenciones utilizadas en la plataforma básica para que sea más consistente y predecible. Las puedes seguir en tu propio código, pero no es necesario. - -Nombres de método ------------------ - -Cuando un objeto tiene una relación «principal» con muchas «cosas» relacionadas (objetos, parámetros, ...), los nombres de los métodos están normalizados: - - * ``get()`` - * ``set()`` - * ``has()`` - * ``all()`` - * ``replace()`` - * ``remove()`` - * ``clear()`` - * ``isEmpty()`` - * ``add()`` - * ``register()`` - * ``count()`` - * ``keys()`` - -El uso de estos métodos sólo se permite cuando es evidente que existe una relación principal: - -* un ``CookieJar`` tiene muchos objetos :dfn:`Cookie`; - -* un ``Contenedor`` de servicios tiene muchos servicios y muchos parámetros (dado que los servicios son la relación principal, utilizamos la convención de nomenclatura para esta relación); - -* una Consola ``Input`` tiene muchos argumentos y muchas opciones. No hay una relación «principal», por lo tanto no aplica la convención de nomenclatura. - -Para muchas relaciones cuando la convención no aplica, en su lugar se deben utilizar los siguientes métodos (donde ``XXX`` es el nombre de aquello relacionado): - -+----------------+-------------------+ -| Relación | Otras relaciones | -| principal | | -+================+===================+ -| ``get()`` | ``getXXX()`` | -+----------------+-------------------+ -| ``set()`` | ``setXXX()`` | -+----------------+-------------------+ -| n/a | ``replaceXXX()`` | -+----------------+-------------------+ -| ``has()`` | ``hasXXX()`` | -+----------------+-------------------+ -| ``all()`` | ``getXXXs()`` | -+----------------+-------------------+ -| ``replace()`` | ``setXXXs()`` | -+----------------+-------------------+ -| ``remove()`` | ``removeXXX()`` | -+----------------+-------------------+ -| ``clear()`` | ``clearXXX()`` | -+----------------+-------------------+ -| ``isEmpty()`` | ``isEmptyXXX()`` | -+----------------+-------------------+ -| ``add()`` | ``addXXX()`` | -+----------------+-------------------+ -| ``register()`` | ``registerXXX()`` | -+----------------+-------------------+ -| ``count()`` | ``countXXX()`` | -+----------------+-------------------+ -| ``keys()`` | n/a | -+----------------+-------------------+ - -.. note:: - - Si bien «setXXX» y «replaceXXX» son muy similares, hay una notable diferencia: «setXXX» puede sustituir o agregar nuevos elementos a la relación. - «replaceXXX», por otro lado, no puede añadir nuevos elementos. Si se pasa una clave no reconocida a «replaceXXX», lanzará una excepción. - -.. _contributing-code-conventions-deprecations: - -Depreciaciones --------------- - -De vez en cuando, algunas clases y/o métodos son depreciados en la plataforma; esto sucede cuando la implementación de una característica no se puede cambiar debido a cuestiones de compatibilidad hacia atrás, pero todavía queremos proponer una mejor «alternativa». En este caso, la antigua implementación sencillamente se puede **depreciar**. - -Una característica es marcada como depreciada añadiendo una anotación ``@deprecated`` de *phpdoc* a las clases, métodos, propiedades, ... pertinentes:: - - /** - * @deprecated Depreciada a partir de la versión 2.X, será removida en 2.Y. En su lugar usa XXX. - */ - -El mensaje de la depreciación debería indicar la versión cuándo el método/clase fue depreciado, la versión cuándo será removido, y siempre que sea posible, cómo sustituir la característica. - -Un error ``E_USER_DEPRECATED`` de *PHP* también será provocado para ayudar a las personas con la migración empezando una o dos versiones menores antes de la versión donde la característica será removida (dependiendo de lo delicado de la extracción):: - - trigger_error( - 'XXX() está depreciada a partir de la versión 2.X, será removida en 2.Y. En su lugar usa XXX.', - E_USER_DEPRECATED - ); diff --git a/_sources/contributing/code/git.txt b/_sources/contributing/code/git.txt deleted file mode 100644 index 7bf8e1c..0000000 --- a/_sources/contributing/code/git.txt +++ /dev/null @@ -1,42 +0,0 @@ -Git -=== - -This document explains some conventions and specificities in the way we manage -the Symfony code with Git. - -Pull Requests -------------- - -Whenever a pull request is merged, all the information contained in the pull -request (including comments) is saved in the repository. - -You can easily spot pull request merges as the commit message always follows -this pattern: - -.. block: text - - merged branch USER_NAME/BRANCH_NAME (PR #1111) - -The PR reference allows you to have a look at the original pull request on -Github: https://github.com/symfony/symfony/pull/1111. But all the information -you can get on Github is also available from the repository itself. - -The merge commit message contains the original message from the author of the -changes. Often, this can help understand what the changes were about and the -reasoning behind the changes. - -Moreover, the full discussion that might have occurred back then is also -stored as a Git note (before March 22 2013, the discussion was part of the -main merge commit message). To get access to these notes, add this line to -your ``.git/config`` file: - -.. block: text - - fetch = +refs/notes/*:refs/notes/* - -After a fetch, getting the Github discussion for a commit is then a matter of -adding ``--show-notes=github-comments`` to the ``git show`` command: - -.. block: text - - git show HEAD --show-notes=github-comments diff --git a/_sources/contributing/code/index.txt b/_sources/contributing/code/index.txt deleted file mode 100644 index a6a4406..0000000 --- a/_sources/contributing/code/index.txt +++ /dev/null @@ -1,14 +0,0 @@ -Aportando código -================ - -.. toctree:: - :maxdepth: 2 - - bugs - patches - security - tests - standards - conventions - git - license diff --git a/_sources/contributing/code/license.txt b/_sources/contributing/code/license.txt deleted file mode 100644 index 9ad850f..0000000 --- a/_sources/contributing/code/license.txt +++ /dev/null @@ -1,40 +0,0 @@ -Licencia *Symfony2* -=================== - -*Symfony2* se libera bajo la licencia *MIT*. - -De acuerdo con `Wikipedia`_: - - "Es una licencia permisiva, lo cual significa que permite la - reutilización dentro de software propietario a condición de que la - licencia se distribuya con el software. Esta también es compatible - con la licencia *GPL*, lo cual significa que la licencia *GPL* - permite la combinación y redistribución, con el software que utiliza - la licencia *MIT*". - -La Licencia ------------ - -Copyright (c) 2004-2013 Fabien Potencier -Copyright (c) 2011-2013 Nacho Pacheco - -Se autoriza, de forma gratuita, a cualquier persona que obtenga una -copia de este software y archivos de documentación asociados (el -"Software"), a trabajar con el Software sin restricción, incluyendo sin -limitación, los derechos para usar, copiar, modificar, fusionar, -publicar, distribuir, sublicenciar y/o vender copias del Software, y a -permitir a las personas a quienes se proporcione el Software a hacerlo, -sujeto a las siguientes condiciones: - -El aviso de copyright anterior y este aviso de autorización se incluirán -en todas las copias o partes sustanciales del Software. - -EL SOFTWARE SE ENTREGA "TAL CUAL", SIN GARANTÍA DE NINGÚN TIPO, EXPRESA -O IMPLÍCITA, INCLUYENDO PERO NO LIMITADO A LAS GARANTÍAS DE -COMERCIALIZACIÓN, ADECUACIÓN A UN PROPÓSITO PARTICULAR Y NO -INFRACCIÓN. EN NINGÚN CASO LOS AUTORES O TITULARES DEL COPYRIGHT SERÁN -RESPONSABLES DE NINGUNA RECLAMACIÓN, DAÑO U OTRA RESPONSABILIDAD, YA SEA -EN UNA ACCIÓN DE CONTRATO, AGRAVIO O DE OTRA, DERIVADA DE, O EN RELACIÓN -CON LA UTILIZACIÓN DEL SOFTWARE U OTRAS OPERACIONES EN EL SOFTWARE. - -.. _Wikipedia: http://en.wikipedia.org/wiki/MIT_License diff --git a/_sources/contributing/code/patches.txt b/_sources/contributing/code/patches.txt deleted file mode 100644 index 7ca2557..0000000 --- a/_sources/contributing/code/patches.txt +++ /dev/null @@ -1,333 +0,0 @@ -Enviando un parche -================== - -Los parches son la mejor manera de proporcionar una corrección de error o de proponer mejoras a *Symfony2*. - -Paso 1: Configura tu entorno ----------------------------- - -Instalando el software necesario -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de trabajar en *Symfony2*, configura un entorno amigable con el siguiente software: - -* *Git*; -* *PHP* version 5.3.3 o más reciente; -* *PHPUnit* 3.6.4 o más reciente. - -Configura *Git* -~~~~~~~~~~~~~~~ - -Configura la información de usuario con tu nombre real y una dirección de correo electrónico operativa: - -.. code-block:: bash - - $ git config --global user.name "Tu nombre" - $ git config --global user.email tu@ejemplo.com - -.. tip:: - - Si eres nuevo en *Git*, es muy recomendable que leas el excelente libro `ProGit`_, que además es gratis. - -.. tip:: - - Si tu *IDE* crea los archivos de configuración dentro del directorio del proyecto, puedes usar el archivo global ``.gitignore`` (para todos los proyectos) o el archivo ``.git/info/exclude`` (por proyecto) para descartarlos. Consulta la `documentación de Github`_. - -.. tip:: - - Usuarios de Windows: al instalar *Git*, se te preguntará qué hacer con los finales de línea y te sugiere reemplazar todos los ``LF`` por ``CRLF``. ¡Esta es la opción incorrecta si deseas contribuir con *Symfony*! Seleccionar el método tal cual es tu mejor opción, puesto que *Git* convertirá tus saltos de línea a los del repositorio. Si ya has instalado *Git*, puedes comprobar el valor de esta opción escribiendo: - - .. code-block:: bash - - $ git config core.autocrlf - - Esto devolverá o bien ``«false»``, ``«input»`` o ``«true»``, ``«true»`` y ``«false»`` son valores incorrectos. Para fijarlo a otro valor, escribe: - - .. code-block:: bash - - $ git config --global core.autocrlf input - - Sustituye ``--global`` por ``--local`` si lo quieres fijar únicamente para el repositorio activo. - -Consigue el código fuente de *Symfony* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Obtén el código fuente de *Symfony2*: - -* Crea una cuenta `GitHub`_ e ingresa; - -* Consigue el `repositorio Symfony2`_ (haz clic en el botón «Fork»); - -* Después de concluida la «acción de bifurcar el núcleo», clona tu bifurcación a nivel local (esto creará un directorio `symfony`): - -.. code-block:: bash - - $ git clone git@github.com:NOMBREUSUARIO/symfony.git - -* Añade el repositorio como remoto de escritura: - -.. code-block:: bash - - $ cd symfony - $ git remote add upstream git://github.com/symfony/symfony.git - -Comprueba que pasa la batería de pruebas actual -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora que *Symfony2* está instalado, comprueba que todas las pruebas unitarias pasan en tu entorno como se explica en el :doc:`documento ` dedicado. - -Paso 2: Trabaja en tu parche ----------------------------- - -La Licencia -~~~~~~~~~~~ - -Antes de empezar, debes saber que todos los parches que envíes se deben liberar bajo la *Licencia MIT*, a menos que explícitamente lo especifiques en tus confirmaciones de cambios. - -Eligiendo la rama adecuada -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Antes de trabajar en tu parche, debes determinar en cual rama necesitas trabajar. La rama debe estar basada en la rama ``master`` si deseas agregar una nueva característica. Pero, si deseas corregir un fallo, utiliza la versión más antigua, pero aún mantenida de *Symfony* donde ocurre el fallo (como ``2.0``). - -.. note:: - - Todas las correcciones de fallos se fusionarán en las ramas de mantenimiento y además se fusionaran regularmente en las ramas más recientes. Por ejemplo, si envías un parche para la rama ``2.0``, el parche también será aplicado por el equipo del núcleo a la rama ``master``. - -Crea una rama para ese asunto -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada vez que desees trabajar en un parche para un fallo o una mejora, crea una rama para ese asunto: - -.. code-block:: bash - - $ git checkout -b NOMBRE_RAMA master - -O, si deseas proporcionar una corrección de fallo para la rama 2.0, en primer lugar, actualiza tu copia local a la rama `2.0`: - -.. code-block:: bash - - $ git checkout -t origin/2.0 - -Luego, crea una nueva rama de la rama 2.0 para trabajar en la corrección del fallo: - -.. code-block:: bash - - $ git checkout -b NOMBRE_RAMA 2.0 - -.. tip:: - - Utiliza un nombre descriptivo para tu rama (``ticket_XXX`` donde ``XXX`` es el número del boleto, esta es una buena convención para la corrección de fallos). - -La orden ``checkout`` anterior, automáticamente cambia el código a la rama recién creada (para ver la rama en que estás trabajando utiliza ``git branch``). - -Trabaja en tu parche -~~~~~~~~~~~~~~~~~~~~ - -Trabaja en el código tanto como quieras y confirma tus cambios tanto como desees; pero ten en cuenta lo siguiente: - -* Lee sobre las :doc:`convenciones ` de *Symfony* y sigue los :doc:`estándares ` de codificación (usa `git diff --check` para verificar los espacios finales -- también lee el consejo de abajo); - -* Añade pruebas unitarias para probar que el fallo se corrigió o que la nueva característica realmente funciona; - -* Esfuérzate para no romper la compatibilidad hacia atrás (si debes hacerlo, trata de proporcionar una capa de compatibilidad para apoyar la manera antigua) --- los parches que rompen la compatibilidad con versiones anteriores tienen menos posibilidades de que se fusionen; - -* Haz confirmaciones atómicas y separadas lógicamente (usa el poder de ``git rebase`` para tener un historial limpio y lógico); - -* Aplana confirmaciones de cambios irrelevantes que estén a punto de comprometer los estándares de codificación o corrigen fallos en tu propio código; - -* No corrijas los estándares de codificación en algún código existente, ya que dificulta la revisión del código; - -* Escribe buenos mensajes de confirmación (ve el consejo más adelante). - -.. tip:: - - Puedes comprobar los estándares de codificación de tu parche ejecutando el siguiente `archivo `_ que puedes conseguir `aquí `_: - - .. code-block:: bash - - $ cd /ruta/a/symfony/src - $ php symfony-cs-fixer.phar fix . Symfony20Finder - -.. tip:: - - Un buen mensaje de confirmación de cambios sustancial está compuesto por un resumen (en la primera línea), seguido opcionalmente por una línea en blanco y una descripción más detallada. El resumen debe comenzar con el componente en el que estás trabajando entre corchetes (``[DependencyInjection]``, ``[FrameworkBundle]``, ...). Utiliza un verbo (``fixed...``, ``added...``, ...) para iniciar el resumen y no agregues un punto al final. - -Prepara tu parche para enviarlo -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cuando tu parche no se trata de una corrección de fallo (cuando agregas una nueva característica o cambias una existente, por ejemplo), además debes incluir lo siguiente: - -* Una explicación de los cambios en el/los archivo(s) ``CHANGELOG`` correspondiente(s) (cuando sea pertinente debes usar los prefijos ``[BC BREAK]`` o ``[DEPRECATION]``); - -* Una explicación sobre cómo actualizar una aplicación existente en el archivo ``UPGRADE`` correspondiente si los cambios rompen la compatibilidad hacia atrás o si depreciaste algo que a la larga rompa la compatibilidad hacia atrás. - -Paso 3: Envía tu parche ------------------------ - -Siempre que sientas que tu parche esté listo para su presentación, sigue los siguientes pasos. - -Reorganiza tu parche -~~~~~~~~~~~~~~~~~~~~ - -Antes de presentar tu revisión, actualiza tu rama (es necesario si te toma cierto tiempo terminar los cambios): - -.. code-block:: bash - - $ git checkout master - $ git fetch upstream - $ git merge upstream/master - $ git checkout NOMBRE_RAMA - $ git rebase master - -.. tip:: - - Sustituye ``master`` con ``2.0`` si estás trabajando en una corrección de fallo - -Al ejecutar la orden ``rebase``, posiblemente tengas que arreglar conflictos de fusión. -``git status`` te mostrará los archivos *sin fusionar*. Resuelve todos los conflictos, y luego continua el rebase: - -.. code-block:: bash - - $ git add ... # Añade archivos resueltos - $ git rebase --continue - -Comprueba que todas las pruebas todavía pasan y envía a tu rama remota: - -.. code-block:: bash - - $ git push origin NOMBRE_RAMA - -Envía una solicitud de atracción -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ahora puedes hacer una solicitud de atracción en el repositorio ``symfony/synfony`` de *Github*. - -.. tip:: - - Ten cuidado de marcar tu solicitud de atracción para ``symfony:2.1`` si deseas que el equipo del núcleo atraiga una corrección de fallo basada en la rama 2.1. - -Para facilitar el trabajo del equipo del núcleo, siempre incluye los componentes modificados en el mensaje (en inglés) de tu solicitud de atracción, como este: - -.. code-block:: text - - [Yaml] fixed something - [Form] [Validator] [FrameworkBundle] added something - -La descripción de la solicitud de atracción debe incluir la siguiente lista de comprobación en la parte superior para garantizar que las aportaciones se pueden revisar sin la necesidad infinitos ciclos de retroalimentación y que tus aportaciones se pueden incluir a *Symfony2* tan rápidamente como sea posible: - -.. code-block:: text - - | Q | A - | ------------- | --- - | Bug fix? | [yes|no] - | New feature? | [yes|no] - | BC breaks? | [yes|no] - | Deprecations? | [yes|no] - | Tests pass? | [yes|no] - | Fixed tickets | [lista separada por comas de las entradas corregidas por el PR] - | License | MIT - | Doc PR | [Referencia a la documentación del PR, si la hay] - -Un envío de ejemplo ahora se ve de la siguiente manera: - -.. code-block:: text - - | Q | A - | ------------- | --- - | Bug fix? | no - | New feature? | no - | BC breaks? | no - | Deprecations? | no - | Tests pass? | yes - | Fixed tickets | #12, #43 - | License | MIT - | Doc PR | symfony/symfony-docs#123 - -Debes incluir la tabla completa (**no** quites líneas que pienses que no son relevantes). Para errores tipográficos sencillos, cambios menores en la *PHPDocs*, o cambios en los archivos de traducción, usa la versión corta de la lista de comprobación: - -.. code-block:: text - - | Q | A - | ------------- | --- - | Fixed tickets | [lista separada por comas de las entradas corregidas por el PR] - | License | MIT - -Algunas respuestas a cuestiones que provocan algunos requisitos adicionales: - - * Si contestaste ``yes`` a ``«Bug fix?»``, comprueba si el fallo ya está enumerado en los problemas de *Symfony* y proporciona una referencia a la(s) «entrada(s) corregida(s)»; - - * Si contestaste ``yes`` a ``«New feature?»``, debes enviar una solicitud de atracción a la documentación y referirla bajo la sección ``«Doc PR»``; - - * Si contestaste ``yes`` a ``«BC breaks?»``, el parche debe contener actualizaciones a los archivos ``CHANGELOG`` y ``UPGRADE`` pertinentes; - - * Si contestaste ``yes`` a ``«Deprecations?»``, el parche debe contener actualizaciones a los archivos ``CHANGELOG`` y ``UPGRADE`` pertinentes; - - * Si contestaste ``no`` a ``«Tests pass»``, debes añadir un elemento a una lista de pendientes con las acciones que se deben tomar para corregir las pruebas; - - * Si la ``«license»`` no es *MIT*, sólo no envíes la solicitud de atracción puesto que no será aceptada en ningún caso. - -Si no cubres alguno de los requisitos anteriores, crea una lista de pendientes y añade los -elementos pertinentes: - -.. code-block:: text - - - [ ] fix the tests as they have not been updated yet - - [ ] submit changes to the documentation - - [ ] document the BC breaks - -Si el código no está terminado todavía porque no tienes tiempo para acabarlo o porque quieres retroalimentación temprana sobre tu trabajo, añade un elemento a la lista de pendientes: - -.. code-block:: text - - - [ ] finish the code - - [ ] gather feedback my changes - -Mientras tengas elementos en la lista de pendientes, por favor prefija el título de la petición -de atracción con ``«[WIP]»``. - -En la descripción de tu solicitud de atracción, da tantos detalles como sea posible acerca de tus cambios (no dudes en dar ejemplos de código para ilustrar tus puntos). Si tu solicitud de atracción está a punto de añadir una nueva característica o modificar una existente, explica las razones para los cambios. La descripción de la solicitud de atracción ayuda a la revisión del código y sirve como referencia al fusionar el código (la descripción de la solicitud de atracción y todos tus comentarios asociados son parte del mensaje de confirmación de la fusión). - -Además de este ``código`` de la solicitud de atracción, también debes enviar una solicitud de atracción al `repositorio de documentación`_ para actualizar la documentación cuando sea apropiado. - -Revisando tu parche -~~~~~~~~~~~~~~~~~~~ - -Basándote en la retroalimentación sobre tu solicitud de atracción, posiblemente debas volver a trabajar en tu parche. Antes de volver a presentarlo, reorganiza con ``upstream/master`` o ``upstream/2.0``, no lo fusiones; y fuerza el envío al origen: - -.. code-block:: bash - - $ git rebase -f upstream/master - $ git push -f origin NOMBRE_RAMA - -.. note:: - - cuando haces un ``push --force``, **siempre** especifica el nombre de la rama de forma explícita para evitar dañar otras ramas en el repositorio (``--force`` le dice a *Git* que realmente quieres meterte con esas cosas por lo tanto hazlo con mucho cuidado). - -A menudo, los moderadores te pedirán que «aplanes» tus confirmaciones de cambios. Lo cual significa que combines varias confirmaciones de cambios en una sola. Para ello, utiliza la orden ``rebase``: - -.. code-block:: bash - - $ git rebase -i HEAD~3 - $ git push -f origin NOMBRE_RAMA - -Aquí, el número 3 debe ser igual a la cantidad de confirmaciones de cambios en tu rama. Después que escribas esta orden, aparecerá un editor mostrándote la lista de confirmaciones de cambios: - -.. code-block:: text - - pick 1a31be6 first commit - pick 7fc64b4 second commit - pick 7d33018 third commit - -Para revertir todas las confirmaciones de cambios a la primera, elimina la palabra ``«pick»`` antes de la segunda y última confirmación de cambios, y sustitúyela por la palabra ``«squash»`` o simplemente ``«s»``. -Cuando guardes, *Git* iniciará el rebase, y si tiene éxito, te pedirá que -edites el mensaje de confirmación, el cual de manera predefinida es una lista con todos los mensajes de las confirmaciones de cambios. Cuando hayas terminado, ejecuta la orden ``push``. - -.. _`ProGit`: http://git-scm.com/book -.. _`GitHub`: https://github.com/signup/free -.. _`documentación de Github`: https://help.github.com/articles/ignoring-files -.. _`repositorio Symfony2`: https://github.com/symfony/symfony -.. _`lista de correo dev`: http://groups.google.com/group/symfony-devs -.. _travis-ci.org: https://travis-ci.org/ -.. _`icono de estado de travis-ci.org`: http://about.travis-ci.org/docs/user/status-images/ -.. _`guía comenzando con travis-ci.org`: http://about.travis-ci.org/docs/user/getting-started/ -.. _`repositorio de documentación`: https://github.com/symfony/symfony-docs diff --git a/_sources/contributing/code/security.txt b/_sources/contributing/code/security.txt deleted file mode 100644 index df2ddd3..0000000 --- a/_sources/contributing/code/security.txt +++ /dev/null @@ -1,69 +0,0 @@ -Problemas de seguridad -====================== - -Este documento explica cómo maneja el equipo del núcleo de *Symfony* los problemas de seguridad (*Symfony* es el código alojado en el `repositorio Git`_ principal ``symfony/symfony``). - -Informando un problema de seguridad ------------------------------------ - -Si crees que has encontrado un problema de seguridad en *Symfony*, no utilices la lista de correo o el rastreador de fallos y no lo divulgues públicamente. En su lugar, todos los problemas de seguridad se tienen que enviar a **security arroba symfony.com**. Los correos electrónicos enviados a esta dirección se reenvían a la lista de correo privado del equipo del núcleo. - -Proceso de resolución ---------------------- - -Para cada informe, en primer lugar, trata de confirmar la vulnerabilidad. Cuando esté confirmada, el equipo del núcleo trabaja en una solución siguiendo estos pasos: - -1. Envía un reconocimiento al informante; -2. Trabaja en un parche; -3. Obtiene un identificador *CVE* (por ``«Common Vulnerabilities and Exposures»``) de cve.mitre.org; -4. Escribe un anuncio de seguridad para el `blog`_ oficial de *Symfony* sobre la vulnerabilidad. Este comunicado debería contener la siguiente información: - - * Un título que siempre incluya la cadena ``«Security release»``; - * Una descripción de la vulnerabilidad; - * Las versiones afectadas; - * La posible explotación; - * Cómo afecta el parche/actualización/solución del problema a las aplicaciones; - * El identificador *CVE*; - * Créditos. -5. Envia el parche y anuncio al reportero para revisión; -6. Aplica el parche en todas las versiones mantenidas de *Symfony*; -7. Empaca las nuevas versiones para todas las versiones afectadas; -8. Publica un comunicado en el `blog`_ oficial de *Symfony* (también se tiene que añadir a la categoría «`Avisos de seguridad`_»); -9. Actualiza la lista de avisos de seguridad (ve abajo). - -.. note:: - - Versiones que incluyen problemas de seguridad no se tendrían que hacer en sábado o domingo, excepto si la vulnerabilidad se difundió públicamente. - -.. note:: - - Mientras estemos trabajando en un parche, por favor, no reveles el tema públicamente. - -Avisos de seguridad -------------------- - -Estas sección registra en orden cronológico las vulnerabilidades de seguridad que fueron corregidas en las diferentes versiones de *Symfony*, empezando en *Symfony 1.0.0*: - -* Enero 17, 2013: `Emisión de seguridad: Liberados *Symfony* 2.0.22 y 2.1.7 `_ (`CVE-2013-1348 `_ y `CVE-2013-1397 `_) -* Diciembre 20, 2012: `Emisión de seguridad: Symfony 2.0.20 y 2.1.5 `_ (`CVE-2012-6431 `_ y `CVE-2012-6432 `_) -* Noviembre 29, 2012: `Emisión de seguridad: Symfony 2.0.19 y 2.1.4 `_ -* Noviembre 25, 2012: `Emisión de seguridad: Symfony 1.4.20 liberado `_ (`CVE-2012-5574 `_) -* Agosto 28, 2011: `Emisión de seguridad: Symfony 2.0.17 liberado `_ -* Mayo 30, 2012: `Emisión de seguridad: Symfony 1.4.18 liberado `_ (`CVE-2012-2667 `_) -* Febrero 24, 2012: `Emisión de seguridad: Symfony 2.0.11 liberado `_ -* Noviembre 16, 2011: `Emisión de seguridad: Symfony 2.0.6 `_ -* Marzo 21, 2011: `symfony 1.3.10 y 1.4.10: versiones de seguridad `_ -* Junio 29, 2010: `Emisión de seguridad: symfony 1.3.6 y 1.4.6 `_ -* Mayo 31, 2010: `symfony 1.3.5 y 1.4.5 `_ -* Febrero 25, 2010: `Emisión de seguridad: 1.2.12, 1.3.3 y 1.4.3 `_ -* Febrero 13, 2010: `symfony 1.3.2 y 1.4.2 `_ -* Abril 27, 2009: `symfony 1.2.6: Parche de seguridad `_ -* Octubre 3, 2008: `symfony 1.1.4 liberado: Parche de seguridad `_ -* Mayo 14, 2008: `symfony 1.0.16 está fuera `_ -* Abril 01, 2008: `symfony 1.0.13 está fuera `_ -* Marzo 21, 2008: `¡symfony 1.0.12 (finalmente) está fuera! `_ -* Junio 25, 2007: `symfony 1.0.5 publicado (parche de seguridad) `_ - -.. _`repositorio Git`: https://github.com/symfony/symfony -.. _blog: http://symfony.com/blog/ -.. _`Avisos de seguridad`: http://symfony.com/blog/category/security-advisories diff --git a/_sources/contributing/code/standards.txt b/_sources/contributing/code/standards.txt deleted file mode 100644 index 43ed480..0000000 --- a/_sources/contributing/code/standards.txt +++ /dev/null @@ -1,138 +0,0 @@ -Estándares de codificación -========================== - -Cuando aportes código a *Symfony2*, debes seguir sus estándares de codificación. Para no hacer el cuento largo, aquí está la regla de oro: **limítate el código Symfony2 existente**. La mayoría de los Paquetes de código abierto y librerías utilizadas por *Symfony2* también siguen las mismas pautas, y también deberías hacerlo. - -Recuerda que la principal ventaja de los estándares es que cada pieza de código se ve y se siente familiar, no se trata de que esta o esa sea más legible. - -*Symfony* sigue los estándares definidos en los documentos `PSR-0`_, `PSR-1`_ y `PSR-2`_. - -Ya que una imagen ---o algún código--- vale más que mil palabras, he aquí un pequeño ejemplo que contiene la mayoría de las funciones descritas más adelante: - -.. code-block:: html+php - - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - - namespace Acme; - - /** - * Demostración de estándares de la codificación. - */ - class FooBar - { - const SOME_CONST = 42; - - private $fooBar; - - /** - * @param string $dummy Some argument description - */ - public function __construct($dummy) - { - $this->fooBar = $this->transformText($dummy); - } - - /** - * @param string $dummy Some argument description - * @param array $options - * - * @return string|null Transformed input - */ - private function transformText($dummy, $options = array()) - { - $mergedOptions = array_merge( - $options, - array( - 'some_default' => 'values', - 'another_default' => 'more values', - ) - ); - - if (true === $dummy) { - return; - } - if ('string' === $dummy) { - if ('values' === $mergedOptions['some_default']) { - $dummy = substr($dummy, 0, 5); - } else { - $dummy = ucwords($dummy); - } - } else { - throw new \RuntimeException(sprintf('Unrecognized dummy option "%s"', $dummy)); - } - - return $dummy; - } - } - -Estructura ----------- - -* Añade un solo espacio después de cada delimitador coma; - -* Añade un solo espacio alrededor de los operadores (``==``, ``&&``, ...); - -* Añade una coma después de cada elemento del arreglo en un arreglo multilínea, incluso después del último; - -* Añade una línea en blanco antes de las declaraciones ``return``, a menos que el valor devuelto solo sea dentro de un grupo de declaraciones (tal como una declaración ``if``); - -* Usa llaves para indicar la estructura del cuerpo de control, independientemente del número de declaraciones que contenga; - -* Define una clase por archivo --- esto no se aplica a las clases ayudante privadas, de las cuales no se tiene la intención de crear una instancia desde el exterior y por lo tanto no les preocupa la norma `PSR-0`_; - -* Declara las propiedades de clase antes que los métodos; - -* Declare public methods first, then protected ones and finally private ones; - -* Use parentheses when instantiating classes regardless of the number of - arguments the constructor has; - -* Exception message strings should be concatenated using :phpfunction:`sprintf`. - -Convenciones de nomenclatura ----------------------------- - -* Utiliza mayúsculas intercaladas ---sin guiones bajos--- en nombres de variable, función, método o argumentos; - -* Usa guiones bajos para nombres de opción y nombres de parámetro; - -* Utiliza espacios de nombres para todas las clases; - -* Prefija las clases abstractas con ``Abstract``. Por favor, ten en cuenta que algunas de las primeras clases de *Symfony2* no siguen esta convención y no se han rebautizado por razones de compatibilidad hacia atrás. No obstante, todas las nuevas clases abstractas tienen que seguir esta convención de nomenclatura; - -* Sufija las interfaces con ``Interface``; - -* Sufijo las características con ``Trait``; - -* Sufija las excepciones con ``Exception``; - -* Utiliza caracteres alfanuméricos y guiones bajos para los nombres de archivo; - -* No olvides consultar el documento más detallado :doc:`conventions` para más consideraciones de nomenclatura subjetivas. - -Documentación -------------- - -* Añade bloques *PHPDoc* a todas las clases, métodos y funciones; - -* Omite la etiqueta ``@return`` si el método no devuelve nada; - -* Las anotaciones `@package` y `@subpackage` no se utilizan. - -Licencia --------- - -* *Symfony* se distribuye bajo la licencia *MIT*, y el bloque de la licencia tiene que estar presente en la parte superior de todos los archivos *PHP*, antes del espacio de nombres. - -.. _`PSR-0`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md -.. _`PSR-1`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-1-basic-coding-standard.md -.. _`PSR-2`: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md diff --git a/_sources/contributing/code/tests.txt b/_sources/contributing/code/tests.txt deleted file mode 100644 index 3ad455c..0000000 --- a/_sources/contributing/code/tests.txt +++ /dev/null @@ -1,104 +0,0 @@ -Corriendo las pruebas de *Symfony2* -=================================== - -Antes de presentar un :doc:`parche ` para su inclusión, es necesario ejecutar la batería de pruebas de *Symfony2* para comprobar que no ha roto nada. - -*PHPUnit* ---------- - -Para ejecutar la batería de pruebas de *Symfony2*, primero `instala PHPUnit`_ 3.6.4 o superior: - -.. code-block:: bash - - $ pear config-set auto_discover 1 - $ pear install pear.phpunit.de/PHPUnit - -Dependencias (opcional) ------------------------ - -Para ejecutar la batería de pruebas completa, incluyendo las pruebas supeditadas con dependencias externas, *Symfony2* tiene que ser capaz de cargarlas automáticamente. De forma predeterminada, se cargan automáticamente desde :file:`vendor/` en la raíz del directorio principal (consulta la sección :file:`autoload.php.dist`). - -La batería de pruebas necesita las siguientes bibliotecas de terceros: - -* Doctrine -* Swiftmailer -* Twig -* Monolog - -Para instalarlas todas, usa `Composer`_: - -Paso 1: Consigue `Composer`_ - -.. code-block:: bash - - curl -s http://getcomposer.org/installer | php - -Asegúrate de descargar :file:`composer.phar` en el mismo directorio dónde se encuentra el archivo :file:`composer.json`. - -Paso 2: Instala las bibliotecas de terceros - -.. code-block:: bash - - $ php composer.phar --dev install - -.. note:: - - Ten en cuenta que el guión toma algún tiempo para terminar. - -.. note:: - - Si no tienes instalado ``curl``, simplemente puedes descargar manualmente el archivo ``instalador`` de http://getcomposer.org/installer. Coloca ese archivo en tu proyecto y luego ejecuta: - - .. code-block:: bash - - $ php installer - $ php composer.phar --dev install - -Después de la instalación, puedes actualizar los proveedores en cualquier momento con la siguiente orden. - -.. code-block:: bash - - $ php composer.phar --dev update - -Ejecutando ----------- - -En primer lugar, actualiza los proveedores (consulta más arriba). - -A continuación, ejecuta la batería de pruebas desde el directorio raíz de *Symfony2* con la siguiente orden: - -.. code-block:: bash - - $ phpunit - -La salida debe mostrar `OK`. Si no es así, es necesario averiguar qué está pasando y si las pruebas se rompen a causa de tus modificaciones. - -.. tip:: - - Si deseas probar la ruta de un tipo de componente único después de la orden ``phpunit``, por ejemplo: - - .. code-block:: bash - - $ phpunit src/Symfony/Component/Finder/ - -.. tip:: - - Ejecuta la batería de pruebas antes de aplicar las modificaciones para comprobar que funcionan bien en tu configuración. - -Cobertura de código -------------------- - -Si agregas una nueva característica, también necesitas comprobar la cobertura de código usando la opción `coverage-html`: - -.. code-block:: bash - - $ phpunit --coverage-html=cov/ - -Verifica la cobertura de código abriendo en un navegador la página generada ``cov/index.html``. - -.. tip:: - - La cobertura de código sólo funciona si tienes activado *XDebug* e instaladas todas las dependencias. - -.. _`instala PHPUnit`: http://www.phpunit.de/manual/current/en/installation.html -.. _`Composer`: http://getcomposer.org/ diff --git a/_sources/contributing/community/index.txt b/_sources/contributing/community/index.txt deleted file mode 100644 index 62b68ef..0000000 --- a/_sources/contributing/community/index.txt +++ /dev/null @@ -1,9 +0,0 @@ -Comunidad -========= - -.. toctree:: - :maxdepth: 2 - - releases - irc - other diff --git a/_sources/contributing/community/irc.txt b/_sources/contributing/community/irc.txt deleted file mode 100644 index e979786..0000000 --- a/_sources/contributing/community/irc.txt +++ /dev/null @@ -1,40 +0,0 @@ -Reuniones *IRC* -=============== - -El propósito de esta reunión es deliberar temas en tiempo real con muchos de los desarrolladores de *Symfony2*. - -Cualquier persona puede proponer temas en la lista de correo `symfony-dev`_ hasta 24 horas antes de la reunión, idealmente incluyendo también información preparada pertinentemente a través de una *URL*. 24 horas antes de la reunión será publicado un enlace a `doodle`_ con una lista de todos los temas propuestos. Cualquier persona puede votar los temas hasta el comienzo de la reunión para definir su posición en el orden del día. Cada tema tendrá una duración fija de 15 minutos y la sesión dura una hora, dejando tiempo suficiente para por lo menos 4 temas. - -.. caution:: - - Note that it's not the expected goal of the meeting to find final - solutions, but more to ensure that there is a common understanding of the - issue at hand and move the discussion forward in ways which are hard to - achieve with less real time communication tools. - -Las reuniones son cada jueves a las 17:00 CET (+01:00) en el canal #symfony-dev del servidor Freenode IRC. - -Los `registros`_ IRC se publicarán más tarde en el wiki de trac, el cual incluirá un breve resumen de cada uno de los temas. Puedes crear `tickets` para cualquiera de las tareas o problemas identificados durante la reunión y referidas en el resumen. - -Algunas sencillas instrucciones y orientación para participar: - -* Es posible cambiar tu voto hasta el comienzo de la reunión, haciendo clic en «Editar una entrada»; -* *doodle* cerrará la votación al comienzo de la reunión; -* La agenda se define por los temas que tienen el mayor número de votos en *doodle*, o la que se propuso primero en caso de empate; -* En el comienzo de la reunión una persona se identifica a sí misma como moderador(a); -* El moderador se encarga fundamentalmente de velar por el tiempo de 15 minutos para cada tema y garantizar que las tareas están claramente identificadas; -* Por lo general, el moderador se encargará de escribir el resumen y la creación de entradas en el ``trac`` a menos que alguien más lo releve; -* Cualquier persona puede unirse y expresamente está invitado a participar; -* Lo ideal sería que uno se familiarizara con el tema propuesto antes de la reunión; -* Al iniciar un nuevo tema se invita al proponente a empezar con unas cuantas palabras; -* Cualquiera puede comentar como mejor le parezca; -* Dependiendo de cuántas personas participen, potencialmente debes contenerte de enviar un argumento específico muy difícil; -* Recuerda que los `registros`_ IRC serán publicados más tarde, por lo tanto la gente tiene la oportunidad de revisar los comentarios más adelante; -* Animamos a todos a levantar la mano para asumir las tareas definidas en la reunión. - -Aquí está un `ejemplo`_ doodle. - -.. _`symfony-dev`: http://groups.google.com/group/symfony-devs -.. _`doodle`: http://doodle.com -.. _`registros`: http://trac.symfony-project.org/wiki/Symfony2IRCMeetingLogs -.. _`ejemplo`: http://doodle.com/4cnzme7xys3ay53w diff --git a/_sources/contributing/community/other.txt b/_sources/contributing/community/other.txt deleted file mode 100644 index bb93927..0000000 --- a/_sources/contributing/community/other.txt +++ /dev/null @@ -1,14 +0,0 @@ -Otros recursos -============== - -Con el fin de dar seguimiento a lo que está sucediendo en la comunidad pueden ser útiles estos recursos adicionales: - -* Lista de `peticiones de atracción`_ abiertas -* Lista de `envíos`_ recientes -* Lista de `fallos y mejoras`_ abiertas -* Lista de `paquetes`_ de fuente abierta - -.. _`peticiones de atracción`: https://github.com/symfony/symfony/pulls -.. _`envíos`: https://github.com/symfony/symfony/commits/master -.. _`fallos y mejoras`: https://github.com/symfony/symfony/issues -.. _`paquetes`: http://knpbundles.com/ diff --git a/_sources/contributing/community/releases.txt b/_sources/contributing/community/releases.txt deleted file mode 100644 index 95fbb2d..0000000 --- a/_sources/contributing/community/releases.txt +++ /dev/null @@ -1,93 +0,0 @@ -El proceso de versionado -======================== - -Este documento explica el proceso de versionado de *Symfony* (*Symfony* es el código alojado en el `repositorio Git`_ principal ``symfony/symfony``). - -*Symfony* gestiona sus versiones a través de un *modelo basado en tiempo*; *cada seis meses* sale una nueva versión de *Symfony*: una en *Mayo* y otra en *Noviembre*. - -.. note:: - - Este proceso de versionado fue adoptado a partir de *Symfony 2.2*, y todas las «reglas» explicadas en este documento se deben seguir estrictamente a partir de *Symfony 2.4*. - -Desarrollo ----------- - -El periodo de seis meses se divide en dos fases: - -* *Desarrollo*: *Cuatro meses* para agregar nuevas características y mejorar las existentes; - -* *Estabilización*: *Dos meses* para corregir errores, preparar el lanzamiento y esperar a que el ecosistema de *Symfony* en conjunto (bibliotecas de terceros, paquetes y proyectos que utilizan *Symfony*) se pongan al día. - -Durante la fase de desarrollo, cualquier nueva característica se puede revertir si no será terminada a tiempo o si no estará lo suficientemente estable como para ser incluida en la versión final actual. - -Manteniendo ------------ - -Cada versión de *Symfony* se mantiene durante un determinado periodo de tiempo, en función del tipo de la versión. - -Versiones estándar -~~~~~~~~~~~~~~~~~~ - -Una versión estándar se mantiene durante un periodo de *ocho mes*. - -Plazo de apoyo a versiones mayores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada dos años, se apoya una nueva versión mayor (también conocidas como versiones *LTS* por *Long Term Support Release*) será publicada. Cada versión *LTS* será apoyada por un periodo de *tres años*. - -.. note:: - - El apoyo pagado después de tres años con el apoyo prestado por la comunidad también se puede comprar en `SensioLabs`_. - -Programa --------- - -A continuación se muestra el cronograma de las primeras versiones que utilizan este modelo de edición: - -.. image:: /images/release-process.jpg - :align: center - -* El **amarillo** representa la fase de desarrollo -* El **azul** representa la fase de estabilización -* El **verde** representa el periodo de mantenimiento - -Este se traduce en fechas y periodos de mantenimiento muy predecibles. - -* *(especial)* *Symfony 2.2* será lanzado a finales de Febrero del 2013; -* *(especial)* *Symfony 2.3* (la primer versión *LTS*) se liberará a finales de Mayo del 2013; -* *Symfony 2.4* será lanzado a finales de Noviembre del 2013; -* *Symfony 2.5* será lanzado a finales de Mayo del 2014; -* ... - -Compatibilidad con versiones anteriores ---------------------------------------- - -Después del lanzamiento de *Symfony 2.3*, la compatibilidad hacia atrás se mantendrá a toda costa. Si no es posible, la característica, mejora o corrección de errores -se programará para la siguiente versión principal: *Symfony 3.0*. - -.. note:: - - El trabajo sobre *Symfony 3.0* se iniciará cuando suficientes características principales de compatibilidad con versiones anteriores estén esperando en la lista de pendientes. - -Depreciaciones --------------- - -Cuando la implementación de una característica no se pueda reemplazar con otra mejor sin romper la compatibilidad hacia atrás, todavía existe la posibilidad de depreciar la vieja implementación y añadir una nueva preferida al lado de la otra. Lee el documento con las :ref:`convenciones ` para conocer más sobre el manejo de la depreciación en *Symfony*. - -Justificación -------------- - -Este proceso de liberación se adoptó para dar mayor *predecibilidad* y *transparencia*. Se discutió basándose en los siguientes objetivos: - -* Acortar el ciclo de liberación (permitiendo que los desarrolladores se puedan beneficiar más rápido de las nuevas características); -* Darle más visibilidad a los desarrolladores que están usando la plataforma y a los proyectos de código abierto que utilizan *Symfony*; -* Mejorar la experiencia de los colaboradores del núcleo de *Symfony*: que todo mundo sepa cuando una característica esté disponible en *Symfony*; -* Coordinar el calendario de *Symfony* con proyectos *PHP* populares que funcionan bien con *Symfony* y con proyectos que utilizan *Symfony*; -* Darle tiempo al ecosistema de *Symfony* para ponerse al día con las nuevas versiones (autores de paquete, escritores de documentación, traductores, ...). - -El periodo de seis meses fue elegido para ajustarse a dos lanzamientos en un año. Este también permite un montón de tiempo para trabajar en las nuevas características y permite que las características que no están listas se pospongan para la próxima versión sin tener que esperar demasiado tiempo para el siguiente ciclo. - -El modo de mantenimiento dual se adoptó para hacer feliz a cada usuario de *Symfony*. Moviéndose rápido, a quién quiera trabajar con la última y más reciente, utilizando las versiones estándar: una nueva versión será publicada cada seis meses, y esta cuenta con un periodo de dos meses para actualizarse. Las empresas que deseen una mayor estabilidad pueden utilizar las versiones *LTS*: una nueva versión será publicada cada dos años y cuenta con un año para actualizarse. - -.. _`repositorio Git`: https://github.com/symfony/symfony -.. _SensioLabs: http://sensiolabs.com/ diff --git a/_sources/contributing/documentation/format.txt b/_sources/contributing/documentation/format.txt deleted file mode 100644 index 9b1569c..0000000 --- a/_sources/contributing/documentation/format.txt +++ /dev/null @@ -1,208 +0,0 @@ -Formato de documentación -======================== - -La documentación de *Symfony2* utiliza `reStructuredText`_ como lenguaje de marcado y `Sphinx`_ para generarla (en *HTML*, *PDF*, ...). - -*reStructuredText* ------------------- - -*reStructuredText* «es un sistema analizador y sintaxis de marcado de texto, fácil de leer, lo que ves es lo que obtienes». - -Puedes aprender más acerca de su sintaxis leyendo los `documentos`_ existentes de *Symfony2* o leyendo el `Primer reStructuredText`_ en el sitio web de Sphinx. - -Si estás familiarizado con *Markdown*, ten cuidado que las cosas a veces se ven muy similares, pero son diferentes: - -* Las listas se inician al comienzo de una línea (no se permite sangría); - -* Los bloques de código en línea utilizan comillas dobles (````como estas````). - -Sphinx ------- - -*Sphinx* es un sistema generador que añade algunas herramientas agradables para crear la documentación a partir de documentos *reStructuredText*. Como tal, agrega nuevas directivas e interpreta texto en distintos roles al `marcado`_ *reST* estándar. - -Resaltado de sintaxis -~~~~~~~~~~~~~~~~~~~~~ - -Todo el código de los ejemplos de manera predeterminada utiliza *PHP* como lenguaje a resaltar. Puedes cambiarlo con la directiva ``code-block``: - -.. code-block:: rst - - .. code-block:: yaml - - { foo: bar, bar: { foo: bar, bar: baz } } - -Si el código *PHP* comienza con ``foobar(); ?> - -.. note:: - - Una lista de lenguajes apoyados está disponible en el sitio web de `Pygments`_. - -Bloques de configuración -~~~~~~~~~~~~~~~~~~~~~~~~ - -Cada vez que muestres una configuración, debes utilizar la directiva ``configuration-block`` para mostrar la configuración en todos los formatos de configuración compatibles (*YAML*, *XML* y *PHP*) - -.. code-block:: rst - - .. configuration-block:: - - .. code-block:: yaml - - # Configuración en YAML - - .. code-block:: xml - - - - .. code-block:: php - - // Configuración en PHP - -El fragmento *reST* anterior se dibuja de la siguiente manera: - -.. configuration-block:: - - .. code-block:: yaml - - # Configuración en YAML - - .. code-block:: xml - - - - .. code-block:: php - - // Configuración en PHP - -La lista de formatos apoyados actualmente es la siguiente: - -+----------------------+-------------+ -| Formato de marcado | Muestra | -+======================+=============+ -| html | *HTML* | -+----------------------+-------------+ -| xml | *XML* | -+----------------------+-------------+ -| php | *PHP* | -+----------------------+-------------+ -| yaml | *YAML* | -+----------------------+-------------+ -| jinja | *Twig* | -+----------------------+-------------+ -| html+jinja | *Twig* | -+----------------------+-------------+ -| html+php | *PHP* | -+----------------------+-------------+ -| ini | *INI* | -+----------------------+-------------+ -| php-annotations | Anotaciones | -+----------------------+-------------+ - -Añadiendo enlaces -~~~~~~~~~~~~~~~~~ - -Para añadir enlaces a otras páginas en los documentos utiliza la siguiente sintaxis: - -.. code-block:: rst - - :doc:`/ruta/a/pagina` - -Usando la ruta y nombrearchivo de la página sin extensión, por ejemplo: - -.. code-block:: rst - - :doc:`/book/controller` - - :doc:`/components/event_dispatcher/introduction` - - :doc:`/cookbook/configuration/environments` - -El texto del enlace será el encabezado principal del documento enlazado. También puedes especificar texto alternativo para el enlace: - -.. code-block:: rst - - :doc:`Cola de impresión de correo electrónico
    ` - -También puedes añadir enlaces a la documentación de la *API*: - -.. code-block:: rst - - :namespace:`Symfony\\Component\\BrowserKit` - - :class:`Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher` - - :method:`Symfony\\Component\\HttpKernel\\Bundle\\Bundle::build` - -y a la documentación de *PHP*: - -.. code-block:: rst - - :phpclass:`SimpleXMLElement` - - :phpmethod:`DateTime::createFromFormat` - - :phpfunction:`iterator_to_array` - -Probando la documentación -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Para probar la documentación antes de enviarla: - -* Instala `Sphinx`_; - -* Ejecuta el programa de `instalación rápida de Sphinx`_; - -* Instala las extensiones de ``Sphinx`` (ve más adelante); - -* Ejecuta ``make html`` y revisa el código *HTML* generado en el directorio ``_build``. - -Instalando las extensiones de ``Sphinx`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Descarga las extensiones desde el repositorio `source`_ - -* Copia el directorio ``sensio`` al directorio ``_exts`` bajo tu directorio fuente (donde está ``conf.py``) - -* Agrega lo siguiente al archivo ``conf.py``: - -.. code-block:: py - - # ... - sys.path.append(os.path.abspath('_exts')) - - # añadiendo PhpLexer - from sphinx.highlighting import lexers - from pygments.lexers.web import PhpLexer - - # ... - # añade la extensión a la lista de extensiones - extensions = [..., 'sensio.sphinx.refinclude', - 'sensio.sphinx.configurationblock', - 'sensio.sphinx.phpcode'] - - # habilita el resaltado para el código PHP que no esté - # entre ```` por omisión - lexers['php'] = PhpLexer(startinline=True) - lexers['php-annotations'] = PhpLexer(startinline=True) - - # usa PHP como el dominio primario - primary_domain = 'php' - - # fija la url para los enlaces de la API - api_url = 'http://api.symfony.com/master/%s' - -.. _`reStructuredText`: http://docutils.sourceforge.net/rst.html -.. _`Sphinx`: http://sphinx-doc.org/ -.. _`documentos`: https://github.com/symfony/symfony-docs -.. _`Primer reStructuredText`: http://sphinx-doc.org/rest.html -.. _`marcado`: http://sphinx-doc.org/markup/ -.. _`Pygments`: http://pygments.org/languages/ -.. _source: https://github.com/fabpot/sphinx-php -.. _`instalación rápida de Sphinx`: http://sphinx-doc.org/tutorial.html#setting-up-the-documentation-sources diff --git a/_sources/contributing/documentation/index.txt b/_sources/contributing/documentation/index.txt deleted file mode 100644 index 1774b7f..0000000 --- a/_sources/contributing/documentation/index.txt +++ /dev/null @@ -1,10 +0,0 @@ -Aportando documentación -======================= - -.. toctree:: - :maxdepth: 2 - - overview - format - translations - license diff --git a/_sources/contributing/documentation/license.txt b/_sources/contributing/documentation/license.txt deleted file mode 100644 index 4b5e6a0..0000000 --- a/_sources/contributing/documentation/license.txt +++ /dev/null @@ -1,46 +0,0 @@ -Licencia de la documentación de *Symfony2* -========================================== - -La documentación de *Symfony2* está bajo una licencia Creative Commons Attribution-Share Alike 3.0 Unported `Licencia`_. - -**Estás en libertad:** - -* para *Compartir* — copiar, distribuir y trasmitir públicamente la obra; - -* para *Derivar* — adaptando la obra. - -**Bajo las siguientes condiciones:** - -* *Atribución* — Debes atribuir el trabajo de la manera especificada por - el autor o licenciador (pero de ninguna manera que sugiera que apoya tu - uso de la obra). - -* *Compartir bajo la misma licencia* — Si alteras, transformas, o creas - sobre esta obra, sólo podrás distribuir la obra resultante bajo una - licencia idéntica o similar a esta. - -**En el entendido de:** - -* *Renuncia* — Cualquiera de estas condiciones puede no aplicarse si - obtienes el permiso del titular de los derechos de autor; - -* *Dominio Público* — Cuando la obra o cualquiera de sus elementos es del - dominio público bajo la legislación aplicable, este estatus de ninguna - manera es afectado por la licencia; - -* *Otros Derechos* — De ninguna manera cualquiera de los siguientes derechos - se ve afectado por la licencia: - - * Tu derecho leal o uso justo, u otros derechos de autor excepciones y limitaciones aplicables; - - * Los derechos morales del autor; - - * Otras personas pueden tener derechos ya sea en la propia obra o en la forma en que se utiliza la obra, como los derechos de publicidad o privacidad. - -* *Atención* — Para cualquier reutilización o distribución, debes dejar - claros los términos de la licencia de esta obra. La mejor manera de hacerlo es con un enlace a esta página web. - -Este es un resumen humanamente legible del `Texto legal (Licencia completa)`_. - -.. _`Licencia`: http://creativecommons.org/licenses/by-sa/3.0/ -.. _`Texto legal (Licencia completa)`: http://creativecommons.org/licenses/by-sa/3.0/legalcode diff --git a/_sources/contributing/documentation/overview.txt b/_sources/contributing/documentation/overview.txt deleted file mode 100644 index 9000209..0000000 --- a/_sources/contributing/documentation/overview.txt +++ /dev/null @@ -1,205 +0,0 @@ -Colaborando en la documentación -=============================== - -La documentación es tan importante como el código. Esta sigue exactamente los mismos principios: -una vez y sólo una, pruebas, facilidad de mantenimiento, -extensibilidad, optimización y reconstrucción sólo por nombrar algunos. Y, por supuesto, la documentación tiene errores, errores tipográficos, guías difíciles de leer y mucho más. - -Colaborando ------------ - -Antes de colaborar, necesitas familiarizarte con el :doc:`lenguaje de marcado ` empleado en la documentación. - -La documentación de *Symfony2* se encuentra alojada en *GitHub*: - -.. code-block:: text - - https://github.com/symfony/symfony-docs - -Si deseas enviar un parche `bifurca`_ el repositorio oficial en *GitHub* y luego clona tu bifurcación: - -.. code-block:: bash - - $ git clone git://github.com/TUNOMBRE/symfony-docs.git - -Consistent with Symfony's source code, the documentation repository is split into -multiple branches: ``2.0``, ``2.1``, ``2.2`` corresponding to the different -versions of Symfony itself. The ``master`` branch holds the documentation -for the development branch of the code. - -Unless you're documenting a feature that was introduced *after* Symfony 2.0 -(e.g. in Symfony 2.1), your changes should always be based on the 2.0 branch. -To do this checkout the 2.0 branch before the next step: - -.. code-block:: bash - - $ git checkout 2.0 - -.. tip:: - - Your base branch (e.g. 2.0) will become the "Applies to" in the :ref:`doc-contributing-pr-format` - that you'll use later. - -A continuación, crea una rama dedicada a tus cambios (para mantenerla organizada): - -.. code-block:: bash - - $ git checkout -b improving_foo_and_bar - -Ahora puedes hacer los cambios directamente en esta rama y confirmarlos ahí. Cuando hayas terminado, impulsa esta rama a *tu* *GitHub* e inicia una solicitud de atracción. - -Creando una solicitud de atracción -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Siguiendo el ejemplo, la solicitud de atracción de manera predeterminada debe ser entre tu rama ``improving_foo_and_bar`` y la rama ``master`` de ``symfony-docs``. - -.. image:: /images/docs-pull-request.png - :align: center - -Si has hecho tus cambios basándote en la rama 2.0, entonces necesitas cambiar la rama base para que sea *2.0* en la página anterior: - -.. image:: /images/docs-pull-request-change-base.png - :align: center - -.. note:: - - Todos los cambios hechos a una rama (p. ej. 2.0) serán fusionados en cada «nueva» rama (p. ej. 2.1, maestra, etc.) para la siguiente liberación en una base semanal. - -*GitHub* aborda en detalle el tema de las `solicitudes de atracción`_. - -.. note:: - - La documentación de *Symfony2* está bajo una licencia Creative Commons Attribution-Share Alike 3.0 Unported :doc:`Licencia `. - -You can also prefix the title of your pull request in a few cases: - -* ``[WIP]`` (Work in Progress) is used when you are not yet finished with your - pull request, but you would like it to be reviewed. The pull request won't - be merged until you say it is ready. - -* ``[WCM]`` (Waiting Code Merge) is used when you're documenting a new feature - or change that hasn't been accepted yet into the core code. The pull request - will not be merged until it is merged in the core code (or closed if the - change is rejected). - -.. _doc-contributing-pr-format: - -Formato de la solicitud de atracción -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Unless you're fixing some minor typos, the pull request description **must** -include the following checklist to ensure that contributions may be reviewed -without needless feedback loops and that your contributions can be included -into the documentation as quickly as possible: - -.. code-block:: text - - | Q | A - | ------------- | --- - | Doc fix? | [yes|no] - | New docs? | [yes|no] (PR # si es aplicable en symfony/symfony) - | Applies to | [numero de versiones Symfony a las que es aplicable] - | Fixed tickets | [lista separada por comas de los boletos corregidos por PR] - -Un envío de ejemplo ahora se ve de la siguiente manera: - -.. code-block:: text - - | Q | A - | ------------- | --- - | Doc fix? | yes - | New docs? | yes (symfony/symfony#2500) - | Applies to | all (or 2.1+) - | Fixed tickets | #1075 - -.. tip:: - - Por favor, ten paciencia. Puede tomar desde 15 minutos hasta varios días para que tus cambios aparezcan en el sitio *web* ``symfony.com`` después de que el equipo de documentación fusione tu solicitud de atracción. Puedes comprobar si tus cambios introducen algunas cuestiones sobre el marcado yendo a la página de `Errores en la generación de documentación`_ (esta se actualiza en Francia cada noche a las 3 a.m. cuando el servidor vuelve a generar la documentación). - -Documentando nuevas características o cambios de comportamiento ---------------------------------------------------------------- - -Si estás documentando un nuevo tipo de característica o un cambio que se esté haciendo en *Symfony2*, deberías preceder tu descripción del cambio con una etiqueta ``.. versionadded:: 2.X`` -y una breve descripción: - -.. code-block:: text - - .. versionadded:: 2.2 - El método ``askHiddenResponse`` se añadió en *Symfony 2.2*. - - Además, puedes hacer una pregunta y ocultar la respuesta. Esto particularmente es... - -Si estás documentando un cambio de comportamiento, puedría ser útil describir *brevemente* cómo ha cambiado tal comportamiento. - -.. code-block:: text - - .. versionadded:: 2.2 - La función ``include()`` es una nueva característica de *Twig* que está disponible en *Symfony 2.2*. Anteriormente se utilizaba la etiqueta ``{% include %}``. - -Siempre que se libere una nueva versión menor de *Symfony2* (p. ej. 2.3, 2.4, etc.), se crea una nueva rama de la documentación desde la rama ``maestra``. -En este punto, todas las etiquetas ``versionadded`` para las versiones de *Symfony2* que han alcanzado su fin-de-vida serán eliminadas. Por ejemplo, si *Symfony 2.5* fuera liberado hoy, y *2.2* recientemente alcanzó su fin-de-vida, la etiqueta ``versionadded`` 2.2 sería eliminada de la nueva rama *2.5*. - -Estándares ----------- - -Con el fin de ayudar lo más posible al lector y crear código de ejemplo que se vea y sienta familiar, debes seguir estas reglas: - -* El código sigue los :doc:`Estándares de codificación de Symfony ` así como los `Estándares de codificación de Twig`_; -* Each line should break approximately after the first word that crosses the - 72nd character (so most lines end up being 72-78 characters); -* Para evitar desplazamiento horizontal en bloques de código, preferimos romper una línea correctamente si rebasa el 85º carácter; -* Cuándo pliegues una o más líneas de código, coloca ``...`` en un comentario en el punto del pliegue. Estos comentarios son: ``// ...`` (php), ``# ...`` (yaml/bash), ``{# ... #}`` - (twig), ```` (xml/html), ``; ...`` (ini), ``...`` (texto); -* Cuándo pliegues una parte de una línea, p. ej. un valor variable, coloca ``...`` (sin comentario) en el sitio del pliegue; -* Descripción del código plegado: (opcional) si pliegas varias líneas: la descripción del pliegue se puede colocar después de los ``...``; si sólo pliegas una parte de una línea: la descripción se puede colocar antes de la línea; -* Si se considera útil, un bloque de código ``codeblock`` debe comenzar con un comentario que contenga el nombre del archivo en el bloque de código. No coloques una línea de espacio en blanco después de este comentario, a no ser que la próxima línea también sea un comentario; -* Debes poner un ``$`` delante de cada línea del intérprete; -* El atajo ``::`` es preferible sobre ``.. code-block:: php`` para comenzar un bloque de código *PHP*. -* Deberías utilizar la forma *tú* en vez de *nosotros*. - -Un ejemplo:: - - // src/Foo/Bar.php - - // ... - class Bar - { - // ... - - public function foo($bar) - { - // ajusta foo al valor de bar - $foo = ...; - - // ... comprueba si $bar tiene el valor correcto - - return $foo->baz($bar, ...); - } - } - -.. caution:: - - en *Yaml* debes dejar un espacio después de ``{`` y antes de ``}`` (p. ej. ``{ _controlador: ... }``), - pero esto no lo debes hacer en Twig (p. ej. ``{'hola' : 'valor'}``). - -Informando un problema ----------------------- - -La contribución más sencilla que puedes hacer es reportar algún problema: a typo, a grammar -mistake, a bug in a code example, a missing explanation, and so on. - -Pasos a seguir: - -* Reporta un error en el rastreador de fallos; - -* *(Opcional)* Envía un parche. - -Traduciendo ------------ - -Lee el :doc:`documento dedicado `. - -.. _`bifurca`: https://help.github.com/articles/fork-a-repo -.. _`solicitudes de atracción`: https://help.github.com/articles/using-pull-requests -.. _`Errores en la generación de documentación`: http://symfony.com/doc/build_errors -.. _`Estándares de codificación de Twig`: http://gitnacho.github.com/Twig/coding_standards.html diff --git a/_sources/contributing/documentation/translations.txt b/_sources/contributing/documentation/translations.txt deleted file mode 100644 index c827140..0000000 --- a/_sources/contributing/documentation/translations.txt +++ /dev/null @@ -1,69 +0,0 @@ -Traduciendo -=========== - -La documentación de *Symfony2* está escrita en inglés y hay muchas personas involucradas en el proceso de traducción. - -Colaborando ------------ - -En primer lugar, familiarízate con el :doc:`lenguaje de marcado ` empleado en la documentación. - -A continuación, suscríbete a la `lista de correo de la documentación de Symfony`_, debido a que la colaboración sucede allí. - -Por último, busca el repositorio *maestro* del idioma con el que deseas contribuir. Esta es la lista oficial de los repositorios *maestros*: - -* *Inglés*: https://github.com/symfony/symfony-docs -* *Francés*: https://github.com/symfony-fr/symfony-docs-fr -* *Italiano*: https://github.com/garak/symfony-docs-it -* *Japonés*: https://github.com/symfony-japan/symfony-docs-ja -* *Polaco*: https://github.com/ampluso/symfony-docs-pl -* *Portugués (Brasil)*: https://github.com/andreia/symfony-docs-pt-BR -* *Rumano*: https://github.com/sebio/symfony-docs-ro -* *Ruso*: https://github.com/avalanche123/symfony-docs-ru -* *Español*: https://github.com/gitnacho/symfony-docs-es -* *Turco*: https://github.com/symfony-tr/symfony-docs-tr - -.. note:: - - Si quieres contribuir traducciones para un nuevo idioma, lee la :ref:`sección dedicada `. - -Uniéndote al equipo de traducción ---------------------------------- - -Si quieres ayudar a traducir algunos documentos a tu idioma o corregir algunos errores, considera unirte; es un proceso muy sencillo: - -* Preséntese en la `lista de correo de la documentación de Symfony`_; -* *(Opcional)* Pregunta en cuales documentos puedes trabajar; -* Bifurca el repositorio *master* de tu idioma (haciendo clic en el botón “Fork” en la página de *GitHub*); -* Traduce algún documento; -* Haz una solicitud de atracción (haciendo clic en ``Pull Request`` de tu página en *GitHub*); -* El administrador del equipo acepta tus modificaciones y las combina en el repositorio maestro; -* El sitio web de documentación se actualiza todas las noches desde el repositorio maestro. - -.. _translations-adding-a-new-language: - -Añadiendo un nuevo idioma -------------------------- - -Esta sección ofrece algunas pautas para comenzar la traducción de la documentación de *Symfony2* para un nuevo idioma. - -Debido a que iniciar una traducción conlleva mucho trabajo, habla acerca de tu plan en la `lista de correo de la documentación de Symfony`_ y trata de encontrar personas motivadas dispuestas a ayudar. - -Cuando el grupo esté listo, nomina un administrador del equipo; quién será el responsable del repositorio *maestro*. - -Crea el repositorio y copia los documentos en *Inglés*. - -El equipo ahora puede iniciar el proceso de traducción. - -Cuando el equipo confíe en que el repositorio está en un estado coherente y estable (se ha traducido todo, o los documentos sin traducir se han retirado de los árboles de tablas de contenido ---archivos con el nombre ``index.rst`` y ``map.rst.inc``---), el administrador del equipo puede pedir que el repositorio se añada a la lista *maestra* de repositorios oficiales enviando un correo electrónico a Fabien (fabien arroba symfony.com). - -Manteniendo ------------ - -La traducción no termina cuando se ha traducido todo. La documentación es un ente en continuo movimiento (se agregan nuevos documentos, se corrigen errores, se reorganizan párrafos, ...). El equipo de traducción tiene que seguir de cerca los cambios del repositorio en Inglés y aplicarlos a los documentos traducidos tan pronto como sea posible. - -.. caution:: - - Los idiomas sin mantenimiento se quitan de la lista oficial de repositorios de documentación puesto que la obsolescencia es peligrosa. - -.. _`lista de correo de la documentación de Symfony`: http://groups.google.com/group/symfony-docs diff --git a/_sources/contributing/index.txt b/_sources/contributing/index.txt deleted file mode 100644 index 2e67888..0000000 --- a/_sources/contributing/index.txt +++ /dev/null @@ -1,11 +0,0 @@ -Colaborando -=========== - -.. toctree:: - :hidden: - - code/index - documentation/index - community/index - -.. include:: /contributing/map.rst.inc diff --git a/_sources/cookbook/assetic/apply_to_option.txt b/_sources/cookbook/assetic/apply_to_option.txt deleted file mode 100644 index 590144e..0000000 --- a/_sources/cookbook/assetic/apply_to_option.txt +++ /dev/null @@ -1,165 +0,0 @@ -.. index:: - single: Assetic; Aplicando filtros - -Cómo aplicar un *filtro* ``Assetic`` a una extensión de archivo especifica -========================================================================== - -Los *filtros* ``Assetic`` se pueden aplicar a archivos individuales, grupos de archivos o incluso, como veremos aquí, a archivos que tengan una determinada extensión. Para mostrarte cómo manejar cada opción, vamos a suponer que quieres usar el filtro ``CoffeeScript`` de ``Assetic``, el cual compila archivos de ``CoffeeScript`` en *Javascript*. - -La configuración principal sólo son las rutas a ``coffee`` y ``node``. Estas por omisión son ``/usr/bin/coffee`` y ``/usr/bin/node`` respectivamente: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - coffee: - bin: /usr/bin/coffee - node: /usr/bin/node - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'coffee' => array( - 'bin' => '/usr/bin/coffee', - 'node' => '/usr/bin/node', - ), - ), - )); - -Filtrando un solo archivo -------------------------- - -Ahora puedes servir un solo archivo ``CoffeeScript`` como *JavaScript* dentro de tus plantillas: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' filter='coffee' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array('@AcmeFooBundle/Resources/public/js/example.coffee'), - array('coffee') - ) as $url): ?> - - - -Esto es todo lo que se necesita para compilar este archivo ``CoffeeScript`` y servirlo como *JavaScript* compilado. - -Filtrando múltiples archivos ----------------------------- - -También puedes combinar varios archivos ``CoffeeScript`` y producir un único archivo: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' - '@AcmeFooBundle/Resources/public/js/another.coffee' - filter='coffee' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array( - '@AcmeFooBundle/Resources/public/js/example.coffee', - '@AcmeFooBundle/Resources/public/js/another.coffee', - ), - array('coffee') - ) as $url): ?> - - - -Ahora, ambos archivos se sirven como un solo archivo compilado en *JavaScript* regular. - -.. _cookbook-assetic-apply-to: - -Filtrando en base a la extensión de archivo -------------------------------------------- - -Una de las grandes ventajas de usar ``Assetic`` es minimizar el número de archivos de activos para reducir las peticiones ``HTTP``. Con el fin de usar esto completamente, sería bueno combinar *todos* los archivos *JavaScript* y ``CoffeeScript`` juntos puesto que en última instancia, todo se debe servir como *JavaScript*. Desafortunadamente sólo añadir los archivos *JavaScript* a los archivos combinados como el anterior no funciona puesto que los archivos *JavaScript* regulares no sobrevivirán a la compilación de ``CoffeeScript``. - -Este problema se puede evitar usando la opción ``apply_to`` en la configuración, lo cual te permite especificar que siempre se aplique un *filtro* a las extensiones de archivo en particular. En este caso puedes especificar que el *filtro* ``Coffee`` se aplique a todos los archivos ``.coffee``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - coffee: - bin: /usr/bin/coffee - node: /usr/bin/node - apply_to: "\.coffee$" - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'coffee' => array( - 'bin' => '/usr/bin/coffee', - 'node' => '/usr/bin/node', - 'apply_to' => '\.coffee$', - ), - ), - )); - -Con esto, ya no tendrás que especificar el *filtro* ``coffee`` en la plantilla. -También puedes listar archivos *JavaScript* regulares, los cuales serán combinados y reproducidos como un único archivo *JavaScript* (con sólo ejecutar los archivos ``.coffee`` a través del *filtro* ``CoffeeScript``.) - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/example.coffee' - '@AcmeFooBundle/Resources/public/js/another.coffee' - '@AcmeFooBundle/Resources/public/js/regular.js' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array( - '@AcmeFooBundle/Resources/public/js/example.coffee', - '@AcmeFooBundle/Resources/public/js/another.coffee', - '@AcmeFooBundle/Resources/public/js/regular.js', - ) - ) as $url): ?> - - diff --git a/_sources/cookbook/assetic/asset_management.txt b/_sources/cookbook/assetic/asset_management.txt deleted file mode 100644 index 0716727..0000000 --- a/_sources/cookbook/assetic/asset_management.txt +++ /dev/null @@ -1,385 +0,0 @@ -.. index:: - single: Assetic; Introducción - -Cómo utilizar ``Assetic`` para gestionar activos -================================================ - -``Assetic`` combina dos ideas principales: :ref:`assets` y :ref:`filters`. Los activos son archivos tales como *CSS*, *JavaScript* y archivos de imagen. Los *filtros* son cosas que se pueden aplicar a estos archivos antes de servirlos al navegador. Esto te permite una separación entre los archivos de activos almacenados en tu aplicación y los archivos realmente presentados al usuario. - -Sin ``Assetic``, sólo sirves los archivos que están almacenados directamente en la aplicación: - -.. configuration-block:: - - .. code-block:: html+jinja - - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array('@AcmeFooBundle/Resources/public/js/*') - ) as $url): ?> - - - -.. tip:: - - También puedes incluir hojas de estilo *CSS*: consulta :ref:`cookbook-assetic-including-css`. - -En este ejemplo, se cargan todos los archivos en el directorio ``Resources/public/js/`` del ``AcmeFooBundle`` y se sirven desde un lugar diferente. -En realidad la etiqueta reproducida simplemente podría ser: - -.. code-block:: html - - - -Este es un punto clave: once you let Assetic handle your assets, the files are -served from a different location. This *will* cause problems with CSS files -that reference images by their relative path. See :ref:`cookbook-assetic-cssrewrite`. - -.. _cookbook-assetic-including-css: - -Including CSS Stylesheets -~~~~~~~~~~~~~~~~~~~~~~~~~ - -To bring in CSS stylesheets, you can use the same methodologies seen -above, except with the ``stylesheets`` tag. If you're using the default -block names from the Symfony Standard Distribution, this will usually live -inside a ``stylesheets`` block: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% stylesheets 'bundles/acme_foo/css/*' filter='cssrewrite' %} - - {% endstylesheets %} - - .. code-block:: html+php - - stylesheets( - array('bundles/acme_foo/css/*'), - array('cssrewrite') - ) as $url): ?> - - - -But because Assetic changes the paths to your assets, this *will* break any -background images (or other paths) that uses relative paths, unless you use -the :ref:`cssrewrite` filter. - -.. note:: - - Notice that in the original example that included JavaScript files, you - referred to the files using a path like ``@AcmeFooBundle/Resources/public/file.js``, - but that in this example, you referred to the CSS files using their actual, - publicly-accessible path: ``bundles/acme_foo/css``. You can use either, except - that there is a known issue that causes the ``cssrewrite`` filter to fail - when using the ``@AcmeFooBundle`` syntax for CSS Stylesheets. - -.. _cookbook-assetic-cssrewrite: - -Fixing CSS Paths with the ``cssrewrite`` Filter -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Since Assetic generates new URLs for your assets, any relative paths inside -your CSS files will break. To fix this, make sure to use the ``cssrewrite`` -filter with your ``stylesheets`` tag. This parses your CSS files and corrects -the paths internally to reflect the new location. - -You can see an example in the previous section. - -.. caution:: - - When using the ``cssrewrite`` filter, don't refer to your CSS files using - the ``@AcmeFooBundle``. See the note in the above section for details. - -Combinando activos -~~~~~~~~~~~~~~~~~~ - -One feature of Assetic is that it will combine many files into one. This helps -to reduce the number of HTTP requests, which is great for front end performance. -It also allows you to maintain the files more easily by splitting them into -manageable parts. This can help with re-usability as you can easily split -project-specific files from those which can be used in other applications, -but still serve them as a single file: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts - '@AcmeFooBundle/Resources/public/js/*' - '@AcmeBarBundle/Resources/public/js/form.js' - '@AcmeBarBundle/Resources/public/js/calendar.js' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array( - '@AcmeFooBundle/Resources/public/js/*', - '@AcmeBarBundle/Resources/public/js/form.js', - '@AcmeBarBundle/Resources/public/js/calendar.js', - ) - ) as $url): ?> - - - -In the ``dev`` environment, each file is still served individually, so that -you can debug problems more easily. However, in the ``prod`` environment -(or more specifically, when the ``debug`` flag is ``false``), this will be -rendered as a single ``script`` tag, which contains the contents of all of -the JavaScript files. - -.. tip:: - - Si eres nuevo en ``Assetic`` y tratas de usar la aplicación en el entorno ``prod`` (usando el controlador :file:`app.php`), lo más probable es que se rompan todos tus *CSS* y *JS*. ¡No te preocupes! Esto es a propósito. - For details on using Assetic in the ``prod`` environment, see :ref:`cookbook-assetic-dumping`. - -Y la combinación de archivos no sólo se aplica a *tus* archivos. También puedes usar ``Assetic`` para combinar activos de terceros, como *jQuery*, con tu propio *JavaScript* en un solo archivo: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js' - '@AcmeFooBundle/Resources/public/js/*' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array( - '@AcmeFooBundle/Resources/public/js/thirdparty/jquery.js', - '@AcmeFooBundle/Resources/public/js/*', - ) - ) as $url): ?> - - - -.. _cookbook-assetic-filters: - -Filtros -------- - -Una vez que son gestionados por ``Assetic``, puedes aplicar filtros a tus activos antes de servirlos. Esto incluye filtros que comprimen la salida de tus activos a un archivo más pequeño (y mejor optimización en la interfaz de usuario). Otros filtros incluyen la compilación de archivos *JavaScript* desde archivos *CoffeeScript* y *SASS* a *CSS*. -De hecho, ``Assetic`` tiene una larga lista de filtros disponibles. - -Muchos de los filtros no hacen el trabajo directamente, sino que utilizan otras bibliotecas para hacerlo, a menudo, esta es la razón por la que tienes que instalar esos programas también. Esto significa que a menudo tendrás que instalar una biblioteca de terceros para usar un filtro. La gran ventaja de utilizar ``Assetic`` para invocar estas bibliotecas (en lugar de utilizarlas directamente) es que en lugar de tener que ejecutarlo manualmente cuando has trabajado en los archivos, ``Assetic`` se hará cargo de esto por ti y elimina por completo este paso de tu proceso de desarrollo y despliegue. - -Para usar un filtro debes especificarlo en la configuración de ``Assetic``. -Añadir un filtro aquí no quiere decir que se esté utilizando ---sólo significa que está disponible para usarlo (vamos a utilizar el filtro en seguida). - -Por ejemplo, para utilizar el *JavaScript YUI Compressor* debes añadir la siguiente configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - yui_js: - jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'yui_js' => array( - 'jar' => '%kernel.root_dir%/Resources/java/yuicompressor.jar', - ), - ), - )); - -Ahora, para realmente *usar* el filtro en un grupo de archivos *JavaScript*, añade esto a tu plantilla: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), - array('yui_js') - ) as $url): ?> - - - -Puedes encontrar una guía más detallada sobre la configuración y uso de filtros ``Assetic`` así como detalles del modo de depuración ``Assetic`` en :doc:`/cookbook/assetic/yuicompressor`. - -Controlando la *URL* utilizada ------------------------------- - -Si quieres, puedes controlar las *URL* que produce *Assetic*. Esto se hace desde la plantilla y es relativo a la raíz del documento público: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), - array(), - array('output' => 'js/compiled/main.js') - ) as $url): ?> - - - -.. note:: - - *Symfony* también contiene un método para caché *rota*, donde la *URL* final generada por ``Assetic`` en el entorno ``prod`` contiene un parámetro de consulta que puedes incrementar por medio de configuración en cada despliegue. Para más información, consulta la opción de configuración :ref:`ref-framework-assets-version`. - -.. _cookbook-assetic-dumping: - -Volcando archivos de activos ----------------------------- - -En el entorno ``dev``, ``Assetic`` genera rutas para los archivos *CSS* y *JavaScript* que no existen físicamente en el ordenador. Pero, sin embargo, los reproduce porque un controlador interno de *Symfony* abre y sirve los archivos volcando el contenido (después de ejecutar todos los filtros). - -Este tipo de servicio dinámico de procesar los activos es muy bueno porque significa que puedes ver inmediatamente el nuevo estado de los archivos de activos que cambies. -Por otro lado es malo, porque puede ser bastante lento. Si estás utilizando una gran cantidad de filtros, puede ser realmente frustrante. - -Afortunadamente, ``Assetic`` proporciona una manera de volcar tus activos a los archivos reales, en lugar de generarlos dinámicamente. - -Volcando archivos de activos en el entorno ``prod`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -En entorno ``prod``, cada uno de tus archivos *JS* y *CSS* está representado por una sola etiqueta. En otras palabras, en lugar de incluir cada archivo *JavaScript* en tu código fuente, probablemente acabes viendo algo como esto: - -.. code-block:: html - - - -Por otra parte, ese archivo **no** existe en realidad, ni es reproducido dinámicamente por *Symfony* (debido a que los archivos de activos se encuentran en el entorno ``dev``). Esto es a propósito ---permitir que *Symfony* genere estos archivos de forma dinámica en un entorno de producción es demasiado lento. - -En cambio, cada vez que utilices tu aplicación en el entorno ``prod`` (y por lo tanto, cada vez que la despliegues), debes ejecutar la siguiente tarea: - -.. code-block:: bash - - $ php app/console assetic:dump --env=prod --no-debug - -Esto va a generar y escribir físicamente todos los archivos que necesitas (por ejemplo ``/js/abcd123.js``). -Si actualizas cualquiera de tus activos, tendrás que ejecutarlo de nuevo para generar el archivo. - -Volcando archivos de activos en el entorno ``dev`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Por omisión, *Symfony* procesa dinámicamente cada ruta de activo generada en el entorno ``dev``. Esto no tiene ninguna desventaja (puedes ver tus cambios inmediatamente), salvo que los activos se pueden cargar notablemente lento. Si sientes que tus activos se cargan demasiado lento, sigue esta guía. - -En primer lugar, dile a *Symfony* que deje de intentar procesar estos archivos de forma dinámica. Haz el siguiente cambio en tu archivo :file:`config_dev.yml`: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - assetic: - use_controller: false - - .. code-block:: xml - - - - - .. code-block:: php - - // app/config/config_dev.php - $container->loadFromExtension('assetic', array( - 'use_controller' => false, - )); - -A continuación, debido a que *Symfony* ya no genera estos activos para ti, tendrás que deshacerte de ellos manualmente. Para ello, ejecuta lo siguiente: - -.. code-block:: bash - - $ php app/console assetic:dump - -Esto escribe físicamente todos los archivos de activos que necesita tu entorno ``dev``. La gran desventaja es que necesitas hacerlo manualmente cada vez que actualizas tus activos. Afortunadamente, pasando la opción ``--watch``, la orden regenerará automáticamente tus *activos a medida que cambien*: - -.. code-block:: bash - - $ php app/console assetic:dump --watch - -Debido a que ejecutas esta orden en el entorno ``dev`` puede generar un montón de archivos, por lo general es una buena idea apuntar tus archivos de activos a un directorio aislado (por ejemplo ``/js/compiled``), para mantener las cosas organizadas: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/*' output='js/compiled/main.js' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), - array(), - array('output' => 'js/compiled/main.js') - ) as $url): ?> - - diff --git a/_sources/cookbook/assetic/index.txt b/_sources/cookbook/assetic/index.txt deleted file mode 100644 index 19328ef..0000000 --- a/_sources/cookbook/assetic/index.txt +++ /dev/null @@ -1,10 +0,0 @@ -``Assetic`` -=========== - -.. toctree:: - :maxdepth: 2 - - asset_management - yuicompressor - jpeg_optimize - apply_to_option diff --git a/_sources/cookbook/assetic/jpeg_optimize.txt b/_sources/cookbook/assetic/jpeg_optimize.txt deleted file mode 100644 index fb082ab..0000000 --- a/_sources/cookbook/assetic/jpeg_optimize.txt +++ /dev/null @@ -1,243 +0,0 @@ -.. index:: - single: Assetic; Optimizando imágenes - -Cómo utilizar ``Assetic`` para optimizar imágenes con funciones *Twig* -====================================================================== - -Entre sus muchos filtros, ``Assetic`` tiene cuatro filtros que puedes utilizar para optimizar imágenes al vuelo. Esto te permite obtener el beneficio de archivos de menor tamaño sin tener que usar un editor de imágenes para procesar cada imagen. Los resultados se almacenan en caché y se puede vaciar en producción para que no haya impacto en el rendimiento para los usuarios finales. - -Usando *Jpegoptim* ------------------- - -`Jpegoptim`_ es una utilidad para la optimización de archivos JPEG. Para usarlo con ``Assetic``, añade lo siguiente a la configuración de ``Assetic``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: ruta/a/jpegoptim - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'jpegoptim' => array( - 'bin' => 'ruta/a/jpegoptim', - ), - ), - )); - -.. note:: - - Ten en cuenta que al usar *jpegoptim*, ya lo debes tener instalado en tu sistema. La opción ``bin`` apunta a la ubicación de los binarios compilados. - -Ahora lo puedes utilizar desde una plantilla: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% image '@AcmeFooBundle/Resources/public/images/example.jpg' - filter='jpegoptim' output='/images/example.jpg' %} - Example - {% endimage %} - - .. code-block:: html+php - - images( - array('@AcmeFooBundle/Resources/public/images/example.jpg'), - array('jpegoptim') - ) as $url): ?> - Example - - -Eliminando todos los datos ``EXIF`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -De manera predeterminada, al ejecutar este filtro sólo eliminas parte de la metainformación almacenada en el archivo. Todos los datos ``EXIF`` ​​y comentarios no se eliminan, pero los puedes quitar usando la opción ``strip_all``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: ruta/a/jpegoptim - strip_all: true - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'jpegoptim' => array( - 'bin' => 'path/to/jpegoptim', - 'strip_all' => 'true', - ), - ), - )); - -Reduciendo la calidad máxima -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El nivel de calidad del *JPEG* de manera predeterminada no se ve afectado. Puedes obtener mayor reducción de tamaño del archivo estableciendo la configuración de calidad máxima más baja que el nivel actual de las imágenes. Esto, por supuesto, a expensas de la calidad de la imagen: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: ruta/a/jpegoptim - max: 70 - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'jpegoptim' => array( - 'bin' => 'ruta/a/jpegoptim', - 'max' => '70', - ), - ), - )); - -Sintaxis corta: Función *Twig* ------------------------------- - -Si estás utilizando *Twig*, es posible lograr todo esto con una sintaxis más corta habilitando y utilizando una función especial de *Twig*. Comienza por agregar la siguiente configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: ruta/a/jpegoptim - twig: - functions: - jpegoptim: ~ - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'jpegoptim' => array( - 'bin' => 'ruta/a/jpegoptim', - ), - ), - 'twig' => array( - 'functions' => array('jpegoptim'), - ), - ), - )); - -Ahora,puedes cambiar la plantilla *Twig* a lo siguiente: - -.. code-block:: html+jinja - - Example - -Puedes especificar el directorio de salida en la configuración de la siguiente manera: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - filters: - jpegoptim: - bin: ruta/a/jpegoptim - twig: - functions: - jpegoptim: { output: images/*.jpg } - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - 'filters' => array( - 'jpegoptim' => array( - 'bin' => 'ruta/a/jpegoptim', - ), - ), - 'twig' => array( - 'functions' => array( - 'jpegoptim' => array( - output => 'images/*.jpg' - ), - ), - ), - )); - -.. _`Jpegoptim`: http://www.kokkonen.net/tjko/projects.html diff --git a/_sources/cookbook/assetic/yuicompressor.txt b/_sources/cookbook/assetic/yuicompressor.txt deleted file mode 100644 index 6e3cc26..0000000 --- a/_sources/cookbook/assetic/yuicompressor.txt +++ /dev/null @@ -1,141 +0,0 @@ -.. index:: - single: Assetic; YUI Compressor - -Cómo minimizar *JavaScript* y hojas de estilo con *YUI Compressor* -================================================================== - -Yahoo! proporciona una excelente utilidad para minimizar (minify) *JavaScript* y hojas de estilo para que viajen más rápido por la red, el `YUI Compressor`_. Gracias a ``Assetic``, puedes tomar ventaja de esta herramienta con mucha facilidad. - -Descargando el *JAR* de *YUI Compressor* ----------------------------------------- - -El *YUI Compressor* está escrito en *Java* y se distribuye como *JAR*. `Descarga el JAR`_ desde el sitio Yahoo! y guárdalo en ``app/Resources/java/yuicompressor.jar``. - -Configurando los filtros de *YUI* ---------------------------------- - -Ahora debes configurar dos *filtros* ``Assetic`` en tu aplicación, uno para minimizar *JavaScript* con el compresor *YUI* y otro para minimizar hojas de estilo: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - assetic: - # java: "/usr/bin/java" - filters: - yui_css: - jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" - yui_js: - jar: "%kernel.root_dir%/Resources/java/yuicompressor.jar" - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('assetic', array( - // 'java' => '/usr/bin/java', - 'filters' => array( - 'yui_css' => array( - 'jar' => '%kernel.root_dir%/Resources/java/yuicompressor.jar', - ), - 'yui_js' => array( - 'jar' => '%kernel.root_dir%/Resources/java/yuicompressor.jar', - ), - ), - )); - -.. note:: - - Los usuarios de Windows se tienen que acordar de actualizar la configuración a la ubicación correcta de Java. - En Windows 7 x64 bits por omisión es ``C:\Archivos de programa (x86)\Java\jre6\bin\java.exe``. - -Ahora tienes acceso a dos nuevos *filtros* ``Assetic`` en tu aplicación: -``yui_css`` y ``yui_js``. Estos utilizan el compresor de *YUI* para minimizar hojas de estilo y *JavaScript*, respectivamente. - -Minimizando tus activos ------------------------ - -Ahora tienes configurado el compresor *YUI*, pero nada va a pasar hasta que apliques uno de estos filtros a un activo. Dado que tus activos son una parte de la capa de la vista, este trabajo se hace en tus plantillas: - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), - array('yui_js') - ) as $url): ?> - - - -.. note:: - - El ejemplo anterior asume que tienes un paquete llamado ``AcmeFooBundle`` y tus archivos *JavaScript* están bajo el directorio ``Resources/public/js`` de tu paquete. No obstante, esto no es importante ---puedes incluir tus archivos *Javascript* sin importar donde se encuentren. - -Con la incorporación del filtro ``yui_js`` a las etiquetas de los activos anteriores, ahora deberías ver llegar mucho más rápido tus *JavaScripts* minimizados a través del cable. Puedes repetir el mismo proceso para minimizar tus hojas de estilo. - -.. configuration-block:: - - .. code-block:: html+jinja - - {% stylesheets '@AcmeFooBundle/Resources/public/css/*' filter='yui_css' %} - - {% endstylesheets %} - - .. code-block:: html+php - - stylesheets( - array('@AcmeFooBundle/Resources/public/css/*'), - array('yui_css') - ) as $url): ?> - - - -Desactivando la minimización en modo de depuración --------------------------------------------------- - -El *JavaScript* y las hojas de estilo minimizadas son muy difíciles de leer, y mucho más de depurar. Debido a esto, ``Assetic`` te permite desactivar un determinado filtro cuando tu aplicación está en modo de depuración. Para ello, puedes prefijar el nombre del filtro en tu plantilla con un signo de interrogación: ``?``. Esto le dice a ``Assetic`` que aplique este filtro sólo cuando el modo de depuración está desactivado. - -.. configuration-block:: - - .. code-block:: html+jinja - - {% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='?yui_js' %} - - {% endjavascripts %} - - .. code-block:: html+php - - javascripts( - array('@AcmeFooBundle/Resources/public/js/*'), - array('?yui_js') - ) as $url): ?> - - - - -.. tip:: - - En lugar de añadir el filtro a las etiquetas de activos, también lo puedes activar globalmente añadiendo el atributo ``apply-to`` a la configuración del filtro, por ejemplo, en el filtro ``yui_js apply_to: "\.js$"``. Para aplicar el filtro sólo en producción, agrega esto al archivo ``config_prod`` en lugar de al archivo de configuración común. Para más detalles sobre la aplicación de filtros por extensión de archivo, ve :ref:`cookbook-assetic-apply-to`. - - -.. _`YUI Compressor`: http://developer.yahoo.com/yui/compressor/ -.. _`Descarga el JAR`: http://yuilibrary.com/projects/yuicompressor/ diff --git a/_sources/cookbook/bundles/best_practices.txt b/_sources/cookbook/bundles/best_practices.txt deleted file mode 100644 index 5d1adb6..0000000 --- a/_sources/cookbook/bundles/best_practices.txt +++ /dev/null @@ -1,231 +0,0 @@ -.. index:: - single: Paquete; Buenas prácticas - -Cómo usar las mejores prácticas para estructurar paquetes -========================================================= - -Un paquete es un directorio que tiene una estructura bien definida y puede alojar cualquier cosa, desde clases hasta controladores y recursos web. A pesar de que los paquetes son tan flexibles, se deben seguir algunas recomendaciones si deseas distribuirlos. - -.. index:: - pair: Paquete; Convenciones de nomenclatura - -.. _bundles-naming-conventions: - -Nombre de paquete ------------------ - -Un paquete también es un espacio de nombres *PHP*. El espacio de nombres debe seguir los `estándares`_ de interoperabilidad técnica de los espacios de nombres y nombres de clases de *PHP* 5.3: comienza con un segmento de proveedor, seguido por cero o más segmentos de categoría, y termina con el nombre corto del espacio de nombres, el cual debe terminar con el sufijo ``Bundle``. - -Un espacio de nombres se convierte en un paquete tan pronto como se agrega una clase ``bundle`` al mismo. El nombre de la clase ``bundle`` debe seguir estas sencillas reglas: - -* Solo utiliza caracteres alfanuméricos y guiones bajos; -* Usa mayúsculas intercaladas en el nombre; -* Usa un nombre corto pero descriptivo (de no más de dos palabras); -* Prefija el nombre con la concatenación del proveedor (y, opcionalmente, la categoría del espacio de nombres); -* Sufija el nombre con ``Bundle``. - -Estos son algunos espacios de nombres y nombres de clase ``bundle`` válidos: - -+-----------------------------------+----------------------------+ -| Espacio de nombres | Nombre de clase ``Bundle`` | -+===================================+============================+ -| ``Acme\Bundle\BlogBundle`` | ``AcmeBlogBundle`` | -+-----------------------------------+----------------------------+ -| ``Acme\Bundle\Social\BlogBundle`` | ``AcmeSocialBlogBundle`` | -+-----------------------------------+----------------------------+ -| ``Acme\BlogBundle`` | ``AcmeBlogBundle`` | -+-----------------------------------+----------------------------+ - -Por convención, el método ``getName()`` de la clase ``bundle`` debe devolver el nombre de la clase. - -.. note:: - - Si compartes tu paquete públicamente, debes utilizar el nombre de la clase ``bundle`` como nombre del repositorio (``AcmeBlogBundle`` y no ``BlogBundle`` por ejemplo). - -.. note:: - - Los paquetes del núcleo de *Symfony2* no prefijan la clase ``Bundle`` con ``Symfony`` y siempre agregan un subespacio de nombres ``Bundle``; por ejemplo: - :class:`Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle`. - -Cada paquete tiene un alias, el cual es la versión corta en minúsculas del nombre del paquete con guiones bajos (``acme_hello`` para ``AcmeHelloBundle``, o ``acme_social_blog`` para ``Acme\Social\BlogBundle`` por ejemplo). Este alias se utiliza para forzar la exclusividad dentro de un paquete (ve abajo algunos ejemplos de uso). - -Estructura del directorio -------------------------- - -La estructura básica del directorio del paquete ``HelloBundle`` se debe leer de la siguiente manera: - -.. code-block:: text - - XXX/... - HelloBundle/ - HelloBundle.php - Controller/ - Resources/ - meta/ - LICENSE - config/ - doc/ - index.rst - translations/ - views/ - public/ - Tests/ - -Los directorios ``XXX`` reflejan la estructura del espacio de nombres del paquete. - -Los siguientes archivos son obligatorios: - -* :file:`HelloBundle.php`; -* ``Resources/meta/LICENSE``: La licencia completa para el código; -* ``Resources/doc/index.rst``: El archivo raíz de la documentación del paquete. - -.. note:: - - Estos convenios garantizan que las herramientas automatizadas pueden trabajar confiablemente en esta estructura predeterminada. - -La profundidad de los subdirectorios se debe reducir al mínimo en la mayoría de las clases y archivos utilizados (2 niveles como máximo). Puedes definir más niveles para archivos no estratégicos, los menos utilizados. - -El directorio del paquete es de sólo lectura. Si necesitas escribir archivos temporales, guárdalos en el directorio ``cache/`` o ``log/`` de la aplicación anfitriona. Las herramientas pueden generar archivos en la estructura de directorios del paquete, pero sólo si los archivos generados van a formar parte del repositorio. - -Las siguientes clases y archivos tienen emplazamientos específicos: - -+---------------------------------------+-----------------------------+ -| Tipo | Directorio | -+=======================================+=============================+ -| Ordenes | ``Command/`` | -+---------------------------------------+-----------------------------+ -| Controladores | ``Controller/`` | -+---------------------------------------+-----------------------------+ -| Extensiones contenedoras de servicios | ``DependencyInjection/`` | -+---------------------------------------+-----------------------------+ -| Escuchas de eventos | ``EventListener/`` | -+---------------------------------------+-----------------------------+ -| Configuración | ``Resources/config/`` | -+---------------------------------------+-----------------------------+ -| Recursos *Web* | ``Resources/public/`` | -+---------------------------------------+-----------------------------+ -| Archivos de traducción | ``Resources/translations/`` | -+---------------------------------------+-----------------------------+ -| Plantillas | ``Resources/views/`` | -+---------------------------------------+-----------------------------+ -| Pruebas unitarias y funcionales | ``Tests/`` | -+---------------------------------------+-----------------------------+ - -Clases ------- - -La estructura del directorio de un paquete se utiliza como la jerarquía del espacio de nombres. Por ejemplo, un controlador ``HelloController`` se almacena en ``/HelloBundle/Controller/HelloController.php`` y el nombre de clase completamente cualificado es ``Bundle\HelloBundle\Controller\HelloController``. - -Todas las clases y archivos deben seguir los :doc:`estándares ` de codificación *Symfony2*. - -Algunas clases se deben ver como fachada y deben ser lo más breves posible, al igual que las ordenes, ayudantes, escuchas y controladores. - -Las clases que conectan el Evento al Despachador deben llevar el posfijo ``Listener``. - -Las clases de excepciones se deben almacenar en un subespacio de nombres ``Exception``. - -Terceros --------- - -Un paquete no debe integrar bibliotecas *PHP* de terceros. Se debe confiar en la carga automática estándar de *Symfony2* en su lugar. - -Un paquete no debería integrar bibliotecas de terceros escritas en *JavaScript*, *CSS* o cualquier otro lenguaje. - -Pruebas -------- - -Un paquete debe venir con una batería de pruebas escritas con *PHPUnit* , las cuales se deben almacenar en el directorio ``Test/``. Las pruebas deben seguir los siguientes principios: - -* La batería de pruebas se debe ejecutar con una simple orden ``phpunit`` desde una aplicación de ejemplo; -* Las pruebas funcionales sólo se deben utilizar para probar la respuesta de salida y alguna información de perfilado si tiene alguno; -* Las pruebas por lo menos deben cubrir el 95% del código base. - -.. note:: - Una batería de pruebas no debe contener archivos :file:`AllTests.php`, sino que se debe basar en la existencia de un archivo :file:`phpunit.xml.dist`. - -Documentación -------------- - -Todas las clases y funciones deben venir con *PHPDoc* completo. - -También deberá proporcionar abundante documentación provista en formato :doc:`reStructuredText `, bajo el directorio ``Resources/doc/``; el archivo ``Resources/doc/index.rst`` es el único archivo obligatorio y debe ser el punto de entrada para la documentación. - -Controladores -------------- - -Como práctica recomendada, los controladores en un paquete que está destinado a ser distribuido a otros no debe extender la clase base :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller``. -Puede implementar la :class:`Symfony\\Component\\DependencyInjection\\ContainerAwareInterface` o en su lugar extender la clase :class:`Symfony\\Component\\DependencyInjection\\ContainerAware`. - -.. note:: - - Si echas un vistazo a los métodos de la clase :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller`, podrás ver que sólo son buenos accesos directos para facilitar la curva de aprendizaje. - -Enrutado --------- - -Si el paquete proporciona rutas, estas se deben prefijar con el alias del paquete. -Para un ``AcmeBlogBundle`` por ejemplo, todas las rutas deben llevar el prefijo ``acme_blog_``. - -Plantillas ----------- - -Si un paquete proporciona plantillas, estas deben utilizar *Twig*. Un paquete no debe proporcionar un diseño principal, salvo si ofrece una aplicación completa. - -Archivos de traducción ----------------------- - -Si un paquete proporciona traducción de mensajes, se deben definir en formato *XLIFF*; el dominio se debe mencionar después del nombre del paquete (``bundle.hello``). - -Un paquete no debe reemplazar los mensajes de otro paquete existente. - -Configurando ------------- - -Para proporcionar mayor flexibilidad, un paquete puede proporcionar opciones configurables utilizando los mecanismos integrados de *Symfony2*. - -Para ajustes de configuración simples, confía en los ``parámetros`` predeterminados de la configuración de *Symfony2*. Los parámetros de *Symfony2* simplemente son pares clave/valor; un valor es cualquier valor *PHP* válido. El nombre de cada parámetro debe comenzar con el alias del paquete, aunque esto es sólo una sugerencia de buenas prácticas. El resto del nombre del parámetro utiliza un punto (``.``) para separar las diferentes partes (por ejemplo, ``acme_hello.email.from``). - -El usuario final puede proporcionar valores en cualquier archivo de configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - parameters: - acme_hello.email.from: fabien@example.com - - .. code-block:: xml - - - - fabien@example.com - - - .. code-block:: php - - // app/config/config.php - $container->setParameter('acme_hello.email.from', 'fabien@example.com'); - - .. code-block:: yaml - - ; app/config/config.ini - [parameters] - acme_hello.email.from = fabien@example.com - -Recupera los parámetros de configuración en tu código desde el contenedor:: - - $container->getParameter('acme_hello.email.from'); - -Incluso si este mecanismo es bastante simple, te animamos a usar la configuración semántica descrita en el recetario. - -.. note:: - - Si vas a definir servicios, estos también se deben prefijar con el alias del paquete. - -Aprende más en el recetario ---------------------------- - -* :doc:`/cookbook/bundles/extension` - -.. _`estándares`: http://symfony.com/PSR0 diff --git a/_sources/cookbook/bundles/extension.txt b/_sources/cookbook/bundles/extension.txt deleted file mode 100644 index 8d74469..0000000 --- a/_sources/cookbook/bundles/extension.txt +++ /dev/null @@ -1,497 +0,0 @@ -.. index:: - single: Configuración; Semántica - single: Paquete; Configuración de extensión - -Cómo exponer la configuración semántica de un paquete -===================================================== - -Si abres el archivo de configuración de tu aplicación (por lo general :file:`app/config/config.yml`), puedes encontrar una serie de configuraciones de diferentes «espacios de nombres», como ``framework``, ``twig`` y ``doctrine``. Cada una de estas configura un paquete específico, lo cual te permite configurar las cosas a nivel superior y luego dejar que el paquete haga todo lo de bajo nivel, haciendo los cambios complejos que resulten. - -Por ejemplo, el siguiente fragmento le dice al ``FrameworkBundle`` que habilite la integración de formularios, lo cual implica la definición de unos cuantos servicios, así como la integración de otros componentes relacionados: - -.. configuration-block:: - - .. code-block:: yaml - - framework: - # ... - form: true - - .. code-block:: xml - - - - - - .. code-block:: php - - $container->loadFromExtension('framework', array( - // ... - 'form' => true, - // ... - )); - -Cuando creas un paquete, tienes dos opciones sobre cómo manejar la configuración: - -1. **Configuración normal del servicio** (*fácil*): - - Puedes especificar tus servicios en un archivo de configuración (por ejemplo, :file:`services.yml`) que vive en tu paquete y luego importarlo desde la configuración principal de tu aplicación. Esto es realmente fácil, rápido y completamente eficaz. Si usas :ref:`parámetros `, entonces todavía tienes cierta flexibilidad para personalizar el paquete desde la configuración de tu aplicación. Consulta «:ref:`service-container-imports-directive`» para más detalles. - -2. **Exponiendo la configuración semántica** (*avanzado*): - - Esta es la forma de configuración que se hace con los paquetes básicos (como se describió anteriormente). La idea básica es que, en lugar de permitir al usuario sustituir parámetros individuales, permites al usuario configurar unos cuantos, en concreto la creación de opciones. A medida que desarrollas el paquete, vas analizando la configuración y cargas tus servicios en una clase «Extensión». Con este método, no tendrás que importar ningún recurso de configuración desde la configuración principal de tu aplicación: la clase ``Extension`` puede manejar todo esto. - -La segunda opción ---de la cual aprenderás en este artículo--- es mucho más flexible, pero también requiere más tiempo de configuración. Si te preguntas qué método debes utilizar, probablemente sea una buena idea empezar con el método #1, y más adelante, si es necesario, cambiar al #2. - -El segundo método tiene varias ventajas específicas: - -* Es mucho más poderoso que la simple definición de parámetros: un valor de opción específico podría inducir la creación de muchas definiciones de servicios; - -* La habilidad de tener jerarquías de configuración - -* La fusión inteligente de varios archivos de configuración (por ejemplo, :file:`config_dev.yml` y :file:`config.yml`) sustituye los demás ajustes; - -* Configurando la validación (si utilizas una :ref:`clase Configuración `); - -* Autocompletado en tu *IDE* cuando creas un *XSD* y los desarrolladores del *IDE* utilizan *XML*. - -.. sidebar:: Sustituyendo parámetros del paquete - - Si un paquete proporciona una clase *Extension*, entonces, generalmente *no debes* reemplazar los parámetros del contenedor de servicios de ese paquete. La idea es que si está presente una clase ``Extension``, cada ajuste que deba ser configurable debe estar presente en la configuración disponible en esa clase. En otras palabras, la clase ``Extension`` define cómo dibulgas todas las opciones de configuración apoyadas para las cuales, por mantenimiento, existe compatibilidad hacia atrás. - -.. index:: - single: Paquete; Extensión - single: Inyección de dependencias; Extensión - -Creando una clase ``Extension`` -------------------------------- - -Si eliges exponer una configuración semántica de tu paquete, primero tendrás que crear una nueva clase ``«Extension»``, la cual debe manejar el proceso. -Esta clase debe vivir en el directorio ``DependencyInjection`` de tu paquete y su nombre se debe construir reemplazando el sufijo ``Bundle`` del nombre de la clase del paquete con ``Extension``. Por ejemplo, la clase ``Extension`` de ``AcmeHelloBundle`` se llamaría ``AcmeHelloExtension``:: - - // Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php - namespace Acme\HelloBundle\DependencyInjection; - - use Symfony\Component\HttpKernel\DependencyInjection\Extension; - use Symfony\Component\DependencyInjection\ContainerBuilder; - - class AcmeHelloExtension extends Extension - { - public function load(array $configs, ContainerBuilder $container) - { - // ... aquí se lleva a cabo toda la lógica - } - - public function getXsdValidationBasePath() - { - return __DIR__.'/../Resources/config/'; - } - - public function getNamespace() - { - return 'http://www.ejemplo.com/symfony/schema/'; - } - } - -.. note:: - - Los métodos ``getXsdValidationBasePath`` y ``getNamespace`` sólo son necesarios si el paquete opcional XSD proporciona la configuración. - -La presencia de la clase anterior significa que ahora puedes definir una configuración de espacio de nombres ``acme_hello`` en cualquier archivo de configuración. El espacio de nombres ``acme_hello`` se construyó a partir del nombre en minúsculas de la clase ``Extension`` eliminando la palabra ``Extension``, a continuación un guión bajo y el resto del nombre. En otras palabras, ``AcmeHelloExtension`` se convierte en ``acme_hello``. - -Puedes empezar de inmediato, especificando la configuración en este espacio de nombres: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - acme_hello: ~ - - .. code-block:: xml - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('acme_hello', array()); - -.. tip:: - - Si sigues las convenciones de nomenclatura mencionadas anteriormente, entonces el método ``load()`` el cual carga el código de tu extensión es llamado siempre que tu paquete sea registrado en el núcleo. En otras palabras, incluso si el usuario no proporciona ninguna configuración (es decir, la entrada ``acme_hello`` ni siquiera figura), el método ``load()`` será llamado y se le pasará un arreglo ``$configs`` vacío. Todavía puedes proporcionar algunos parámetros predeterminados para tu paquete si lo deseas. - -Analizando el arreglo ``$configs`` ----------------------------------- - -Cada vez que un usuario incluya el espacio de nombres ``acme_hello`` en un archivo de configuración, la configuración bajo este se agrega a un gran arreglo de configuraciones y se pasa al método ``load()`` de tu extensión (*Symfony2* automáticamente convierte *XML* y *YAML* a un arreglo). - -Tomemos la siguiente configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - acme_hello: - foo: fooValue - bar: barValue - - .. code-block:: xml - - - - - - - - barValue - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('acme_hello', array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - )); - -El arreglo pasado a tu método ``load()`` se verá así:: - - array( - array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - ), - ) - -Ten en cuenta que se trata de un *arreglo de arreglos*, y no sólo un único arreglo plano con los valores de configuración. Esto es intencional. Por ejemplo, si ``acme_hello`` aparece en otro archivo de configuración ---digamos en :file:`config_dev.yml`--- con diferentes valores bajo él, entonces el arreglo entrante puede tener este aspecto:: - - array( - array( - 'foo' => 'fooValue', - 'bar' => 'barValue', - ), - array( - 'foo' => 'fooDevValue', - 'baz' => 'newConfigEntry', - ), - ) - -El orden de los dos arreglos depende de cuál es el primer conjunto. - -Entonces, es tu trabajo, decidir cómo se fusionan estas configuraciones. Es posible que, por ejemplo, después tengas que sustituir valores anteriores o alguna combinación de ellos. - -Más tarde, en la sección :ref:`clase Configuración `, aprenderás una forma realmente robusta para manejar esto. Pero por ahora, sólo puedes combinarlos manualmente:: - - public function load(array $configs, ContainerBuilder $container) - { - $config = array(); - foreach ($configs as $subConfig) { - $config = array_merge($config, $subConfig); - } - - // ... ahora usa el arreglo $config plano - } - -.. caution:: - - Asegúrate de que la técnica de fusión anterior tenga sentido para tu paquete. Este es sólo un ejemplo, y debes tener cuidado de no usarlo a ciegas. - -Usando el método ``load()`` ---------------------------- - -Dentro de ``load()``, la variable ``$container`` se refiere a un contenedor que sólo sabe acerca de esta configuración de espacio de nombres (es decir, no contiene información de los servicios cargados por otros paquetes). El objetivo del método ``load()`` es manipular el contenedor, añadir y configurar cualquier método o servicio necesario por tu paquete. - -Cargando la configuración de recursos externos -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Una de las cosas comunes por hacer es cargar un archivo de configuración externo que puede contener la mayor parte de los servicios que necesita tu paquete. Por ejemplo, supongamos que tienes un archivo ``services.xml`` el cual contiene gran parte de la configuración de los servicios en tu paquete:: - - use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; - use Symfony\Component\Config\FileLocator; - - public function load(array $configs, ContainerBuilder $container) - { - // ... prepara tu variable $config - - $loader = new XmlFileLoader( - $container, - new FileLocator(__DIR__.'/../Resources/config') - ); - $loader->load('services.xml'); - } - -Incluso lo podrías hacer condicionalmente, basándote en uno de los valores de configuración. -Por ejemplo, supongamos que sólo deseas cargar un conjunto de servicios si una opción ``habilitado`` es pasada y fijada en ``true``:: - - public function load(array $configs, ContainerBuilder $container) - { - // ... prepara tu variable $config - - $loader = new XmlFileLoader( - $container, - new FileLocator(__DIR__.'/../Resources/config') - ); - - if (isset($config['enabled']) && $config['enabled']) { - $loader->load('services.xml'); - } - } - -Configurando servicios y ajustando parámetros -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Una vez que hayas cargado alguna configuración de servicios, posiblemente necesites modificar la configuración basándote en alguno de los valores entrantes. Por ejemplo, supón que tienes un servicio cuyo primer argumento es algún «tipo» de cadena que se debe utilizar internamente. Quisieras que el usuario del paquete lo configurara fácilmente, por tanto en el archivo de configuración de tu servicio (por ejemplo, ``services.xml``), defines este servicio y utilizas un parámetro en blanco ---``acme_hello.my_service_options``--- como primer argumento: - -.. code-block:: xml - - - - - - - - - - - %acme_hello.my_service_type% - - - - -Pero ¿por qué definir un parámetro vacío y luego pasarlo a tu servicio? -La respuesta es que vas a establecer este parámetro en tu clase ``Extension``, basándote en los valores de configuración entrantes. Supongamos, por ejemplo, que deseas permitir al usuario definir esta opción de *tipo* en una clave denominada ``my_type``. -Para hacerlo agrega lo siguiente al método ``load()``:: - - public function load(array $configs, ContainerBuilder $container) - { - // ... prepara tu variable $config - - $loader = new XmlFileLoader( - $container, - new FileLocator(__DIR__.'/../Resources/config') - ); - $loader->load('services.xml'); - - if (!isset($config['my_type'])) { - throw new \InvalidArgumentException( - 'The "my_type" option must be set' - ); - } - - $container->setParameter( - 'acme_hello.my_service_type', - $config['my_type'] - ); - } - -Ahora, el usuario puede configurar eficientemente el servicio especificando el valor de configuración ``my_type``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - acme_hello: - my_type: foo - # ... - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('acme_hello', array( - 'my_type' => 'foo', - ..., - )); - -Parámetros globales -~~~~~~~~~~~~~~~~~~~ - -Cuando configures el contenedor, tienes que estar consciente de que los siguientes parámetros globales están disponibles para que los utilices: - -* ``kernel.name`` -* ``kernel.environment`` -* ``kernel.debug`` -* ``kernel.root_dir`` -* ``kernel.cache_dir`` -* ``kernel.logs_dir`` -* ``kernel.bundle_dirs`` -* ``kernel.bundles`` -* ``kernel.charset`` - -.. caution:: - - Todos los nombres de los parámetros y servicios que comienzan con un guión bajo (``_``) están reservados para la plataforma, y no los debes definir en tus nuevos paquetes. - -.. _cookbook-bundles-extension-config-class: - -Validando y fusionando con una clase configuración --------------------------------------------------- - -Hasta ahora, has fusionado manualmente los arreglos de configuración y los has comprobado por medio de la presencia de los valores de configuración utilizando la función ``isset()`` de *PHP*. También hay disponible un sistema de *configuración* opcional, el cual puede ayudar con la fusión, validación, valores predeterminados y normalización de formato. - -.. note:: - - *Normalización de formato* se refiere al hecho de que ciertos formatos ---en su mayoría *XML*--- resultan en arreglos de configuración ligeramente diferentes, y que estos arreglos se deben «normalizar» para que coincidan con todo lo demás. - -Para aprovechar las ventajas de este sistema, debes crear una clase ``Configuración`` y construir un árbol que define tu configuración en esa clase:: - - // src/Acme/HelloBundle/DependencyInjection/Configuration.php - namespace Acme\HelloBundle\DependencyInjection; - - use Symfony\Component\Config\Definition\Builder\TreeBuilder; - use Symfony\Component\Config\Definition\ConfigurationInterface; - - class Configuration implements ConfigurationInterface - { - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('acme_hello'); - - $rootNode - ->children() - ->scalarNode('my_type')->defaultValue('bar')->end() - ->end(); - - return $treeBuilder; - } - } - -Se trata de un ejemplo *muy* sencillo, pero ahora puedes utilizar esta clase en el método ``load()`` para combinar tu configuración y forzar su validación. Si se pasan las demás opciones salvo ``my_type``, el usuario recibirá una notificación con una excepción de que se ha pasado una opción no admitida:: - - public function load(array $configs, ContainerBuilder $container) - { - $configuration = new Configuration(); - - $config = $this->processConfiguration($configuration, $configs); - - // ... - } - -El método ``processConfiguration()`` utiliza el árbol de configuración que has definido en la clase ``Configuración`` para validar, normalizar y fusionar todos los arreglos de configuración. - -La clase ``Configuración`` puede ser mucho más complicada de lo que se muestra aquí, apoyando arreglos de nodos, nodos «prototipo», validación avanzada, normalización *XML* específica y fusión avanzada. Puedes leer más sobre esto en la :doc:`documentación de la configuración del componente `. -También lo puedes ver en acción comprobando alguna de las clases de configuración del núcleo, -como la `configuración del FrameworkBundle`_ o la `configuración del TwigBundle`_. - -Modificando la configuración de otro paquete -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si tienes múltiples paquetes que dependen unos de otros, puede ser útil permitir una clase ``Extension`` para modificar la configuración pasada a la clase ``Extension`` de otro paquete, como si el desarrollador final de hecho haya colocado la configuración en su archivo :file:`app/config/config.yml`. - -Para más detalles, ve :doc:`/cookbook/bundles/prepend_extension`. - -Volcando la configuración predefinida -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - La orden ``config:dump-reference`` se añadió en *Symfony 2.1* - -La orden ``config:dump-reference`` permite volcar a la consola la configuración predefinida de un paquete en formato *YAML*. - -Siempre y cuando la configuración de tu paquete se encuentre en la ubicación estándar (``TuPaquete\DependencyInjection\Configuration``) y no tenga un ``__construct()`` esto funcionará automáticamente. Si tienes algo diferente en tu clase ``Extension`` tendrás que sobrescribir el método :method:`Extension::getConfiguration() ` y devolver una instancia de tu ``Configuration``. - -Puedes añadir comentarios y ejemplos a los nodos de tu configuración usando los métodos -``->info()`` y ``->example()``:: - - // src/Acme/HelloBundle/DependencyExtension/Configuration.php - namespace Acme\HelloBundle\DependencyInjection; - - use Symfony\Component\Config\Definition\Builder\TreeBuilder; - use Symfony\Component\Config\Definition\ConfigurationInterface; - - class Configuration implements ConfigurationInterface - { - public function getConfigTreeBuilder() - { - $treeBuilder = new TreeBuilder(); - $rootNode = $treeBuilder->root('acme_hello'); - - $rootNode - ->children() - ->scalarNode('my_type') - ->defaultValue('bar') - ->info('what my_type configures') - ->example('example setting') - ->end() - ->end() - ; - - return $treeBuilder; - } - } - -Este texto aparece en comentarios *YAML* en el resultado de la orden ``config:dump-reference``. - -.. index:: - pair: Convención; Configuración - -Convenciones de extensión -------------------------- - -Al crear una extensión, sigue estas simples convenciones: - -* La extensión se debe almacenar en el subespacio de nombres ``DependencyInjection``; - -* La extensión se debe nombrar después del nombre del paquete y con el sufijo ``Extension`` (``AcmeHelloExtension`` para ``AcmeHelloBundle``); - -* La extensión debe proporcionar un esquema *XSD*. - -Si sigues estas simples convenciones, *Symfony2* registrará automáticamente las extensiones. De no ser así, sustituye el método :method:`Bundle::build() ` en tu paquete:: - - // ... - use Acme\HelloBundle\DependencyInjection\UnconventionalExtensionClass; - - class AcmeHelloBundle extends Bundle - { - public function build(ContainerBuilder $container) - { - parent::build($container); - - // registra manualmente las extensiones que no siguen las convenciones - $container->registerExtension(new UnconventionalExtensionClass()); - } - } - -En este caso, la clase ``Extension`` también debe implementar un método ``getAlias()`` que devuelva un alias único nombrado después del paquete (por ejemplo, ``acme_hello``). Esto es necesario porque el nombre de clase no sigue la norma de terminar en ``Extension``. - -Además, el método ``load()`` de tu extensión *sólo* se llama si el usuario especifica el alias ``acme_hello`` en por lo menos un archivo de configuración. Una vez más, esto se debe a que la clase ``Extension`` no se ajusta a las normas establecidas anteriormente, por lo tanto nada sucede automáticamente. - -.. _`configuración del FrameworkBundle`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php -.. _`configuración del TwigBundle`: https://github.com/symfony/symfony/blob/master/src/Symfony/Bundle/TwigBundle/DependencyInjection/Configuration.php diff --git a/_sources/cookbook/bundles/index.txt b/_sources/cookbook/bundles/index.txt deleted file mode 100644 index 80d549b..0000000 --- a/_sources/cookbook/bundles/index.txt +++ /dev/null @@ -1,13 +0,0 @@ -Paquetes -======== - -.. toctree:: - :maxdepth: 2 - - installation - best_practices - inheritance - override - remove - extension - prepend_extension diff --git a/_sources/cookbook/bundles/inheritance.txt b/_sources/cookbook/bundles/inheritance.txt deleted file mode 100644 index 0a887fc..0000000 --- a/_sources/cookbook/bundles/inheritance.txt +++ /dev/null @@ -1,80 +0,0 @@ -.. index:: - single: Paquete; Herencia - -Cómo utilizar la herencia de paquetes para redefinir partes de un paquete -========================================================================= - -Cuando trabajes con paquetes de terceros, probablemente llegues a una situación en la que desees reemplazar un archivo en ese paquete de terceros con un archivo de uno de tus propios paquetes. *Symfony* te proporciona una forma muy conveniente para sustituir cosas como controladores, plantillas, traducciones, y otros archivos en el directorio ``Resources/`` de los paquetes. - -Por ejemplo, supongamos que estás instalando el `FOSUserBundle`_, pero deseas sustituir su plantilla base ---``layout.html.twig``---, así como uno de sus controladores. Supongamos también que tienes tu propio ``AcmeUserBundle`` donde deseas que vivan los archivos sobrescritos. Para empezar, registra el ``FOSUserBundle`` como el «padre» de tu paquete:: - - // src/Acme/UserBundle/AcmeUserBundle.php - namespace Acme\UserBundle; - - use Symfony\Component\HttpKernel\Bundle\Bundle; - - class AcmeUserBundle extends Bundle - { - public function getParent() - { - return 'FOSUserBundle'; - } - } - -Al hacer este simple cambio, ahora puedes sustituir varias partes del ``FOSUserBundle`` simplemente creando un archivo con el mismo nombre. - -.. note:: - - A pesar del nombre del método, entre paquetes no hay una relación padre/hijo, sólo es una manera de extender y reemplazar un paquete existente. - -Sustituyendo controladores -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Supongamos que deseas añadir alguna funcionalidad al ``RegisterAction`` de ``RegistrationController`` que vive dentro de ``FOSUserBundle``. Para ello, basta con crear tu propio archivo :file:`RegistrationController.php`, reemplaza el método original, y cambia su funcionalidad:: - - // src/Acme/UserBundle/Controller/RegistrationController.php - namespace Acme\UserBundle\Controller; - - use FOS\UserBundle\Controller\RegistrationController as BaseController; - - class RegistrationController extends BaseController - { - public function registerAction() - { - $response = parent::registerAction(); - - // ... haz tus cosas personalizadas - return $response; - } - } - -.. tip:: - - Dependiendo de cuanto necesites cambiar el comportamiento, puedes llamar a ``parent::RegisterAction()`` o sustituir por completo su lógica con la tuya. - -.. note:: - - La sustitución de controladores de esta forma solo funciona si el paquete se refiere al controlador usando la sintaxis estándar ``FOSUserBundle:Registration:register`` en las rutas y plantillas. Esta es la buena práctica. - -Sustituyendo recursos: Plantillas, Enrutado, Validación, etc. -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -También puedes cambiar la mayoría de los recursos, simplemente creando un archivo en la misma ubicación que en el padre de tu paquete. - -Por ejemplo, es muy común que necesites reemplazar la plantilla ``layout.html.twig`` de ``FOSUserBundle`` para que utilice el diseño base de tu aplicación. -Debido a que el archivo vive en ``Resources/views/layout.html.twig`` en ``FOSUserBundle``, puedes crear tu propio archivo en el mismo lugar dentro de ``AcmeUserBundle``. -*Symfony* ignorará el archivo que vive dentro de ``FOSUserBundle`` por completo, y en su lugar utilizará ese archivo. - -Lo mismo ocurre con los archivos de enrutado, la configuración de validación y otros recursos. - -.. note:: - - La sustitución de recursos sólo funciona cuando haces referencia a los recursos con el método ``@FosUserBundle/Resources/config/routing/security.xml``. - Si te refieres a recursos sin utilizar el acceso directo ``@BundleName``, no puedes reemplazar en esta forma. - -.. caution:: - - Los archivos de traducción no funcionan de la misma manera como se describió anteriormente. todos los archivos de traducción se acumulan en un conjunto de «piscinas» (una por cada) dominio. *Symfony*, primero carga los archivos de traducción desde los paquetes (en el orden en que se inician los paquetes) y luego desde tu directorio ``app(Resources``. Si la misma traducción se especifica en dos recursos, gana la traducción de los recursos cargada al último. - -.. _`FOSUserBundle`: https://github.com/friendsofsymfony/fosuserbundle - diff --git a/_sources/cookbook/bundles/installation.txt b/_sources/cookbook/bundles/installation.txt deleted file mode 100644 index 386410d..0000000 --- a/_sources/cookbook/bundles/installation.txt +++ /dev/null @@ -1,146 +0,0 @@ -.. index:: - single: Paquete; Instalando - -How to install 3rd party Bundles -================================ - -Most bundles provide their own installation instructions. However, the -basic steps for installing a bundle are the same. - -Add Composer Dependencies -------------------------- - -Starting from Symfony 2.1, dependencies are managed with Composer. It's -a good idea to learn some basics of Composer in `their documentation`_. - -Before you can use composer to install a bundle, you should look for a -`Packagist`_ package of that bundle. For example, if you search for the popular -`FOSUserBundle`_ you will find a packaged called `friendsofsymfony/user-bundle`_. - -.. note:: - - Packagist is the main archive for Composer. If you are searching - for a bundle, the best thing you can do is check out - `KnpBundles`_, it is the unofficial achive of Symfony Bundles. If - a bundle contains a ``README`` file, it is displayed there and if it - has a Packagist package it shows a link to the package. It's a - really useful site to begin searching for bundles. - -Now that you have the package name, you should determine the version -you want to use. Usually different versions of a bundle correspond to -a particular version of Symfony. This information should be in the ``README`` -file. If it isn't, you can use the version you want. If you choose an incompatible -version, Composer will throw dependency errors when you try to install. If -this happens, you can try a different version. - -In the case of the FOSUserBundle, the ``README`` file has a caution that version -1.2.0 must be used for Symfony 2.0 and 1.3+ for Symfony 2.1+. Packagist displays -example ``require`` statements for all existing versions of a package. The -current development version of FOSUserBundle is ``"friendsofsymfony/user-bundle": "2.0.*@dev"``. - -Now you can add the bundle to your ``composer.json`` file and update the -dependencies. You can do this manually: - -1. **Add it to the composer.json file:** - - .. code-block:: json - - { - ..., - "require": { - ..., - "friendsofsymfony/user-bundle": "2.0.*@dev" - } - } - -2. **Update the dependency:** - - .. code-block:: bash - - $ php composer.phar update friendsofsymfony/user-bundle - - or update all dependencies - - .. code-block:: bash - - $ php composer.phar update - -Or you can do this in one command: - -.. code-block:: bash - - $ php composer.phar require friendsofsymfony/user-bundle:2.0.*@dev - -Enable the Bundle ------------------ - -At this point, the bundle is installed in your Symfony project (in -``vendor/friendsofsymfony/``) and the autoloader recognizes its classes. -The only thing you need to do now is register the bundle in ``AppKernel``:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - // ... - - public function registerBundles() - { - $bundles = array( - // ..., - new FOS\UserBundle\FOSUserBundle(), - ); - - // ... - } - } - -Configure the Bundle --------------------- - -Usually a bundle requires some configuration to be added to app's -``app/config/config.yml`` file. The bundle's documentation will likely -describe that configuration. But you can also get a reference of the -bundle's config via the ``config:dump-reference`` command. - -For instance, in order to look the reference of the ``assetic`` config you -can use this: - -.. code-block:: bash - - $ app/console config:dump-reference AsseticBundle - -or this: - -.. code-block:: bash - - $ app/console config:dump-reference assetic - -The output will look like this: - -.. code-block:: text - - assetic: - debug: %kernel.debug% - use_controller: - enabled: %kernel.debug% - profiler: false - read_from: %kernel.root_dir%/../web - write_to: %assetic.read_from% - java: /usr/bin/java - node: /usr/local/bin/node - node_paths: [] - # ... - -Other Setup ------------ - -At this point, check the ``README`` file of your brand new bundle to see -what do to next. - -.. _their documentation: http://getcomposer.org/doc/00-intro.md -.. _`Packagist`: https://packagist.org -.. _FOSUserBundle: https://github.com/FriendsOfSymfony/FOSUserBundle -.. _`friendsofsymfony/user-bundle`: https://packagist.org/packages/friendsofsymfony/user-bundle -.. _KnpBundles: http://knpbundles.com/ diff --git a/_sources/cookbook/bundles/override.txt b/_sources/cookbook/bundles/override.txt deleted file mode 100644 index db6a699..0000000 --- a/_sources/cookbook/bundles/override.txt +++ /dev/null @@ -1,99 +0,0 @@ -.. index:: - single: Paquete; Herencia - -Cómo sustituir cualquier parte de un paquete -============================================ - -Este documento es una referencia rápida sobre la forma de reemplazar las diferentes partes de los paquetes de terceros. - -Plantillas ----------- - -Para más información sobre la sustitución de plantillas, consulta: - -* :ref:`overriding-bundle-templates`. -* :doc:`/cookbook/bundles/inheritance` - -Enrutado --------- - -El enrutado en *Symfony2* no se importa automáticamente. Si quieres incluir las rutas de algún paquete, entonces las debes importar manualmente desde algún lugar de tu aplicación (por ejemplo, ``app/config/routing.yml``). - -La forma más fácil de «sustituir» el enrutado de un paquete es no importarlo en absoluto. En lugar de importar el enrutado de un paquete de terceros, simplemente copia el archivo de enrutado a tu aplicación, modifica las rutas, e importarlo en su lugar. - -Controladores -------------- - -Suponiendo que el paquete de terceros involucrado no utiliza los controladores como servicios (que casi siempre es el caso), fácilmente puede reemplazar los controladores a través de la herencia del paquete. Para más información, consulta :doc:`/cookbook/bundles/inheritance`. - -Servicios y configuración -------------------------- - -A fin de redefinir/extender un servicio, tienes dos opciones. En primer lugar, puedes configurar el parámetro que contiene el nombre de clase del servicio ajustándolo a tu propia clase en :file:`app/config/config.yml`. Por supuesto, esto sólo es posible si definiste como parámetro el nombre de la clase en la configuración del servicio en el paquete que contiene el servicio. Por ejemplo, para redefinir la clase utilizada por el servicio ``traductor`` de *Symfony*, debes reajustar el parámetro ``translator.class``. Saber exactamente cual parámetro cambiar posiblemente conlleve un poco de investigación. Para el traductor, el parámetro está definido y se utiliza en el archivo :file:`Resources/config/translation.xml` en el núcleo de ``FrameworkBundle``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - parameters: - translator.class: Acme\HelloBundle\Translation\Translator - - .. code-block:: xml - - - - Acme\HelloBundle\Translation\Translator - - - .. code-block:: php - - // app/config/config.php - $container->setParameter('translator.class', 'Acme\HelloBundle\Translation\Translator'); - -En segundo lugar, si la clase no está disponible como un parámetro, te quieres asegurar de que ---cuando se utilice el paquete--- siempre se redefina la clase, o se tenga que modificar algo más allá de sólo el nombre de la clase, debes utilizar un pase del compilador:: - - // src/Acme/FooBundle/DependencyInjection/Compiler/OverrideServiceCompilerPass.php - namespace Acme\DemoBundle\DependencyInjection\Compiler; - - use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; - use Symfony\Component\DependencyInjection\ContainerBuilder; - - class OverrideServiceCompilerPass implements CompilerPassInterface - { - public function process(ContainerBuilder $container) - { - $definition = $container->getDefinition('original-service-id'); - $definition->setClass('Acme\DemoBundle\YourService'); - } - } - -En este ejemplo recuperaste la definición del servicio desde el servicio original, y ajustaste el nombre de la clase a tu propia clase. - -Consulta :doc:`/cookbook/service_container/compiler_passes` para más información sobre cómo usar los pases del compilador. Si quieres hacer algo más allá que sólo reemplazar la clase ---como agregar una llamada a un método--- sólo puedes usar el método de pase del compilador. - -Entidades y asignación de entidades ------------------------------------ - -En curso... - -Formularios ------------ - -Con el fin de sustituir un tipo de formulario, este se tiene que registrar como un servicio (lo cual significa que se etiqueta como ``«form.type»``). A continuación, lo puedes modificar tal como lo harías para redefinir cualquier servicio, como se explica en `Servicios y configuración`_. Esto, por supuesto, sólo funcionará si el tipo es referido por su alias en lugar de crear una instancia, por ejemplo:: - - $builder->add('name', 'custom_type'); - -rather than:: - - $builder->add('name', new CustomType()); - -Metadatos de validación ------------------------ - -En curso... - -Traducciones ------------- - -En curso... diff --git a/_sources/cookbook/bundles/prepend_extension.txt b/_sources/cookbook/bundles/prepend_extension.txt deleted file mode 100644 index 0ddaa3b..0000000 --- a/_sources/cookbook/bundles/prepend_extension.txt +++ /dev/null @@ -1,116 +0,0 @@ -.. index:: - single: Configuración; Semántica - single: Paquete; Configuración de extensión - -Cómo simplificar la configuración de múltiples paquetes -======================================================= - -Al crear aplicaciones reutilizables y extensibles, los desarrolladores suelen enfrentarse a una elección: o bien crear un gran paquete único o múltiples paquetes más pequeños. La creación de un único paquete tiene la gran desventaja de que le es imposible a los usuarios optar por eliminar funcionalidad que no estén utilizando. La creación de múltiples paquetes tiene la desventaja que la configuración se vuelve más tediosa y los ajustes a menudo se tienen que repetir para varios paquetes. - -Utilizando el enfoque de abajo, es posible eliminar la desventaja del enfoque de múltiples paquetes, permitiendo a una única extensión anteponer los ajustes para cualquier paquete. Puedes utilizar los ajustes definidos en el archivo :file:`app/config/config.yml` anteponiendo los ajustes tal y como si el usuario los hubiera escrito expresamente en la configuración de la aplicación. - -Por ejemplo, esto lo podrías utilizar para configurar el nombre del gestor de entidades a utilizar en múltiples paquetes. O se puede utilizar para activar una función opcional que depende de que otro paquete se cargue también. - -Para dotar a una Extensión con el poder de hacer esto, necesitas implementar la clase :class:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface`:: - - // src/Acme/HelloBundle/DependencyInjection/AcmeHelloExtension.php - namespace Acme\HelloBundle\DependencyInjection; - - use Symfony\Component\HttpKernel\DependencyInjection\Extension; - use Symfony\Component\DependencyInjection\Extension\PrependExtensionInterface; - use Symfony\Component\DependencyInjection\ContainerBuilder; - - class AcmeHelloExtension extends Extension implements PrependExtensionInterface - { - // ... - - public function prepend(ContainerBuilder $container) - { - // ... - } - } - -Dentro del método :method:`Symfony\\Component\\DependencyInjection\\Extension\\PrependExtensionInterface::prepend`, los desarrolladores tienen acceso completo a la instancia de la clase :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder` justo antes de llamar al método :method:`Symfony\\Component\\DependencyInjection\\Extension\\ExtensionInterface::load` en cada una de las extensiones registradas en el paquete. Para añadir al principio una configuración los desarrolladores de la extensión del paquete pueden utilizar el método :method:`Symfony\\Component\\DependencyInjection\\ContainerBuilder::prependExtensionConfig` de la instancia de :class:`Symfony\\Component\\DependencyInjection\\ContainerBuilder`. Dado que este método sólo añade la configuracion al principio, cualquier otra opción colocada explícitamente dentro del archivo :file:`app/config/config.yml` debería reemplazar esta configuración antepuesta. - -El siguiente ejemplo muestra cómo añadir al principio una opción de configuración en múltiples paquetes, así como desactivar una bandera en múltiples paquetes en el caso de que otro paquete específico no esté registrado:: - - public function prepend(ContainerBuilder $container) - { - // obtiene todos los paquetes - $bundles = $container->getParameter('kernel.bundles'); - // determina si AcmeGoodbyeBundle está registrado - if (!isset($bundles['AcmeGoodbyeBundle'])) { - // desactiva AcmeGoodbyeBundle en los paquetes - $config = array('use_acme_goodbye' => false); - foreach ($container->getExtensions() as $name => $extension) { - switch ($name) { - case 'acme_something': - case 'acme_other': - // establece use_acme_goodbye a false en la configuración - // de acme_something y acme_other ten en cuenta que - // si el usuario configura manualmente - // use_acme_goodbye a true en app/config/config.yml - // entonces al final el ajuste sería true y no false - $container->prependExtensionConfig($name, $config); - break; - } - } - } - - // procesa la configuración de AcmeHelloExtension - $configs = $container->getExtensionConfig($this->getAlias()); - // usa la clase Configuration para generar un arreglo config - // con los ajustes de ``acme_hello`` - $config = $this->processConfiguration(new Configuration(), $configs); - - // comprueba si entity_manager_name se ajustó en la - // configuración de ``acme_hello`` - if (isset($config['entity_manager_name'])) { - // añade al principio la configuracion de acme_something - // con el entity_manager_name - $config = array('entity_manager_name' => $config['entity_manager_name']); - $container->prependExtensionConfig('acme_something', $config); - } - } - -Lo anterior sería el equivalente de escribir lo siguiente en el archivo :file:`app/config/config.yml` en caso de que ``AcmeGoodbyeBundle`` no se haya registrado y la opcion ``entity_manager_name`` para ``acme_hello`` esté configurada como ``non_default``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - - acme_something: - # ... - use_acme_goodbye: false - entity_manager_name: non_default - - acme_other: - # ... - use_acme_goodbye: false - - .. code-block:: xml - - - - - non_default - - - - - .. code-block:: php - - // app/config/config.php - - $container->loadFromExtension('acme_something', array( - ..., - 'use_acme_goodbye' => false, - 'entity_manager_name' => 'non_default', - )); - $container->loadFromExtension('acme_other', array( - ..., - 'use_acme_goodbye' => false, - )); - diff --git a/_sources/cookbook/bundles/remove.txt b/_sources/cookbook/bundles/remove.txt deleted file mode 100644 index f6828dd..0000000 --- a/_sources/cookbook/bundles/remove.txt +++ /dev/null @@ -1,78 +0,0 @@ -.. index:: - single: Paquete; Eliminando el AcmeDemoBundle - -Cómo eliminar el ``AcmeDemoBundle`` -=================================== - -La *edición estándar de Symfony2* viene con una demo completa que vive dentro de un paquete llamado ``AcmeDemoBundle``. Es un gran texto modelo al cual referirte mientras empiezas un proyecto, pero probablemente quieras eliminarlo finalmente. - -.. tip:: - - Este artículo utiliza el ``AcmeDemoBundle`` como ejemplo, pero puedes utilizar estos pasos para eliminar cualquier paquete. - -1. Desregistrar el paquete en el :file:`AppKernel` --------------------------------------------------- - -Para desconectar el paquete de la plataforma, lo deberías quitar del método ``Appkernel::registerBundles()``. El paquete normalmente se encuentra en el arreglo ``$bundles pero el ``AcmeDemoBundle`` sólo se registra en un entorno de desarrollo y lo puedes encontrar en la siguiente declaración ``if``:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - public function registerBundles() - { - $bundles = array(...); - - if (in_array($this->getEnvironment(), array('dev', 'test'))) { - // comenta o elimina esta línea: - // $bundles[] = new Acme\DemoBundle\AcmeDemoBundle(); - // ... - } - } - } - -2. Quita la configuración del paquete -------------------------------------- - -Ahora que *Symfony* no sabe sobre el paquete, necesitas quitar toda su configuración y enrutado dentro del directorio :file:`app/config` que refiera al paquete. - -2.1 Quita el enrutado del paquete -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El enrutado para el ``AcmeDemoBundle`` se puede encontrar en :file:`app/config/routing_dev.yml`. Las rutas son ``_welcome``, ``_demo_secured`` y ``_demo``. Quita esas tres entradas. - -2.2 Quita la configuración del paquete -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Algunos paquetes contienen configuración en uno de los archivos en :file:`app/config/config*.yml`. Asegúrate de sacar la configuración relacionada de esos archivos. Rápidamente puedes determinar la configuración del paquete en el sitio buscando una cadena ``acme_demo`` (o alguna cosa que nombre el paquete, p. ej. ``fos_user`` para el ``FOSUserBundle``) en los archivos de configuración. - -El ``AcmeDemoBundle`` no tiene configuración. Sin embargo, se utiliza el paquete en la configuración del archivo :file:`app/config/security.yml`. Lo puedes utilizar como texto modelo para tu propia seguridad, pero también *puedes* quitar todo: A *Symfony* no le importa si lo quitas o no. - -3. Elimina el paquete del sistema de archivos ---------------------------------------------- - -Ahora que has quitado toda referencia al paquete en tu aplicación, deberías quitar el paquete del *sistema de archivos*. El paquete se localiza en el directorio :file:`src/Acme/DemoBundle`. Deberías quitar ese directorio y también puedes quitar el directorio ``Acme``. - -.. tip:: - - Si no sabes la ubicación de un paquete, puedes utilizar el método :method:`Symfony\\Bundle\\FrameworkBundle\\Bundle\\Bundle::getPath` para conseguir la ruta del paquete:: - - echo $this->container->get('kernel')->getBundle('AcmeDemoBundle')->getPath(); - -4. Quita la integración en otros paquetes ------------------------------------------ - -.. note:: - - Esto no aplica al ``AcmeDemoBundle`` --- ningún otro paquete depende de él, así que puedes omitir este paso. - -Algunos paquetes dependen de otros paquetes, si quitas uno de los dos, el otro probablemente no trabaje. Asegúrate que ningún otro paquete, de terceros o personalizado, dependa del paquete que estás a punto de quitar. - -.. tip:: - - Si un paqute depende de otro, significa que este utiliza algunos servicios del paquete. Buscar una cadena ``acme_demo`` te puede ayudar descubrirlos. - -.. tip:: - - Si un paquete de terceros depende de otro, puedes encontrar ese paquete mencionado en el archivo :file:`composer.json` incluido en el directorio :file:`bundle`. \ No newline at end of file diff --git a/_sources/cookbook/cache/index.txt b/_sources/cookbook/cache/index.txt deleted file mode 100644 index 0cf7122..0000000 --- a/_sources/cookbook/cache/index.txt +++ /dev/null @@ -1,7 +0,0 @@ -Almacenando en caché -==================== - -.. toctree:: - :maxdepth: 2 - - varnish diff --git a/_sources/cookbook/cache/varnish.txt b/_sources/cookbook/cache/varnish.txt deleted file mode 100644 index 855bf92..0000000 --- a/_sources/cookbook/cache/varnish.txt +++ /dev/null @@ -1,78 +0,0 @@ -.. index:: - single: Caché; Varnish - -Cómo utilizar *Varnish* para acelerar mi sitio *web* -==================================================== - -Debido a que la caché de *Symfony2* utiliza las cabeceras de caché *HTTP* estándar, el :ref:`symfony-gateway-cache` se puede sustituir fácilmente por cualquier otro delegado inverso. *Varnish* es un potente acelerador *HTTP*, de código abierto, capaz de servir contenido almacenado en caché de forma rápida y es compatible con :ref:`Inclusión del borde lateral `. - -.. index:: - single: Varnish; Configuración - -Configurando ------------- - -Como vimos anteriormente, *Symfony2* es lo suficientemente inteligente como para detectar si está hablando con un delegado inverso que entiende *ESI* o no. Funciona fuera de la caja cuando utilizas el delegado inverso de *Symfony2*, pero necesita una configuración especial para que funcione con *Varnish*. Afortunadamente, *Symfony2* se basa en otra norma escrita por Akamaï (`Arquitectura límite`_), por lo que el consejo de configuración en este capítulo te puede ser útil incluso si no utilizas *Symfony2*. - -.. note:: - - *Varnish* sólo admite el atributo ``src`` para las etiquetas *ESI* (los atributos ``onerror`` y ``alt`` se omiten). - -En primer lugar, configura *Varnish* para que anuncie su apoyo a *ESI* añadiendo una cabecera ``Surrogate-Capability`` a las peticiones remitidas a la interfaz administrativa de tu aplicación: - -.. code-block:: text - - sub vcl_recv { - set req.http.Surrogate-Capability = "abc=ESI/1.0"; - } - -Entonces, optimiza *Varnish* para que sólo analice el contenido de la respuesta cuando al menos hay una etiqueta *ESI* comprobando la cabecera ``Surrogate-Control`` que *Symfony2* añade automáticamente: - -.. code-block:: text - - sub vcl_fetch { - if (beresp.http.Surrogate-Control ~ "ESI/1.0") { - unset beresp.http.Surrogate-Control; - - // para Varnish >= 3.0 - set beresp.do_esi = true; - // para Varnish < 3.0 - // esi; - } - } - -.. caution:: - - La compresión con *ESI* no cuenta con el apoyo de *Varnish* hasta la versión 3.0 (lee `GZIP y Varnish`_). Si no estás utilizando *Varnish 3.0*, coloca un servidor web frente a *Varnish* para llevar a cabo la compresión. - -.. index:: - single: Varnish; Invalidando - -Invalidando la caché --------------------- - -Nunca debería ser necesario invalidar los datos almacenados en caché porque la invalidación ya se tiene en cuenta de forma nativa en los modelos de caché *HTTP* (consulta :ref:`http-cache-invalidation`). - -Sin embargo, *Varnish* se puede configurar para aceptar un método *HTTP* especial ``PURGE`` que invalida la caché para un determinado recurso: - -.. code-block:: text - - sub vcl_hit { - if (req.request == "PURGE") { - set obj.ttl = 0s; - error 200 "Purged"; - } - } - - sub vcl_miss { - if (req.request == "PURGE") { - error 404 "Not purged"; - } - } - -.. caution:: - - De alguna manera, debes proteger el método ``PURGE`` de *HTTP* para evitar que alguien aleatoriamente purgue los datos memorizados. - -.. _`Arquitectura límite`: http://www.w3.org/TR/edge-arch -.. _`GZIP y Varnish`: https://www.varnish-cache.org/docs/3.0/phk/gzip.html \ No newline at end of file diff --git a/_sources/cookbook/configuration/apache_router.txt b/_sources/cookbook/configuration/apache_router.txt deleted file mode 100644 index fd2a5c2..0000000 --- a/_sources/cookbook/configuration/apache_router.txt +++ /dev/null @@ -1,132 +0,0 @@ -.. index:: - single: Enrutador Apache - -Cómo usar el enrutador de *Apache* -================================== - -Si bien *Symfony2*, es muy rápido fuera de la caja, también proporciona varias maneras de aumentar esa velocidad con unos cuantos ajustes. -Una de esas maneras es dejando que *Apache* maneje el enrutado directamente, en lugar de utilizar *Symfony2* para esta tarea. - -Cambiando los parámetros de configuración del enrutador -------------------------------------------------------- - -Para volcar las rutas de *Apache*, primero debes modificar algunos parámetros de configuración para decirle a *Symfony2* que utilice el ``ApacheUrlMatcher`` en lugar de la opción predeterminada. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_prod.yml - parameters: - router.options.matcher.cache_class: ~ # desactiva la caché de enrutado - router.options.matcher_class: Symfony\Component\Routing\Matcher\ApacheUrlMatcher - - .. code-block:: xml - - - - null - - Symfony\Component\Routing\Matcher\ApacheUrlMatcher - - - - .. code-block:: php - - // app/config/config_prod.php - $container->setParameter('router.options.matcher.cache_class', null); // desactuva la caché de enrutado - $container->setParameter( - 'router.options.matcher_class', - 'Symfony\Component\Routing\Matcher\ApacheUrlMatcher' - ); - -.. tip:: - - Ten en cuenta que :class:`Symfony\\Component\\Routing\\Matcher\\ApacheUrlMatcher` extiende a :class:`Symfony\\Component\\Routing\\Matcher\\UrlMatcher` por lo que incluso si no se regeneran las reglas ``url_rewrite``, todo saldrá bien (porque al final ``ApacheUrlMatcher::match()`` hace una llamada a ``parent::match()``). - -Generando reglas para ``mod_rewrite`` -------------------------------------- - -Para comprobar que está funcionando, vamos a crear una ruta muy básica para el paquete de demostración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/routing.yml - hello: - path: /hello/{name} - defaults: { _controller: AcmeDemoBundle:Demo:hello } - - .. code-block:: xml - - - - AcmeDemoBundle:Demo:hello - - - .. code-block:: php - - // app/config/routing.php - $collection->add('hello', new Route('/hello/{name}', array( - '_controller' => 'AcmeDemoBundle:Demo:hello', - ))); - -Ahora genera las reglas **url_rewrite**: - -.. code-block:: bash - - $ php app/console router:dump-apache -e=prod --no-debug - -Lo cual debe mostrar ---más o menos--- lo siguiente: - -.. code-block:: apache - - # salta peticiones «reales» - RewriteCond %{REQUEST_FILENAME} -f - RewriteRule .* - [QSA,L] - - # hello - RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$ - RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle\:Demo\:hello] - -Ahora puedes volver a escribir ``web/.htaccess`` para utilizar las nuevas reglas, de modo que este ejemplo debería tener el siguiente aspecto: - -.. code-block:: apache - - - RewriteEngine On - - # salta peticiones "reales" - RewriteCond %{REQUEST_FILENAME} -f - RewriteRule .* - [QSA,L] - - # hello - RewriteCond %{REQUEST_URI} ^/hello/([^/]+?)$ - RewriteRule .* app.php [QSA,L,E=_ROUTING__route:hello,E=_ROUTING_name:%1,E=_ROUTING__controller:AcmeDemoBundle\:Demo\:hello] - - -.. note:: - - El procedimiento anterior se debe realizar cada vez que añadas/cambies la ruta si quieres sacar el máximo provecho de esta configuración. - -¡Eso es todo! -Ahora estás listo para usar las reglas de enrutado de *Apache*. - -Ajustes adicionales -------------------- - -Para ahorrar un poco de tiempo en el procesamiento, cambia las ocurrencias de ``Request`` a ``ApacheRequest`` en ``web/app.php``:: - - // web/app.php - - require_once __DIR__.'/../app/bootstrap.php.cache'; - require_once __DIR__.'/../app/AppKernel.php'; - //require_once __DIR__.'/../app/AppCache.php'; - - use Symfony\Component\HttpFoundation\ApacheRequest; - - $kernel = new AppKernel('prod', false); - $kernel->loadClassCache(); - //$kernel = new AppCache($kernel); - $kernel->handle(ApacheRequest::createFromGlobals())->send(); diff --git a/_sources/cookbook/configuration/environments.txt b/_sources/cookbook/configuration/environments.txt deleted file mode 100644 index b366d09..0000000 --- a/_sources/cookbook/configuration/environments.txt +++ /dev/null @@ -1,283 +0,0 @@ -.. index:: - single: Entornos - -Cómo dominar y crear nuevos entornos -==================================== - -Cada aplicación es la combinación de código y un conjunto de configuración que dicta la forma en que el código debería funcionar. La configuración puede definir la base de datos a usar, si o no se debe almacenar en caché, o cómo se debe detallar la anotación cronológica de eventos en la bitácora. En *Symfony2*, la idea de «entornos» es la idea de que el mismo código base se puede ejecutar con varias configuraciones diferentes. Por ejemplo, el entorno ``dev`` debería usar la configuración que facilita el desarrollo y lo hace agradable, mientras que el entorno ``prod`` debe usar un conjunto de configuración optimizado para velocidad. - -.. index:: - single: Entornos; Archivos de configuración - -Diferentes entornos, diferentes archivos de configuración ---------------------------------------------------------- - -Una típica aplicación *Symfony2* comienza con tres entornos: ``dev``, ``prod`` y ``test``. Como se mencionó anteriormente, cada «entorno» simplemente representa una manera de ejecutar el mismo código base con diferente configuración. No debería ser una sorpresa entonces que cada entorno cargue su propio archivo de configuración individual. Si estás utilizando el formato de configuración *YAML*, utiliza los siguientes archivos: - -* para el entorno ``dev``: ``app/config/config_dev.yml`` -* para el entorno ``prod``: ``app/config/config_prod.yml`` -* para el entorno ``test``: ``app/config/config_test.yml`` - -Esto funciona a través de un estándar sencillo que se utiliza por omisión dentro de la clase ``AppKernel``: - -.. code-block:: php - - // app/AppKernel.php - - // ... - - class AppKernel extends Kernel - { - // ... - - public function registerContainerConfiguration(LoaderInterface $loader) - { - $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml'); - } - } - -Como puedes ver, cuando se carga *Symfony2*, utiliza el entorno especificado para determinar qué archivo de configuración cargar. Esto cumple con el objetivo de múltiples entornos en una manera elegante, potente y transparente. - -Por supuesto, en realidad, cada entorno difiere un poco de los demás. -En general, todos los entornos comparten una gran configuración base común. -Abriendo el archivo de configuración «dev», puedes ver cómo se logra esto fácil y transparentemente: - -.. configuration-block:: - - .. code-block:: yaml - - imports: - - { resource: config.yml } - # ... - - .. code-block:: xml - - - - - - - .. code-block:: php - - $loader->import('config.php'); - // ... - -Para compartir configuración común, el archivo de configuración de cada entorno simplemente importa primero los ajustes más comunes desde un archivo de configuración central (:file:`config.yml`). -El resto del archivo se puede desviar de la configuración predeterminada sustituyendo parámetros individuales. Por ejemplo, de manera predeterminada, la barra de herramientas ``web_profiler`` está desactivada. Sin embargo, en el entorno ``dev``, la barra de herramientas se activa modificando el valor predeterminado en el archivo de configuración ``dev``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - imports: - - { resource: config.yml } - - web_profiler: - toolbar: true - # ... - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $loader->import('config.php'); - - $container->loadFromExtension('web_profiler', array( - 'toolbar' => true, - - // ... - )); - -.. index:: - single: Entornos; Ejecutando diferentes entornos - -Ejecutando una aplicación en entornos diferentes ------------------------------------------------- - -Para ejecutar la aplicación en cada entorno, carga la aplicación usando como controlador frontal o bien :file:`app.php` (para el entorno ``prod``) o :file:`app_dev.php` (para el entorno ``dev``): - -.. code-block:: text - - http://localhost/app.php -> entorno *prod* - http://localhost/app_dev.php -> entorno *dev* - -.. note:: - - Las *URL* dadas asumen que tu servidor *web* está configurado para utilizar el directorio ``web/`` de la aplicación como su raíz. Lee más en :doc:`Instalando Symfony2`. - -Si abres uno de estos archivos, rápidamente verás que el entorno utilizado por cada uno se fija explícitamente: - -.. code-block:: php - :linenos: - - handle(Request::createFromGlobals())->send(); - -Como puedes ver, la clave ``prod`` especifica que este entorno se ejecutará en el entorno de ``producción``. Una aplicación *Symfony2* se puede ejecutar en cualquier entorno usando este código y simplemente cambiando la cadena de entorno. - -.. note:: - - El entorno ``test`` se utiliza al escribir las pruebas funcionales y no es accesible en el navegador directamente a través de un controlador frontal. En otras palabras, a diferencia de los otros entornos, no hay archivo controlador frontal :file:`app_test.php`. - -.. index:: - single: Configuración; Modo de depuración - -.. sidebar:: Modo de *depuración* - - Importante, pero irrelevante al tema de *entornos* es la clave ``false`` en la línea 8 del controlador frontal anterior. Esto especifica si o no la aplicación se debe ejecutar en «modo de depuración». Independientemente del entorno, una aplicación *Symfony2* se puede ejecutar con el modo de depuración establecido en ``true`` o ``false``. Esto afecta a muchas cosas en la aplicación, tal como cuando o no se deben mostrar los errores o si los archivos de caché se reconstruyen de forma dinámica en cada petición. Aunque no es un requisito, el modo de depuración generalmente se fija a ``true`` para los entornos ``dev`` y ``test`` y ``false`` para el entorno ``prod``. - - Internamente, el valor del modo de depuración viene a ser el parámetro ``kernel.debug`` utilizado en el interior del :doc:`contenedor de servicios `. - Si miras dentro del archivo de configuración de tu aplicación, puedes encontrar el parámetro utilizado, por ejemplo, para activar o desactivar la anotación cronológica de eventos cuando utilizas el *DBAL* de *Doctrine*: - - .. configuration-block:: - - .. code-block:: yaml - - doctrine: - dbal: - logging: "%kernel.debug%" - # ... - - .. code-block:: xml - - - - .. code-block:: php - - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'logging' => '%kernel.debug%', - - // ... - ), - // ... - )); - -.. index:: - single: Entornos; Creando un nuevo entorno - -Creando un nuevo entorno ------------------------- - -De forma predeterminada, una aplicación *Symfony2* tiene tres entornos que se encargan de la mayoría de los casos. Por supuesto, debido a que un entorno no es más que una cadena que corresponde a un conjunto de configuración, la creación de un nuevo entorno es muy fácil. - -Supongamos, por ejemplo, que antes de desplegarla, necesitas medir el rendimiento de tu aplicación. Una forma de medir el rendimiento de la aplicación es usando una configuración cercana a la de producción, pero con el ``web_profiler`` de *Symfony2* habilitado. Esto permite a *Symfony2* registrar información sobre tu aplicación durante la evaluación. - -La mejor manera de lograrlo es a través de un nuevo entorno llamado, por ejemplo, ``benchmark``. Comienza creando un nuevo archivo de configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_benchmark.yml - imports: - - { resource: config_prod.yml } - - framework: - profiler: { only_exceptions: false } - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // app/config/config_benchmark.php - $loader->import('config_prod.php') - - $container->loadFromExtension('framework', array( - 'profiler' => array('only-exceptions' => false), - )); - -Y con esta simple adición, la aplicación ahora es compatible con un nuevo entorno llamado ``benchmark``. - -Este nuevo archivo de configuración importa la configuración del entorno ``prod`` y la modifica. Esto garantiza que el nuevo entorno es idéntico al entorno ``prod``, a excepción de los cambios echos explícitamente aquí. - -Debido a que deseas que este entorno sea accesible a través de un navegador, también debes crear un controlador frontal para el mismo. Copia el archivo ``web/app.php`` a ``web/app_benchmark.php`` y edita el entorno para que sea ``benchmark``: - -.. code-block:: php - - handle(Request::createFromGlobals())->send(); - -El nuevo entorno ahora es accesible a través de:: - - http://localhost/app_benchmark.php - -.. note:: - - Algunos entornos, como el entorno ``dev``, no están destinados a ser visitados en algún servidor empleado para el público en general. Esto se debe a que ciertos entornos, con fines de depuración, pueden dar demasiada información sobre la aplicación o infraestructura subyacente. Para estar seguros de que estos entornos no son accesibles, se suele proteger al controlador frontal de direcciones *IP* externas a través del siguiente código en la parte superior del controlador: - - .. code-block:: php - - if (!in_array(@$_SERVER['REMOTE_ADDR'], array('127.0.0.1', '::1'))) { - die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.'); - } - -.. index:: - single: Entornos; Directorio de caché - -Entornos y el directorio de caché ---------------------------------- - -*Symfony2* aprovecha la memorización en caché de muchas maneras: la configuración de la aplicación, la configuración de enrutado, las plantillas *Twig* y más, se memorizan como objetos *PHP* en archivos del sistema de archivos. - -Por omisión, estos archivos se memorizan principalmente en el directorio :file:`app/cache`. -Sin embargo, cada entorno memoriza su propio conjunto de archivos: - -.. code-block:: text - - app/cache/dev --- directorio caché para el entorno *dev* - app/cache/prod --- directorio caché para el entorno *prod* - -A veces, cuando depuramos, puede ser útil inspeccionar un archivo memorizado para entender cómo está funcionando algo. Al hacerlo, recuerda buscar en el directorio del entorno que estás utilizando (comúnmente ``dev`` mientras desarrollas y depuras). Aunque puede variar, el directorio ``app/cache/dev`` incluye lo siguiente: - -* :file:`appDevDebugProjectContainer.php` --- el «contenedor del servicio» memorizado que representa la configuración de la aplicación en caché; - -* :file:`appdevUrlGenerator.php` --- la clase *PHP* generada a partir de la configuración de enrutado y usada cuando genera las *URL*; - -* :file:`appdevUrlMatcher.php` --- la clase *PHP* usada para emparejar rutas --- busca aquí para ver la lógica de las expresiones regulares compiladas utilizadas para concordar las *URL* entrantes con diferentes rutas; - -* ``twig/`` --- este directorio contiene todas las plantillas *Twig* en caché. - -.. note:: - - Fácilmente puedes cambiar la ubicación y nombre del directorio. Para más información lee el artículo :doc:`/cookbook/configuration/override_dir_structure`. - - -Prosigue --------- - -Lee el artículo en :doc:`/cookbook/configuration/external_parameters`. diff --git a/_sources/cookbook/configuration/external_parameters.txt b/_sources/cookbook/configuration/external_parameters.txt deleted file mode 100644 index 2e60e15..0000000 --- a/_sources/cookbook/configuration/external_parameters.txt +++ /dev/null @@ -1,126 +0,0 @@ -.. index:: - single: Entornos; Parámetros externos - -Cómo configurar parámetros externos en el contenedor de servicios -================================================================= - -En el capítulo :doc:`/cookbook/configuration/environments`, aprendiste cómo gestionar la configuración de tu aplicación. A veces, puedes beneficiar a tu aplicación almacenando ciertas credenciales fuera del código de tu proyecto. La configuración de la base de datos es tal ejemplo. La flexibilidad del contenedor de servicios de *Symfony* te permite hacer esto fácilmente. - -Variables de entorno --------------------- - -*Symfony* grabará cualquier variable de entorno con el prefijo ``SYMFONY__`` y lo configurará como parámetro en el contenedor de servicios. Los dobles guiones bajos son reemplazados por un punto, ya que un punto no es un carácter válido en un nombre de variable de entorno. - -Por ejemplo, si estás usando *Apache*, las variables de entorno se pueden fijar usando la siguiente configuración de ``VirtualHost``: - -.. code-block:: apache - - - ServerName Symfony2 - DocumentRoot "/ruta/a/aplic_symfony_2/web" - DirectoryIndex index.php index.html - SetEnv SYMFONY__DATABASE__USER usuario - SetEnv SYMFONY__DATABASE__PASSWORD secreto - - - AllowOverride All - Allow from All - - - -.. note:: - - El ejemplo anterior es una configuración para *Apache*, con la directiva `SetEnv`_. Sin embargo, esta funcionará para cualquier servidor web compatible con la configuración de variables de entorno. - - Además, con el fin de que tu consola trabaje (la cual no utiliza *Apache*), las tienes que exportar como variables del intérprete. En un sistema Unix, puedes ejecutar las siguientes ordenes: - - .. code-block:: bash - - $ export SYMFONY__DATABASE__USER=usuario - $ export SYMFONY__DATABASE__PASSWORD=secreto - -Ahora que has declarado una variable de entorno, estará presente en la variable global ``$_SERVER`` de *PHP*. Entonces *Symfony* automáticamente fijará todas las variables ``$_SERVER`` con el prefijo ``SYMFONY__`` como parámetros en el contenedor de servicios. - -Ahora puedes referirte a estos parámetros donde los necesites. - -.. configuration-block:: - - .. code-block:: yaml - - doctrine: - dbal: - driver pdo_mysql - dbname: symfony2_project - user: "%database.user%" - password: "%database.password%" - - .. code-block:: xml - - - - - - - - - .. code-block:: php - - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => 'pdo_mysql', - 'dbname' => 'symfony2_project', - 'user' => '%database.user%', - 'password' => '%database.password%', - ) - )); - -Constantes ----------- - -El contenedor también cuenta con apoyo para fijar constantes *PHP* como parámetros. -See :ref:`component-di-parameters-constants` for more details. - -Otra configuración ------------------- - -La directiva ``imports`` se puede utilizar para extraer parámetros almacenados en otro lugar. -Importar un archivo *PHP* te da la flexibilidad de añadir al contenedor lo que sea necesario. La siguiente directiva importa un archivo llamado :file:`parameters.php`. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - imports: - - { resource: parameters.php } - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $loader->import('parameters.php'); - -.. note:: - - Un archivo de recursos puede tener uno de muchos tipos. Los recursos *PHP*, *XML*, *YAML*, *INI* y cierre son compatibles con la directiva ``imports``. - -En :file:`parameters.php`, dile al contenedor de servicios los parámetros que deseas configurar. Esto es útil cuando la configuración importante está en un formato no estándar. El siguiente ejemplo incluye la configuración de una base de datos *Drupal* en el contenedor de servicios de *Symfony*. - -.. code-block:: php - - // app/config/parameters.php - include_once('/ruta/a/drupal/sites/default/settings.php'); - $container->setParameter('drupal.database.url', $db_url); - -.. _`SetEnv`: http://httpd.apache.org/docs/current/env.html diff --git a/_sources/cookbook/configuration/front_controllers_and_kernel.txt b/_sources/cookbook/configuration/front_controllers_and_kernel.txt deleted file mode 100644 index 4a07b93..0000000 --- a/_sources/cookbook/configuration/front_controllers_and_kernel.txt +++ /dev/null @@ -1,171 +0,0 @@ -.. index:: - single: How front controller, ``AppKernel`` and environments - work together - -Understanding how the Front Controller, Kernel and Environments work together -============================================================================= - -The section :doc:`/cookbook/configuration/environments` explained the basics -on how Symfony uses environments to run your application with different configuration -settings. This section will explain a bit more in-depth what happens when -your application is bootstrapped. To hook into this process, you need to understand -three parts that work together: - -* `The Front Controller`_ -* `The Kernel Class`_ -* `The Environments`_ - -.. note:: - - Usually, you will not need to define your own front controller or - ``AppKernel`` class as the `Symfony2 Standard Edition`_ provides - sensible default implementations. - - This documentation section is provided to explain what is going on behind - the scenes. - -The Front Controller --------------------- - -The `front controller`_ is a well-known design pattern; it is a section of -code that *all* requests served by an application run through. - -In the `Symfony2 Standard Edition`_, this role is taken by the `app.php`_ -and `app_dev.php`_ files in the ``web/`` directory. These are the very -first PHP scripts executed when a request is processed. - -The main purpose of the front controller is to create an instance of the -``AppKernel`` (more on that in a second), make it handle the request -and return the resulting response to the browser. - -Because every request is routed through it, the front controller can be -used to perform global initializations prior to setting up the kernel or -to *`decorate`_* the kernel with additional features. Examples include: - -* Configuring the autoloader or adding additional autoloading mechanisms; -* Adding HTTP level caching by wrapping the kernel with an instance of - :ref:`AppCache`; -* Enabling the :doc:`/components/debug`. - -The front controller can be chosen by requesting URLs like: - -.. code-block:: text - - http://localhost/app_dev.php/some/path/... - -As you can see, this URL contains the PHP script to be used as the front -controller. You can use that to easily switch the front controller or use -a custom one by placing it in the ``web/`` directory (e.g. ``app_cache.php``). - -When using Apache and the `RewriteRule shipped with the Standard Edition`_, -you can omit the filename from the URL and the RewriteRule will use ``app.php`` -as the default one. - -.. note:: - - Pretty much every other web server should be able to achieve a - behavior similar to that of the RewriteRule described above. - Check your server documentation for details or see - :doc:`/cookbook/configuration/web_server_configuration`. - -.. note:: - - Make sure you appropriately secure your front controllers against unauthorized - access. For example, you don't want to make a debugging environment - available to arbitrary users in your production environment. - -Technically, the `app/console`_ script used when running Symfony on the command -line is also a front controller, only that is not used for web, but for command -line requests. - -The Kernel Class ----------------- - -The :class:`Symfony\\Component\\HttpKernel\\Kernel` is the core of -Symfony2. It is responsible for setting up all the bundles that make up -your application and providing them with the application's configuration. -It then creates the service container before serving requests in its -:method:`Symfony\\Component\\HttpKernel\\HttpKernelInterface::handle` -method. - -There are two methods declared in the -:class:`Symfony\\Component\\HttpKernel\\KernelInterface` that are -left unimplemented in :class:`Symfony\\Component\\HttpKernel\\Kernel` -and thus serve as `template methods`_: - -* :method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerBundles`, - which must return an array of all bundles needed to run the - application; - -* :method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`, - which loads the application configuration. - -To fill these (small) blanks, your application needs to subclass the -Kernel and implement these methods. The resulting class is conventionally -called the ``AppKernel``. - -Again, the Symfony2 Standard Edition provides an `AppKernel`_ in the ``app/`` -directory. This class uses the name of the environment - which is passed to -the Kernel's :method:`constructor` -method and is available via :method:`Symfony\\Component\\HttpKernel\\Kernel::getEnvironment` - -to decide which bundles to create. The logic for that is in ``registerBundles()``, -a method meant to be extended by you when you start adding bundles to your -application. - -You are, of course, free to create your own, alternative or additional -``AppKernel`` variants. All you need is to adapt your (or add a new) front -controller to make use of the new kernel. - -.. note:: - - The name and location of the ``AppKernel`` is not fixed. When - putting multiple Kernels into a single application, - it might therefore make sense to add additional sub-directories, - for example ``app/admin/AdminKernel.php`` and - ``app/api/ApiKernel.php``. All that matters is that your front - controller is able to create an instance of the appropriate - kernel. - -Having different ``AppKernels`` might be useful to enable different front -controllers (on potentially different servers) to run parts of your application -independently (for example, the admin UI, the frontend UI and database migrations). - -.. note:: - - There's a lot more the ``AppKernel`` can be used for, for example - :doc:`overriding the default directory structure `. - But odds are high that you don't need to change things like this on the - fly by having several ``AppKernel`` implementations. - -The Environments ----------------- - -We just mentioned another method the ``AppKernel`` has to implement - -:method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration`. -This method is responsible for loading the application's -configuration from the right *environment*. - -Environments have been covered extensively -:doc:`in the previous chapter`, -and you probably remember that the Standard Edition comes with three -of them - ``dev``, ``prod`` and ``test``. - -More technically, these names are nothing more than strings passed from the -front controller to the ``AppKernel``'s constructor. This name can then be -used in the :method:`Symfony\\Component\\HttpKernel\\KernelInterface::registerContainerConfiguration` -method to decide which configuration files to load. - -The Standard Edition's `AppKernel`_ class implements this method by simply -loading the ``app/config/config_*environment*.yml`` file. You are, of course, -free to implement this method differently if you need a more sophisticated -way of loading your configuration. - -.. _front controller: http://en.wikipedia.org/wiki/Front_Controller_pattern -.. _`edición estándar de Symfony2`: https://github.com/symfony/symfony-standard -.. _app.php: https://github.com/symfony/symfony-standard/blob/master/web/app.php -.. _app_dev.php: https://github.com/symfony/symfony-standard/blob/master/web/app_dev.php -.. _app/console: https://github.com/symfony/symfony-standard/blob/master/app/console -.. _AppKernel: https://github.com/symfony/symfony-standard/blob/master/app/AppKernel.php -.. _decorate: http://en.wikipedia.org/wiki/Decorator_pattern -.. _RewriteRule shipped with the Standard Edition: https://github.com/symfony/symfony-standard/blob/master/web/.htaccess) -.. _template methods: http://en.wikipedia.org/wiki/Template_method_pattern diff --git a/_sources/cookbook/configuration/index.txt b/_sources/cookbook/configuration/index.txt deleted file mode 100644 index 8e2e805..0000000 --- a/_sources/cookbook/configuration/index.txt +++ /dev/null @@ -1,13 +0,0 @@ -Configurando -============ - -.. toctree:: - :maxdepth: 2 - - environments - override_dir_structure - front_controllers_and_kernel - external_parameters - pdo_session_storage - apache_router - web_server_configuration diff --git a/_sources/cookbook/configuration/override_dir_structure.txt b/_sources/cookbook/configuration/override_dir_structure.txt deleted file mode 100644 index 9bac71d..0000000 --- a/_sources/cookbook/configuration/override_dir_structure.txt +++ /dev/null @@ -1,134 +0,0 @@ -.. index:: - single: Sustituyendo Symfony - -Cómo sustituir la estructura de directorios predeterminada de *Symfony* -======================================================================= - -Automáticamente *Symfony* viene con una estructura de directorios predefinida. Fácilmente puedes sustituir esta estructura de directorios para crear la tuya propia. La estructura de directorios predefinida es: - -.. code-block:: text - - app/ - cache/ - config/ - logs/ - ... - src/ - ... - vendor/ - ... - web/ - app.php - ... - -.. _override-cache-dir: - -Sustituyendo el directorio ``cache`` ------------------------------------- - -Puedes sustituir el directorio ``cache`` redefiniendo el método ``getCacheDir`` en la clase ``AppKernel`` de tu aplicación:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - // ... - - public function getCacheDir() - { - return $this->rootDir.'/'.$this->environment.'/cache'; - } - } - -``$this->rootDir`` es la ruta absoluta al directorio ``app`` y ``$this->environment`` -es el entorno actual (es decir, ``dev``). En este caso cambiaste la ubicación del directorio ``cache`` a ``app/{environment}/cache``. - -.. caution:: - - Tendrías que mantener un diferente directorio ``cache`` para cada entorno, de lo contrario puede ocurrir algún comportamiento inesperado. Cada entorno genera sus propios archivos de configuración almacenados en caché, y por lo tanto cada cual necesita su propio directorio para almacenar esos archivos en caché. - -.. _override-logs-dir: - -Sustituyendo el directorio ``logs`` ------------------------------------ - -Sustituir el directorio ``logs`` es igual que sustituir el directorio ``cache``, la única diferencia es que necesitas reemplazar el método ``getLogDir``:: - - // app/AppKernel.php - - // ... - class AppKernel extends Kernel - { - // ... - - public function getLogDir() - { - return $this->rootDir.'/'.$this->environment.'/logs'; - } - } - -Aquí cambiaste la ubicación del directorio a ``app/{environment}/logs``. - -Sustituyendo el directorio ``web`` ----------------------------------- - -Si necesitas rebautizar o mover tu directorio ``web``, lo único que necesitas es garantizar que la ruta al directorio ``app`` todavía es la correcta en tus controladores frontales :file:`app.php` y :file:`app_dev.php`. Si sencillamente rebautizas el directorio, está bien. Pero si lo mueves de alguna manera, necesitarás modificar las rutas dentro de esos archivos:: - - require_once __DIR__.'/../Symfony/app/bootstrap.php.cache'; - require_once __DIR__.'/../Symfony/app/AppKernel.php'; - -Since Symfony 2.1 (in which Composer is introduced), you also need to change -the ``extra.symfony-web-dir`` option in the ``composer.json`` file: - -.. code-block:: json - - { - ... - "extra": { - ... - "symfony-web-dir": "my_new_web_dir" - } - } - -.. tip:: - - Algunos servidores compartidos tienen un directorio *web* raíz :file:`public_html`. Al renombrar tu directorio *web* de :file:`web` a :file:`public_html` es una manera para hacer que tu proyecto *Symfony* trabaje en tu servidor compartido. Otra manera es desplegar tu aplicación en un directorio fuera de tu :file:`web` raíz, eliminar tu directorio :file:`public_html`, y luego reemplazarlo con un enlace simbólico al :file:`web` en tu proyecto. - -.. note:: - - Si utilizas el ``AsseticBundle`` necesitas configurar esto, para que este pueda utilizar el directorio :file:`web` correcto: - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - - # ... - assetic: - # ... - read_from: "%kernel.root_dir%/../../public_html" - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - - // ... - $container->loadFromExtension('assetic', array( - // ... - 'read_from' => '%kernel.root_dir%/../../public_html', - )); - - Ahora solo necesitas volcar tus activos de nuevo y tu aplicación debería trabajar: - - .. code-block:: bash - - $ php app/console assetic:dump --env=prod --no-debug diff --git a/_sources/cookbook/configuration/pdo_session_storage.txt b/_sources/cookbook/configuration/pdo_session_storage.txt deleted file mode 100644 index 4d652ae..0000000 --- a/_sources/cookbook/configuration/pdo_session_storage.txt +++ /dev/null @@ -1,199 +0,0 @@ -.. index:: - single: Sesión; Almacenamiento en la base de datos - -Cómo utilizar ``PdoSessionHandler`` para almacenar sesiones en la base de datos -=============================================================================== - -El almacenamiento de sesiones predeterminado de *Symfony2* escribe la información de la sesión en archivo(s). La mayoría desde medianos hasta grandes sitios web utilizan una base de datos para almacenar valores de sesión en lugar de archivos, porque las bases de datos son más fáciles de usar y escalar en un entorno web multiservidor. - -*Symfony2* ha incorporado una solución para el almacenamiento de la sesión en la base de datos denominada :class:`Symfony\\\Component\\HttpFoundation\\SessionStorage\\PdoSessionStorage`. -Para usarla, sólo tienes que cambiar algunos parámetros en :file:`config.yml` (o el formato de configuración de tu elección): - -.. versionadded:: 2.1 - En *Symfony 2.1* la clase y el espacio de nombres se han modificado significativamente. Ahora puedes encontrar las clases de almacenamiento de sesión en el espacio de nombres `Session\\Storage`: - ``Symfony\Component\HttpFoundation\Session\Storage``. Además ten en cuenta que en *Symfony 2.1* debes configurar ``handler_id`` no ``storage_id`` como en *Symfony2.0*. - Abajo, notarás que ya no se utiliza ``%sesión.storage.options%``. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - framework: - session: - # ... - handler_id: session.handler.pdo - - parameters: - pdo.db_options: - db_table: session - db_id_col: session_id - db_data_col: session_value - db_time_col: session_time - - services: - pdo: - class: PDO - arguments: - dsn: "mysql:dbname=mydatabase" - user: myuser - password: mypassword - - session.handler.pdo: - class: Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler - arguments: ["@pdo", "%pdo.db_options%"] - - .. code-block:: xml - - - - - - - - - session - session_id - session_value - session_time - - - - - - mysql:dbname=mydatabase - myuser - mypassword - - - - - %pdo.db_options% - - - - .. code-block:: php - - // app/config/config.php - use Symfony\Component\DependencyInjection\Definition; - use Symfony\Component\DependencyInjection\Reference; - - $container->loadFromExtension('framework', array( - ..., - 'session' => array( - // ..., - 'handler_id' => 'session.handler.pdo', - ), - )); - - $container->setParameter('pdo.db_options', array( - 'db_table' => 'session', - 'db_id_col' => 'session_id', - 'db_data_col' => 'session_value', - 'db_time_col' => 'session_time', - )); - - $pdoDefinition = new Definition('PDO', array( - 'mysql:dbname=mydatabase', - 'myuser', - 'mypassword', - )); - $container->setDefinition('pdo', $pdoDefinition); - - $storageDefinition = new Definition('Symfony\Component\HttpFoundation\Session\Storage\Handler\PdoSessionHandler', array( - new Reference('pdo'), - '%pdo.db_options%', - )); - $container->setDefinition('session.handler.pdo', $storageDefinition); - -* ``db_table``: El nombre de la tabla de sesiones en tu base de datos -* ``db_id_col``: El nombre de la columna ``id`` en la tabla de sesiones (``VARCHAR (255)`` o más grande) -* ``db_data_col``: El nombre de la columna de valores en tu tabla de sesiones (``TEXT`` o ``CLOB``) -* ``db_time_col``: El nombre de la columna de tiempo en tu tabla de sesiones (``INTEGER``) - -Compartiendo información de conexión a tu base de datos -------------------------------------------------------- - -Con la configuración dada, la configuración de conexión de la base de datos únicamente se define para la conexión de almacenamiento de sesión. Esto está bien cuando utilizas una base de datos para los datos de sesión. - -Pero si deseas almacenar los datos de sesión en la misma base que el resto de los datos de tu proyecto, puedes utilizar la configuración de conexión de :file:`parameters.yml` refiriendo los parámetros relacionados con la base de datos definidos allí: - -.. configuration-block:: - - .. code-block:: yaml - - pdo: - class: PDO - arguments: - - "mysql:host=%database_host%;port=%database_port%;dbname=%database_name%" - - "%database_user%" - - "%database_password%" - - .. code-block:: xml - - - mysql:host=%database_host%;port=%database_port%;dbname=%database_name% - %database_user% - %database_password% - - - .. code-block:: php - - $pdoDefinition = new Definition('PDO', array( - 'mysql:host=%database_host%;port=%database_port%;dbname=%database_name%', - '%database_user%', - '%database_password%', - )); - -Instrucciones *SQL* de ejemplo ------------------------------- - -*MySQL* -~~~~~~~ - -La declaración *SQL* necesaria para crear la tabla en la base de datos podría ser similar a la siguiente (*MySQL*): - -.. code-block:: sql - - CREATE TABLE `session` ( - `session_id` varchar(255) NOT NULL, - `session_value` text NOT NULL, - `session_time` int(11) NOT NULL, - PRIMARY KEY (`session_id`) - ) ENGINE=InnoDB DEFAULT CHARSET=utf8; - -*PostgreSQL* -~~~~~~~~~~~~ - -Para *PostgreSQL*, la declaración debe tener este aspecto: - -.. code-block:: sql - - CREATE TABLE session ( - session_id character varying(255) NOT NULL, - session_value text NOT NULL, - session_time integer NOT NULL, - CONSTRAINT session_pkey PRIMARY KEY (session_id) - ); - -«Microsoft SQL Server» -~~~~~~~~~~~~~~~~~~~~~~ - -Para *MSSQL*, la declaración se podría parecer a la siguiente: - -.. code-block:: sql - - CREATE TABLE [dbo].[session]( - [session_id] [nvarchar](255) NOT NULL, - [session_value] [ntext] NOT NULL, - [session_time] [int] NOT NULL, - PRIMARY KEY CLUSTERED( - [session_id] ASC - ) WITH ( - PAD_INDEX = OFF, - STATISTICS_NORECOMPUTE = OFF, - IGNORE_DUP_KEY = OFF, - ALLOW_ROW_LOCKS = ON, - ALLOW_PAGE_LOCKS = ON - ) ON [PRIMARY] - ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] diff --git a/_sources/cookbook/configuration/web_server_configuration.txt b/_sources/cookbook/configuration/web_server_configuration.txt deleted file mode 100644 index b498d15..0000000 --- a/_sources/cookbook/configuration/web_server_configuration.txt +++ /dev/null @@ -1,92 +0,0 @@ -.. index:: - single: Web Server - -Configuring a web server -======================== - -The web directory is the home of all of your application's public and static -files. Including images, stylesheets and JavaScript files. It is also where the -front controllers live. For more details, see the :ref:`the-web-directory`. - -The web directory services as the document root when configuring your web -server. In the examples below, this directory is in ``/var/www/project/web/``. - -Apache2 -------- - -For advanced Apache configuration options, see the official `Apache`_ -documentation. The minimum basics to get your application running under Apache2 -are: - -.. code-block:: apache - - - ServerName www.domain.tld - - DocumentRoot /var/www/project/web - - # enable the .htaccess rewrites - AllowOverride All - Order allow,deny - Allow from All - - - ErrorLog /var/log/apache2/project_error.log - CustomLog /var/log/apache2/project_access.log combined - - -.. note:: - - For performance reasons, you will probably want to set - ``AllowOverride None`` and implement the rewrite rules in the ``web/.htaccess`` - into the virtualhost config. - -Nginx ------ - -For advanced Nginx configuration options, see the official `Nginx`_ -documentation. The minimum basics to get your application running under Nginx -are: - -.. code-block:: nginx - - server { - server_name www.domain.tld; - root /var/www/project/web; - - location / { - # try to serve file directly, fallback to rewrite - try_files $uri @rewriteapp; - } - - location @rewriteapp { - # rewrite all to app.php - rewrite ^(.*)$ /app.php/$1 last; - } - - location ~ ^/(app|app_dev)\.php(/|$) { - fastcgi_pass unix:/var/run/php5-fpm.sock; - fastcgi_split_path_info ^(.+\.php)(/.*)$; - include fastcgi_params; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_param HTTPS off; - } - - error_log /var/log/nginx/project_error.log; - access_log /var/log/nginx/project_access.log; - } - -.. note:: - - Depending on your PHP-FPM config, the ``fastcgi_pass`` can also be - ``fastcgi_pass 127.0.0.1:9000``. - -.. tip:: - - This executes **only** ``app.php`` and ``app_dev.php`` in the web directory. - All other files will be served as text. If you have other PHP files in - your web directory, be sure to include them in the ``location`` block - above. - -.. _`Apache`: http://httpd.apache.org/docs/current/mod/core.html#documentroot -.. _`Nginx`: http://wiki.nginx.org/Symfony diff --git a/_sources/cookbook/console/console_command.txt b/_sources/cookbook/console/console_command.txt deleted file mode 100644 index 1788bfd..0000000 --- a/_sources/cookbook/console/console_command.txt +++ /dev/null @@ -1,127 +0,0 @@ -.. index:: - single: Console; Creando ordenes - -Cómo crear una orden de consola -=============================== - -La página ``Console`` de la sección de componentes (:doc:`/components/console/introduction`) trata sobre cómo crear una orden de Consola. Este artículo trata sobre las diferencias cuando creas ordenes de consola en la plataforma *Symfony2*. - -Registrando ordenes automáticamente ------------------------------------ - -Para hacer que las ordenes de consola estén disponibles automáticamente en *Symfony2*, crea un directorio ``Command`` dentro de tu paquete y crea un archivo *PHP* con el sufijo :file:`Command.php` para cada orden que deses proveer. Por ejemplo, si deseas ampliar el ``AcmeDemoBundle`` (disponible en la *edición estándar de Symfony*) para darte la bienvenida desde la línea de ordenes, crea el archivo :file:`GreetCommand.php` y agrégale lo siguiente:: - - // src/Acme/DemoBundle/Command/GreetCommand.php - namespace Acme\DemoBundle\Command; - - use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; - use Symfony\Component\Console\Input\InputArgument; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Input\InputOption; - use Symfony\Component\Console\Output\OutputInterface; - - class GreetCommand extends ContainerAwareCommand - { - protected function configure() - { - $this - ->setName('demo:greet') - ->setDescription('Greet someone') - ->addArgument('name', InputArgument::OPTIONAL, 'Who do you want to greet?') - ->addOption('yell', null, InputOption::VALUE_NONE, 'If set, the task will yell in uppercase letters') - ; - } - - protected function execute(InputInterface $input, OutputInterface $output) - { - $name = $input->getArgument('name'); - if ($name) { - $text = 'Hello '.$name; - } else { - $text = 'Hello'; - } - - if ($input->getOption('yell')) { - $text = strtoupper($text); - } - - $output->writeln($text); - } - } - -Ahora, automáticamente, esta orden estará disponible para funcionar: - -.. code-block:: bash - - $ app/console demo:greet Fabien - -Obteniendo servicios del contenedor de servicios ------------------------------------------------- - -Al usar :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand` como la clase base para la orden (en lugar de la clase más básica :class:`Symfony\\Component\\Console\\Command\\Command`), tienes acceso al contenedor de servicios. En otras palabras, tienes acceso a cualquier servicio configurado. -Por ejemplo, fácilmente podrías extender la tarea para que sea traducible:: - - protected function execute(InputInterface $input, OutputInterface $output) - { - $name = $input->getArgument('name'); - $translator = $this->getContainer()->get('translator'); - if ($name) { - $output->writeln($translator->trans('Hello %name%!', array('%name%' => $name))); - } else { - $output->writeln($translator->trans('Hello!')); - } - } - -Probando ordenes ----------------- - -Cuando pruebes ordenes utilizadas como parte de la plataforma completa debes utilizar la :class:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application` en lugar de :class:`Symfony\\Component\\Console\\Application`:: - - use Symfony\Component\Console\Tester\CommandTester; - use Symfony\Bundle\FrameworkBundle\Console\Application; - use Acme\DemoBundle\Command\GreetCommand; - - class ListCommandTest extends \PHPUnit_Framework_TestCase - { - public function testExecute() - { - // simula el Kernel o crea uno dependiendo de tus necesidades - $application = new Application($kernel); - $application->add(new GreetCommand()); - - $command = $application->find('demo:greet'); - $commandTester = new CommandTester($command); - $commandTester->execute(array('command' => $command->getName())); - - $this->assertRegExp('/.../', $commandTester->getDisplay()); - - // ... - } - } - -Para poder utilizar plenamente el contenedor del servicios configurado en tus pruebas de consola puedes extender tu prueba de :class:`Symfony\\Bundle\\FrameworkBundle\\Test\\WebTestCase`:: - - use Symfony\Component\Console\Tester\CommandTester; - use Symfony\Bundle\FrameworkBundle\Console\Application; - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - use Acme\DemoBundle\Command\GreetCommand; - - class ListCommandTest extends WebTestCase - { - public function testExecute() - { - $kernel = $this->createKernel(); - $kernel->boot(); - - $application = new Application($kernel); - $application->add(new GreetCommand()); - - $command = $application->find('demo:greet'); - $commandTester = new CommandTester($command); - $commandTester->execute(array('command' => $command->getName())); - - $this->assertRegExp('/.../', $commandTester->getDisplay()); - - // ... - } - } diff --git a/_sources/cookbook/console/index.txt b/_sources/cookbook/console/index.txt deleted file mode 100644 index 3298093..0000000 --- a/_sources/cookbook/console/index.txt +++ /dev/null @@ -1,10 +0,0 @@ -Consola -======= - -.. toctree:: - :maxdepth: 2 - - console_command - usage - sending_emails - logging diff --git a/_sources/cookbook/console/logging.txt b/_sources/cookbook/console/logging.txt deleted file mode 100644 index f4f571c..0000000 --- a/_sources/cookbook/console/logging.txt +++ /dev/null @@ -1,228 +0,0 @@ -.. index:: - single: Console; Habilitando el registro cronológico - -Cómo habilitar el registro cronológico en la consola de ordenes -=============================================================== - -El componente ``Console`` no proporciona la capacidad de registro cronológico fuera de la caja. -Normalmente, ejecutas manualmente las ordenes de la consola y observas el resultado, motivo por el cual no se proporciona el registro cronológico. Sin embargo, hay casos en los que puedes necesitar el registro. Por ejemplo, si estás ejecutando ordenes de consola desatendidas, tal como trabajos programados o guiones de despliegue, posiblemente sea más fácil usar las capacidades de registro de *Symfony* en lugar de configurar otras herramientas para reunir el resultado de la consola y procesarlo. Esto puede ser especialmente útil si ya tienes alguna configuración existente para agregar y analizar los registros de *Symfony*. - -Básicamente, hay dos casos de registro que puedes necesitar: - * El registo manual de cierta información de tu orden; - * El registro de excepciones no capturadas. - -Registrando manualmente una orden de consola --------------------------------------------- - -Esto es realmente sencillo. Cuando creas una orden de consola en la plataforma completa, como se describe en «:doc:`/cookbook/console/console_command`», tu orden extiende a :class:`Symfony\\Bundle\\FrameworkBundle\\Command\\ContainerAwareCommand`. -Esto significa que sólo tienes que acceder al servicio de registro estándar a través del contenedor y usarlo para hacer el registro:: - - // src/Acme/DemoBundle/Command/GreetCommand.php - namespace Acme\DemoBundle\Command; - - use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand; - use Symfony\Component\Console\Input\InputArgument; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Input\InputOption; - use Symfony\Component\Console\Output\OutputInterface; - use Symfony\Component\HttpKernel\Log\LoggerInterface; - - class GreetCommand extends ContainerAwareCommand - { - // ... - - protected function execute(InputInterface $input, OutputInterface $output) - { - /** @var $logger LoggerInterface */ - $logger = $this->getContainer()->get('logger'); - - $name = $input->getArgument('name'); - if ($name) { - $text = 'Hello '.$name; - } else { - $text = 'Hello'; - } - - if ($input->getOption('yell')) { - $text = strtoupper($text); - $logger->warn('Yelled: '.$text); - } - else { - $logger->info('Greeted: '.$text); - } - - $output->writeln($text); - } - } - -Dependiendo del entorno en el que se ejecuta la orden (y tu configuración de registro cronológico), deberías ver las entradas registradas en :file:`app/logs/dev.log` o :file:`app/logs/prod.log`. - -Habilitando el registro automático de excepciones -------------------------------------------------- - -Para que tu aplicación de consola automáticamente registre las excepciones no capturadas para todas tus órdenes, tendrás que hacer un poco más de trabajo. - -En primer lugar, crea una nueva subclase de :class:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application` y reemplaza su método :method:`Symfony\\Bundle\\FrameworkBundle\\Console\\Application::run`, donde debe suceder el manejo de las excepciones: - -.. caution:: - - Debido a la naturaleza de la clase :class:`Symfony\\Component\\Console\\Application` del núcleo, gran parte del método :method:`run` se tiene que duplicar e incluso reimplementar la propiedad privada ``originalAutoExit``. Esto sirve como ejemplo de lo que *podrías* hacer en tu código, aunque hay un muy alto riesgo de que algo se pueda romper al actualizar a las futuras versiones de *Symfony*. - - -.. code-block:: php - - // src/Acme/DemoBundle/Console/Application.php - namespace Acme\DemoBundle\Console; - - use Symfony\Bundle\FrameworkBundle\Console\Application as BaseApplication; - use Symfony\Component\Console\Input\InputInterface; - use Symfony\Component\Console\Output\OutputInterface; - use Symfony\Component\Console\Output\ConsoleOutputInterface; - use Symfony\Component\HttpKernel\Log\LoggerInterface; - use Symfony\Component\HttpKernel\KernelInterface; - use Symfony\Component\Console\Output\ConsoleOutput; - use Symfony\Component\Console\Input\ArgvInput; - - class Application extends BaseApplication - { - private $originalAutoExit; - - public function __construct(KernelInterface $kernel) - { - parent::__construct($kernel); - $this->originalAutoExit = true; - } - - /** - * Ejecuta la aplicación actual. - * - * @param InputInterface $input un ejemplar de entrada - * @param OutputInterface $output un ejemplar de salida - * - * @return integer 0 si todo salió bien, o un código de error - * - * @throws \Exception cuándo run devuelva una Excepción - * - * @api - */ - public function run(InputInterface $input = null, OutputInterface $output = null) - { - // hace que el padre lance excepciones, para que las puedas registrar - $this->setCatchExceptions(false); - - if (null === $input) { - $input = new ArgvInput(); - } - - if (null === $output) { - $output = new ConsoleOutput(); - } - - try { - $statusCode = parent::run($input, $output); - } catch (\Exception $e) { - - /** @var $logger LoggerInterface */ - $logger = $this->getKernel()->getContainer()->get('logger'); - - $message = sprintf( - '%s: %s (uncaught exception) at %s line %s while running console command `%s`', - get_class($e), - $e->getMessage(), - $e->getFile(), - $e->getLine(), - $this->getCommandName($input) - ); - $logger->crit($message); - - if ($output instanceof ConsoleOutputInterface) { - $this->renderException($e, $output->getErrorOutput()); - } else { - $this->renderException($e, $output); - } - $statusCode = $e->getCode(); - - $statusCode = is_numeric($statusCode) && $statusCode ? $statusCode : 1; - } - - if ($this->originalAutoExit) { - if ($statusCode > 255) { - $statusCode = 255; - } - // @codeCoverageIgnoreStart - exit($statusCode); - // @codeCoverageIgnoreEnd - } - - return $statusCode; - } - - public function setAutoExit($bool) - { - // la propiedad parent es privada, por lo que es necesario - // interceptarla en un definidor - $this->originalAutoExit = (Boolean) $bool; - parent::setAutoExit($bool); - } - - } - -En el código anterior, desactivaste la captura de excepciones para que el padre lance el método ``run`` en todas las excepciones. Cuando se detecta una excepción, simplemente la registras accediendo al servicio ``logger`` del contenedor de servicios y luego manejas el resto de la lógica de la misma manera que lo hace el método ``run`` del padre (específicamente, debido a que el método :method:`run` del padre no se encargará de la representación y manipulación del código de estado de las excepciones cuando ``catchExceptions`` se establezca en false, esto se tiene que hacer en el método redefinido). - -Para que trabaje correctamente la clase aplicación extendida con la consola en modo de intérprete, tienes que hacer un pequeño truco para interceptar el definidor ``autoExit`` y almacenar el valor en una propiedad diferente, ya que la propiedad del padre es privada. - -Ahora, para poder utilizar tu clase ``Aplicación`` extendida es necesario ajustar el guión :file:`app/console` para utilizar la nueva clase en lugar de la predeterminada:: - - // app/console - - // ... - // reemplaza la siguiente línea: - // use Symfony\Bundle\FrameworkBundle\Console\Application; - use Acme\DemoBundle\Console\Application; - - // ... - -¡Eso es todo! Gracias al cargador automático, ahora puedes utilizar tu clase en lugar de la original. - -Registrando estados de salida distintos de 0 --------------------------------------------- - -Las capacidades de registro de la consola se pueden ampliar aún más registrando estados de salida distintos de 0. De esta manera sabrás si alguna orden tuvo algún error, incluso si no se lanzaron excepciones. - -Con el fin de hacerlo, tendrás que modificar el método ``run()`` de tu clase ``Aplicación`` extendida de la siguiente manera:: - - public function run(InputInterface $input = null, - OutputInterface $output = null) - { - // hace que el padre lance excepciones, para que las puedas registrar - $this->setCatchExceptions(false); - - // almacena el valor de autoExit antes de - // restablecerlo - lo necesitarás más adelante - $autoExit = $this->originalAutoExit; - $this->setAutoExit(false); - - // ... - - if ($autoExit) { - if ($statusCode > 255) { - $statusCode = 255; - } - - // registra códigos de salida distintos de 0 junto - // con el nombre de la orden - if ($statusCode !== 0) { - /** @var $logger LoggerInterface */ - $logger = $this->getKernel()->getContainer()->get('logger'); - $logger->warn(sprintf('Command `%s` exited with status code %d', - $this->getCommandName($input), - $statusCode)); - } - - // @codeCoverageIgnoreStart - exit($statusCode); - // @codeCoverageIgnoreEnd - } - - return $statusCode; - } diff --git a/_sources/cookbook/console/sending_emails.txt b/_sources/cookbook/console/sending_emails.txt deleted file mode 100644 index 3df7395..0000000 --- a/_sources/cookbook/console/sending_emails.txt +++ /dev/null @@ -1,101 +0,0 @@ -.. index:: - single: Console; Enviando mensajes de correo electrónico - single: Console; Generando URL - -Cómo generar *URL* y enviar mensajes de correo electrónico desde la consola -=========================================================================== - -Desafortunadamente, el contexto de la línea de ordenes no sabe nada de tu servidor virtual o nombre de dominio. Esto significa que si generas *URL* absolutas dentro de una orden de consola, probablemente terminarás con algo inútil similar a ``http://localhost/foo/bar``. - -Para solucionar este problema, necesitas configurar el «contexto de la petición», que es una caprichosa manera de decir que necesitas configurar el entorno para que sepa qué *URL* debe utilizar al generar direcciones *URL*. - -Hay dos maneras de configurar el contexto de la petición: a nivel de aplicación y por orden. - -Configurando el contexto de la petición a nivel global ------------------------------------------------------- - - -.. versionadded:: 2.1 - Los parámetros ``host`` y ``scheme`` están disponibles a partir de *Symfony 2.1* - -.. versionadded:: 2.2 - El parámetro ``base_url`` está disponible desde *Symfony 2.2* - -Para configurar el contexto de la petición ---el cual utiliza el generador de *URL*--- puedes redefinir los parámetros que utiliza como valores predefinidos para cambiar el servidor (``localhost``) y esquema (``http``) predeterminado. A partir de *Symfony 2.2* también puedes configurar la ruta base siempre y cuando *Symfony* no se esté ejecutando en el directorio raíz. - -Ten en cuenta que esto no impacta directamente en las *URL* generadas a través de peticiones web normales, ya que se sustituyen los valores predeterminados. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - router.request_context.host: example.org - router.request_context.scheme: https - router.request_context.base_url: my/path - - .. code-block:: xml - - - - - - - - example.org - https - my/path - - - - .. code-block:: php - - // app/config/config_test.php - $container->setParameter('router.request_context.host', 'example.org'); - $container->setParameter('router.request_context.scheme', 'https'); - $container->setParameter('router.request_context.base_url', 'my/path'); - -Configurando el contexto de la petición por orden -------------------------------------------------- - -Para cambiarlo sólo en una orden sencillamente puede recuperar el contexto del servicio de la petición y sustituir sus opciones:: - - // src/Acme/DemoBundle/Command/DemoCommand.php - - // ... - class DemoCommand extends ContainerAwareCommand - { - protected function execute(InputInterface $input, OutputInterface $output) - { - $context = $this->getContainer()->get('router')->getContext(); - $context->setHost('example.com'); - $context->setScheme('https'); - $context->setBaseUrl('my/path'); - - // ... tu código aquí - } - } - -Con cola de memoria -------------------- - -El envío de mensajes de correo electrónico en una orden de consola funciona de la misma manera descrita en el artículo :doc:`/cookbook/email/email` del recetario, salvo que esta utiliza memoria de la cola de impresión. - -Cuando se utiliza memoria de la cola (consulta el :doc:`/cookbook/email/spool` del recetario para más información), debes tener en cuenta que debido a la forma en que *Symfony* se maneja de consola de ordenes, los mensajes de correo electrónico no se envían automáticamente. Debes tener cuidado de vaciar tú mismo la cola. Utiliza el siguiente código para enviar mensajes de correo electrónico dentro de tu orden de consola:: - - $container = $this->getContainer(); - $mailer = $container->get('mailer'); - $spool = $mailer->getTransport()->getSpool(); - $transport = $container->get('swiftmailer.transport.real'); - - $spool->flushQueue($transport); - -Otra opción es crear un ambiente que sólo sea utilizado por esa orden de consola y utilizar un método diferente en la cola de impresión. - -.. note:: - - Sólo es necesario tener cuidado de la cola de impresión cuando se utiliza memoria de la cola de impresión. - Si estás utilizando un archivo para la cola (o no utilizas la cola de impresión), no es necesario vaciar manualmente la cola dentro de la orden. - diff --git a/_sources/cookbook/console/usage.txt b/_sources/cookbook/console/usage.txt deleted file mode 100644 index 900abc2..0000000 --- a/_sources/cookbook/console/usage.txt +++ /dev/null @@ -1,52 +0,0 @@ -.. index:: - single: Console; Usando - -Cómo usar la consola -==================== - -La página :doc:`/components/console/usage` de la documentación de los componentes en las opciones globales de la consola. Cuándo utilizas la consola como parte de la pila completa de la plataforma, también dispones de algunas opciones globales adicionales. - -De manera predeterminada, las ordenes de la consola se ejecutan en el entorno ``dev`` y posiblemente lo quieras cambiar en algunas órdenes. Por ejemplo, posiblemente quieras ejecutar algunas órdenes en el entorno ``prod`` por razones de rendimiento. Además, el resultado de algunas órdenes será diferente dependiendo del entorno. Por ejemplo, la orden ``cache:clear`` limpiará y preparará la caché sólo para el entorno especificado. Para limpiar y preparar la caché del entorno ``prod`` debes ejecutar: - -.. code-block:: bash - - $ php app/console cache:clear --env=prod - -o la equivalente: - -.. code-block:: bash - - $ php app/console cache:clear -e=prod - -Además de cambiar el entorno, también puedes elegir desactivar el modo de depuración. -Esto puede ser útil cuándo quieras ejecutar órdenes en el entorno ``dev`` pero evitando el impacto negativo en el rendimiento por la recolección de datos para depuración: - -.. code-block:: bash - - $ php app/console list --no-debug - -hay un intérprete interactivo que te permite introducir órdenes sin tener que especificar cada vez ``php app/console``, el cual es útil si necesitas ejecutar varias órdenes. Para entrar al intérprete ejecuta: - -.. code-block:: bash - - $ php app/console --shell - $ php app/console -s - -Ahora puedes ejecutar órdenes sólo con el nombre de la orden: - -.. code-block:: bash - - Symfony > list - -Cuándo utilizas el intérprete puedes elegir ejecutar cada orden en un proceso separado: - -.. code-block:: bash - - $ php app/console --shell --process-isolation - $ php app/console -s --process-isolation - -Cuándo lo haces, la producción no será coloreada y no cuentas con apoyo para la interactividad por lo tanto necesitarás pasar todos los argumentos para las ordenes explícitamente. - -.. note:: - - A no ser que estés usando procesos separados, limpiar la caché en el intérprete no tendrá efecto en las subsecuentes ordenes que ejecutes. Esto se debe a que todavía se están utilizando los archivos memorizados originalmente. \ No newline at end of file diff --git a/_sources/cookbook/controller/error_pages.txt b/_sources/cookbook/controller/error_pages.txt deleted file mode 100644 index b5a147b..0000000 --- a/_sources/cookbook/controller/error_pages.txt +++ /dev/null @@ -1,70 +0,0 @@ -.. index:: - single: Controlador; Personalizando páginas de error - single: Páginas de error - -Cómo personalizar páginas de error -================================== - -Cuando se lanza alguna excepción en *Symfony2*, la excepción es capturada dentro de la clase del ``núcleo`` y, finalmente, remitida a un controlador especial, ``TwigBundle:Exception:show`` para procesarla. Este controlador, el cual vive dentro del núcleo de ``TwigBundle``, determina cual plantilla de error mostrar y el código de estado que se debe establecer para la excepción dada. - -Puedes personalizar las páginas de error de dos formas diferentes, dependiendo de la cantidad de control que necesites: - -1. Personalizando las plantillas de error de las diferentes páginas de error (se explica más adelante); - -2. Reemplazando el controlador de excepciones ``TwigBundle::Exception:show`` predeterminado con tu propio controlador y procesándolo como quieras (consulta :ref:`exception_controller en la referencia de Twig `); - -.. tip:: - - Personalizar el tratamiento de las excepciones en realidad es mucho más poderoso que lo escrito aquí. Produce un evento interno, ``core.exception``, el cual te permite completo control sobre el manejo de la excepción. Para más información, consulta el :ref:`kernel-kernel.exception`. - -Todas las plantillas de error viven dentro de ``TwigBundle``. Para sustituir las plantillas, simplemente confiamos en el método estándar para reemplazar las plantillas que viven dentro de un paquete. Para más información, consulta :ref:`overriding-bundle-templates`. - -Por ejemplo, para sustituir la plantilla de error predeterminada mostrada al usuario final, crea una nueva plantilla ubicada en ``app/Resources/TwigBundle/views/Exception/error.html.twig``: - -.. code-block:: html+jinja - - - - - - An Error Occurred: {{ status_text }} - - -

    Oops! An Error Occurred

    -

    The server returned a "{{ status_code }} {{ status_text }}".

    - - - -.. caution:: - - **No debes** utilizar ``is_granted`` en tus páginas de error (o diseños utilizados por tus páginas de error), porque el enrutador se ejecuta antes del cortafuegos. Si el enrutador lanza una excepción (por ejemplo, cuándo la ruta no coincide), entonces utilizar ``is_granted`` desencadenará una nueva excepción. You - can use ``is_granted`` safely by saying ``{% if app.user and is_granted('...') %}``. - -.. tip:: - - Si no estás familiarizado con *Twig*, no te preocupes. *Twig* es un sencillo, potente y opcional motor de plantillas que se integra con *Symfony2*. Para más información sobre *Twig* consulta :doc:`/book/templating`. - -Además de la página de error *HTML* estándar, *Symfony* proporciona una página de error predeterminada para muchos de los más comunes formatos de respuesta, como *JSON* (``error.json.twig``), *XML* (``error.xml.twig``) e incluso *Javascript* (``error.js.twig``), sólo por nombrar algunos. Para sustituir cualquiera de estas plantillas, basta con crear un nuevo archivo con el mismo nombre en el directorio ``app/Resources/TwigBundle/views/Exception``. Esta es la manera estándar de sustituir cualquier plantilla que viva dentro de un paquete. - -.. _cookbook-error-pages-by-status-code: - -Personalizando la página 404 y otras páginas de error ------------------------------------------------------ - -También puedes personalizar plantillas de error específicas de acuerdo con el código de estado *HTTP*. Por ejemplo, crea una plantilla ``app/Resources/TwigBundle/views/Exception/error404.html.twig`` para mostrar una página especial para los errores 404 (página no encontrada). - -*Symfony* utiliza el siguiente algoritmo para determinar qué plantilla utilizar: - -* En primer lugar, busca una plantilla para el formato dado y el código de estado (como ``error404.json.twig``); - -* Si no existe, busca una plantilla para el formato propuesto (como ``error.json.twig``); - -* Si no existe, este cae de nuevo a la plantilla *HTML* (como ``error.html.twig``). - -.. tip:: - - Para ver la lista completa de plantillas de error predeterminadas, revisa el directorio ``Resources/views/Exception`` de ``TwigBundle``. En una instalación estándar de *Symfony2*, el ``TwigBundle`` se puede encontrar en ``vendor/symfony/symfony/src/Symfony/Bundle/TwigBundle``. A menudo, la forma más fácil de personalizar una página de error es copiarla de ``TwigBundle`` a ``app/Resources/TwigBundle/views/Exception`` y luego modificarla. - -.. note:: - - El amigable depurador de páginas de excepción muestra al desarrollador cómo, incluso, puede personalizar de la misma manera creando plantillas como ``exception.html.twig`` para la página de excepción *HTML* estándar o ``exception.json.twig`` para la página de excepción *JSON*. diff --git a/_sources/cookbook/controller/index.txt b/_sources/cookbook/controller/index.txt deleted file mode 100644 index 85bd4cf..0000000 --- a/_sources/cookbook/controller/index.txt +++ /dev/null @@ -1,8 +0,0 @@ -Controlador -=========== - -.. toctree:: - :maxdepth: 2 - - error_pages - service diff --git a/_sources/cookbook/controller/service.txt b/_sources/cookbook/controller/service.txt deleted file mode 100644 index 92da95d..0000000 --- a/_sources/cookbook/controller/service.txt +++ /dev/null @@ -1,43 +0,0 @@ -.. index:: - single: Controlador; Como servicio - -Cómo definir controladores como servicios -========================================= - -En el libro, has aprendido lo fácilmente que puedes utilizar un controlador cuando extiende la clase base :class:`Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller`. Si bien esto funciona estupendamente, los controladores también se pueden especificar como servicios. - -Para referir un controlador que se defina como servicio, utiliza la notación de dos puntos individuales (:). Por ejemplo, supongamos que has definido un servicio llamado ``my_controller`` y ---dentro del servicio--- lo quieres redirigir a un método llamado ``indexAction()``:: - - $this->forward('my_controller:indexAction', array('foo' => $bar)); - -Necesitas usar la misma notación para definir el valor ``_controller`` de la ruta: - -.. code-block:: yaml - - my_controller: - path: / - defaults: { _controller: my_controller:indexAction } - -Para utilizar un controlador de esta manera, este se debe definir en la configuración del contenedor de servicios. Para más información, consulta el capítulo :doc:`Contenedor de servicios `. - -Cuando se utiliza un controlador definido como servicio, lo más probable es no ampliar la clase base ``Controller``. En lugar de confiar en sus métodos de acceso directo, debes interactuar directamente con los servicios que necesitas. Afortunadamente, esto suele ser bastante fácil y la clase base ``Controller`` en sí es una gran fuente sobre la manera de realizar muchas tareas comunes. - -.. note:: - - Especificar un controlador como servicio requiere un poco más de trabajo. La principal ventaja es que el controlador completo o cualquier otro servicio pasado al controlador se puede modificar a través de la configuración del contenedor de servicios. - Esto es útil especialmente cuando desarrollas un paquete de código abierto o cualquier paquete que se pueda utilizar en muchos proyectos diferentes. Así que, aunque no especifiques los controladores como servicios, es probable que veas hacer esto en algunos paquetes de código abierto de *Symfony2*. - -Utilizando anotaciones en enrutado ----------------------------------- - -Cuándo utilizas anotaciones para configurar el enrutado al utilizar un controlador definido como servicio, es necesario especificar tu servicio de la siguiente manera:: - - /** - * @Route("/blog", service="my_bundle.annot_controller") - * @Cache(expires="tomorrow") - */ - class AnnotController extends Controller - { - } - -En este ejemplo, ``my_bundle.annot_controller`` tendría que ser el *id* de la instancia de ``AnnotController`` definida en el contenedor del servicio. Esto está documentado en el capítulo :doc:`/bundles/SensioFrameworkExtraBundle/annotations/routing`. diff --git a/_sources/cookbook/debugging.txt b/_sources/cookbook/debugging.txt deleted file mode 100644 index 4cedd3d..0000000 --- a/_sources/cookbook/debugging.txt +++ /dev/null @@ -1,51 +0,0 @@ -.. index:: - single: Depurando - -Cómo optimizar tu entorno de desarrollo para depuración -======================================================= - -Cuando trabajas en un proyecto de *Symfony* en tu equipo local, debes usar el entorno ``dev`` (con el controlador frontal :file:`app_dev.php`). Esta configuración del entorno se ha optimizado para dos propósitos principales: - -* Proporcionar retroalimentación de desarrollo precisa cada vez que algo sale mal (barra de herramientas de depuración *web*, páginas de excepción agradables, perfiles, ...); - -* Ser lo más parecido posible al entorno de producción para evitar problemas al desplegar el proyecto. - -.. _cookbook-debugging-disable-bootstrap: - -Desactivando el archivo de arranque y la caché de clase -------------------------------------------------------- - -Y para que el entorno de producción sea lo más rápido posible, *Symfony* crea grandes archivos *PHP* en la memoria caché que contienen la agregación de las clases *PHP* que tu proyecto necesita para cada petición. Sin embargo, este comportamiento puede confundir a tu *IDE* o depurador. Esta fórmula muestra cómo puedes ajustar este mecanismo de memorización para que sea más amigable cuando necesitas depurar código que incluye clases de *Symfony*. - -El controlador frontal :file:`app_dev.php` por omisión lee lo siguiente:: - - // ... - - $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; - require_once __DIR__.'/../app/AppKernel.php'; - - $kernel = new AppKernel('dev', true); - $kernel->loadClassCache(); - $request = Request::createFromGlobals(); - -Para contentar a tu depurador, desactiva toda la caché de las clases *PHP* eliminando la llamada a ``loadClassCache()`` y sustituyendo las declaraciones `require` como la siguiente:: - - // ... - - // $loader = require_once __DIR__.'/../app/bootstrap.php.cache'; - $loader = require_once __DIR__.'/../app/autoload.php'; - require_once __DIR__.'/../app/AppKernel.php'; - - use Symfony\Component\HttpFoundation\Request; - - $kernel = new AppKernel('dev', true); - // $kernel->loadClassCache(); - $request = Request::createFromGlobals(); - -.. tip:: - - Si desactivas la caché *PHP*, no olvides reactivarla después de tu sesión de depuración. - -A algunos *IDE* no les gusta el hecho de que algunas clases se almacenen en lugares diferentes. Para evitar problemas, puedes decirle a tu *IDE* que omita los archivos de cache *PHP*, o puedes cambiar la extensión utilizada por *Symfony* para estos archivos:: - - $kernel->loadClassCache('classes', '.php.cache'); diff --git a/_sources/cookbook/deployment-tools.txt b/_sources/cookbook/deployment-tools.txt deleted file mode 100644 index 2bfb514..0000000 --- a/_sources/cookbook/deployment-tools.txt +++ /dev/null @@ -1,163 +0,0 @@ -.. index:: - single: Desplegando - -Cómo desplegar una aplicación *Symfony2* -======================================== - -.. note:: - - El desplegando puede ser una tarea compleja y diversa que depende de tu configuración y necesidades. - Esta sección no trata de explicarlo todo, sino que más bien ofrece la mayoría de los requisitos e ideas comunes para su implementación. - -Fundamentos para desplegar *Symfony2* -------------------------------------- - -Los pasos típicos tomados para desplegar una aplicación *Symfony2* incluyen: - -#. Subir tu código modificado al servidor en producción; -#. Actualizar tus dependencias de proveedores (normalmente esto se realiza a través de ``Composer``, y se puede hacer antes de subir tu código); -#. Ejecutar las migraciones de bases de datos o tareas similares para actualizar la estructura de los datos modificados; -#. Limpiar (y tal vez lo más importante, preparar) tu caché. - -El desplegado también puede incluir otras cosas, tal como: - -* Etiquetar una versión particular del tu código como una versión en tu repositorio de control de código fuente; -* Creación de una zona de estacionamiento temporal para construir la configuración actualizada «fuera de línea»; -* Ejecutar cualquier prueba disponible para garantizar la estabilidad del código y/o el servidor; -* La eliminación de todos los archivos innecesarios del directorio ``web`` para mantener limpio tu entorno de producción; -* Limpieza de los sistemas externos de memoria caché (como `Memcached`_ o `Redis`_). - -Cómo desplegar una aplicación *Symfony2* ----------------------------------------- - -Hay varias maneras en que puedes desplegar una aplicación *Symfony2*. - -Empezarás con algunas estrategias para desplegar básicas y a partir de ahí continuarás. - -Transferencia de archivos básica -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -La forma más básica para desplegar una aplicación es copiar los archivos manualmente -via ``ftp/scp`` (o algún otro método similar). Esto tiene sus desventajas, ya que carecen de control sobre el sistema a medida que progresa su actualización. Este método también requiere que tomes algunos pasos manuales después de transferir los archivos (consulta la sección `Tareas habituales posteriores al despliegue`_) - -Usando control de código fuente -------------------------------- - -Si estás utilizando control de código fuente (por ejemplo, ``git`` o ``svn``), puedes simplificar esto teniendo tu instalación en vivo también como una copia en tu repositorio. Cuando estés listo para actualizarlo es tan simple como ir a buscar a las novedades desde tu sistema de control de código fuente. - -Esto *facilita* la actualización de tus archivos, pero todavía te tienes que preocupar de tomar otras medidas de control manualmente (consulta la sección `Tareas habituales posteriores al despliegue`_). - -Usando programas de creación y otras herramientas -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -También hay herramientas de alta calidad para ayudarte a aliviar el dolor de la implantación. Incluso hay unas cuantas herramientas que se han adaptado específicamente a las necesidades de *Symfony2*, y que tienen un cuidado especial para asegurarse de que todo esté correcto antes, durante y después de un despliegue. - -Ve `Las herramientas`_ para obtener una lista de las herramientas que te pueden ayudar con el despliegue. - -Tareas habituales posteriores al despliegue -------------------------------------------- - -Después de implantar el código fuente real, hay una serie de tareas comunes que -tendrás que llevar a cabo: - -A) Configura tu archivo :file:`app/config/parameters.yml` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Este archivo se debe personalizar en cada sistema. Cualquier método que utilices para desplegar tu código fuente *no* debe trasladar este archivo. En su lugar, los debes configurar manualmente (o mediante algún proceso de construcción) en tu servidor. - -B) Actualiza tus proveedores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Tus proveedores se pueden actualizar antes de transferir el código fuente (es decir, -actualiza el directorio ``vendor/``, y luego transfierelo, con tu código fuente), o actualízalo en el servidor después de transferir tu código fuente. En cualquier caso, basta con actualizar tus proveedores como lo haces normalmente: - -.. code-block:: bash - - $ php composer.phar install --optimize-autoloader - -.. tip:: - - La bandera ``--optimize-autoloader`` hace que ``Composer`` lleve a cabo una optimización de la carga automática construyendo un «mapa de clases». - -C) Borra la caché de *Symfony* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Asegúrate de limpiar (y preparar) la caché de *Symfony*: - -.. code-block:: bash - - $ php app/console cache:clear --env=prod --no-debug - -D) Vuelca tus activos con *Assetic* -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si estás utilizando *Assetic*, también querrás volcar tus activos: - -.. code-block:: bash - - $ php app/console assetic:dump --env=prod --no-debug - -E) ¡Otras cosas! -~~~~~~~~~~~~~~~~ - -Es posible que haya un montón de otras cosas que necesites hacer, dependiendo de tu -configuración: - -* La ejecución de cualquier migración de bases de datos -* Vaciado de tu caché *APC* -* - La ejecución de ``assets:install`` (teniendo cuidado si ya se hizo en ``composer.phar install``) -* Añadir/editar trabajos ``CRON`` -* Empujar activos a un ``CDN`` -* ... - -Ciclo de vida de una aplicación: Integración continua, control de calidad, etc. -------------------------------------------------------------------------------- - -Si bien esta sección corresponde a los detalles técnicos del despliegue, el ciclo de vida completo a partir de tomar el código fuente de desarrollo y trasladarlo a producción puede tener mucho más pasos (creo que desde el despliegue hasta la puesta en escena, control de calidad, ejecución de pruebas, etc.). - -Utilizar la puesta en escena, pruebas, control de calidad, integración continua, migraciones de bases de datos y la capacidad para poder retroceder en caso de fallo, todas son muy recomendables. Hay herramientas simples y complejas y puedes hacer tan fácil (o compleja) la instalación como tu entorno requiera. - -No olvides que el despliegue de tu aplicación también implica la actualización de cualquier dependencia (normalmente a través de ``Composer``), la migración de tu base de datos, vaciar la caché y otras cosas posibles como empujar tus activos a un *CDN* (ve `Tareas habituales posteriores al despliegue`_). - -Las herramientas ----------------- - -`Capifony`_: - - Esta herramienta ofrece un conjunto especializado de herramientas en la parte superior de *Capistrano*, específicamente a la medida de proyectos *Symfony* y *Symfony2*. - -`sf2debpkg`_: - - Esta herramienta te ayuda a construir un paquete nativo de *Debian* para tu proyecto *Symfony2*. - -`Magallanes`_: - - Esta herramienta de despliegue tipo *Capistrano* está construida en *PHP*, y puede ser facilitar a los desarrolladores de *PHP* su extensión para cubrir sus necesidades. - -Paquetes: - - Hay muchos `paquetes que añaden características de despliegue`_ directamente en tu consola *Symfony2*. - -Programación básica: - - Puedes, por supuesto, usar el intérprete de ordenes, `Ant`_, o cualquier otra herramienta para programar el despliegue de tu proyecto. - -El ambiente como proveedor de servicios: - - ``PaaS`` es una forma relativamente nueva para desplegar tu aplicación. Normalmente, un ``PaaS`` utilizará un único archivo de configuración en el directorio raíz de tu proyecto para determinar la forma de construir un ambiente al vuelo que apoye a tu software. - Un proveedor con el apoyo confirmado para *Symfony2* es `PagodaBox`_. - -.. tip:: - - ¿Buscando más? Habla con la comunidad en el `canal IRC de Symfony`_ ``#symfony`` (en freenode) para más información. - -.. _`Capifony`: http://capifony.org/ -.. _`sf2debpkg`: https://github.com/liip/sf2debpkg -.. _`Ant`: http://blog.sznapka.pl/deploying-symfony2-applications-with-ant -.. _`PagodaBox`: https://github.com/jmather/pagoda-symfony-sonata-distribution/blob/master/Boxfile -.. _`Magallanes`: https://github.com/andres-montanez/Magallanes -.. _`paquetes que añaden características de despliegue`: http://knpbundles.com/search?q=deploy -.. _`canal IRC de Symfony`: http://webchat.freenode.net/?channels=symfony -.. _`Memcached`: http://memcached.org/ -.. _`Redis`: http://redis.io/ diff --git a/_sources/cookbook/doctrine/common_extensions.txt b/_sources/cookbook/doctrine/common_extensions.txt deleted file mode 100644 index 934b1e1..0000000 --- a/_sources/cookbook/doctrine/common_extensions.txt +++ /dev/null @@ -1,29 +0,0 @@ -.. index:: - single: Doctrine; Extensiones comunes - -Cómo usar extensiones *Doctrine*: ``Timestampable``, ``Sluggable``, ``Translatable``, etc. -========================================================================================== - -*Doctrine2* es muy flexible, y la comunidad ya ha creado una serie de útiles extensiones *Doctrine* para ayudarte con las tareas habituales relacionadas con entidades. - -Una biblioteca en particular ---la biblioteca `DoctrineExtensions`_--- proporciona funcionalidad de integración con los comportamientos `Sluggable`_, `Translatable`_, `Timestampable`_, `Loggable`_, `Tree`_ y `Sortable`_. - -El uso de cada una de estas extensiones se explica en ese repositorio. - -Sin embargo, para instalar/activar cada extensión debes registrar y activar un -:doc:`escucha de eventos `. -Para ello, tienes dos opciones: - -#. Usar el `StofDoctrineExtensionsBundle`_, que integra la biblioteca de arriba. - -#. Implementar estos servicios directamente siguiendo la documentación para la integración con *Symfony2*: `Instalando extensiones Gedmo de Doctrine2 en Symfony2`_ - -.. _`DoctrineExtensions`: https://github.com/l3pp4rd/DoctrineExtensions -.. _`StofDoctrineExtensionsBundle`: https://github.com/stof/StofDoctrineExtensionsBundle -.. _`Sluggable`: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sluggable.md -.. _`Translatable`: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/translatable.md -.. _`Timestampable`: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/timestampable.md -.. _`Loggable`: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/loggable.md -.. _`Tree`: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/tree.md -.. _`Sortable`: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/sortable.md -.. _`Instalando extensiones Gedmo de Doctrine2 en Symfony2`: https://github.com/l3pp4rd/DoctrineExtensions/blob/master/doc/symfony2.md \ No newline at end of file diff --git a/_sources/cookbook/doctrine/custom_dql_functions.txt b/_sources/cookbook/doctrine/custom_dql_functions.txt deleted file mode 100644 index ec05aec..0000000 --- a/_sources/cookbook/doctrine/custom_dql_functions.txt +++ /dev/null @@ -1,84 +0,0 @@ -.. index:: - single: Doctrine; Funciones DQL personalizadas - -Cómo registrar funciones *DQL* personalizadas -============================================= - -*Doctrine* te permite especificar funciones *DQL* personalizadas. Para más información sobre este tema, lee el artículo «`Funciones DQL definidas por el usuario`_» de *Doctrine*. - -En *Symfony*, puedes registrar tus funciones *DQL* personalizadas de la siguiente manera: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - orm: - # ... - entity_managers: - default: - # ... - dql: - string_functions: - test_string: Acme\HelloBundle\DQL\StringFunction - second_string: Acme\HelloBundle\DQL\SecondStringFunction - numeric_functions: - test_numeric: Acme\HelloBundle\DQL\NumericFunction - datetime_functions: - test_datetime: Acme\HelloBundle\DQL\DatetimeFunction - - .. code-block:: xml - - - - - - - - - - - Acme\HelloBundle\DQL\SecondStringFunction - Acme\HelloBundle\DQL\DatetimeFunction - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'orm' => array( - // ... - - 'entity_managers' => array( - 'default' => array( - // ... - - 'dql' => array( - 'string_functions' => array( - 'test_string' => 'Acme\HelloBundle\DQL\StringFunction', - 'second_string' => 'Acme\HelloBundle\DQL\SecondStringFunction', - ), - 'numeric_functions' => array( - 'test_numeric' => 'Acme\HelloBundle\DQL\NumericFunction', - ), - 'datetime_functions' => array( - 'test_datetime' => 'Acme\HelloBundle\DQL\DatetimeFunction', - ), - ), - ), - ), - ), - )); - -.. _`Funciones DQL definidas por el usuario`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/cookbook/dql-user-defined-functions.html diff --git a/_sources/cookbook/doctrine/dbal.txt b/_sources/cookbook/doctrine/dbal.txt deleted file mode 100644 index 6eaeaa2..0000000 --- a/_sources/cookbook/doctrine/dbal.txt +++ /dev/null @@ -1,174 +0,0 @@ -.. index:: - pair: Doctrine; DBAL - -Cómo utiliza *Doctrine* la capa *DBAL* -====================================== - -.. note:: - - Este artículo es sobre la capa *DBAL* de *Doctrine*. Normalmente, vas a trabajar con el nivel superior de la capa *ORM* de *Doctrine*, la cual simplemente utiliza *DBAL* detrás del escenario para comunicarse realmente con la base de datos. Para leer más sobre el *ORM* de *Doctrine*, consulta «:doc:`/book/doctrine`». - -`Doctrine`_ la capa de abstracción de base de datos (*DataBase Abstraction Layer* --- *DBAL*) es una capa que se encuentra en la parte superior de `PDO`_ y ofrece una *API* intuitiva y flexible para comunicarse con las bases de datos relacionales más populares. En otras palabras, la biblioteca *DBAL* facilita la ejecución de consultas y realización de otras acciones de base de datos. - -.. tip:: - - Lee la `documentación oficial de DBAL`_ para conocer todos los detalles y las habilidades de la biblioteca *DBAL* de *Doctrine*. - -Para empezar, configura los parámetros de conexión a la base de datos: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - driver: pdo_mysql - dbname: Symfony2 - user: root - password: null - charset: UTF8 - - .. code-block:: xml - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'driver' => 'pdo_mysql', - 'dbname' => 'Symfony2', - 'user' => 'root', - 'password' => null, - ), - )); - -Para ver todas las opciones de configuración *DBAL*, consulta :ref:`reference-dbal-configuration`. - -A continuación, puedes acceder a la conexión *Doctrine DBAL* accediendo al servicio ``database_connection``:: - - class UserController extends Controller - { - public function indexAction() - { - $conn = $this->get('database_connection'); - $users = $conn->fetchAll('SELECT * FROM users'); - - // ... - } - } - -Registrando tipos de asignación personalizados ----------------------------------------------- - -Puedes registrar tipos de asignación personalizados a través de la configuración de *Symfony*. Ellos se sumarán a todas las conexiones configuradas. Para más información sobre los tipos de asignación personalizados, lee la sección `Tipos de asignación personalizados`_ de la documentación de *Doctrine*. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - types: - custom_first: Acme\HelloBundle\Type\CustomFirst - custom_second: Acme\HelloBundle\Type\CustomSecond - - .. code-block:: xml - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'types' => array( - 'custom_first' => 'Acme\HelloBundle\Type\CustomFirst', - 'custom_second' => 'Acme\HelloBundle\Type\CustomSecond', - ), - ), - )); - -Registrando tipos de asignación personalizados en *SchemaTool* --------------------------------------------------------------- - -La *SchemaTool* se utiliza al inspeccionar la base de datos para comparar el esquema. Para lograr esta tarea, es necesario saber qué tipo de asignación se debe utilizar para cada tipo de la base de datos. Por medio de la configuración puedes registrar nuevos tipos. - -Vamos a asociar el tipo ``ENUM`` (por omisión no apoyado por *DBAL*) al tipo ``string``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - dbal: - connections: - default: - // otros parámetros de conexión - mapping_types: - enum: string - - .. code-block:: xml - - - - - - - - - string - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'connections' => array( - 'default' => array( - 'mapping_types' => array( - 'enum' => 'string', - ), - ), - ), - ), - )); - -.. _`PDO`: http://www.php.net/pdo -.. _`Doctrine`: http://www.doctrine-project.org -.. _`documentación oficial de DBAL`: http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/index.html -.. _`Tipos de asignación personalizados`: http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/types.html#custom-mapping-types diff --git a/_sources/cookbook/doctrine/event_listeners_subscribers.txt b/_sources/cookbook/doctrine/event_listeners_subscribers.txt deleted file mode 100644 index 0038031..0000000 --- a/_sources/cookbook/doctrine/event_listeners_subscribers.txt +++ /dev/null @@ -1,200 +0,0 @@ -.. index:: - single: Doctrine; Escuchas y suscriptores de eventos - -.. _doctrine-event-config: - -Cómo registrar escuchas y suscriptores de eventos -================================================= - -*Doctrine* cuenta con un rico sistema de eventos que lanza eventos en casi todo lo que sucede dentro del sistema. Para ti, esto significa que puedes crear :doc:`servicios ` arbitrarios y decirle a *Doctrine* que notifique a esos objetos cada vez que ocurra una determinada acción (por ejemplo, ``PrePersist``) dentro de *Doctrine*. -Esto podría ser útil, por ejemplo, para crear un índice de búsqueda independiente cuando se guarde un objeto en tu base de datos. - -*Doctrine* define dos tipos de objetos que pueden escuchar los eventos de *Doctrine*: -escuchas y suscriptores. Ambos son muy similares, pero los escuchas son un poco más sencillos. Para más información, consulta el `Sistema de eventos`_ en el sitio web de *Doctrine*. - -The Doctrine website also explains all existing events that can be listened to. - -Configurando escuchas/suscriptores ----------------------------------- - -Para registrar un servicio para que actúe como un escucha o suscriptor de eventos sólo lo tienes que :ref:`etiquetar ` con el nombre apropiado. Dependiendo de tu caso de uso, puedes enganchar un escucha en cada conexión *DBAL* y gestor de entidad *ORM* o simplemente en una conexión *DBAL* específica y todos los gestores de entidad que utilicen esta conexión. - -.. configuration-block:: - - .. code-block:: yaml - - doctrine: - dbal: - default_connection: default - connections: - default: - driver: pdo_sqlite - memory: true - - services: - my.listener: - class: Acme\SearchBundle\EventListener\SearchIndexer - tags: - - { name: doctrine.event_listener, event: postPersist } - my.listener2: - class: Acme\SearchBundle\EventListener\SearchIndexer2 - tags: - - { name: doctrine.event_listener, event: postPersist, connection: default } - my.subscriber: - class: Acme\SearchBundle\EventListener\SearchIndexerSubscriber - tags: - - { name: doctrine.event_subscriber, connection: default } - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - use Symfony\Component\DependencyInjection\Definition; - - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'default_connection' => 'default', - 'connections' => array( - 'default' => array( - 'driver' => 'pdo_sqlite', - 'memory' => true, - ), - ), - ), - )); - - $container - ->setDefinition( - 'my.listener', - new Definition('Acme\SearchBundle\EventListener\SearchIndexer') - ) - ->addTag('doctrine.event_listener', array('event' => 'postPersist')) - ; - $container - ->setDefinition( - 'my.listener2', - new Definition('Acme\SearchBundle\EventListener\SearchIndexer2') - ) - ->addTag('doctrine.event_listener', array('event' => 'postPersist', 'connection' => 'default')) - ; - $container - ->setDefinition( - 'my.subscriber', - new Definition('Acme\SearchBundle\EventListener\SearchIndexerSubscriber') - ) - ->addTag('doctrine.event_subscriber', array('connection' => 'default')) - ; - -Creando la clase Escucha ------------------------- - -En el ejemplo anterior, se configuró un servicio ``my.listener`` como un escucha de *Doctrine* del evento ``postPersist``. The class behind that service must have -a ``postPersist`` method, which will be called when the event is dispatched:: - - // src/Acme/SearchBundle/EventListener/SearchIndexer.php - namespace Acme\SearchBundle\EventListener; - - use Doctrine\ORM\Event\LifecycleEventArgs; - use Acme\StoreBundle\Entity\Product; - - class SearchIndexer - { - public function postPersist(LifecycleEventArgs $args) - { - $entity = $args->getEntity(); - $entityManager = $args->getEntityManager(); - - // tal vez sólo quieres actuar en alguna entidad "producto" - if ($entity instanceof Product) { - // ... haz algo con el Producto - } - } - } - -En cada caso, tienes acceso a un objeto ``LifecycleEventArgs``, el cual te da acceso tanto al objeto entidad del evento como al mismo gestor de la entidad. - -Una cosa importante a resaltar es que un escucha debe estar atento a *todas* las entidades en tu aplicación. So, if you're interested in only handling a -specific type of entity (e.g. a ``Product`` entity but not a ``BlogPost`` -entity), you should check for the entity's class type in your method -(as shown above). - -Creating the Subscriber Class ------------------------------ - -A doctrine event subscriber must implement the ``Doctrine\Common\EventSubscriber`` -interface and have an event method for each event it subscribes to:: - - // src/Acme/SearchBundle/EventListener/SearchIndexerSubscriber.php - namespace Acme\SearchBundle\EventListener; - - use Doctrine\Common\EventSubscriber; - use Doctrine\ORM\Event\LifecycleEventArgs; - // for doctrine 2.4: Doctrine\Common\Persistence\Event\LifecycleEventArgs; - use Acme\StoreBundle\Entity\Product; - - class SearchIndexerSubscriber implements EventSubscriber - { - public function getSubscribedEvents() - { - return array( - 'postPersist', - 'postUpdate', - ); - } - - public function postUpdate(LifecycleEventArgs $args) - { - $this->index($args); - } - - public function postPersist(LifecycleEventArgs $args) - { - $this->index($args); - } - - public function index(LifecycleEventArgs $args) - { - $entity = $args->getEntity(); - $entityManager = $args->getEntityManager(); - - // tal vez sólo quieres actuar en alguna entidad "producto" - if ($entity instanceof Product) { - // ... haz algo con el Producto - } - } - } - -.. tip:: - - Doctrine event subscribers can not return a flexible array of methods to - call for the events like the :ref:`Symfony event subscriber ` - can. Doctrine event subscribers must return a simple array of the event - names they subscribe to. Doctrine will then expect methods on the subscriber - with the same name as each subscribed event, just as when using an event listener. - -For a full reference, see chapter `The Event System`_ in the Doctrine documentation. - -.. _`Sistema de eventos`: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/events.html diff --git a/_sources/cookbook/doctrine/file_uploads.txt b/_sources/cookbook/doctrine/file_uploads.txt deleted file mode 100644 index 0236339..0000000 --- a/_sources/cookbook/doctrine/file_uploads.txt +++ /dev/null @@ -1,516 +0,0 @@ -.. index:: - single: Doctrine; Subiendo archivos - -Cómo manejar archivos subidos con *Doctrine* -============================================ - -Manejar el envío de archivos con entidades *Doctrine* no es diferente a la manipulación de cualquier otra carga de archivo. En otras palabras, eres libre de mover el archivo en tu controlador después de manipular el envío de un formulario. Para ver ejemplos de cómo hacerlo, consulta el :doc:`/reference/forms/types/file` en la referencia. - -Si lo deseas, también puedes integrar la carga de archivos en el ciclo de vida de tu entidad (es decir, creación, actualización y eliminación). En este caso, ya que tu entidad es creada, actualizada y eliminada desde *Doctrine*, el proceso de carga y remoción de archivos se llevará a cabo de forma automática (sin necesidad de hacer nada en el controlador); - -Para que esto funcione, tendrás que hacerte cargo de una serie de detalles, los cuales serán cubiertos en este artículo del recetario. - -Configuración básica --------------------- - -En primer lugar, crea una sencilla clase Entidad de *Doctrine* con la cual trabajar:: - - // src/Acme/DemoBundle/Entity/Document.php - namespace Acme\DemoBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - use Symfony\Component\Validator\Constraints as Assert; - - /** - * @ORM\Entity - */ - class Document - { - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - public $id; - - /** - * @ORM\Column(type="string", length=255) - * @Assert\NotBlank - */ - public $name; - - /** - * @ORM\Column(type="string", length=255, nullable=true) - */ - public $path; - - public function getAbsolutePath() - { - return null === $this->path - ? null - : $this->getUploadRootDir().'/'.$this->path; - } - - public function getWebPath() - { - return null === $this->path - ? null - : $this->getUploadDir().'/'.$this->path; - } - - protected function getUploadRootDir() - { - // la ruta absoluta del directorio donde se deben - // guardar los archivos cargados - return __DIR__.'/../../../../web/'.$this->getUploadDir(); - } - - protected function getUploadDir() - { - // se deshace del __DIR__ para no meter la pata - // al mostrar el documento/imagen cargada en la vista. - return 'uploads/documents'; - } - } - -La entidad ``Documento`` tiene un nombre y está asociado con un archivo. La propiedad ``ruta`` almacena la ruta relativa al archivo y se persiste en la base de datos. -El ``getAbsolutePath()`` es un método útil que devuelve la ruta absoluta al archivo, mientras que ``getWebPath()`` es un conveniente método que devuelve la ruta web, la cual se utiliza en una plantilla para enlazar el archivo cargado. - -.. tip:: - - Si no lo has hecho, probablemente primero deberías leer el tipo :doc:`archivo ` en la documentación para comprender cómo trabaja el proceso de carga básico. - -.. note:: - - Si estás utilizando anotaciones para especificar tus reglas de validación (como muestra este ejemplo), asegúrate de que has habilitado la validación por medio de anotaciones (consulta :ref:`configurando la validación `). - -Para manejar el archivo real subido en el formulario, utiliza un campo ``file`` «virtual». -Por ejemplo, si estás construyendo tu formulario directamente en un controlador, este podría tener el siguiente aspecto:: - - public function uploadAction() - { - // ... - - $form = $this->createFormBuilder($document) - ->add('name') - ->add('file') - ->getForm(); - - // ... - } - -A continuación, crea esta propiedad en tu clase ``Documento`` y agrega algunas reglas de validación:: - - use Symfony\Component\HttpFoundation\File\UploadedFile; - - // ... - class Document - { - /** - * @Assert\File(maxSize="6000000") - */ - private $file; - - /** - * Sets file. - * - * @param UploadedFile $file - */ - public function setFile(UploadedFile $file = null) - { - $this->file = $file; - } - - /** - * Get file. - * - * @return UploadedFile - */ - public function getFile() - { - return $this->file; - } - } - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/DemoBundle/Resources/config/validation.yml - Acme\DemoBundle\Entity\Document: - properties: - file: - - File: - maxSize: 6000000 - - .. code-block:: php-annotations - - // src/Acme/DemoBundle/Entity/Document.php - namespace Acme\DemoBundle\Entity; - - // ... - use Symfony\Component\Validator\Constraints as Assert; - - class Document - { - /** - * @Assert\File(maxSize="6000000") - */ - private $file; - - // ... - } - - .. code-block:: xml - - - - - - - - - - - .. code-block:: php - - // src/Acme/DemoBundle/Entity/Document.php - namespace Acme\DemoBundle\Entity; - - // ... - use Symfony\Component\Validator\Mapping\ClassMetadata; - use Symfony\Component\Validator\Constraints as Assert; - - class Document - { - // ... - - public static function loadValidatorMetadata(ClassMetadata $metadata) - { - $metadata->addPropertyConstraint('file', new Assert\File(array( - 'maxSize' => 6000000, - ))); - } - } - -.. note:: - - Debido a que estás utilizando la restricción ``File``, *Symfony2* automáticamente supone que el campo del formulario es una entrada para cargar un archivo. Es por eso que no lo tienes que establecer explícitamente al crear el formulario anterior (``->add('file')``). - -El siguiente controlador muestra cómo manipular todo el proceso:: - - // ... - use Acme\DemoBundle\Entity\Document; - use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template; - // ... - - /** - * @Template() - */ - public function uploadAction() - { - $document = new Document(); - $form = $this->createFormBuilder($document) - ->add('name') - ->add('file') - ->getForm() - ; - - if ($this->getRequest()->isMethod('POST')) { - $form->bind($this->getRequest()); - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - - $em->persist($document); - $em->flush(); - - return $this->redirect($this->generateUrl(...)); - } - } - - return array('form' => $form->createView()); - } - -.. note:: - - Al escribir la plantilla, no olvides fijar el atributo ``enctype``: - - .. configuration-block:: - - .. code-block:: html+jinja - -

    Upload File

    - -
    - {{ form_widget(form) }} - - -
    - - .. code-block:: html+php - -

    Upload File

    - -
    enctype($form) ?>> - widget($form) ?> - - -
    - -El controlador anterior automáticamente persistirá la entidad ``Documento`` con el nombre presentado, pero no hará nada sobre el archivo y la propiedad ``path`` quedará en blanco. - -Una manera fácil de manejar la carga de archivos es que lo muevas justo antes de que se persista la entidad y a continuación, establece la propiedad ``path`` en consecuencia. Comienza por invocar a un nuevo método ``upload()`` en la clase ``Documento``, el cual deberás crear en un momento para manejar la carga del archivo:: - - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - - $document->upload(); - - $em->persist($document); - $em->flush(); - - return $this->redirect(...); - } - -El método ``upload()`` tomará ventaja del objeto :class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile`, el cual es lo que devuelve después de que se presenta un campo ``file``:: - - public function upload() - { - // the file property can be empty if the field is not required - if (null === $this->getFile()) { - return; - } - - // aquí usa el nombre de archivo original pero lo debes - // sanear al menos para evitar cualquier problema de seguridad - - // move takes the target directory and then the - // target filename to move to - $this->getFile()->move( - $this->getUploadRootDir(), - $this->getFile()->getClientOriginalName() - ); - - // set the path property to the filename where you've saved the file - $this->path = $this->getFile()->getClientOriginalName(); - - // limpia la propiedad «file» ya que no la necesitas más - $this->file = null; - } - -Usando el ciclo de vida de las retrollamadas --------------------------------------------- - -Incluso si esta implementación trabaja, sufre de un importante defecto: ¿Qué pasa si hay un problema al persistir la entidad? El archivo ya se ha movido a su ubicación final, incluso aunque la propiedad ``path`` de la entidad no se persista correctamente. - -Para evitar estos problemas, debes cambiar la implementación para que la operación de base de datos y el traslado del archivo sean atómicos: si hay un problema al persistir la entidad o si el archivo no se puede mover, entonces, no debe suceder *nada*. - -Para ello, es necesario mover el archivo justo cuando *Doctrine* persista la entidad a la base de datos. Esto se puede lograr enganchando el ciclo de vida de la entidad a una retrollamada:: - - /** - * @ORM\Entity - * @ORM\HasLifecycleCallbacks - */ - class Document - { - } - -A continuación, reconstruye la clase ``Documento`` para que tome ventaja de estas retrollamadas:: - - use Symfony\Component\HttpFoundation\File\UploadedFile; - - /** - * @ORM\Entity - * @ORM\HasLifecycleCallbacks - */ - class Document - { - private $temp; - - /** - * Sets file. - * - * @param UploadedFile $file - */ - public function setFile(UploadedFile $file = null) - { - $this->file = $file; - // check if we have an old image path - if (isset($this->path)) { - // store the old name to delete after the update - $this->temp = $this->path; - $this->path = null; - } else { - $this->path = 'initial'; - } - } - - /** - * @ORM\PrePersist() - * @ORM\PreUpdate() - */ - public function preUpload() - { - if (null !== $this->getFile()) { - // haz lo que quieras para generar un nombre único - $filename = sha1(uniqid(mt_rand(), true)); - $this->path = $filename.'.'.$this->getFile()->guessExtension(); - } - } - - /** - * @ORM\PostPersist() - * @ORM\PostUpdate() - */ - public function upload() - { - if (null === $this->getFile()) { - return; - } - - // si hay un error al mover el archivo, move() automáticamente - // envía una excepción. This will properly prevent - // the entity from being persisted to the database on error - $this->getFile()->move($this->getUploadRootDir(), $this->path); - - // check if we have an old image - if (isset($this->temp)) { - // delete the old image - unlink($this->getUploadRootDir().'/'.$this->temp); - // clear the temp image path - $this->temp = null; - } - $this->file = null; - } - - /** - * @ORM\PostRemove() - */ - public function removeUpload() - { - if ($file = $this->getAbsolutePath()) { - unlink($file); - } - } - } - -La clase ahora hace todo lo que necesitas: genera un nombre de archivo único antes de persistirlo, mueve el archivo después de persistirlo y elimina el archivo si la entidad es eliminada. - -Ahora que la entidad maneja automáticamente el movimiento del archivo, debes quitar del controlador la llamada a ``$document->upload()``:: - - if ($form->isValid()) { - $em = $this->getDoctrine()->getManager(); - - $em->persist($document); - $em->flush(); - - return $this->redirect(...); - } - -.. note:: - - Los eventos retrollamados ``@ORM\PrePersist()`` y ``@ORM\PostPersist()`` se disparan antes y después de almacenar la entidad en la base de datos. Por otro lado, los eventos retrollamados ``@ORM\PreUpdate()`` y ``@ORM\PostUpdate()`` se llaman al actualizar la entidad. - -.. caution:: - - Las retrollamadas ``PreUpdate`` y ``PostUpdate`` sólo se activan si se persiste algún cambio en uno de los campos de la entidad. Esto significa que, de manera predeterminada, si sólo modificas la propiedad ``$file``, estos eventos no se activarán, puesto que esa propiedad no se persiste directamente a través de *Doctrine*. Una solución sería usar un campo ``actualizado`` que *Doctrine* persista, y modificarlo manualmente al cambiar el archivo. - -Usando el ``id`` como nombre de archivo ---------------------------------------- - -Si deseas utilizar el ``id`` como el nombre del archivo, la implementación es un poco diferente conforme sea necesaria para guardar la extensión en la propiedad ``path``, en lugar del nombre de archivo real:: - - use Symfony\Component\HttpFoundation\File\UploadedFile; - - /** - * @ORM\Entity - * @ORM\HasLifecycleCallbacks - */ - class Document - { - private $temp; - - /** - * Sets file. - * - * @param UploadedFile $file - */ - public function setFile(UploadedFile $file = null) - { - $this->file = $file; - // check if we have an old image path - if (is_file($this->getAbsolutePath())) { - // store the old name to delete after the update - $this->temp = $this->getAbsolutePath(); - } else { - $this->path = 'initial'; - } - } - - /** - * @ORM\PrePersist() - * @ORM\PreUpdate() - */ - public function preUpload() - { - if (null !== $this->getFile()) { - $this->path = $this->getFile()->guessExtension(); - } - } - - /** - * @ORM\PostPersist() - * @ORM\PostUpdate() - */ - public function upload() - { - if (null === $this->getFile()) { - return; - } - - // check if we have an old image - if (isset($this->temp)) { - // delete the old image - unlink($this->temp); - // clear the temp image path - $this->temp = null; - } - - // you must throw an exception here if the file cannot be moved - // so that the entity is not persisted to the database - // which the UploadedFile move() method does - $this->getFile()->move( - $this->getUploadRootDir(), - $this->id.'.'.$this->getFile()->guessExtension() - ); - - $this->setFile(null); - } - - /** - * @ORM\PreRemove() - */ - public function storeFilenameForRemove() - { - $this->temp = $this->getAbsolutePath(); - } - - /** - * @ORM\PostRemove() - */ - public function removeUpload() - { - if (isset($this->temp)) { - unlink($this->temp); - } - } - - public function getAbsolutePath() - { - return null === $this->path - ? null - : $this->getUploadRootDir().'/'.$this->id.'.'.$this->path; - } - } - -Habrás notado en este caso que necesitas trabajar un poco más para poder eliminar el archivo. Antes de eliminarlo, debes almacenar la ruta del archivo (puesto que depende del ``id``). Entonces, una vez que el objeto se ha eliminado completamente de la base de datos, puedes eliminar el archivo (en ``PostRemove``). diff --git a/_sources/cookbook/doctrine/index.txt b/_sources/cookbook/doctrine/index.txt deleted file mode 100644 index 5c26c10..0000000 --- a/_sources/cookbook/doctrine/index.txt +++ /dev/null @@ -1,15 +0,0 @@ -*Doctrine* -========== - -.. toctree:: - :maxdepth: 2 - - file_uploads - common_extensions - event_listeners_subscribers - dbal - reverse_engineering - multiple_entity_managers - custom_dql_functions - resolve_target_entity - registration_form diff --git a/_sources/cookbook/doctrine/multiple_entity_managers.txt b/_sources/cookbook/doctrine/multiple_entity_managers.txt deleted file mode 100644 index 0737c3f..0000000 --- a/_sources/cookbook/doctrine/multiple_entity_managers.txt +++ /dev/null @@ -1,214 +0,0 @@ -.. index:: - single: Doctrine; Múltiples gestores de entidad - -Cómo trabajar con múltiples gestores de entidad y conexiones -============================================================ - -Puedes utilizar múltiples gestores de entidad de *Doctrine* o conexiones en una -aplicación *Symfony2*. Esto es necesario si estás utilizando diferentes bases de datos e incluso proveedores con conjuntos de entidades totalmente diferentes. En otras palabras, un gestor de entidad que se conecta a una base de datos deberá administrar algunas entidades, mientras que otro gestor de entidad conectado a otra base de datos puede manejar el resto. - -.. note:: - - Usar varios gestores de entidad es bastante fácil, pero más avanzado y generalmente no se requiere. Asegúrate de que realmente necesitas varios gestores de entidad antes de añadir complejidad a ese nivel. - -El siguiente código de configuración muestra cómo puedes configurar dos gestores de entidad: - -.. configuration-block:: - - .. code-block:: yaml - - doctrine: - dbal: - default_connection: default - connections: - default: - driver: "%database_driver%" - host: "%database_host%" - port: "%database_port%" - dbname: "%database_name%" - user: "%database_user%" - password: "%database_password%" - charset: UTF8 - customer: - driver: "%database_driver2%" - host: "%database_host2%" - port: "%database_port2%" - dbname: "%database_name2%" - user: "%database_user2%" - password: "%database_password2%" - charset: UTF8 - - orm: - default_entity_manager: default - entity_managers: - default: - connection: default - mappings: - AcmeDemoBundle: ~ - AcmeStoreBundle: ~ - customer: - connection: customer - mappings: - AcmeCustomerBundle: ~ - - .. code-block:: xml - - - - - - - - - - - - - - - - - - - - - - - - - - .. code-block:: php - - $container->loadFromExtension('doctrine', array( - 'dbal' => array( - 'default_connection' => 'default', - 'connections' => array( - 'default' => array( - 'driver' => '%database_driver%', - 'host' => '%database_host%', - 'port' => '%database_port%', - 'dbname' => '%database_name%', - 'user' => '%database_user%', - 'password' => '%database_password%', - 'charset' => 'UTF8', - ), - 'customer' => array( - 'driver' => '%database_driver2%', - 'host' => '%database_host2%', - 'port' => '%database_port2%', - 'dbname' => '%database_name2%', - 'user' => '%database_user2%', - 'password' => '%database_password2%', - 'charset' => 'UTF8', - ), - ), - ), - - 'orm' => array( - 'default_entity_manager' => 'default', - 'entity_managers' => array( - 'default' => array( - 'connection' => 'default', - 'mappings' => array( - 'AcmeDemoBundle' => null, - 'AcmeStoreBundle' => null, - ), - ), - 'customer' => array( - 'connection' => 'customer', - 'mappings' => array( - 'AcmeCustomerBundle' => null, - ), - ), - ), - ), - )); - -En este caso, hemos definido dos gestores de entidad y los llamamos ``default`` y ``customer``. El gestor de entidad ``default`` administra cualquier entidad en los paquetes ``AcmeDemoBundle`` y ``AcmeStoreBundle``, mientras que el gestor de entidad ``customer`` gestiona cualquiera en el paquete ``AcmeCustomerBundle``. También definiste dos conexiones, una para cada gestor de entidad. - -.. note:: - - Cuando trabajas con múltiples conexiones y gestores de entidad, debes ser explícito sobre cual configuración deseas. Si *omites* el nombre de la conexión o gestor de entidad, se usa el predeterminado (es decir, ``default``). - -Cuando trabajes con múltiples conexiones para crear tu base de datos: - -.. code-block:: bash - - # Juega sólo con la conexión «default» - $ php app/console doctrine:database:create - - # Juega sólo con la conexión «customer» - $ php app/console doctrine:database:create --connection=customer - -Cuándo trabajas múltiple gestores de entidad para actualizar tu esquema: - -.. code-block:: bash - - # Juega sólo con asociaciones «default» - $ php app/console doctrine:schema:update --force - - # Juega sólo con asociaciones «customer» - $ php app/console doctrine:schema:update --force --em=customer - -Si *omites* el nombre del gestor de la entidad al consultar por él, se devuelve el gestor de entidad predeterminado (es decir, ``default``):: - - class UserController extends Controller - { - public function indexAction() - { - // ambos devuelven el gestor de entidad "predefinido" - $em = $this->get('doctrine')->getManager(); - $em = $this->get('doctrine')->getManager('default'); - - $customerEm = $this->get('doctrine')->getManager('customer'); - } - } - -Ahora puedes utilizar *Doctrine* tal como lo hiciste antes --- con el gestor de entidad ``default`` para persistir y recuperar las entidades que gestiona y el gestor de entidad ``customer`` para persistir y recuperar sus entidades. - -Lo mismo aplica para llamadas al repositorio:: - - class UserController extends Controller - { - public function indexAction() - { - // Recupera un repositorio gestionado por el em 'predefinido' - $products = $this->get('doctrine') - ->getRepository('AcmeStoreBundle:Product') - ->findAll() - ; - - // La manera explícita para tratar con el em 'predefinido' - $products = $this->get('doctrine') - ->getRepository('AcmeStoreBundle:Product', - 'default') - ->findAll(); - - // Recupera un repositorio gestionado por el em 'customer' - $customers = $this->get('doctrine') - ->getRepository('AcmeCustomerBundle:Customer', - 'customer') - ->findAll() - ; - } - } diff --git a/_sources/cookbook/doctrine/registration_form.txt b/_sources/cookbook/doctrine/registration_form.txt deleted file mode 100644 index 838634b..0000000 --- a/_sources/cookbook/doctrine/registration_form.txt +++ /dev/null @@ -1,268 +0,0 @@ -.. index:: - single: Doctrine; Sencillo formulario de registro - single: Formulario; Sencillo formulario de registro - -Cómo implementar un sencillo formulario de registro -=================================================== - -Algunos formularios tienen campos adicionales cuyos valores no se necesita almacenar en la base de datos. Por ejemplo, posiblemente quieras crear un formulario de inscripción con unos cuantos campos adicionales (como un campo de casilla de verificación «términos aceptados») e integrar en el formulario lo que realmente almacena la información de la cuenta. - -El sencillo modelo de usuario ------------------------------ - -Tienes una sencilla entidad ``Usuario`` asignada a la base de datos:: - - // src/Acme/AccountBundle/Entity/User.php - namespace Acme\AccountBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - use Symfony\Component\Validator\Constraints as Assert; - use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity; - - /** - * @ORM\Entity - * @UniqueEntity(fields="email", message="Email already taken") - */ - class User - { - /** - * @ORM\Id - * @ORM\Column(type="integer") - * @ORM\GeneratedValue(strategy="AUTO") - */ - protected $id; - - /** - * @ORM\Column(type="string", length=255) - * @Assert\NotBlank() - * @Assert\Email() - */ - protected $email; - - /** - * @ORM\Column(type="string", length=255) - * @Assert\NotBlank() - */ - protected $plainPassword; - - public function getId() - { - return $this->id; - } - - public function getEmail() - { - return $this->email; - } - - public function setEmail($email) - { - $this->email = $email; - } - - public function getPlainPassword() - { - return $this->plainPassword; - } - - public function setPlainPassword($password) - { - $this->plainPassword = $password; - } - } - -Esta entidad ``Usuario`` contiene tres campos y dos de ellos (``email`` y ``plainPassword``) se deben mostrar en el formulario. La propiedad ``email`` debe ser única en la base de datos, esto se consigue añadiendo esta validación en la parte superior de la clase. - -.. note:: - - Si deseas integrar este ``Usuario`` en el sistema de seguridad, necesitas implementar la entidad :ref:`UserInterface ` del componente de seguridad. - -Creando un formulario para el modelo ------------------------------------- - -A continuación, crea el formulario para el modelo ``Usuario``:: - - // src/Acme/AccountBundle/Form/Type/UserType.php - namespace Acme\AccountBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class UserType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('email', 'email'); - $builder->add('plainPassword', 'repeated', array( - 'first_name' => 'password', - 'second_name' => 'confirm', - 'type' => 'password', - )); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\AccountBundle\Entity\User' - )); - } - - public function getName() - { - return 'user'; - } - } - -Sólo hay dos campos: ``email`` y ``plainPassword`` (repetido para confirmar la contraseña introducida). La opción ``data_class`` le dice al formulario el nombre de la clase datos (es decir, la entidad ``usuario``). - -.. tip:: - - Para explorar más cosas sobre el componente ``formulario``, consulta :doc:`/book/forms` en el libro. - -Incorporando el formulario del usuario en un formulario de inscripción ----------------------------------------------------------------------- - -El formulario que vamos a usar para la página de registro no es el mismo que el -formulario para simplemente modificar al ``usuario`` (es decir, ``UserType``). El formulario de inscripción contendrá otros campos, como «Acepto los términos», cuyo valor no se almacena en la base de datos. - -Comienza creando una simple clase que represente la «inscripción»:: - - // src/Acme/AccountBundle/Form/Model/Registration.php - namespace Acme\AccountBundle\Form\Model; - - use Symfony\Component\Validator\Constraints as Assert; - - use Acme\AccountBundle\Entity\User; - - class Registration - { - /** - * @Assert\Type(type="Acme\AccountBundle\Entity\User") - * @Assert\Valid() - */ - protected $user; - - /** - * @Assert\NotBlank() - * @Assert\True() - */ - protected $termsAccepted; - - public function setUser(User $user) - { - $this->user = $user; - } - - public function getUser() - { - return $this->user; - } - - public function getTermsAccepted() - { - return $this->termsAccepted; - } - - public function setTermsAccepted($termsAccepted) - { - $this->termsAccepted = (Boolean) $termsAccepted; - } - } - -Luego, crea el formulario para este modelo ``Registration``:: - - // src/Acme/AccountBundle/Form/Type/RegistrationType.php - namespace Acme\AccountBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - - class RegistrationType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('user', new UserType()); - $builder->add( - 'terms', - 'checkbox', - array('property_path' => 'termsAccepted') - ); - } - - public function getName() - { - return 'registration'; - } - } - -No es necesario utilizar el método especial para incorporar el formulario ``UserType``. -Un formulario es un campo, también --- así que lo puedes agregar como cualquier otro campo, con la expectativa de que la propiedad ``Registration.user`` mantendrá una instancia de la clase ``usuario``. - -Manejando el envío del formulario ---------------------------------- - -Después, necesitas un controlador para manejar el formulario. Comienza creando un simple controlador para mostrar el formulario de inscripción:: - - // src/Acme/AccountBundle/Controller/AccountController.php - namespace Acme\AccountBundle\Controller; - - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - use Symfony\Component\HttpFoundation\Response; - - use Acme\AccountBundle\Form\Type\RegistrationType; - use Acme\AccountBundle\Form\Model\Registration; - - class AccountController extends Controller - { - public function registerAction() - { - $form = $this->createForm( - new RegistrationType(), - new Registration() - ); - - return $this->render( - 'AcmeAccountBundle:Account:register.html.twig', - array('form' => $form->createView()) - ); - } - } - -y su plantilla: - -.. code-block:: html+jinja - - {# src/Acme/AccountBundle/Resources/views/Account/register.html.twig #} -
    - {{ form_widget(form) }} - - -
    - -Finalmente, crea el controlador que maneja el envío del formulario. Esto llevará a cabo la validación y guardará los datos en la base de datos:: - - public function createAction() - { - $em = $this->getDoctrine()->getEntityManager(); - - $form = $this->createForm(new RegistrationType(), new Registration()); - - $form->bind($this->getRequest()); - - if ($form->isValid()) { - $registration = $form->getData(); - - $em->persist($registration->getUser()); - $em->flush(); - - return $this->redirect(...); - } - - return $this->render( - 'AcmeAccountBundle:Account:register.html.twig', - array('form' => $form->createView()) - ); - } - -¡Eso es todo! Tu formulario ahora valida, y te permite guardar en la base de datos el objeto ``Usuario``. La casilla de verificación adicional ``términos `` en la clase ``Registration`` del modelo se utiliza durante la validación, pero en realidad no se utiliza más tarde, cuando se guarda al usuario en la base de datos. diff --git a/_sources/cookbook/doctrine/resolve_target_entity.txt b/_sources/cookbook/doctrine/resolve_target_entity.txt deleted file mode 100644 index daf0870..0000000 --- a/_sources/cookbook/doctrine/resolve_target_entity.txt +++ /dev/null @@ -1,143 +0,0 @@ -.. index:: - single: Doctrine; Resolviendo entidades destino - single: Doctrine; Definiendo relaciones con interfaces y clases abstractas - -Cómo definir relaciones con interfaces y clases abstractas -========================================================== - -.. versionadded:: 2.1 - El ``ResolveTargetEntityListener`` es nuevo para *Doctrine 2.2*, fue empacado por primera vez con *Symfony 2.1*. - -Uno de los objetivos de los paquetes es la creación discreta de paquetes de funcionalidad que no tienen muchas dependencias (o ninguna, en su caso), lo cual te permite utilizar esa funcionalidad en otras aplicaciones sin incluir elementos innecesarios. - -*Doctrine 2.2* incluye una nueva utilidad llamada ``ResolveTargetEntityListener``, con la cual las funciones interceptan llamadas dentro de *Doctrine* y reescriben los parámetros ``targetEntity`` en tu correlación de metadatos en tiempo de ejecución. Esto significa que en tu paquete eres capaz de utilizar una interfaz o una clase abstracta en tus asignaciones y esperar una correcta correlación en una entidad concreta en tiempo de ejecución. - -Esta funcionalidad te permite definir las relaciones entre diferentes entidades sin volverlas dependencias duras. - -Antecedentes ------------- - -Supongamos que tienes un ``InvoiceBundle`` que proporciona la funcionalidad de facturación y un ``CustomerBundle`` que contiene herramientas para la gestión de clientes. Los quieres mantener separados, ya que se pueden utilizar en otros sistemas el uno sin el otro, pero para tu aplicación deseas usarlos juntos. - -En este caso, tiene una entidad factura (``Invoice``) con una relación a un objeto inexistente, un ``InvoiceSubjectInterface``. El objetivo es conseguir que el ``ResolveTargetEntityListener`` reemplace cualquier mención a la interfaz con un objeto real que implemente esa interfaz. - -Configurando ------------- - -Vamos a usar las siguientes entidades básicas (incompletas por razones de brevedad) para explicar cómo configurar y utilizar el ``ResolveTargetEntityListener``. - -Una entidad ``Cliente``:: - - // src/Acme/AppBundle/Entity/Customer.php - - namespace Acme\AppBundle\Entity; - - use Doctrine\ORM\Mapping as ORM; - use Acme\CustomerBundle\Entity\Customer as BaseCustomer; - use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; - - /** - * @ORM\Entity - * @ORM\Table(name="customer") - */ - class Customer extends BaseCustomer implements InvoiceSubjectInterface - { - // En nuestro ejemplo, todos los métodos definidos en la - // InvoiceSubjectInterface ya están implementados en - // BaseCustomer - -La entidad ``Factura``:: - - // src/Acme/InvoiceBundle/Entity/Invoice.php - - namespace Acme\InvoiceBundle\Entity; - - use Doctrine\ORM\Mapping AS ORM; - use Acme\InvoiceBundle\Model\InvoiceSubjectInterface; - - /** - * Representa una ``Factura``. - * - * @ORM\Entity - * @ORM\Table(name="invoice") - */ - class Invoice - { - /** - * @ORM\ManyToOne(targetEntity="Acme\InvoiceBundle\Model\InvoiceSubjectInterface") - * @var InvoiceSubjectInterface - */ - protected $subject; - } - -Una ``InvoiceSubjectInterface``:: - - // src/Acme/InvoiceBundle/Model/InvoiceSubjectInterface.php - - namespace Acme\InvoiceBundle\Model; - - /** - * Una interfaz que debe implementar el objeto súbdito de la factura. - * En la mayoría de los casos, un único objeto debe implementar esta - * interfaz puesto que ResolveTargetEntityListener sólo puede - * cambiar el destino a un único objeto. - */ - interface InvoiceSubjectInterface - { - // Enumera los métodos adicionales que necesita tu InvoiceBundle - // para acceder al súbdito para que puedas garantizar - // que tienes acceso a esos métodos. - - /** - * @return string - */ - public function getName(); - } - -A continuación, tienes que configurar el escucha, el cual informa a ``DoctrineBundle`` acerca de la sustitución: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - doctrine: - # .... - orm: - # .... - resolve_target_entities: - Acme\InvoiceBundle\Model\InvoiceSubjectInterface: Acme\AppBundle\Entity\Customer - - .. code-block:: xml - - - - - - - - Acme\AppBundle\Entity\Customer - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('doctrine', array( - 'orm' => array( - // ... - 'resolve_target_entities' => array( - 'Acme\InvoiceBundle\Model\InvoiceSubjectInterface' => 'Acme\AppBundle\Entity\Customer', - ), - ), - )); - -Consideraciones finales ------------------------ - -Con el ``ResolveTargetEntityListener``, puedes separar tus paquetes, y siguen siendo útiles por sí mismos, pero aún así capaces de definir relaciones entre diferentes objetos. Usando este método, los paquetes terminan siendo más fáciles de mantener de manera independiente. diff --git a/_sources/cookbook/doctrine/reverse_engineering.txt b/_sources/cookbook/doctrine/reverse_engineering.txt deleted file mode 100644 index cc799d2..0000000 --- a/_sources/cookbook/doctrine/reverse_engineering.txt +++ /dev/null @@ -1,146 +0,0 @@ -.. index:: - single: Doctrine; Generando entidades desde una base de datos existe - -Cómo generar entidades desde una base de datos existente -======================================================== - -Cuando empiezas a trabajar en el proyecto de una nueva marca que utiliza una base de datos, es algo natural que sean dos situaciones diferentes. En la mayoría de los casos, el modelo de base de datos se diseña y construye desde cero. A veces, sin embargo, comenzarás con un modelo de base de datos existente y probablemente inmutable. Afortunadamente, *Doctrine* viene con un montón de herramientas para ayudarte a generar las clases del modelo desde tu base de datos existente. - -.. note:: - - Como dicen las `herramientas de documentación de Doctrine`_, la ingeniería inversa es un proceso de una sola vez para empezar a trabajar en un proyecto. *Doctrine* es capaz de convertir aproximadamente el 70-80% de la información asignada basándose en los campos, índices y restricciones de clave externa. *Doctrine* no puede descubrir asociaciones inversas, tipos de herencia, entidades con claves externas como claves principales u operaciones semánticas en asociaciones tales como eventos en cascada o ciclo de vida de los eventos. Posteriormente, será necesario algún trabajo adicional sobre las entidades generadas para diseñar cada una según tus características específicas del modelo de dominio. - -Esta guía asume que estás usando una sencilla aplicación de *blog* con las siguientes dos tablas: ``blog_post`` y ``blog_comment``. Un registro de comentarios está vinculado a un registro de comentario gracias a una restricción de clave externa. - -.. code-block:: sql - - CREATE TABLE `blog_post` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `title` varchar(100) COLLATE utf8_unicode_ci NOT NULL, - `content` longtext COLLATE utf8_unicode_ci NOT NULL, - `created_at` datetime NOT NULL, - PRIMARY KEY (`id`) - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - - CREATE TABLE `blog_comment` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `post_id` bigint(20) NOT NULL, - `autor` varchar(20) COLLATE utf8_unicode_ci NOT NULL, - `contenido` longtext COLLATE utf8_unicode_ci NOT NULL, - `creado_at` datetime NOT NULL, - PRIMARY KEY (`id`), - KEY `blog_comment_post_id_idx` (`post_id`), - CONSTRAINT `blog_post_id` FOREIGN KEY (`post_id`) REFERENCES `blog_post` (`id`) ON DELETE CASCADE - ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; - -Antes de zambullirte en la receta, asegúrate de que los parámetros de conexión de tu base de datos están configurados correctamente en el archivo ``app/config/parameters.yml`` (o cualquier otro lugar donde mantienes la configuración de base de datos) y que has iniciado un paquete que será la sede de tu futura clase entidad. Esta guía supone que existe un ``AcmeBlogBundle`` y está localizado bajo el directorio ``src/Acme/BlogBundle``. - -El primer paso para crear las clases de la entidad en una base de datos existente es pedir a *Doctrine* que introspeccione la base de datos y genere los archivos de metadatos correspondientes. Los archivos de metadatos describen la clase de la entidad para generar tablas basándose en los campos. - -.. code-block:: bash - - $ php app/console doctrine:mapping:convert xml ./src/Acme/BlogBundle/Resources/config/doctrine/metadata/orm --from-database --force - -Esta herramienta de línea de ordenes le pide a *Doctrine* que inspeccione la estructura de la base de datos y genere los archivos *XML* de metadatos bajo el directorio :file:`src/Acme/BlogBundle/Resources/config/doctrine/metadata/orm` de tu paquete. - -.. tip:: - - También es posible generar los metadatos de clase en formato *YAML* cambiando el primer argumento a `yml`. - -El archivo de metadatos generado ``BlogPost.dcm.xml`` es el siguiente: - -.. code-block:: xml - - - - - DEFERRED_IMPLICIT - - - - - - - - - - - - - -.. note:: - - Si tienes relaciones ``UnoAMuchos`` entre tus entidades, necesitarás editar los archivos ``xml`` o ``yml`` generados para añadir una sección en las entidades específicas ``UnoAMuchos`` definiendo las piezas ``inversedBy`` y ``mappedBy``. - -Una vez generados los archivos de metadatos, puedes pedir a *Doctrine* que importe el esquema y construya las clases relacionadas con la entidad, ejecutando las dos siguientes ordenes. - -.. code-block:: bash - - $ php app/console doctrine:mapping:import AcmeBlogBundle annotation - $ php app/console doctrine:generate:entities AcmeBlogBundle - -La primer orden genera las clases de entidad con una asignación de anotaciones, pero por supuesto puedes cambiar el argumento ``annotation`` a ``xml`` o ``yml``. -La clase entidad ``BlogComment`` recién creada se ve de la siguiente manera: - -.. code-block:: php - - - - - - - - .. code-block:: php - - // app/config/config_test.php - $container->loadFromExtension('swiftmailer', array( - 'disable_delivery' => "true", - )); - -Si también deseas inhabilitar el envío en el entorno ``dev``, sólo tienes que añadir esta misma configuración en el archivo :file:`config_dev.yml`. - -Enviando a una dirección específica ------------------------------------ - -También puedes optar por hacer que todos los correos sean enviados a una dirección específica, en vez de la dirección real especificada cuando se envía el mensaje. Esto se puede conseguir a través de la opción ``delivery_address``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - swiftmailer: - delivery_address: dev@example.com - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $container->loadFromExtension('swiftmailer', array( - 'delivery_address' => "dev@example.com", - )); - -Ahora, supongamos que estás enviando un correo electrónico a ``recipient@example.com``. - -.. code-block:: php - - public function indexAction($name) - { - $message = \Swift_Message::newInstance() - ->setSubject('Hello Email') - ->setFrom('send@example.com') - ->setTo('recipient@example.com') - ->setBody( - $this->renderView( - 'HelloBundle:Hello:email.txt.twig', - array('name' => $name) - ) - ) - ; - $this->get('mailer')->send($message); - - return $this->render(...); - } - -En el entorno ``dev``, el correo electrónico será enviado a ``dev@example.com``. -``SwiftMailer`` añadirá una cabecera adicional al correo electrónico, ``X-Swift-To``, conteniendo la dirección reemplazada, por lo tanto todavía serás capaz de ver qué se ha enviado. - -.. note:: - - Además de las direcciones ``para``, también se detendrá el correo electrónico que se envíe a cualquier dirección ``CC`` y ``BCC`` establecida. ``SwiftMailer`` agregará encabezados adicionales al correo electrónico con la dirección reemplazada en ellos. - Estas son ``X-Swift-CC`` y ``X-Swift-CCO`` para las direcciones ``CC`` y ``BCC``, respectivamente. - -Visualizando desde la barra de depuración web ---------------------------------------------- - -Puedes ver cualquier correo electrónico enviado por una respuesta cuando estás en el entorno ``dev`` usando la barra de depuración web. El icono de correo electrónico en la barra de herramientas mostrará cuántos correos electrónicos fueron enviados. Si haces clic en él, se abrirá un informe mostrando los detalles de los mensajes de correo electrónico enviados. - -Si estás enviando un correo electrónico e inmediatamente rediriges a otra página, la barra de herramientas de depuración web no mostrará un icono de correo electrónico o un informe en la siguiente página. - -En su lugar, puedes ajustar la opción ``intercept_redirects`` a ``true`` en el archivo :file:`config_dev.yml`, lo cual provocará que la redirección se detenga y te permitirá abrir el informe con los detalles de los correos enviados. - -.. tip:: - - Como alternativa, puedes abrir el perfilador después de la redirección y buscar la *URL* utilizada en la petición anterior (por ejemplo ``/contact/handle``). - La característica de búsqueda del perfilador te permite cargar información del perfil de peticiones anteriores. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - web_profiler: - intercept_redirects: true - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $container->loadFromExtension('web_profiler', array( - 'intercept_redirects' => 'true', - )); diff --git a/_sources/cookbook/email/email.txt b/_sources/cookbook/email/email.txt deleted file mode 100644 index 2b37179..0000000 --- a/_sources/cookbook/email/email.txt +++ /dev/null @@ -1,127 +0,0 @@ -.. index:: - single: Emails - -Cómo enviar correo electrónico -============================== - -El envío de correo electrónico es una tarea clásica para cualquier aplicación web, y la cual tiene complicaciones especiales y peligros potenciales. En lugar de reinventar la rueda, una solución para enviar mensajes de correo electrónico es usando el ``SwiftmailerBundle``, el cual aprovecha el poder de la biblioteca `SwiftMailer`_. - -.. note:: - - No olvides activar el paquete en tu núcleo antes de usarlo:: - - public function registerBundles() - { - $bundles = array( - // ... - - new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), - ); - - // ... - } - -.. _swift-mailer-configuration: - -Configurando ------------- - -Antes de usar ``SwiftMailer``, asegúrate de incluir su configuración. El único parámetro de configuración obligatorio es ``transport``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - swiftmailer: - transport: smtp - encryption: ssl - auth_mode: login - host: smtp.gmail.com - username: your_username - password: your_password - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('swiftmailer', array( - 'transport' => "smtp", - 'encryption' => "ssl", - 'auth_mode' => "login", - 'host' => "smtp.gmail.com", - 'username' => "your_username", - 'password' => "your_password", - )); - -La mayoría de los atributos de configuración de ``SwiftMailer`` tratan con la forma en que se deben entregar los mensajes. - -Los atributos de configuración disponibles son los siguientes: - -* ``transport`` (``smtp``, ``mail``, ``sendmail`` o ``gmail``) -* ``username`` -* ``password`` -* ``host`` -* ``port`` -* ``encryption`` (``tls``, o ``ssl``) -* ``auth_mode`` (``plain``, ``login`` o ``cram-md5``) -* ``spool`` - - * ``type`` (cómo a organizar los mensajes, ``file`` o ``memory`` son compatibles, ve :doc:`/cookbook/email/spool`) - * ``path`` (dónde almacenar los mensajes) -* ``delivery_address`` (una dirección de correo electrónico de donde enviar todo el correo electrónico) -* ``disable_delivery`` (``true`` para desactivar la entrega por completo) - -Enviando correo electrónico ---------------------------- - -La biblioteca ``SwiftMailer`` trabaja creando, configurando y luego enviando objetos ``Swift_Message``. El ``«mailer»`` es responsable de la entrega real del mensaje y es accesible a través del servicio ``mailer``. En general, el envío de un correo electrónico es bastante sencillo:: - - public function indexAction($name) - { - $message = \Swift_Message::newInstance() - ->setSubject('Hello Email') - ->setFrom('send@example.com') - ->setTo('recipient@example.com') - ->setBody( - $this->renderView( - 'HelloBundle:Hello:email.txt.twig', - array('name' => $name) - ) - ) - ; - $this->get('mailer')->send($message); - - return $this->render(...); - } - -Para mantener las cosas disociadas, el cuerpo del correo electrónico se ha almacenado en una plantilla y reproducido con el método ``RenderView()``. - -El objeto ``$message`` admite muchas más opciones, como incluir archivos adjuntos, agregar contenido *HTML*, y mucho más. Afortunadamente, ``SwiftMailer`` cubre el tema con gran detalle en `Creando mensajes`_ de su documentación. - -.. tip:: - - Hay disponibles varios artículos en el recetario relacionados con el envío de mensajes de correo electrónico en *Symfony2*: - - * :doc:`gmail` - * :doc:`dev_environment` - * :doc:`spool` - -.. _`Swiftmailer`: http://swiftmailer.org/ -.. _`Creando mensajes`: http://swiftmailer.org/docs/messages.html diff --git a/_sources/cookbook/email/gmail.txt b/_sources/cookbook/email/gmail.txt deleted file mode 100644 index e016592..0000000 --- a/_sources/cookbook/email/gmail.txt +++ /dev/null @@ -1,66 +0,0 @@ -.. index:: - single: Correo electrónico; Gmail - -Cómo utilizar *Gmail* para enviar mensajes de correo electrónico -================================================================ - -Durante el desarrollo, en lugar de utilizar un servidor *SMTP* regular para enviar mensajes de correo electrónico, verás que es más fácil y más práctico utilizar *Gmail*. El paquete ``SwiftMailer`` hace que esto sea muy fácil. - -.. tip:: - - En lugar de utilizar tu cuenta normal de *Gmail*, por supuesto, recomendamos crear una cuenta especial. - -En el archivo de configuración de desarrollo, cambia el ajuste ``transport`` a ``gmail`` y establece el ``username`` y ``password`` a las credenciales de Google: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config_dev.yml - swiftmailer: - transport: gmail - username: tu_nombre_de_usuario_gmail - password: tu_contraseña_gmail - - .. code-block:: xml - - - - - - - - .. code-block:: php - - // app/config/config_dev.php - $container->loadFromExtension('swiftmailer', array( - 'transport' => "gmail", - 'username' => "tu_nombre_de_usuario_gmail", - 'password' => "tu_contraseña_gmail", - )); - -¡Ya está! - -.. tip:: - - Si estás usando la *edición estándar de Symfony*, configura los parámetros en tu archivo :file:`parameters.yml` - - .. code-block:: yaml - - # app/config/parameters.yml - parameters: - ... - mailer_transport: gmail - mailer_host: ~ - mailer_user: tu_nombre_de_usuario_gmail - mailer_password: tu_contraseña_gmail - -.. note:: - - El transporte ``gmail`` simplemente es un acceso directo que utiliza el transporte ``smtp`` y establece ``encryption``, ``auth_mode`` y ``host`` para trabajar con *Gmail*. diff --git a/_sources/cookbook/email/index.txt b/_sources/cookbook/email/index.txt deleted file mode 100644 index 37d973a..0000000 --- a/_sources/cookbook/email/index.txt +++ /dev/null @@ -1,11 +0,0 @@ -``Email`` -========= - -.. toctree:: - :maxdepth: 2 - - email - gmail - dev_environment - spool - testing diff --git a/_sources/cookbook/email/spool.txt b/_sources/cookbook/email/spool.txt deleted file mode 100644 index 2467063..0000000 --- a/_sources/cookbook/email/spool.txt +++ /dev/null @@ -1,114 +0,0 @@ -.. index:: - single: Correo electrónico; Organizando - -Cómo formar en la cola mensajes de correo electrónico -===================================================== - -Cuando estás utilizando el ``SwiftmailerBundle`` para enviar correo electrónico desde una aplicación *Symfony2*, de manera predeterminada el mensaje será enviado inmediatamente. Sin embargo, posiblemente quieras evitar el impacto en el rendimiento de la comunicación entre ``SwiftMailer`` y el transporte de correo electrónico, lo cual podría hacer que el usuario tuviera que esperar la carga de la siguiente página mientras que se envía el correo electrónico. Puedes evitar todo esto eligiendo ``«spool»``, para formar los mensajes en la cola de correo en lugar de enviarlos directamente. Esto significa que ``SwiftMailer`` no intentará enviar el correo electrónico, sino que guardará el mensaje en alguna parte, tal como un archivo. Otro proceso puede leer la cola y hacerse cargo de enviar los correos que están organizados en la cola. Actualmente sólo la organización en archivos o memoria es compatible con ``Swiftmailer``. - -Organizar usando la memoria ---------------------------- - -Cuándo organizas almacenando los mensajes de correo electrónico en memoria, conseguirás enviarlos antes de que el ``núcleo`` termine. Esto significa que el mensaje de correo será enviado si la petición entera se ejecutó sin excepción no controladas o algún error. Para configurar ``swiftmailer`` con la opción de memoria, usa la siguiente configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - swiftmailer: - # ... - spool: { type: memory } - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('swiftmailer', array( - ..., - 'spool' => array('type' => 'memory') - )); - -Organizar usando un archivo ---------------------------- - -Para utilizar la organización con un archivo, usa la siguiente configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - swiftmailer: - # ... - spool: - type: file - path: /ruta/a/spool - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('swiftmailer', array( - // ... - - 'spool' => array( - 'type' => 'file', - 'path' => '/ruta/a/spool', - ), - )); - -.. tip:: - - Si deseas almacenar la cola de correo en algún lugar en el directorio de tu proyecto, recuerda que puedes utilizar el parámetro ``%kernel.root_dir%`` para referirte a la raíz del proyecto: - - .. code-block:: yaml - - path: "%kernel.root_dir%/spool" - -Ahora, cuando tu aplicación envía un correo electrónico, no se enviará realmente, sino que se añade a la cola de correo. El envío de los mensajes desde la cola se hace por separado. -Hay una orden de consola para enviar los mensajes en la cola de correo: - -.. code-block:: bash - - $ php app/console swiftmailer:spool:send --env=prod - -Tiene una opción para limitar el número de mensajes que se enviarán: - -.. code-block:: bash - - $ php app/console swiftmailer:spool:send --message-limit=10 --env=prod - -También puedes establecer el límite de tiempo en segundos: - -.. code-block:: bash - - $ php app/console swiftmailer:spool:send --time-limit=10 --env=prod - -Por supuesto que en realidad no deseas ejecutar esto manualmente. En cambio, la orden de consola la debe activar un trabajo cronometrado o tarea programada y ejecutarse a intervalos regulares. diff --git a/_sources/cookbook/email/testing.txt b/_sources/cookbook/email/testing.txt deleted file mode 100644 index b04942d..0000000 --- a/_sources/cookbook/email/testing.txt +++ /dev/null @@ -1,68 +0,0 @@ -.. index:: - single: Correo electrónico; Probando - -Cómo verificar que se envía un mensaje de correo electrónico en una prueba funcional -==================================================================================== - -Enviar correos electrónicos con *Symfony2* es bastante sencillo gracias al ``SwiftmailerBundle``, el cual aprovecha el poder de la biblioteca `Swiftmailer`_. - -A probar funcionalmente que se envió un correo electrónico, e incluso acertar el asunto del correo electrónico, contenido o cualquiera otra cabecera, puedes utilizar el :ref:`Generador de perfiles de Symfony2 `. - -Empieza con una fácil acción del controlador que envía un correo electrónico:: - - public function sendEmailAction($name) - { - $message = \Swift_Message::newInstance() - ->setSubject('Hello Email') - ->setFrom('send@example.com') - ->setTo('recipient@example.com') - ->setBody('You should see me from the profiler!') - ; - - $this->get('mailer')->send($message); - - return $this->render(...); - } - -.. note:: - - No olvides habilitar el generador de perfiles como se explica en :doc:`/cookbook/testing/profiling`. - -En tu prueba funcional, usa el recolector ``swiftmailer`` en el generador de perfiles para conseguir la información sobre los mensajes enviados en la petición anterior:: - - // src/Acme/DemoBundle/Tests/Controller/MailControllerTest.php - use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; - - class MailControllerTest extends WebTestCase - { - public function testMailIsSentAndContentIsOk() - { - $client = static::createClient(); - - // Habilita el perfilador para la próxima petición - // (no hace nada si el perfilador no está disponible) - $cliente->enableProfiler(); - - $crawler = $client->request('POST', '/path/to/above/action'); - - $mailCollector = $client->getProfile()->getCollector('swiftmailer'); - - // Comprueba que se envió un mensaje de correo electrónico - $this->assertEquals(1, $mailCollector->getMessageCount()); - - $collectedMessages = $mailCollector->getMessages(); - $message = $collectedMessages[0]; - - // Acierta los datos del correo electrónico - $this->assertInstanceOf('Swift_Message', $message); - $this->assertEquals('Hello Email', $message->getSubject()); - $this->assertEquals('send@example.com', key($message->getFrom())); - $this->assertEquals('recipient@example.com', key($message->getTo())); - $this->assertEquals( - 'You should see me from the profiler!', - $message->getBody() - ); - } - } - -.. _Swiftmailer: http://swiftmailer.org/ diff --git a/_sources/cookbook/event_dispatcher/before_after_filters.txt b/_sources/cookbook/event_dispatcher/before_after_filters.txt deleted file mode 100644 index af855f4..0000000 --- a/_sources/cookbook/event_dispatcher/before_after_filters.txt +++ /dev/null @@ -1,255 +0,0 @@ -.. index:: - single: Despachador de evento - -Cómo configurar los filtros ``before`` y ``after`` -================================================== - -Es bastante común en el desarrollo de aplicaciones *web* la necesidad de ejecutar algo de lógica justo antes o justo después de la acción actuando como filtros o ganchos. - -En *symfony1*, esto se consiguió con los métodos ``preExecute`` y ``postExecute``. -La mayoría de las plataformas importantes tienen métodos similares pero no hay tal cosa en *Symfony2*. -La buena noticia es que hay mucho mejor una manera de interferir en el proceso ``Petición -> Respuesta`` usando el :doc:`componente EventDispatcher `. - -Ejemplo de validación de ficha ------------------------------- - -Imagina que necesitas desarrollar una *API* dónde algunos controladores son públicos, pero otros se restringen a uno o algunos clientes. Por estas características particulares, podrías proporcionar una ficha a tus clientes para identificarse. - -Por lo tanto, antes de ejecutar la acción del controlador, necesitas comprobar si la acción está restringida o no. Si está restringida, necesitas validar la ficha proporcionada. - -.. note:: - - Por favor, ten en cuenta que para simplificar esta receta, las fichas se definen en la configuración y no utilizaremos ni configuración de base de datos ni proveedor de autenticación a través del componente ``Security``. - -Filtros «antes» con el evento ``kernel.controller`` ---------------------------------------------------- - -Primero, almacena alguna ficha de configuración básica usando :file:`config.yml` y parámetros clave: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - parameters: - tokens: - client1: pass1 - client2: pass2 - - .. code-block:: xml - - - - - pass1 - pass2 - - - - .. code-block:: php - - // app/config/config.php - $container->setParameter('tokens', array( - 'client1' => 'pass1', - 'client2' => 'pass2', - )); - -Comprobando etiquetas de controladores -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Un escucha del ``kernel.controller`` recibe una notificación en cada petición, justo antes de ejecutar el controlador. Por lo tanto, primero necesitas alguna manera de identificar si el controlador que responde a la petición necesita validar la ficha. - -Una forma limpia y fácil es crear una interfaz vacía y hacer que los controladores la implementen:: - - namespace Acme\DemoBundle\Controller; - - interface TokenAuthenticatedController - { - // ... - } - -Un controlador que implementa esta interfaz es similar al siguiente:: - - namespace Acme\DemoBundle\Controller; - - use Acme\DemoBundle\Controller\TokenAuthenticatedController; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class FooController extends Controller implements TokenAuthenticatedController - { - // una acción que necesita autenticación - public function barAction() - { - // ... - } - } - -Creando un escucha del evento -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -A continuación, tienes que crear un escucha del evento que contenga la lógica que deseas ejecutar antes que tus controladores. Si no estás familiarizado con los escuchas de eventos, puedes aprender más sobre ellos en :doc:`/cookbook/service_container/event_listener`:: - - // src/Acme/DemoBundle/EventListener/TokenListener.php - namespace Acme\DemoBundle\EventListener; - - use Acme\DemoBundle\Controller\TokenAuthenticatedController; - use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; - use Symfony\Component\HttpKernel\Event\FilterControllerEvent; - - class TokenListener - { - private $tokens; - - public function __construct($tokens) - { - $this->tokens = $tokens; - } - - public function onKernelController(FilterControllerEvent $event) - { - $controller = $event->getController(); - - /* - * el $controller pasado puede ser una clase o un cierre. - * Esto no es habitual en Symfony2 pero puede suceder. - * Si se trata de una clase, viene en formato de arreglo - */ - if (!is_array($controller)) { - return; - } - - if ($controller[0] instanceof TokenAuthenticatedController) { - $token = $event->getRequest()->query->get('token'); - if (!in_array($token, $this->tokens)) { - throw new AccessDeniedHttpException('This action needs a valid token!'); - } - } - } - } - -Registrando el escucha -~~~~~~~~~~~~~~~~~~~~~~ - -Por último, registra tu escucha como un servicio y etiquétalo como un escucha de eventos. -Al escuchar el ``kernel.controller``, le estas diciendo a *Symfony* que deseas que tu escucha sea llamado justo antes de ejecutar cualquier controlador: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml (o en tu services.yml) - services: - demo.tokens.action_listener: - class: Acme\DemoBundle\EventListener\TokenListener - arguments: ["%tokens%"] - tags: - - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } - - .. code-block:: xml - - - - %tokens% - - - - .. code-block:: php - - // app/config/config.php (or inside your services.php) - use Symfony\Component\DependencyInjection\Definition; - - $listener = new Definition('Acme\DemoBundle\EventListener\TokenListener', array('%tokens%')); - $listener->addTag('kernel.event_listener', array( - 'event' => 'kernel.controller', - 'method' => 'onKernelController' - )); - $container->setDefinition('demo.tokens.action_listener', $listener); - -Con esta configuración, tu método ``TokenListener`` del ``onKernelController`` será ejecutado en cada petición. Si el controlador que está a punto de ejecutarse implementa el ``TokenAuthenticatedController``, se aplica la autenticación. Esto te permite tener un filtro «antes» en cualquier controlador que desees. - -Filtros «después» con el evento ``kernel.response`` ---------------------------------------------------- - -Además de tener un gancho que se ejecuta antes que tu controlador, también puedes añadir un gancho que se ejecuté «después» de tu controlador. Para este ejemplo, imagina que quieres agregar un fragmento cifrado con ``sha1`` (con cierta sal que utiliza la ficha) a todas las respuestas a la que se les pase esta ficha de autenticación. - -Otro evento del núcleo de *Symfony* ---llamado ``kernel.response``--- es notificado en cada petición, pero después de que el controlador regresa un objeto ``Respuesta``. Crear un escucha «después» es tan fácil cómo crear una clase escucha y registrarla como servicio en este evento. - -Por ejemplo, tomando el ``TokenListener`` del ejemplo anterior y grabando primero la ficha de autenticación en los atributos de la petición. Esto servirá como indicador básico de que esta petición experimentó la autenticación de la ficha:: - - public function onKernelController(FilterControllerEvent $event) - { - // ... - - if ($controller[0] instanceof TokenAuthenticatedController) { - $token = $event->getRequest()->query->get('token'); - if (!in_array($token, $this->tokens)) { - throw new AccessDeniedHttpException('This action needs a valid token!'); - } - - // marca la petición con autenticación de ficha aprobada - $event->getRequest()->attributes->set('auth_token', $token); - } - } - -Ahora, añade otro método a esta clase ---``onKernelResponse``--- que busque este indicador en el objeto ``Petición`` y de encontrarlo ponga una cabecera personalizada en la respuesta:: - - // Añade la nueva declaración use en la parte superior de tu archivo - use Symfony\Component\HttpKernel\Event\FilterResponseEvent; - - public function onKernelResponse(FilterResponseEvent $event) - { - // comprueba si onKernelController marcó este como una - // petición de ficha autenticada - if (!$token = $event->getRequest()->attributes->get('auth_token')) { - return; - } - - $response = $event->getResponse(); - - // crea un fragmento cifrado y lo pone como cabecera de la respuesta - $hash = sha1($respuesta->getContent().$token); - $response->headers->set('X-CONTENT-HASH', $hash); - } - -Finalmente, es necesaria una segunda «etiqueta» en la definición del servicio para notificar a *Symfony* que el evento ``onKernelResponse`` se tendrá que notificar al evento ``kernel.response``: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml (o en tu services.yml) - services: - demo.tokens.action_listener: - class: Acme\DemoBundle\EventListener\TokenListener - arguments: ["%tokens%"] - tags: - - { name: kernel.event_listener, event: kernel.controller, method: onKernelController } - - { name: kernel.event_listener, event: kernel.response, method: onKernelResponse } - - .. code-block:: xml - - - - %tokens% - - - - - .. code-block:: php - - // app/config/config.php (or inside your services.php) - use Symfony\Component\DependencyInjection\Definition; - - $listener = new Definition('Acme\DemoBundle\EventListener\TokenListener', array('%tokens%')); - $listener->addTag('kernel.event_listener', array( - 'event' => 'kernel.controller', - 'method' => 'onKernelController' - )); - $listener->addTag('kernel.event_listener', array( - 'event' => 'kernel.response', - 'method' => 'onKernelResponse' - )); - $container->setDefinition('demo.tokens.action_listener', $listener); - -¡Eso es todo! El evento ``TokenListener`` ahora es notificado antes de ejecutar cada controlador (``onKernelController``) y después de que cada controlador regrese una respuesta (``onKernelResponse``). Al hacer que controladores específicos implementen la interfaz ``TokenAuthenticatedController``, tu escucha sabe cuales controladores deben tomar una acción. -Y almacenando un valor en la bolsa de «atributos» de la petición, el método ``onKernelResponse`` sabe que tiene que añadir la cabecera extra. ¡Que te diviertas! diff --git a/_sources/cookbook/event_dispatcher/class_extension.txt b/_sources/cookbook/event_dispatcher/class_extension.txt deleted file mode 100644 index 8dd2db5..0000000 --- a/_sources/cookbook/event_dispatcher/class_extension.txt +++ /dev/null @@ -1,121 +0,0 @@ -.. index:: - single: Despachador de evento - -Cómo extender una clase sin necesidad de utilizar herencia -========================================================== - -Para permitir que varias clases agreguen métodos a otra, puedes definir el método mágico ``__call()`` de la clase que deseas ampliar de esta manera: - -.. code-block:: php - - class Foo - { - // ... - - public function __call($method, $arguments) - { - // crea un evento llamado 'foo.method_is_not_found' - $event = new HandleUndefinedMethodEvent($this, $method, $arguments); - $this->dispatcher->dispatch('foo.method_is_not_found', $event); - - // ¿ningún escucha es capaz de procesar el evento? El método no existe - if (!$event->isProcessed()) { - throw new \Exception(sprintf('Call to undefined method %s::%s.', get_class($this), $method)); - } - - // regresa el escucha que devolvió el valor - return $event->getReturnValue(); - } - } - -Este utiliza un ``HandleUndefinedMethodEvent`` especial que también se debe crear. Esta es una clase genérica que podrías reutilizar cada vez que necesites usar este modelo para extender clases: - -.. code-block:: php - - use Symfony\Component\EventDispatcher\Event; - - class HandleUndefinedMethodEvent extends Event - { - protected $subject; - protected $method; - protected $arguments; - protected $returnValue; - protected $isProcessed = false; - - public function __construct($subject, $method, $arguments) - { - $this->asunto = $asunto; - $this->metodo = $metodo; - $this->arguments = $arguments; - } - - public function getSubject() - { - return $this->subject; - } - - public function getMethod() - { - return $this->method; - } - - public function getArguments() - { - return $this->arguments; - } - - /** - * Fija el valor a devolver y detiene la notificación a otros escuchas - */ - public function setReturnValue($val) - { - $this->returnValue = $val; - $this->isProcessed = true; - $this->stopPropagation(); - } - - public function getReturnValue($val) - { - return $this->returnValue; - } - - public function isProcessed() - { - return $this->isProcessed; - } - } - -A continuación, crea una clase que debe escuchar el evento ``foo.method_is_not_found`` y *añade* el método ``bar()``: - -.. code-block:: php - - class Bar - { - public function onFooMethodIsNotFound(HandleUndefinedMethodEvent $event) - { - // únicamente responde a las llamadas al método 'bar' - if ('bar' != $event->getMethod()) { - // permite que otro escucha se preocupe del método desconocido - return; - } - - // el objeto subject (la instancia foo) - $foo = $event->getSubject(); - - // los argumentos del método bar - $arguments = $event->getArguments(); - - // ... hace algo - - // fija el valor de retorno - $event->setReturnValue($someValue); - } - } - -Finally, add the new ``bar`` method to the ``Foo`` class by registering an -instance of ``Bar`` with the ``foo.method_is_not_found`` event: - -.. code-block:: php - - $bar = new Bar(); - $dispatcher->addListener('foo.method_is_not_found', array($bar, 'onFooMethodIsNotFound')); diff --git a/_sources/cookbook/event_dispatcher/index.txt b/_sources/cookbook/event_dispatcher/index.txt deleted file mode 100644 index 6859d90..0000000 --- a/_sources/cookbook/event_dispatcher/index.txt +++ /dev/null @@ -1,10 +0,0 @@ -Despachador de eventos -====================== - -.. toctree:: - :maxdepth: 2 - - before_after_filters - class_extension - method_behavior - \ No newline at end of file diff --git a/_sources/cookbook/event_dispatcher/method_behavior.txt b/_sources/cookbook/event_dispatcher/method_behavior.txt deleted file mode 100644 index 70e13b5..0000000 --- a/_sources/cookbook/event_dispatcher/method_behavior.txt +++ /dev/null @@ -1,49 +0,0 @@ -.. index:: - single: Despachador de evento - -Cómo personalizar el comportamiento de un método sin utilizar herencia -====================================================================== - -Haciendo algo antes o después de llamar a un método ---------------------------------------------------- - -Si quieres hacer algo justo antes o justo después de invocar a un método, puedes enviar un evento, al principio o al final del método, respectivamente:: - - class Foo - { - // ... - - public function send($foo, $bar) - { - // hace algo antes que el método - $event = new FilterBeforeSendEvent($foo, $bar); - $this->dispatcher->dispatch('foo.pre_send', $event); - - // obtiene $foo y $bar desde el evento, esto se puede modificar - $foo = $event->getFoo(); - $bar = $event->getBar(); - - // aquí va la implementación real del método - $ret = ...; - - // hace algo después del método - $event = new FilterSendReturnValue($ret); - $this->dispatcher->dispatch('foo.post_send', $event); - - return $event->getReturnValue(); - } - } - -En este ejemplo, se lanzan dos eventos: ``foo.pre_send``, antes de ejecutar el método, y ``foo.post_send`` después de ejecutar el método. Cada uno utiliza una clase Evento personalizada para comunicar información a los escuchas de los dos eventos. Estas clases de evento se tendrían que crear por ti y deben permitir que, en este ejemplo, las variables ``$foo``, ``$bar`` y ``$ret`` sean recuperadas y establecidas por los escuchas. - -Por ejemplo, suponiendo que el ``FilterSendReturnValue`` tiene un método ``setReturnValue``, un escucha puede tener este aspecto: - -.. code-block:: php - - public function onFooPostSend(FilterSendReturnValue $event) - { - $ret = $event->getReturnValue(); - // modifica el valor original de ``$ret`` - - $event->setReturnValue($ret); - } diff --git a/_sources/cookbook/form/create_custom_field_type.txt b/_sources/cookbook/form/create_custom_field_type.txt deleted file mode 100644 index 08db50c..0000000 --- a/_sources/cookbook/form/create_custom_field_type.txt +++ /dev/null @@ -1,300 +0,0 @@ -.. index:: - single: Formulario; Tipo de campo personalizado - -Cómo crear un tipo de campo personalizado para formulario -========================================================= - -*Symfony* viene con un montón de tipos de campos fundamentales para la construcción de formularios. -Sin embargo, hay situaciones en las cuales quieres crear un tipo de campo de formulario personalizado -para un propósito específico. Esta receta asume que necesitas una definición de campo que contiene el género de una persona, basándote en el campo ``choice`` existente. Esta sección explica cómo definir el campo, cómo puedes personalizar su diseño y, por último, cómo lo puedes registrar para usarlo en tu aplicación. - -Definiendo el tipo de campo ---------------------------- - -Con el fin de crear el tipo de campo personalizado, primero tienes que crear la clase que representa el campo. En esta situación, la clase contendrá el tipo de campo que se llamará ``GenderType`` y el archivo se guardará en la ubicación predeterminada para campos de formulario, la cual es ``\Form\Type``. Asegúrate de que el campo se extiende de -:class:`Symfony\\Component\\Form\\AbstractType`:: - - // src/Acme/DemoBundle/Form/Type/GenderType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class GenderType extends AbstractType - { - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'choices' => array( - 'm' => 'Male', - 'f' => 'Female', - ) - )); - } - - public function getParent() - { - return 'choice'; - } - - public function getName() - { - return 'gender'; - } - } - -.. tip:: - - La ubicación de este archivo no es importante - el directorio ``Form\Type`` sólo es una convención. - -En este caso, el valor de retorno de la función ``getParent`` indica que estas extendiendo el tipo de campo ``choice``. Esto significa que, por omisión, heredas toda la lógica y represtación de ese tipo de campo. Para ver algo de la lógica, echa un vistazo a la clase `ChoiceType`_. Hay tres métodos que son particularmente importantes: - -* ``buildForm()`` - Cada tipo de campo tiene un método ``buildForm``, que es donde configuras y construyes cualquier campo(s). Ten en cuenta que este es el mismo método que utilizas para configurar *tus* formularios, y aquí funciona igual. - -* ``buildView()`` - Este método se utiliza para establecer las variables extra que necesitarás al reproducir el campo en una plantilla. Por ejemplo, en `ChoiceType`_, está definida una variable ``multiple`` que se fija y utiliza en la plantilla para establecer (o no un conjunto), el atributo ``multiple`` en el campo ``select``. Ve `Creando una plantilla para el campo`_ para más detalles. - -* ``setDefaultOptions()`` - Define opciones para tu tipo de formulario que puedes utilizar en ``buildForm()`` y ``buildView()``. Hay un montón de opciones comunes a todos los campos (consulta :doc:`/reference/forms/types/form`), pero aquí, puedes crear cualquier otra que necesites. - -.. tip:: - - Si vas a crear un campo que consta de muchos campos, entonces, asegúrate de establecer tu tipo «padre» como ``form`` o algo que extienda a ``form``. - Además, si necesitas modificar la «vista» de cualquiera de tus tipos descendientes de tu tipo padre, usa el método ``finishView()``. - -El método ``getName()`` devuelve un identificador que debe ser único en tu aplicación. Este se utiliza en varios lugares, tales como cuando personalizas cómo será pintado tu tipo de formulario. - -El objetivo de este campo es extender el tipo ``choice`` para habilitar la selección de un género. Esto se consigue fijando las opciones a una lista de posibles géneros. - -Creando una plantilla para el campo ------------------------------------ - -Cada tipo de campo está representado por un fragmento de la plantilla, el cual se determina en parte por el valor de su método ``getName()``. Para más información, consulta :ref:`cookbook-form-customization-form-themes`. - -En este caso, debido a que el campo padre es ``choice``, no *necesitas* hacer ningún trabajo debido a que el tipo de campo personalizado automáticamente lo dibuja como el tipo ``choice``. Pero por el bien de este ejemplo, supón que al «expandir» tu campo (es decir, botones de radio o casillas de verificación, en vez de un campo de selección), lo quieres dibujar siempre en un elemento ``ul``. En la plantilla del tema de tu formulario (consulta el enlace de arriba para más detalles), crea un bloque ``gender_widget`` para manejar esto: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} - {% block gender_widget %} - {% spaceless %} - {% if expanded %} -
      - {% for child in form %} -
    • - {{ form_widget(child) }} - {{ form_label(child) }} -
    • - {% endfor %} -
    - {% else %} - {# simplemente deja que el elemento gráfico 'choice' reproduzca la etiqueta select #} - {{ block('choice_widget') }} - {% endif %} - {% endspaceless %} - {% endblock %} - - .. code-block:: html+php - - - -
      block($form, 'widget_container_attributes') ?>> - -
    • - widget($child) ?> - label($child) ?> -
    • - -
    - - - renderBlock('choice_widget') ?> - - -.. note:: - - Asegúrate que utilizas el prefijo correcto para el elemento gráfico. En este ejemplo, el nombre debe set ``gender_widget``, de acuerdo con el valor devuelto por ``getName``. - Además, el archivo de configuración principal debe apuntar a la plantilla del formulario personalizado de modo que este se utilice al reproducir todos los formularios. - - .. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - twig: - form: - resources: - - 'AcmeDemoBundle:Form:fields.html.twig' - - .. code-block:: xml - - - - - AcmeDemoBundle:Form:fields.html.twig - - - - .. code-block:: php - - // app/config/config.php - $container->loadFromExtension('twig', array( - 'form' => array( - 'resources' => array( - 'AcmeDemoBundle:Form:fields.html.twig', - ), - ), - )); - -Usando el tipo de campo ------------------------ - -Ahora puedes utilizar el tipo de campo personalizado de inmediato, simplemente creando una nueva instancia del tipo en uno de tus formularios:: - - // src/Acme/DemoBundle/Form/Type/AuthorType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - - class AuthorType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('gender_code', new GenderType(), array( - 'empty_value' => 'Choose a gender', - )); - } - } - -Pero esto sólo funciona porque el ``GenderType()`` es muy sencillo. ¿Qué pasa si los códigos de género se almacena en la configuración o en una base de datos? La siguiente sección se explica cómo resuelven este problema los tipos de campo más complejos. - -.. _form-cookbook-form-field-service: - -Creando tu tipo de campo como un servicio ------------------------------------------ - -Hasta ahora, este artículo ha supuesto que tienes un tipo de campo personalizado muy simple. -Pero si necesitas acceder a la configuración de una conexión base de datos, o a algún otro servicio, entonces querrás registrar tu tipo personalizado como un servicio. Por ejemplo, supón que estas almacenando los parámetros de género en la configuración: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - parameters: - genders: - m: Male - f: Female - - .. code-block:: xml - - - - - Male - Female - - - - .. code-block:: php - - // app/config/config.php - $container->setParameter('genders.m', 'Male'); - $container->setParameter('genders.f', 'Female'); - -Para utilizar el parámetro, define tu tipo de campo personalizado como un servicio, inyectando el valor del parámetro ``genders`` como el primer argumento de la función ``__construct`` que vas a crear: - -.. configuration-block:: - - .. code-block:: yaml - - # src/Acme/DemoBundle/Resources/config/services.yml - services: - acme_demo.form.type.gender: - class: Acme\DemoBundle\Form\Type\GenderType - arguments: - - "%genders%" - tags: - - { name: form.type, alias: gender } - - .. code-block:: xml - - - - %genders% - - - - .. code-block:: php - - // src/Acme/DemoBundle/Resources/config/services.php - use Symfony\Component\DependencyInjection\Definition; - - $container - ->setDefinition('acme_demo.form.type.gender', new Definition( - 'Acme\DemoBundle\Form\Type\GenderType', - array('%genders%') - )) - ->addTag('form.type', array( - 'alias' => 'gender', - )) - ; - -.. tip:: - - Asegúrate de que estás importando el archivo de servicios. Consulta :ref:`service-container-imports-directive` para más detalles. - -Asegúrate de que la etiqueta del atributo ``alias`` corresponde con el valor devuelto por el método ``getName`` definido anteriormente. Verás la importancia de esto en un momento cuando utilices el tipo de campo personalizado. Pero primero, agrega un método ``__construct``, el cual recibe la configuración del género:: - - // src/Acme/DemoBundle/Form/Type/GenderType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - // ... - - // ... - class GenderType extends AbstractType - { - private $genderChoices; - - public function __construct(array $genderChoices) - { - $this->genderChoices = $genderChoices; - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'choices' => $this->genderChoices, - )); - } - - // ... - } - -¡Genial! El ``GenderType`` ahora es impulsado por los parámetros de configuración y está registrado como un servicio. Adicionalmente, debido a que utilizaste el alias ``form.type`` en tu configuración, es mucho más fácil utilizar el campo:: - - // src/Acme/DemoBundle/Form/Type/AuthorType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\FormBuilderInterface; - - // ... - - class AuthorType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('gender_code', 'gender', array( - 'empty_value' => 'Choose a gender', - )); - } - } - -Ten en cuenta que en vez de crear una nueva instancia, puedes simplemente referirte a ella -por el alias utilizado en tu configuración del servicio, ``gender``. ¡Que te diviertas! - -.. _`ChoiceType`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/ChoiceType.php -.. _`FieldType`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Extension/Core/Type/FieldType.php diff --git a/_sources/cookbook/form/create_form_type_extension.txt b/_sources/cookbook/form/create_form_type_extension.txt deleted file mode 100644 index d471e35..0000000 --- a/_sources/cookbook/form/create_form_type_extension.txt +++ /dev/null @@ -1,279 +0,0 @@ -.. index:: - single: Formulario; Extendiendo el tipo Form - -Cómo crear una extensión del tipo «Form» -======================================== - -Los :doc:`Tipos de campo de formulario personalizados ` son buenos cuando necesitas tipos de campo con un propósito específico, tal como un selector de género, o una entrada de *RFC* (en méxico, Registro Federal de Causantes o *VAT* en los paises europeos). - -Pero a veces, en realidad no necesitas añadir nuevos tipos de campos ---deseas -agregar características en lo alto de tipos existentes---. Aquí es donde entran en juego la extensiones del tipo «Form». - -Las extensiones del tipo «Form» tienen 2 casos de uso principales: - -#. ¿Quieres añadir una **característica genérica a varios tipos** (por ejemplo, añadir un texto de «ayuda» a cada tipo de campo); -#. ¿Quieres añadir una **característica específica a un solo tipo** (tal como la adición de una característica «download» al tipo de campo «file»). - -En ambos casos, puede ser posible lograr tu objetivo personalizando la representación del formulario, o con tipos de campos de formulario personalizados. Pero usar extensiones del tipo «Form» puede ser más limpio (al limitar la cantidad de lógica del negocio en plantillas) y más flexible (puedes agregar varias extensiones de tipo a un solo tipo «form»). - -Las extensiones del tipo «Form» pueden conseguir la mayoría de los tipos de campos personalizados que puedes hacer, pero en vez de ser los tipos de campo propios, **estos se conectan a los tipos existentes**. - -Imagina que administras una entidad ``Media``, y que cada medio se asocia a un archivo. Tu formulaio ``Media`` utiliza un tipo de archivo, pero cuando editas la entidad, -te gustaría ver su imagen dibujada automáticamente junto a la entrada de archivo. - -Por supuesto, lo podrías hacer personalizando la representación de este en una -plantilla. Pero las extensiones de tipo de campo te permiten hacer esto de una manera mucho más agradable. - -Definiendo la extensión del tipo «Form» ---------------------------------------- - -Tu primera tarea será crear la clase de la extensión del tipo «Form». La llamarás ``ImageTypeExtension``. Por norma gemeral, las extensiones de «form» viven en el directorio ``Form\Extension`` de uno de tus paquetes. - -Al crear una extensión del tipo «form», puedes implementar la interfaz :class:`Symfony\\Component\\Form\\FormTypeExtensionInterface` o extender la clase :class:`Symfony\\Component\\Form\\AbstractTypeExtension`. En la mayoría de los casos, es más fácil extender la clase abstracta:: - - // src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php - namespace Acme\DemoBundle\Form\Extension; - - use Symfony\Component\Form\AbstractTypeExtension; - - class ImageTypeExtension extends AbstractTypeExtension - { - /** - * Devuelve el nombre del tipo que será extendido. - * - * @return string The name of the type being extended - */ - public function getExtendedType() - { - return 'file'; - } - } - -El único método que **necesitas** implementar es la función ``getExtendedType``. -Esta se usa para indicar el nombre del tipo «form» que será extendido -por tu extensión. - -.. tip:: - - El valor que regresas en el método ``getExtendedType`` corresponde al valor devuelto por el método ``getName`` en la clase del tipo «form» que deseas extender. - -Además de la función ``getExtendedType``, probablemente quieras sustituir uno de los siguientes métodos: - -* ``buildForm()`` - -* ``buildView()`` - -* ``setDefaultOptions()`` - -* ``finishView()`` - -Para más información sobre que hacen estos métodos, puedes referirte al artículo -:doc:`Creando tipos de campo personalizados ` en el recetario. - -Registrando como servicio tu extensión del tipo «Form» ------------------------------------------------------- - -El siguiente paso es dar a conocer tu extensión a *Symfony*. Todo lo que tienes que hacer es declararla como un servicio utilizando la etiqueta ``form.type_extension``: - -.. configuration-block:: - - .. code-block:: yaml - - services: - acme_demo_bundle.image_type_extension: - class: Acme\DemoBundle\Form\Extension\ImageTypeExtension - tags: - - { name: form.type_extension, alias: file } - - .. code-block:: xml - - - - - - .. code-block:: php - - $container - ->register( - 'acme_demo_bundle.image_type_extension', - 'Acme\DemoBundle\Form\Extension\ImageTypeExtension' - ) - ->addTag('form.type_extension', array('alias' => 'file')); - -La clave ``alias`` de la etiqueta es el tipo de campo al que esta extensión se debe aplicar. En tu caso, cuando quieras extender el tipo de campo ``file``, utilizarás ``file`` como un alias. - -Añadiendo lógica del negocio a la extensión -------------------------------------------- - -El objetivo de tu extensión es mostrar bonitas imágenes junto a las entradas de archivo (cuándo el modelo subyacente contenga imágenes). Para ese propósito, supón que utilizas un enfoque similar al descrito en artículo -:doc:`Cómo manejar archivos subidos con Doctrine `: -Tienes un modelo de Medios con una propiedad ``file`` (correspondiente al campo de archivo en el formulario) y una propiedad ``path`` (correspondiente a la ruta de la imagen en la base de datos):: - - // src/Acme/DemoBundle/Entity/Media.php - namespace Acme\DemoBundle\Entity; - - use Symfony\Component\Validator\Constraints as Assert; - - class Media - { - // ... - - /** - * @var string The path - typically stored in the database - */ - private $path; - - /** - * @var \Symfony\Component\HttpFoundation\File\UploadedFile - * @Assert\File(maxSize="2M") - */ - public $file; - - // ... - - /** - * Obtiene la url de la imagen - * - * @return null|string - */ - public function getWebPath() - { - // ... $webPath comienza la url completa de la imagen, para utilizarla en plantillas - - return $webPath; - } - } - -Tu clase de la extensión del tipo «form» deberá hacer dos cosas para extender -el tipo ``file`` de «form»: - -#. Sustituye el método ``setDefaultOptions`` con el fin de añadir una opción ``image_path``; -#. Sustituye los métodos ``buildForm`` y ``buildView`` para pasar la *URL* de la imagen a la vista. - -La lógica es la siguiente: cuándo añades un campo de tipo ``file`` al formulario, serás capaz de especificar una nueva opción: ``image_path``. Esta opción dirá al campo ``file`` cómo conseguir la *URL* real de la imagen para mostrarla en la vista:: - - // src/Acme/DemoBundle/Form/Extension/ImageTypeExtension.php - namespace Acme\DemoBundle\Form\Extension; - - use Symfony\Component\Form\AbstractTypeExtension; - use Symfony\Component\Form\FormView; - use Symfony\Component\Form\FormInterface; - use Symfony\Component\PropertyAccess\PropertyAccess; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class ImageTypeExtension extends AbstractTypeExtension - { - /** - * Devuelve el nombre del tipo que será extendido. - * - * @return string The name of the type being extended - */ - public function getExtendedType() - { - return 'file'; - } - - /** - * Añade la opción image_path - * - * @param OptionsResolverInterface $resolver - */ - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setOptional(array('image_path')); - } - - /** - * Pase la URL de la imagen a la vista - * - * @param FormView $view - * @param FormInterface $form - * @param array $options - */ - public function buildView(FormView $view, FormInterface $form, array $options) - { - if (array_key_exists('image_path', $options)) { - $parentData = $form->getParent()->getData(); - - if (null !== $parentData) { - $accessor = PropertyAccess::getPropertyAccessor(); - $imageUrl = $accessor->getValue($parentData, $options['image_path']); - } else { - $imageUrl = null; - } - - // configura una variable "image_url" que debe estar disponible al dibujar este campo - $view->set('image_url', $imageUrl); - } - } - - } - -Sustituye el elemento gráfico del fragmento de plantilla --------------------------------------------------------- - -Cada tipo de campo es dibujado por un fragmento de plantilla. Estos fragmentos de plantilla se pueden sustituir para personalizar la forma de dibujarlo. Para más información, puedes referirte al artículo :ref:`cookbook-form-customization-form-themes`. - -En tu clase de la extensión, añadiste una nueva variable (``image_url``), pero -todavía necesitas aprovechar en tus plantillas esta nueva variable. -Específicamente, necesitas sustituir el bloque ``file_widget``: - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #} - {% extends 'form_div_layout.html.twig' %} - - {% block file_widget %} - {% spaceless %} - - {{ block('form_widget') }} - {% if image_url is not null %} - - {% endif %} - - {% endspaceless %} - {% endblock %} - - .. code-block:: html+php - - - widget($form) ?> - - - - -.. note:: - - Necesitarás cambiar tu archivo «config» o especificar explícitamente cómo quieres tematizar tu formulario a fin de que *Symfony* utilice tu bloque redefinido. Ve :ref:`cookbook-form-customization-form-themes` para más información. - -Usando la extensión del tipo «Form» ------------------------------------ - -De ahora en adelante, cuándo añadas un campo de tipo ``file`` a tu formulario, puedes especificar una opción ``image_path`` que será la imagen a mostrar junto al campo de archivo. Por ejemplo:: - - // src/Acme/DemoBundle/Form/Type/MediaType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - - class MediaType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('name', 'text') - ->add('file', 'file', array('image_path' => 'webPath')); - } - - public function getName() - { - return 'media'; - } - } - -Cuándo muestres el formulario, si el modelo subyacente ya fue asociado con una imagen, la verás junto a la entrada de archivo. diff --git a/_sources/cookbook/form/data_transformers.txt b/_sources/cookbook/form/data_transformers.txt deleted file mode 100644 index e89883f..0000000 --- a/_sources/cookbook/form/data_transformers.txt +++ /dev/null @@ -1,317 +0,0 @@ -.. index:: - single: Formulario; Transformadores de datos - -Cómo usar transformadores de datos -================================== - -A menudo te encontrarás con la necesidad de transformar en alguna otra cosa los datos que el usuario introdujo en un formulario para usarlos en tu programa. Lo podrías hacer fácilmente a mano en tu controlador, pero, ¿qué pasa si quieres utilizar ese formulario específico en diferentes sitios? - -Digamos que tienes una relación uno a uno entre una ``Tarea`` y una ``Incidencia``, por ejemplo, una ``Tarea`` opcionalmente está vinculada a una ``Incidencia``. Añadir un cuadro de lista con todas las posibles ``Incidencias`` finalmente te puede conducir a una lista realmente larga en la cual es imposible encontrar algo. En su lugar mejor querrás añadir un cuadro de texto, en el cual el usuario sencillamente puede introducir el número de la incidencia. - -Podrías intentar hacer esto en tu controlador, pero no es la mejor solución. -Sería mejor si esta incidencia se buscara y convirtiera automáticamente a un objeto ``Incidencia``, para usarla en tu acción. -Aquí es donde entran en juego los Transformadores de datos. - -Creando el transformador ------------------------- - -En primer lugar, crea una clase ``IssueToNumberTransformer`` --- esta clase será la responsable de la conversión hacia y desde el número de incidencia y el objeto ``Incidencia``:: - - // src/Acme/TaskBundle/Form/DataTransformer/IssueToNumberTransformer.php - namespace Acme\TaskBundle\Form\DataTransformer; - - use Symfony\Component\Form\DataTransformerInterface; - use Symfony\Component\Form\Exception\TransformationFailedException; - use Doctrine\Common\Persistence\ObjectManager; - use Acme\TaskBundle\Entity\Issue; - - class IssueToNumberTransformer implements DataTransformerInterface - { - /** - * @var ObjectManager - */ - private $om; - - /** - * @param ObjectManager $om - */ - public function __construct(ObjectManager $om) - { - $this->om = $om; - } - - /** - * Transforma un objeto (``issue``) a una cadena (``number``). - * - * @param Issue|null $issue - * @return string - */ - public function transform($issue) - { - if (null === $issue) { - return ""; - } - - return $issue->getNumber(); - } - - /** - * Transforma una cadena (``number``) a un objeto (``issue``). - * - * @param string $number - * - * @return Issue|null - * - * @throws TransformationFailedException si no encuentra el objeto (issue). - */ - public function reverseTransform($number) - { - if (!$number) { - return null; - } - - $issue = $this->om - ->getRepository('AcmeTaskBundle:Issue') - ->findOneBy(array('number' => $number)) - ; - - if (null === $issue) { - throw new TransformationFailedException(sprintf( - 'An issue with number "%s" does not exist!', - $number - )); - } - - return $issue; - } - } - -.. tip:: - - Si quieres crear una nueva incidencia al introducir un número desconocido, puedes crear una nueva instancia en lugar de lanzar una ``TransformationFailedException``. - -Usando el transformador ------------------------ - -Ahora que ya tienes incorporado el transformador, solamente lo tienes que añadir a tu campo ``incidencia`` en algún formulario. - - También puedes utilizar transformadores sin necesidad de crear un nuevo tipo de formulario personalizado llamando a ``addModelTransformer`` (o a ``addViewTransformer`` --- consulta la sección `Transformadores de modelo y vista`_) en cualquier campo del constructor:: - - use Symfony\Component\Form\FormBuilderInterface; - use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; - - class TaskType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - // ... - - // este asume que el gestor de la entidad se pasó como una - // opción - $entityManager = $options['em']; - $transformer = new IssueToNumberTransformer($entityManager); - - // agrega un campo de texto normal, pero le añade tu transformador - $builder->add( - $builder->create('issue', 'text') - ->addModelTransformer($transformer) - ); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', - )); - - $resolver->setRequired(array( - 'em', - )); - - $resolver->setAllowedTypes(array( - 'em' => 'Doctrine\Common\Persistence\ObjectManager', - )); - - // ... - } - - // ... - } - -Este ejemplo requiere que pases como una opción el gestor de la entidad al crear el formulario. Más tarde, aprenderás cómo puedes crear un tipo de campo ``incidencia`` personalizado para evitar la necesidad de hacer esto en tu controlador:: - - $taskForm = $this->createForm(new TaskType(), $task, array( - 'em' => $this->getDoctrine()->getEntityManager(), - )); - -¡Estupendo, ya está! El usuario será capaz de introducir un número de incidencia en el campo de texto y se transformará en un objeto ``Incidencia``. Esto significa que, después de vincularlo satisfactoriamente, la infraestructura del formulario pasa un objeto ``Incidencia`` real a ``Task::setIssue()`` en vez de el número de incidencia. - -Si no encuentra la incidencia, creará un error en el formulario para ese campo y puedes controlar su mensaje de error con la opción ``invalid_message`` del campo. - -.. caution:: - - Ten en cuenta que al añadir un transformador necesitas usar una sintaxis un poco más complicada cuando agregas el campo. Lo siguiente es **incorrecto**, debido a que puedes aplicar el transformador a todo el formulario, en lugar de sólo a este campo:: - - // ESTE ESTÁ MAL - LA TRANSFORMACIÓN SE APLICARÁ AL - // FORMULARIO COMPLETO - // ve el código correcto en el ejemplo anterior - $builder->add('issue', 'text') - ->addModelTransformer($transformer); - -Transformadores de modelo y vista -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - Los nombres y métodos de los transformadores cambiaron en *Symfony 2.1*. - ``prependNormTransformer`` se convirtió en ``addModelTransformer`` y ``appendClientTransformer`` cambió a ``addViewTransformer``. - -En el ejemplo anterior, el transformador se utilizó como un transformador del ``«modelo»``. -De hecho, hay dos diferentes tipos de transformadores y tres diferentes tipos de datos subyacentes. - -.. image:: /images/cookbook/form/DataTransformersTypes.png - :align: center - -En cualquier formulario, los 3 diferentes tipos de datos son los siguientes: - -1) **Datos del modelo** --- Estos son los datos en el formato que se utiliza en tu aplicación (por ejemplo, un objeto ``Insidencia``). Si llamas a ``Form::getData`` o ``Form::setData``, estás tratando con los datos del «modelo». - -2) **Datos normales** --- Esta es una versión normalizada de los datos, y suelen ser los mismos datos que los de tu «modelo» (aunque no en nuestro ejemplo). Comúnmente no se utiliza directamente. - -3) **Datos de la vista** --- Este es el formato que se utiliza para rellenar los campos del formulario en sí. También es el formato en el que el usuario enviará los datos. Cuando llamas a ``Form::bind($datos)``, los ``$datos`` están en el formato de la «vista». - -Los 2 diferentes tipos de transformadores te ayudan a convertir hacia y desde cada uno de estos tipos de datos: - -**transformadores del modelo**: - --- ``transform``: "datos del modelo" => "datos normales" - --- ``reverseTransform``: "datos normales" => "datos del modelo" - -**Transformadores de la vista**: - --- ``transform``: "datos normales" => "datos de la vista" - --- ``reverseTransform``: "datos de la vista" => "datos normales" - -¿Qué transformador necesitas? depende de tu situación. - -Para utilizar el transformador de la vista, llama a ``addViewTransformer``. - -¿Así que por qué uso el modelo transformador? ---------------------------------------------- - -En este ejemplo, el campo es un campo ``text``, y un campo de texto siempre se espera que sea un sencillo, formato escalar en los formatos ``norm`` y ``view``. Por esta razón, el transformador más adecuado fue el transformador del «modelo» (que se convierte a/desde el formato *normal* --- cadena a número --- al formato del *modelo* --- emisión de objeto). - -La diferencia entre los transformadores es sutil y siempre se debe pensar en lo que deben ser realmente los datos «normales» para un campo. Por ejemplo, los datos «normales» de un campo ``text`` son una cadena, pero es un objeto ``DateTime`` para un campo ``date``. - -Usando transformadores en un tipo de campo personalizado --------------------------------------------------------- - -En el ejemplo anterior, aplicaste el transformador a un campo ``text`` normal. -Esto fue fácil, pero tiene dos inconvenientes: - -1) Necesitas recordar siempre que debes aplicar el transformador cuando vas a añadir un campo de número de incidencia. - -2) Tienes que preocuparte de pasarlo en la opción ``em`` cada vez que creas un formulario que utiliza el transformador. - -Debido a esto, puedes optar por :doc:`crear un tipo de campo personalizado `. -En primer lugar, crea la clase del tipo de campo personalizado:: - - // src/Acme/TaskBundle/Form/Type/IssueSelectorType.php - namespace Acme\TaskBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Acme\TaskBundle\Form\DataTransformer\IssueToNumberTransformer; - use Doctrine\Common\Persistence\ObjectManager; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class IssueSelectorType extends AbstractType - { - /** - * @var ObjectManager - */ - private $om; - - /** - * @param ObjectManager $om - */ - public function __construct(ObjectManager $om) - { - $this->om = $om; - } - - public function buildForm(FormBuilderInterface $builder, array $options) - { - $transformer = new IssueToNumberTransformer($this->om); - $builder->addModelTransformer($transformer); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'invalid_message' => 'The selected issue does not exist', - )); - } - - public function getParent() - { - return 'text'; - } - - public function getName() - { - return 'issue_selector'; - } - } - -A continuación, registra el tipo como un servicio y etiquétalo con ``form.type``, para que sea reconocido como un tipo de campo personalizado: - -.. configuration-block:: - - .. code-block:: yaml - - services: - acme_demo.type.issue_selector: - class: Acme\TaskBundle\Form\Type\IssueSelectorType - arguments: ["@doctrine.orm.entity_manager"] - tags: - - { name: form.type, alias: issue_selector } - - .. code-block:: xml - - - - - - - .. code-block:: php - - $container - ->setDefinition('acme_demo.type.issue_selector', array( - new Reference('doctrine.orm.entity_manager'), - )) - ->addTag('form.type', array( - 'alias' => 'issue_selector', - )) - ; - -Ahora, cada vez que necesites utilizar tu tipo de campo ``issue_selector`` especial, es muy fácil:: - - // src/Acme/TaskBundle/Form/Type/TaskType.php - namespace Acme\TaskBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - - class TaskType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('task') - ->add('dueDate', null, array('widget' => 'single_text')); - ->add('issue', 'issue_selector'); - } - - public function getName() - { - return 'task'; - } - } diff --git a/_sources/cookbook/form/dynamic_form_generation.txt b/_sources/cookbook/form/dynamic_form_generation.txt deleted file mode 100644 index c7b3050..0000000 --- a/_sources/cookbook/form/dynamic_form_generation.txt +++ /dev/null @@ -1,129 +0,0 @@ -.. index:: - single: Formulario; Eventos - -Cómo generar formularios dinámicamente usando eventos del formulario -==================================================================== - -Antes de zambullirte en la generación dinámica de formularios, hagamos una rápida revisión de lo que es una clase formulario desnuda:: - - // src/Acme/DemoBundle/Form/Type/ProductType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - - class ProductType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('name'); - $builder->add('price'); - } - - public function getName() - { - return 'product'; - } - } - -.. note:: - - Si esta sección de código en particular no te es familiar, probablemente necesites dar un paso atrás y revisar en primer lugar el :doc:`Capítulo de Formularios ` antes de continuar. - -Asumiremos por un momento que este formulario utiliza una clase imaginaria ``«Product»`` que únicamente tiene dos propiedades relevantes (``name`` y ``price``). El formulario generado de esta clase a toda costa se verá exactamente igual si estás creando un nuevo Producto o si estás editando un producto existente (p. ej. un producto recuperado de la base de datos). - -Ahora, supongamos que no deseas que el usuario pueda cambiar el valor del ``name`` una vez creado el objeto. Para ello, puedes confiar en el sistema :doc:`Despachador de eventos ` de *Symfony* para analizar los datos en el objeto y modificar el formulario basándose en los datos del objeto ``Producto``. En este artículo, aprenderás cómo añadir este nivel de flexibilidad a tus formularios. - -.. _`cookbook-forms-event-subscriber`: - -Añadiendo un suscriptor de evento a una clase formulario --------------------------------------------------------- - -Por lo tanto, en lugar de añadir directamente el elemento gráfico ``name`` vía tu clase formulario ``ProductType``, vas a delegar la responsabilidad de crear este campo en particular a un suscriptor de evento:: - - // src/Acme/DemoBundle/Form/Type/ProductType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber; - - class ProductType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('price'); - - $builder->addEventSubscriber(new AddNameFieldSubscriber()); - } - - public function getName() - { - return 'product'; - } - } - -.. _`cookbook-forms-inside-subscriber-class`: - -Dentro de la clase suscriptor de eventos ----------------------------------------- - -El objetivo es crear el campo ``«name»`` *únicamente* si el objeto ``Producto`` subyacente es nuevo (por ejemplo, no se ha persistido a la base de datos). Basándose en esto, el suscriptor podría tener la siguiente apariencia: - -.. versionadded:: 2.2 - La habilidad de pasar una cadena al método :method:`FormInterface::add ` se añadió en *Symfony 2.2*. - -.. code-block:: php - - // src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php - namespace Acme\DemoBundle\Form\EventListener; - - use Symfony\Component\Form\FormEvent; - use Symfony\Component\Form\FormEvents; - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - - class AddNameFieldSubscriber implements EventSubscriberInterface - { - public static function getSubscribedEvents() - { - // Informa al despachador que deseas escuchar el evento - // form.pre_set_data y se debe llamar al método 'preSetData'. - return array(FormEvents::PRE_SET_DATA => 'preSetData'); - } - - public function preSetData(FormEvent $event) - { - $data = $event->getData(); - $form = $event->getForm(); - - // Durante la creación del formulario setData() es llamado con null como - // argumento por el constructor FormBuilder. Solo te interesa cuando - // setData es llamado con un objeto Entity real (ya sea nuevo, - // o recuperado con Doctrine). Esta declaración 'if' permite evadir - // la condición null. - if (null === $data) { - return; - } - - // comprueba si el objeto producto es "nuevo" - if (!$data->getId()) { - $form->add('name', 'text'); - } - } - } - -.. caution:: - - Es fácil malinterpretar el propósito del segmento ``if (null === $data)`` de este suscriptor de eventos. Para comprender plenamente su papel, también podrías considerar echarle un vistazo a la `clase Form`_ prestando especial atención a donde se llama a ``setData()`` al final del constructor, así como al método ``setData()`` en sí mismo. - -La línea ``FormEvents::PRE_SET_DATA`` en realidad se resuelve en la cadena ``form.pre_set_data``. -La `clase FormEvents`_ sirve a un propósito organizacional. Se trata de una ubicación centralizada en la cual puedes encontrar todos los eventos de formulario disponibles. - -Si bien este ejemplo podría haber utilizado eficientemente el evento ``form.post_set_data``, al usar ``form.pre_set_data`` garantizas que el dato recuperado desde el objeto ``Event`` no tiene manera alguna de ser modificado por ningún otro suscriptor o escucha debido a que ``form.pre_set_data`` es el primer evento remitido por el formulario. - -.. note:: - - Puedes ver la lista completa de eventos de formulario vía la `clase FormEvents`_, del paquete ``form``. - -.. _`clase FormEvents`: https://github.com/symfony/Form/blob/master/FormEvents.php -.. _`clase Form`: https://github.com/symfony/symfony/blob/master/src/Symfony/Component/Form/Form.php diff --git a/_sources/cookbook/form/dynamic_form_modification.txt b/_sources/cookbook/form/dynamic_form_modification.txt deleted file mode 100644 index 61f4f98..0000000 --- a/_sources/cookbook/form/dynamic_form_modification.txt +++ /dev/null @@ -1,609 +0,0 @@ -.. index:: - single: Formulario; Eventos - -How to Dynamically Modify Forms Using Form Events -================================================= - -Often times, a form can't be created statically. In this entry, you'll learn -how to customize your form based on three common use-cases: - -1) :ref:`cookbook-form-events-underlying-data` - -Ejemplo: you have a "Product" form and need to modify/add/remove a field -based on the data on the underlying Product being edited. - -2) :ref:`cookbook-form-events-user-data` - -Ejemplo: you create a "Friend Message" form and need to build a drop-down -that contains only users that are friends with the *current* authenticated -user. - -3) :ref:`cookbook-form-events-submitted-data` - -Ejemplo: on a registration form, you have a "country" field and a "state" -field which should populate dynamically based on the value in the "country" -field. - -.. _cookbook-form-events-underlying-data: - -Customizing your Form based on the underlying Data --------------------------------------------------- - -Antes de zambullirte en la generación dinámica de formularios, hagamos una rápida revisión de lo que es una clase formulario desnuda:: - - // src/Acme/DemoBundle/Form/Type/ProductType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class ProductType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('name'); - $builder->add('price'); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\DemoBundle\Entity\Product' - )); - } - - public function getName() - { - return 'product'; - } - } - -.. note:: - - Si esta sección de código en particular no te es familiar, probablemente necesites dar un paso atrás y en primer lugar revisar el :doc:`Capítulo de formularios ` antes de continuar. - -Assume for a moment that this form utilizes an imaginary "Product" class -that has only two properties ("name" and "price"). The form generated from -this class will look the exact same regardless if a new Product is being created -or if an existing product is being edited (e.g. a product fetched from the database). - -Ahora, supongamos que no deseas que el usuario pueda cambiar el valor del ``name`` una vez creado el objeto. Para ello, puedes confiar en el sistema :doc:`Despachador de eventos ` de *Symfony* para analizar los datos en el objeto y modificar el formulario basándote en los datos del objeto ``Producto``. En este artículo, aprenderás cómo añadir este nivel de flexibilidad a tus formularios. - -.. _`cookbook-forms-event-subscriber`: - -Adding An Event Subscriber To A Form Class -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Por lo tanto, en lugar de añadir directamente el elemento gráfico ``name`` vía tu clase formulario ``ProductType``, vas a delegar la responsabilidad de crear este campo en particular a un suscriptor de evento:: - - // src/Acme/DemoBundle/Form/Type/ProductType.php - namespace Acme\DemoBundle\Form\Type; - - // ... - use Acme\DemoBundle\Form\EventListener\AddNameFieldSubscriber; - - class ProductType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('price'); - - $builder->addEventSubscriber(new AddNameFieldSubscriber()); - } - - // ... - } - -.. _`cookbook-forms-inside-subscriber-class`: - -Inside the Event Subscriber Class -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -El objetivo es crear el campo ``«name»`` *únicamente* si el objeto ``Producto`` subyacente es nuevo (por ejemplo, no se ha persistido a la base de datos). Basándose en esto, el suscriptor podría tener la siguiente apariencia: - -.. versionadded:: 2.2 - La habilidad de pasar una cadena al método :method:`FormInterface::add ` se añadió en *Symfony 2.2*. - -.. code-block:: php - - // src/Acme/DemoBundle/Form/EventListener/AddNameFieldSubscriber.php - namespace Acme\DemoBundle\Form\EventListener; - - use Symfony\Component\Form\FormEvent; - use Symfony\Component\Form\FormEvents; - use Symfony\Component\EventDispatcher\EventSubscriberInterface; - - class AddNameFieldSubscriber implements EventSubscriberInterface - { - public static function getSubscribedEvents() - { - // Informa al despachador que deseas escuchar el evento - // form.pre_set_data y se debe llamar al método 'preSetData'. - return array(FormEvents::PRE_SET_DATA => 'preSetData'); - } - - public function preSetData(FormEvent $event) - { - $data = $event->getData(); - $form = $event->getForm(); - - // check if the product object is "new" - // If you didn't pass any data to the form, the data is "null". - // This should be considered a new "Product" - if (!$data || !$data->getId()) { - $form->add('name', 'text'); - } - } - } - -.. tip:: - - The ``FormEvents::PRE_SET_DATA`` line actually resolves to the string - ``form.pre_set_data``. :class:`Symfony\\Component\\Form\\FormEvents` serves - an organizational purpose. It is a centralized location in which you can - find all of the various form events available. - -.. note:: - - You can view the full list of form events via the :class:`Symfony\\Component\\Form\\FormEvents` - class. - -.. _cookbook-form-events-user-data: - -How to Dynamically Generate Forms based on user Data ----------------------------------------------------- - -Sometimes you want a form to be generated dynamically based not only on data -from the form but also on something else - like some data from the current user. -Suppose you have a social website where a user can only message people who -are his friends on the website. In this case, a "choice list" of whom to message -should only contain users that are the current user's friends. - -Creating the Form Type -~~~~~~~~~~~~~~~~~~~~~~ - -Using an event listener, your form might look like this:: - - // src/Acme/DemoBundle/Form/Type/FriendMessageFormType.php - namespace Acme\DemoBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\Form\FormEvents; - use Symfony\Component\Form\FormEvent; - use Symfony\Component\Security\Core\SecurityContext; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class FriendMessageFormType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('subject', 'text') - ->add('body', 'textarea') - ; - $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event){ - // ... add a choice list of friends of the current application user - }); - } - - public function getName() - { - return 'acme_friend_message'; - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - } - } - -The problem is now to get the current user and create a choice field that -contains only this user's friends. - -Luckily it is pretty easy to inject a service inside of the form. This can be -done in the constructor:: - - private $securityContext; - - public function __construct(SecurityContext $securityContext) - { - $this->securityContext = $securityContext; - } - -.. note:: - - You might wonder, now that you have access to the User (through the security - context), why not just use it directly in ``buildForm`` and omit the - event listener? This is because doing so in the ``buildForm`` method - would result in the whole form type being modified and not just this - one form instance. This may not usually be a problem, but technically - a single form type could be used on a single request to create many forms - or fields. - -Customizing the Form Type -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Now that you have all the basics in place you an take advantage of the ``securityContext`` -and fill in the listener logic:: - - // src/Acme/DemoBundle/FormType/FriendMessageFormType.php - - use Symfony\Component\Security\Core\SecurityContext; - use Doctrine\ORM\EntityRepository; - // ... - - class FriendMessageFormType extends AbstractType - { - private $securityContext; - - public function __construct(SecurityContext $securityContext) - { - $this->securityContext = $securityContext; - } - - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('subject', 'text') - ->add('body', 'textarea') - ; - - // grab the user, do a quick sanity check that one exists - $user = $this->securityContext->getToken()->getUser(); - if (!$user) { - throw new \LogicException( - 'The FriendMessageFormType cannot be used without an authenticated user!' - ); - } - - $factory = $builder->getFormFactory(); - - $builder->addEventListener( - FormEvents::PRE_SET_DATA, - function(FormEvent $event) use($user, $factory){ - $form = $event->getForm(); - - $formOptions = array( - 'class' => 'Acme\DemoBundle\Entity\User', - 'multiple' => false, - 'expanded' => false, - 'property' => 'fullName', - 'query_builder' => function(EntityRepository $er) use ($user) { - // build a custom query, or call a method on your repository (even better!) - }, - ); - - // create the field, this is similar the $builder->add() - // field name, field type, data, options - $form->add($factory->createNamed('friend', 'entity', null, $formOptions)); - } - ); - } - - // ... - } - -Using the Form -~~~~~~~~~~~~~~ - -Our form is now ready to use and there are two possible ways to use it inside -of a controller: - -a) create it manually and remember to pass the security context to it; - -o - -b) define it as a service. - -a) Creating the Form manually -............................. - -This is very simple, and is probably the better approach unless you're using -your new form type in many places or embedding it into other forms:: - - class FriendMessageController extends Controller - { - public function newAction(Request $request) - { - $securityContext = $this->container->get('security.context'); - $form = $this->createForm( - new FriendMessageFormType($securityContext) - ); - - // ... - } - } - -b) Defining the Form as a Service -................................. - -To define your form as a service, just create a normal service and then tag -it with :ref:`dic-tags-form-type`. - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - services: - acme.form.friend_message: - class: Acme\DemoBundle\Form\Type\FriendMessageFormType - arguments: [@security.context] - tags: - - - name: form.type - alias: acme_friend_message - - .. code-block:: xml - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $definition = new Definition('Acme\DemoBundle\Form\Type\FriendMessageFormType'); - $definition->addTag('form.type', array('alias' => 'acme_friend_message')); - $container->setDefinition( - 'acme.form.friend_message', - $definition, - array('security.context') - ); - -If you wish to create it from within a controller or any other service that has -access to the form factory, you then use:: - - class FriendMessageController extends Controller - { - public function newAction(Request $request) - { - $form = $this->createForm('acme_friend_message'); - - // ... - } - } - -You can also easily embed the form type into another form:: - - // inside some other "form type" class - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('message', 'acme_friend_message'); - } - -.. _cookbook-form-events-submitted-data: - -Dynamic generation for submitted Forms --------------------------------------- - -Another case that can appear is that you want to customize the form specific to -the data that was submitted by the user. For example, imagine you have a registration -form for sports gatherings. Some events will allow you to specify your preferred -position on the field. This would be a ``choice`` field for example. However the -possible choices will depend on each sport. Football will have attack, defense, -goalkeeper etc... Baseball will have a pitcher but will not have goalkeeper. You -will need the correct options to be set in order for validation to pass. - -The meetup is passed as an entity hidden field to the form. So we can access each -sport like this:: - - // src/Acme/DemoBundle/Form/Type/SportMeetupType.php - class SportMeetupType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder - ->add('number_of_people', 'text') - ->add('discount_coupon', 'text') - ; - $factory = $builder->getFormFactory(); - - $builder->addEventListener( - FormEvents::PRE_SET_DATA, - function(FormEvent $event) use($user, $factory){ - $form = $event->getForm(); - - // this would be your entity, i.e. SportMeetup - $data = $event->getData(); - - $positions = $data->getSport()->getAvailablePositions(); - - // ... proceed with customizing the form based on available positions - } - ); - } - } - -When you're building this form to display to the user for the first time, -then this example works perfectly. - -However, things get more difficult when you handle the form submission. This -is be cause the ``PRE_SET_DATA`` event tells us the data that you're starting -with (e.g. an empty ``SportMeetup`` object), *not* the submitted data. - -On a form, we can usually listen to the following events: - -* ``PRE_SET_DATA`` -* ``POST_SET_DATA`` -* ``PRE_BIND`` -* ``BIND`` -* ``POST_BIND`` - -When listening to ``BIND`` and ``POST_BIND``, it's already "too late" to make -changes to the form. Fortunately, ``PRE_BIND`` is perfect for this. There -is, however, a big difference in what ``$event->getData()`` returns for each -of these events. Specifically, in ``PRE_BIND``, ``$event->getData()`` returns -the raw data submitted by the user. - -This can be used to get the ``SportMeetup`` id and retrieve it from the database, -given you have a reference to the object manager (if using doctrine). In -the end, you have an event subscriber that listens to two different events, -requires some external services and customizes the form. In such a situation, -it's probably better to define this as a service rather than using an anonymouse -function as the event listener callback. - -The subscriber would now look like:: - - // src/Acme/DemoBundle/Form/EventListener/RegistrationSportListener.php - namespace Acme\DemoBundle\Form\EventListener; - - use Symfony\Component\Form\FormFactoryInterface; - use Doctrine\ORM\EntityManager; - use Symfony\Component\Form\FormEvent; - - class RegistrationSportListener implements EventSubscriberInterface - { - /** - * @var FormFactoryInterface - */ - private $factory; - - /** - * @var EntityManager - */ - private $om; - - /** - * @param factory FormFactoryInterface - */ - public function __construct(FormFactoryInterface $factory, EntityManager $om) - { - $this->factory = $factory; - $this->om = $om; - } - - public static function getSubscribedEvents() - { - return array( - FormEvents::PRE_BIND => 'preBind', - FormEvents::PRE_SET_DATA => 'preSetData', - ); - } - - /** - * @param event FormEvent - */ - public function preSetData(FormEvent $event) - { - $meetup = $event->getData()->getMeetup(); - - // Before binding the form, the "meetup" will be null - if (null === $meetup) { - return; - } - - $form = $event->getForm(); - $positions = $meetup->getSport()->getPositions(); - - $this->customizeForm($form, $positions); - } - - public function preBind(FormEvent $event) - { - $data = $event->getData(); - $id = $data['event']; - $meetup = $this->om - ->getRepository('AcmeDemoBundle:SportMeetup') - ->find($id); - - if ($meetup === null) { - $msg = 'The event %s could not be found for you registration'; - throw new \Exception(sprintf($msg, $id)); - } - $form = $event->getForm(); - $positions = $meetup->getSport()->getPositions(); - - $this->customizeForm($form, $positions); - } - - protected function customizeForm($form, $positions) - { - // ... customize the form according to the positions - } - } - -You can see that you need to listen on these two events and have different callbacks -only because in two different scenarios, the data that you can use is given in a -different format. Other than that, this class always performs exactly the same -things on a given form. - -Now that you have that setup, register your form and the listener as services: - -.. configuration-block:: - - .. code-block:: yaml - - # app/config/config.yml - acme.form.sport_meetup: - class: Acme\SportBundle\Form\Type\SportMeetupType - arguments: [@acme.form.meetup_registration_listener] - tags: - - { name: form.type, alias: acme_meetup_registration } - acme.form.meetup_registration_listener - class: Acme\SportBundle\Form\EventListener\RegistrationSportListener - arguments: [@form.factory, @doctrine] - - .. code-block:: xml - - - - - - - - - - - - - - .. code-block:: php - - // app/config/config.php - $definition = new Definition('Acme\SportBundle\Form\Type\SportMeetupType'); - $definition->addTag('form.type', array('alias' => 'acme_meetup_registration')); - $container->setDefinition( - 'acme.form.meetup_registration_listener', - $definition, - array('security.context') - ); - $definition = new Definition('Acme\SportBundle\Form\EventListener\RegistrationSportListener'); - $container->setDefinition( - 'acme.form.meetup_registration_listener', - $definition, - array('form.factory', 'doctrine') - ); - -In this setup, the ``RegistrationSportListener`` will be a constructor argument -to ``SportMeetupType``. You can then register it as an event subscriber on -your form:: - - private $registrationSportListener; - - public function __construct(RegistrationSportListener $registrationSportListener) - { - $this->registrationSportListener = $registrationSportListener; - } - - public function buildForm(FormBuilderInterface $builder, array $options) - { - // ... - $subscriber = new AddNameFieldSubscriber($builder->getFormFactory()); - $builder->addEventSubscriber($this->registrationSportListener); - } - -And this should tie everything together. You can now retrieve your form from the -controller, display it to a user, and validate it with the right choice options -set for every possible kind of sport that our users are registering for. - -One piece that may still be missing is the client-side updating of your form -after the sport is selected. This should be handled by making an AJAX call -back to your application. In that controller, you can bind your form, but -instead of processing it, simply use the bound form to render the updated -fields. The response from the AJAX call can then be used to update the view. diff --git a/_sources/cookbook/form/form_collections.txt b/_sources/cookbook/form/form_collections.txt deleted file mode 100644 index f97e0fd..0000000 --- a/_sources/cookbook/form/form_collections.txt +++ /dev/null @@ -1,604 +0,0 @@ -.. index:: - single: Formulario; Integrar colección de formularios - -Cómo integrar una colección de formularios -========================================== - -En este artículo, aprenderás cómo crear un formulario que integra una colección de muchos otros formularios. Esto podría ser útil, por ejemplo, si tienes una clase ``Tarea`` y quieres crear/editar/eliminar muchos objetos ``Etiqueta`` relacionados con esa ``Tarea``, justo dentro del mismo formulario. - -.. note:: - - En este artículo, vagamente asume que estás utilizando *Doctrine* como almacén de base de datos. Pero si no estás usando *Doctrine* (por ejemplo, *Propel* o simplemente una conexión directa a la base de datos), es casi lo mismo. Sólo hay unas cuantas partes de esta guía que realmente se preocupan de la «persistencia». - - Si *estás* utilizando *Doctrine*, tienes que añadir los metadatos de *Doctrine*, incluyendo el asociación ``MuchosAMuchos`` en la definición de asignación en la propiedad ``tags`` de la Tarea. - -Vamos a empezar por ahí: Supongamos que cada ``Tarea`` pertenece a múltiples objetos ``Tags``. Empieza creando una sencilla clase ``Tarea``:: - - // src/Acme/TaskBundle/Entity/Task.php - namespace Acme\TaskBundle\Entity; - - use Doctrine\Common\Collections\ArrayCollection; - - class Task - { - protected $description; - - protected $tags; - - public function __construct() - { - $this->tags = new ArrayCollection(); - } - - public function getDescription() - { - return $this->description; - } - - public function setDescription($description) - { - $this->description = $description; - } - - public function getTags() - { - return $this->tags; - } - - public function setTags(ArrayCollection $tags) - { - $this->tags = $tags; - } - } - -.. note:: - - El ``ArrayCollection`` es específico de *Doctrine* y básicamente es lo mismo que usar un ``arreglo`` (pero este debe ser un ``ArrayCollection``) si estás usando *Doctrine*. - -Ahora, crea una clase ``Etiqueta``. Cómo vimos arriba, una ``Tarea`` puede tener muchos objetos ``Etiqueta``:: - - // src/Acme/TaskBundle/Entity/Tag.php - namespace Acme\TaskBundle\Entity; - - class Tag - { - public $name; - } - -.. tip:: - - Aquí, la propiedad ``name`` es pública, pero fácilmente puede ser protegida o privada (pero entonces necesitaríamos métodos ``getName`` y ``setName``). - -Ahora veamos los formularios. Crea una clase formulario para que el usuario pueda modificar un objeto ``Tag``:: - - // src/Acme/TaskBundle/Form/Type/TagType.php - namespace Acme\TaskBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class TagType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('name'); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Tag', - )); - } - - public function getName() - { - return 'tag'; - } - } - -Con esto, tienes suficiente para que una etiqueta de formulario se dibuje por sí misma. Pero debido a que el objetivo final es permitir que las etiquetas de una ``Tarea`` sean modificadas directamente dentro del formulario de la tarea en sí mismo, crea un formulario para la clase ``Tarea``. - -Ten en cuenta que integraste una colección de formularios ``TagType`` usando el tipo de campo :doc:`collection `:: - - // src/Acme/TaskBundle/Form/Type/TaskType.php - namespace Acme\TaskBundle\Form\Type; - - use Symfony\Component\Form\AbstractType; - use Symfony\Component\Form\FormBuilderInterface; - use Symfony\Component\OptionsResolver\OptionsResolverInterface; - - class TaskType extends AbstractType - { - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('description'); - - $builder->add('tags', 'collection', array('type' => new TagType())); - } - - public function setDefaultOptions(OptionsResolverInterface $resolver) - { - $resolver->setDefaults(array( - 'data_class' => 'Acme\TaskBundle\Entity\Task', - )); - } - - public function getName() - { - return 'task'; - } - } - -En tu controlador, ahora tendrás que iniciar una nueva instancia de ``TaskType``:: - - // src/Acme/TaskBundle/Controller/TaskController.php - namespace Acme\TaskBundle\Controller; - - use Acme\TaskBundle\Entity\Task; - use Acme\TaskBundle\Entity\Tag; - use Acme\TaskBundle\Form\Type\TaskType; - use Symfony\Component\HttpFoundation\Request; - use Symfony\Bundle\FrameworkBundle\Controller\Controller; - - class TaskController extends Controller - { - public function newAction(Request $request) - { - $task = new Task(); - - // código ficticio - esto está aquí sólo para que la tarea tenga algunas - // etiquetas, de lo contrario, este no sería un ejemplo interesante - $tag1 = new Tag(); - $tag1->name = 'tag1'; - $task->getTags()->add($tag1); - $tag2 = new Tag(); - $tag2->name = 'tag2'; - $task->getTags()->add($tag2); - // termina el código maniquí - - $form = $this->createForm(new TaskType(), $task); - - // procesa el formulario en POST - if ($request->isMethod('POST')) { - $form->bind($request); - if ($form->isValid()) { - // posiblemente hagas algún procesamiento del formulario, - // tal como guardar los objetos Task y Tag - } - } - - return $this->render('AcmeTaskBundle:Task:new.html.twig', array( - 'form' => $form->createView(), - )); - } - } - -La plantilla correspondiente, ahora es capaz de reproducir tanto el campo ``descripción`` del formulario de ``Tarea``, así como todos los formularios ``TagType`` de las etiquetas que ya están relacionados con esta ``Tarea``. En el controlador anterior, agregamos cierto código ficticio para poder ver esto en acción (debido a que una ``tarea`` tiene cero etiquetas al crearla por primera vez). - -.. configuration-block:: - - .. code-block:: html+jinja - - {# src/Acme/TaskBundle/Resources/views/Task/new.html.twig #} - - {# ... #} - -
    - {# reproduce únicamente los campos task: descripción #} - {{ form_row(form.description) }} - -

    Tags

    -
      - {# itera sobre cada etiqueta existente y reproduce su único campo: name #} - {% for tag in form.tags %} -
    • {{ form_row(tag.name) }}
    • - {% endfor %} -
    - - {{ form_rest(form) }} - {# ... #} -
    - - .. code-block:: html+php - - - - - -
    -

    Tags

    -
      - -
    • row($tag['name']) ?>
    • - -
    - - rest($form) ?> -
    - - - -Cuando el usuario envía el formulario, los datos presentados por los campos ``Tag`` se utilizan para construir un ``ArrayCollection`` de los objetos ``Tag``, que luego se establecen en el campo ``tag`` de la instancia ``Tarea``. - -La colección ``Tags``, naturalmente, es accesible a través de ``$task->getTags()`` y se puede persistir en la base de datos o utilizar donde sea necesaria. - -Hasta el momento, esto funciona muy bien, pero aún no te permite agregar dinámicamente nuevas etiquetas o eliminar existentes. Por lo tanto, durante la edición de etiquetas existentes funcionará bien, tu usuario en realidad no puede añadir ninguna nueva etiqueta, todavía. - - -.. caution:: - - En esta entrada, integraste una sola colección, pero de ninguna manera estás limitado a esto. También puedes integrar colecciones anidadas con tantos niveles descendientes como quieras. Pero si utilizas *XDebug* en tu configuración de desarrollo, puedes recibir un error ``La función alcanzó el máximo nivel de anidamiento de «100», ¡abortando!``. - Esto se debe a la opción ``xdebug.max_nesting_level`` de *PHP*, que por omisión es de ``100``. - - Esta directiva limita la recursividad a 100 llamadas que pueden no ser suficientes para reproducir el formulario en la plantilla si pintas todo el formulario de una vez (por ejemplo, ``form_widget(form)``). Para solucionar este problema puedes redefinir esta directiva a un valor más alto (ya sea a través de un archivo ``ini`` de *PHP* o por medio de :phpfunction:`ini_set`, por ejemplo, en ``app/autoload.php``) o pintar cada campo del formulario a mano usando ``form_row``. - -.. _cookbook-form-collections-new-prototype: - -Permitiendo «nuevas» etiquetas con «prototype» ----------------------------------------------- - -Permitir al usuario añadir nuevas etiquetas dinámicamente significa que necesitarás usar algo de *JavaScript*. Anteriormente añadiste dos etiquetas a tu formulario en el controlador. -Now let the user add as many tag forms as he needs directly in the browser. -Esto se hará a través de un poco de *JavaScript*. - -Lo primero que tienes que hacer es darle a conocer la colección del formulario que va a recibir una cantidad desconocida de etiquetas. Hasta ahora, añadiste dos etiquetas y el tipo de formulario espera recibir exactamente dos, de lo contrario lanzará un error: -``Este formulario no debe contener campos adicionales``. Para que esto sea flexible, añade la opción ``allow_add`` a tu campo colección:: - - // src/Acme/TaskBundle/Form/Type/TaskType.php - - // ... - - use Symfony\Component\Form\FormBuilderInterface; - - public function buildForm(FormBuilderInterface $builder, array $options) - { - $builder->add('description'); - - $builder->add('tags', 'collection', array( - 'type' => new TagType(), - 'allow_add' => true, - 'by_reference' => false, - )); - } - -Ten en cuenta que también se añadió ``'by_reference' => false``. Normalmente, la plataforma de formularios modificaría las etiquetas en un objeto ``Tarea`` *sin* llamar en realidad a ``setTags``. Al configurar :ref:`by_reference ` a ``false``, llamará a ``setTags``. Esto, como verás, será muy importante más adelante. - -Además de decirle al campo que acepte cualquier número de objetos presentados, ``allow_add`` también pone a tu disposición una variable «prototipo». Este «prototipo» es como una «plantilla» que contiene todo el código *HTML* necesario para poder pintar cualquier nueva ``etiqueta`` del formulario. Para ello, haz el siguiente cambio en tu plantilla: - -.. configuration-block:: - - .. code-block:: html+jinja - -
      - ... -
    - - .. code-block:: html+php - -
      - ... -
    - -.. note:: - - Si pintas todas tus ``etiquetas`` en subformularios simultáneamente (por ejemplo, ``form_row(form.tags)``), entonces el prototipo estará disponible automáticamente en el ``div`` externo como el atributo ``data-prototype``, similar a lo que ves arriba. - -.. tip:: - - The ``form.tags.vars.prototype`` is a form element that looks and feels just - like the individual ``form_widget(tag)`` elements inside your ``for`` loop. - This means that you can call ``form_widget``, ``form_row`` or ``form_label`` - on it. Incluso, puedes optar por pintar sólo uno de tus campos (por ejemplo, el campo ``nombre``): - - .. code-block:: html+jinja - - {{ form_widget(form.tags.vars.prototype.name)|e }} - -En la página producida, el resultado será muy parecido a este: - -.. code-block:: html - -
      - -El objetivo de esta sección es usar *JavaScript* para leer este atributo y agregar dinámicamente nuevas etiquetas al formulario cuando el usuario haga clic en un enlace «Agregar una etiqueta». -Para simplificar las cosas, este ejemplo usa *jQuery* y supone que lo has incluido en algún lugar de tu página. - -Añade un elemento `` - -.. tip:: - - Si estás reproduciendo la colección entera a la vez, entonces el prototipo automáticamente está disponible en el atributo ``data-prototype`` del elemento (por ejemplo ``div`` o ``table``) que rodea a tu colección. La única diferencia es que la «fila del formulario» entera es dibujada para ti, lo cual significa que no tendrás que envolverla en ningún elemento contenedor como se hizo arriba. - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``type`` -~~~~~~~~ - -**tipo**: ``string`` o :class:`Symfony\\Component\\Form\\FormTypeInterface` **requerido** - -Este es el tipo de campo para cada elemento de esta colección (por ejemplo, ``text``, ``choice``, etc.) Por ejemplo, si tienes un arreglo de direcciones de correo electrónico, debes usar el tipo :doc:`email `. Si quieres integrar una colección de algún otro formulario, crea una nueva instancia de tu tipo formulario y pásala como esta opción. - -``options`` -~~~~~~~~~~~ - -**tipo**: ``array`` **predeterminado**: ``array()`` - -Este es el arreglo que pasaste al tipo formulario especificado en la opción `type`_. Por ejemplo, si utilizaste el tipo :doc:`choice ` como tu opción `type`_ (por ejemplo, para una colección de menús desplegables), entonces necesitarás ---por lo menos--- pasar la opción ``choices`` al tipo subyacente:: - - $builder->add('favorite_cities', 'collection', array( - 'type' => 'choice', - 'options' => array( - 'choices' => array( - 'nashville' => 'Nashville', - 'paris' => 'Paris', - 'berlin' => 'Berlin', - 'london' => 'London', - ), - ), - )); - -``allow_add`` -~~~~~~~~~~~~~ - -**tipo**: ``Boolean`` **predeterminado**: ``false`` - -Si la fijas a ``true``, entonces si envías elementos no reconocidos a la colección, estos se añadirán cómo nuevos elementos. El arreglo final contendrá los elementos existentes así como el nuevo elemento que estaba en el dato entregado. Ve el ejemplo anterior para más detalles. - -Puedes usar la opción `prototype`_ para ayudarte a reproducir un elemento prototipo que puedes utilizar ---con *JavaScript*--- para crear dinámicamente nuevos elementos en el cliente. Para más información, ve el ejemplo anterior y :ref:`cookbook-form-collections-new-prototype`. - -.. caution:: - - Si estás integrando otros formularios enteros para reflejar una relación de base de datos uno-a-muchos, posiblemente necesites asegurarte manualmente que la llave externa de estos nuevos objetos se establece correctamente. Si estás utilizando *Doctrine*, esto no sucederá automáticamente. Ve el enlace anterior para más detalles. - -``allow_delete`` -~~~~~~~~~~~~~~~~ - -**tipo**: ``Boolean`` **predeterminado**: ``false`` - -Si lo fijas a ``true``, entonces si un elemento existente no está contenido en el dato entregado, correctamente estará ausente del arreglo de los elementos finales. Esto significa que puedes implementar un botón «eliminar» vía *JavaScript* el cuál sencillamente quita un elemento del *DOM* del formulario. Cuándo el usuario envía el formulario, la ausencia del dato entregado significará que fue quitado del arreglo final. - -Para más información, ve :ref:`cookbook-form-collections-remove`. - -.. caution:: - - Se prudente cuándo utilices esta opción al integrar una colección de objetos. En este caso, si se retira algún formulario incrustado, este se *debería* olvidar correctamente en el arreglo de objetos final. Aun así, dependiendo de la lógica de tu aplicación, cuándo uno de estos objetos es removido, posiblemente quieras eliminarlo o al menos quitar la referencia de la clave externa del objeto principal. - Nada de esto se maneja automáticamente. Para más información, ve :ref:`cookbook-form-collections-remove`. - -``prototype`` -~~~~~~~~~~~~~ - -**tipo**: ``Boolean`` **predeterminado**: ``true`` - -Esta opción es útil cuando utilizas la opción `allow_add`_. Si es ``true`` (y si además `allow_add`_ también es ``true``), un atributo ``«prototype»`` especial estará disponible a modo de que puedas reproducir una ``«plantilla»`` de ejemplo en la página en que aparecerá un nuevo elemento. El atributo ``name`` dado a este elemento es ``__name__``. Esto te permite añadir un botón «añadir otro» vía *JavaScript* el cuál lee el prototipo, reemplaza ``__name__`` con algún nombre o número único y lo reproduce dentro de tu formulario. Cuándo se envía, este se añade a tu arreglo subyacente gracias a la opción `allow_add`_. - -El campo prototipo se puede pintar vía la variable ``prototype`` del campo ``colección``: - -.. configuration-block:: - - .. code-block:: jinja - - {{ form_row(form.emails.vars.prototype) }} - - .. code-block:: php - - row($form['emails']->vars['prototype']) ?> - -Ten en cuenta que todo lo que realmente necesitas es el «elemento gráfico», pero dependiendo del formulario que estés reproduciendo, puede ser más fácil para ti pintar la «fila del formulario» entera. - -.. tip:: - - Si estás reproduciendo el campo ``colección`` entero a la vez, entonces el prototipo de la fila del formulario automáticamente está disponible en el atributo ``data-prototype`` del elemento (por ejemplo ``div`` o ``table``) que envuelve tu colección. - -Para obtener más información sobre como utilizar esta opción, ve el ejemplo anterior, así como :ref:`cookbook-form-collections-new-prototype`. - -``prototype_name`` -~~~~~~~~~~~~~~~~~~ - -.. versionadded:: 2.1 - La opción ``prototype_name`` se agregó en *Symfony 2.1* - -**tipo**: ``string`` **predefinido**: ``__name__`` - -Si tienes varias colecciones en tu formulario, o peor aún, colecciones anidadas posiblemente quieras cambiar el marcador de posición a modo que los marcadores de posición no relacionados no sean reemplazados con el mismo valor. - -Opciones heredadas ------------------- - -Estas opciones heredan del tipo :doc:`field `. -No todas las opciones figuran en esta lista ---solo las aplicables a este tipo: - -.. include:: /reference/forms/types/options/label.rst.inc - -``error_bubbling`` -~~~~~~~~~~~~~~~~~~ - -**tipo**: ``Boolean`` **predeterminado**: ``true`` - -.. include:: /reference/forms/types/options/_error_bubbling_body.rst.inc - -.. _reference-form-types-by-reference: - -.. include:: /reference/forms/types/options/by_reference.rst.inc - -.. include:: /reference/forms/types/options/empty_data.rst.inc diff --git a/_sources/reference/forms/types/country.txt b/_sources/reference/forms/types/country.txt deleted file mode 100644 index 4cd7eb1..0000000 --- a/_sources/reference/forms/types/country.txt +++ /dev/null @@ -1,58 +0,0 @@ -.. index:: - single: Formularios; Campos: country - -Tipo de campo ``country`` -========================= - -El tipo ``country`` es un subconjunto de ``ChoiceType`` que muestra los países del mundo. Como bono adicional, los nombres de los países se muestran en el idioma del usuario. - -El «valor» para cada país es el código de país de dos letras. - -.. note:: - - La configuración regional del usuario se deduce usando :phpmethod:`Locale::getDefault`. - -A diferencia del tipo ``choice``, no es necesario especificar una opción ``choices`` o ``choice_list`` como el tipo de campo utilizando automáticamente todos los países del mundo. *Puedes* especificar cualquiera de estas opciones manualmente, pero entonces sólo debes utilizar el tipo ``choice`` directamente. - -+-------------+-----------------------------------------------------------------------+ -| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | -+-------------+-----------------------------------------------------------------------+ -| Inherited | - `multiple`_ | -| options | - `expanded`_ | -| | - `preferred_choices`_ | -| | - `empty_value`_ | -| | - `error_bubbling`_ | -| | - `required`_ | -| | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -+-------------+-----------------------------------------------------------------------+ -| Parent type | :doc:`choice` | -+-------------+-----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\CountryType` | -+-------------+-----------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`choice`: - -.. include:: /reference/forms/types/options/multiple.rst.inc - -.. include:: /reference/forms/types/options/expanded.rst.inc - -.. include:: /reference/forms/types/options/preferred_choices.rst.inc - -.. include:: /reference/forms/types/options/empty_value.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/csrf.txt b/_sources/reference/forms/types/csrf.txt deleted file mode 100644 index fc000b0..0000000 --- a/_sources/reference/forms/types/csrf.txt +++ /dev/null @@ -1,39 +0,0 @@ -.. index:: - single: Formularios; Campos: csrf - -Tipo de campo ``csrf`` -====================== - -El tipo ``csrf`` es un campo de entrada oculto que contiene un fragmento *CSRF*. - -+----------------+--------------------------------------------------------------------+ -| Dibujado como | campo ``input`` ``hidden`` | -+----------------+--------------------------------------------------------------------+ -| Opciones | - ``csrf_provider`` | -| | - ``intention`` | -| | - ``property_path`` | -+----------------+--------------------------------------------------------------------+ -| Tipo del padre | ``hidden`` | -+----------------+--------------------------------------------------------------------+ -| Clase | :class:`Symfony\\Component\\Form\\Extension\\Csrf\\Type\\CsrfType` | -+----------------+--------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``csrf_provider`` -~~~~~~~~~~~~~~~~~ - -**tipo**: ``Symfony\Component\Form\CsrfProvider\CsrfProviderInterface`` - -El objeto ``CsrfProviderInterface`` que debe generar la ficha *CSRF*. -Si no se establece, el valor predeterminado es el proveedor predeterminado. - -intención -~~~~~~~~~ - -**tipo**: ``string`` - -Un opcional identificador único que se utiliza para generar la ficha *CSRF*. - -.. include:: /reference/forms/types/options/property_path.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/date.txt b/_sources/reference/forms/types/date.txt deleted file mode 100644 index ab5ae8b..0000000 --- a/_sources/reference/forms/types/date.txt +++ /dev/null @@ -1,119 +0,0 @@ -.. index:: - single: Formularios; Campos: date - -Tipo de campo ``date`` -====================== - -Un campo que permite al usuario modificar información de fecha a través de una variedad de diferentes elementos *HTML*. - -Los datos subyacentes utilizados para este tipo de campo pueden ser un objeto ``DateTime``, una cadena, una marca de tiempo o un arreglo. Siempre y cuando la opción `input`_ se configure correctamente, el campo se hará cargo de todos los detalles. - -El campo se puede reproducir como un cuadro de texto, tres cuadros de texto (mes, día y año) o tres cuadros de selección (ve la opción `widget`_). - -+----------------------+-----------------------------------------------------------------------------+ -| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | -+----------------------+-----------------------------------------------------------------------------+ -| Rendered as | single text box or three select fields | -+----------------------+-----------------------------------------------------------------------------+ -| Options | - `widget`_ | -| | - `input`_ | -| | - `empty_value`_ | -| | - `years`_ | -| | - `months`_ | -| | - `days`_ | -| | - `format`_ | -| | - `data_timezone`_ | -| | - `user_timezone`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Inherited | - `invalid_message`_ | -| options | - `invalid_message_parameters`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `virtual`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Parent type | ``field`` (if text), ``form`` otherwise | -+----------------------+-----------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateType` | -+----------------------+-----------------------------------------------------------------------------+ - -Uso básico ----------- - -Este tipo de campo es altamente configurable, pero fácil de usar. Las opciones más importantes son ``input`` y ``widget``. - -Supongamos que tienes un campo ``publishedAt`` cuya fecha subyacente es un objeto ``DateTime``. El siguiente código configura el tipo ``date`` para ese campo como tres campos de opciones diferentes: - -.. code-block:: php - - $builder->add('publishedAt', 'date', array( - 'input' => 'datetime', - 'widget' => 'choice', - )); - -La opción ``input`` se *debe* cambiar para que coincida con el tipo de dato de la fecha subyacente. Por ejemplo, si los datos del campo ``publishedAt`` eran una marca de tiempo Unix, habría la necesidad de establecer ``input`` a ``timestamp``: - -.. code-block:: php - - $builder->add('publishedAt', 'date', array( - 'input' => 'timestamp', - 'widget' => 'choice', - )); - -El campo también es compatible con ``array`` y ``string`` como valores válidos de la opción ``input``. - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -.. include:: /reference/forms/types/options/date_widget.rst.inc - -.. _form-reference-date-input: - -.. include:: /reference/forms/types/options/date_input.rst.inc - -``empty_value`` -~~~~~~~~~~~~~~~ - -**tipo**: ``string`` o ``array`` - -Si la opción de elemento gráfico se ajusta a ``choice``, entonces este campo se reproduce como una serie de cajas de ``selección``. Puedes utilizar la opción ``empty_value`` para agregar una entrada «en blanco» en la parte superior de cada caja de selección:: - - $builder->add('dueDate', 'date', array( - 'empty_value' => '', - )); - -Alternativamente, puedes especificar una cadena que se mostrará en lugar del valor «en blanco»:: - - $builder->add('dueDate', 'date', array( - 'empty_value' => array('year' => 'Year', 'month' => 'Month', 'day' => 'Day') - )); - -.. include:: /reference/forms/types/options/years.rst.inc - -.. include:: /reference/forms/types/options/months.rst.inc - -.. include:: /reference/forms/types/options/days.rst.inc - -.. _reference-forms-type-date-format: - -.. include:: /reference/forms/types/options/date_format.rst.inc - -.. include:: /reference/forms/types/options/data_timezone.rst.inc - -.. include:: /reference/forms/types/options/user_timezone.rst.inc - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -These options inherit from the :doc:`date` type: - -.. include:: /reference/forms/types/options/virtual.rst.inc diff --git a/_sources/reference/forms/types/datetime.txt b/_sources/reference/forms/types/datetime.txt deleted file mode 100644 index 1efd22c..0000000 --- a/_sources/reference/forms/types/datetime.txt +++ /dev/null @@ -1,113 +0,0 @@ -.. index:: - single: Formularios; Campos: datetime - -Tipo de campo ``datetime`` -========================== - -Este tipo de campo permite al usuario modificar los datos que representan una fecha y hora específica (por ejemplo, ``05/06/2011 12:15:30``). - -Se pueden reproducir como una entrada de texto o etiquetas de selección. El formato subyacente de los datos puede ser un objeto ``DateTime``, una cadena, una marca de tiempo o un arreglo. - -+----------------------+-----------------------------------------------------------------------------+ -| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | -+----------------------+-----------------------------------------------------------------------------+ -| Rendered as | single text box or three select fields | -+----------------------+-----------------------------------------------------------------------------+ -| Options | - `date_widget`_ | -| | - `time_widget`_ | -| | - `input`_ | -| | - `date_format`_ | -| | - `hours`_ | -| | - `minutes`_ | -| | - `seconds`_ | -| | - `years`_ | -| | - `months`_ | -| | - `days`_ | -| | - `with_seconds`_ | -| | - `data_timezone`_ | -| | - `user_timezone`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Inherited | - `invalid_message`_ | -| options | - `invalid_message_parameters`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `virtual`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Parent type | :doc:`form` | -+----------------------+-----------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\DateTimeType` | -+----------------------+-----------------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``date_widget`` -~~~~~~~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``choice`` - -Define la opción ``widget`` para el tipo :doc:`date ` - -``time_widget`` -~~~~~~~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``choice`` - -Define la opción ``widget`` para el tipo :doc:`time ` - -``input`` -~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``datetime`` - -El formato del dato *input* ---es decir, el formato de la fecha en que se almacena en el objeto subyacente. Los valores válidos son los siguientes: - -* ``string`` (por ejemplo ``2011-06-05 12:15:00``) -* ``datetime`` (un objeto ``DateTime``) -* ``array`` (por ejemplo ``array(2011, 06, 05, 12, 15, 0)``) -* ``timestamp`` (por ejemplo ``1307276100``) - -El valor devuelto por el formulario también se normaliza de nuevo a este formato. - -``date_format`` -~~~~~~~~~~~~~~~ - -**tipo**: ``integer`` o ``string`` **predefinido**: ``IntlDateFormatter::MEDIUM`` - -Define la opción ``format`` que se transmite al campo de fecha. -Consulta la :ref:`opción de formato para el tipo date ` para más detalles. - -.. include:: /reference/forms/types/options/hours.rst.inc - -.. include:: /reference/forms/types/options/minutes.rst.inc - -.. include:: /reference/forms/types/options/seconds.rst.inc - -.. include:: /reference/forms/types/options/years.rst.inc - -.. include:: /reference/forms/types/options/months.rst.inc - -.. include:: /reference/forms/types/options/days.rst.inc - -.. include:: /reference/forms/types/options/with_seconds.rst.inc - -.. include:: /reference/forms/types/options/data_timezone.rst.inc - -.. include:: /reference/forms/types/options/user_timezone.rst.inc - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -These options inherit from the :doc:`date` type: - -.. include:: /reference/forms/types/options/virtual.rst.inc diff --git a/_sources/reference/forms/types/email.txt b/_sources/reference/forms/types/email.txt deleted file mode 100644 index ab4cde0..0000000 --- a/_sources/reference/forms/types/email.txt +++ /dev/null @@ -1,42 +0,0 @@ -.. index:: - single: Formularios; Campos: email - -Tipo de campo ``email`` -======================= - -El campo ``email`` es un campo de texto que se reproduce usando etiquetas *HTML5* ````. - -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``email`` field (a text box) | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `max_length`_ | -| options | - `required`_ | -| | - `label`_ | -| | - `trim`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\EmailType` | -+-------------+---------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/max_length.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/entity.txt b/_sources/reference/forms/types/entity.txt deleted file mode 100644 index bca2755..0000000 --- a/_sources/reference/forms/types/entity.txt +++ /dev/null @@ -1,132 +0,0 @@ -.. index:: - single: Formularios; Campos: Choice - -Tipo de campo ``entity`` -======================== - -Un campo ``choice`` especial que está diseñado para cargar las opciones de una entidad *Doctrine*. Por ejemplo, si tiene una entidad ``Categoria``, puedes utilizar este campo para mostrar un campo ``select`` todos o algunos de los objetos ``Categoria`` de la base de datos. - -+-------------+------------------------------------------------------------------+ -| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | -+-------------+------------------------------------------------------------------+ -| Options | - `class`_ | -| | - `property`_ | -| | - `group_by`_ | -| | - `query_builder`_ | -| | - `em`_ | -+-------------+------------------------------------------------------------------+ -| Inherited | - `required`_ | -| options | - `label`_ | -| | - `multiple`_ | -| | - `expanded`_ | -| | - `preferred_choices`_ | -| | - `empty_value`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+------------------------------------------------------------------+ -| Parent type | :doc:`choice` | -+-------------+------------------------------------------------------------------+ -| Class | :class:`Symfony\\Bridge\\Doctrine\\Form\\Type\\EntityType` | -+-------------+------------------------------------------------------------------+ - -Uso básico ----------- - -El tipo ``entidad`` tiene una sola opción obligatoria: la entidad que debe aparecer dentro del campo de elección:: - - $builder->add('users', 'entity', array( - 'class' => 'AcmeHelloBundle:User', - 'property' => 'username', - )); - -En este caso, todos los objetos ``Usuario`` serán cargados desde la base de datos y representados como etiquetas ``select``, botones de radio o una serie de casillas de verificación (esto depende del valor ``multiple`` y ``expanded``). -Si el objeto entidad no tiene un método ``__toString()`` la opción ``property`` es necesaria. - -Usando una consulta personalizada para las Entidades -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Si es necesario especificar una consulta personalizada a utilizar al recuperar las entidades (por ejemplo, sólo deseas devolver algunas entidades, o necesitas ordenarlas), utiliza la opción ``query_builder``. La forma más fácil para utilizar la opción es la siguiente:: - - use Doctrine\ORM\EntityRepository; - // ... - - $builder->add('users', 'entity', array( - 'class' => 'AcmeHelloBundle:User', - 'query_builder' => function(EntityRepository $er) { - return $er->createQueryBuilder('u') - ->orderBy('u.username', 'ASC'); - }, - )); - -.. include:: /reference/forms/types/options/select_how_rendered.rst.inc - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``class`` -~~~~~~~~~ - -**tipo**: ``string`` **required** - -La clase de tu entidad (por ejemplo, ``AcmeStoreBundle:Category``). Este puede ser un nombre de clase completo (por ejemplo, ``Acme\StoreBundle\Entity\Category``) o el nombre del alias corto (como te mostramos anteriormente). - -``property`` -~~~~~~~~~~~~ - -**tipo**: ``string`` - -Esta es la propiedad que se debe utilizar para visualizar las entidades como texto en el elemento *HTML*. Si la dejas en blanco, el objeto entidad será convertido en una cadena y por lo tanto debe tener un método ``__toString()``. - -``group_by`` -~~~~~~~~~~~~ - -**tipo**: ``string`` - -Esta es una propiedad ruta (por ejemplo, ``author.name``) usada para organizar en grupos las opciones disponibles. Sólo funciona cuando se representa como una etiqueta de selección y lo hace añadiendo etiquetas ``optgroup`` en torno a las opciones. Las opciones que no devuelvan un valor para esta propiedad ruta se dibujan directamente debajo de la etiqueta de selección, sin un ``optgroup`` circundante. - -``query_builder`` -~~~~~~~~~~~~~~~~~ - -**tipo**: ``Doctrine\ORM\QueryBuilder`` o un ``Closure`` - -Si lo especificas, se utiliza para consultar el subconjunto de opciones (y el orden) que se debe utilizar para el campo. El valor de esta opción puede ser un objeto ``QueryBuilder`` o un Cierre. Si utilizas un Cierre, este debe tener un sólo argumento, el cual es el ``EntityRepository`` de la entidad. - -``em`` -~~~~~~ - -**tipo**: ``string`` **predefinido**: el gestor de la entidad - -Si lo especificas, el gestor de la entidad especificada se utiliza para cargar las opciones en lugar de las predeterminadas del gestor de la entidad. - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`choice`: - -.. include:: /reference/forms/types/options/multiple.rst.inc - -.. note:: - - If you are working with a collection of Doctrine entities, it will be helpful - to read the documention for the :doc:`/reference/forms/types/collection` - as well. In addition, there is a complete example in the cookbook article - :doc:`/cookbook/form/form_collections`. - -.. include:: /reference/forms/types/options/expanded.rst.inc - -.. include:: /reference/forms/types/options/preferred_choices.rst.inc - -.. include:: /reference/forms/types/options/empty_value.rst.inc - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/_sources/reference/forms/types/field.txt b/_sources/reference/forms/types/field.txt deleted file mode 100644 index e297292..0000000 --- a/_sources/reference/forms/types/field.txt +++ /dev/null @@ -1,8 +0,0 @@ -.. index:: - single: Formularios; Campos: field - -El tipo abstracto ``field`` -=========================== - -A partir de *Symfony 2.1* se desaconseja el uso del tipo ``field`` para formularios. -Por favor, en su lugar usa el :doc:`Tipo field del formulario `. diff --git a/_sources/reference/forms/types/file.txt b/_sources/reference/forms/types/file.txt deleted file mode 100644 index a3ba900..0000000 --- a/_sources/reference/forms/types/file.txt +++ /dev/null @@ -1,89 +0,0 @@ -.. index:: - single: Formularios; Campos: file - -Tipo de campo ``file`` -====================== - -El tipo ``file`` representa una entrada de archivo en tu formulario. - -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``file`` field | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `required`_ | -| options | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`form` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FileType` | -+-------------+---------------------------------------------------------------------+ - -Uso básico ----------- - -Digamos que tienes esta definición de formulario: - -.. code-block:: php - - $builder->add('attachment', 'file'); - -.. caution:: - - No olvides añadir el atributo ``enctype`` en la etiqueta del formulario: ``
      ``. - -Cuando se envía el formulario, el campo de datos adjuntos (``attchment``) será una instancia de :class:`Symfony\\Component\\HttpFoundation\\File\\UploadedFile`. Este lo puedes utilizar para mover el archivo ``adjunto`` a una ubicación permanente: - -.. code-block:: php - - use Symfony\Component\HttpFoundation\File\UploadedFile; - - public function uploadAction() - { - // ... - - if ($form->isValid()) { - $someNewFilename = ... - - $form['attachment']->getData()->move($dir, $someNewFilename); - - // ... - } - - // ... - } - -El método ``move()`` toma un directorio y un nombre de archivo como argumentos. -Puedes calcular el nombre de archivo en una de las siguientes formas:: - - // usa el nombre de archivo original - $file->move($dir, $file->getClientOriginalName()); - - // calcula un nombre aleatorio e intenta deducir la extensión (más seguro) - $extension = $file->guessExtension(); - if (!$extension) { - // no se puede deducir la extensión - $extension = 'bin'; - } - $file->move($dir, rand(1, 99999).'.'.$extension); - -Usar el nombre original a través de ``getClientOriginalName()`` no es seguro, ya que el usuario final lo podría haber manipulado. Además, puede contener caracteres que no están permitidos en nombres de archivo. Antes de usar el nombre directamente, lo debes desinfectar. - -Lee en el recetario el :doc:`ejemplo ` de cómo manejar la carga de archivos adjuntos con una entidad *Doctrine*. - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/form.txt b/_sources/reference/forms/types/form.txt deleted file mode 100644 index b020596..0000000 --- a/_sources/reference/forms/types/form.txt +++ /dev/null @@ -1,31 +0,0 @@ -.. index:: - single: Formularios; Campos: form - -Tipo de campo ``Form`` -====================== - -Consulta :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\FormType`. - -El tipo ``form`` predefine un par de opciones que luego estarán disponibles en todos los campos. - -.. include:: /reference/forms/types/options/data.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/constraints.rst.inc - -.. include:: /reference/forms/types/options/cascade_validation.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/mapped.rst.inc - -.. include:: /reference/forms/types/options/property_path.rst.inc - -.. include:: /reference/forms/types/options/attr.rst.inc - -.. include:: /reference/forms/types/options/translation_domain.rst.inc diff --git a/_sources/reference/forms/types/hidden.txt b/_sources/reference/forms/types/hidden.txt deleted file mode 100644 index 981a0e1..0000000 --- a/_sources/reference/forms/types/hidden.txt +++ /dev/null @@ -1,27 +0,0 @@ -.. index:: - single: Formularios; Campos: hidden - -Tipo de campo ``hidden`` -======================== - -El tipo ``hidden`` representa un campo de entrada oculto. - -+----------------+----------------------------------------------------------------------+ -| Dibujado como | campo ``input`` ``hidden`` | -+----------------+----------------------------------------------------------------------+ -| Opciones | - ``data`` | -| heredadas | - ``property_path`` | -+----------------+----------------------------------------------------------------------+ -| Tipo del padre | :doc:`field` | -+----------------+----------------------------------------------------------------------+ -| Clase | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\HiddenType` | -+----------------+----------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/data.rst.inc - -.. include:: /reference/forms/types/options/property_path.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/integer.txt b/_sources/reference/forms/types/integer.txt deleted file mode 100644 index 9fdbd63..0000000 --- a/_sources/reference/forms/types/integer.txt +++ /dev/null @@ -1,67 +0,0 @@ -.. index:: - single: Formularios; Campos: integer - -Tipo de campo ``integer`` -========================= - -Reproduce un campo de entrada para «número». Básicamente, se trata de un campo de texto que es bueno manejando datos enteros en un formulario. El campo de entrada ``number`` se parece a un cuadro de texto, salvo que ---si el navegador del usuario es compatible con *HTML5*--- tendrá algunas funciones de interfaz adicionales. - -Este campo tiene diferentes opciones sobre cómo manejar los valores de entrada que no son enteros. Por omisión, todos los valores no enteros (por ejemplo 6.78) se redondearán hacia abajo (por ejemplo, 6). - -+-------------+-----------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+-----------------------------------------------------------------------+ -| Options | - `rounding_mode`_ | -| | - `grouping`_ | -+-------------+-----------------------------------------------------------------------+ -| Inherited | - `required`_ | -| options | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -+-------------+-----------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+-----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\IntegerType` | -+-------------+-----------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``rounding_mode`` -~~~~~~~~~~~~~~~~~ - -**tipo**: ``integer`` **predeterminado**: ``IntegerToLocalizedStringTransformer::ROUND_DOWN`` - -Por omisión, si el usuario introduce un número no entero, se redondeará hacia abajo. Hay varios métodos de redondeo, y cada uno es una constante en la clase :class:`Symfony\\Component\\Form\\Extension\\Core\\DataTransformer\\IntegerToLocalizedStringTransformer`: - -* ``IntegerToLocalizedStringTransformer::ROUND_DOWN`` modo de redondeo para redondear hacia cero. - -* ``IntegerToLocalizedStringTransformer::ROUND_FLOOR`` modo de redondeo para redondear hacia el infinito negativo. - -* ``IntegerToLocalizedStringTransformer::ROUND_UP`` modo de redondeo para redondear alejándose del cero. - -* ``IntegerToLocalizedStringTransformer::ROUND_CEILING`` modo de redondeo para redondear hacia el infinito positivo. - -.. include:: /reference/forms/types/options/grouping.rst.inc - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc diff --git a/_sources/reference/forms/types/language.txt b/_sources/reference/forms/types/language.txt deleted file mode 100644 index a9d2395..0000000 --- a/_sources/reference/forms/types/language.txt +++ /dev/null @@ -1,58 +0,0 @@ -.. index:: - single: Formularios; Campos: language - -Tipo de campo ``language`` -========================== - -El tipo ``lenguaje`` es un subconjunto de ``ChoiceType`` que permite al usuario seleccionar entre una larga lista de idiomas. Como bono adicional, los nombres de idioma se muestran en el idioma del usuario. - -El «valor» para cada región es el *identificador de idioma Unicode* (por ejemplo, ``es`` o ``zh-Hant``). - -.. note:: - - La configuración regional del usuario se deduce usando :phpmethod:`Locale::getDefault`. - -A diferencia del tipo ``choice``, no es necesario especificar una opción ``choice`` o ``choice_list`` ya que el tipo de campo automáticamente utiliza una larga lista de idiomas. *Puedes* especificar cualquiera de estas opciones manualmente, pero entonces sólo debes utilizar el tipo ``choice`` directamente. - -+-------------+------------------------------------------------------------------------+ -| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `multiple`_ | -| options | - `expanded`_ | -| | - `preferred_choices`_ | -| | - `empty_value`_ | -| | - `error_bubbling`_ | -| | - `required`_ | -| | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`choice` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LanguageType` | -+-------------+------------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`choice`: - -.. include:: /reference/forms/types/options/multiple.rst.inc - -.. include:: /reference/forms/types/options/expanded.rst.inc - -.. include:: /reference/forms/types/options/preferred_choices.rst.inc - -.. include:: /reference/forms/types/options/empty_value.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/locale.txt b/_sources/reference/forms/types/locale.txt deleted file mode 100644 index 02890e7..0000000 --- a/_sources/reference/forms/types/locale.txt +++ /dev/null @@ -1,58 +0,0 @@ -.. index:: - single: Formularios; Campos: locale - -Tipo de campo ``locale`` -======================== - -El tipo ``locale`` es un subconjunto de ``ChoiceType`` que permite al usuario seleccionar entre una larga lista de regiones (idioma + país). Como bono adicional, los nombres regionales se muestran en el idioma del usuario. - -El «valor» de cada región es o bien el del código de *idioma* ISO639-1 de dos letras (por ejemplo, «\ ``es``\ »), o el código de idioma seguido de un guión bajo (``_``), luego el código de *país* ISO3166 (por ejemplo, «\ ``fr_FR``\ » para Francés/Francia). - -.. note:: - - La configuración regional del usuario se deduce usando :phpmethod:`Locale::getDefault`. - -A diferencia del tipo ``choice``, no es necesario especificar una opción ``choices`` o ``choice_list``, ya que el tipo de campo utiliza automáticamente una larga lista de regiones. *Puedes* especificar cualquiera de estas opciones manualmente, pero entonces sólo debes utilizar el tipo ``choice`` directamente. - -+-------------+------------------------------------------------------------------------+ -| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `multiple`_ | -| options | - `expanded`_ | -| | - `preferred_choices`_ | -| | - `empty_value`_ | -| | - `error_bubbling`_ | -| | - `required`_ | -| | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`choice` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\LanguageType` | -+-------------+------------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`choice`: - -.. include:: /reference/forms/types/options/multiple.rst.inc - -.. include:: /reference/forms/types/options/expanded.rst.inc - -.. include:: /reference/forms/types/options/preferred_choices.rst.inc - -.. include:: /reference/forms/types/options/empty_value.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/money.txt b/_sources/reference/forms/types/money.txt deleted file mode 100644 index 2a0fc58..0000000 --- a/_sources/reference/forms/types/money.txt +++ /dev/null @@ -1,87 +0,0 @@ -.. index:: - single: Formularios; Campos: money - -Tipo de campo ``money`` -======================= - -Reproduce un campo de entrada de texto especializado en el manejo de la presentación de datos tipo «moneda». - -Este tipo de campo te permite especificar una moneda, cuyo símbolo se representa al lado del campo de texto. También hay otras opciones para personalizar la forma de la entrada y salida de los datos manipulados. - -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+---------------------------------------------------------------------+ -| Options | - `currency`_ | -| | - `divisor`_ | -| | - `precision`_ | -| | - `grouping`_ | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `required`_ | -| options | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\MoneyType` | -+-------------+---------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``currency`` -~~~~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``EUR`` - -Especifica la moneda en la cual se especifica el dinero. Esta determina el símbolo de moneda que se debe mostrar en el cuadro de texto. Dependiendo de la moneda --- el símbolo de moneda se puede mostrar antes o después del campo de entrada de texto. - -This can be any `3 letter ISO 4217 code`_. You can also set this to false to -hide the currency symbol. - -``divisor`` -~~~~~~~~~~~ - -**tipo**: ``integer`` **predeterminado**: ``1`` - -Si, por alguna razón, tienes que dividir tu valor inicial por un número antes de reproducirlo para el usuario, puedes utilizar la opción ``divisor``. -Por ejemplo:: - - $builder->add('price', 'money', array( - 'divisor' => 100, - )); - -En este caso, si el campo ``price`` está establecido en ``9900``, entonces en realidad al usuario se le presentará el valor ``99``. Cuando el usuario envía el valor ``99``, este se multiplicará por ``100`` y finalmente se devolverá ``9900`` a tu objeto. - -``precision`` -~~~~~~~~~~~~~~ - -**tipo**: ``integer`` **predeterminado**: ``2`` - -Por alguna razón, si necesitas alguna precisión que no sean dos decimales, puedes modificar este valor. Probablemente no necesitarás hacer esto a menos que, por ejemplo, desees redondear al dólar más cercano (ajustando la precisión a ``0``). - -.. include:: /reference/forms/types/options/grouping.rst.inc - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc - -.. _`3 letter ISO 4217 code`: http://en.wikipedia.org/wiki/ISO_4217 diff --git a/_sources/reference/forms/types/number.txt b/_sources/reference/forms/types/number.txt deleted file mode 100644 index 3d38bf7..0000000 --- a/_sources/reference/forms/types/number.txt +++ /dev/null @@ -1,79 +0,0 @@ -.. index:: - single: Formularios; Campos: number - -Tipo de campo ``number`` -======================== - -Reproduce un campo de entrada de texto y se especializa en el manejo de entradas numéricas. Este tipo ofrece diferentes opciones para precisión, redondeo y agrupamiento que desees utilizar para tu número. - -+-------------+----------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+----------------------------------------------------------------------+ -| Options | - `rounding_mode`_ | -| | - `precision`_ | -| | - `grouping`_ | -+-------------+----------------------------------------------------------------------+ -| Inherited | - `required`_ | -| options | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -+-------------+----------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\NumberType` | -+-------------+----------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``precision`` -~~~~~~~~~~~~~~ - -**tipo**: ``integer`` **predeterminado**: Específico a la región (usualmente alrededor de ``3``) - -Este especifica cuantos decimales se permitirán para redondear el campo al valor presentado (a través de ``rounding_mode``). Por ejemplo, si ``precision`` se establece en ``2``, un valor presentado de ``20.123`` se redondeará a, por ejemplo, ``20.12`` (dependiendo de tu ``rounding_mode``). - -``rounding_mode`` -~~~~~~~~~~~~~~~~~ - -**tipo**: ``integer`` **predeterminado**: ``IntegerToLocalizedStringTransformer::ROUND_HALFUP`` - -Si es necesario redondear un número presentado (basándonos en la opción ``precision``), tienes varias opciones configurables para el redondeo. Cada opción es una constante en :class:`Symfony\\Component\\Form\\Extension\\Core\\DataTransformer\\IntegerToLocalizedStringTransformer`: - -* ``IntegerToLocalizedStringTransformer::ROUND_DOWN`` modo de redondeo para redondear hacia cero. - -* ``IntegerToLocalizedStringTransformer::ROUND_FLOOR`` modo de redondeo para redondear hacia el infinito negativo. - -* ``IntegerToLocalizedStringTransformer::ROUND_UP`` modo de redondeo para redondear alejándose del cero. - -* ``IntegerToLocalizedStringTransformer::ROUND_CEILING`` modo de redondeo para redondear hacia el infinito positivo. - -* ``IntegerToLocalizedStringTransformer::ROUND_HALFDOWN`` El modo de redondeo para redondear hacia «el vecino más cercano» a menos que ambos vecinos sean equidistantes, en cuyo caso se redondea hacia abajo. - -* ``IntegerToLocalizedStringTransformer::ROUND_HALFEVEN`` El modo de redondeo para redondear hacia «el vecino más cercano» a menos que ambos vecinos sean equidistantes, en cuyo caso, se redondea hacia el vecino par. - -* ``IntegerToLocalizedStringTransformer::ROUND_HALFUP`` El modo de redondeo para redondear hacia «el vecino más cercano» a menos que ambos vecinos sean equidistantes, en cuyo caso se redondea hacia arriba. - -.. include:: /reference/forms/types/options/grouping.rst.inc - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc diff --git a/_sources/reference/forms/types/password.txt b/_sources/reference/forms/types/password.txt deleted file mode 100644 index 940313c..0000000 --- a/_sources/reference/forms/types/password.txt +++ /dev/null @@ -1,56 +0,0 @@ -.. index:: - single: Formularios; Campos: password - -Tipo de campo ``password`` -========================== - -El tipo de campo ``password`` reproduce un campo de texto para entrada de contraseñas. - -+-------------+------------------------------------------------------------------------+ -| Rendered as | ``input`` ``password`` field | -+-------------+------------------------------------------------------------------------+ -| Options | - `always_empty`_ | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `max_length`_ | -| options | - `required`_ | -| | - `label`_ | -| | - `trim`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`text` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PasswordType` | -+-------------+------------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``always_empty`` -~~~~~~~~~~~~~~~~ - -**tipo**: ``Boolean`` **predeterminado**: ``true`` - -Si es ``true``, el campo *siempre* se reproduce en blanco, incluso si el campo correspondiente tiene un valor. Cuando se establece en ``false``, el campo de la contraseña se reproduce con el atributo ``value`` fijado a su valor real. - -En pocas palabras, si por alguna razón deseas reproducir tu campo de contraseña *con* el valor de contraseña ingresado anteriormente en el cuadro, ponlo a ``false``. - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/max_length.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc \ No newline at end of file diff --git a/_sources/reference/forms/types/percent.txt b/_sources/reference/forms/types/percent.txt deleted file mode 100644 index 52b386b..0000000 --- a/_sources/reference/forms/types/percent.txt +++ /dev/null @@ -1,74 +0,0 @@ -.. index:: - single: Formularios; Campos: percent - -Tipo de campo ``percent`` -========================= - - -El tipo de campo ``percent`` reproduce un campo de entrada de texto, especializado en el manejo de datos porcentuales. Si los datos porcentuales se almacenan como un decimal (por ejemplo, ``0.95``), puedes utilizar este campo fuera de la caja. Si almacenas tus datos como un número (por ejemplo, ``95``), debes establecer la opción ``type`` a ``integer``. - -Este campo añade un signo de porcentaje «``%``» después del cuadro de entrada. - -+-------------+-----------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+-----------------------------------------------------------------------+ -| Options | - `type`_ | -| | - `precision`_ | -+-------------+-----------------------------------------------------------------------+ -| Inherited | - `required`_ | -| options | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -| | - `invalid_message`_ | -| | - `invalid_message_parameters`_ | -+-------------+-----------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+-----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\PercentType` | -+-------------+-----------------------------------------------------------------------+ - -Opciones --------- - -``type`` -~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``fractional`` - -Esto controla la forma en que se almacenan tus datos en el objeto. Por ejemplo, en el objeto puedes almacenar un porcentaje correspondiente al «55%» como ``0.55`` o ``55``. Los dos «tipos» manejan estos dos casos: - -* ``fractional`` - Si los datos se almacenan como un decimal (por ejemplo, ``0.55``), usa este tipo. - Los datos se multiplicarán por ``100`` antes de mostrarlos al usuario (por ejemplo, ``55``). Los datos presentados se dividirán por ``100`` al presentar el formulario para almacenar el valor decimal (``0.55``); - -* ``integer`` - Si almacenas tus datos como un entero (por ejemplo, 55), entonces, utiliza esta opción. - El valor crudo (``55``) se muestra al usuario y se almacena en tu objeto. - Ten en cuenta que esto sólo funciona para valores enteros. - -``precision`` -~~~~~~~~~~~~~~ - -**tipo**: ``integer`` **predeterminado**: ``0`` - -De manera predeterminada, los números ingresados se redondean. Para tomar en cuenta más cifras decimales, utiliza esta opción. - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc diff --git a/_sources/reference/forms/types/radio.txt b/_sources/reference/forms/types/radio.txt deleted file mode 100644 index 3d213c1..0000000 --- a/_sources/reference/forms/types/radio.txt +++ /dev/null @@ -1,51 +0,0 @@ -.. index:: - single: Formularios; Campos: radio - -Tipo de campo ``radio`` -======================= - -Crea un solo botón de radio. Si el botón de radio es seleccionado, el campo se establecerá al valor especificado. Los botones de radio no se pueden deseleccionar ---el valor cambia únicamente cuándo seleccionas otro botón de radio con el mismo nombre. - -El tipo ``radio`` no suele usarse directamente. Comúnmente se utiliza internamente por otros tipos, tales como :doc:`choice `. -Si quieres tener un campo booleano, utiliza una :doc:`casilla de verificación `. - -+-------------+---------------------------------------------------------------------+ -| Rendered as | ``input`` ``radio`` field | -+-------------+---------------------------------------------------------------------+ -| Options | - `value`_ | -+-------------+---------------------------------------------------------------------+ -| Inherited | - `required`_ | -| options | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+---------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+---------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RadioType` | -+-------------+---------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``value`` -~~~~~~~~~ - -**tipo**: ``mixed`` **predeterminado**: ``1`` - -El valor utilizado realmente como valor para el botón de radio. Esto no afecta al valor establecido en tu objeto. - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/_sources/reference/forms/types/repeated.txt b/_sources/reference/forms/types/repeated.txt deleted file mode 100644 index 6d0239f..0000000 --- a/_sources/reference/forms/types/repeated.txt +++ /dev/null @@ -1,155 +0,0 @@ -.. index:: - single: Formularios; Campos: repetidos - -Tipo de campo ``repeated`` -========================== - -Este es un campo «grupo» especial, el cual crea dos campos idénticos, cuyos valores deben coincidir (o lanza un error de validación). Se utiliza comúnmente cuando necesitas que el usuario repita su contraseña o correo electrónico para verificar su exactitud. - -+----------------+------------------------------------------------------------------------+ -| Dibujado como | campo ``input`` ``text`` por omisión, pero ve la opción `type`_ | -+----------------+------------------------------------------------------------------------+ -| Opciones | - `type`_ | -| | - `options`_ | -| | - `first_options`_ | -| | - `second_options`_ | -| | - `first_name`_ | -| | - `second_name`_ | -+----------------+------------------------------------------------------------------------+ -| Opciones | - `invalid_message`_ | -| heredadas | - `invalid_message_parameters`_ | -| | - `error_bubbling`_ | -+----------------+------------------------------------------------------------------------+ -| Tipo del padre | :doc:`field` | -+----------------+------------------------------------------------------------------------+ -| Clase | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\RepeatedType` | -+----------------+------------------------------------------------------------------------+ - -Ejemplo de uso --------------- - -.. code-block:: php - - $builder->add('password', 'repeated', array( - 'type' => 'password', - 'invalid_message' => 'The password fields must match.', - 'options' => array('attr' => array('class' => 'password-field')), - 'required' => true, - 'first_options' => array('label' => 'Password'), - 'second_options' => array('label' => 'Repeat Password'), - )); - -Al presentar satisfactoriamente un formulario, el valor ingresado en ambos campos «contraseña» se convierte en los datos de la clave ``password``. En otras palabras, a pesar de que ambos campos efectivamente son reproducidos, sólo el dato final del formulario es el único valor que necesitas (generalmente una cadena). - -La opción más importante es ``type``, la cual puede ser cualquier tipo de campo y determina el tipo real de los dos campos subyacentes. La opción ``options`` se pasa a cada uno de los campos individuales, lo cual significa ---en este ejemplo--- que en este arreglo puedes pasar cualquier opción compatible con el tipo ``password``. - -Rendering -~~~~~~~~~ - -The repeated field type is actually two underlying fields, which you can -render all at once, or individually. To render all at once, use something -like: - -.. configuration-block:: - - .. code-block:: jinja - - {{ form_row(form.password) }} - - .. code-block:: php - - row($form['password']) ?> - -To render each field individually, use something like this: - -.. configuration-block:: - - .. code-block:: jinja - - {{ form_row(form.password.first) }} - {{ form_row(form.password.second) }} - - .. code-block:: php - - row($form['password']['first']) ?> - row($form['password']['second']) ?> - -.. note:: - - The sub-field names are ``first`` and ``second`` by default, but can - be controlled via the `first_name`_ and `second_name`_ options. - -Validando -~~~~~~~~~ - -Una de las características clave del campo ``repeated`` es la validación interna (sin necesidad de hacer nada para configurar esto) el cual obliga a que los dos campos tengan un valor coincidente. Si los dos campos no coinciden, se mostrará un error al usuario. - -El ``invalid_message`` se utiliza para personalizar el error que se mostrará cuando los dos campos no coinciden entre sí. - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``type`` -~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``text`` - -Los dos campos subyacentes serán de este tipo de campo. Por ejemplo, pasando un tipo de ``password`` reproducirá dos campos de contraseña. - -``options`` -~~~~~~~~~~~ - -**tipo**: ``array`` **predeterminado**: ``array()`` - -Este arreglo de opciones se pasará a cada uno de los dos campos subyacentes. En otras palabras, estas son las opciones que personalizan los tipos de campo individualmente. -Por ejemplo, si la opción ``type`` se establece en ``password``, este arreglo puede contener las opciones ``always_empty`` o ``required`` --- ambas opciones son compatibles con el tipo de campo ``password``. - -``first_options`` -~~~~~~~~~~~~~~~~~ - -**tipo**: ``array`` **predeterminado**: ``array()`` - -.. versionadded:: 2.1 - La opción ``first_options`` es nueva en *Symfony 2.1*. - -Las opciones adicionales (se fusionarán en ``opciones`` arriba) estas *sólo* se deben suministrar para el primer campo. Estas son útiles especialmente para personalizar la etiqueta:: - - $builder->add('password', 'repeated', array( - 'first_options' => array('label' => 'Password'), - 'second_options' => array('label' => 'Repeat Password'), - )); - -``second_options`` -~~~~~~~~~~~~~~~~~~ - -**tipo**: ``array`` **predeterminado**: ``array()`` - -.. versionadded:: 2.1 - La opción ``second_options`` es nueva en *Symfony 2.1*. - -Las opciones adicionales (se fusionarán en ``opciones`` arriba) estas *sólo* se deben suministrar para el segundo campo. Estas son útiles especialmente para personalizar la etiqueta (ve las `first_options`_). - -``first_name`` -~~~~~~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``first`` - -Este es el nombre real del campo que se utilizará para el primer campo. Esto sobre todo no tiene sentido, sin embargo, puesto que los datos reales especificados en ambos campos disponibles bajo la clave asignada al campo ``repeated`` en sí mismo (por ejemplo, ``password``). Sin embargo, si no especificas una etiqueta, se utiliza el nombre de este campo para «deducir» la etiqueta por ti. - -``second_name`` -~~~~~~~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``second`` - -Al igual que ``first_name``, pero para el segundo campo. - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/_sources/reference/forms/types/search.txt b/_sources/reference/forms/types/search.txt deleted file mode 100644 index eddf019..0000000 --- a/_sources/reference/forms/types/search.txt +++ /dev/null @@ -1,46 +0,0 @@ -.. index:: - single: Formularios; Campos: search - -Tipo de campo ``search`` -======================== - -Este reproduce un campo ````, el cual es un cuadro de texto con funcionalidad especial apoyada por algunos navegadores. - -Lee sobre el campo de entrada para búsqueda en `DiveIntoHTML5.info`_ - -+-------------+----------------------------------------------------------------------+ -| Rendered as | ``input search`` field | -+-------------+----------------------------------------------------------------------+ -| Inherited | - `max_length`_ | -| options | - `required`_ | -| | - `label`_ | -| | - `trim`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+----------------------------------------------------------------------+ -| Parent type | :doc:`text` | -+-------------+----------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\SearchType` | -+-------------+----------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/max_length.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - -.. _`DiveIntoHTML5.info`: http://diveintohtml5.info/forms.html#type-search diff --git a/_sources/reference/forms/types/text.txt b/_sources/reference/forms/types/text.txt deleted file mode 100644 index 3efbd4a..0000000 --- a/_sources/reference/forms/types/text.txt +++ /dev/null @@ -1,43 +0,0 @@ -.. index:: - single: Formularios; Campos: text - -Tipo de campo text -================== - -El campo de texto reproduce el campo de entrada de texto más básico. - -+-------------+--------------------------------------------------------------------+ -| Rendered as | ``input`` ``text`` field | -+-------------+--------------------------------------------------------------------+ -| Inherited | - `max_length`_ | -| options | - `required`_ | -| | - `label`_ | -| | - `trim`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+--------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+--------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TextType` | -+-------------+--------------------------------------------------------------------+ - - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/max_length.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/_sources/reference/forms/types/textarea.txt b/_sources/reference/forms/types/textarea.txt deleted file mode 100644 index 0b5a173..0000000 --- a/_sources/reference/forms/types/textarea.txt +++ /dev/null @@ -1,43 +0,0 @@ -.. index:: - single: Formularios; Campos: textarea - -Tipo de campo ``textarea`` -========================== - -Reproduce un elemento ``textarea`` *HTML*. - -+-------------+------------------------------------------------------------------------+ -| Rendered as | ``textarea`` tag | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `max_length`_ | -| options | - `required`_ | -| | - `label`_ | -| | - `trim`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`field` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TextareaType` | -+-------------+------------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/max_length.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc - diff --git a/_sources/reference/forms/types/time.txt b/_sources/reference/forms/types/time.txt deleted file mode 100644 index 0596524..0000000 --- a/_sources/reference/forms/types/time.txt +++ /dev/null @@ -1,118 +0,0 @@ -.. index:: - single: Formularios; Campos: time - -Tipo de campo ``time`` -====================== - -Un campo para capturar entradas horarias. - -Este se puede reproducir como un campo de texto, una serie de campos de texto (por ejemplo, horas, minutos, segundos) o una serie de campos de selección. Los datos subyacentes se pueden almacenar como un objeto ``DateTime``, una cadena, una marca de tiempo (``timestamp``) o un arreglo. - -+----------------------+-----------------------------------------------------------------------------+ -| Underlying Data Type | can be ``DateTime``, string, timestamp, or array (see the ``input`` option) | -+----------------------+-----------------------------------------------------------------------------+ -| Rendered as | can be various tags (see below) | -+----------------------+-----------------------------------------------------------------------------+ -| Options | - `widget`_ | -| | - `input`_ | -| | - `with_seconds`_ | -| | - `hours`_ | -| | - `minutes`_ | -| | - `seconds`_ | -| | - `data_timezone`_ | -| | - `user_timezone`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Inherited | - `invalid_message`_ | -| options | - `invalid_message_parameters`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `virtual`_ | -+----------------------+-----------------------------------------------------------------------------+ -| Parent type | form | -+----------------------+-----------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimeType` | -+----------------------+-----------------------------------------------------------------------------+ - -Uso básico ----------- - -Este tipo de campo es altamente configurable, pero fácil de usar. Las opciones más importantes son ``input`` y ``widget``. - -Supongamos que tienes un campo ``startTime`` cuyo dato de hora subyacente es un objeto ``DateTime``. Lo siguiente configura el tipo ``time`` para ese campo como tres campos de opciones diferentes: - -.. code-block:: php - - $builder->add('startTime', 'time', array( - 'input' => 'datetime', - 'widget' => 'choice', - )); - -La opción ``input`` se *debe* cambiar para que coincida con el tipo de dato de la fecha subyacente. Por ejemplo, si los datos del campo ``startTime`` fueran una marca de tiempo Unix, habría necesidad de establecer la ``entrada`` a ``timestamp``: - -.. code-block:: php - - $builder->add('startTime', 'time', array( - 'input' => 'timestamp', - 'widget' => 'choice', - )); - -El campo también es compatible con ``array`` y ``string`` como valores válidos de la opción ``input``. - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``widget`` -~~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``choice`` - -La forma básica en que se debe reproducir este campo. Puede ser una de las siguientes: - -* ``choice``: pinta dos (o tres si `with_seconds`_ es ``true``) cuadros de selección. - -* ``text``: pinta dos o tres cuadros de texto (horas, minutos, segundos). - -* ``single_text``: pinta un sólo campo de entrada de tipo ``text``. La entrada del usuario se validará contra la forma ``hh:mm`` (o ``hh:mm:ss`` si se utilizan segundos). - -``input`` -~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``datetime`` - -El formato del dato *input* ---es decir, el formato de la fecha en que se almacena en el objeto subyacente. Los valores válidos son los siguientes: - -* ``string`` (por ejemplo ``12:17:26``) -* ``datetime`` (un objeto ``DateTime``) -* ``array`` (por ejemplo ``array('hour' => 12, 'minute' => 17, 'second' => 26)``) -* ``timestamp`` (por ejemplo ``1307232000``) - -El valor devuelto por el formulario también se normaliza de nuevo a este formato. - -.. include:: /reference/forms/types/options/with_seconds.rst.inc - -.. include:: /reference/forms/types/options/hours.rst.inc - -.. include:: /reference/forms/types/options/minutes.rst.inc - -.. include:: /reference/forms/types/options/seconds.rst.inc - -.. include:: /reference/forms/types/options/data_timezone.rst.inc - -.. include:: /reference/forms/types/options/user_timezone.rst.inc - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/invalid_message.rst.inc - -.. include:: /reference/forms/types/options/invalid_message_parameters.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -These options inherit from the :doc:`date` type: - -.. include:: /reference/forms/types/options/virtual.rst.inc diff --git a/_sources/reference/forms/types/timezone.txt b/_sources/reference/forms/types/timezone.txt deleted file mode 100644 index 659c1b2..0000000 --- a/_sources/reference/forms/types/timezone.txt +++ /dev/null @@ -1,54 +0,0 @@ -.. index:: - single: Formularios; Campos: timezone - -Tipo de campo ``timezone`` -========================== - -El tipo ``timezone`` es un subconjunto de ``ChoiceType`` que permite al usuario seleccionar entre todas las posibles zonas horarias. - -El «``valor``» para cada zona horaria es el nombre completo de la zona horaria, por ejemplo ``América/Chicago`` o ``Europa/Estambul``. - -A diferencia del tipo ``choice``, no es necesario especificar una opción ``choices`` o ``choice_list``, ya que el tipo de campo utiliza automáticamente una larga lista de regiones. *Puedes* especificar cualquiera de estas opciones manualmente, pero entonces sólo debes utilizar el tipo ``choice`` directamente. - -+-------------+------------------------------------------------------------------------+ -| Rendered as | can be various tags (see :ref:`forms-reference-choice-tags`) | -+-------------+------------------------------------------------------------------------+ -| Inherited | - `multiple`_ | -| options | - `expanded`_ | -| | - `preferred_choices`_ | -| | - `empty_value`_ | -| | - `required`_ | -| | - `label`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+------------------------------------------------------------------------+ -| Parent type | :doc:`choice` | -+-------------+------------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\TimezoneType` | -+-------------+------------------------------------------------------------------------+ - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`choice`: - -.. include:: /reference/forms/types/options/multiple.rst.inc - -.. include:: /reference/forms/types/options/expanded.rst.inc - -.. include:: /reference/forms/types/options/preferred_choices.rst.inc - -.. include:: /reference/forms/types/options/empty_value.rst.inc - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/_sources/reference/forms/types/url.txt b/_sources/reference/forms/types/url.txt deleted file mode 100644 index 9d1f492..0000000 --- a/_sources/reference/forms/types/url.txt +++ /dev/null @@ -1,54 +0,0 @@ -.. index:: - single: Formularios; Campos: url - -Tipo de campo ``url`` -===================== - -El campo ``url`` es un campo de texto que prefija el valor presentado con un determinado protocolo (por ejemplo, ``http://``) si el valor presentado no tiene ya un protocolo. - -+-------------+-------------------------------------------------------------------+ -| Rendered as | ``input url`` field | -+-------------+-------------------------------------------------------------------+ -| Options | - `default_protocol`_ | -+-------------+-------------------------------------------------------------------+ -| Inherited | - `max_length`_ | -| options | - `required`_ | -| | - `label`_ | -| | - `trim`_ | -| | - `read_only`_ | -| | - `disabled`_ | -| | - `error_bubbling`_ | -+-------------+-------------------------------------------------------------------+ -| Parent type | :doc:`text` | -+-------------+-------------------------------------------------------------------+ -| Class | :class:`Symfony\\Component\\Form\\Extension\\Core\\Type\\UrlType` | -+-------------+-------------------------------------------------------------------+ - -Opciones del campo -~~~~~~~~~~~~~~~~~~ - -``default_protocol`` -~~~~~~~~~~~~~~~~~~~~ - -**tipo**: ``string`` **predefinido**: ``http`` - -Si un valor es presentado que no comience con un protocolo (por ejemplo, ``http://``, ``ftp://``, etc.), se prefija la cadena con este protocolo al vincular los datos al formulario. - -Opciones heredadas ------------------- - -Estas opciones las hereda del tipo :doc:`field `: - -.. include:: /reference/forms/types/options/max_length.rst.inc - -.. include:: /reference/forms/types/options/required.rst.inc - -.. include:: /reference/forms/types/options/label.rst.inc - -.. include:: /reference/forms/types/options/trim.rst.inc - -.. include:: /reference/forms/types/options/read_only.rst.inc - -.. include:: /reference/forms/types/options/disabled.rst.inc - -.. include:: /reference/forms/types/options/error_bubbling.rst.inc diff --git a/_sources/reference/index.txt b/_sources/reference/index.txt deleted file mode 100644 index 7d9b27e..0000000 --- a/_sources/reference/index.txt +++ /dev/null @@ -1,26 +0,0 @@ -Documentos de referencia ------------------------- - -.. toctree:: - :hidden: - - configuration/framework - configuration/assetic - configuration/doctrine - configuration/security - configuration/swiftmailer - configuration/twig - configuration/monolog - configuration/web_profiler - configuration/kernel - - forms/types - forms/twig_reference - - twig_reference - - constraints - dic_tags - requirements - -.. include:: /reference/map.rst.inc diff --git a/_sources/reference/requirements.txt b/_sources/reference/requirements.txt deleted file mode 100644 index 1257c51..0000000 --- a/_sources/reference/requirements.txt +++ /dev/null @@ -1,44 +0,0 @@ -.. index:: - single: Requisitos - -Requisitos para que funcione *Symfony2* -======================================= - -Para ejecutar *Symfony2*, el sistema debe cumplir con una lista de requisitos. Fácilmente puedes ver si el sistema pasa todos los requisitos ejecutando :file:`web/config.php` en tu distribución de *Symfony*. Debido a que la *CLI* (por *Command Line Interface* o Interfaz de la línea de ordenes) a menudo utiliza un archivo de configuración :file:`php.ini` diferente, también es una buena idea revisar tus requisitos desde la línea de ordenes por medio de: - -.. code-block:: bash - - $ php app/check.php - -A continuación mostramos la lista de requisitos obligatorios y opcionales. - -Obligatorio ------------ - -* *PHP* debe ser una versión mínima de *PHP 5.3.3* -* Es necesario habilitar *JSON* -* Es necesario tener habilitado el ``ctype`` -* Tu :file:`PHP.ini` debe tener configurado el valor ``date.timezone`` - -Opcional --------- - -* Necesitas tener instalado el módulo ``PHP-XML`` -* Necesitas tener por lo menos la versión 2.6.21 de ``libxml`` -* Necesitas activar el ``tokenizer`` de *PHP* -* tienes que habilitar las funciones ``mbstring`` -* tienes que activar ``iconv`` -* ``POSIX`` tiene que estar habilitado (únicamente en \*nix) -* Debes tener instalado *Intl* con *ICU 4+* -* *APC 3.0.17+* (u otra caché opcode debe estar instalada) -* Configuración recomendada en :file:`PHP.ini` - - * ``short_open_tag = Off`` - * ``magic_quotes_gpc = Off`` - * ``register_globals = Off`` - * ``session.autostart = Off`` - -*Doctrine* ----------- - -Si deseas utilizar *Doctrine*, necesitarás tener instalado *PDO*. Además, es necesario tener instalado el controlador de *PDO* para el servidor de base de datos que desees utilizar. diff --git a/_sources/reference/twig_reference.txt b/_sources/reference/twig_reference.txt deleted file mode 100644 index a023e37..0000000 --- a/_sources/reference/twig_reference.txt +++ /dev/null @@ -1,187 +0,0 @@ -.. index:: - single: Extensiones Twig de Symfony2 - -Extensiones *Twig* de *Symfony2* -================================ - -*Twig* es el motor de plantillas predeterminado en *Symfony2*. Por sí mismo, ya tiene integradas muchas funciones, filtros, etiquetas y pruebas (`http://gitnacho.github.com/Twig`_ luego desplázate hasta abajo). - -*Symfony2* añade más extensiones personalizadas en lo alto de *Twig* para integrar algunos componentes a las plantillas de *Twig*. Abajo está la información sobre todas las funciones personalizadas, filtros, etiquetas y pruebas añadidas usando el núcleo de la plataforma *Symfony2*. - -También puede haber etiquetas en los paquetes que utilizas comúnmente que no están enumeradas aquí. - -Funciones ---------- - -.. versionadded:: 2.1 - Las funciones ``csrf_token``, ``logout_path`` y ``logout_url`` se agregaron en *Symfony 2.1* - -.. versionadded:: 2.2 - The ``render`` and ``controller`` functions are new in Symfony 2.2. Anteriormente se utilizaba la etiqueta ``{% render %}`` y tenía una firma diferente. - -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| Function Syntax | Usage | -+====================================================+============================================================================================+ -| ``render(uri, options = {})`` | This will render the fragment for the given controller or URL | -| ``render(controller('B:C:a', {params}))`` | For more information, see :ref:`templating-embedding-controller`. | -| ``render(path('route', {params}))`` | :ref:`templating-embedding-controller`. | -| ``render(url('route', {params}))`` | | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``render_esi(controller('B:C:a', {params}))`` | Esta genera una etiqueta *ESI* cuando es posible o de lo contrario vuelve a caer | -| ``render_esi(url('route', {params}))`` | al comportamiento de ``render``. Para más | -| ``render_esi(path('route', {params}))`` | información, ve :ref:`templating-embedding-controller`. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``render_hinclude(controller(...))`` | Esta genera una etiqueta ``hinclude`` para el controlador o la *URL* | -| ``render_hinclude(url('route', {params}))`` | dada. para más información, ve | -| ``render_hinclude(path('route', {params}))`` | | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``controller(attributes = {}, query = {})`` | Used along with the ``render`` tag to refer to the controller that you want to render. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``asset(path, packageName = null)`` | Get the public path of the asset, more information in | -| | ":ref:`book-templating-assets`". | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``asset_version(packageName = null)`` | Obtiene la versión actual del paquete, más información en | -| | «:ref:`book-templating-assets`». | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_enctype(view)`` | Dibuja el atributo ``enctype="multipart/form-data"`` requerido si el formulario cuando | -| | menos tiene un campo para subir archivos, más información en la | -| | :ref:`Referencia de formularios Twig `. | -| | | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_widget(view, variables = {})`` | Dibuja un formulario completo o el HTML del elemento gráfico de un campo específico, más | -| | información en la | -| | :ref:`Referencia de formularios Twig `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_errors(view)`` | Dibuja cualquier error para el campo dado o los errores «globales», más información en la | -| | :ref:`Referencia de formularios Twig `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_label(view, label = null, variables = {})`` | Dibuja la etiqueta del campo dado, más información en la | -| | :ref:`Referencia de formularios Twig `. | -| | | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_row(view, variables = {})`` | Dibuja la fila (las etiquetas de los campos, errores y elementos gráficos) del campo dado, | -| | más información en la | -| | :ref:`Referencia de formularios Twig `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``form_rest(view, variables = {})`` | Dibuja todos los campos que no se han pintado todavía, más información en la | -| | :ref:`Referencia de formularios Twig `. | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``csrf_token(intention)`` | Dibuja un testigo CSRF. Usa esta función si deseas protección *CSRF* sin crear un | -| | formulario | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``is_granted(role, object = null, field = null)`` | Devuelve ``true`` si el usuario actual tiene el rol necesario, más información en | -| | «:ref:`book-security-template`» | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``logout_path(key)`` | Genera la *URL* relativa para cerrar la sesión del cortafuegos dado | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``logout_url(key)`` | Igual a ``logout_path(...)`` pero esta genera una *URL* absoluta | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``path(name, parameters = {})`` | Obtiene una *URL* relativa para la ruta dada, más información en | -| | «:ref:`book-templating-pages`». | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ -| ``url(name, parameters = {})`` | Igual a ``path(...)`` pero esta genera una *URL* absoluta | -+----------------------------------------------------+--------------------------------------------------------------------------------------------+ - -Filtros -------- - -.. versionadded:: 2.1 - El filtro ``humanize`` se añadió en *Symfony 2.1* - -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| Filter Syntax | Usage | -+=================================================================================+===================================================================+ -| ``text|humanize`` | Makes a technical name human readable (replaces underscores by | -| | spaces and capitalizes the string) | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``text|trans(arguments = {}, domain = 'messages', locale = null)`` | This will translate the text into the current language, more | -| | information in . | -| | :ref:`Translation Filters`. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``text|transchoice(count, arguments = {}, domain = 'messages', locale = null)`` | This will translate the text with pluralization, more information | -| | in :ref:`Translation Filters`. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``variable|yaml_encode(inline = 0)`` | Transforma el texto de la variable a la sintaxis de YAML. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``variable|yaml_dump`` | Dibuja una sintaxis yaml con su tipo. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``classname|abbr_class`` | Dibuja un elemento ``abbr`` con el nombre corto de una clase | -| | *PHP*. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``methodname|abbr_method`` | Dibuja un método *PHP* dentro de un elemento ``abbr`` (p. ej. | -| | ``Symfony\Component\HttpFoundation\Response::getContent``) | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``arguments|format_args`` | Dibuja una cadena con los argumentos de una función y sus tipos | -| | | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``arguments|format_args_as_text`` | Igual a ``[...]|format_args``, pero le quita las etiquetas. | -| | | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``path|file_excerpt(line)`` | Dibuja un extracto de un archivo de código en torno a una | -| | determinada línea. | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``path|format_file(line, text = null)`` | Dibuja la ruta a un archivo en un enlace. | -| | | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``exceptionMessage|format_file_from_text`` | Igual a ``format_file`` excepto que añade a la cadena de error | -| | predefinida la ruta al archivo (es decir, 'en foo.php en la línea | -| | 45') | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ -| ``path|file_link(line)`` | Dibuja la ruta al archivo correcto (y número de línea) | -+---------------------------------------------------------------------------------+-------------------------------------------------------------------+ - -Etiquetas ---------- - -+---------------------------------------------------+--------------------------------------------------------------------+ -| Tag Syntax | Usage | -+===================================================+====================================================================+ -| ``{% form_theme form 'file' %}`` | This will look inside the given file for overridden form blocks, | -| | more information in :doc:`/cookbook/form/form_customization`. | -+---------------------------------------------------+--------------------------------------------------------------------+ -| ``{% trans with {variables} %}...{% endtrans %}`` | This will translate and render the text, more information in | -| | :ref:`book-translation-tags` | -+---------------------------------------------------+--------------------------------------------------------------------+ -| ``{% transchoice count with {variables} %}`` | This will translate and render the text with pluralization, more | -| ... | information in :ref:`book-translation-tags` | -| ``{% endtranschoice %}`` | | -+---------------------------------------------------+--------------------------------------------------------------------+ -| ``{% trans_default_domain language %}`` | This will set the default domain for message catalogues in the | -| | current template | -+---------------------------------------------------+--------------------------------------------------------------------+ - -Pruebas -------- - -.. versionadded:: 2.1 - La prueba ``selectedchoice`` se añadió en *Symfony 2.1* - -+---------------------------------------------------+------------------------------------------------------------------------------+ -| Sintaxis de la prueba | Uso | -+===================================================+==============================================================================+ -| ``selectedchoice(choice, selectedValue)`` | Devuelve ``true`` si la opción para el valor dado en el formulario | -| | está seleccionada | -+---------------------------------------------------+------------------------------------------------------------------------------+ - -Variables globales ------------------- - -+-------------------------------------------------------+------------------------------------------------------------------------------------+ -| Variable | Uso | -+=======================================================+====================================================================================+ -| ``app`` *Attributes*: ``app.user``, ``app.request`` | La variable ``app`` está disponible en todas partes, y proporciona rápido acceso a | -| ``app.session``, ``app.environment``, ``app.debug`` | muchos objetos necesarios comúnmente. La variable ``app`` es una instancia de | -| ``app.security`` | :class:`Symfony\\Bundle\\FrameworkBundle\\Templating\\GlobalVariables` | -+-------------------------------------------------------+------------------------------------------------------------------------------------+ - -Extensiones de la edición estándar de *Symfony* ------------------------------------------------ - -La edición estándar de *Symfony* añade algunos paquetes al núcleo de la plataforma *Symfony2*. -Dichos paquetes pueden tener otras extensiones de *Twig*: - -* **Extensión de Twig** incluye todas las extensiones que no pertenecen al núcleo de *Twig* pero pueden ser interesantes. Puedes leer más en la `documentación oficial de las Extensiones de Twig`_ -* **Assetic** añade las etiquetas ``{% stylesheets %}``, ``{% javascripts %}`` e ``{% image %}``. Puedes leer más sobre ellas en la :doc:`Documentación de Assetic `; - -.. _`documentación oficial de las Extensiones de Twig`: http://twig.sensiolabs.org/doc/extensions/index.html -.. _`http://gitnacho.github.com/Twig`: http://gitnacho.github.com/Twig/ diff --git a/_static/ajax-loader.gif b/_static/ajax-loader.gif deleted file mode 100644 index 61faf8c..0000000 Binary files a/_static/ajax-loader.gif and /dev/null differ diff --git a/_static/basic.css b/_static/basic.css deleted file mode 100644 index f0379f3..0000000 --- a/_static/basic.css +++ /dev/null @@ -1,540 +0,0 @@ -/* - * basic.css - * ~~~~~~~~~ - * - * Sphinx stylesheet -- basic theme. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/* -- main layout ----------------------------------------------------------- */ - -div.clearer { - clear: both; -} - -/* -- relbar ---------------------------------------------------------------- */ - -div.related { - width: 100%; - font-size: 90%; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - display: inline; -} - -div.related li.right { - float: right; - margin-right: 5px; -} - -/* -- sidebar --------------------------------------------------------------- */ - -div.sphinxsidebarwrapper { - padding: 10px 5px 0 10px; -} - -div.sphinxsidebar { - float: left; - width: 230px; - margin-left: -100%; - font-size: 90%; -} - -div.sphinxsidebar ul { - list-style: none; -} - -div.sphinxsidebar ul ul, -div.sphinxsidebar ul.want-points { - margin-left: 20px; - list-style: square; -} - -div.sphinxsidebar ul ul { - margin-top: 0; - margin-bottom: 0; -} - -div.sphinxsidebar form { - margin-top: 10px; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - -div.sphinxsidebar input[type="text"] { - width: 170px; -} - -div.sphinxsidebar input[type="submit"] { - width: 30px; -} - -img { - border: 0; -} - -/* -- search page ----------------------------------------------------------- */ - -ul.search { - margin: 10px 0 0 20px; - padding: 0; -} - -ul.search li { - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -ul.search li a { - font-weight: bold; -} - -ul.search li div.context { - color: #888; - margin: 2px 0 0 30px; - text-align: left; -} - -ul.keywordmatches li.goodmatch a { - font-weight: bold; -} - -/* -- index page ------------------------------------------------------------ */ - -table.contentstable { - width: 90%; -} - -table.contentstable p.biglink { - line-height: 150%; -} - -a.biglink { - font-size: 1.3em; -} - -span.linkdescr { - font-style: italic; - padding-top: 5px; - font-size: 90%; -} - -/* -- general index --------------------------------------------------------- */ - -table.indextable { - width: 100%; -} - -table.indextable td { - text-align: left; - vertical-align: top; -} - -table.indextable dl, table.indextable dd { - margin-top: 0; - margin-bottom: 0; -} - -table.indextable tr.pcap { - height: 10px; -} - -table.indextable tr.cap { - margin-top: 10px; - background-color: #f2f2f2; -} - -img.toggler { - margin-right: 3px; - margin-top: 3px; - cursor: pointer; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -/* -- general body styles --------------------------------------------------- */ - -a.headerlink { - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -div.body p.caption { - text-align: inherit; -} - -div.body td { - text-align: left; -} - -.field-list ul { - padding-left: 1em; -} - -.first { - margin-top: 0 !important; -} - -p.rubric { - margin-top: 30px; - font-weight: bold; -} - -img.align-left, .figure.align-left, object.align-left { - clear: left; - float: left; - margin-right: 1em; -} - -img.align-right, .figure.align-right, object.align-right { - clear: right; - float: right; - margin-left: 1em; -} - -img.align-center, .figure.align-center, object.align-center { - display: block; - margin-left: auto; - margin-right: auto; -} - -.align-left { - text-align: left; -} - -.align-center { - text-align: center; -} - -.align-right { - text-align: right; -} - -/* -- sidebars -------------------------------------------------------------- */ - -div.sidebar { - margin: 0 0 0.5em 1em; - border: 1px solid #ddb; - padding: 7px 7px 0 7px; - background-color: #ffe; - width: 40%; - float: right; -} - -p.sidebar-title { - font-weight: bold; -} - -/* -- topics ---------------------------------------------------------------- */ - -div.topic { - border: 1px solid #ccc; - padding: 7px 7px 0 7px; - margin: 10px 0 10px 0; -} - -p.topic-title { - font-size: 1.1em; - font-weight: bold; - margin-top: 10px; -} - -/* -- admonitions ----------------------------------------------------------- */ - -div.admonition { - margin-top: 10px; - margin-bottom: 10px; - padding: 7px; -} - -div.admonition dt { - font-weight: bold; -} - -div.admonition dl { - margin-bottom: 0; -} - -p.admonition-title { - margin: 0px 10px 5px 0px; - font-weight: bold; -} - -div.body p.centered { - text-align: center; - margin-top: 25px; -} - -/* -- tables ---------------------------------------------------------------- */ - -table.docutils { - border: 0; - border-collapse: collapse; -} - -table.docutils td, table.docutils th { - padding: 1px 8px 1px 5px; - border-top: 0; - border-left: 0; - border-right: 0; - border-bottom: 1px solid #aaa; -} - -table.field-list td, table.field-list th { - border: 0 !important; -} - -table.footnote td, table.footnote th { - border: 0 !important; -} - -th { - text-align: left; - padding-right: 5px; -} - -table.citation { - border-left: solid 1px gray; - margin-left: 1px; -} - -table.citation td { - border-bottom: none; -} - -/* -- other body styles ----------------------------------------------------- */ - -ol.arabic { - list-style: decimal; -} - -ol.loweralpha { - list-style: lower-alpha; -} - -ol.upperalpha { - list-style: upper-alpha; -} - -ol.lowerroman { - list-style: lower-roman; -} - -ol.upperroman { - list-style: upper-roman; -} - -dl { - margin-bottom: 15px; -} - -dd p { - margin-top: 0px; -} - -dd ul, dd table { - margin-bottom: 10px; -} - -dd { - margin-top: 3px; - margin-bottom: 10px; - margin-left: 30px; -} - -dt:target, .highlighted { - background-color: #fbe54e; -} - -dl.glossary dt { - font-weight: bold; - font-size: 1.1em; -} - -.field-list ul { - margin: 0; - padding-left: 1em; -} - -.field-list p { - margin: 0; -} - -.refcount { - color: #060; -} - -.optional { - font-size: 1.3em; -} - -.versionmodified { - font-style: italic; -} - -.system-message { - background-color: #fda; - padding: 5px; - border: 3px solid red; -} - -.footnote:target { - background-color: #ffa; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} - -.guilabel, .menuselection { - font-family: sans-serif; -} - -.accelerator { - text-decoration: underline; -} - -.classifier { - font-style: oblique; -} - -abbr, acronym { - border-bottom: dotted 1px; - cursor: help; -} - -/* -- code displays --------------------------------------------------------- */ - -pre { - overflow: auto; - overflow-y: hidden; /* fixes display issues on Chrome browsers */ -} - -td.linenos pre { - padding: 5px 0px; - border: 0; - background-color: transparent; - color: #aaa; -} - -table.highlighttable { - margin-left: 0.5em; -} - -table.highlighttable td { - padding: 0 0.5em 0 0.5em; -} - -tt.descname { - background-color: transparent; - font-weight: bold; - font-size: 1.2em; -} - -tt.descclassname { - background-color: transparent; -} - -tt.xref, a tt { - background-color: transparent; - font-weight: bold; -} - -h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { - background-color: transparent; -} - -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - margin: -1px -10px; - padding: 0 10px; -} - -/* -- math display ---------------------------------------------------------- */ - -img.math { - vertical-align: middle; -} - -div.body div.math p { - text-align: center; -} - -span.eqno { - float: right; -} - -/* -- printout stylesheet --------------------------------------------------- */ - -@media print { - div.document, - div.documentwrapper, - div.bodywrapper { - margin: 0 !important; - width: 100%; - } - - div.sphinxsidebar, - div.related, - div.footer, - #top-link { - display: none; - } -} \ No newline at end of file diff --git a/_static/comment-bright.png b/_static/comment-bright.png deleted file mode 100644 index 551517b..0000000 Binary files a/_static/comment-bright.png and /dev/null differ diff --git a/_static/comment-close.png b/_static/comment-close.png deleted file mode 100644 index 09b54be..0000000 Binary files a/_static/comment-close.png and /dev/null differ diff --git a/_static/comment.png b/_static/comment.png deleted file mode 100644 index 92feb52..0000000 Binary files a/_static/comment.png and /dev/null differ diff --git a/_static/configurationblock.css b/_static/configurationblock.css deleted file mode 100644 index c1f987b..0000000 --- a/_static/configurationblock.css +++ /dev/null @@ -1,97 +0,0 @@ -div.configuration-block ul.simple -{ - margin: 0; - padding: 0; - margin-left: 30px; -} - -div.configuration-block ul.simple li -{ - margin: 0 !important; - margin-right: 5px !important; - display: inline; - margin-left: 10px; - padding: 10px; -} - -div.configuration-block em -{ - margin-bottom: 10px; -} - -div.configuration-block li -{ - padding: 5px; -} - -div.configuration-block em -{ - font-style: normal; - font-size: 90%; -} - -div.jsactive -{ - position: relative; -} - -div.jsactive ul -{ - list-style: none; -} - -div.jsactive li -{ - float: left; - list-style: none; - margin-left: 0; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - background-color: #ddd; - margin-right: 5px; -} - -div.jsactive .selected -{ - background-color: #000; -} - -div.jsactive .selected a -{ - color: #fff; - text-decoration: none; -} - -div.jsactive .selected a:hover -{ - color: #fff; - text-decoration: underline; -} - -div.jsactive a -{ - color: #000; - text-decoration: none; -} - -div.jsactive a:hover -{ - color: #000; - text-decoration: underline; -} - -div.jsactive div -{ - position: absolute; - top: 30px; - left: 0; -} - -div.jsactive div div -{ - position: static; -} - -div.jsactive pre -{ - margin: 0; -} diff --git a/_static/configurationblock.js b/_static/configurationblock.js deleted file mode 100644 index 0f708a1..0000000 --- a/_static/configurationblock.js +++ /dev/null @@ -1,34 +0,0 @@ -$(document).ready(function(){ - $('div.configuration-block [class^=highlight-]').hide(); - $('div.configuration-block [class^=highlight-]').width($('div.configuration-block').width()); - - $('div.configuration-block').addClass('jsactive'); - $('div.configuration-block').addClass('clearfix'); - - $('div.configuration-block').each(function (){ - var el = $('[class^=highlight-]:first', $(this)); - el.show(); - el.parents('ul').height(el.height() + 40); - }); - - // Global - $('div.configuration-block li').each(function(){ - var str = $(':first', $(this)).html(); - $(':first ', $(this)).html(''); - $(':first ', $(this)).append('' + str + '') - $(':first', $(this)).bind('click', function(){ - $('[class^=highlight-]', $(this).parents('ul')).hide(); - $('li', $(this).parents('ul')).removeClass('selected'); - $(this).parent().addClass('selected'); - - var block = $('[class^=highlight-]', $(this).parent('li')); - block.show(); - block.parents('ul').height(block.height() + 40); - return false; - }); - }); - - $('div.configuration-block').each(function (){ - $('li:first', $(this)).addClass('selected'); - }); -}); diff --git a/_static/default.css b/_static/default.css deleted file mode 100644 index 1024c0b..0000000 --- a/_static/default.css +++ /dev/null @@ -1,960 +0,0 @@ -/* - * default.css_t - * ~~~~~~~~~~~~~ - * - * Sphinx stylesheet -- default theme. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -@import url("basic.css"); - -div.sphinxsidebar { - width: 300px; -} - -body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,textarea,p,blockquote,th,td,button { margin:0; padding:0; } -ul,li { list-style-type:none; } -select { min-width: 1.5em;} -select > option { padding: 0 2px 0 3px; } -form { margin: 0; padding: 0; } -img { border: 0; } -hr { clear:both; display: none; } -label { display: none; } -fieldset { border: 0; } -q:before,q:after { content:''; } -abbr,acronym { border:0; } - -.reset div, -.reset dl, -.reset dt, -.reset dd, -.reset ul, -.reset ol, -.reset li, -.reset h1, -.reset h2, -.reset h3, -.reset h4, -.reset h5, -.reset h6, -.reset pre, -.reset code, -.reset form, -.reset fieldset, -.reset legend, -.reset input, -.reset textarea, -.reset p, -.reset blockquote, -.reset th, -.reset td{margin:0 !important;padding:0 !important;} -.reset table{border-collapse:collapse !important;border-spacing:0 !important;} -.reset fieldset,.reset img{border:0 !important;} -.reset address, -.reset caption, -.reset cite, -.reset code, -.reset dfn, -.reset em, -.reset strong, -.reset th, -.reset var{font-style:normal !important;font-weight:normal !important;} -.reset li{list-style:none !important;} -.reset caption,.reset th{text-align:left !important;} -.reset h1,.reset h2,.reset h3,.reset h4,.reset h5,.reset h6{font-size:100% !important;font-weight:normal !important;} -.reset q:before,.reset q:after{content:'' !important;} -.reset abbr,.reset acronym {border:0 !important;font-variant:normal !important;} -/* to preserve line-height and selector appearance */ -.reset sup {vertical-align:text-top !important;} -.reset sub {vertical-align:text-bottom !important;} -.reset input,.reset textarea,.reset select{font-family:inherit !important;font-size:inherit !important;font-weight:inherit !important;} -/*to enable resizing for IE*/ -.reset input,.reset textarea,.reset select{*font-size:100% !important;} -/*because legend doesn't inherit in IE */ -.reset legend{color:#000 !important;} - -/* -- page layout ----------------------------------------------------------- */ - -div.document { - margin-right: 30px; -} - -div.documentwrapper { - float: left; - width: 100%; -} - -div.bodywrapper { - margin: 0 0 0 300px; -} - -div.body { - background-color: #ffffff; - color: #000000; -} - -div.footer { - color: #ffffff; - width: 100%; - padding: 9px 0 9px 0; - text-align: center; - font-size: 75%; -} - -div.footer a { - color: #ffffff; - text-decoration: underline; -} - -div.related { - background-color: #ffc; - width: 100%; - font-size: 90%; - margin-top: -40px; - height: 35px; -} - -div.related h3 { - display: none; -} - -div.related ul { - margin: 0; - padding: 0 0 0 10px; - list-style: none; -} - -div.related li { - float: left; - font-weight: bold; -} - -div.related li.right { - float: right; - margin-right: 5px !important; - margin-left: 5px !important; -} -div.sphinxsidebar { - background: #f2f2f2; -} - -div.sphinxsidebar h3 { - font-family: 'Trebuchet MS', sans-serif; - color: #000000; - font-size: 1.4em; - font-weight: normal; - margin: 0; - padding: 0; - margin-left: 0 !important; -} - -div.sphinxsidebar h3 a { - color: #000000; -} - -div.sphinxsidebar h4 { - font-family: 'Trebuchet MS', sans-serif; - color: #000000; - font-size: 1.3em; - font-weight: normal; - margin: 5px 0 0 0; - padding: 0; - margin-left: 0 !important; -} - -div.sphinxsidebar p { - color: #000000; -} - -div.sphinxsidebar p.topless { - margin: 5px 10px 10px 10px; -} - -div.sphinxsidebar ul { - margin: 0; - padding: 0; - color: #000000; -} - -div.sphinxsidebar ul li { - margin-left: 5px !important; -} - -div.sphinxsidebar a { - color: #000000; - font-weight: bold; -} - -div.sphinxsidebar input { - border: 1px solid #98dbcc; - font-family: sans-serif; - font-size: 1em; -} - - - -/* -- hyperlink styles ------------------------------------------------------ */ - -a { - color: #355f7c; - text-decoration: none; -} - -a:visited { - color: #355f7c; - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - - - -/* -- body styles ----------------------------------------------------------- */ - -div.body h1, -div.body h2, -div.body h3, -div.body h4, -div.body h5, -div.body h6 { - font-family: 'Trebuchet MS', sans-serif; - background-color: #f2f2f2; - font-weight: normal; - color: #20435c; - border-bottom: 1px solid #ccc; - margin: 20px -20px 10px -20px; - padding: 3px 0 3px 10px; -} - -div.body h1 { margin-top: 0; font-size: 200%; } -div.body h2 { font-size: 160%; } -div.body h3 { font-size: 140%; } -div.body h4 { font-size: 120%; } -div.body h5 { font-size: 110%; } -div.body h6 { font-size: 100%; } - -a.headerlink { - color: #c60f0f; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none; -} - -a.headerlink:hover { - background-color: #c60f0f; - color: white; -} - -div.body p, div.body dd, div.body li { - text-align: justify; - line-height: 130%; -} - -div.highlight { - margin-left: 30px; -} - -div.highlight pre { - margin-left: 0; -} - -div.admonition p.admonition-title + p { - display: inline; -} - -div.admonition p { - margin-bottom: 5px; -} - -div.admonition pre { - margin-bottom: 5px; -} - -div.admonition ul, div.admonition ol { - margin-bottom: 5px; -} - -div.note { - background-color: #eee; - border: 1px solid #ccc; -} - -div.seealso { - background-color: #ffc; - border: 1px solid #ff6; -} - -div.topic { - background-color: #eee; -} - -div.warning { - background-color: #ffe4e4; - border: 1px solid #f66; -} - -p.admonition-title { - display: inline; -} - -p.admonition-title:after { - content: ":"; -} - -pre { - padding: 5px; - background-color: #eeffcc; - color: #71777E; - line-height: 120%; - border-left: none; - border-right: none; - margin-left: 30px; -} - -tt { - background-color: #ecf0f3; - padding: 0 1px 0 1px; - font-size: 1.4em; -} - -th { - background-color: #ede; -} - -.warning tt { - background: #efc2c2; -} - -.note tt { - background: #d6d6d6; -} - -.viewcode-back { - font-family: sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border-top: 1px solid #ac9; - border-bottom: 1px solid #ac9; -} - -img -{ - vertical-align: middle; -} - -.js_active -{ - cursor: pointer; - display: inline-block; - background: url(/images/btn-open.png) no-repeat 550px center; -} - -ul.error_list -{ - margin: 0; - list-style: none; - color: #f22; -} - -ul.error_list li -{ - list-style: none; -} - -pre -{ - padding: 0.7em; - background-color: #000; - line-height: 1.3em !important; - font-size: 100%; - color: #fff; -} - -pre code -{ - background-color: #000; -} - -pre.command-line -{ - background-color: #333; - color: #eee; - padding-bottom: 10px; -} - -pre.command-line code -{ - background-color: #333; -} - -blockquote -{ - padding: 2px 20px 5px 45px; - margin: 15px 0; - background-color: #fff; -} - -div.admonition -{ - padding: 2px 20px 5px 45px; - background-color: #fff; - margin-left: 30px; -} - -div.note -{ - background: #fff url(http://www.doctrine-project.org/images/note.png) no-repeat -5px -5px; -} - -div.caution -{ - background: #fff url(http://www.doctrine-project.org/images/caution.png) no-repeat -5px -5px; -} - -div.warning -{ - background: #fff url(http://www.doctrine-project.org/images/caution.png) no-repeat -5px -5px; -} - -div.tip -{ - background: #fff url(http://www.doctrine-project.org/images/tip.png) no-repeat -5px -5px; -} - -div.seealso -{ - background: #fff url(http://www.doctrine-project.org/images/tip.png) no-repeat -5px -5px; -} - -/* Note/Caution/Tip Boxes */ - -/* Note */ - -div.note { - padding: 5px 20px 5px 45px; - margin: 10px 0; - background: #ffd url(http://www.doctrine-project.org/images/note.png) no-repeat 5px 10px; -} - -/* Caution */ - -div.caution, div.warning { - padding: 5px 20px 5px 45px; - margin: 10px 0; - background: #ffd url(http://www.doctrine-project.org/images/caution.png) no-repeat 5px 10px; -} - -/* Tip */ - -div.tip { - padding: 5px 20px 5px 45px; - margin: 10px 0; - background: #ffd url(http://www.doctrine-project.org/images/tip.png) no-repeat 5px 10px; -} - -/* Sidebar */ - -div.seealso { - margin: 10px 0; - padding: 10px; - background: #ccc; -} - -div.seealso ul { - margin: 10px; -} - -div.seealso p.title { - margin: -20px; - margin-bottom: 10px !important; - padding: 10px !important; - background: #aaa; - color: #fff; - font-weight: bold; -} - -/* Shared Styles */ -div.note, div.caution, div.tip, div.seealso, div.warning { - overflow/**/: auto; - margin-left: 30px; - font-weight: normal; - min-height: 40px; - border: 1px solid #ddd; -} - -div.note p, div.caution p, div.tip p, div.seealso p, div.warning p { - margin: 0 !important; - padding: 0 !important; - padding-top: 10px; -} - -.admonition-title -{ - display: none !important; -} - -blockquote.quote -{ - background: #D7CABA; -} - -blockquote.sidebar -{ - padding: 10px; - background: #eee; -} - -blockquote.sidebar p.title -{ - margin: -10px; - margin-bottom: 10px; - padding: 10px; - background: #ddd; - font-style: italic; -} - -.navigation -{ - font-family: Arial, sans-serif; - padding: 10px 15px; - font-size: 0.9em; - text-align:center; - background-color: #e3e3e3; - border: 1px solid #e3e3e3; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; -} - -.navigation a -{ - text-decoration: none; -} - -.navigation a:hover -{ - text-decoration: underline; -} - -.navigation .separator -{ - padding: 0 10px; - color: #ccc; -} - -.feedback p -{ - font-family: Arial, sans-serif; - color: #858585; - font-size: 0.8em; -} - -.feedback h3 -{ - border-top: 1px solid #ddd; - font-family: Georgia, "Times New Roman", serif; - margin-top: 10px; - padding-top: 20px; - color: #858585; -} - -.toc -{ - margin-top: 10px; - padding: 10px; - background-color: #f1f1f1; - border: 1px solid #e3e3e3; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - font-family: Arial, sans-serif; -} - -.toc h2 -{ - margin: 0; - padding: 0; -} - -.pages -{ - padding: 10px 0 0 0; -} - -.pages ul.inline -{ - display: inline; - padding: 5px 0 0 0; -} - -.pages .inline li -{ - display: inline; - margin: 0 5px; -} - -#quick-tour ul.simple -{ - padding: 5px 0 0 0; -} - -#quick-tour ul.simple li -{ - margin: 0; - margin-right: 5px; - display: inline; -} - -.toc a -{ - text-decoration: none; - color: #777; -} - -.toc a:hover -{ - text-decoration: underline; -} - -.bd .content .toc li -{ - padding: 2px; - list-style: square; - margin-left: 15px; -} - -.bd .content .toc li.current -{ - font-weight: bold; - background-color: #e3e3e3; -} - -.bd .content .toc ul.inline -{ - padding: 0; - margin: 0; - margin-left: 3px; -} - -.bd .content .toc .inline li -{ - margin: 0; - padding: 0; -} - -.bd .content .toc li.separator -{ - color: #ccc; -} - -#release_info -{ - background-color: #e3e3e3; - border: 1px solid #e3e3e3; - margin-bottom: 15px; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - width: 290px; -} - -#license -{ - margin-top: 40px; - line-height: 1.2em; - font-size: 80%; -} - -#license img -{ - margin-right: 5px; -} - -table.docutils -{ - margin-bottom: 10px; -} - -table.docutils th -{ - font-weight:bold; - background-color: #efefef; -} - -table.docutils td, table.docutils th -{ - padding: 4px 6px; - border: 0; - border-bottom: 1px solid #ddd; - text-align: left; - vertical-align: top; -} - -.menu -{ - float: right; - width: 300px; - margin: 15px; - font-size: 0.7em; - background-color: #fff; - position: relative; - z-index: 999; -} - -#searchform -{ - display: inline; -} - -#search -{ - -webkit-appearance: searchfield; -} - -span.pre -{ - font-size: 85%; -} - -.bd .content .navigation li -{ - margin-left: 0; -} - -a.headerlink -{ - padding: 2px; - color: #ddd; - text-decoration: none; - font-size: 80%; -} - -a.reference em, a.internal em -{ - font-style: normal; -} - -#guides ul ul, #contributing ul ul -{ - display: inline; - padding: 5px 0 0 0; -} - -#guides ul ul li, #contributing ul ul li -{ - display: inline; - margin: 0; -} - -.sidebarbox -{ - margin-top: 10px; - padding: 10px; - background-color: #f1f1f1; - border: 1px solid #e3e3e3; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - font-family: Arial, sans-serif; -} - -.sidebarbox h2 -{ - margin: 0; - padding: 0; -} - -.sidebarbox h3 -{ - margin: 0; - padding: 0; - margin-top: 5px; -} - -#searchbox h3 a.nolink -{ - padding: 0; - text-decoration: none; -} - -#searchbox h3 a.nolink:hover -{ - text-decoration: underline; -} - -div.breadcrumb h3 -{ - display: none; -} - -.bd .content div.breadcrumb ul -{ - margin: 0; - padding: 0; - list-style: none; - margin-top: 5px; -} - -.bd .content div.breadcrumb li -{ - display: inline; - margin: 0; - padding: 0; - line-height: 0.9em; -} - -.bd .content div.breadcrumb li a -{ - color: #777; - text-decoration: none; -} - -.bd .content div.breadcrumb li a:hover -{ - text-decoration: underline; -} - -.p-Indicator -{ - color: #FF8400; -} - -.bd .content ul.search li -{ - margin-left: 0; - padding: 5px 0 5px 20px; - background-image: url(file.png); - background-repeat: no-repeat; - background-position: 0 7px; -} - -div.genindex-jumpbox -{ - font-size: 85%; - border: 0; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -div.genindex-jumpbox -{ - color: #999; -} - -div.genindex-jumpbox strong -{ - font-weight: normal; -} - -div.genindex-jumpbox a -{ - padding: 0 4px; -} - -h2#A, h2#B, h2#C, h2#D, h2#E, h2#F, h2#G, h2#H, h2#I, h2#J, h2#K, h2#L, h2#M, h2#N, h2#O, -h2#P, h2#Q, h2#R, h2#S, h2#T, h2#U, h2#V, h2#W, h2#X, h2#Y, h2#Z -{ - background-color: #eee; - border-bottom: 1px solid #aaa; - font-size: 120%; - font-weight: bold; - margin: 20px 0; - padding: 5px; -} - -.indextable a, div.genindex-jumpbox a -{ - text-decoration: none; -} - -.indextable a:hover, div.genindex-jumpbox a:hover -{ - text-decoration: underline; -} - -div.configuration-block em -{ - margin-bottom: 10px; -} - -.bd .content div.configuration-block li -{ - padding: 5px; -} - -.bd .content div.configuration-block em -{ - font-style: normal; - font-size: 90%; -} - -div.jsactive -{ - position: relative; -} - -.bd .content div.jsactive ul -{ - list-style: none; -} - -.bd .content div.jsactive li -{ - float: left; - list-style: none; - margin-left: 0; - -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; - background-color: #ddd; - margin-right: 5px; -} - -.bd .content div.jsactive .selected -{ - background-color: #000; -} - -.bd .content div.jsactive .selected a -{ - color: #fff; - text-decoration: none; -} - -.bd .content div.jsactive .selected a:hover -{ - color: #fff; - text-decoration: underline; -} - -.bd .content div.jsactive a -{ - color: #000; - text-decoration: none; -} - -.bd .content div.jsactive a:hover -{ - color: #000; - text-decoration: underline; -} - -div.jsactive div -{ - position: absolute; - top: 30px; - left: 0; -} - -div.jsactive div div -{ - position: static; -} - -div.jsactive pre -{ - margin: 0; -} - -.highlight -{ - overflow: auto; - margin-bottom: 10px; -} \ No newline at end of file diff --git a/_static/doctools.js b/_static/doctools.js deleted file mode 100644 index d4619fd..0000000 --- a/_static/doctools.js +++ /dev/null @@ -1,247 +0,0 @@ -/* - * doctools.js - * ~~~~~~~~~~~ - * - * Sphinx JavaScript utilities for all documentation. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * select a different prefix for underscore - */ -$u = _.noConflict(); - -/** - * make the code below compatible with browsers without - * an installed firebug like debugger -if (!window.console || !console.firebug) { - var names = ["log", "debug", "info", "warn", "error", "assert", "dir", - "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", - "profile", "profileEnd"]; - window.console = {}; - for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {}; -} - */ - -/** - * small helper function to urldecode strings - */ -jQuery.urldecode = function(x) { - return decodeURIComponent(x).replace(/\+/g, ' '); -} - -/** - * small helper function to urlencode strings - */ -jQuery.urlencode = encodeURIComponent; - -/** - * This function returns the parsed url parameters of the - * current request. Multiple values per key are supported, - * it will always return arrays of strings for the value parts. - */ -jQuery.getQueryParameters = function(s) { - if (typeof s == 'undefined') - s = document.location.search; - var parts = s.substr(s.indexOf('?') + 1).split('&'); - var result = {}; - for (var i = 0; i < parts.length; i++) { - var tmp = parts[i].split('=', 2); - var key = jQuery.urldecode(tmp[0]); - var value = jQuery.urldecode(tmp[1]); - if (key in result) - result[key].push(value); - else - result[key] = [value]; - } - return result; -}; - -/** - * small function to check if an array contains - * a given item. - */ -jQuery.contains = function(arr, item) { - for (var i = 0; i < arr.length; i++) { - if (arr[i] == item) - return true; - } - return false; -}; - -/** - * highlight a given string on a jquery object by wrapping it in - * span elements with the given class name. - */ -jQuery.fn.highlightText = function(text, className) { - function highlight(node) { - if (node.nodeType == 3) { - var val = node.nodeValue; - var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { - var span = document.createElement("span"); - span.className = className; - span.appendChild(document.createTextNode(val.substr(pos, text.length))); - node.parentNode.insertBefore(span, node.parentNode.insertBefore( - document.createTextNode(val.substr(pos + text.length)), - node.nextSibling)); - node.nodeValue = val.substr(0, pos); - } - } - else if (!jQuery(node).is("button, select, textarea")) { - jQuery.each(node.childNodes, function() { - highlight(this); - }); - } - } - return this.each(function() { - highlight(this); - }); -}; - -/** - * Small JavaScript module for the documentation. - */ -var Documentation = { - - init : function() { - this.fixFirefoxAnchorBug(); - this.highlightSearchWords(); - this.initIndexTable(); - }, - - /** - * i18n support - */ - TRANSLATIONS : {}, - PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, - LOCALE : 'unknown', - - // gettext and ngettext don't access this so that the functions - // can safely bound to a different name (_ = Documentation.gettext) - gettext : function(string) { - var translated = Documentation.TRANSLATIONS[string]; - if (typeof translated == 'undefined') - return string; - return (typeof translated == 'string') ? translated : translated[0]; - }, - - ngettext : function(singular, plural, n) { - var translated = Documentation.TRANSLATIONS[singular]; - if (typeof translated == 'undefined') - return (n == 1) ? singular : plural; - return translated[Documentation.PLURALEXPR(n)]; - }, - - addTranslations : function(catalog) { - for (var key in catalog.messages) - this.TRANSLATIONS[key] = catalog.messages[key]; - this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); - this.LOCALE = catalog.locale; - }, - - /** - * add context elements like header anchor links - */ - addContextElements : function() { - $('div[id] > :header:first').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this headline')). - appendTo(this); - }); - $('dt[id]').each(function() { - $('\u00B6'). - attr('href', '#' + this.id). - attr('title', _('Permalink to this definition')). - appendTo(this); - }); - }, - - /** - * workaround a firefox stupidity - */ - fixFirefoxAnchorBug : function() { - if (document.location.hash && $.browser.mozilla) - window.setTimeout(function() { - document.location.href += ''; - }, 10); - }, - - /** - * highlight the search words provided in the url in the text - */ - highlightSearchWords : function() { - var params = $.getQueryParameters(); - var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; - if (terms.length) { - var body = $('div.body'); - window.setTimeout(function() { - $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlighted'); - }); - }, 10); - $('') - .appendTo($('#searchbox')); - } - }, - - /** - * init the domain index toggle buttons - */ - initIndexTable : function() { - var togglers = $('img.toggler').click(function() { - var src = $(this).attr('src'); - var idnum = $(this).attr('id').substr(7); - $('tr.cg-' + idnum).toggle(); - if (src.substr(-9) == 'minus.png') - $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); - else - $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); - }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { - togglers.click(); - } - }, - - /** - * helper function to hide the search marks again - */ - hideSearchWords : function() { - $('#searchbox .highlight-link').fadeOut(300); - $('span.highlighted').removeClass('highlighted'); - }, - - /** - * make the url absolute - */ - makeURL : function(relativeURL) { - return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; - }, - - /** - * get the current relative url - */ - getCurrentURL : function() { - var path = document.location.pathname; - var parts = path.split(/\//); - $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { - if (this == '..') - parts.pop(); - }); - var url = parts.join('/'); - return path.substring(url.lastIndexOf('/') + 1, path.length - 1); - } -}; - -// quick alias for translations -_ = Documentation.gettext; - -$(document).ready(function() { - Documentation.init(); -}); diff --git a/_static/down-pressed.png b/_static/down-pressed.png deleted file mode 100644 index 6f7ad78..0000000 Binary files a/_static/down-pressed.png and /dev/null differ diff --git a/_static/down.png b/_static/down.png deleted file mode 100644 index 3003a88..0000000 Binary files a/_static/down.png and /dev/null differ diff --git a/_static/file.png b/_static/file.png deleted file mode 100644 index d18082e..0000000 Binary files a/_static/file.png and /dev/null differ diff --git a/_static/icotnp.ico b/_static/icotnp.ico deleted file mode 100644 index 55dea01..0000000 Binary files a/_static/icotnp.ico and /dev/null differ diff --git a/_static/jquery.js b/_static/jquery.js deleted file mode 100644 index 7c24308..0000000 --- a/_static/jquery.js +++ /dev/null @@ -1,154 +0,0 @@ -/*! - * jQuery JavaScript Library v1.4.2 - * http://jquery.com/ - * - * Copyright 2010, John Resig - * Dual licensed under the MIT or GPL Version 2 licenses. - * http://jquery.org/license - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * Copyright 2010, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * - * Date: Sat Feb 13 22:33:48 2010 -0500 - */ -(function(A,w){function ma(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(ma,1);return}c.ready()}}function Qa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function X(a,b,d,f,e,j){var i=a.length;if(typeof b==="object"){for(var o in b)X(a,o,b[o],f,e,d);return a}if(d!==w){f=!j&&f&&c.isFunction(d);for(o=0;o)[^>]*$|^#([\w-]+)$/,Ua=/^.[^:#\[\.,]*$/,Va=/\S/, -Wa=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Xa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],L,$=Object.prototype.toString,aa=Object.prototype.hasOwnProperty,ba=Array.prototype.push,R=Array.prototype.slice,ya=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(a==="body"&&!b){this.context=s;this[0]=s.body;this.selector="body";this.length=1;return this}if(typeof a==="string")if((d=Ta.exec(a))&& -(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Xa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])];c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=sa([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}return c.merge(this,a)}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return T.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a);return c.merge(this, -a)}else return!b||b.jquery?(b||T).find(a):c(b).find(a);else if(c.isFunction(a))return T.ready(a);if(a.selector!==w){this.selector=a.selector;this.context=a.context}return c.makeArray(a,this)},selector:"",jquery:"1.4.2",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){var f=c();c.isArray(a)?ba.apply(f,a):c.merge(f,a);f.prevObject=this;f.context=this.context;if(b=== -"find")f.selector=this.selector+(this.selector?" ":"")+d;else if(b)f.selector=this.selector+"."+b+"("+d+")";return f},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this, -function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject||c(null)},push:ba,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,j,i,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
      a"; -var e=d.getElementsByTagName("*"),j=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!j)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(j.getAttribute("style")),hrefNormalized:j.getAttribute("href")==="/a",opacity:/^0.55$/.test(j.style.opacity),cssFloat:!!j.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected, -parentNode:d.removeChild(d.appendChild(s.createElement("div"))).parentNode===null,deleteExpando:true,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(i){}a.insertBefore(b,a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}try{delete b.test}catch(o){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function k(){c.support.noCloneEvent= -false;d.detachEvent("onclick",k)});d.cloneNode(true).fireEvent("onclick")}d=s.createElement("div");d.innerHTML="";a=s.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var k=s.createElement("div");k.style.width=k.style.paddingLeft="1px";s.body.appendChild(k);c.boxModel=c.support.boxModel=k.offsetWidth===2;s.body.removeChild(k).style.display="none"});a=function(k){var n= -s.createElement("div");k="on"+k;var r=k in n;if(!r){n.setAttribute(k,"return;");r=typeof n[k]==="function"}return r};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=j=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var G="jQuery"+J(),Ya=0,za={};c.extend({cache:{},expando:G,noData:{embed:true,object:true, -applet:true},data:function(a,b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var f=a[G],e=c.cache;if(!f&&typeof b==="string"&&d===w)return null;f||(f=++Ya);if(typeof b==="object"){a[G]=f;e[f]=c.extend(true,{},b)}else if(!e[f]){a[G]=f;e[f]={}}a=e[f];if(d!==w)a[b]=d;return typeof b==="string"?a[b]:a}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?za:a;var d=a[G],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{if(c.support.deleteExpando)delete a[c.expando]; -else a.removeAttribute&&a.removeAttribute(c.expando);delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this, -a,b)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b=== -w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var Aa=/[\n\t]/g,ca=/\s+/,Za=/\r/g,$a=/href|src|style/,ab=/(button|input)/i,bb=/(button|input|object|select|textarea)/i, -cb=/^(a|area)$/i,Ba=/radio|checkbox/;c.fn.extend({attr:function(a,b){return X(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(n){var r=c(this);r.addClass(a.call(this,n,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ca),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var j=b?d:0;for(d=b?d+1:e.length;j=0;else if(c.nodeName(this,"select")){var u=c.makeArray(r);c("option",this).each(function(){this.selected= -c.inArray(c(this).val(),u)>=0});if(!u.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var j=$a.test(b);if(b in a&&f&&!j){if(e){b==="type"&&ab.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed"); -a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:bb.test(a.nodeName)||cb.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&j?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var O=/\.(.*)$/,db=function(a){return a.replace(/[^\w\s\.\|`]/g, -function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;var e,j;if(d.handler){e=d;d=e.handler}if(!d.guid)d.guid=c.guid++;if(j=c.data(a)){var i=j.events=j.events||{},o=j.handle;if(!o)j.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,arguments):w};o.elem=a;b=b.split(" ");for(var k,n=0,r;k=b[n++];){j=e?c.extend({},e):{handler:d,data:f};if(k.indexOf(".")>-1){r=k.split("."); -k=r.shift();j.namespace=r.slice(0).sort().join(".")}else{r=[];j.namespace=""}j.type=k;j.guid=d.guid;var u=i[k],z=c.event.special[k]||{};if(!u){u=i[k]=[];if(!z.setup||z.setup.call(a,f,r,o)===false)if(a.addEventListener)a.addEventListener(k,o,false);else a.attachEvent&&a.attachEvent("on"+k,o)}if(z.add){z.add.call(a,j);if(!j.handler.guid)j.handler.guid=d.guid}u.push(j);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,f){if(!(a.nodeType===3||a.nodeType===8)){var e,j=0,i,o,k,n,r,u,z=c.data(a), -C=z&&z.events;if(z&&C){if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(e in C)c.event.remove(a,e+b)}else{for(b=b.split(" ");e=b[j++];){n=e;i=e.indexOf(".")<0;o=[];if(!i){o=e.split(".");e=o.shift();k=new RegExp("(^|\\.)"+c.map(o.slice(0).sort(),db).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(r=C[e])if(d){n=c.event.special[e]||{};for(B=f||0;B=0){a.type= -e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(f=c.data(d,"handle"))&&f.apply(d,b);f=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+e]&&d["on"+e].apply(d,b)===false)a.result=false}catch(j){}if(!a.isPropagationStopped()&& -f)c.event.trigger(a,b,f,true);else if(!a.isDefaultPrevented()){f=a.target;var i,o=c.nodeName(f,"a")&&e==="click",k=c.event.special[e]||{};if((!k._default||k._default.call(d,a)===false)&&!o&&!(f&&f.nodeName&&c.noData[f.nodeName.toLowerCase()])){try{if(f[e]){if(i=f["on"+e])f["on"+e]=null;c.event.triggered=true;f[e]()}}catch(n){}if(i)f["on"+e]=i;c.event.triggered=false}}},handle:function(a){var b,d,f,e;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive; -if(!b){d=a.type.split(".");a.type=d.shift();f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)")}e=c.data(this,"events");d=e[a.type];if(e&&d){d=d.slice(0);e=0;for(var j=d.length;e-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},fa=function(a,b){var d=a.target,f,e;if(!(!da.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Fa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data", -e);if(!(f===w||e===f))if(f!=null||e){a.type="change";return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:fa,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return fa.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return fa.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a, -"_change_data",Fa(a))}},setup:function(){if(this.type==="file")return false;for(var a in ea)c.event.add(this,a+".specialChange",ea[a]);return da.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return da.test(this.nodeName)}};ea=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a, -d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,f,e){if(typeof d==="object"){for(var j in d)this[b](j,f,d[j],e);return this}if(c.isFunction(f)){e=f;f=w}var i=b==="one"?c.proxy(e,function(k){c(this).unbind(k,i);return e.apply(this,arguments)}):e;if(d==="unload"&&b!=="one")this.one(d,f,e);else{j=0;for(var o=this.length;j0){y=t;break}}t=t[g]}m[q]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, -e=0,j=Object.prototype.toString,i=false,o=true;[0,0].sort(function(){o=false;return 0});var k=function(g,h,l,m){l=l||[];var q=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return l;for(var p=[],v,t,y,S,H=true,M=x(h),I=g;(f.exec(""),v=f.exec(I))!==null;){I=v[3];p.push(v[1]);if(v[2]){S=v[3];break}}if(p.length>1&&r.exec(g))if(p.length===2&&n.relative[p[0]])t=ga(p[0]+p[1],h);else for(t=n.relative[p[0]]?[h]:k(p.shift(),h);p.length;){g=p.shift();if(n.relative[g])g+=p.shift(); -t=ga(g,t)}else{if(!m&&p.length>1&&h.nodeType===9&&!M&&n.match.ID.test(p[0])&&!n.match.ID.test(p[p.length-1])){v=k.find(p.shift(),h,M);h=v.expr?k.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:p.pop(),set:z(m)}:k.find(p.pop(),p.length===1&&(p[0]==="~"||p[0]==="+")&&h.parentNode?h.parentNode:h,M);t=v.expr?k.filter(v.expr,v.set):v.set;if(p.length>0)y=z(t);else H=false;for(;p.length;){var D=p.pop();v=D;if(n.relative[D])v=p.pop();else D="";if(v==null)v=h;n.relative[D](y,v,M)}}else y=[]}y||(y=t);y||k.error(D|| -g);if(j.call(y)==="[object Array]")if(H)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&E(h,y[g])))l.push(t[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&l.push(t[g]);else l.push.apply(l,y);else z(y,l);if(S){k(S,q,l,m);k.uniqueSort(l)}return l};k.uniqueSort=function(g){if(B){i=o;g.sort(B);if(i)for(var h=1;h":function(g,h){var l=typeof h==="string";if(l&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,q=g.length;m=0))l||m.push(v);else if(l)h[p]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, -CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,l,m,q,p){h=g[1].replace(/\\/g,"");if(!p&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,l,m,q){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,h);else{g=k.filter(g[3],h,l,true^q);l||m.push.apply(m, -g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,l){return!!k(l[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, -text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, -setFilters:{first:function(g,h){return h===0},last:function(g,h,l,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,l){return hl[3]-0},nth:function(g,h,l){return l[3]-0===h},eq:function(g,h,l){return l[3]-0===h}},filter:{PSEUDO:function(g,h,l,m){var q=h[1],p=n.filters[q];if(p)return p(g,l,h,m);else if(q==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(q==="not"){h= -h[3];l=0;for(m=h.length;l=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var l=h[1];g=n.attrHandle[l]?n.attrHandle[l](g):g[l]!=null?g[l]:g.getAttribute(l);l=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== -"="?l===h:m==="*="?l.indexOf(h)>=0:m==="~="?(" "+l+" ").indexOf(h)>=0:!h?l&&g!==false:m==="!="?l!==h:m==="^="?l.indexOf(h)===0:m==="$="?l.substr(l.length-h.length)===h:m==="|="?l===h||l.substr(0,h.length+1)===h+"-":false},POS:function(g,h,l,m){var q=n.setFilters[h[2]];if(q)return q(g,l,h,m)}}},r=n.match.POS;for(var u in n.match){n.match[u]=new RegExp(n.match[u].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[u]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[u].source.replace(/\\(\d+)/g,function(g, -h){return"\\"+(h-0+1)}))}var z=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){z=function(g,h){h=h||[];if(j.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var l=0,m=g.length;l";var l=s.documentElement;l.insertBefore(g,l.firstChild);if(s.getElementById(h)){n.find.ID=function(m,q,p){if(typeof q.getElementById!=="undefined"&&!p)return(q=q.getElementById(m[1]))?q.id===m[1]||typeof q.getAttributeNode!=="undefined"&& -q.getAttributeNode("id").nodeValue===m[1]?[q]:w:[]};n.filter.ID=function(m,q){var p=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&p&&p.nodeValue===q}}l.removeChild(g);l=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,l){l=l.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;l[m];m++)l[m].nodeType===1&&h.push(l[m]);l=h}return l};g.innerHTML=""; -if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=k,h=s.createElement("div");h.innerHTML="

      ";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){k=function(m,q,p,v){q=q||s;if(!v&&q.nodeType===9&&!x(q))try{return z(q.querySelectorAll(m),p)}catch(t){}return g(m,q,p,v)};for(var l in g)k[l]=g[l];h=null}}(); -(function(){var g=s.createElement("div");g.innerHTML="
      ";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,l,m){if(typeof l.getElementsByClassName!=="undefined"&&!m)return l.getElementsByClassName(h[1])};g=null}}})();var E=s.compareDocumentPosition?function(g,h){return!!(g.compareDocumentPosition(h)&16)}: -function(g,h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ga=function(g,h){var l=[],m="",q;for(h=h.nodeType?[h]:h;q=n.match.PSEUDO.exec(g);){m+=q[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;q=0;for(var p=h.length;q=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var j=d;j0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,j= -{},i;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:i,elem:f});delete j[i]}}f=f.parentNode}}return d}var k=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,r){for(;r&&r.ownerDocument&&r!==b;){if(k?k.index(r)>-1:c(r).is(a))return r;r=r.parentNode}return null})},index:function(a){if(!a||typeof a=== -"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(qa(a[0])||qa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", -d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? -a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);eb.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||gb.test(f))&&fb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||a.nodeType!==1||!c(a).is(d));){a.nodeType=== -1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ja=/ jQuery\d+="(?:\d+|null)"/g,V=/^\s+/,Ka=/(<([\w:]+)[^>]*?)\/>/g,hb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,La=/<([\w:]+)/,ib=/"},F={option:[1,""],legend:[1,"
      ","
      "],thead:[1,"","
      "],tr:[2,"","
      "],td:[3,"","
      "],col:[2,"","
      "],area:[1,"",""],_default:[0,"",""]};F.optgroup=F.option;F.tbody=F.tfoot=F.colgroup=F.caption=F.thead;F.th=F.td;if(!c.support.htmlSerialize)F._default=[1,"div
      ","
      "];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d= -c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, -wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})}, -prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b, -this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,f;(f=this[d])!=null;d++)if(!a||c.filter(a,[f]).length){if(!b&&f.nodeType===1){c.cleanData(f.getElementsByTagName("*"));c.cleanData([f])}f.parentNode&&f.parentNode.removeChild(f)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild); -return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ja,"").replace(/=([^="'>\s]+\/)>/g,'="$1">').replace(V,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ra(this,b);ra(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(Ja, -""):null;else if(typeof a==="string"&&!ta.test(a)&&(c.support.leadingWhitespace||!V.test(a))&&!F[(La.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Ka,Ma);try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?k.cloneNode(true):k)}o.length&&c.each(o,Qa)}return this}});c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var f=[];d=c(d);var e=this.length===1&&this[0].parentNode;if(e&&e.nodeType===11&&e.childNodes.length===1&&d.length===1){d[b](this[0]); -return this}else{e=0;for(var j=d.length;e0?this.clone(true):this).get();c.fn[b].apply(c(d[e]),i);f=f.concat(i)}return this.pushStack(f,a,d.selector)}}});c.extend({clean:function(a,b,d,f){b=b||s;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||s;for(var e=[],j=0,i;(i=a[j])!=null;j++){if(typeof i==="number")i+="";if(i){if(typeof i==="string"&&!jb.test(i))i=b.createTextNode(i);else if(typeof i==="string"){i=i.replace(Ka,Ma);var o=(La.exec(i)||["", -""])[1].toLowerCase(),k=F[o]||F._default,n=k[0],r=b.createElement("div");for(r.innerHTML=k[1]+i+k[2];n--;)r=r.lastChild;if(!c.support.tbody){n=ib.test(i);o=o==="table"&&!n?r.firstChild&&r.firstChild.childNodes:k[1]===""&&!n?r.childNodes:[];for(k=o.length-1;k>=0;--k)c.nodeName(o[k],"tbody")&&!o[k].childNodes.length&&o[k].parentNode.removeChild(o[k])}!c.support.leadingWhitespace&&V.test(i)&&r.insertBefore(b.createTextNode(V.exec(i)[0]),r.firstChild);i=r.childNodes}if(i.nodeType)e.push(i);else e= -c.merge(e,i)}}if(d)for(j=0;e[j];j++)if(f&&c.nodeName(e[j],"script")&&(!e[j].type||e[j].type.toLowerCase()==="text/javascript"))f.push(e[j].parentNode?e[j].parentNode.removeChild(e[j]):e[j]);else{e[j].nodeType===1&&e.splice.apply(e,[j+1,0].concat(c.makeArray(e[j].getElementsByTagName("script"))));d.appendChild(e[j])}return e},cleanData:function(a){for(var b,d,f=c.cache,e=c.event.special,j=c.support.deleteExpando,i=0,o;(o=a[i])!=null;i++)if(d=o[c.expando]){b=f[d];if(b.events)for(var k in b.events)e[k]? -c.event.remove(o,k):Ca(o,k,b.handle);if(j)delete o[c.expando];else o.removeAttribute&&o.removeAttribute(c.expando);delete f[d]}}});var kb=/z-?index|font-?weight|opacity|zoom|line-?height/i,Na=/alpha\([^)]*\)/,Oa=/opacity=([^)]*)/,ha=/float/i,ia=/-([a-z])/ig,lb=/([A-Z])/g,mb=/^-?\d+(?:px)?$/i,nb=/^-?\d/,ob={position:"absolute",visibility:"hidden",display:"block"},pb=["Left","Right"],qb=["Top","Bottom"],rb=s.defaultView&&s.defaultView.getComputedStyle,Pa=c.support.cssFloat?"cssFloat":"styleFloat",ja= -function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){return X(this,a,b,true,function(d,f,e){if(e===w)return c.curCSS(d,f);if(typeof e==="number"&&!kb.test(f))e+="px";c.style(d,f,e)})};c.extend({style:function(a,b,d){if(!a||a.nodeType===3||a.nodeType===8)return w;if((b==="width"||b==="height")&&parseFloat(d)<0)d=w;var f=a.style||a,e=d!==w;if(!c.support.opacity&&b==="opacity"){if(e){f.zoom=1;b=parseInt(d,10)+""==="NaN"?"":"alpha(opacity="+d*100+")";a=f.filter||c.curCSS(a,"filter")||"";f.filter= -Na.test(a)?a.replace(Na,b):b}return f.filter&&f.filter.indexOf("opacity=")>=0?parseFloat(Oa.exec(f.filter)[1])/100+"":""}if(ha.test(b))b=Pa;b=b.replace(ia,ja);if(e)f[b]=d;return f[b]},css:function(a,b,d,f){if(b==="width"||b==="height"){var e,j=b==="width"?pb:qb;function i(){e=b==="width"?a.offsetWidth:a.offsetHeight;f!=="border"&&c.each(j,function(){f||(e-=parseFloat(c.curCSS(a,"padding"+this,true))||0);if(f==="margin")e+=parseFloat(c.curCSS(a,"margin"+this,true))||0;else e-=parseFloat(c.curCSS(a, -"border"+this+"Width",true))||0})}a.offsetWidth!==0?i():c.swap(a,ob,i);return Math.max(0,Math.round(e))}return c.curCSS(a,b,d)},curCSS:function(a,b,d){var f,e=a.style;if(!c.support.opacity&&b==="opacity"&&a.currentStyle){f=Oa.test(a.currentStyle.filter||"")?parseFloat(RegExp.$1)/100+"":"";return f===""?"1":f}if(ha.test(b))b=Pa;if(!d&&e&&e[b])f=e[b];else if(rb){if(ha.test(b))b="float";b=b.replace(lb,"-$1").toLowerCase();e=a.ownerDocument.defaultView;if(!e)return null;if(a=e.getComputedStyle(a,null))f= -a.getPropertyValue(b);if(b==="opacity"&&f==="")f="1"}else if(a.currentStyle){d=b.replace(ia,ja);f=a.currentStyle[b]||a.currentStyle[d];if(!mb.test(f)&&nb.test(f)){b=e.left;var j=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;e.left=d==="fontSize"?"1em":f||0;f=e.pixelLeft+"px";e.left=b;a.runtimeStyle.left=j}}return f},swap:function(a,b,d){var f={};for(var e in b){f[e]=a.style[e];a.style[e]=b[e]}d.call(a);for(e in b)a.style[e]=f[e]}});if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b= -a.offsetWidth,d=a.offsetHeight,f=a.nodeName.toLowerCase()==="tr";return b===0&&d===0&&!f?true:b>0&&d>0&&!f?false:c.curCSS(a,"display")==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var sb=J(),tb=//gi,ub=/select|textarea/i,vb=/color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week/i,N=/=\?(&|$)/,ka=/\?/,wb=/(\?|&)_=.*?(&|$)/,xb=/^(\w+:)?\/\/([^\/?#]+)/,yb=/%20/g,zb=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!== -"string")return zb.call(this,a);else if(!this.length)return this;var f=a.indexOf(" ");if(f>=0){var e=a.slice(f,a.length);a=a.slice(0,f)}f="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b==="object"){b=c.param(b,c.ajaxSettings.traditional);f="POST"}var j=this;c.ajax({url:a,type:f,dataType:"html",data:b,complete:function(i,o){if(o==="success"||o==="notmodified")j.html(e?c("
      ").append(i.responseText.replace(tb,"")).find(e):i.responseText);d&&j.each(d,[i.responseText,o,i])}});return this}, -serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ub.test(this.nodeName)||vb.test(this.type))}).map(function(a,b){a=c(this).val();return a==null?null:c.isArray(a)?c.map(a,function(d){return{name:b.name,value:d}}):{name:b.name,value:a}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "), -function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:f})},getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,f){if(c.isFunction(b)){f=f||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:f})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href, -global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:A.XMLHttpRequest&&(A.location.protocol!=="file:"||!A.ActiveXObject)?function(){return new A.XMLHttpRequest}:function(){try{return new A.ActiveXObject("Microsoft.XMLHTTP")}catch(a){}},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},etag:{},ajax:function(a){function b(){e.success&& -e.success.call(k,o,i,x);e.global&&f("ajaxSuccess",[x,e])}function d(){e.complete&&e.complete.call(k,x,i);e.global&&f("ajaxComplete",[x,e]);e.global&&!--c.active&&c.event.trigger("ajaxStop")}function f(q,p){(e.context?c(e.context):c.event).trigger(q,p)}var e=c.extend(true,{},c.ajaxSettings,a),j,i,o,k=a&&a.context||e,n=e.type.toUpperCase();if(e.data&&e.processData&&typeof e.data!=="string")e.data=c.param(e.data,e.traditional);if(e.dataType==="jsonp"){if(n==="GET")N.test(e.url)||(e.url+=(ka.test(e.url)? -"&":"?")+(e.jsonp||"callback")+"=?");else if(!e.data||!N.test(e.data))e.data=(e.data?e.data+"&":"")+(e.jsonp||"callback")+"=?";e.dataType="json"}if(e.dataType==="json"&&(e.data&&N.test(e.data)||N.test(e.url))){j=e.jsonpCallback||"jsonp"+sb++;if(e.data)e.data=(e.data+"").replace(N,"="+j+"$1");e.url=e.url.replace(N,"="+j+"$1");e.dataType="script";A[j]=A[j]||function(q){o=q;b();d();A[j]=w;try{delete A[j]}catch(p){}z&&z.removeChild(C)}}if(e.dataType==="script"&&e.cache===null)e.cache=false;if(e.cache=== -false&&n==="GET"){var r=J(),u=e.url.replace(wb,"$1_="+r+"$2");e.url=u+(u===e.url?(ka.test(e.url)?"&":"?")+"_="+r:"")}if(e.data&&n==="GET")e.url+=(ka.test(e.url)?"&":"?")+e.data;e.global&&!c.active++&&c.event.trigger("ajaxStart");r=(r=xb.exec(e.url))&&(r[1]&&r[1]!==location.protocol||r[2]!==location.host);if(e.dataType==="script"&&n==="GET"&&r){var z=s.getElementsByTagName("head")[0]||s.documentElement,C=s.createElement("script");C.src=e.url;if(e.scriptCharset)C.charset=e.scriptCharset;if(!j){var B= -false;C.onload=C.onreadystatechange=function(){if(!B&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){B=true;b();d();C.onload=C.onreadystatechange=null;z&&C.parentNode&&z.removeChild(C)}}}z.insertBefore(C,z.firstChild);return w}var E=false,x=e.xhr();if(x){e.username?x.open(n,e.url,e.async,e.username,e.password):x.open(n,e.url,e.async);try{if(e.data||a&&a.contentType)x.setRequestHeader("Content-Type",e.contentType);if(e.ifModified){c.lastModified[e.url]&&x.setRequestHeader("If-Modified-Since", -c.lastModified[e.url]);c.etag[e.url]&&x.setRequestHeader("If-None-Match",c.etag[e.url])}r||x.setRequestHeader("X-Requested-With","XMLHttpRequest");x.setRequestHeader("Accept",e.dataType&&e.accepts[e.dataType]?e.accepts[e.dataType]+", */*":e.accepts._default)}catch(ga){}if(e.beforeSend&&e.beforeSend.call(k,x,e)===false){e.global&&!--c.active&&c.event.trigger("ajaxStop");x.abort();return false}e.global&&f("ajaxSend",[x,e]);var g=x.onreadystatechange=function(q){if(!x||x.readyState===0||q==="abort"){E|| -d();E=true;if(x)x.onreadystatechange=c.noop}else if(!E&&x&&(x.readyState===4||q==="timeout")){E=true;x.onreadystatechange=c.noop;i=q==="timeout"?"timeout":!c.httpSuccess(x)?"error":e.ifModified&&c.httpNotModified(x,e.url)?"notmodified":"success";var p;if(i==="success")try{o=c.httpData(x,e.dataType,e)}catch(v){i="parsererror";p=v}if(i==="success"||i==="notmodified")j||b();else c.handleError(e,x,i,p);d();q==="timeout"&&x.abort();if(e.async)x=null}};try{var h=x.abort;x.abort=function(){x&&h.call(x); -g("abort")}}catch(l){}e.async&&e.timeout>0&&setTimeout(function(){x&&!E&&g("timeout")},e.timeout);try{x.send(n==="POST"||n==="PUT"||n==="DELETE"?e.data:null)}catch(m){c.handleError(e,x,null,m);d()}e.async||g();return x}},handleError:function(a,b,d,f){if(a.error)a.error.call(a.context||a,b,d,f);if(a.global)(a.context?c(a.context):c.event).trigger("ajaxError",[b,a,f])},active:0,httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status=== -1223||a.status===0}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),f=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(f)c.etag[b]=f;return a.status===304||a.status===0},httpData:function(a,b,d){var f=a.getResponseHeader("content-type")||"",e=b==="xml"||!b&&f.indexOf("xml")>=0;a=e?a.responseXML:a.responseText;e&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b=== -"json"||!b&&f.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&f.indexOf("javascript")>=0)c.globalEval(a);return a},param:function(a,b){function d(i,o){if(c.isArray(o))c.each(o,function(k,n){b||/\[\]$/.test(i)?f(i,n):d(i+"["+(typeof n==="object"||c.isArray(n)?k:"")+"]",n)});else!b&&o!=null&&typeof o==="object"?c.each(o,function(k,n){d(i+"["+k+"]",n)}):f(i,o)}function f(i,o){o=c.isFunction(o)?o():o;e[e.length]=encodeURIComponent(i)+"="+encodeURIComponent(o)}var e=[];if(b===w)b=c.ajaxSettings.traditional; -if(c.isArray(a)||a.jquery)c.each(a,function(){f(this.name,this.value)});else for(var j in a)d(j,a[j]);return e.join("&").replace(yb,"+")}});var la={},Ab=/toggle|show|hide/,Bb=/^([+-]=)?([\d+-.]+)(.*)$/,W,va=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b){if(a||a===0)return this.animate(K("show",3),a,b);else{a=0;for(b=this.length;a").appendTo("body");f=e.css("display");if(f==="none")f="block";e.remove();la[d]=f}c.data(this[a],"olddisplay",f)}}a=0;for(b=this.length;a=0;f--)if(d[f].elem===this){b&&d[f](true);d.splice(f,1)}});b||this.dequeue();return this}});c.each({slideDown:K("show",1),slideUp:K("hide",1),slideToggle:K("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(a,b){c.fn[a]=function(d,f){return this.animate(b,d,f)}});c.extend({speed:function(a,b,d){var f=a&&typeof a==="object"?a:{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};f.duration=c.fx.off?0:typeof f.duration=== -"number"?f.duration:c.fx.speeds[f.duration]||c.fx.speeds._default;f.old=f.complete;f.complete=function(){f.queue!==false&&c(this).dequeue();c.isFunction(f.old)&&f.old.call(this)};return f},easing:{linear:function(a,b,d,f){return d+f*a},swing:function(a,b,d,f){return(-Math.cos(a*Math.PI)/2+0.5)*f+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]|| -c.fx.step._default)(this);if((this.prop==="height"||this.prop==="width")&&this.elem.style)this.elem.style.display="block"},cur:function(a){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];return(a=parseFloat(c.css(this.elem,this.prop,a)))&&a>-10000?a:parseFloat(c.curCSS(this.elem,this.prop))||0},custom:function(a,b,d){function f(j){return e.step(j)}this.startTime=J();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start; -this.pos=this.state=0;var e=this;f.elem=this.elem;if(f()&&c.timers.push(f)&&!W)W=setInterval(c.fx.tick,13)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;this.custom(this.cur(),0)},step:function(a){var b=J(),d=true;if(a||b>=this.options.duration+this.startTime){this.now= -this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var f in this.options.curAnim)if(this.options.curAnim[f]!==true)d=false;if(d){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;a=c.data(this.elem,"olddisplay");this.elem.style.display=a?a:this.options.display;if(c.css(this.elem,"display")==="none")this.elem.style.display="block"}this.options.hide&&c(this.elem).hide();if(this.options.hide||this.options.show)for(var e in this.options.curAnim)c.style(this.elem, -e,this.options.orig[e]);this.options.complete.call(this.elem)}return false}else{e=b-this.startTime;this.state=e/this.options.duration;a=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||a](this.state,e,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=c.timers,b=0;b
      "; -a.insertBefore(b,a.firstChild);d=b.firstChild;f=d.firstChild;e=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=f.offsetTop!==5;this.doesAddBorderForTableAndCells=e.offsetTop===5;f.style.position="fixed";f.style.top="20px";this.supportsFixedPosition=f.offsetTop===20||f.offsetTop===15;f.style.position=f.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=f.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==j;a.removeChild(b); -c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.curCSS(a,"marginTop",true))||0;d+=parseFloat(c.curCSS(a,"marginLeft",true))||0}return{top:b,left:d}},setOffset:function(a,b,d){if(/static/.test(c.curCSS(a,"position")))a.style.position="relative";var f=c(a),e=f.offset(),j=parseInt(c.curCSS(a,"top",true),10)||0,i=parseInt(c.curCSS(a,"left",true),10)||0;if(c.isFunction(b))b=b.call(a, -d,e);d={top:b.top-e.top+j,left:b.left-e.left+i};"using"in b?b.using.call(a,d):f.css(d)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),f=/^body|html$/i.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.curCSS(a,"marginTop",true))||0;d.left-=parseFloat(c.curCSS(a,"marginLeft",true))||0;f.top+=parseFloat(c.curCSS(b[0],"borderTopWidth",true))||0;f.left+=parseFloat(c.curCSS(b[0],"borderLeftWidth",true))||0;return{top:d.top- -f.top,left:d.left-f.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||s.body;a&&!/^body|html$/i.test(a.nodeName)&&c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(f){var e=this[0],j;if(!e)return null;if(f!==w)return this.each(function(){if(j=wa(this))j.scrollTo(!a?f:c(j).scrollLeft(),a?f:c(j).scrollTop());else this[d]=f});else return(j=wa(e))?"pageXOffset"in j?j[a?"pageYOffset": -"pageXOffset"]:c.support.boxModel&&j.document.documentElement[d]||j.document.body[d]:e[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();c.fn["inner"+b]=function(){return this[0]?c.css(this[0],d,false,"padding"):null};c.fn["outer"+b]=function(f){return this[0]?c.css(this[0],d,false,f?"margin":"border"):null};c.fn[d]=function(f){var e=this[0];if(!e)return f==null?null:this;if(c.isFunction(f))return this.each(function(j){var i=c(this);i[d](f.call(this,j,i[d]()))});return"scrollTo"in -e&&e.document?e.document.compatMode==="CSS1Compat"&&e.document.documentElement["client"+b]||e.document.body["client"+b]:e.nodeType===9?Math.max(e.documentElement["client"+b],e.body["scroll"+b],e.documentElement["scroll"+b],e.body["offset"+b],e.documentElement["offset"+b]):f===w?c.css(e,d):this.css(d,typeof f==="string"?f:f+"px")}});A.jQuery=A.$=c})(window); diff --git a/_static/minus.png b/_static/minus.png deleted file mode 100644 index da1c562..0000000 Binary files a/_static/minus.png and /dev/null differ diff --git a/_static/normaltnp.png b/_static/normaltnp.png deleted file mode 100644 index 0e73f90..0000000 Binary files a/_static/normaltnp.png and /dev/null differ diff --git a/_static/plus.png b/_static/plus.png deleted file mode 100644 index b3cb374..0000000 Binary files a/_static/plus.png and /dev/null differ diff --git a/_static/pygments.css b/_static/pygments.css deleted file mode 100644 index 1247614..0000000 --- a/_static/pygments.css +++ /dev/null @@ -1,70 +0,0 @@ -.highlight .hll { background-color: #ffffcc } -.highlight { background: #000000; } -.highlight .c { color: #B729D9; font-style: italic } /* Comment */ -.highlight .err { color: #a40000; border: 1px solid #ef2929 } /* Error */ -.highlight .g { color: #ffffff } /* Generic */ -.highlight .k { color: #FF8400 } /* Keyword */ -.highlight .l { color: #ffffff } /* Literal */ -.highlight .n { color: #ffffff } /* Name */ -.highlight .o { color: #E0882F } /* Operator */ -.highlight .x { color: #ffffff } /* Other */ -.highlight .p { color: #999999 } /* Punctuation */ -.highlight .cm { color: #B729D9; font-style: italic } /* Comment.Multiline */ -.highlight .cp { color: #a0a0a0 } /* Comment.Preproc */ -.highlight .c1 { color: #B729D9; font-style: italic } /* Comment.Single */ -.highlight .cs { color: #B729D9; font-style: italic } /* Comment.Special */ -.highlight .gd { color: #a40000 } /* Generic.Deleted */ -.highlight .ge { color: #ffffff; font-style: italic } /* Generic.Emph */ -.highlight .gr { color: #ef2929 } /* Generic.Error */ -.highlight .gh { color: #000080 } /* Generic.Heading */ -.highlight .gi { color: #00A000 } /* Generic.Inserted */ -.highlight .go { color: #808080 } /* Generic.Output */ -.highlight .gp { color: #745334 } /* Generic.Prompt */ -.highlight .gs { color: #ffffff; font-weight: bold } /* Generic.Strong */ -.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ -.highlight .gt { color: #a40000; font-weight: bold } /* Generic.Traceback */ -.highlight .kc { color: #004461 } /* Keyword.Constant */ -.highlight .kd { color: #004461 } /* Keyword.Declaration */ -.highlight .kn { color: #004461 } /* Keyword.Namespace */ -.highlight .kp { color: #004461 } /* Keyword.Pseudo */ -.highlight .kr { color: #004461 } /* Keyword.Reserved */ -.highlight .kt { color: #004461 } /* Keyword.Type */ -.highlight .ld { color: #ffffff } /* Literal.Date */ -.highlight .m { color: #1299DA } /* Literal.Number */ -.highlight .s { color: #56DB3A } /* Literal.String */ -.highlight .na { color: #ffffff } /* Name.Attribute */ -.highlight .nb { color: #ffffff } /* Name.Builtin */ -.highlight .nc { color: #ffffff } /* Name.Class */ -.highlight .no { color: #ffffff } /* Name.Constant */ -.highlight .nd { color: #808080 } /* Name.Decorator */ -.highlight .ni { color: #ce5c00 } /* Name.Entity */ -.highlight .ne { color: #cc0000 } /* Name.Exception */ -.highlight .nf { color: #ffffff } /* Name.Function */ -.highlight .nl { color: #f57900 } /* Name.Label */ -.highlight .nn { color: #ffffff } /* Name.Namespace */ -.highlight .nx { color: #ffffff } /* Name.Other */ -.highlight .py { color: #ffffff } /* Name.Property */ -.highlight .nt { color: #cccccc } /* Name.Tag */ -.highlight .nv { color: #ffffff } /* Name.Variable */ -.highlight .ow { color: #E0882F } /* Operator.Word */ -.highlight .w { color: #f8f8f8; text-decoration: underline } /* Text.Whitespace */ -.highlight .mf { color: #1299DA } /* Literal.Number.Float */ -.highlight .mh { color: #1299DA } /* Literal.Number.Hex */ -.highlight .mi { color: #1299DA } /* Literal.Number.Integer */ -.highlight .mo { color: #1299DA } /* Literal.Number.Oct */ -.highlight .sb { color: #56DB3A } /* Literal.String.Backtick */ -.highlight .sc { color: #56DB3A } /* Literal.String.Char */ -.highlight .sd { color: #B729D9; font-style: italic } /* Literal.String.Doc */ -.highlight .s2 { color: #56DB3A } /* Literal.String.Double */ -.highlight .se { color: #56DB3A } /* Literal.String.Escape */ -.highlight .sh { color: #56DB3A } /* Literal.String.Heredoc */ -.highlight .si { color: #56DB3A } /* Literal.String.Interpol */ -.highlight .sx { color: #56DB3A } /* Literal.String.Other */ -.highlight .sr { color: #56DB3A } /* Literal.String.Regex */ -.highlight .s1 { color: #56DB3A } /* Literal.String.Single */ -.highlight .ss { color: #56DB3A } /* Literal.String.Symbol */ -.highlight .bp { color: #3465a4 } /* Name.Builtin.Pseudo */ -.highlight .vc { color: #ffffff } /* Name.Variable.Class */ -.highlight .vg { color: #ffffff } /* Name.Variable.Global */ -.highlight .vi { color: #ffffff } /* Name.Variable.Instance */ -.highlight .il { color: #1299DA } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/_static/searchtools.js b/_static/searchtools.js deleted file mode 100644 index 663be4c..0000000 --- a/_static/searchtools.js +++ /dev/null @@ -1,560 +0,0 @@ -/* - * searchtools.js_t - * ~~~~~~~~~~~~~~~~ - * - * Sphinx JavaScript utilties for the full-text search. - * - * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. - * :license: BSD, see LICENSE for details. - * - */ - -/** - * helper function to return a node containing the - * search summary for a given text. keywords is a list - * of stemmed words, hlwords is the list of normal, unstemmed - * words. the first one is used to find the occurance, the - * latter for highlighting it. - */ - -jQuery.makeSearchSummary = function(text, keywords, hlwords) { - var textLower = text.toLowerCase(); - var start = 0; - $.each(keywords, function() { - var i = textLower.indexOf(this.toLowerCase()); - if (i > -1) - start = i; - }); - start = Math.max(start - 120, 0); - var excerpt = ((start > 0) ? '...' : '') + - $.trim(text.substr(start, 240)) + - ((start + 240 - text.length) ? '...' : ''); - var rv = $('
      ').text(excerpt); - $.each(hlwords, function() { - rv = rv.highlightText(this, 'highlighted'); - }); - return rv; -} - - -/** - * Porter Stemmer - */ -var Stemmer = function() { - - var step2list = { - ational: 'ate', - tional: 'tion', - enci: 'ence', - anci: 'ance', - izer: 'ize', - bli: 'ble', - alli: 'al', - entli: 'ent', - eli: 'e', - ousli: 'ous', - ization: 'ize', - ation: 'ate', - ator: 'ate', - alism: 'al', - iveness: 'ive', - fulness: 'ful', - ousness: 'ous', - aliti: 'al', - iviti: 'ive', - biliti: 'ble', - logi: 'log' - }; - - var step3list = { - icate: 'ic', - ative: '', - alize: 'al', - iciti: 'ic', - ical: 'ic', - ful: '', - ness: '' - }; - - var c = "[^aeiou]"; // consonant - var v = "[aeiouy]"; // vowel - var C = c + "[^aeiouy]*"; // consonant sequence - var V = v + "[aeiou]*"; // vowel sequence - - var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 - var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 - var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 - var s_v = "^(" + C + ")?" + v; // vowel in stem - - this.stemWord = function (w) { - var stem; - var suffix; - var firstch; - var origword = w; - - if (w.length < 3) - return w; - - var re; - var re2; - var re3; - var re4; - - firstch = w.substr(0,1); - if (firstch == "y") - w = firstch.toUpperCase() + w.substr(1); - - // Step 1a - re = /^(.+?)(ss|i)es$/; - re2 = /^(.+?)([^s])s$/; - - if (re.test(w)) - w = w.replace(re,"$1$2"); - else if (re2.test(w)) - w = w.replace(re2,"$1$2"); - - // Step 1b - re = /^(.+?)eed$/; - re2 = /^(.+?)(ed|ing)$/; - if (re.test(w)) { - var fp = re.exec(w); - re = new RegExp(mgr0); - if (re.test(fp[1])) { - re = /.$/; - w = w.replace(re,""); - } - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1]; - re2 = new RegExp(s_v); - if (re2.test(stem)) { - w = stem; - re2 = /(at|bl|iz)$/; - re3 = new RegExp("([^aeiouylsz])\\1$"); - re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re2.test(w)) - w = w + "e"; - else if (re3.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - else if (re4.test(w)) - w = w + "e"; - } - } - - // Step 1c - re = /^(.+?)y$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(s_v); - if (re.test(stem)) - w = stem + "i"; - } - - // Step 2 - re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step2list[suffix]; - } - - // Step 3 - re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - suffix = fp[2]; - re = new RegExp(mgr0); - if (re.test(stem)) - w = stem + step3list[suffix]; - } - - // Step 4 - re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; - re2 = /^(.+?)(s|t)(ion)$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - if (re.test(stem)) - w = stem; - } - else if (re2.test(w)) { - var fp = re2.exec(w); - stem = fp[1] + fp[2]; - re2 = new RegExp(mgr1); - if (re2.test(stem)) - w = stem; - } - - // Step 5 - re = /^(.+?)e$/; - if (re.test(w)) { - var fp = re.exec(w); - stem = fp[1]; - re = new RegExp(mgr1); - re2 = new RegExp(meq1); - re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); - if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) - w = stem; - } - re = /ll$/; - re2 = new RegExp(mgr1); - if (re.test(w) && re2.test(w)) { - re = /.$/; - w = w.replace(re,""); - } - - // and turn initial Y back to y - if (firstch == "y") - w = firstch.toLowerCase() + w.substr(1); - return w; - } -} - - -/** - * Search Module - */ -var Search = { - - _index : null, - _queued_query : null, - _pulse_status : -1, - - init : function() { - var params = $.getQueryParameters(); - if (params.q) { - var query = params.q[0]; - $('input[name="q"]')[0].value = query; - this.performSearch(query); - } - }, - - loadIndex : function(url) { - $.ajax({type: "GET", url: url, data: null, success: null, - dataType: "script", cache: true}); - }, - - setIndex : function(index) { - var q; - this._index = index; - if ((q = this._queued_query) !== null) { - this._queued_query = null; - Search.query(q); - } - }, - - hasIndex : function() { - return this._index !== null; - }, - - deferQuery : function(query) { - this._queued_query = query; - }, - - stopPulse : function() { - this._pulse_status = 0; - }, - - startPulse : function() { - if (this._pulse_status >= 0) - return; - function pulse() { - Search._pulse_status = (Search._pulse_status + 1) % 4; - var dotString = ''; - for (var i = 0; i < Search._pulse_status; i++) - dotString += '.'; - Search.dots.text(dotString); - if (Search._pulse_status > -1) - window.setTimeout(pulse, 500); - }; - pulse(); - }, - - /** - * perform a search for something - */ - performSearch : function(query) { - // create the required interface elements - this.out = $('#search-results'); - this.title = $('

      ' + _('Searching') + '

      ').appendTo(this.out); - this.dots = $('').appendTo(this.title); - this.status = $('

      ').appendTo(this.out); - this.output = $('